先理解围棋为什么难,才能看懂 AlphaGo 难在哪、巧在哪。

下棋本质上是一棵巨大的博弈树:当前局面是根,每一手棋是一个分叉,对手的应对又是分叉,层层展开。理论上,只要把这棵树完全展开到终局,就能算出每一手的胜负,选最优的走。国际象棋的程序(如 1997 年击败卡斯帕罗夫的“深蓝”)大致就是这么干的——靠强大的搜索加上人工设计的局面评估函数。

但围棋的树太宽太深。每一手大约有 250 种合法选择(国际象棋约 35 种),一盘棋约 150 手,完全展开是 250150250^{150} 这个量级——远超任何算力。更麻烦的是评估难:给定一个中盘局面,连职业棋手都很难说清“这局面值多少”,没有简单的子力计算能像国际象棋那样估出优劣。围棋的“好坏”高度依赖全局形势的微妙判断,长期被认为只能靠人类的“棋感”。

所以攻克围棋需要同时解决两个问题:怎么不展开整棵树也能聪明地搜索(应对宽和深),以及怎么评估一个局面的价值判断哪些手值得考虑(应对评估难)。AlphaGo 的答案,是用两个神经网络解决后者,用一种带引导的搜索解决前者。


2016 年发表在《自然》上的 AlphaGo 论文,核心是两个深度卷积网络1

策略网络(policy network)p(as)p(a\mid s):输入当前棋盘(当作一张多通道的“图像”),输出每个落点的概率——也就是“凭直觉,这些地方值得下”。它把 250 个选择缩小到少数几个候选,解决了树太宽的问题。

价值网络(value network)v(s)v(s):输入棋盘,输出一个标量——“从这个局面出发,当前方最终获胜的概率有多大”。它直接给出局面评估,解决了围棋难以估值的问题。

这两个网络怎么训练?AlphaGo 分了两步,这也是它和后续版本的关键区别。第一步,监督学习——用人类高手的几十万局棋谱,训练策略网络去模仿人类落子(这是个标准的分类问题:给棋盘,预测人类下在哪)。第二步,强化学习——让网络通过自我对弈(self-play)继续提升:两个当前策略互相下棋,赢的一方的落子被强化(这正是第十四章的策略梯度),同时用自我对弈产生的大量“局面—胜负”数据训练价值网络1

到这里 AlphaGo 有了“直觉”(策略网络)和“判断力”(价值网络)。但光有直觉还不够稳——直觉会看走眼。它还需要在落子前,真正地往前算几步、推演对手的应对。这就是搜索的部分。


AlphaGo 用的搜索叫蒙特卡洛树搜索(Monte-Carlo Tree Search, MCTS)。在讲它怎么和神经网络结合前,先说清楚 MCTS 本身。

传统的树搜索要展开所有分支,MCTS 不这样——它有选择地、逐步地生长一棵搜索树,把算力集中在最有希望的分支上。每一次模拟(simulation)走四步:

  1. 选择(selection):从根节点出发,沿着树往下走,每一步选一个“既看起来好、又没怎么试过”的动作,直到走到一个还没充分展开的节点。
  2. 扩展(expansion):在那里新增一个子节点。
  3. 评估(evaluation):估计这个新节点的价值(传统 MCTS 用随机模拟走到终局看胜负,AlphaGo 用价值网络直接给分)。
  4. 回传(backup):把这个评估值沿着刚才的路径回传上去,更新沿途每个节点的统计。

跑成千上万次模拟后,根节点下被访问次数最多的那个动作,就是 MCTS 推荐的落子——访问多意味着反复模拟都觉得它好。

第一步“选择”是 MCTS 的灵魂,它要平衡探索(试试没怎么走过的分支)和利用(多走已知好的分支)。这个平衡的理论根,是 2006 年 Kocsis 与 Szepesvári 提出的 UCT 算法——他们把多臂老虎机里的 UCB1 准则用到了树搜索上2。AlphaGo 用的是它的一个变体 PUCT,把策略网络的先验概率也加了进来:

