翻译Deep Learning and the Game of Go(15)第13章   AlphaGo:把这一切都汇集在一起 

第三部分 

        此时,您已经学习了许多人工智能技术,这些技术来自经典的树搜索、机器学习和强化学习。每一个都是强大的,但每一个都有局限性。要做一个真正强大的围棋AI,你需要结合你到目前为止学到的一切。整合所有这些部件是一项严肃的工程壮举。这一部分涵盖了AlphaGo的体系结构,这个AI震撼了围棋世界-还有AI世界!这本书结束之前,你将了解到AlphaGo Zero的优雅的简单设计,这是迄今为止最强的AlphaGo版本。 

第13章   AlphaGo:把这一切都汇集在一起 

本章包括:

  • 引导围棋AI实现超越人类水平的指导原则
  • 使用树搜索、监督深度学习和强化学习来构建这样一个机器人
  • 实现你自己类似DeepMind的 alphago引擎

当DeepMind的围棋AI AlphaGo在2016年对李世石的比赛的第二局中下到37步时,它让围棋世界陷入了疯狂。评论员Michael Redmond,,一个职业棋手,他手下有近千场顶级比赛,他在空中做了两次同样的动作;他甚至短暂地从演示棋盘上取出了棋子,同时环顾四周,仿佛要确认AlphaGo做出了正确的举动。)“我还是不太清楚它的逻辑“,Redmond第二天对《美国围棋杂志》这样说。过去十年的围棋称霸者李世石在下棋前花了12分钟研究棋局。图13.1就显示了这个传奇的落子

                        图13.1  AlphaGo在系列赛的第二场与李世石的对局中下了这步尖冲。这个棋震惊了许多职业棋手。

这一举动违背了传统的围棋理论。尖冲,是邀请白棋沿着这条边延伸成为坚实的墙壁。如果白棋在第三行,黑棋在第四行,这可以认为是一个大致均等的交换:白棋得到了边,而黑棋得到了外势,但是当白棋在第四行时,就可以得到很多的地盘,五路尖冲看起来有点业余-----在alphago与传奇对局五盘中至少有四盘是这样的,这个尖冲是阿尔法狗众多奇特落子中的第一个。一年之内,每个顶级棋手到业余棋手都在尝试AlphaGo的动作。 

在本章中,您将通过实现其所有构建模块来了解AlphaGo是如何工作的。AlphaGo是一个巧妙的组合,通过专业的围棋记录进行监督式的深度学习(你在第5-8章中了解到),自我对弈的深度强化学习(在第9-12章中讨论),并利用这些深层网络以一种新的方式改进树搜索。你可能会感到惊讶,你对AlphaGo的成分已经知道多少了。更准确地说,我们将详细描述的AlphaGo系统如下: 

  • 您首先要训练两个用于落子预测的深度卷积神经网络(策略网络)。其中一种网络体系结构更深入一些,并且产生更精确的结果,而另一个是更小并且评估更快。我们将分别称之为强大和快速的政策网络。
  • 强大而快速的策略网络使用稍微复杂一点的棋盘编码器,带有48个特征平面。他们还使用了比你在第6章和第7章中看到的更深的体系结构,除此之外,他们应该看起来很熟悉。第13.1节涵盖AlphaGo的策略网络架构。
  • 在策略网络的第一步训练完成之后,您将以强大的策略网络作为13.2节中自我对弈的起点。如果你用很多计算量来做这件事,这会导致你的机器人得到大幅度的提升。
  • 作为下一步,您可以在第13.3节中使用强大的自我对弈网络来导出一个价值网络。这就完成了网络训练阶段,在这一点之后,你不会做任何深度学习。
  • 要玩一个围棋游戏,你使用树搜索作为游戏的基础,但不是像第四章那样简单的蒙特卡洛模拟,而是使用快速策略网络来指导下一步。此外,您还要平衡此树搜索算法的输出与您的价值函数告诉您的内容。我们将在第13.4节中告诉你所有关于这个创新。
  • 通过训练策略、自我对弈,再到在超越人类水平上去运行游戏搜索,整个过程将需要大量的计算资源和时间。第13.5节给你一些建议,它需要什么才能使AlphaGo一样强大,以及从你自己的实验中你能期望什么。

图13.2给出了我们刚刚绘制的整个过程的概述。在整个章节中,我们将放大这张图的部分,并在各自的章节中为您提供更多的细节。

 图13.2   如何训练三个神经网络给AlphaGo A I提供动力。从人类游戏记录的集合开始,你可以训练两个神经网络来预测下一步:一个小的,快速的网络和一个大的,强大的网络。然后,您可以通过强化学习进一步提高大型网络的落子水平。自我对弈游戏也提供了数据来训练价值网络。然后,AlphaGo使用树搜索算法中的所有三个网络,可以产生难以置信的强大的游戏玩法。

13.1.为AlphaGo训练深度神经网络

在介绍中,您了解到AlphaGo使用三个神经网络:两个策略网络和一个价值网络。即使这看起来好像第一次听说,但在本节中,你将看到这些网络和输入到它们中的输入特性在概念上是接近的。也许最令人惊讶的部分是在AlphaGo中关于深度学习的使用,在完成第5章至第12章后,您已经对它了解了多少。在详细介绍这些神经网络是如何构建和训练之前,让我们先来讨论一下AlphaGo系统中神经网络中的作用:

  • 快速策略网络——这个围棋落子预测网络的大小与您在第7章和第8章中训练的网络相当。它的目的不是成为最准确的落子预测器,而是一个很好的预测器,在预测落子方面真的很快。这种网络在树搜索模拟中的第13.4节中被使用-你在第4章中已经看到你需要创建大量的数据让树搜索成为一种选择。我们将稍微少强调这个网络,并集中在下面两个网络。
  • 强大的策略网络——这一落子预测网络是为准确性而优化的,而不是速度。它是一个卷积网络,比它的快速版本更深,并且可以比预测围棋落子好两倍以上。作为一个快速版本,这个网络是用人类对弈数据训练,就像你在第7章中所做的那样。在这个训练步骤结束后,这个强大的策略网络被使用作为使用第9章和第10章的强化学习技术的自我对弈的开始。这个步骤将会使策略网络更强化。
  • 价值网络——强大的策略网络自我对弈产生了一个新的数据集,你可以用来训练一个价值网络。具体来说,你使用这些游戏的结果和来自第11章和12章的技术学习价值函数。然后,这一价值网络将在第13.4节中发挥不可或缺的作用。

