我正在尝试教一个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×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]]
我倾向于认为可能是游戏状态的表示方式有问题,但实际上我也不知道 😀
有谁能帮帮我吗?
这是我使用的完整代码
import numpy as npfrom keras.models import Sequentialfrom keras.layers.core import Activation, Densefrom keras.optimizers import SGDtraining_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
- 增加更多神经元
- 增加更多层
- 以上所有方法的混合
回答:
经过一段时间的尝试后,我觉得我学到了足够的知识,可以添加一个有价值的答案。
1. 网格大小
增加网格大小将使生成更多训练样本变得更加容易,同时仍为验证数据留出足够的空间,神经网络在训练过程中不会看到这些验证数据。我不是说不能在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
的目标数据表示方式更能提示你的大脑。
现在的问题是。我们如何构建我们的神经网络,使其目标形状与我们的输入数据形状相同?因为通常情况下,每个卷积层都会将网格切割成更小的网格来寻找某些特征,这实际上会改变传递到下一层的…