使用神经网络和深度学习构造围棋智能算法:实现棋盘落子编码

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tyler_download/article/details/89015067

在前面章节中,我们引入不少算法和数据结构用以支持围棋机器人实现。由于围棋的步骤组合太多,几乎没有确定性的算法能在合理的时间内给出好的走法。从本节开始,我们将像AlphGo那样引入深度学习技术,通过训练神经网络的方式打造出一个强大的围棋机器人,使得这个机器人的围棋技能能够超越人类智慧之上。

在如下课程中对深度学习技术进行了非常详细详尽的讲解,在这里我们不再对深度学习技术进行启蒙似讲解,而是认为你已经比较好的掌握和理解了深度学习技术:

深度学习课程,请点击当前链接

我们看看神经网络如何应用到围棋落子算法上,首先我们先获得当前棋盘:

屏幕快照 2019-04-02 下午4.47.06.png

然后我们将棋盘转换为二维向量:

11.gif

上面编码的二维向量对弈网络输入,训练神经网络时,我们必须将输入数据和标签配对,网络对输入进行计算后输出,如果输出结果与输入数据对应的标签不一致,那么网络就可以自我修改,上面二维数组对应的标签就是当前落子方的下一步走法。

此时落子方只有右上角一处走法,于是我们将走法编码为一维向量:[0,0,1,0,0,0,0,0,0]。这个一维向量就是上面二维向量对弈的标签,如果网络得出的一维向量与编码的向量不同,它就可以根据它输出的结果与我们编码向量的差异进行自我调整

然后我们将上面二维向量输入到一个训练好的神经网络:

神经网络.jpeg

网络识别输入的二维数组后,在输出端给出一个向量,向量中的分量指明下一步走法。对神经网络而言,它需要配备对应的数据结构,那就是向量。因此我们将使用代码将棋盘转换成一个二维向量,其中黑棋所在的位置用1表示,白棋所在位置用-1表示,空余点用0表示。

这种方法并不是很有利于网络做出精准预测,但在后续的章节中,我们会不断改进数据结构和网络形态,从而使得网络的预测越来越精准。神经网络还有一个问题是,它需要大量的训练数据,此时我们可以运用前面章节创造随机机器人的方法,让两个机器人随机博弈,把博弈过程产生的棋盘和结果当做数据对网络进行训练。

在前面我们尝试使用蒙特卡洛树搜索时,我们让机器人模拟对弈500盘,然后根据胜率统计来决定下一步走法。这种方法很管用,特别是模拟的次数越多,最后得出的胜率越准确,于是选择出的走法就越好。但它有一大缺陷是,速度非常慢,模拟博弈的次数越多,程序运行得就越慢,我们上节使用500次模拟博弈时,已经是几分钟才能算出一步走法来。

俗话说长痛不如短痛。本节开始使用的神经网络方法就是以短痛的方式解决长痛。我们会集中一大块时间执行蒙特卡洛树搜索算法,让两个机器人随机对弈,同时增加一次落子时模拟对弈的次数,例如把500次提升到1000次。显然这会大大增加运行时长。

但我们把所有运行结果存储起来,然后使用这些数据训练神经网络,让神经网络识别出蒙特卡洛树搜索的精髓。当网络掌握其规律后,我们以后可以直接使用网络快速得出符合蒙特卡洛树搜索的落子算法,而不需要每一步都要执行500次模拟对弈,从而能大大加快落子速度。由此可见,强大的神经网络,在背地里有着艰苦卓绝的训练过程。

由于对棋盘的编码方式有多种,因此我们先抽象出编码接口,以后不同的编码方式可以统一使用相同接口:

class Encoder:
  def  name(self):
    raise NotImplementedError()
    
  def  encode(self, game_state):
    raise NotImplementedError()
    
  def  encode_point(self, point):
    #对棋盘给定坐标点的编码
    raise NotImplementedError()
    
  def  decode_point_index(self, index):
    #将索引转换为棋盘坐标点
    raise NotImplementedError()
    
  def  num_points(self):
    #返回棋盘大小
    raise NotImplementedError()
    
  def  shape(self):
    #获得棋盘规格
    raise NotImplementedError()