13.1.1.AlphaGo中的网络架构 

 现在,您大致了解了AlphaGo中使用的三个深度神经网络中的每一个,我们可以向您演示如何使用在Python中的keras库去构建这些网络。在我们给你看代码之前,先看下面对网络工作体系结构的快速描述。如果您需要复习卷积网络的术语,请再看第七章。

  • 强大的策略网络是一个13层的卷积网络。所有这些层都产生19×19个过滤器;您始终保持整个网络的原始棋盘大小。为了让这起作用,你需要相应地填充输入,就像你在第7章中所做的那样一样。第一卷积层中内核大小为5,以下所有层的内核大小为3。最后一层使用softmax激活函数并有一个输出滤波器,前12层使用ReLU激活函数,每层有192个输出过滤器。 
  • 价值网络是一个16层卷积网络,前12层与强策略网络完全相同。第13层是一个额外的卷积层,与2-12层相同。第14层是一个具有核大小1和一个输出滤波器的卷积层。网络顶部有两个Dense层,一个有256个输出和使用ReLU激活函数,最后一个有一个输出和使用tanh激活函数。 

正如您所看到的,AlphaGo中的策略网络和价值网络都是与第六章中已经遇到的深度卷积神经网络具有相同的类型。事实是这两个网络是如此的相似,可以允许您在单个Python函数中定义它们。在这样做之前,我们在Keras中引入了一个小快捷方式,它可以很好地缩短了网络定义。回想第七章,你可以使用keras填充输出图像,在层之前带有ZeroPadding2D层。这样做是非常好的,但您可以通过将填充放到Conv2D层来节省模型定义所需的时间。在价值网络和策略网络中都要做的是将输入填充到每个卷积层,以便输出滤波器与输入(19×19)具有相同的大小。例如,不是将第一层的19*19输入填充到23×23图像,而是使下面的核大小为5的卷积层产生19×19的输出滤波器,您可以让卷积层保留输入的大小,然后通过向卷积层提供参数padding=‘same’来完成这一操作。考虑到这条捷径,让我们定义前11层,这是AlphaGo的策略和价值网络有共同的。您可以在Github里的dlgo.networks模块下的alphago.py中找到此定义。 

from keras.layers import Conv2D
from keras.models import Sequential


def alphgo_model(input_shape,is_policy_net=False, # 通过这个标志,你指定了是策略网络还是价值网络
                 num_filters=192,# 除最后一层外,所有的卷积层都有相同数量的滤波器。
                 first_kernel_size=5,# 第一层有内核大小5,其他所有的内核大小只有3
                 other_kernel_size=3):

    model = Sequential()
    model.add(
        Conv2D(filters=num_filters,kernel_size=first_kernel_size,
               padding="same",data_format="channels_first",activation="relu")
    )

    # 用另一个内核大小去添加剩下的层
    for i in range(2,12):
        model.add(
            Conv2D(filters=num_filters, kernel_size=other_kernel_size,
                   padding="same", data_format="channels_first", activation="relu")
        )

请注意,您还没有指定第一层的输入形状。这是因为策略和价值网络的形状略有不同。这是因为当我们介绍AlphaGo棋盘编码时,你会看到与下一节中的棋盘编码器不同的地方。为了继续model的定义,您只定义一个最终的卷积层,而不是定义强策略网络。 

  #如果是策略网络
    if is_policy_net:
        model.add(
            Conv2D(filters=1,kernel_size=1,padding="same",data_format="channels_first",activation="softmax")
        )
        model.add(Flatten())
        return model

正如您所看到的,您可以添加一个最终的Flatten层来压平预测,并确保与您从第5章到第8章的模型定义的一致性。

如果你想返回AlphaGo的价值网络,那就增加超过两个Conv2D层,两个Dense层和一个Flatten层连接他们。

 # 如果是价值网络
    else:
        model.add(
            Conv2D(num_filters, other_kernel_size, padding='same',
                   data_format='channels_first', activation='relu'))
        model.add(
            Conv2D(filters=1, kernel_size=1, padding='same',
                   data_format='channels_first', activation='relu'))
        model.add(Flatten())
        model.add(Dense(256, activation='relu'))
        model.add(Dense(1, activation='tanh'))
        return model

我们在这里没有明确讨论快速策略网络的体系结构;快速策略的输入特征和网络体系结构的定义在技术上是隐藏的,并且对加深AlphaGo系统的理解没有帮助。对于你自己的实验,使用我们的dlgo.network模块中的其中一个网络是非常好的,比如small、medium或large。快速策略的主要是拥有一个比强大策略要小的快速评估。我们将在下一节中更详细地指导您完成训练过程。

13.1.2 AlphaGo的棋盘编码 

现在,您知道了AlphaGo中使用的所有网络体系结构,让我们讨论如AlphaGo是如何编码围棋棋盘数据的。在第6章和第7章中,您已经实现了相当多的棋盘编码器,包括单平面编码,七平面编码,或十一平面编码,所有这些都存储在dlgo.encodes模块中。在AlphaGo中使用的特征平面只是比你以前遇到的编码器要复杂一点,但代表了目前编码器的自然延续。

用于策略网络的AlphaGo棋盘编码器有48个特征平面;对于价值网络,您使用额外的平面来增强这些特征。这48个平面由11个概念组成,其中一些是你以前使用过的,另一些是新的。我们将更详细地讨论每一个问题。总得来说,AlphaGo比我们目前已有棋盘编码器更关注围棋特定的战术情况。这方面的一个主要例子是将引征和逃征子的概念(见图13.3)作为特征集。 

您在所有围棋棋盘编码器中一致使用的一种技术,也存在于AlphaGo中,就是使用二进制特性。例如,在捕捉气时,你不是使用一个特征平面去对棋盘上的每个棋子进行气的计数,而是选择了一个带有平面的二进制表示,指示一块石头是否有1、2、3或更多的气。在AlphaGo中,你会看到完全相同的想法,但是有八个特征平面来进行二元计数。在气的例子中,这意味着8个平面表示一块棋1、2、3、4、5、6、7或至少8个气。

与你在第6章到第8章中看到的唯一区别是,AlphaGo在单独的特征平面中显式地编码棋子颜色。回想一下在第七章的七平面编码器,你有黑棋和白棋的气平面。在AlphaGo中,您只有一组计数气的集合功能。此外,所有的功能都是以棋手的身份来表达的接下来的对弈。例如,特征集Capture Size,计算一个落子将捕获的棋子数量,不管棋子可能是什么颜色的。

