AlphaGo Zero你也來造一隻,PyTorch實現五臟俱全| 附代碼

2021-01-20 量子位
原作 Dylan Djian
慄子棋 編譯整理
量子位 出品 | 公眾號 QbitAI



遙想當年,AlphaGo的Master版本,在完勝柯潔九段之後不久,就被後輩AlphaGo Zero (簡稱狗零) 擊潰了。

從一隻完全不懂圍棋的AI,到打敗Master,狗零隻用了21天



而且,它不需要用人類知識來餵養,成為頂尖棋手全靠自學

如果能培育這樣一隻AI,即便自己不會下棋,也可以很驕傲吧。

於是,來自巴黎的少年Dylan Djian (簡稱小笛) ,就照著狗零的論文去實現了一下。



他給自己的AI棋手起名SuperGo,也提供了代碼 (傳送門見文底) 。

除此之外,還有教程——

一個身子兩個頭

智能體分成三個部分:

一是特徵提取器 (Feature Extractor) ,二是策略網絡 (Policy Network) ,三是價值網絡 (Value Network) 。

於是,狗零也被親切地稱為「雙頭怪」。特徵提取器是身子,其他兩個網絡是腦子

特徵提取器

特徵提取模型,是個殘差網絡 (ResNet) ,就是給普通CNN加上了跳層連接 (Skip Connection) , 讓梯度的傳播更加通暢。



跳躍的樣子,寫成代碼就是:

1class BasicBlock(nn.Module):
2    """
3    Basic residual block with 2 convolutions and a skip connection
4    before the last ReLU activation.
5    """ 
6
7    def __init__(self, inplanes, planes, stride=1, downsample=None):
8        super(BasicBlock, self).__init__()
9
10        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=3,
11                        stride=stride, padding=1, bias=False)
12        self.bn1 = nn.BatchNorm2d(planes)
13
14        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
15                        stride=stride, padding=1, bias=False)
16        self.bn2 = nn.BatchNorm2d(planes)
17
18
19    def forward(self, x):
20        residual = x
21
22        out = self.conv1(x)
23        out = F.relu(self.bn1(out))
24
25        out = self.conv2(out)
26        out = self.bn2(out)
27
28        out += residual
29        out = F.relu(out)
30
31        return out

然後,把它加到特徵提取模型裡面去:

1class Extractor(nn.Module):
2    def __init__(self, inplanes, outplanes):
3        super(Extractor, self).__init__()
4        self.conv1 = nn.Conv2d(inplanes, outplanes, stride=1,
5                        kernel_size=3, padding=1, bias=False)
6        self.bn1 = nn.BatchNorm2d(outplanes)
7
8        for block in range(BLOCKS):
9            setattr(self, "res{}".format(block), \
10                BasicBlock(outplanes, outplanes))
11
12
13    def forward(self, x):
14        x = F.relu(self.bn1(self.conv1(x)))
15        for block in range(BLOCKS - 1):
16            x = getattr(self, "res{}".format(block))(x)
17
18        feature_maps = getattr(self, "res{}".format(BLOCKS - 1))(x)
19        return feature_maps

策略網絡

策略網絡就是普通的CNN了,裡面有個批量標準化 (Batch Normalization) ,還有一個全連接層,輸出概率分布



1class PolicyNet(nn.Module):
2    def __init__(self, inplanes, outplanes):
3        super(PolicyNet, self).__init__()
4        self.outplanes = outplanes
5        self.conv = nn.Conv2d(inplanes, 1, kernel_size=1)
6        self.bn = nn.BatchNorm2d(1)
7        self.logsoftmax = nn.LogSoftmax(dim=1)
8        self.fc = nn.Linear(outplanes - 1, outplanes)
9
10
11    def forward(self, x):
12        x = F.relu(self.bn(self.conv(x)))
13        x = x.view(-1, self.outplanes - 1)
14        x = self.fc(x)
15        probas = self.logsoftmax(x).exp()
16
17        return probas

價值網絡

這個網絡稍微複雜一點。除了標配之外,還要再多加一個全連接層。最後,用雙曲正切 (Hyperbolic Tangent) 算出 (-1,1) 之間的數值,來表示當前狀態下的贏面多大。



代碼長這樣——