a=argmaxa [ Q(s,a)+cP(s,a)bN(s,b)1+N(s,a) ]a^* = \arg\max_a\ \Big[\ Q(s,a) + c\cdot P(s,a)\cdot \frac{\sqrt{\sum_b N(s,b)}}{1 + N(s,a)}\ \Big]

拆开看:Q(s,a)Q(s,a) 是这个动作目前模拟下来的平均价值(利用项);后一项是探索项——P(s,a)P(s,a) 是策略网络给的先验(“直觉觉得这手好”),N(s,a)N(s,a) 是访问次数。一个动作访问次数越少,探索项越大,越容易被再试;访问次数上来后,探索项衰减,决策转而由 QQ 主导。cc 调节探索的强度。这条公式让搜索被神经网络的直觉引导——策略网络先告诉搜索“往这几个方向看”,避免在 250 个选择里盲目铺开,价值网络则在叶子节点给出快速评估,不必每次都模拟到终局。


把 PUCT 引导的 MCTS 写成最小可运行的代码,能去掉很多神秘感。配套 code/15_mcts_puct.py(纯 numpy)用一个最简单的博弈“抢 21”(两人轮流报 1~3,累加,谁报到 21 谁赢)演示这套内核。它已知有必胜策略——抢到 1、5、9、13、17 的人锁定胜局,所以先手第一步应该报 1。

代码 · 15_mcts_puct.py
展开代码 · 15_mcts_puct.py
"""
第 15 章配套代码:MCTS + PUCT 最小实现示意(AlphaZero 风格)。
用一个最简单的确定性博弈("抢到 21":两人轮流报 1~3,谁报到 21 谁赢)演示
AlphaZero 的搜索内核——PUCT 选择 + 用一个(这里用先验均匀的)策略/价值"网络"
引导的蒙特卡洛树搜索。重点是 PUCT 公式与"选择→扩展→评估→回传"四步。
Runnable with: numpy only.  python3 15_mcts_puct.py
"""
import numpy as np

TARGET = 21
MOVES = [1, 2, 3]


def legal(total):
    return [m for m in MOVES if total + m <= TARGET]


class Node:
    def __init__(self, total, to_move):
        self.total = total          # 当前累计点数
        self.to_move = to_move      # 该谁走(+1 / -1)
        self.children = {}          # move -> Node
        self.N = {m: 0 for m in legal(total)}   # 访问次数
        self.W = {m: 0.0 for m in legal(total)} # 累计价值
        self.P = {}                 # 先验概率(策略网络输出,这里均匀)
        ms = legal(total)
        for m in ms:
            self.P[m] = 1.0 / len(ms) if ms else 0.0

    def is_terminal(self):
        return self.total == TARGET or not legal(self.total)


def puct_select(node, c=1.5):
    """PUCT: a* = argmax  Q(s,a) + c * P(s,a) * sqrt(ΣN) / (1+N(s,a))。"""
    total_N = sum(node.N.values())
    best, best_score = None, -1e9
    for m in node.N:
        q = node.W[m] / node.N[m] if node.N[m] > 0 else 0.0
        u = c * node.P[m] * np.sqrt(total_N + 1) / (1 + node.N[m])
        score = q + u
        if score > best_score:
            best_score, best = score, m
    return best


_solve_cache = {}


def solve(total):
    """完美评估器(代替价值网络):返回'当前待走方'在最优对弈下的价值 ±1。
    这扮演 AlphaZero 中 value 网络的角色——给搜索一个准确的叶子评估。"""
    if total in _solve_cache:
        return _solve_cache[total]
    ms = legal(total)
    if not ms:                       # 走不了 => 对手刚报到 21 => 当前方输
        _solve_cache[total] = -1.0
        return -1.0
    best = -1.0
    for m in ms:
        if total + m == TARGET:      # 当前方报到 21,直接赢
            best = 1.0
            break
        v = -solve(total + m)        # 轮到对手,对手价值取负
        best = max(best, v)
    _solve_cache[total] = best
    return best