表13.1总结了AlphaGo中使用的所有特征平面。前48个平面用于策略网络,最后一个平面仅用于价值网络。

 这些特性的实现可以在我们的GitHub存储库中的dlgo.encodes模块中的alphago.py中找到。表13.1中的每个特征集实现并不困难,而且当它与AlphaGo中所有令人兴奋的部分相比,也不是特别有趣。实现征吃有点棘手,并且编码数字由于一个落子点被下,需要修改您的围棋棋盘定义。因此,如果您对如何做到这一点感兴趣,请查看我们在GitHub上的实现。

 

from dlgo.Encoder.Base import Encoder
from dlgo.Encoder.utils import is_ladder_escape, is_ladder_capture
from dlgo.gotypes import Point, Player
from dlgo.agent.FastRandomAgent.goboard_fast import Move
from dlgo.agent.helpers import is_point_true_eye
import numpy as np

"""
Feature name            num of planes   Description
Stone colour            3               Player stone / opponent stone / empty
Ones                    1               A constant plane filled with 1
Zeros                   1               A constant plane filled with 0
Sensibleness            1               Whether a move is legal and does not fill its own eyes
Turns since             8               How many turns since a move was played
Liberties               8               Number of liberties (empty adjacent points)
Liberties after move    8               Number of liberties after this move is played
Capture size            8               How many opponent stones would be captured
Self-atari size         8               How many of own stones would be captured
Ladder capture          1               Whether a move at this point is a successful ladder capture
Ladder escape           1               Whether a move at this point is a successful ladder escape
"""

FEATURE_OFFSETS = {
    "stone_color": 0,
    "ones": 3,
    "zeros": 4,
    "sensibleness": 5,
    "turns_since": 6,
    "liberties": 14,
    "liberties_after": 22,
    "capture_size": 30,
    "self_atari_size": 38,
    "ladder_capture": 46,
    "ladder_escape": 47,
    "current_player_color": 48
}


def offset(feature):
    return FEATURE_OFFSETS[feature]


class AlphaGoEncoder(Encoder):

    def __init__(self, board_size=19, use_player_plane=True):
        self.board_width = board_size
        self.board_height = board_size
        self.use_player_plane = use_player_plane # True就是1
        self.num_planes = 48 + use_player_plane

    def name(self):
        return 'AlphaGoEncoder'

    def encode(self, game_state):
        board_tensor = np.zeros((self.num_planes, self.board_height, self.board_width))
        for r in range(self.board_height):
            for c in range(self.board_width):
                point = Point(row=r + 1, col=c + 1)

                # 棋子颜色特征平面
                go_string = game_state.board.get_go_string(point)
                if go_string and go_string.color == game_state.current_player:
                    board_tensor[offset("stone_color")][r][c] = 1
                elif go_string and go_string.color == game_state.current_player.other:
                    board_tensor[offset("stone_color") + 1][r][c] = 1
                else:
                    board_tensor[offset("stone_color") + 2][r][c] = 1

                # 全填充1和填充0的平面
                board_tensor[offset("ones")] = self.ones()
                board_tensor[offset("zeros")] = self.zeros()

                # 指示是否是真眼
                if not is_point_true_eye(game_state.board, point, game_state.current_player):
                    board_tensor[offset("sensibleness")][r][c] = 1

                # 到该点轮数
                ages = min(game_state.board.move_ages.get(r, c), 8)
                if ages > 0:
                    print(ages)
                    board_tensor[offset("turns_since") + ages][r][c] = 1

                # 该点的气
                if game_state.board.get_go_string(point):
                    liberties = min(game_state.board.get_go_string(point).num_liberties, 8)
                    board_tensor[offset("liberties") + liberties][r][c] = 1

                move = Move(point)
                if game_state.is_valid(move):
                    new_state = game_state.apply_move(move)
                    # 该点落下后的气
                    liberties = min(new_state.board.get_go_string(point).num_liberties, 8)
                    board_tensor[offset("liberties_after") + liberties][r][c] = 1

                    adjacent_strings = [game_state.board.get_go_string(nb)
                                        for nb in point.neighbors()]
                    capture_count = 0
                    for go_string in adjacent_strings:
                        other_player = game_state.current_player.other
                        if go_string and go_string.num_liberties == 1 and go_string.color == other_player:
                            capture_count += len(go_string.stones)
                    capture_count = min(capture_count, 8)
                    # 吃子平面
                    board_tensor[offset("capture_size") + capture_count][r][c] = 1

                # 自杀大小
                if go_string and go_string.num_liberties == 1:
                    go_string = game_state.board.get_go_string(point)
                    if go_string:
                        num_atari_stones = min(len(go_string.stones), 8)
                        board_tensor[offset("self_atari_size") + num_atari_stones][r][c] = 1

                # 是否会被征吃
                if is_ladder_capture(game_state, point):
                    board_tensor[offset("ladder_capture")][r][c] = 1

                # 是否会避免被征吃
                if is_ladder_escape(game_state, point):
                    board_tensor[offset("ladder_escape")][r][c] = 1

                # 指示当前棋手颜色
                if self.use_player_plane:
                    if game_state.current_player == Player.black:
                        board_tensor[offset("current_player_color")] = self.ones()
                    else:
                        board_tensor[offset("current_player_color")] = self.zeros()

        return board_tensor

    def ones(self):
        return np.ones((1, self.board_height, self.board_width))


    def zeros(self):
        return np.zeros((1, self.board_height, self.board_width))

    def capture_size(self, game_state, num_planes=8):
        pass

    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


def create(board_size):
    return AlphaGoEncoder(board_size)

13.1.3. 训练AlphaGo式策略网络

在准备好网络体系结构和输入特性之后,AlphaGo训练策略网络的第一步遵循了我们在第7章中介绍的确切过程:指定一个棋盘编码器和一个代理,加载围棋数据,并使用这些数据对代理进行训练。图13.4说明了这一过程。事实上,您使用稍微复杂一点的功能和网络并不能改变这一点。 

要初始化和训练AlphaGo的强策略网络,您首先需要实例化AlphaGoEncoder,并创建两个用于训练和测试的围棋数据生成器,就像您在第7章中所做的那样。你在examples/alphago/alphago_policy_sl.py下找到GitHub上的这一步。 

