为啥我的神经网络不能正确分类这些井字游戏模式?
Posted
技术标签:
【中文标题】为啥我的神经网络不能正确分类这些井字游戏模式?【英文标题】:Why does my NN not classify these tic tac toe pattern correctly?为什么我的神经网络不能正确分类这些井字游戏模式? 【发布时间】:2016-12-18 18:51:34 【问题描述】:我正在尝试教 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 种不同的获胜状态。我让 NN 看到其中的 5 个,这样我还有 3 个要测试,看看泛化是否有效。我现在给它喂了 15 个州,它不应该认为是胜利。
但是,我的验证结果似乎实际上变得更糟了。
[[ 1.06987642e-07]
[ 4.72647212e-02]
[ 1.97011139e-03]
[ 2.93282426e-07]]
我尝试过的事情:
-
从 sigmoid 更改为 softmax
添加更多神经元
添加更多层
以上所有内容的混合
【问题讨论】:
转自AI beta (access link)。 您是否尝试过在预测之前生成更大的样本库? 你在这里解决回归问题?似乎它可以作为一个分类问题,这意味着使用 softmax 输出和二元交叉熵损失。 @MatiasValdenegro 我也尝试过,但没有成功。我在想这真的是网格的大小太小了。我将尝试使用更大的网格,这样我就有更多的空间来绘制正样本,而无需将所有样本都用于证明泛化。 @Christoph 你是什么意思没有成功?你得到了什么准确度? 【参考方案1】:我立即看到了您的问题:您的训练集太小了。您的问题空间由 9 维超立方体的 512 个角组成。您的训练将其中两个角涂成绿色,另外两个涂成红色。您现在以某种方式期望经过训练的模型能够正确地感知剩余 508 个角的正确颜色。
没有任何通用机器学习算法会凭直觉判断“这个棋盘位置是否包含八个已批准的三个等距 '1' 值序列中的任何一个?”只有两个正面和两个负面的例子。一方面,请注意您的训练数据没有行胜,不排除不是胜的均匀间隔点,以及......空间中的许多其他模式。
我希望您在分类的每一侧都需要至少两打精心挑选的示例,以便从您的模型中获得任何可观的性能。从测试用例的角度考虑:1-2-3 位成功,但 3-4-5 没有; 3-5-7 获胜,但 1-3-5 和 2-4-6 没有。
这是否会让您找到解决方案?
您可能会尝试的一件事是生成随机向量,然后使用子程序对它们进行分类;将这些作为训练数据提供。为测试和验证数据做更多工作。
【讨论】:
非常感谢您的回答!你也许是对的。在考虑 3 x 3 网格时,我没有考虑过 512 种不同的状态。事实上,你能多解释一下它背后的数学原理吗?我知道它是 2^9。但是例如为什么不是 9^2 呢?换句话说,在这种情况下,我如何计算不同状态的数量?您可以将此添加到您的答案中吗?与此同时,我扩展了我的案例集,看看效果如何。 我根据我最近的实验更新了我的答案。 9 个独立元素中的每一个都有 2 个可能的值。那是 2*2*2 * 2*2*2 * 2*2*2,或 2^9 种可能性。这就是基本的组合学。我宁愿不将它添加到答案中,因为它是解决方案的“草丛”。添加对独立性和概率计算的解释超出了 Stack Overflow 的预期范围。【参考方案2】:我认为除了小数据集之外还有一些问题,这些问题在于你对游戏状态的表示。在井字游戏中,在任何给定时间,棋盘上的每个空间都有三种可能的状态:[X]、[O] 或空 []。此外,游戏中存在限制可能的棋盘配置的条件。即,给定 n [O] 个正方形,不能有超过 n+1 [X] 个正方形。我建议回过头来思考一下如何表示游戏方格的三态性质。
【讨论】:
如果我打算让整个游戏流程由 AI 控制,我认为你是对的。但我并不是想让 AI 执行游戏规则。我真的只是想识别一种模式。实际上,我们甚至不必将其称为井字游戏。所以在我的情况下,没有三个状态。这实际上只是在网格中的特定点具有 0 或 1 的二进制事物。 这里真正的问题是,只有 13 种可能的排列会导致在潜在的 138 种可能的棋盘状态中“获胜”。换句话说,应用深度学习策略的问题有点浅。为了正确训练算法,必须在训练集中包含几乎所有可能的正例。我建议使用三态系统来提供更深入的问题。 ——【参考方案3】:Prune 说的很有道理。鉴于您的问题空间是 138 个端子板位置(不包括旋转和反射!-参见wiki),仅通过在 4 条目数据集上进行训练,学习算法不太可能充分调整权重和偏差.我在我的一个“学习实验”中也有类似的经历,尽管网络是在完整的数据集上训练的,但由于数据集非常小,我最终不得不在多个 epoch 上对其进行训练,直到它能够输出体面的预测。
我认为这里要记住的重要一点是,训练 FF 神经网络最终要做的是微调权重和偏差,以便尽可能地最小化损失函数。损失越低,预测越接近预期输出,神经网络就越好。这意味着训练数据越多越好:)
我为井字游戏找到了完整的training set,虽然它不是您所设定的格式,但谁知道呢,也许它对您有用。我很想知道,该训练集的最小子集是什么,以便网络开始做出可靠的预测:P
【讨论】:
【参考方案4】:这是一个有趣的问题。我认为您确实希望您的系统能够识别“线条”,但正如其他人所说,训练数据如此之少,系统很难概括。
另一种违反直觉的方法可能是从 更大 板开始,例如 10x10,而不是 3x3,并在该空间中生成随机线并尝试让模型学习它们。在这种情况下,您可能会探索卷积网络。这很像手写数字识别问题,我希望它很容易成功。一旦您的系统擅长识别线条,也许您可以创造性地以某种方式对其进行调整并缩小以识别 3x3 案例中的细线条。
(也就是说,我认为你可以通过给你的网络提供所有数据来学习这个特殊的 3x3 问题。它可能太小而无法泛化,所以我什至不会在这种情况下尝试。毕竟,在训练一个net 来学习二进制 XOR 函数,我们只需要 4 个例子——完整的空间。你不能只从 3 个例子中可靠地训练它。)
【讨论】:
非常感谢您的回答。事实上,我正在尝试做的是识别线条。我还得出结论,我应该尝试使用更大的网格。只是还没有机会尝试。一旦我尝试过,我会回帖。但可以肯定的是,我将网格中的线表示为矢量的方式应该有效,对吧?在某些时候,我很困惑是否应该将每一行分组到它自己的向量中,以便我最终得到一个 3 维训练集......但我什至不知道如何让它工作。 好问题——这取决于。如果你想真正构建一个卷积网络,你需要二维结构。但是,如果您试图在 3x3 之类的小空间中记住答案,则不需要。即使在更大的网格情况下,我认为如果你给它足够的训练数据,你可以让它在常规网络(非卷积)上正常工作,但它不会自然地泛化。我认为如果您可以尝试这两种方法并在这里报告您的发现,那将是有启发性的!祝你好运。【参考方案5】:在玩了一段时间之后,我认为我学到了足够的知识来添加一个有价值的答案。
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]
...我们的 NN 怎么知道我们的意思实际上不是 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]
如果我们以 NN 知道我们在谈论大小为 6 x 6
的网格的方式表示数据,我们可以为我们的 NN 提供更多上下文。
[[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]]
这样,我们就可以为 NN 提供更多关于我们实际寻找的上下文。想一想,如果你必须理解这些样本,我敢肯定,与0
或@987654332 的目标数据表示相比,这个目标数据表示也会更好地暗示你的大脑@。
现在的问题是。我们如何对 NN 建模,使其具有与输入数据相同形状的目标形状?因为通常发生的情况是每个卷积层将网格分割成较小的网格以寻找某些特征,这些特征有效地改变了传递给下一层的数据的形状。
但是,我们可以为卷积层设置border_mode='same'
,它基本上用零边界填充较小的网格,从而保留原始形状。
4.测量
衡量我们模型的性能是做出正确调整的关键。特别是,我们想看看我们的 NN 对训练数据和验证数据的预测有多准确。这些数字给了我们正确的提示。
例如,如果我们的训练数据的预测准确度上升,而我们的验证数据的预测准确度过时甚至下降,这意味着我们的 NN 过度拟合。这意味着,它基本上记忆训练数据,但它实际上并没有概括学习,因此它可以将它们应用于以前从未见过的数据(例如我们的验证数据)。
我们想做三件事:
A.) 我们希望在调用 model.fit(...)
时设置 validation_data = (val_input_data, val_target_data)
,以便 keras 可以在每个 epoch 之后通知我们验证数据的准确性。
B.) 我们希望在调用 model.fit(...)
时设置 verbose=2
,以便 keras 在每个 epoch 之后实际打印出进度。
C.)我们希望在调用 model.compile(...)
时设置 metrics=['binary_accuracy']
,以便在每个 epoch 之后 keras 提供给我们的这些进度日志中实际包含正确的指标。
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'))
【讨论】:
以上是关于为啥我的神经网络不能正确分类这些井字游戏模式?的主要内容,如果未能解决你的问题,请参考以下文章