翻译Deep Learning and the Game of Go(8)第六章:给围棋数据设计神经网络(上)

本章包括(篇幅关系,本文章介绍前两个,后两个放在下个文章)

  • 构建一个深度学习应用程序从数据出发来预测下一步的围棋落子点
  • 引入Keras深度学习框架
  • 了解卷积神经网络
  • 构建分析空间围棋数据的神经网络

在前一章中,您看到了神经网络在行动中的基本原理,并从零开始实现了前馈网络。在本章中,你将把注意力转回到围棋游戏,并可以解决如何使用深度学习技术来预测在给定任何围棋盘面的情况下的下一步落子的问题。特别是,您将使用在第4章中运用的树搜索技术来生成围棋数据,并可以使用这个数据来训练神经网络。图6.1给出了您将在本章中构建的应用程序的概述。

 如图6.1所示,要利用到你上一章的神经网络工作知识,您必须首先解决几个关键步骤:

  1. 在第3章中,你把重点放在实现围棋棋盘上的数据结构,第4章使用这些结构进行树搜索。但在第5章中,你看到了神经网络需要数值的输入;对于你实现的前馈网络,向量是需要的。
  2. 要将围棋棋盘局面转换为向量输入到神经网络中,您必须创建一个Encoder类来完成这项工作。图中6.1,我们勾画了一个简单的编码器,这会在6.1节中实现;围棋棋盘会被编码为一个棋盘大小的矩阵,白色的棋子表示为-1,黑色的棋子表示为1,空点表示为0。其矩阵可以被展开化为向量,就像你在前一章中对MNIST数据集所做的那样。虽然这种表示有点太简单了,不能为落子预测提供出色的结果,但这是朝着正确方向迈出的第一步。在第7章中,您将看到更复杂和更有用的方法来编码棋盘局面。
  3. 要训练一个神经网络来预测下一步落子点,你必须先得到要输入到神经网络的数据。在6.2节中,您将使用第4章的技术来生成游戏记录。您将编码每个棋盘的局面,如刚才讨论的,这将作为您的特征,并将每个局面的下一步存储为标签。
  4. 虽然你在第5章中实现的一个神经网络是有用的,但同样重要的是我们需要获得更多的速度和可靠性,因此我们引入一个更成熟的深度学习库。为此,在第6.3节介绍了Keras,一个用Python编写的流行深度学习库。您将使用Keras来构建预测落子的网络
  5. 在这一点上,你可能会想,为什么你完全放弃了围棋棋盘的空间结构,把编码的棋盘展平到一个向量。在第6.4节中,你将了解到一个新的层类型称为卷积层,它更适合您的用例。您将使用这些层来构建一个名为卷积神经网络的新体系结构。
  6. 本章即将结束时,您将了解更多的现代深度学习的关键概念,这些概念将进一步提高落子预测的准确性,例如在第6.5节或第6.5节中使用Softmax有效地预测概率。在6.6节中建立更深层次的神经网络,并具有一个有趣的激活函数,称为校正线性单元(ReLU)。

6.1.给棋盘局面进行编码

在第三章中,您构建了一个Python类库,该库表示围棋游戏中的所有实体:Player、Board、GameState等。现在您想将机器学习应用于解决围棋中的一些问题。但是神经网络无法不能在像GameState类这样的高级对象上工作;它们只能处理数学对象,比如向量和矩阵。因此在本节中,我们将创建一个Encoder类,该类将你的本地游戏对象转换为数学形式。在本章的其余部分,您可以将该数学形式提供给你的机器学习工具。

建立围棋落子点预测的深度学习模型的第一步是加载可以输入神经网络的数据。通过为围棋棋盘定义一个简单的编码器来做到这一点,这在上图中已经介绍了。编码器是以合适的方式转换您在第3章中实现的围棋棋盘的一种方法。你所学到的神经网络,多层感知器,会作为输入的向量,但在6.4节中,您将看到另一个基于高维数据的网络体系结构。图6.2给出了如何定义这样一个编码器的想法。

 其核心思想就是,编码器必须知道如何编码一个完整的围棋游戏状态。特别是,它应该定义如何在围棋板上对单点进行编码。有时反过来看也很有趣:如果你用网络预测下一步落子,该落子将被编码,您需要将其转换回围棋实际的落子。这个操作叫做解码,对于应用预测落子很重要