rows, cols = 19, 19
num_classes = rows * cols
num_games = 10000

# 训练和测试数据生成器
encoder = AlphaGoEncoder()
processor = GoDataProcessor(encoder=encoder.name())
generator = processor.load_go_data('train', num_games, use_generator=True)
test_generator = processor.load_go_data('test', num_games, use_generator=True)

接下来,您可以使用本节前面定义的alphago_model函数去加载AlphaGo的策略网络,并使用分类交叉熵损失函数和随机梯度函数编译这个Keras模型,我们称这个模型为alphago_sl_policy,表示它是一个由监督学习(sl)训练的策略网络。

#加载和编译模型
input_shape = (encoder.num_planes,rows,cols)
alphago_sl_policy = alphago_model(input_shape=input_shape,is_policy_net=True)
alphago_sl_policy.compile(optimizer="sgd",loss="categorical_crossentropy",metrics=["accuracy"])

现在,第一阶段的训练只剩下调用这个策略网络上的FIT_generator,训练和测试生成器,就像您在第7章中所做的那样。除了使用更大的network和一个更复杂的编码器,这正是您在第6章至第8章中所做的。

训练结束后,您可以用model和Encoder创建DeepLearningAgent并将其存储到下一个我们接下来讨论的两个训练阶段。 

# 训练和测试模型
epochs = 200
batch_size = 128
alphago_sl_policy.fit_generator(
    generator=generator.generate(batch_size, num_classes),
    epochs=epochs,
    steps_per_epoch=generator.get_num_samples() / batch_size,
    validation_data=test_generator.generate(batch_size, num_classes),
    validation_steps=test_generator.get_num_samples() / batch_size,
    callbacks=[ModelCheckpoint('alphago_sl_policy_{epoch}.h5')]
)

alphago_sl_agent = DeepLearningAgent(alphago_sl_policy, encoder)

with h5py.File('alphago_sl_policy.h5', 'w') as sl_agent_out:
    alphago_sl_agent.serialize(sl_agent_out)
# end::alphago_sl_train[]

alphago_sl_policy.evaluate_generator(
    generator=test_generator.generate(batch_size, num_classes),
    steps=test_generator.get_num_samples() / batch_size
)

 为了简单起见,在本章中,您不需要单独训练快速和强大的策略网络,就像在最初的AlphaGo论文中一样。不是去训练另一个更小、更快的策略网络,您可以使用alphago_sl_agent作为快速策略。在下一节中,您将看到如何使用此代理作为强化学习的起点,这将导致更强的策略网络

13.2 从策略网络进行反复自我对弈

在用alphago_sl_agent训练了一个相对强大的策略代理之后,您现在可以使用这个代理来让它自己发挥作用,使用第10章所涵盖的策略梯度算法。就像你的在13.5节看到的那样,在DeepMind的AlphaGo中,它让不同版本的强策略网络与当前最强的版本进行对弈。这样可以防止过度拟合,导致较好的结果,但我们采用简单的方法,让alphago_sl_agent和自己对弈,,使一个策略代理更强大。

在下一个训练阶段,你首先要加载监督学习的策略网络alphago_sl_agent两次:一个版本作为您的新强化学习代理名为alphago_rl_agent,另一个版本作为其对手。这一步可以是在GitHub上的示例/alphago/alphago_policy_rl.py下找到。

# tag::load_opponents[]
from dlgo.agent.ReinforcementLearning.PolicyAgent import PolicyAgent
from dlgo.agent.DeepLearningAgent.DeepLearningAgent import load_prediction_agent
from dlgo.Encoder.AlphaGoEncoder import AlphaGoEncoder
from dlgo.agent.AlphaGo.simulate_game import experience_simulate
import h5py

encoder = AlphaGoEncoder()

sl_agent = load_prediction_agent(h5py.File('alphago_sl_policy.h5'))
sl_opponent = load_prediction_agent(h5py.File('alphago_sl_policy.h5'))

alphago_rl_agent = PolicyAgent(sl_agent.model, encoder)
opponent = PolicyAgent(sl_opponent.model, encoder)
# end::load_opponents[]

# tag::run_simulation[]
num_games = 1000
experience = experience_simulate(num_games, alphago_rl_agent, opponent)

with h5py.File('alphago_rl_experience.h5', 'w') as exp_out:
    experience.serialize(exp_out)

alphago_rl_agent.train(experience)

with h5py.File('alphago_rl_policy.h5', 'w') as rl_agent_out:
    alphago_rl_agent.serialize(rl_agent_out)

 simulate_game.py如下

from dlgo.agent.FastRandomAgent.goboard_fast import GameState,Move,Player
from dlgo.agent.ReinforcementLearning.ExperienceCollector import ExperienceCollector
from dlgo.utils import print_board
from dlgo.ComputeWinner import compute_game_result
from dlgo.agent.ReinforcementLearning.ExperienceCollector import combine_experience
from collections import namedtuple

class GameRecord(namedtuple('GameRecord', 'moves winner margin')):
    pass


def simulate_game(black_player, white_player,board_size):
    moves = []
    game = GameState.new_game(board_size)
    agents = {
        Player.black: black_player,
        Player.white: white_player,
    }
    while not game.is_over():
        next_move = agents[game.current_player].select_move(game)
        moves.append(next_move)
        game = game.apply_move(next_move)

    print_board(game.board)
    game_result = compute_game_result(game)
    print(game_result)

    return GameRecord(
        moves=moves,
        winner=game_result.winner,
        margin=game_result.winning_margin,
    )

def experience_simulate(num_games,agent1,agent2):
    collector1 = ExperienceCollector()
    collector2 = ExperienceCollector()
    agent1.set_collector(collector1)
    agent2.set_collector(collector2)
    # 开始模拟对局
    for i in range(num_games):
        collector1.begin_episode()
        collector2.begin_episode()
        game_record = simulate_game(agent1,agent2,19)
        # 黑棋赢了给第一个AI回报1,给第二个AI回报-1
        if game_record.winner == Player.black:
            collector1.complete_episode(reward=1)
            collector2.complete_episode(reward=-1)
        # 白棋赢了给第一个AI回报-1,给第二个AI回报1
        else:
            collector1.complete_episode(reward=-1)
            collector2.complete_episode(reward=1)
        experience = combine_experience([collector1, collector2])
        return experience

