翻譯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值。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章