考虑到这些,您现在可以定义Encoder类,这是您将在本章和下一章中将要创建的编码器的接口。您将在dlgo中定义一个名为Encoder的新模块。您将使用一个空的__init__.py进行初始化,并将文件base.py放入其中。然后,您将在下面的内容放入到文件中。

# 编码器接口
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()

编码器的定义很简单,但是我们希望在base.py中增加一个更方便的特性:一个函数,用它的名字创建一个编码器,一个字符串,而不是创建一个对象。你可以使用下面的get_encoder_by_name函数进行此操作

# 用来导入模块的库
import importlib


# 通过名字返回相应的编码器
def get_encoder_by_name(name, board_size):
    if isinstance(board_size, int):
        board_size = (board_size, board_size)
    # 获取名字对应的模块
    module = importlib.import_module('dlgo.Encoder.' + name)
    # 得到该模块用来初始化的create方法
    constructor = getattr(module, 'create')
    return constructor(board_size)

现在您知道了编码器是什么,以及如何构建一个编码器,让我们实现第一个编码器:一种颜色表示为1,另一种颜色表示为-1,空点表示为0。为了得到准确的预测,模型也需要知道现在该轮到谁下了。因此,不要用1表示黑棋和-1表示白棋,你将使用1表示下一回合落子方,并用-1表示对手。你将把这种编码称为OnePlaneEncoder,因为您将要把围棋棋盘编码成与棋盘相同大小的单个矩阵或平面。在第7章中,您将看到具有更多特征平面的编码器;例如,您将实现这样一个编码器,一个平面放每个黑棋和白棋,一个平面用来识别劫。现在,您将在oneplane.py中实现的最简单的one-plane编码思想。下面的列表显示了第一部分。

class OnePlaneEncoder(Encoder):

    def __init__(self,board_size):
        self.board_width = board_size
        self.board_height = board_size
        self.num_planes = 1  # 平面数量

    # 允许您支持日志记录或保存你的模型中正在使用的编码器的名称
    def name(self):
        return"oneplane"

    # 把棋盘棋盘转化成数字数据,编码成三维矩阵
    def encode(self,game_state):
        # 棋盘对应的三维矩阵
        board_matrix = np.zeros(self.shape())
        current_player = game_state.current_player
        for r in range(self.board_height):
            for c in range(self.board_width):
                point = Point(row=r,col=c)
                point_color = game_state.board.get(point)
                if point_color is None:
                    continue
                # 是当前落子方,编码为1
                elif point_color == current_player:
                    board_matrix[0,r,c] = 1
                # 不是当前落子方,编码为-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):
         r = index // self.board_width+1
         c = index % self.board_width+1
         return Point(row=r,col=c)

    #围棋棋盘上的交叉点的数目 - 棋盘宽度乘以棋盘高度
    def num_points(self):
        return self.board_width * self.board_height

    # 已经编码的棋盘结构外形
    def shape(self):
        return self.num_planes,self.board_width,self.board_height

这样就结束了我们关于围棋棋盘编码器的部分。您现在可以继续创建你可以编码的数据并将其输入到神经网络中

6.2.生成网络训练数据的树搜索游戏

在您可以将机器学习应用于围棋游戏之前,您需要一组训练数据。幸运的是,棋力高的玩家一直在公共围棋服务器上玩。第7章中涉及如何查找和处理这些游戏记录来创建训练数据。目前,您可以生成自己的游戏记录。本节演示如何使用您在第4章中创建的树搜索机器人来生成游戏记录。在本章的其余部分,你可以使用这些机器人游戏记录作为训练数据来进行深度学习的实验。

用机器学习来模仿经典算法看起来是不是很愚蠢?如果传统算法不是很慢!在这里,您希望使用机器学习来对一个缓慢的树搜索的进行快速的近似。这个概念是AlphaGoZero的关键部分,这是AlphaGo的最强版本。第14章讲述了AlphaGoZero是如何工作的。

继续在dlgo模块之外创建一个名为generate_mcts_games.py的文件。正如文件名所建议的,您将编写用MCTS生成游戏的代码。每一个在这些游戏中的落子将被编码成第6.1节实现的OnePlaneEncoder,并存储在numpy数组,以供未来使用。首先,将下列导入语句放在generate_mcts_games.py.