请注意,名为experience_simulate的这个函数所做的只是设置两个代理自我对弈指定数量的对局,并将经验数据作为Experience Collector返回,这是第9章中介绍的概念。

2016年AlphaGo登场时,最强的开源围棋AI是Pachi(你可以在附录C中了解更多),水平在业余2段左右。结果只是让强化学习代理alphago_rl_agent对阵它时就达到85%的胜率。卷积神经网络以前曾用于围棋落子预测,但对阵Pachi胜率从未高过10%。如果你自己做实验,不要指望你的机器人能达到这么高的水平——因为必要的计算能力你负担不起

13.3.从Self-Play数据中提取一个价值网络 

AlphaGo网络训练过程中的第三步也是最后一步是从刚刚用于AlphaGo_rl_agent的自我对弈经验数据中训练出一个价值网络。这一步从结构上看类似于上一步。您首先初始化一个AlphaGo价值网络,并使用AlphaGo棋盘编码器创建一个ValueAgent。这个训练步骤也可以在GitHub中的examples/alphago/alphago_value.py中找到。

from dlgo.network.AlphaNet import alphago_model
from dlgo.Encoder.AlphaGoEncoder import AlphaGoEncoder
from dlgo.agent.ReinforcementLearning.ValueAgent import ValueAgent
from dlgo.agent.ReinforcementLearning.ExperienceCollector import load_experience
import h5py

rows, cols = 19, 19
encoder = AlphaGoEncoder()
input_shape = (encoder.num_planes, rows, cols)
alphago_value_network = alphago_model(input_shape)

alphago_value = ValueAgent(alphago_value_network, encoder)

 你现在可以再次从自我对弈中获得经验数据,然后拿它训练你的价值网络。

# 加载经验数据用来训练价值网络
alphago_experience = load_experience(h5py.File("alphago_rl_experience.h5","r"))
alphago_value.train(alphago_value)
with h5py.File("alphago_value.h5","w") as out_file:
    alphago_value.serialize(out_file)

你现在已经拥有快速策略、强大策略,和价值网络,如果你知道如何让这三个网络在树搜索算法中工作得当,就能发挥超人的水平。下一节是关于这一点的。

13.4. 使用策略和价值网络实现更好搜索

从第4章中回想起,在应用于围棋游戏的纯蒙特卡罗树搜索中,您使用以下四个步骤构建了一个游戏状态树:

  1. Select---您通过随机选择孩子节点来遍历游戏树。
  2. Expand---向树添加一个新节点(一个新的游戏状态)。
  3. Evaluate---从这种状态开始,有时被称为叶,完全随机地模拟一个游戏。
  4. Update---在模拟完成后,相应地更新您的树统计信息。

模拟许多游戏将导致越来越准确的统计数据,然后你可以用来选择下一步 

AlphaGo系统使用了一种更复杂的树搜索算法,但您仍将识别它的许多部分。前面的四个步骤仍然是AlphaGo的MCTS算法的组成部分,但是您使用深度神经网络以一种智能的方式来评估局面、扩展节点和跟踪统计。在本章的其余部分,我们将向您展示如何开发一个AlphaGo的树搜索版本

13.4.1.利用神经网络提升蒙特卡洛推出

第13.1、13.2和13.3节中详细描述了如何为AlphaGo训练三个神经网络:快速策略、强策略和价值网络。如何利用这些网络来改进蒙特卡洛树搜索?首先想到的是停止随意落子,而是使用策略网络来指导推出,这正是快速策略网络的目的,它解释了这个名字---推出需要很快才能执行许多的任务。

下面的列表展示了如何贪婪地用策略网络为给定的围棋游戏状态选择落子。你尽可能选择概率最高的落子直到游戏结束,如果当前玩家获胜,就返回1,否则返回-1。

def policy_rollout(game_state, fast_policy): 
    current_player = game_state.current_player()
    while not game_state.is_over():
       move_probabilities = fast_policy.predict(game_state) 
       greedy_move = max(move_probabilities)
       game_state = game_state.apply_move(greedy_move)
    winner = game_state.winner()
    return 1 if winner == current_player else ­-1

使用这种推出策略本身就是有益的,因为策略网络自然比随机落子更擅长于选择落子。但你还有很大的改进空间。

例如,当您到达树中的叶子节点并需要扩展时,不是去随机选择一个新节点进行扩展,您可以向强大的策略网络去询问好的落子点。一个策略网络给出了下一个落子点的所有概率分布,每个节点都可以跟踪这个概率,这样就更有可能选择好的落子点。我们叫这些节点概率为先验概率,因为它们在进行任何树搜索之前给我们提供了落子点是否好的先验知识。

最后,来看价值网络是如何发挥作用的。你已经通过策略网络去替换随机猜测来改进您的推出机制。然而,在每片叶子上,你只计算一个游戏的结果来估计叶子的价值。但是估计一个落子的价值恰恰是你训练的价值网络擅长的,所以你已经有了一个复杂的猜测。AlphaGo所做的就是权衡推出的结果与价值网络的输出。如果你想一想,这类似于你作为一个人在玩游戏时做决定的方式:你试图向前看,就像现实的可能性一样多,但你也要考虑到你的游戏经验。如果你能读出一系列可能对你有好处的落子点,这可以取代你的直觉。

现在,您大致知道AlphaGo中使用的三个深度神经网络中的每一个都是用于什么,以及如何用它们来改进树搜索,让我们仔细看看细节。

13.4.2 带有结合价值函数的树搜索

在第11章中,您看到了落子价值,也称为Q值,应用于围棋游戏。概括地说,对于当前的棋局状态和潜在的下一步落子,一个落子值Q(s,a)估计了在情况s下一个落子a有多好。您将看到如何定义Q(s,a);现在,只需注意,AlphaGo搜索树中的每个节点都存储Q值。另外,每个节点跟踪visit_counts,这指代着着这个节点被搜索遍历几次。还有先验概率P(s,a)。以及强大的策略网络认为当前棋局s下落子点a的价值。

树中的每个节点都精确地有一个父节点,但可能有多个子节点,当Python字典映射到其它节点时,你可以对此进行编码。使用此约定,您可以定义AlphaGoNode如下所示。 