1class ValueNet(nn.Module):
2    def __init__(self, inplanes, outplanes):
3        super(ValueNet, self).__init__()
4        self.outplanes = outplanes
5        self.conv = nn.Conv2d(inplanes, 1, kernel_size=1)
6        self.bn = nn.BatchNorm2d(1)
7        self.fc1 = nn.Linear(outplanes - 1, 256)
8        self.fc2 = nn.Linear(256, 1)
9
10
11    def forward(self, x):
12        x = F.relu(self.bn(self.conv(x)))
13        x = x.view(-1, self.outplanes - 1)
14        x = F.relu(self.fc1(x))
15        winning = F.tanh(self.fc2(x))
16        return winning

未雨綢繆的樹

狗零,還有一個很重要的組成部分,就是蒙特卡洛樹搜索 (MCTS) 。

它可以讓AI棋手提前找出,勝率最高的落子點。

在模擬器裡,模擬對方的下一手,以及再下一手,給出應對之策,所以提前的遠不止是一步

節點 (Node)

樹上的每一個節點,都代表一種不同的局勢,有不同的統計數據:

每個節點被經過的次數n,總動作值w,經過這一點的先驗概率p,平均動作值q (q=w/n) ,還有從別處來到這個節點走的那一步,以及從這個節點出發、所有可能的下一步

1class Node:
2    def __init__(self, parent=None, proba=None, move=None):
3        self.p = proba
4        self.n = 0
5        self.w = 0
6        self.q = 0
7        self.children = []
8        self.parent = parent
9        self.move = move

部署 (Rollout)

第一步是PUCT (多項式上置信樹) 算法,選擇能讓PUCT函數 (下圖) 的某個變體 (Variant) 最大化,的走法。


寫成代碼的話——

1def select(nodes, c_puct=C_PUCT):
2    " Optimized version of the selection based of the PUCT formula "
3
4    total_count = 0
5    for i in range(nodes.shape[0]):
6        total_count += nodes[i][1]
7
8    action_scores = np.zeros(nodes.shape[0])
9    for i in range(nodes.shape[0]):
10        action_scores[i] = nodes[i][0] + c_puct * nodes[i][2] * \
11                (np.sqrt(total_count) / (1 + nodes[i][1]))
12
13    equals = np.where(action_scores == np.max(action_scores))[0]
14    if equals.shape[0] > 0:
15        return np.random.choice(equals)
16    return equals[0]

結束 (Ending)

選擇在不停地進行,直至到達一個葉節點 (Leaf Node) ,而這個節點還沒有往下生枝。

1def is_leaf(self):
2    """ Check whether a node is a leaf or not """
3
4    return len(self.children) == 0

到了葉節點,那裡的一個隨機狀態就會被評估,得出所有「下一步」的概率。

所有被禁的落子點,概率會變成零,然後重新把總概率歸為1。

然後,這個葉節點就會生出枝節 (都是可以落子的位置,概率不為零的那些) 。代碼如下——

1def expand(self, probas):
2    self.children = [Node(parent=self, move=idx, proba=probas[idx]) \
3                for idx in range(probas.shape[0]) if probas[idx] > 0]

更新一下

枝節生好之後,這個葉節點和它的媽媽們,身上的統計數據都會更新,用的是下面這兩串代碼。

1def update(self, v):
2    """ Update the node statistics after a rollout """
3
4    self.w = self.w + v
5    self.q = self.w / self.n if self.n > 0 else 0

1while current_node.parent:
2    current_node.update(v)
3    current_node = current_node.parent

選擇落子點

模擬器搭好了,每個可能的「下一步」,都有了自己的統計數據。

按照這些數據,算法會選擇其中一步,真要落子的地方。

選擇有兩種,一就是選擇被模擬的次數最多的點。試用於測試和實戰。

另外一種,隨機 (Stochastically) 選擇,把節點被經過的次數轉換成概率分布,用的是以下代碼——

1total = np.sum(action_scores)
2probas = action_scores / total
3move = np.random.choice(action_scores.shape[0], p=probas)

後者適用於訓練,讓AlphaGo探索更多可能的選擇。

三位一體的修煉

狗零的修煉分為三個過程,是異步的。

一是自對弈 (Self-Play) ,用來生成數據。

1def self_play():
2    while True:
3        new_player, checkpoint = load_player()
4        if new_player:
5            player = new_player
6
7        
8        results = create_matches(player, cores=PARALLEL_SELF_PLAY,
9                                         match_number=SELF_PLAY_MATCH) 
10        for _ in range(SELF_PLAY_MATCH):
11            result = results.get()
12            db.insert({
13                "game": result,
14                "id": game_id
15            })
16            game_id += 1

