机器学习-为什么我的神经网络不能正确分类这些井字游戏模式



我正在尝试教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]]

我尝试过的事情:

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

我立刻看出你的问题:你的训练集太小了。你的问题空间由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]]

这样我们就给了神经网络更多关于我们实际寻找的上下文。想想看,如果必须理解这些样本,我相信这个目标数据表示也会比只有01的目标数据表示更好地暗示你的大脑。

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

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

4。

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

例如,如果我们的训练数据的预测精度上升,而我们的验证数据的预测精度是陈旧的甚至下降,这意味着我们的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'],以便在这些进程日志中实际包含keras在每个epoch后给我们的正确度量。

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

最新更新