class AlphaGoNode:
    def __init__(self,parent=None,probability=1.0):
        self.parent = parent  # 树节点有一个父亲和有潜力的多个孩子
        self.children = {}

        self.visit_count = 0
        self.q_value = 0
        self.prior_value = probability  # 一个叶节点价值通过先验概率初始化
        self.u_value = probability  # 搜索中更新的价值

 假设你进入到一个正在进行的游戏中,游戏已经建立了一棵搜索树,并收集了访问计数和落子价值的良好估计。你想要的是模拟许多对局并跟踪对局数据,以便在模拟结束时,您可以选择您找到的最佳落子。你如何遍历树来模拟游戏呢?如果您处于游戏状态s,则表示相应的状态访问计数为N(s),您可以选择如下操作:

起初,这看起来有点复杂,但你可以分解这个方程:

  • argmax表示法意味着你取的参数a是最大的。
  • 最大化由两部分组成:Q值和通过访问计数归一化先验概率。
  • 起初,访问计数是零,这意味着通过将Q(s,a)+P(s,a)上最大化来给Q值和先验概率给予相同的权重。
  • 如果访问计数变得非常大,则P(s,a)/(1+N(s,a))一词变得可以忽略不计,那只有Q(s,a)有效。
  • 我们调用这个效用函数u(s,a)=P(s,a)/(1+N(s,a))。在下一节中,您将稍微修改u(s,a),但是这个版本有您需要解释的所有组件。有了这个符号,你还可以写成表示落子选择

总之,您通过权衡先验概率和Q值来选择落子。当你遍历这棵树时,会积累访问次数,然后会得到更好的价值估计,然后你会慢慢忘记你先前的估计,并对Q值寄予越来越多的信任。你也可以说,你会更少地依赖先前的知识,而去更多地探索。这是与你自己的游戏体验相似。假设你整晚都在玩你最喜欢的策略棋盘游戏。在晚上开始的时候,你把你所有的经验都带到桌子上,但是随着时间的流逝,你(希望)尝试新的东西,并更新你对什么有效和什么无效的信念。

因此,这就是AlphaGo如何从现有树中选择落子,但是当你到达一个叶子l时,如何去扩展树?见图13.5。首先,要计算强策略网络P(L)的预测,并将它们存储为l的每个孩子节点的先验概率,然后通过将策略推出和价值网络结合起来去评估叶节点如下: 

 在这个等式中,value(l)是l节点的价值网络的结果,rollout(l)表示从l节点进行快速策略网络推出的游戏结果,λ是介于0到1之间的值,默认设置为0.5

图13.5 为了评估可能的棋局,AlphaGo结合了两个独立的评估。首先,它将棋局输入到其价值网络中,从而直接返回到估计的获胜机会。然后它使用快速策略网络从那个棋局开始完成游戏,并观察谁会赢。树搜索中使用的评价是这两个部分的加权和。

 退一步说,记住,你想使用树搜索去模拟总共n个游戏,从而到最后选择一个落子。为了使它发挥作用,您需要在模拟结束时更新访问计数和Q值。访问计数很容易;如果一个节点已被搜索遍历,则该节点的计数将增加1。若要更新Q值,请将所有访问过的叶节点l的V(L)除以访问计数加起来:

在这里,您所有的n个模拟加起来,并添加了第i次模拟的叶节点值,如果该模拟遍历了对应于(s,a)的节点。为了总结整个过程,让我们看看你是修改第四章中树搜索过程的四个步骤:

  1. 选择——您通过选择使Q(s,a)+u(s,a)最大化的操作来遍历游戏树。
  2. 扩展——当拓展一片新叶,你要求强大的策略网络一次存储每个孩子节点的先验概率。
  3. 评估——在模拟结束时,通过平均价值网络的输出来评估一个叶节点使用快速策略推出的结果。
  4. 更新——在所有模拟完成后,您将更新在模拟中遍历的访问计数和Q值。

只有一件事我们没有目前讨论,就是如何在模拟完成后选择一个落子点去下。这很简单:你要选择访问最多的节点!这可能看起来有点过于简单,但请记住节点随着时间的推移,随着他们的Q值提高,他们会得到越来越多的访问。在您经过足够的模拟后,节点访问计数将给您一个很好的指示落子的相对价值。

13.4.3 实现AlphaGo树搜索算法

在讨论了AlphaGo如何结合树搜索使用神经网络之后,让我们继续在Python中实现这个算法。您的目标是创建一个具有select_move方法的Agent。本节的代码可以在GitHub上的dlgo/agent/alphago.py下找到。

从AlphaGo树节点的完整定义开始,您在上一节已经使用了该节点。一个AlphaGoNode有一个父节点的和表示对其他落子节点的字典。节点还带有一个访问计数、一个Q值和一个先验概率。还有,您存储此节点的实用函数u_value。

class AlphaGoNode:
    def __init__(self,parent=None,probability=1.0):
        self.parent = parent  # 树节点有一个父亲和有潜力的多个孩子
        self.children = {}

        self.visit_count = 0
        self.q_value = 0
        self.prior_value = probability  # 一个叶节点价值通过先验概率初始化
        self.u_value = probability  # 搜索中更新的价值

树搜索算法将在三个地方使用这样的节点:

  1. choose_child——在模拟对局中遍历树,根据选择该节点下的子节点;要去选择其和值最大的落子
  2. expand_children——在一个叶子节点上,您将会让强策略来评估从这个局面下的所有合法落子,并为它们中的每一个添加新的AlphaGoNode实例。
  3. update_value——最后,在所有的模拟之后,您可以相应地更新visit_count、q_value和u_value

 前两种方法是直截了当的

    # 选择孩子节点,即为Q+U的最大值
    def select_child(self):
        return max(self.children.items(),
                   key=lambda child: child[1].q_value + child[1].u_value)

    # 扩展孩子
    def expand_children(self, moves, probabilities):
        for move, prob in zip(moves, probabilities):
            if move not in self.children:
                self.children[move] = AlphaGoNode(probability=prob)

第三种方法是更新AlphaGoNode的汇总统计信息,这种方法有点复杂。首先,您使用稍微复杂一点的实用函数版本:

与上一节介绍的版本相比,这个实用程序有两个额外的术语。第一个术语,我们在代码中称之为c_u,它对所有节点的效用进行了一个固定常数的缩放。默认情况下,我们设置为5。第二个术语进一步缩放父访问计数校园的平方根(您通过表示该节点的父节点),这导致了父节点被访问频繁节点的更高效用。