import argparse  # python自带的命令行参数解析包
import numpy as np

from dlgo.Encoder.Base import get_encoder_by_name
from dlgo.agent import MCTSAgent
from dlgo.agent.FastRandomAgent import goboard_fast as goboard
from dlgo.utils import print_board,print_move

从这些导入中,您已经可以看到哪些工具将会被使用:mcts模块、第三章中的goboard以及您刚刚定义的编码器模块。让我们把注意力转向为你生成游戏数据的函数上来。在generate_game中,您让第4章的MCTSAgent的一个实例与自己对弈(回想第4章中的tempature一个MCTS来调节你的树搜索的波动性)。对于每一个落子,您在落子之前要对棋盘状态进行编码,并将棋盘编码为一个one-hot向量,然后将落子应用到棋盘上。


#自我对弈生成游戏数据
def generate_game(board_size,round,temperature,max_moves):

    # 在围棋棋盘中存储编码的围棋棋盘状态;落子是编码的落子
    boards,moves = [],[]
    # 获取oneplane编码器
    encoder = get_encoder_by_name("OnePlaneEncoder",board_size)
    #开始游戏
    game = goboard.GameState.new_game(board_size)
    #创建MCTS机器人
    bot = MCTSAgent.MCTSAgent(round,temperature)

    num_moves = 0
    while not game.is_over():
        print_board(game.board)
        # 让MCTS机器人选择落子点
        move = bot.select_move(game)
        if move.is_play:
            # 把落子前的游戏进行编码,并加入到boards里
            boards.append(encoder.encode(game))
            # 将one-hot编码后的下一步落子添加到moves里
            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.current_player,move)
        game = game.apply_move(move)
        num_moves += 1
        if num_moves > max_moves:
            break
    return np.array(boards), np.array(moves)

现在您已经有了使用蒙特卡洛树搜索创建和编码游戏数据的方法,您可以定义一个主方法来运行几个游戏并在后面持久化,您也可以将它们放入到generate_mcts_games.py.

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()

使用此工具,您现在可以轻松地生成游戏数据。假设您想为20个9×9Go游戏创建数据,并将功能存储在feature.npy中,并在label.npy中存储标签

请注意,这样生成游戏可能相当缓慢,因此生成大量的游戏将需要一段时间。你可以减少MCTS的轮数,但这也减少了机器人的水平。因此,我们已经为您生成了游戏数据,您可以下面地址中找到这些数据。

https://github.com/maxpumperla/deep_learning_and_the_game_of_go/tree/chapter_6/code/generated_games

此时,您已经完成了所有您需要的预处理,以便将神经网络应用于生成的数据。从第5章开始,您可以直接使用网络来实现一些东西--这是一个很好的锻炼方式-但向前看,你需要一个更强大的工具与日益复杂的深层神经网络来满足你的需要。为此,我们接下来介绍了Keras。

6.3.使用Keras 深度学习库

       计算梯度和神经网络的反向传递越来越成为一种过时的艺术形式,因为许多强大的深度学习库的出现隐藏了较低层次的抽象概念。在前一章中从零开始实现神经网络是很好的,但现在是时候转向更成熟和功能丰富的软件了。

这个Keras深度学习库是一种特别优雅和流行的深度学习工具,用Python编写。开源项目创建于2015年,迅速积累了强大的用户基础,其代码托管在https://github.com/keras-team/keras,并且有很好的文档可以在https://keras.io.上找到

6.3.1.了解Keras的设计原则

 Keras的一个强项是它是一个直观且易于提取的API,其允许快速原型和快速实验周期。这使得Keras在许多数据科学挑战中成为一个受欢迎的选择,比如https://kaggle.com。Keras是模块化构建的,最初灵感来自于其他深度学习工具,如Torch。另一大优点是它的可扩展性,添加新的自定义层或增强现有功能相对简单。

另一个让Keras很容易的方面是它带有电池。例如,许多流行的数据集,如MNIST,可以直接加载Keras,您可以在GitHub存储库中找到许多很好的示例。除此之外,还有一个完整的keras扩展和独立项目社区生态系统https://github.com/fchollet/keras-resources

