前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >改变棋盘编码方式,增强围棋机器人的智能肌肉

改变棋盘编码方式,增强围棋机器人的智能肌肉

作者头像
望月从良
发布2019-05-17 14:00:43
5430
发布2019-05-17 14:00:43
举报
文章被收录于专栏:Coding迪斯尼Coding迪斯尼

在上一节,我们把棋盘编码成二维数组后输入到网络,对网络进行训练。我们编码棋盘的方式很简单,把当前落子方在棋盘上棋子摆放的位置设置成1,对方在棋盘上落子的位置设置成-1,然后落子方根据当前棋盘情况实现的落子,也被编码成二维数组,所有元素都是0,只有落子位置设置成1,由此我们就形成了一条训练记录,落子前的棋盘编码是训练数据,落子方式对应的二维数组是训练标签。

这种编码方式特点是简单,但缺点是忽略了很多关键信息。例如该编码方式无法防止落子出现”ko”的情况,也就是这一次落子将棋盘带回到它以前出现过的情况。因此前面使用的OnePlainEncoder,也就是对棋盘进行单层编码导致很多关键信息丢失,这样就无法有效的训练强大的神经网络。

本节我们要做一个棋盘编码增强版。它会对棋盘进行七层编码,第一层使用1表示落子方只有一个自由点的棋子。第2层编码落子方拥有2个自由点的棋子;第3层编码拥有3个或以上自由点的落子方棋子。第4到6层使用相同的办法编码对方棋子;第7层用1编码那些一旦落子就形成”ko”的点。

这种编码方式相比原来要复杂很多,但是它的优势是记录了很多重要信息。例如只有一个自由点的棋子很重要,因为它很容易被对手吃掉。如果棋盘编码能把这些关键信息标明,网络在训练时就会有意识的去保护这些关键点,于是训练出来的网络就会变得更强大。

我们看看相应编码方式的实现:

代码语言:javascript
复制
class  SevenPlaneEncoder(Encoder):
  def  __init__(self, board_size):
    self.board_width, self.board_height = board_size
    self.num_planes = 7
  def  name(self):
    return "sevenplane"
  def  encode(self, game_state):
    board_tensor = np.zeros(self.shape())
    base_plane = {gampe_state.next_player: 0,
                 game_state.next_player.other: 3}
    for row in range(self.board_height):
      for col in range(self.board_width):
        p = Point(row = row + 1, col = col + 1)
        go_string = game_state.board.get_go_string(p)
        if go_string is None:
          if game_state.does_move_violate_ko(game_state.next_player,
                                            Move.play(p)):
            #将当前落子方会形成ko的位置设置为1
            board_tensor[6][row][col] = 1
        else:
          '''
          第二层编码拥有2个自由点的棋子,第3层编码拥有3个或以上自由点的棋子
          '''
          liberty_plane = min(3, go_string.num_liberties) - 1
          #落子方编码在第2,3层,对方编码在第5,6层
          liberty_plane += base_plane[go_string.color]
          board_tensor[liberty_plane][row][col] = 1
    return board_tensor

以上是新编码方式的主要内容,其他接口实现如下:

代码语言:javascript
复制
def  encode_point(self, point):
    return self.board_with * (point.row - 1) + (point.col - 1)

  def  decode_point_index(self, index):
    row = index // self.board_width
    col = index % self.board_width
    return Point(row = row + 1, col + 1)

  def  num_points(self):
    return self.board_width * self.board_height
  def shape(self):
    return self.num_planes, self.board_height, self.width

  def  create(board_size):
    return SevenPlaneEncoder(board_size)

这种编码方式还可以进一步扩展成11层,前4层用于编码落子方棋子拥有1,2,3以及大于等于4个自由点的棋子;接下来4层同样编码对方棋子;如果当前是黑棋落子那么低9层设置为1,要不然就把第10层设置为1;最后一层依然用于编码会造成”ko”的位置。