# 更新visit_count,q_value,u_value
    def update_values(self, leaf_value):
        if self.parent is not None:
            self.parent.update_values(leaf_value) # 您首先更新父母,以确保您从上到下遍历树
        self.visit_count += 1 #增加此节点的访问计数
        self.q_value += leaf_value / self.visit_count  # 将指定的叶值添加到Q值中,通过访问计数进行归一化。
        if self.parent is not None:
            c_u = 5
            self.u_value = c_u * np.sqrt(self.parent.visit_count) * self.prior_value / (1 + self.visit_count) # 用访问计数来更新u_value

 这就完成了AlphaGoNode的定义。您现在可以在AlphaGo中使用的搜索算法中使用此树结构。您将要实现的AlphaGoMCTS类是一个Agent,并且使用多个参数进行初始化。首先,您为此代理提供了一个快速和强大的策略和价值网络。其次,您需要为推出和评估去指定AlphaGo的特定参数:

  • lambda_value——这是您用来平衡推出和价值函数的l值:V(l)=l·value(l)+(1-l)·rollout(l)。
  • num_simulations——此值指定在落子的选择过程中将有多少模拟对局。
  • depth——使用此参数,您将告诉算法每次模拟要向前搜索多少步(您指定搜索深度)。
  • rollout_limit——当确定一个叶值时,您运行一个策略推出rollout(l)。在判断结果之前,您可以使用参数rollout_limited告诉AlphaGo要推出多少落子。
# AlphaGo结合MCTS
class AlphaGoMCTSAgent(Agent):
    """
       lambda_value——这是您用来平衡推出和价值函数的l值:V(l)=l·value(l)+(1-l)·rollout(l)。
       num_simulations——此值指定在落子的选择过程中将有多少模拟对局。
       depth——使用此参数,您将告诉算法每次模拟要向前搜索多少步(您指定搜索深度)。
       rollout_limit——当确定一个叶值时,您运行一个策略推出rollout(l)。在判断结果之前,您可以使用参数rollout_limited告诉AlphaGo要推出多少落子
    """
    def __init(self,policy_agent,fast_policy_agent,value_agent,lambda_value=0.5,num_simulations=1000,depth=50,rollout_limit=100):
        self.policy = policy_agent
        self.rollout_policy = fast_policy_agent
        self.value = value_agent

        self.lambda_value = lambda_value
        self.num_simulations = num_simulations
        self.depth = depth
        self.rollout_limit = rollout_limit
        self.root = AlphaGoNode()

 现在是时候实现这个新代理的select_move方法了,它在这个算法中做了几乎所有的提升。我们在前面的章节中勾画了AlphaGo的树搜索过程,但让我们再经历一次步骤:

  • 当你想下一个落子时,你做的第一件事是在你的游戏树上运行num_simulations次的模拟对局。
  • 在每一次模拟中,你都会进行前瞻搜索,直到达到指定的深度。
  • 如果一个节点没有任何子节点,则通过为每个合法落子添加新的AlphaGoNode来扩展树,并使用强大的策略网络给出先验概率。
  • 如果一个节点确实有子节点,那么通过选择使Q值最大的落子加上效用来选择一个节点。
  • 在围棋盘上落下此模拟中使用的落子点。
  • 当到达深度时,通过计算来自价值网络和策略推出的组合价值函数来评估叶节点。
  • 用模拟中的叶节点价值去更新所有AlphaGo节点。

这个过程正是你要在select_move中实现的。请注意,此方法使用了我们稍后将讨论的另外两种实用方法:policy_probabilitiespolicy_rollout

    # 选择落子
    def select_move(self, game_state):
        for simulation in range(self.num_simulations):  # 从当前游戏状态下模拟一系列对局
            current_state = game_state
            node = self.root
            for depth in range(self.depth):  # 到指定的深度到达前应用落子
                if not node.children:  # 如果当前节点没有任何子节点,就去扩展子节点
                    if current_state.is_over():
                        break
                    moves, probabilities = self.policy_probabilities(current_state)
                    node.expand_children(moves, probabilities)  # 从强策略网络扩展它们的孩子节点

                # 如果有孩子,就选择一个然后应用落子
                move, node = node.select_child()
                current_state = current_state.apply_move(move)

            # 计算价值网络的输出和快速策略的推出。
            value = self.value.predict(current_state)
            rollout = self.policy_rollout(current_state)

            # 确定组合价值函数
            weighted_value = (1 - self.lambda_value) * value + self.lambda_value * rollout

            # 在最后阶段更新此节点的值
            node.update_values(weighted_value)

 你可能已经注意到,虽然你运行了所有的模拟,但你仍然没有下任何落子。您通过下访问最多的节点,在此之后,剩下的唯一要做的事情就是设置一个新的根节点,并返回建议的落子

 # 下一步要挑选最受欢迎的孩子
        move = max(self.root.children, key=lambda move: self.root.children.get(move).visit_count)

        self.root = AlphaGoNode()
        # 如果选择的落子是子节点,则将新根节点设置为子节点
        if move in self.root.children:
             self.root = self.root.children[move]
             self.root.parent = None

        return move

这已经完成了AlphaGo的树搜索的主要过程,然后让我们来看看我们前面遗漏的两个实用程序方法。policy_probabilities,在节点扩展中,用强大的策略网络计算概率,将这些预测限制在合法的落子,然后将其余的预测规范化。该方法返回合法的落子和它们的规范化策略网络概率。

  # 强策略网络预测合法落子的概率
    def policy_probabilities(self, game_state):
        encoder = self.policy.encoder
        outputs = self.policy.predict(game_state)
        legal_moves = game_state.legal_moves()
        if not legal_moves:
            return [], []
        encoded_points = [encoder.encode_point(move.point) for move in legal_moves if move.point]
        # 合法落子输出
        legal_outputs = outputs[encoded_points]
        # 归一化
        normalized_outputs = legal_outputs / np.sum(legal_outputs)
        return legal_moves, normalized_outputs