Keras的一个显著特点是后端的概念:它运行强大的引擎使得其可以交换需求。一种看待Keras的方法是将其作为一个深度学习前端,一个提供一组方便的高级抽象和功能来运行模型的库。在写这本书的时候,三个官方后端可供Keras使用:TensorFlow、Theano和the Microsoft Cognitive Toolkit。在这本书中,您将只与Google的TensorFlow库一起工作,该库也是Keras使用的默认后端。但是,如果你喜欢另一个后端,你不需要太多的努力去切换;Keras为你处理大部分的差异。

在本节中,您将首先安装Keras。然后您将通过运行第5章中的手写数字分类示例来了解它的API,然后转到围棋去预测落子

6.3.2.安装Keras深度学习库

要开始使用Keras之前,您需要首先安装后端。您可以从TensorFlow开始,它通过PIP安装最简单,运行以下操作:     

pip install tensorflow

如果您的机器中安装了NVIDIA GPU和CUDA驱动程序,您可以尝试安装GPU加速版本的TensorFlow

pip install tensorflow-­gpu
如果tensorflow-gpu与您的硬件和驱动程序兼容,这将给您带来巨大的速度提升。
一些有助于模型序列化和可视化的可选依赖项可以为使用Keras下载安装,但现在你将跳过它们,直接继续安装库本身:
pip install Keras

注:因为我们在国内,所以推荐pip install --index-url https://pypi.douban.com/simple tensorflow

和pip install --index-url https://pypi.douban.com/simple keras

6.3.3.运行keras的第一个例子

在本节中,您将看到定义和运行Keras模型要遵循四步工作流:

  1. 数据预处理-加载和准备一个数据集,以输入到神经网络。
  2. 模型定义-模型实例化模型并根据需要向其添加层。
  3. 模型编译-用优化器、损失函数和可选的评估指标列表编译您以前定义的模型。
  4. 模型的训练和评估-对您的深度学习模型进行训练和评估。

要开始使用Keras,我们将给您介绍上一章中遇到的一个示例用例:用MNIST数据集预测手写数字。如你所见,我们第5章的简单模型已经非常接近Keras语法,因此使用Keras就变得更容易。

使用Keras,您可以定义两种类型的模型:顺序模型和更一般的非顺序模型。您将在这里只使用顺序模型。这两种模型类型都可以在keras.models中找到。要定义一个顺序模型,您必须向它添加层,就像您在第5章中在自己的示例中所做的那样。可以通过keras.layers模块获得keras层。用Keras加载MNIST很数据集很简单,这个数据集可以在keras.dataset模块中找到。让我们先引入这个应用程序中所需要的一切。

import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense

接下来,加载和预处理MNIST数据,这只用几行就可以实现的。加载后,您将6万个训练样本和1万个测试样本展开(即原来的28*28展开成784*1),并将它们转换为float类型,通过除以255来规范输入数据。这样做是因为数据集的像素值从0到255不等,并且将这些值规范化为[0,1]的范围,因为这可以让你的网络有更好的训练。此外,标签必须是一个one-hot编码,就像你在第5章中所做的那样。下面的代码展示了我们用Keras去做刚才描述的事情。

#加载数据
(x_train,y_train),(x_test,y_test) = mnist.load_data()

#将60000个训练样本和10000个测试样本都进行展平
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

# 符合one-hot编码,将原来向量的每个值都转为矩阵中的一个行向量,比如原来是2,那对应的行向量是第3行是1
'''
   
#类别向量定义
b = [0,1,2,3,4,5,6,7,8]
#调用to_categorical将b按照9个类别来进行转换
b = to_categorical(b, 9)
print(b)
 
执行结果如下:
[[1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 1.]]
'''
y_train = keras.utils.to_categorical(y_train,10)
y_test = keras.utils.to_categorical(y_test,10)

随着数据准备就绪,您现在可以继续定义要运行的神经网络。在Keras中,先初始化一个顺序模型,然后逐层添加。在第一层中,你必须提供i通过input_shape提供的输入数据的形状。在我们的例子中,输入数据是长度为784的向量,因此必须提供input_shape=(784,)(注:1可以省略)作为形状信息。Keras的Dense层可以被为该层提供激活函数的激活关键字创造出来。你将选择Sigmoid作为激活函数,因为它是到目前为止您所知道的唯一激活函数。Keras有更多的激活函数,其中一些我们之后会更详细地讨论。