def rollout_value(total, to_move, rng):
    """叶子评估:调用完美评估器(demo 里把 value 网络理想化为精确解)。"""
    return solve(total)


def mcts(root_total, root_mover, sims=2000, seed=0):
    rng = np.random.default_rng(seed)
    root = Node(root_total, root_mover)
    for _ in range(sims):
        node = root
        path = []
        # 1) 选择:沿 PUCT 下行到未扩展或终止
        while not node.is_terminal() and all(node.N[m] > 0 for m in node.N):
            m = puct_select(node)
            path.append((node, m))
            node = node.children[m]
        # 2) 扩展 + 3) 评估
        if not node.is_terminal():
            m = puct_select(node)
            path.append((node, m))
            child = Node(node.total + m, -node.to_move)
            node.children[m] = child
            value = -rollout_value(child.total, child.to_move, rng)  # 站在父节点视角
        else:
            value = 0.0
        # 4) 回传(沿路径交替取负,因为零和博弈视角每层翻转)
        for parent, mv in reversed(path):
            parent.N[mv] += 1
            parent.W[mv] += value
            value = -value
    return root


def report():
    print("=== MCTS + PUCT 最小示意('抢21'博弈,先手必胜)===")
    print("规则: 轮流报 1~3,累加,谁报到 21 谁赢。")
    print("已知最优: 抢到 17/13/9/5/1 的人必胜(先手报1即锁胜)。\n")
    # 从空局(先手待走)跑 MCTS,看它是否找到 '先报 1'
    root = mcts(0, +1, sims=4000)
    print("根节点(先手第一步)各动作的访问次数 N 与均值价值 Q:")
    for m in sorted(root.N):
        q = root.W[m] / root.N[m] if root.N[m] else 0.0
        print(f"  报 {m}: N={root.N[m]:5d}  Q={q:+.3f}")
    best = max(root.N, key=lambda m: root.N[m])
    print(f"\nMCTS 选择的第一步: 报 {best}  (最优解=报1,使对手面对 20 必败)")
    print("机制: PUCT = Q + c·P·√ΣN/(1+N),前期靠先验P探索、后期靠Q利用;")
    print("AlphaZero 把这里的随机 rollout 换成 value 网络,把均匀 P 换成 policy 网络。")


if __name__ == "__main__":
    report()

↓ 下载 15_mcts_puct.py

代码里的 PUCT 选择和四步循环:

def puct_select(node, c=1.5):
    total_N = sum(node.N.values())
    for m in node.N:
        q = node.W[m] / node.N[m] if node.N[m] > 0 else 0.0
        u = c * node.P[m] * np.sqrt(total_N + 1) / (1 + node.N[m])
        score = q + u            # PUCT = 利用 Q + 探索 c·P·√ΣN/(1+N)

把里面充当“价值网络”的叶子评估用精确解给出(演示理想化的 critic),跑 4000 次模拟,根节点各动作的访问统计:

  报 1: N= 3868  Q=+0.007
  报 2: N=   67  Q=-0.463
  报 3: N=   65  Q=-0.477
MCTS 选择的第一步: 报 1  (最优解=报1,使对手面对 20 必败)

搜索把绝大部分模拟(3868/4000)都投在了正确的那一手上——这就是 MCTS 的本事:它不均匀地展开所有分支,而是被价值评估和先验概率引导,把算力雪崩式地集中到好的方向。AlphaGo 做的事,本质上就是把这里理想化的“精确评估”换成价值网络的估计、把均匀先验换成策略网络的输出,在围棋这棵天文数字的树上跑同样的循环。代码注释里写得明白:“AlphaZero 把这里的随机 rollout 换成 value 网络,把均匀 P 换成 policy 网络。”


AlphaGo 击败李世石后,DeepMind 没有停步,而是开始做减法——一步步去掉系统里依赖人类知识的部分,看强化学习能纯靠自己走多远。

