原理
主要是看了这一篇Get a taste of reinforcement learning — implement a tic tac toe agent,里面作者提出了大概的训练思路,我基本没有参照他的具体实现,但是思路肯定是差不多的。而且训练结果是我几乎下不过这个AI,比这篇的结果要好很多(不过这种明显算法可以解决的问题用AI也没什么意思)。
实现
核心在于训练一个模型,这个模型用来学习当前的棋局时,我们的当前玩家该怎么处理。强化学习不需要训练用例,而是我们自己产生训练用例。这是一个有点循环依赖的问题。因为模型显然自己不知道该往什么方向发展,通常的机器学习训练中,我们的依靠外在的数据让模型去调整自己的参数。所幸,我们得服从一套游戏规则,这个游戏规则可以帮助我们产生(越来越好的)训练用例。
训练过程
我们的训练流程是这样的。圈圈用符号,叉叉用符号表示。先手圈圈的模型称为,后手圈圈的模型称为,他们需要学习一个映射,映射到当前奖励。代表模型对当前各种走法(圈叉棋中至多9种)的价值的判断。即
- 初始状态时我们随机初始化 和 。
- 然后我们利用当前参数的,在盘面上进行搏斗,即产生(一个batch的)训练用例。为了产生更广泛的训练样例,我们采用算法来产生随机的训练用例(之后定义算法)。
- 利用训练用例调整和的参数,返回 2。
算法
- 随机产生 盘合法棋局,这些棋局满足游戏规则,而且当前不存在胜者。
- 对于每个棋局 ,如果是轮到选手 , 则认为,否则 。
- 对于每个棋局 ,按照游戏规则产生用例。对当前中每个空格,填入当前选手的符号。之后按照当前选手,通过 或 预测奖励,选择奖励最大的位置进行走子(我们会解决当前模型预测奖励最大的位置不是空格的问题*)。直到当前棋局 分出胜负(或平局)。对每个空格处理完后,我们可以对当前棋局按照如下规则进行估算奖励,注意这里的是棋局的当前玩家,可以当作一个9元向量,是上文所说的当前走位置的奖励,假设我们把棋局编号到的整数。:
- 如果 已经被占了,那么 定义为 0。
- 如果 在上述过程第一步"填入当前选手的符号"后,经过 步胜利了,定义 ,这里的 是我们自己定义的函数,它可以是常函数
lambda V : 1
也可以是某个关于step
的减函数,来促使我们的模型尽早胜利。我们可以定义最优的 。 - 如果 2 中,经过 步后平局或者没有胜利,定义 。这个 F 也可以自己来定义。根据我的经验,F是的增函数会促使模型学会堵子。
- 如此一来我们就有了分别面向模型和训练集和。
理解
模型训练的核心显然在我们为什么用算法能产生对和有优化作用的训练用例集和。
正如训练过程(1)所示,一开始和的参数是随机的,所以我们左右互搏产生的测试用例实际是质量很低的,因为双方并没有建立起如实反映的映射。但是我们有强制的一步算法的(3),我们对每个空格都进行了试验,因此至少获得了一部分 的真实 值,例如
当前选手是,那么。之后我们对 (假设位置编号从左到右从上到下,从0开始。)便得到了可靠的目标值。然后我们在训练过程 3. 中利用优化算法,便使得我们的模型对 有了更加准确的映射。如此而来,由于 变得更加准确了,下一轮便能得到更加高质量的训练用例。例如当
轮到走子,对于试验填入位置 4 ,那么由于我们的模型对 有较高的价值判断(从而导致此次试验输),因此将学习避免走位置 。这样我们的模型就学习到了以外的正确映射。
当然,这是非常不mathematic的,但是可以作为一个肤浅的理解。
实验
- 代码 https://github.com/hanayashiki/TicTacToe (python 3.6, keras, tensorflow)。
- 棋局用 表示,其中最后一个维度是 表示填入 ”O", 表示是空白, 表示填入 “X”,采用独热编码是为了尽量减少假设。
- 表现最好的模型定义在
model.py
中的ModelThreeDensesReluReluAdamMasked
。它的结构是shape=(27,)
的输入层- 两层输出为 256 维的全连接层,激活函数为
relu
- 全连接输出层为 9 维,激活函数为
relu
- mask 层生成 9 维 0-1 mask 对应当前能走的点位 *,因此不能走的地方总是 0,我们的算法不需要学习不符合规则的情况。mask 层按元素与 3 层相连接。 同时由于
relu
可能有全 0 的输出,我们接着在 mask 层对应的位置加上一个小量 0.0001,避免最终预测结果全 0。 - 算法 中的 和 函数进行瞎几把定义,我这里保证平局或者输产生的 取值在 ,随步数增加;胜利产生的 在 之间。直觉地促使如果输,那么要尽量拖延(学会堵子);如果赢,尽量用较少的步数(学会将死)。
- 训练时每次产生 64 个测试用例。loss function 使用
MSE
。
结果
到了见证结果的激动人心的时间!
经过 3000 轮训练,的loss
达到了 0.002995
,的loss
达到了 0.00329
,这说明机器左右互搏的预测已经相当准确了,那么和人下一盘呢?
New game!
>> Select your role: 'X' or 'O'. o
_ _ _
_ _ _
_ _ _
key >>s // 下方是我的走法
_ _ _
_ O _
_ _ _
_ _ X
_ O _
_ _ _
key >>c
_ _ X
_ O _
_ _ O
X _ X
_ O _
_ _ O
key >>w
X O X
_ O _
_ _ O
X O X
_ O _
_ X O
key >>a
X O X
O O _
_ X O
X O X
O O X
_ X O
key >>z
X O X
O O X
O X O
It's a tie !
可见电脑后手有一定的智能,和我打平了。它的先手更有出乎意料的操作。
>> Select your role: 'X' or 'O'. X
_ _ _
_ _ _
_ _ _
_ _ O // 电脑没有像我们人一样选择中间位置
_ _ _
_ _ _
key >>s
_ _ O
_ X _
_ _ _
_ _ O
_ X _
O _ _
key >>c
_ _ O
_ X _
O _ X
O _ O
_ X _ // 将死了随便下的我
O _ X
key >>x
O _ O
_ X _
O X X
O O O
_ X _
O X X
Player O wins !
极限操作方可平局:
New game!
>> Select your role: 'X' or 'O'. x
_ _ _
_ _ _
_ _ _
_ _ O
_ _ _
_ _ _
key >>s
_ _ O
_ X _
_ _ _
_ _ O
_ X _
O _ _
key >>a
_ _ O
X X _
O _ _
_ _ O
X X O
O _ _
key >>c
_ _ O
X X O
O _ X
O _ O
X X O
O _ X
key >>w
O X O
X X O
O _ X
O X O
X X O
O O X
It's a tie !
看看机器左右互搏的情况(最后是平局)
_ _ O
_ _ _
_ _ _
_ _ O
_ X _
_ _ _
_ _ O
_ X _
O _ _
_ _ O
_ X _
O X _
_ O O
_ X _
O X _
X O O
_ X _
O X _
X O O
_ X _
O X O
X O O
_ X X
O X O
X O O
O X X
O X O
总之这个训练结果,非常有意思!机器仅仅从规则学习到的操作,出乎人类玩家意料!