接下来还有一个改进时对网络训练方法的改进。上一节我们使用SGD方式调整网络参数,这种计算方法存在一些问题,它的计算方法是,假设当前网络某个参数的值是W,它对该参数求偏导数后得到的值为r,那么参数修改的方法是W = W + l*r,其中l表示学习率。这种方法在某些特殊情况下很难收敛到最小值,一种改进方法叫Momentum,假设当前要调整的参数值为W,它上一次调整的数值是U,同时对该参数求偏导数得到的结果为r,那么参数的调整由以下公式计算: W = W - l*(b* U + (1-b)*r) 其中0<=b<=1。这里主要是数值运算上的考量,对数学不感兴趣的朋友可以忽略细节。它的思想是,如果这次对参数求偏导数所得的值与上一次求偏导数所得的值符号相同,那么我们在偏导数指向的方向上加到改变的步伐。

如果本次求偏导数结果与上一次偏导数不同,这意味着上一次改变的步伐太大,使得网络一下子越过了最低点,于是这次我们修改时,要让步伐变小一些,无论是增大步伐还是减少步伐,我们都要结合上一次改变步伐也就是U的值进行运算。

还有一种改进是不断调整学习率,学习率用于控制一次改进步伐的大小,如下图:

上图左边是学习率比较大的情况,当我们一开始训练网络时,网络参数离最优值还很远,此时我们让学习率大一些,这样参数的该变量也打一些。当运行一段时间后,参数离最优值很近,如果此时该变量过大,那么它就会在一次改变中一下子越过最优值点,所以此时我们需要减少改变量,这样才能保证参数在调整过程中不会一下子越过最优点。

于是我们在训练时让学习率随着时间的增长以一定的比率缩小,这个缩小得比率叫decay,对应到代码中如下:

代码语言:javascript
复制
#momentum 对应上面公式中的b
#lr 对应学习率
#decay 对应学习率缩小比率
sgd = SGD(lr = 0.1, momentum = 0.9, decay = 0.01)

上面改进可以可以让网络在训练时得到效果更好,但还是存在一些问题没有解决。这里我们使用全局学习率,也就是网络所有参数在改进时都使用同一个学习率,这显然是不合理的,对于一个含有几十上百万参数的网络,不同的参数会从输入数据中抓取不同规律。例如有些参数可能专注于抓取落子在棋盘中间位置的规律,有些参数可能抓取落子在边缘位置的规律,绝大多数情况下落子都会在中间,这意味着抓取中间位置规律的参数得到充足的修正机会,而落子在边缘的参数很少得到修正的机会。

一种想法是不同参数采用不同学习率,例如识别中间位置变化的参数由于修正机会多,因此学习率可以减小,识别边缘位置变化的参数,由于修正机会少,因此它的学习率可以增大一些,这种方法称为Adagrad。该方法会给每个参数不同的学习率,如果训练数据中只有一小部分数据含有一些独特规律时,该方法能捕捉到这些微小规律,围棋数据恰好具备这种特点,绝大多数落子都在中间,极少数落子在边缘,但这些少数情况往往产生重要影响因此不得不认真考虑,它对应公式如下:

它表示第t个参数的该变量。左边x表示改变的幅度,右边分子表示学习率,分母是根号下对过往调整幅度平方加总,g(t)表示参数当前求偏导数后的数值。从中我们看出,如果参数以往调整的次数越多,那么本次调整的幅度就越小,如果以往调整的次数越少,那么它本次调整的幅度就越大,代码使用Adagrad的方式是:

代码语言:javascript
复制
adagrad = Adagrad()

还有一种对Adagrad算法的改进叫Adadelta,细节我们在此不做深入研究。这里强调的都是数值算法,对数学不感兴趣的同学可以忽略。

接下来我们设计网络进行训练。神经网络设计一个有意思的现象是,尽管它包含很多公式,推导,但是整个设计过程其实是一种“手艺活”,你无法推导出什么学习率有效,网络中有几层卷积层更好,这些问题都是凭直觉”试“出来,这种实践方法与神经网络的理论化描述反差相当大,这有点类似于得诺贝尔医学奖的科学家在开药方时要通过算卦来做决定!

这里我们不再重复给出网络训练的代码,只要把上一节代码中的OnePlainEncoder改成本节的SevenPlainEncoder即可,同时可以根据描述,把网络训练方法例如SGD改成Adagrad等方法,相关处理留给大家自己尝试。

新书上架,请诸位朋友多多支持:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coding迪斯尼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档