我试图通过一条获胜线来教授人工智能来识别井字游戏的模式。
不幸的是,它没有学会正确识别它们。我认为我将游戏表示/编码为向量的方式是错误的。
我选择一种方便人类(特别是我!)理解的方式:
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]]
我尝试的事情:
答案 0 :(得分:3)
我立即看到了你的问题:你的训练组太小了。您的问题空间由一个9维超立方体的512个角组成。你的训练将两个角落变成绿色,另外两个变成红色。你现在不知何故希望训练有素的模型能够正确地直视剩下的508个角落的正确着色。
没有通用的机器学习算法会直觉“这个板位置是否包含三个均匀间隔的'1'值的八个批准序列中的任何一个?”仅来自两个正面和两个负面的例子。首先,请注意您的训练数据没有排胜,不排除不是胜利的均匀间隔点,以及......以及空间中的许多其他模式。
我希望您在分类的每一侧都需要至少二十几个精心挑选的示例才能从模型中获得任何明显的性能。根据测试用例考虑:位1-2-3取胜,但3-4-5不胜; 3-5-7取胜,但1-3-5和2-4-6没有。
这是否会让您走向解决方案?
您可能尝试的一件事是生成随机向量,然后使用子例程对它们进行分类;将这些作为训练数据。为测试和验证数据做更多工作。
答案 1 :(得分:2)
Prune所说的很有道理。鉴于你的问题空间是138个终端板位置(并且不包括旋转和反射! - 见wiki),学习算法不太可能通过4项训练来充分调整权重和偏差数据集。我在我的一个“学习实验”中有过类似的经历,即使网络是在完整的数据集上训练的,因为这个集非常小,我最终必须在多个时期训练它,直到它能够输出体面的预测。
我认为在这里要记住的重要一点是,FF神经网络最终的作用是微调权重和偏差,以便尽可能地减少损失函数。损失越低,预测越接近预期输出,神经网络越好。这意味着越多的训练数据越好:)
我发现这个完整的training set用于tic tac toe,虽然它不是你提出的格式,但谁知道,也许它对你有用。我很想知道,该训练集的最小子集是什么,网络开始做出可靠的预测:P
答案 2 :(得分:2)
这是一个有趣的问题。我认为你真的希望你的系统能够识别" line",但正如其他人所说的那样,由于训练数据太少,系统难以概括。
一种不同且违反直觉的方法可能是从更大的板开始,比如10x10,而不是3x3,并在该空间中生成随机线并尝试让模型学习它们。在这种情况下,您可以探索卷积网络。这很像手写数字识别问题,我希望它能够轻松取得成功。一旦你的系统擅长识别线条,也许你可以创造性地以某种方式调整它并将其缩小以识别3x3情况下的细线。
(也就是说,我认为你可以通过给你的网络提供所有数据来学习这个特殊的3x3问题。它可能太小而无法推广,所以我甚至不会尝试这种情况。毕竟,在训练一个网来学习二进制XOR函数,我们只需要收取所有4个例子 - 完整的空间。你只能从3个例子中可靠地训练它。)
答案 3 :(得分:1)
我认为除了小数据集之外还存在一些问题,这些问题在于你对游戏状态的表示。在Tic-Tac-Toe中,在任何给定时间,电路板上的每个空间都有三种可能的状态:[X],[O]或空[]。此外,游戏中存在限制可能的板配置的条件。即,给定n [0]个正方形,不能再有n + 1 [X]个正方形。我建议回去思考如何表现游戏方块的三态性质。
答案 4 :(得分:1)
在玩了一段时间之后我觉得我学到了足够的东西来添加一个有价值的答案。
<强> 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
的目标数据表示更好地暗示你的大脑1
。
现在的问题是。我们如何建模我们的NN以获得与输入数据形状相同的目标形状?因为通常发生的是每个卷积层以较小的网格切割网格以查找某些特征,这些特征有效地改变了传递给下一层的数据的形状。
但是我们可以为卷积层设置border_mode='same'
,这些层基本上用零边框填充较小的网格,以便保留原始形状。
<强> 4。测量强>
测量模型的性能是进行正确调整的关键。特别是,我们希望了解NN对训练数据和验证数据的预测有多准确。拥有这些数字可以为我们提供正确的提示。
例如,如果我们的训练数据预测的准确性上升而我们的验证数据预测的准确性过时甚至下降,则意味着我们的NN 过度拟合。这意味着,它基本上记忆训练数据,但它实际上没有概括学习,以便它可以将它们应用于以前从未见过的数据(例如我们的验证数据)。
我们想做三件事:
A。)我们要在调用validation_data = (val_input_data, val_target_data)
时设置model.fit(...)
,以便keras可以告知我们每个纪元后验证数据的准确性。
B。)我们想在调用verbose=2
时设置model.fit(...)
,以便keras实际打印出每个纪元后的进度。
C。)我们想要metrics=['binary_accuracy']
设置model.compile(...)
,以便在每个纪元后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'))