补充:激活函数是为了增加神经网络模型的非线性,负责将输入映射为输出,主要有sigmoid、Tanh、Relu

# 新建一个顺序模型,给顺序模型添加Dense层
model = Sequential()
# 指定Dense层有392个神经元,激活函数为sigmoid,输入的向量形状是784*1(第一层必须要输入)
model.add(Dense(392, activation="sigmoid", input_shape=(784,)))
model.add(Dense(196, activation="sigmoid"))
model.add(Dense(10, activation="sigmoid"))
# 输出各层的参数情况
model.summary()

 创建Keras模型的下一步是使用损失函数和优化器去编译模型。您可以通过指定字符串来做到这一点,您将选择sgd(随机梯度下降)作为优化器和使用mean_square_error(均方误差)作为损失函数。同样,Keras有更多的损失函数和优化器,但因为是刚开始,您将使用您已经在第5章遇到的东西。另一位需要争辩的是编译中你可以输入评估列表给keras模型。对于您的第一个应用程序,您将使用精确性作为唯一的度量。精度度量表示如何使在模型的最高得分预测可以匹配真正的标签。

# 编译模型,给定优化器为sgd,损失函数为mean_squared_error,度量根据精确性
model.compile(optimizer='sgd',loss='mean_squared_error',metrics=['accuracy'])

此应用程序的最后一步是执行网络的训练步骤,然后在测试数据上对其进行评估。这是通过调用model上的fit来完成的,它不仅提供训练数据,同时也提供每个梯度更新的样本数和训练模型的时期数

# 执行训练,再拿测试数据进行评估
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])

总结,建立和运行Keras模型分为四个步骤:数据预处理、模型定义、模型编译和模型训练加评估。Keras的核心优势之一是这四步循环可以快速完成,从而导致一个快速的实验周期。这是非常重要的,因为通常您的初始模型定义可以通过调整参数得到很大的提高。

6.3.4.利用Keras中的前馈神经网络进行落子预测

现在,您已经知道了用于顺序神经网络的Keras的API是什么样子的,让我们回到我们的围棋落子预测用例。图6.3说明了该过程的这一步骤。你要先使用从6.2节生成的围棋数据,如下面代码所示。请注意,与前面的MNIST一样,您需要将围棋棋盘数据展平化为向量。 

神经网络可以预测游戏的落子。我们已经将游戏状态编码为矩阵,你可以将该矩阵提供给落子预测模型。该模型输出一个向量,表示每个可能落子的概率

import numpy as np
from keras.models import Sequential
from keras.layers import Dense

# 通过设置随机种子,您可以确保此脚本完全可复制
np.random.seed(123)

# 加载特征(就是局面)
X = np.load("../generate_game/features-200.npy")
# 加载标签(当前局面的落子)
Y = np.load("../generate_game/features-200.npy")

# 样本数量
num_samples = X.shape[0]
board_size = 9

# 把特征和标签展平
X = X.reshape(num_samples,board_size*board_size)
Y = Y.reshape(num_samples,board_size*board_size)

# 拿出90%用来训练,10%用于测试
num_train_samples = int(0.9*num_samples)
X_train,X_test = X[:num_train_samples],X[num_train_samples:]
Y_train,Y_test = Y[:num_train_samples],Y[num_train_samples:]

接下来,让我们定义并运行一个模型来预测围棋落子。对于一个9×9的棋盘,有81个可能的落子,所以你需要用网络预测81类。伯克。假设你闭上眼睛,随意地指着棋盘上的一个点。你有1/81的机会找到下一个落子,所以你想要你的模型精度显著超过1/81。

定义了一个简单的Keras机器语言程序,它有三个Dense层,每个层都有Sigmoid激活函数,用均方误差作为损失函数和随机梯度下降作为优化器。然后,你让这个网络训练15轮,并评估它的测试数据。

# 2.模型定义
# 新建一个顺序神经网络,并添加3个Dense层
model = Sequential()
model.add(Dense(300,activation="sigmoid",input_shape=(board_size*board_size,)))
model.add(Dense(200,activation="sigmoid"))
model.add(Dense(board_size*board_size,activation="sigmoid"))
model.summary()
# 模型定义结束