您需要的最后一个辅助方法是policy_rollout,以使用快速策略计算一个推出的游戏结果。所有的方法都是贪婪地根据快速策略选择最强的落子直到达到推出限制,然后去看看谁赢了。如果玩家下一步落子获胜,就返回1;如果是另一个玩家获胜就返回-1,如果没有结果,则返回0。

    # 使用快速策略网络推出
    def policy_rollout(self, game_state):
        for step in range(self.rollout_limit):
            if game_state.is_over():
                break
            move_probabilities = self.rollout_policy.predict(game_state)
            encoder = self.rollout_policy.encoder
            valid_moves = [m for idx, m in enumerate(move_probabilities)
                           if Move(encoder.decode_point_index(idx)) in game_state.legal_moves()]
            
            # 得到最大合法落子点
            max_index, max_value = max(enumerate(valid_moves), key=operator.itemgetter(1))
            max_point = encoder.decode_point_index(max_index)
            greedy_move = Move(max_point)
            if greedy_move in game_state.legal_moves():
                game_state = game_state.apply_move(greedy_move)

        current_player = game_state.current_player
        winner = game_state.winner()
        if winner is not None:
            return 1 if winner == current_player else -1
        else:
            return 0

 通过开发Agent框架和实现AlphaGo代理,您现在可以使用AlphaGoMCTS实例轻松地下棋了

 

from dlgo.agent.ReinforcementLearning.ValueAgent import load_value_agent,ValueAgent
from dlgo.Encoder.AlphaGoEncoder import AlphaGoEncoder
from dlgo.agent.DeepLearningAgent.DeepLearningAgent import load_prediction_agent,DeepLearningAgent
from dlgo.agent.ReinforcementLearning.PolicyAgent import load_policy_agent,PolicyAgent
from dlgo.agent.AlphaGo.AlphaGo_MCTS import AlphaGoMCTSAgent
encoder = AlphaGoEncoder(19)

policy_network = load_prediction_agent("alphago_sl_policy.h5")
policy_network = DeepLearningAgent(model=policy_network.model,encoder=encoder)

fast_policy_network = load_policy_agent("alphago_rl_policy.h5")
fast_policy_network = PolicyAgent(model=fast_policy_network.model,encoder=encoder)

value_network = load_value_agent("alphago_value.h5")
value_network = ValueAgent(model = value_network.model,encoder=encoder)

AlphaGo = AlphaGoMCTSAgent(policy_agent=policy_network,fast_policy_agent=fast_policy_network,value_agent=value_network)

此代理可以与您在第7至12章中开发的其他代理一样采取完全相同的方式使用。特别是,您可以为此代理注册HTTP和GTP前端,就像您在前面章节中所做的那样,这样就可以对抗你的AlphaGo围棋机器人,并让其他机器人来对抗它,甚至可以在在线围棋服务器上注册并运行它。

13.5 实际考虑你自己AlphaGo的训练

在上一节中,您开发了AlphaGo使用树搜索算法的基本版本。这种算法可以导致AI超越人类,但你需要阅读这里的内容。您不仅需要确保您在训练AlphaGo中使用的所有三个深度神经网络时都做得很好,还需要确保树搜索中的模拟运行得很快,所以你不想等上几个小时才能得到AlphaGo的下一步。下面有几条建议让你充分利用:

  • 第一步训练,从KGS16万个对局上进行策略网络监督学习,总共大约3000万个游戏状态。DeepMind的AlphaGo团队总共计算了3.4亿个训练步骤。
  • 好消息是你使用了完全相同的数据集,DeepMind正是使用了我们在第7章中介绍的KGS训练集。原则上,没有什么能阻止你运行相同数量的训练步骤。坏消息是,即使你有一个最先进的GPU,训练过程也需要几个月,即使不是几年。
  • AlphaGo小组解决了这一问题,在50个GPU中分步式训练,将训练时间缩短到三周。这对您来说不太可能是一个选择,特别是因为我们还没有讨论如何以分布式的方式训练深度网络。
  • 你要获得满意结果能做的就是缩小方程的每一部分。使用第7章或第8章中的一个棋盘编码器,并使用比本章介绍的AlphaGo策略和价值网络小得多的网络。同时,要从一个小的训练集开始,这样你就会对训练过程有一种感觉。
  • 在自我对弈中,DeepMind创造了3000万个不同的局面,这远远超出了你的实际能力。作为一种经验法则,试着在监督学习中去生成与人类游戏局面一样多的自我对弈游戏局面。
  • 如果你只是简单地使用本章列出的大型网络在非常少的数据上,您可能会比在更多的数据上运行较小的网络更糟糕。
  • 快速策略网络在推出时经常被使用,所以为了加快树搜索速度,请确保您的快速策略网络在一开始真的很小,就像你在第6章中使用的网络一样。
  • 您在前一节中实现的树搜索算法依次模拟计算。为了加快这个过程,DeepMind进行并行化搜索,总共使用了40个搜索线程。在这个并行版本中,使用多个GPU并行评估深层网络,并使用多个CPU进行其他部分的树搜索。
  • 在多个CPU上运行树搜索在原则上是可行的(回想一下,您在第7章中也使用了多线程进行数据准备),但是有点太复杂了。
  • 你可以提高游戏体验的做法是以速度换水平,减少模拟运行的数量和搜索中使用的搜索深度,这样不会导致超人的表现,但至少这个系统变得可以用。

从这些观点可以看出,尽管用这种新颖的方式将监督学习、强化学习与树搜索结合起来是一个令人印象深刻的壮举,但工程方面的努力却变成了进展为了缩小网络训练、评估和树搜索,在建立第一个比顶级围棋棋手AI发挥得更好的方面,它有一席之地。

在最后一章中,你会看到阿尔法狗系统的下一个发展版本。它不仅跳过了对人类游戏记录的监督学习,而且比本章中实现的原始AlphaGo系统发挥得更强

13.6 总结 

  • 要加强一个AlphaGo系统,你必须要训练三个深度神经网络:两个策略网络和一个价值网络。
  • 快速策略网络是从人类游戏数据中训练出来的,必须是快速的。在AlphaGo的树搜索算法中要运行许多的推出。推出的结果被用来评估叶子的局面。
  • 强大策略网络首先使用人类数据进行培训,然后通过自我对弈,使用策略梯度算法。您可以在AlphaGo中使用此网络来计算节点选择的先验概率。
  • 价值网络是根据自我对弈生成的经验数据进行训练并使用策略推出用于叶节点的棋局评估。
  • AlphaGo选择落子意味着产生大量的模拟,遍历游戏树。在模拟步骤完成后,再去选择访问次数最多的节点。
  • 在模拟中,节点是通过最大化Q值加效用来选择的。
  • 当一片叶节点到达时,一个节点通过使用强策略的先验概率去扩展。
  • 一个叶节点通过将价值函数和最大化的价值网络输出与快速策略推出来评估。
  • 在算法的最后阶段根据所选择的落子去更新访问计数、Q值和u_value值。 
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值