接下来我们看一种特定编码,我们将当前要落子方的棋子用1来编码,对方用-1来编码,也就是说1对应的不是特定棋子,谁是当前落子方,谁在棋盘上的棋子就对应1,代码如下:

class OnePlainEncoder(Encoder):
  def  __init__(self, board_size):
    self.board_width = self.board_height = board_size
    '''
    num_planes表示编码的层数,例如我们可以把棋盘分成两层,每层对应一个二维数组,其中第一层只显示黑棋在棋盘上的位置,
    第二层只显示白棋在棋盘上的对应位置,第三层显示棋盘的特殊情况,例如当前形成ko的棋子摆放情况等
    '''
    self.num_planes = 1
    
    def  name(self):
      #返回当前编码名称
      return 'oneplane'
    
    def  encode(self, game_state):
      #使用一个二维数组对应棋盘
      board_matrix = np.zeros(self.shape())
      next_player = game_state.next_player
      #将当前落子方对应的棋子设置成1
      for r in range(self.board_width):
        for c in range(self.board_width):
          p = Point(row = r + 1, col = c + 1)
          go_string = game_state.board.get_go_string(p)
          if go_string is None:
            continue
          if go_string.color == next_player:
            #棋盘是三维数组,最高维表示棋盘‘层数’
            board_matrix[0, r, c] = 1
          else:
            board_matrix[1, r, c] = -1
            
      return board_matrix
    
    def  encode_point(self, point):
      #将坐标点转换为整数索引
      return self.board_width * (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 = col + 1)
    
    def  num_points(self):
      return self.board_width * self.board_height
    
    def  shape(self):
      return self.num_planes , self.board_height, self.board_width

有了编码器后,我们需要数据以便让编码器进行编码。使用深度学习技术,要想训练出精确的神经网络,需要足量的训练数据。我们在前面章节中实现的机器人随机对弈,对弈的棋盘和结果就可以用来作为训练网络的数据,使用机器人对弈产生的数据来训练网络时AlphaGoZero的精髓所在。

接下来我们生成两个使用蒙特卡洛落子算法的机器人,然后让他们相互对弈,同时使用上面编码类对机器人落子的每一步进行记录:

def  generate_game(board_size, rounds, max_moves, temperature):
  boards, moves = [], []
  encoder = OnePlainEncoder(board_size)
  game = GameState.new_game(board_size)
  bot = MCTSAgent(rounds, temperature)
  
  num_moves = 0
  while not game.is_over():
    print_board(game.board)
    move = bot.select_move(game)
    if move.is_play:
      boards.append(encoder.encode(game))
      #将每一步落子转换成一位数组
      move_one_hot = np.zeros(encoder.num_points())
      move_one_hot[encoder.encode_point(move.point)] = 1
      moves.append(move_one_hot)
      
    
    print_move(game.next_player, move)
    game = game.apply_move(move)
    num_moves += 1
    if num_moves > max_moves:
      break
    
  return  np.array(boards), np.array(moves)   

最后我们运行上面代码,启动一个机器人随机落子,然后将其编码,并把编码结果打印出来:

import numpy as np

def  main():
  board_size = 9
  rounds = 1000
  temperature = 0.8
  max_moves = 60
  num_games = 10
  xs = []
  ys = []
  
  for i in range(num_games):
    print('Generating game %d/%d...' %(i + 1, num_games))
    x, y = generate_game(board_size, rounds, max_moves, temperature)
    xs.append(x)
    ys.append(y)
    
  x = np.concatenate(xs)
  y = np.concatenate(ys)
  
  #代码运行很慢,我们要把运行结果存储起来以便后面用于训练网络
  np.save('content/gdrive/My Drive/board-out', x)
  np.save('content/gdrive/My Drive/move-out', y)
  
  print('finishing generating')

main()

上面过程非常缓慢,好在我们只需要运行一次,将结果存储成文件后,后面我们可以用来训练网络。我们会训练好的结果作为视频附件上传,大家可以直接使用训练好的数据而不必消耗自己的时间和CPU资源进行艰苦的运算。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这里写图片描述

更多内容,请点击进入csdn学院

猜你喜欢

转载自blog.csdn.net/tyler_download/article/details/89015067