AlphaGo Zero(2017)3。它砍掉了那个最像“作弊”的部分——人类棋谱。AlphaGo Zero 完全从零开始,不看任何人类对局,只知道围棋规则,从随机乱下起步,纯靠自我对弈学习。结构也更简洁:策略和价值合并成一个网络(输出一个落子概率分布 pp 和一个价值 vv),且不再用 MCTS 之外的随机模拟,评估完全交给价值网络。

它的训练循环极其优雅,是这一章值得记住的核心:用当前网络引导 MCTS 下一盘自我对弈;MCTS 经过大量模拟后给出的落子分布,比网络自己的原始直觉 pp 更强(因为它做了前瞻搜索);于是把“网络的输出”朝“MCTS 的结果”训练——让策略去模仿搜索后的落子分布,让价值去预测这盘自我对弈的真实胜负。损失大致是:

=(zv)2πlogp+cθ2\ell = (z - v)^2 - \boldsymbol{\pi}^\top \log \mathbf{p} + c\lVert\theta\rVert^2

其中 zz 是自我对弈的真实结果(±1),vv 是价值网络的预测,π\boldsymbol\pi 是 MCTS 给出的(更强的)落子分布,p\mathbf p 是策略网络的原始输出。第一项让价值学准胜负,第二项让策略向“搜索增强后的自己”靠拢。这是一个自我提升的飞轮:网络引导搜索 → 搜索产生比网络更强的策略 → 用它来改进网络 → 更强的网络引导更强的搜索。AlphaGo Zero 从零开始训练几天,就超过了那个击败李世石的版本,且下出了许多人类几千年来未曾发现的定式3。它说明:人类棋谱不仅不是必需的,反而可能是一种束缚——纯自我对弈能探索到人类经验之外的空间。


减法还在继续。

AlphaZero(2018)4。它把 AlphaGo Zero 里任何围棋特有的处理也去掉,变成一个通用的自我对弈算法——同一套方法、同一种网络,不做任何针对性改动,分别学会了围棋、国际象棋和将棋,都达到或超过了各自领域此前最强的程序(国际象棋上击败了顶级引擎 Stockfish)。AlphaZero 证明了那个“自我对弈 + MCTS + 神经网络”的飞轮不是围棋专属,而是一个适用于一大类完美信息博弈的通用范式。

MuZero(2020)5。这是减法的极致——它连游戏规则都不给。前面所有版本都需要知道规则,因为 MCTS 要靠规则来推演“走这一手之后棋盘变成什么样”。MuZero 不知道规则,于是它自己学一个环境模型:不是去预测真实的下一帧画面(那既难又没必要),而是学一个隐空间里的动力学——给定当前隐状态和一个动作,预测下一个隐状态、即时奖励、以及策略和价值。然后 MCTS 就在这个学出来的隐空间模型里做前瞻搜索5

MuZero 的意义是把两条线接通了。前面的 AlphaGo 系是“已知模型 + 搜索”,第十三章的 DQN 系是“未知模型 + 无搜索的试错”。MuZero 是“学到的模型 + 搜索”——它在围棋、国际象棋、将棋上追平 AlphaZero,同时在 Atari 上(那里没有现成规则可用,正是 DQN 的地盘)也达到了顶尖水平。一个系统,既能下棋又能打游戏,靠的是“自己学一个够用的世界模型,然后在里面规划”。这个“学世界模型再规划”的思路,和第二十一类节点里 World Models、Dreamer 那条 model-based 强化学习的线索遥相呼应——它们都在赌一件事:智能体如果能在脑中模拟世界,就能比纯试错高效得多。


自我对弈的范式在棋类上登顶后,DeepMind 和 OpenAI 几乎同时把它推向了更难的战场——即时、不完全信息、多人的电子游戏,这里没有回合制的从容,没有完全可见的棋盘。

