在任何领域内,高手的一个特点是,它能在一瞬间对局面的好坏有一个比较准确的判断。例如对于围棋高手而言,假设当前棋盘有5处落子位置,像李世石和柯洁这样的高手,他们能在很快的时间内衡量这几个位置的好坏,而且衡量的准确度远比普通棋手高。
他们是怎么做到的呢?难道他们在头脑中按照5个位置模拟着把棋下一遍吗?就算他们再聪明,一来他们也不可能在头脑里进行模拟,人脑无法容许这种复杂度。其次时间不允许,落子时间是有限的,如果不在给定时间内做出决定就当弃权,因此高手也无法在短时间内通过计算或模拟的方式评判落子的好坏,他们能做出判断的依据更多的是依靠经验和直觉,而人的感觉很难量化,但是如果不能量化的话,我们就不能通过算法实现出来,而增强式学习就找到了量化这种模糊概念的方法。
事实上有一种衡量直觉好坏的方法,那就是三个臭皮匠顶个诸葛亮。要实现柯洁或李世石的直觉,我们不一定要让他们亲自上,而是找来一百个水平比他们第一级的棋手,然后让棋手依靠直觉对那5个位置进行投票,获得票数多的位置,它是好棋的概率就打,投票的人数越多,准确性越高,因此我们找几百个次一级的棋手,通过投票统计得出的判断和可能与柯洁,李世石的判断差不多。
我们将使用类似的方法在计算机上实现很难量化的”直觉“。在增强式学习里,有一种数值算法叫Q-Learning,它能让机器人对当前棋盘落子方式的好坏进行“预估”。假设有一个函数,你将当前棋盘输入,它会返回当前每个落子位置获胜的概率,那么下棋就变成了一种机械运动,我们只要把棋盘输入函数,然后将棋子放在赢率最高的位置即可,这种函数叫激活值函数,接下来我们要看看如何实现这样的函数。
这个激活值函数也称为Q函数:Q(state, action),它对应两个输入参数,一个是当前环境状态,一个是你想要采取的行动,然后它计算出在当前状态下,你采取给定行动能得到的回报,对于围棋而言,状态就是当前棋盘布局,行动就是在哪里落子,如下图:
屏幕快照 2019-07-09 下午5.29.40.png在上图左边的棋盘对应函数参数state,右边给出的位置对应参数action,最后函数给出一个值0.72,表示在当前棋盘下,在给定方块处落子,对应的赢率是72%。如果假设我们已经有了这种功能的函数,我们是不是就可以机械的按照函数的返回值去落子呢?不是!这里我们引入一种算法叫ε-贪婪算法。我要引入某种不确定性,假设当前有5个落子位置,Q函数分别给出了5个位置的赢率,按理我们应该选择赢率最高的位置落子,但由于函数给出的只是概率,它的判断有可能不是很准确,于是我们就可以冒险尝试一下其他落子是不是能得到更好结果。
于是我们先设定一个ε值,也叫决策值,比如将它设定为0.05,然后在区间[0,1]内产生一个随机数,如果结果大于ε,那么我们就按照Q函数给定的结果落子,如果小于ε,那么我们就随机落子,尝试一下其他位置看看有没有可能获得更好结果,因此基本思路如下:
屏幕快照 2019-07-09 下午5.40.53.png对应的逻辑代码,我们实现如下:
def select_action(state, epsilon):
#state对应棋盘,epsilon就是预先给定的决策值
possible_actions = get_possible_actions(state)
if random.random() < epsilon:
#如果随机值小于决策值,我们在所有落子步骤中随便选一个
return random.choice(possible_actions)
best_action = None
best_value = MIN_VALUE
for action in get_possible_actions(state):
action_value = Q(state, action) #用Q函数判断每个落子位置的赢率
if action_value > best_value:
best_action = action
best_value = action_value
return best_action #采用赢率最大的落子方式
ε-贪婪算法跟我们人类技能的学习很像,我们学习新技能时一开始万事开头难,入门后你的水平会快速增长,然后你会进入瓶颈期,发现自己怎么努力都很难获得实质性进步。这时候你需要迫使你走出舒适圈,进入学习圈,这时候你需要采用一些以前没有用过的方法,从事一些以前没有尝试过的方式,只有引入新信息,新变量,你才有可能从瓶颈期走出,上面代码中,随机采用某个落子步骤就是相同道理。
在训练机器人时,我们一开始会把ε的值设定得比较大,让它尽可能的随机探索,根据探索结果改进Q函数的准确度,当达到一定层度后,我们会逐步减少ε的值,于是机器人更多的依赖Q函数给出的结果来决定落子位置。
现在你或许会疑惑,这个Q函数是怎么构造的。其实它是机器人Agent对象附带的一个神经网络,跟上一节类似,只不过网络训练的原理与前面讲过的基于政策的梯度下降法不同而已。我们在训练Q函数对应的网络时,方法如下图:
屏幕快照 2019-07-09 下午6.01.57.png神经网络将接收两种输入,一种是棋盘编码,另一种是落子位置,它对应的标签是0或1,如果给定对应棋盘以及输入位置,这种走法最终获得胜利,那么输入数据对应的标签就是1,如果最终获得失败,训练数据对应的标签就是-1.这里的网络与我们以前看到的结构不一样,以前的网络时一个输入层,最后是一个输出层,现在这个网络结构是两个输入层,最后是一个输出层。
因此我们为了能跟以前网络结构相同,我们需要把这两种输入合并成一种输入然后在输入给网络。由于这两种输入都对应两个维度相同的矩阵,因此我们可以把两个矩阵合成一个矩阵,keras框架给我们提供了方便的操作方式:
屏幕快照 2019-07-09 下午6.10.11.png接下来我们看看要开发的网络结构,我们将用若干个卷积层来识别棋盘,然后将识别结果连接到两个全连接层,最后一个全连接层含有19*19个神经元,每个神经元输出结果对应棋盘上每个位置的赢率,如下图:
屏幕快照 2019-07-09 下午6.13.23.png由于我们使用-1表示失败,用1表示胜利,因此我们需要在区间[-1, 1]之间取一个值来衡量落子好坏。于是在最后一个全连接层输出结果时,我们需要使用的激活函数叫tanh(x),它能接收任何输入数值,最后输出的结果一定在[-1,1]之间,因此我们需要把上图的输出转换成一个数值,于是我们在上面网络结构的基础之上再添加一个输出层,把上图输出的二维矩阵转换为单个数值,于是最终网络形态如下图:
屏幕快照 2019-07-09 下午6.21.30.png
左边的分支用来识别棋盘,右边分支输入落子位置,最后输出一个值表示给定落子位置的好坏。根据上图给出的结构,我们实现网络代码如下:
from keras.models import Model
from keras.layers import Conv2D, Dense, Flatten, Input
from keras.layers import ZeroPadding2D, concatenate
board_input = Input(shape=encoder.shape(), name = 'board_input') #输入棋盘编码
action_input = Input(shape = (encoder.num_points,) , 'action_input') #输入指定的落子位置
conv1a = ZeroPadding2D((2, 2))(board_input) #扩展输入数据维度保证卷积层识别后输出的结果不会变小
conv1b = Conv2D(64, (5,5), activation = 'relu')(conv1a)
conv2a = ZeroPadding2D((1, 1))(conv1b)
conv2b = Conv2D(64, (3, 3), activation = 'relu')(conv2a)
flat = Flatten(conv2b)
processed_board = Dense(512)(flat)
board_and_action = concatenate([action_input, processed_board]) #将落子位置与卷积层识别后的结果合并
hidden_layer = Dense(256, activation = 'relu')(board_and_action)
value_output = Dense(1, activation = 'tanh')(hidden_layer)
model = Model(inputs = [board_input, action_input], outputs = value_output)
下一节我们将深入研究如何使用上面网络来训练机器人。
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这里写图片描述 新书上架,请诸位朋友多多支持: WechatIMG1.jpeg
网友评论