二是訓練 (Training) ,拿新鮮生成的數據,來改進當前的神經網絡。

1def train():
2    criterion = AlphaLoss()
3    dataset = SelfPlayDataset()
4    player, checkpoint = load_player(current_time, loaded_version) 
5    optimizer = create_optimizer(player, lr,
6                                    param=checkpoint['optimizer'])
7    best_player = deepcopy(player)
8    dataloader = DataLoader(dataset, collate_fn=collate_fn, \
9                batch_size=BATCH_SIZE, shuffle=True)
10
11    while True:
12        for batch_idx, (state, move, winner) in enumerate(dataloader):
13
14            
15            if total_ite % TRAIN_STEPS == 0:
16                pending_player = deepcopy(player)
17                result = evaluate(pending_player, best_player)
18
19                if result:
20                    best_player = pending_player
21
22            example = {
23                'state': state,
24                'winner': winner,
25                'move' : move
26            }
27            optimizer.zero_grad()
28            winner, probas = pending_player.predict(example['state'])
29
30            loss = criterion(winner, example['winner'], \
31                            probas, example['move'])
32            loss.backward()
33            optimizer.step()
34
35            
36            if total_ite % REFRESH_TICK == 0:
37                last_id = fetch_new_games(collection, dataset, last_id)

訓練用的損失函數表示如下:

1class AlphaLoss(torch.nn.Module):
2    def __init__(self):
3        super(AlphaLoss, self).__init__()
4
5    def forward(self, pred_winner, winner, pred_probas, probas):
6        value_error = (winner - pred_winner) ** 2
7        policy_error = torch.sum((-probas * 
8                                (1e-6 + pred_probas).log()), 1)
9        total_error = (value_error.view(-1) + policy_error).mean()
10        return total_error

三是評估 (Evaluation) ,看訓練過的智能體,比起正在生成數據的智能體,是不是更優秀了 (最優秀者回到第一步,繼續生成數據) 。

1def evaluate(player, new_player):
2    results = play(player, opponent=new_player)
3    black_wins = 0
4    white_wins = 0
5
6    for result in results:
7        if result[0] == 1:
8            white_wins += 1
9        elif result[0] == 0:
10            black_wins += 1
11
12    
13    
14    if black_wins >= EVAL_THRESH * len(results):
15        return True
16    return False

第三部分很重要,要不斷選出最優的網絡,來不斷生成高質量的數據,才能提升AI的棋藝。

三個環節周而復始,才能養成強大的棋手。

年幼的SuperGo

小笛用學校的伺服器訓練了AI棋手一星期

SuperGo還年幼,是在9x9棋盤上訓練的。


小笛說,他的AI現在好像還不懂生死一類的事,但應該已經知道圍棋是個搶地盤的遊戲了。

雖然,沒有訓練出什麼超神的棋手,但這次嘗試依然值得慶祝。

Reddit上面也有同仁發來賀電。

△ 有前途的意思

有志於AI圍棋的各位,也可以試一試這個PyTorch實現。

代碼實現傳送門:
https://github.com/dylandjian/SuperGo

教程原文傳送門:
https://dylandjian.github.io/alphago-zero/

AlphaGo Zero論文傳送門:
https://www.nature.com/articles/nature24270.epdf

最後一句

昨天 (8月2日) ,是柯潔的生日。



量子位AI社群18群開始招募啦,歡迎對AI感興趣的同學,在量子位公眾號(QbitAI)對話界面回復關鍵字「交流群」,獲取入群方式;


此外,量子位專業細分群(自動駕駛、CV、NLP、機器學習等)正在招募,面向正在從事相關領域的工程師及研究人員。


進群專業群請在量子位公眾號(QbitAI)對話界面回復關鍵字「專業群」,獲取入群方式。(專業群審核較嚴,敬請諒解)

量子位正在招募活動運營實習生,策劃執行AI明星公司CEO、高管等參與的線上/線下活動,有機會與AI行業大牛直接交流。工作地點在北京中關村。簡歷歡迎投遞到quxin@qbitai.com


具體細節,請在量子位公眾號(QbitAI)對話界面,回復「實習生」三個字。

