其他
【强基固本】深度学习与围棋:为围棋数据设计神经网络
“强基固本,行稳致远”,科学研究离不开理论基础,人工智能学科更是需要数学、物理、神经科学等基础学科提供有力支撑,为了紧扣时代脉搏,我们推出“强基固本”专栏,讲解AI领域的基础知识,为你的科研学习提供助力,夯实理论基础,提升原始创新能力,敬请关注。
地址:https://www.zhihu.com/people/epubit
构建一个深度学习应用,可以根据数据预测围棋的下一步动作。 介绍Keras深度学习框架。 了解卷积神经网络。 构建能够分析围棋空间数据的神经网络。
图6-1 如何使用深度学习预测围棋游戏中的下一步动作
6.1
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()
import importlib
def get_encoder_by_name(name, board_size): ⇽--- 可以根据编码器的名称来创建它的实例
if isinstance(board_size, int):
board_size = (board_size, board_size) ⇽--- 如果board_size是一个整数,则依据这个尺寸创建一个正方形棋盘
module = importlib.import_module('dlgo.encoders.' + name)
constructor = getattr(module, 'create') ⇽--- 每个编码器的实现类都必须提供一个“create”函数来创建新实例
return constructor(board_size)
import numpy as np
from dlgo.encoders.base import Encoder
from dlgo.goboard import Point
class OnePlaneEncoder(Encoder):
def __init__(self, board_size):
self.board_width, self.board_height = board_size
self.num_planes = 1
def name(self): ⇽--- 可以用名称“oneplane”来指代这个编码器
return 'oneplane'
def encode(self, game_state): ⇽--- 编码逻辑:对于棋盘上每一个交叉点,如果该点落下的是当前执子方的棋子,则在矩阵中填充1;如果是对手方的棋子,则填充−1;如果该点为空点,则填充0
board_matrix = np.zeros(self.shape())
next_player = game_state.next_player
for r in range(self.board_height):
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[0, 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
6.2
import argparse
import numpy as np
from dlgo.encoders import get_encoder_by_name
from dlgo import goboard_fast as goboard
from dlgo import mcts
from dlgo.utils import print_board, print_move
def generate_game(board_size, rounds, max_moves, temperature):
boards, moves = [], [] ⇽--- 在boards变量中存储编码后的棋盘状态;而moves变量用于存放编码后的落子动作
encoder = get_encoder_by_name('oneplane', board_size) ⇽--- 用给定的棋盘尺寸、按名称初始化一个OnePlaneEncoder实例
game = goboard.GameState.new_game(board_size) ⇽--- 一个尺寸为board_size的新棋局被实例化好了
bot = mcts.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)) ⇽--- 把编码的棋盘状态添加到boards数组中
move_one_hot = np.zeros(encoder.num_points())
move_one_hot[encoder.encode_point(move.point)] = 1
moves.append(move_one_hot) ⇽--- 把下一步动作进行独热编码,并添加到moves数组中
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)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--board-size', '-b', type=int, default=9)
parser.add_argument('--rounds', '-r', type=int, default=1000)
parser.add_argument('--temperature', '-t', type=float, default=0.8)
parser.add_argument('--max-moves', '-m', type=int, default=60,
help='Max moves per game.')
parser.add_argument('--num-games', '-n', type=int, default=10)
parser.add_argument('--board-out')
parser.add_argument('--move-out')
args = parser.parse_args() ⇽--- 这个应用允许用命令行参数进行自定义设置
xs = []
ys = []
for i in range(args.num_games):
print('Generating game %d/%d...' % (i + 1, args.num_games))
x, y = generate_game(args.board_size, args.rounds, args.max_moves,
➥ args.temperature) ⇽--- 根据给定棋局数量来生成相应的棋局数据
xs.append(x)
ys.append(y)
x = np.concatenate(xs) ⇽--- 当所有棋局都生成之后,为棋局添加相应的特征与标签
y = np.concatenate(ys)
np.save(args.board_out, x) ⇽--- 根据命令行参数所指定的选项,将特征与标签数据存放到不同的文件中
np.save(args.move_out, y)
if __name__ == '__main__':
main()
python generate_mcts_games.py -n 20 --board-out features.npy
➥ --move-out labels.npy
6.3
6.3.1 了解Keras的设计原理
6.3.2 安装Keras深度学习库
pip install tensorflow
pip install tensorflow-gpu
pip install Keras
6.3.3 热身运动:在Keras中运行一个熟悉的示例
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)
model = Sequential()
model.add(Dense(392, activation='sigmoid', input_shape=(784,)))
model.add(Dense(196, activation='sigmoid'))
model.add(Dense(10, activation='sigmoid'))
model.summary()
model.compile(loss='mean_squared_error',
optimizer='sgd',
metrics=['accuracy'])
model.fit(x_train, y_train,
batch_size=128,
epochs=20)
score = model.evaluate(x_test, y_test)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
6.3.4 使用Keras中的前馈神经网络进行动作预测
import numpy as np
from keras.models import Sequential
from keras.layers import Dense
np.random.seed(123) ⇽--- 设置一个固定的随机种子,以确保这个脚本可以严格重现
X = np.load('../generated_games/features-40k.npy') ⇽--- 将样本数据加载到NumPy数组中
Y = np.load('../generated_games/labels-40k.npy')
samples = X.shape[0]
board_size = 9 * 9
X = X.reshape(samples, board_size) ⇽--- 将输入数据由9×9的矩阵转换为维度为81的向量
Y = Y.reshape(samples, board_size)
train_samples = int(0.9 * samples) ⇽--- 预留数据集的10%作为测试集;其他的90%用于训练
X_train, X_test = X[:train_samples], X[train_samples:]
Y_train, Y_test = Y[:train_samples], Y[train_samples:]
model = Sequential()
model.add(Dense(1000, activation='sigmoid', input_shape=(board_size,)))
model.add(Dense(500, activation='sigmoid'))
model.add(Dense(board_size, activation='sigmoid'))
model.summary()
model.compile(loss='mean_squared_error',
optimizer='sgd',
metrics=['accuracy'])
model.fit(X_train, Y_train,
batch_size=64,
epochs=15,
verbose=1,
validation_data=(X_test, Y_test))
score = model.evaluate(X_test, Y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
__________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_1 (Dense) (None, 1000) 82000
__________________________________________________
dense_2 (Dense) (None, 500) 500500
__________________________________________________
dense_3 (Dense) (None, 81) 40581
=================================================================
Total params: 623,081
Trainable params: 623,081
Non-trainable params: 0
__________________________________________________
...
Test loss: 0.0129547887068
Test accuracy: 0.0236486486486
图6-4 用于测试模型的示例棋局。在这个棋局中,黑方可以通过在A点落子来吃掉白方两颗子,而白方也可以在B点落子来吃掉黑方两颗子。在这里,先落子的一方会占握巨大的优势
test_board = np.array([[
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, -1, 1, -1, 0, 0, 0, 0,
0, 1, -1, 1, -1, 0, 0, 0, 0,
0, 0, 1, -1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
]])
move_probs = model.predict(test_board)[0]
i = 0
for row in range(9):
row_formatted = []
for col in range(9):
row_formatted.append('{:.3f}'.format(move_probs[i]))
i += 1
print(' '.join(row_formatted))
0.037 0.037 0.038 0.037 0.040 0.038 0.039 0.038 0.036
0.036 0.040 0.040 0.043 0.043 0.041 0.042 0.039 0.037
0.039 0.042 0.034 0.046 0.042 0.044 0.039 0.041 0.038
0.039 0.041 0.044 0.046 0.046 0.044 0.042 0.041 0.038
0.042 0.044 0.047 0.041 0.045 0.042 0.045 0.042 0.040
0.038 0.042 0.045 0.045 0.045 0.042 0.045 0.041 0.039
0.036 0.040 0.037 0.045 0.042 0.045 0.037 0.040 0.037
0.039 0.040 0.041 0.041 0.043 0.043 0.041 0.038 0.037
0.036 0.037 0.038 0.037 0.040 0.039 0.037 0.039 0.037
这个预测模型使用的数据是由树搜索算法生成的,而这个算法的随机性很高。有时候MCTS引擎会产生很奇怪的动作,尤其在遥遥领先或远远落后的局面下。在第7章中我们将利用人工棋谱数据创建一个深度学习模型。当然,人类的策略也有出乎意料的时候,但他们至少不会下一些毫无道理的废棋。 本章使用的神经网络架构还有很大的改进空间。在多层感知机中,因为必须将二维的棋盘数据展平为一维向量,从而丢失了棋盘相关的全部空间信息,所以它其实并不太适合用来处理围棋棋盘数据。6.4节将介绍一种新型的神经网络,它可以更好地捕获围棋棋盘的结构。 到目前为止,在所有网络中我们都只用过sigmoid这一个激活函数。6.5节和6.6节将介绍两种新的激活函数,它们通常可以产生更好的结果。 到目前为止,我们只用过MSE这一个损失函数。它很直观,但并不太适合我们的使用场景。6.5节我们将采用为分类任务量身定制的损失函数。
围棋这个古老的策略游戏是AI研究的特别适用的案例。2016年,一个基于深度学习的系统战胜了围棋世界冠军,震惊了整个围棋界。不久之后,这个系统的升级版AlphaGo Zero利用深度强化学习掌握了围棋技艺,轻松击败了其原始版本。读者可以通过阅读本书来学习潜藏在它们背后的深度学习技术,并构建属于自己的围棋机器人!
本书通过教读者构建一个围棋机器人来介绍深度学习技术。随着阅读的深入,读者可以通过Python深度学习库Keras采用更复杂的训练方法和策略。读者可以欣赏自己的机器人掌握围棋技艺,并找出将学到的深度学习技术应用到其他广泛的场景中的方法。
“强基固本”历史文章
最受欢迎的算法之一:反向传播训练
神经网络结构下理解Logistic Regression &TF框架下构造Logistic实现Mnist分类
深入探究MMD距离
机器学习常用评价指标总览
信息量、熵、相对熵(KL散度)、交叉熵
神经网络常用求导
深度学习算法收敛性证明之拓展SGD
看了这篇文章你还不懂SVM你就来打我
卷积神经网络(CNN)反向传播算法
边框回归(Bounding Box Regression)详解
《CV中的多视图几何》——相机模型与标定
AI 框架基础技术之自动求导机制 (Autograd)
主成分分析(PCA)
深度学习算法收敛性证明
深度学习:数学知识基础
更多强基固本专栏文章,
请点击文章底部“阅读原文”查看
分享、点赞、在看,给个三连击呗!