AlphaStar(2019)攻克了《星际争霸 II》6。这个游戏的难点叠满了:实时(每秒要做多个操作)、信息不完全(战争迷雾遮住对手)、动作空间巨大(成百上千种操作组合)、需要长达几十分钟的长程战略规划。AlphaStar 的关键创新是 league training(联盟训练)——不是让一个智能体自我对弈,而是维护一整个“联盟”里许多风格各异、相互克制的智能体,让它们彼此对抗、共同进化,避免陷入“只会一种套路、被针对就崩”的局部最优。最终 AlphaStar 达到了星际争霸的宗师(Grandmaster)段位,超过 99.8% 的人类玩家6

OpenAI Five(2019)攻克了《Dota 2》的 5v5 团队对战7。它的路线和 AlphaStar 不同——不靠精巧的联盟设计,而是靠规模:用上一章讲的 PPO,在上千块 GPU 上做大规模分布式自我对弈,每天积累相当于数百年的游戏经验,硬生生把团队配合、资源运营、长期战略从海量试错中磨出来,最终击败了 Dota 2 的世界冠军战队 OG7。OpenAI Five 是“规模本身能带来能力”这个信念(第五、十一章那条暗线)在强化学习领域的一次有力印证——同样的 PPO,喂足够多的算力和经验,就能解决曾被认为需要特殊技巧的复杂协作问题。

到这里,强化学习这条线在“和环境/对手博弈”的世界里抵达了顶峰:从 Atari 到围棋到星际到 Dota,机器在一个又一个曾被认为是人类智能堡垒的领域里登顶。但这些成就有一个共同前提——它们都活在游戏里,一个奖励明确(赢或输、分数高低)、可以无限次重来、试错不要钱的世界。当这套强大的“从奖励中学习决策”的能力,要走出游戏、回到第十、十一章那个语言模型的世界时,最棘手的问题浮现了:奖励从哪来? 现实里没有人给“一段回答”自动打分。下一章讲强化学习如何带着它在游戏里磨出的全部武器,回流到语言模型,去解决这个奖励难题——从 RLHF 的人类偏好,到 2025 年用“可自动验证的对错”重新定义奖励的推理模型。这条正交了八十年的主线,将在那里与主干合为一体。

配套的 manim 动画 assets/manim/ch15_alphago.pyMCTSGrowthSelfPlayFlywheel 两个 Scene)把两个内核演出来:前者按“选择→扩展→评估→回传”四步演示蒙特卡洛树搜索如何朝最有希望的分支生长——PUCT 选路、扩展叶子、value 网络估值、再把估值沿路径回传更新祖先;后者把自博弈画成一个三节点循环——当前最强网络自我对弈生成数据、训出更强网络、再回到自博弈,每转一圈都更强,无需任何人类棋谱。


本质

AlphaGo 一族真正的突破,是把两种各有短板的能力拧成了一股绳。神经网络(policy/value)能在一瞬间给出对局面的直觉判断,但单凭直觉会看走眼;显式的前瞻搜索(MCTS)能推演很多步、纠正直觉的错误,但若没有方向、在围棋这种天文数字的分支里盲目展开就是徒劳。把它们结合:用网络的直觉给搜索指方向、剪掉绝大多数无谓分支,再用搜索的结果回过头来纠正、提纯网络的直觉——搜索让网络变准,网络让搜索变快。而自博弈则解决了“老师从哪来”的问题:让当前最强的版本和自己对弈,产生的数据天然就比它现在的水平略高一点,用这点数据训出更强的版本,再自博弈,如此螺旋上升。它不需要人类棋谱,因为它自己持续生成着略微超出自己的目标。这套“搜索与学习互相提纯、并用自博弈自举”的机制,是强化学习迄今最干净也最有力的一次思想结晶。


