为什么我的神经网络不能正确分类这些井字游戏模式?

Why does my NN not classify these tic tac toe pattern correctly?

我正在尝试教 AI 识别带获胜线的井字游戏模式。

不幸的是,它没有学会正确识别它们。我认为我 representing/encoding 将游戏转换为向量的方式是错误的。

我选择一种人(尤其是我!)容易理解的方式:

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 的板。前三项代表第一行,接下来的三项代表第二行,依此类推。换行符应该很明显。然后目标数据将前两个游戏状态映射到 "no wins",将最后两个游戏状态映射到 "wins".

然后我想创建一些略有不同的验证数据,看看它是否可以概括。

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")

显然,最后两个游戏状态应该 "wins" 而前两个不应该。

我试着调整神经元的数量和学习率,但无论我尝试什么,我的输出看起来都很差,例如

[[ 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]]

我尝试过的事情:

  1. sigmoid 变为 softmax
  2. 添加更多神经元
  3. 添加更多图层
  4. 以上所有的混合

我立刻明白了你的问题:你的训练集太小了。你的问题 space 由一个 9 维超立方体的 512 个角组成。您的训练将两个角染成绿色,另外两个染成红色。您现在以某种方式期望经过训练的模型能够正确地凭直觉为剩余的 508 个角正确着色。

没有通用的机器学习算法会仅从两个正例和两个负例中直觉 "does this board position contain any of the eight approved sequences of three evenly-spaced '1' values?" 的模式。一方面,请注意您的训练数据没有行获胜,不排除非获胜的均匀 spaced 点,以及......好吧,space 中的许多其他模式。

我希望您在分类的每一侧至少需要 两打精心挑选的示例才能获得 any 可观的评价模型的性能。从测试用例的角度思考:位 1-2-3 获胜,但 3-4-5 没有; 3-5-7 获胜,但 1-3-5 和 2-4-6 不获胜。

这会让您找到解决方案吗?

你可能会尝试的一件事是生成随机向量,然后用子程序对它们进行分类;将这些作为训练数据提供。为测试和验证数据做更多。

我认为这里存在超出小数据集的问题,这些问题在于您对游戏状态的表示。在 Tic-Tac-Toe 中,棋盘上的每个 space 在任何给定时间都有三种可能的状态:[X]、[O] 或空 []。此外,游戏中存在限制可能的棋盘配置的条件。即,给定 n [O] 个方块,不能超过 n+1 [X] 个方块。我建议回过头来思考如何表示游戏方块的三态性质。

Prune说的很有道理。鉴于您的问题 space 是 138 个接线板位置(不包括旋转和反射!- 参见 wiki),学习算法不太可能仅通过训练来充分调整权重和偏差一个 4 项数据集。我在我的一个 "learning experiments" 中有过类似的经历,尽管网络是在完整的数据集上训练的,但由于数据集非常小,我最终不得不在多个时期训练它直到它能够输出不错的预测。

我认为这里要记住的重要一点是,训练 FF 神经网络最终所做的是微调权重和偏差,以便尽可能地减少损失函数。损失越低,预测越接近预期输出,神经网络就越好。这意味着训练数据越多越好:)

我找到了这个完整的 training set 井字游戏,虽然它不是您最初使用的格式,但谁知道呢,也许它会对您有用。我很想知道,该训练集的最小子集是多少,网络才能开始做出可靠的预测:P

这是一个有趣的问题。我认为您真的希望您的系统能够识别 "lines",但正如其他人所说,训练数据太少,系统很难概括。

一种不同且违反直觉的方法可能是从 更大的 板开始,比如 10x10,而不是 3x3,然后在 space 中生成随机线并尝试让模型学习它们。在这种情况下,您可能会探索卷积网络。这很像手写数字识别问题,我希望它很容易成功。一旦您的系统擅长识别线条,也许您可​​以以某种方式创造性地调整它并将其缩小以识别 3x3 案例中的细线。

(也就是说,我认为你可以通过给你的网络所有数据来学习这个特殊的 3x3 问题。它可能太小而无法泛化,所以我什至不会在这种情况下尝试。毕竟,在训练一个网络来学习二元异或函数,我们只给它所有的 4 个例子——完整​​的 space。你不能仅仅从 3 个例子可靠地训练它。)

玩了一会儿之后,我想我学到了足够多的东西来添加一个有价值的答案。

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]

如果我们以 NN 知道我们正在谈论大小为 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]]

这样我们就可以为 NN 提供更多关于我们实际正在寻找的上下文。想一想,如果 you 必须理解这些样本,我相信与 [=21] 的目标数据表示相比,这个目标数据表示也会更好地暗示你的大脑=] 或 1.

现在问题来了。我们如何对神经网络建模,使其具有与输入数据形状相同的目标形状?因为通常发生的情况是每个卷积层将网格分割成更小的网格以寻找某些特征,这些特征有效地改变了我们传递到下一层的数据的形状。

但是我们可以为我们的卷积层设置 border_mode='same',它基本上用零边界填充较小的网格,以便保留原始形状。

4.测量

衡量我们模型的性能是做出正确调整的关键。特别是,我们希望了解我们的神经网络对训练数据和验证数据的预测有多准确。这些数字给了我们正确的提示。

例如,如果我们训练数据的预测准确性上升,而验证数据的预测准确性陈旧甚至下降,这意味着我们的神经网络 过度拟合.这意味着,它基本上 记住了 训练数据,但它实际上并没有概括学习,因此它可以将它们应用于以前从未见过的数据(例如我们的验证数据)。

我们要做三件事:

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'] 以在 keras 在每个纪元后给我们的这些进度日志中实际包含正确的指标。

5.数据生成

最后但并非最不重要的一点,正如大多数其他答案所暗示的那样。数据越多越好。我最终编写了一个数据生成器,为我生成训练数据和目标数据样本。我的验证数据是手工挑选的,我确保生成器 不会 生成与我的验证数据相同的训练数据。我最终用 1000 个样本进行了训练。

最终模型

这是我最终使用的模型。它使用 Dropout64 的特征大小。也就是说,您可以使用这些数字,并且会发现有很多模型都可以很好地工作。

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'))