# 3.模型编译
model.compile(optimizer="sgd",loss="mean_squared_error",metrics=["accuracy"])
# 模型编译结束

# 4.模型训练与评估
# 模型训练,其中verbase为1表示显示进度条
# validation_data用来在每轮之后,或者每几轮后都去验证一次验证集,用来及早发现问题,防止过拟合,或者超参数设置有问题。
model.fit(X_train,Y_train,batch_size=64,epochs=15,verbose=1,validation_data=(X_test,Y_test))
# 模型训练结束

# 模型评估
# verbose为0表示不显示进度条
score = model.evaluate(X_test,Y_test,verbose=0)
#模型评估结束
# 得到错误数和精确数
print("loss:",score[0])
print("accuracy:",score[1])

运行此代码,您应该看到评估结果:

注意Trainable params中的101,081这一行;这意味着训练过程正在更新超过10万个权重的值。它还让你大致了解你的模型能力:一种学习复杂关系的能力。当您比较不同的网络体系结构时,参数的总数提供了一种近似比较模型大小的方法。

正如你所看到的,你的实验的预测准确率只有1.4%左右,这一点乍一看并不令人满意。但是你随机猜测的正确率只有是1.2%左右。这告诉你,虽然性能不是很好,但模型正在学习,并且可以比随机预测更好地预测落子点。

图6.4显示了一个棋盘局面,无论是哪一方下在A还是B,都可以在棋盘吃掉对方。这个局面不会出现在我们的训练集中。 

图6.4 一个测试我们模型的示例游戏局面。在这个局面下,黑棋可以通过在A处落子来吃掉两颗白棋,或者白色可以下在B处吃掉两颗黑棋。这样在游戏中就会有巨大的优势。 

 现在你可以把当前的局面输入到已经训练好的模型,并打印出它的预测。

这个矩阵原来的9×9棋盘的映射:每个数字表示模型下在这一点上的可能性。但这个结果并不令人满意;它甚至没有学会避开已有落子的地方。但请注意,围棋棋盘边缘的分数总是低于接近中心的分数。围棋中的传统智慧是你应该避免在棋盘的边缘玩,除非在游戏结束和其他特殊情况下。因此,该模型已经学会了一个关于游戏的合理概念:不是通过理解策略或者效率,而只是复制我们的MCTS机器人所做的事情。这个模型不太可能预测许多好的落子,但它已经学会了避免一些糟糕额的落子。 

这是真正的进步,但你还可以做得更好。本章的其余部分就可以解决了您的第一个缺点,并提高沿途的围棋落子的预测精度。你会注意到以下几点:

  • 你是通过树搜索来预测落子的,它具有很强的随机性。有时MCTS引擎会产生奇怪的落子点,特别是当它们在游戏中不落后或远远落后。在第七章中,您将从人类游戏数据中创建一个深度学习模型。当然,人类也是不可预测的,但他们不太可能去走一些无意义的落子。
  • 你的神经网络体系结构可以得到极大的改进。多层感知器不太适合捕获围棋棋盘数据。你必须把二维棋盘数据放平到一个平面向量上,丢弃关于围棋棋盘的所有空间信息。在6.4节中,您将了解一种新类型的网络,它在捕获围棋空间结构方面要好得多。
  • 在所有的网络中,你只使用sigmoid激活函数。在第6.5节和第6.6节中,您将了解两个新的激活函数,这些函数通常会带来更好的结果。
  • 到目前为止,你只使用MSE作为损失函数,它很直观,但不太适合你的用例。在6.5节中,你将使用另一个损失函数,该函数是为这样的分类任务量身定做的。

在本章结尾,我们就可以解决了其中的大部分问题,您将能够构建一个神经网络,该神经网络的预测比你之前的预测要好的多。在第七章中你将会学习构建一个更强大的机器人所需的关键技术

请记住,你不是为了能尽可能准确地预测落子,而是创建一个可以尽可能好地发挥水平的机器人。即使你的深层神经网络非常善于从历史数据中预测下一步的落子,但深度神经网络的核心仍然是获得棋盘结构并选择合理落子

发布了11 篇原创文章 · 获赞 10 · 访问量 3348

猜你喜欢

转载自blog.csdn.net/qq_41957257/article/details/104068407