参考文献

  1. Silver, D., Huang, A., Maddison, C. J., et al. (2016). Mastering the game of Go with deep neural networks and tree search. Nature, 529, 484–489. 策略网络(选点)+ 价值网络(评局)+ MCTS;监督学习人类棋谱起步 + 自我对弈强化学习;5:0 胜欧洲冠军樊麾,后 4:1 胜李世石。Nature:https://www.nature.com/articles/nature16961 ;DeepMind 博客:https://deepmind.google/discover/blog/alphago-mastering-the-ancient-game-of-go-with-machine-learning/

  2. Kocsis, L., & Szepesvári, C. (2006). Bandit Based Monte-Carlo Planning. ECML 2006, LNCS 4212, 282–293. 把 UCB1 多臂老虎机准则用于树搜索(UCT),平衡探索/利用,给出有限样本界;AlphaGo 的 PUCT 是其加策略先验的变体。Springer:https://link.springer.com/chapter/10.1007/11871842_29 ;PDF:http://ggp.stanford.edu/readings/uct.pdf

  3. Silver, D., Schrittwieser, J., Simonyan, K., et al. (2017). Mastering the game of Go without human knowledge. Nature, 550, 354–359. AlphaGo Zero:去掉人类棋谱、单一 (p,v)(p,v) 网络、纯自我对弈;自提升飞轮(网络引导搜索→搜索产生更强策略→改进网络);损失 =(zv)2πlogp+cθ2\ell=(z-v)^2-\boldsymbol\pi^\top\log\mathbf p+c\lVert\theta\rVert^2。Nature:https://www.nature.com/articles/nature24270

  4. Silver, D., Hubert, T., Schrittwieser, J., et al. (2018). A general reinforcement learning algorithm that masters chess, shogi, and Go through self-play. Science, 362(6419), 1140–1144. AlphaZero:通用自我对弈算法,仅给规则、无领域知识,学会围棋/国象/将棋并击败各自最强程序(含 Stockfish)。Science:https://www.science.org/doi/10.1126/science.aar6404

  5. Schrittwieser, J., Antonoglou, I., Hubert, T., et al. (2020). Mastering Atari, Go, chess and shogi by planning with a learned model. Nature, 588, 604–609. MuZero:不给规则,自学隐空间环境模型(预测下一隐状态/奖励/策略/价值),在学到的模型里做 MCTS 规划;棋类追平 AlphaZero,Atari 达顶尖。Nature:https://www.nature.com/articles/s41586-020-03051-4 ;博客:https://deepmind.google/discover/blog/muzero-mastering-go-chess-shogi-and-atari-without-rules/

  6. Vinyals, O., Babuschkin, I., Czarnecki, W. M., et al. (2019). Grandmaster level in StarCraft II using multi-agent reinforcement learning. Nature, 575, 350–354. league training(多智能体相互演化策略);不完全信息、实时、长程;达宗师段位,超 99.8% 人类玩家。Nature:https://www.nature.com/articles/s41586-019-1724-z

  7. OpenAI, Berner, C., Brockman, G., Chan, B., et al. (2019). Dota 2 with Large Scale Deep Reinforcement Learning. 大规模分布式 PPO(上千 GPU、每天数百年自我对弈经验);5v5 团队博弈击败世界冠军 OG。arXiv:https://arxiv.org/abs/1912.06680 ;OpenAI 博客:https://openai.com/research/openai-five-defeats-dota-2-world-champions

在一棵从根生长的搜索树上演示 MCTS 四步(选择→扩展→评估→回传),并以玩具博弈「抢21」的真实访问统计 N=3868/67/65 让节点按访问次数雪崩式点亮——访问最多的「报1」正是必胜手。
把 PUCT 选择公式逐项拆开同色高亮(Q利用蓝/P策略先验棕/探索项灰),再用「抢21」根节点的真实 Q、P、Q+U 数值双轨算出报1综合最高,本质上演示策略网络指方向、价值网络估胜率如何引导搜索。
把自博弈画成三节点闭环(网络→引导MCTS自我对弈→产出更强的π→训练改进网络),配自提升损失 ℓ=(z−v)²−πᵀlog p+c‖θ‖² 两项分别高亮,中心实力光晕阶梯式变亮演示螺旋上升,并以 AlphaGo→Zero→AlphaZero→MuZero 的「减法」收尾。