我正在尝试教AI识别带有获胜线的井字棋模式。
不幸的是,它没有学会正确地识别它们。我认为我将游戏呈现/编码为向量的方法是错误的。
我选择了一种人类(尤其是我!)容易理解的方式:
training_data = np.array([[0,0,0,
0,0,0,
0,0,0],
[0,0,1,
0,1,0,
0,0,1],
[0,0,1,
0,1,0,
1,0,0],
[0,1,0,
0,1,0,
0,1,0]], "float32")
target_data = np.array([[0],[0],[1],[1]], "float32")
使用一个长度为9的数组来表示一个3 x 3的棋盘。前三个项目表示第一行,后三个项目表示第二行,依此类推。换行应该让它更明显。目标数据然后将前两个游戏状态映射为"没有赢",最后两个游戏状态映射为"赢"。
然后我想创建一些稍微不同的验证数据,看看它是否泛化。
validation_data = np.array([[0,0,0,
0,0,0,
0,0,0],
[1,0,0,
0,1,0,
1,0,0],
[1,0,0,
0,1,0,
0,0,1],
[0,0,1,
0,0,1,
0,0,1]], "float32")
显然,最后两个游戏状态应该是"获胜",而前两个不应该。
我尝试了神经元的数量和学习率,但无论我怎么尝试,我的输出看起来都很差,例如
[[ 0.01207292]
[ 0.98913926]
[ 0.00925775]
[ 0.00577191]]
我倾向于认为我表现游戏状态的方式可能是错误的,但实际上我不知道:D
谁能帮我一下吗?这是我使用的全部代码
import numpy as np
from keras.models import Sequential
from keras.layers.core import Activation, Dense
from keras.optimizers import SGD
training_data = np.array([[0,0,0,
0,0,0,
0,0,0],
[0,0,1,
0,1,0,
0,0,1],
[0,0,1,
0,1,0,
1,0,0],
[0,1,0,
0,1,0,
0,1,0]], "float32")
target_data = np.array([[0],[0],[1],[1]], "float32")
validation_data = np.array([[0,0,0,
0,0,0,
0,0,0],
[1,0,0,
0,1,0,
1,0,0],
[1,0,0,
0,1,0,
0,0,1],
[0,0,1,
0,0,1,
0,0,1]], "float32")
model = Sequential()
model.add(Dense(2, input_dim=9, activation='sigmoid'))
model.add(Dense(1, activation='sigmoid'))
sgd = SGD(lr=0.1, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='mean_squared_error', optimizer=sgd)
history = model.fit(training_data, target_data, nb_epoch=10000, batch_size=4, verbose=0)
print(model.predict(validation_data))
我试着听从建议,使用更多的训练数据,但到目前为止还没有成功。
我的训练集现在是这样的
training_data = np.array([[0,0,0,
0,0,0,
0,0,0],
[0,0,1,
0,0,0,
1,0,0],
[0,0,1,
0,1,0,
0,0,1],
[1,0,1,
0,1,0,
0,0,0],
[0,0,0,
0,1,0,
1,0,1],
[1,0,0,
0,0,0,
0,0,0],
[0,0,0,
0,0,0,
1,0,0],
[0,0,0,
0,1,0,
0,0,1],
[1,0,1,
0,0,0,
0,0,0],
[0,0,0,
0,0,0,
0,0,1],
[1,1,0,
0,0,0,
0,0,0],
[0,0,0,
1,0,0,
1,0,0],
[0,0,0,
1,1,0,
0,0,0],
[0,0,0,
0,0,1,
0,0,1],
[0,0,0,
0,0,0,
0,1,1],
[1,0,0,
1,0,0,
1,0,0],
[1,1,1,
0,0,0,
0,0,0],
[0,0,0,
0,0,0,
1,1,1],
[0,0,1,
0,1,0,
1,0,0],
[0,1,0,
0,1,0,
0,1,0]], "float32")
target_data = np.array([[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[1],[1],[1],[1],[1]], "float32")
考虑到我只把1
的模式算作获胜,那么我表示数据的方式只有8种不同的获胜状态。我让神经网络看了5个,所以我还有3个测试,看看泛化是否有效。我现在给它提供了15个不应该考虑获胜的州。
然而,我的验证结果似乎实际上变得更糟了。
[[ 1.06987642e-07]
[ 4.72647212e-02]
[ 1.97011139e-03]
[ 2.93282426e-07]]
我尝试过的事情:
- 从sigmoid更改为softmax
- 添加更多神经元
- 添加更多图层
- 以上所有内容的混合
我立刻看出你的问题:你的训练集太小了。你的问题空间由512个角组成一个9维超立方体。你的训练将两个角涂成绿色,另外两个角涂成红色。现在,您可以期望经过训练的模型能够正确地直觉出剩余508个角的正确颜色。
任何通用机器学习算法都无法从两个正反例中直觉出"这个棋盘位置是否包含八个被认可的三个均匀间隔的'1'值序列中的任何一个?"的模式。首先,请注意你的训练数据没有连续获胜,不排除均匀间隔的非获胜点,而且……还有很多其他的图案
我希望您在分类的每一边至少需要两打精心选择的示例,才能从您的模型中获得任何可观的性能。从测试用例的角度来思考:1-2-3位是成功的,但3-4-5位不是;3-5-7会赢,但1-3-5和2-4-6不会。
这会让你找到解决方案吗?
你可以尝试的一件事是生成随机向量,然后用子程序对它们进行分类;将这些作为训练数据。为测试和验证数据做更多的工作。
Prune说的很有道理。考虑到你的问题空间是138个终端板位置(不包括旋转和反射)!(参见wiki)仅通过在4个条目的数据集上进行训练,学习算法不太可能充分调整权重和偏差。我在我的一个"学习实验"中也有类似的经历,在那里,即使网络是在完整的数据集上训练的,因为这个数据集非常小,我最终不得不在多个时代训练它,直到它能够输出像样的预测。
我认为这里重要的是要记住,训练FF神经网络最终要做的是微调权重和偏差,以便损失函数尽可能地最小化。损失越低,预测结果就越接近预期输出,神经网络就越好。这意味着训练数据越多越好:)
我找到了这个完整的井字游戏训练集,虽然它不是你设定的格式,但谁知道呢,也许它会对你有用。我很想知道,这个训练集的最小子集是什么,才能让网络开始做出可靠的预测:P
这是一个有趣的问题。我认为你真的希望你的系统能够识别"线条",但正如其他人所说,由于训练数据太少,系统很难进行泛化。
一种不同的、违反直觉的方法可能是从更大的板开始,比如10x10,而不是3x3,并在该空间中生成随机的线条,并尝试让模型学习它们。在这种情况下,你可以探索卷积网络。这很像手写数字识别问题,我希望它很容易成功。一旦你的系统擅长识别线条,也许你可以以某种方式创造性地调整它,并将其缩小以识别3x3情况下的微小线条。
也就是说,我认为你可以通过给你的网络所有的数据来学习这个特殊的3x3问题。对于泛化来说,它可能太小了,所以在这种情况下我甚至不会尝试。毕竟,在训练一个网络学习二进制异或函数时,我们只需要给它所有的4个例子——完整的空间。我认为这里除了小数据集之外还有问题,这些问题在于你对游戏状态的表示。在三字棋中,棋盘上的每个空间在任何给定时间都有三种可能的状态:[X], [O]或空[]。此外,游戏中还存在限制可能的棋盘配置的条件。也就是说,给定n [O]个正方形,不可能有超过n+1 [X]个正方形。我建议大家回过头来思考一下如何表现游戏的三种状态——方块。
在玩了一段时间后,我想我学到了足够的东西来添加一个有价值的答案。
1。网格大小
增加网格的大小将更容易为训练提供更多的样本,同时仍然为NN在训练期间不会看到的验证数据留下足够的空间。我并不是说3 x 3
网格不能这样做,但增加网格的大小肯定会有所帮助。我最终将大小增加到6 x 6
,并寻找最小长度为四个连接点的直线。
2。数据表示
用一维向量表示数据不是最优的。
想想看。当我们想要在网格中表示以下行时…
[0,1,0,0,0,0,
0,1,0,0,0,0,
0,1,0,0,0,0,
0,1,0,0,0,0,
0,0,0,0,0,0,
0,0,0,0,0,0]
…我们的神经网络如何知道我们的意思实际上不是3 x 12
大小的网格中的这个模式?
[0,1,0,0,0,0,0,1,0,0,0,0,
0,1,0,0,0,0,0,1,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0]
如果我们以一种神经网络知道我们在谈论一个大小为6 x 6
的网格的方式来表示数据,我们可以为我们的神经网络提供更多的上下文。
[[0,1,0,0,0,0],
[0,1,0,0,0,0],
[0,1,0,0,0,0],
[0,1,0,0,0,0],
[0,0,0,0,0,0],
[0,0,0,0,0,0]]
好消息是我们可以在keras中使用Convolution2D
层来做到这一点。
3。目标数据表示
这不仅有助于重新思考我们的训练数据的表示,我们还可以调整我们的目标数据的表示。最初我想用一个二元问题:这个网格是否包含一条直线?1或0.
事实证明,我们可以做得更好,通过使用相同的形状为我们的目标数据,我们使用我们的输入数据,并重新定义我们的问题为:这个像素是否属于一条直线?因此,考虑到我们有一个像这样的输入样本:
[[0,1,1,0,0,1],
[0,1,0,1,0,0],
[0,1,0,0,1,0],
[0,1,0,0,0,1],
[0,0,0,1,0,0],
[1,0,1,0,0,0]]
我们的目标输出看起来像这样。
[[0,1,1,0,0,0],
[0,1,0,1,0,0],
[0,1,0,0,1,0],
[0,1,0,0,0,1],
[0,0,0,0,0,0],
[0,0,0,0,0,0]]
这样我们就给了神经网络更多关于我们实际寻找的上下文。想想看,如果你必须理解这些样本,我相信这个目标数据表示也会比只有0
或1
的目标数据表示更好地暗示你的大脑。
现在问题是。我们如何对我们的神经网络建模,使其具有与输入数据相同的目标形状?因为通常情况下,每个卷积层将网格分割成更小的网格,以寻找某些特征,这些特征有效地改变了传递给下一层的数据的形状。
然而,我们可以为卷积层设置border_mode='same'
,这实际上是用零边界填充较小的网格,以便保留原始形状。
4。
衡量我们模型的性能是做出正确调整的关键。特别是,我们想看看我们的神经网络对训练数据和验证数据的预测有多准确。这些数字给了我们正确的提示。
例如,如果我们的训练数据的预测精度上升,而我们的验证数据的预测精度是陈旧的甚至下降,这意味着我们的NN是过拟合。这意味着,它基本上记住了训练数据,但它实际上并没有泛化学习,因此它可以将它们应用到以前没有见过的数据(例如我们的验证数据)。
我们想做三件事:
a)我们希望在调用model.fit(...)
时设置validation_data = (val_input_data, val_target_data)
,以便keras可以在每个epoch之后通知我们验证数据的准确性。
model.fit(...)
时设置verbose=2
,以便keras实际上在每个epoch之后打印进度。
C.)当我们调用model.compile(...)
时,我们希望设置metrics=['binary_accuracy']
,以便在这些进程日志中实际包含keras在每个epoch后给我们的正确度量。
5。数据生成
最后但并非最不重要的,正如大多数其他答案所表明的那样。数据越多越好。我最终编写了一个数据生成器,它为我生成训练数据和目标数据样本。我的验证数据是手工挑选的,我确保生成器不会生成与我的验证数据相同的训练数据。我最终训练了1000个样本。
最终模型
这是我最终使用的模型。它使用Dropout
和64
的特征大小。也就是说,你可以使用这些数字,并会注意到有很多模型都可以很好地工作。
model = Sequential()
model.add(Convolution2D(64, 3, 3, input_shape=(1, 6, 6), activation='relu', border_mode='same'))
model.add(Dropout(0.25))
model.add(Convolution2D(64, 3, 3, activation='relu', border_mode='same'))
model.add(Dropout(0.25))
model.add(Convolution2D(64, 3, 3, activation='relu', border_mode='same'))
model.add(Dropout(0.25))
model.add(Convolution2D(1, 1, 1, activation='sigmoid', border_mode='same'))