相關焦點

  • 自學圍棋的AlphaGo Zero,你也能用PyTorch造一個 | 附代碼實現
    從一隻完全不懂圍棋的AI,到打敗Master,狗零隻用了21天。而且,它不需要用人類知識來餵養,成為頂尖棋手全靠自學。如果能培育這樣一隻AI,即便自己不會下棋,也可以很驕傲吧。於是,來自巴黎的少年Dylan Djian (簡稱小笛) ,就照著狗零的論文去實現了一下。他給自己的AI棋手起名SuperGo,也提供了代碼(傳送門見文底) 。
  • 教你用PyTorch實現「看圖說話」(附代碼、學習資源)
    這將會幫助你更深刻地掌握各個主題,成為一名更好的DeepLearning實踐者。這篇文章將和大家一起看一個有趣的多模態主題,我們將結合圖像和文本處理技術來構建一個有用的深度學習應用,即看圖說話(ImageCaptioning)。看圖說話是指從一個圖像中基於其中的對象和動作生成文本描述的過程。例如:
  • 雲計算學習:用PyTorch實現一個簡單的分類器
    主要流程分為以下三個部分:1,自定義生成一個訓練集,具體為在二維平面上的一些點,分為兩類;2,構建一個淺層神經網絡,實現對特徵的擬合,主要是明白在 pytorch 中網絡結構如何搭建;3,完成訓練和測試部分的工作,熟悉 pytorch 如何對網絡進行訓練和測試。1.
  • 獨家 | 教你使用torchlayers 來構建PyTorch 模型(附連結)
    附連結:https://github.com/szymonmaszke/torchlayers與Keras中的操作類似,上述的操作不需要用戶幹預(除了調用一次torchlayers.build)。torchlayers 的GitHub 主頁提供了一些例子來展示它的一些功能。我喜歡這個圖像和文本分類二合一的例子(見下面連結)!
  • PyTorch實現TPU版本CNN模型
    為了克服訓練時間的問題,我們使用TPU運行時環境來加速訓練。為此,PyTorch一直在通過提供最先進的硬體加速器來支持機器學習的實現。PyTorch對雲TPU的支持是通過與XLA(加速線性代數)的集成實現的,XLA是一種用於線性代數的編譯器,可以針對多種類型的硬體,包括CPU、GPU和TPU。
  • 分離硬體和代碼、穩定 API,PyTorch Lightning 1.0.0 版本正式發布
    博客地址:https://medium.com/pytorch/pytorch-lightning-1-0-from-0-600k-80fc65e2fab0 GitHub 地址:https://github.com/PyTorchLightning/pytorch-lightningPyTorch Lightning 的運行原理和目標人工智慧的發展速度比單一框架發展要快得多
  • 手把手:AlphaGo有啥了不起,我也能教你做一個(附Python代碼)
    窮盡你對未來的想像,用你試過最多的招數來應對。在你考慮了未來的可能性之後,採取你已經探索過的行動。在遊戲結束時,回過頭來評估你在哪裡錯誤地判斷了未來的位置,並相應地更新你的理解。這聽起來是不是很像你學玩遊戲的方式?
  • 用Java實現目標檢測|PyTorch
    魚羊 編輯整理量子位 報導 | 公眾號 QbitAI編者按:作為一個Java開發者,你是否曾為在PyTorch上部署模型而苦惱?這篇來自AWS軟體工程師的投稿,結合實例,詳細介紹了DJL這個為Java開發者設計的深度學習庫:5分鐘,你就能在PyTorch上,用Java實現目標檢測。
  • PyTorch最佳實踐,教你寫出一手風格優美的代碼
    我們在 list 對象前使用「*」操作來展開它。 在前向傳導過程中,我們直接使用輸入數據運行模型。 PyTorch 環境下的簡單殘差網絡 在這裡,ResNet 模塊的跳躍連接直接在前向傳導過程中實現了,PyTorch 允許在前向傳導過程中進行動態操作。
  • 一行代碼安裝,TPU也能運行PyTorch,修改少量代碼即可快速移植
    現在福利來了,一個叫做Pytorch Lightning的項目,可以讓你幾乎修改代碼的情況下用上TPU。Pytorch Lightning已經上傳到PyPI,因此只需一行代碼就能安裝這個軟體。pip install pytorch-lightning該項目的開發者William Falcon說,PyTorch Lightning是他在紐約大學和FAIR做博士生時研發,專門為從事AI研究的專業研究人員和博士生創建的。
  • 一行代碼即可調用18款主流模型!PyTorch Hub輕鬆解決論文可復現性
    有時候論文讀者經常為了調用各種經典機器學習模型,還要重複造輪子。隨著提交給arXiv以及各種會議上的論文數量開始暴漲,可復現性的重要性也越來越凸顯。很多論文選擇隨文附上代碼和訓練模型,在一定程度上對使用者起到了幫助作用,但成效並不明顯。復現過程中,仍有大量工作需要論文讀者自己摸索。PyTorch Hub是什麼?
  • 用Java實現目標檢測 | PyTorch
    這篇來自AWS軟體工程師的投稿,結合實例,詳細介紹了DJL這個為Java開發者設計的深度學習庫:5分鐘,你就能在PyTorch上,用Java實現目標檢測。5分鐘,用Java實現目標檢測文 / 知乎用戶@LankingPyTorch在深度學習領域中的應用日趨廣泛,得益於它獨到的設計。
  • 華為雲應用編排,手把手教您完成pytorch代碼部署
    本文將以一個使用了pytorch的demo代碼pytorch-classify為例,通過華為雲上的容器服務一鍵式部署, 5 分鐘完成免費的雲上pytorch代碼的部署。傳統部署方式首先是準備環境。先有個伺服器,這臺伺服器需要能夠被外部訪問。
  • 幾行代碼教你在Pytorch和Python中實現神經風格遷移
    從左到右為:內容圖像、風格圖像、生成的圖像這個方法相當直觀,本文是在pytorch和python中實現神經風格遷移的簡單指南,並預先解釋了這個方法。Github完整代碼:https://github.com/ksivaman/Transfer-image-styling理解風格和內容神經風格遷移涉及到將一個圖像的風格遷移到另一個圖像的內容上。它們分別是什麼?內容就是圖像的組成部分。可能是風景、海灘環境、花園裡的貓、動物園裡的長頸鹿等等……在典型的圖像分類網絡中,基本上是圖像的標籤。
  • 60分鐘入門深度學習工具PyTorch
    autograd包為張量上的所有操作提供了自動求導.它是一個運行時定義的框架,這意味著反向傳播是根據你的代碼如何運行來定義,並且每次迭代可以不同.weight=weightlearning_rategradient我們可以使用簡單的Python代碼實現這個規則。
  • 《PyTorch中文手冊》來了
    PyTorch 是一個深度學習框架,旨在實現簡單靈活的實驗。自 2017 年初首次推出,PyTorch 很快成為 AI 研究人員的熱門選擇並受到推崇。conda create -n pytorch python=3.6# 切換到 pytorch 環境activate pytorch#安裝 GPU 版本,根據 cuda 版本選擇 cuda80,
  • 給訓練踩踩油門:編寫高效的PyTorch代碼技巧
    正因為它是一個非常常用的運算操作,如果過度使用可以導致代碼變得低效。這裡給出一個例子來展示它是如何導致代碼變得低效的。而如果沒辦法自己手動實現批次的運算操作,那麼可以採用 TorchScript 來提升代碼的性能。TorchScript 是一個 Python 函數的子集,但經過了 PyTorch 的驗證,PyTorch 可以通過其 just in time(jtt) 編譯器來自動優化 TorchScript 代碼,提高性能。
  • Pytorch中的分布式神經網絡訓練|pytorch|bat|拆分|調用_網易訂閱
    經常,在訓練這些網絡時,深度學習從業人員需要使用多個GPU來有效地訓練它們。 在本文中,我將向您介紹如何使用PyTorch在GPU集群上設置分布式神經網絡訓練。  以下是用於實現累積漸變的PyTorch代碼段。
  • Pytorch 0.3.0 發布,實現多方面提速,增加對ONNX支持
    根據官方文檔的介紹,此次增加了多個函數和功能,多方面的性能均實現提升。重大變更0.3 版本中刪掉了 Variable.reinforce() 等隨機函數,因為它們的功能有限,並且會影響到性能。設置隨機函數的初衷是為了避免採樣值的 book-keeping,在實際中,由於各種各樣的原因,用戶的代碼中仍然會包括 book-keeping。
  • 讓PyTorch更輕便,這款深度學習框架你值得擁有!GitHub 6.6k星
    話不多說,我們就來看看這個輕量版的「PyTorch」。關於LightningLightning將DL/ML代碼分為三種類型:研究代碼、工程代碼、非必要代碼。針對不同的代碼,Lightning有不同的處理方式。這裡的研究代碼指的是特定系統及其訓練方式,比如GAN、VAE,這類的代碼將由LightningModule直接抽象出來。