无反向传播训练范式对比:前向前向、GRPO与只学赢棋的黑白棋实验

发布时间:2026/10/20 23:50:51
无反向传播训练范式对比:前向前向、GRPO与只学赢棋的黑白棋实验
最近我把一小块业余时间全砸在了8x8黑白棋上不是为了怀旧而是想给自己出一道题如果完全不靠反向传播只靠前向前向Forward-Forward、GRPO、只学赢棋这三种信号一个智能体到底能不能学会下棋这个问题不是拍脑袋想出来的我手头有两个项目都受困于反向传播的显存墙和串行特性所以迫切想知道有没有更轻量、更局部、更贴近“边看边学”的训练范式。黑白棋规则简单、终局信号明确天然适合做这种训练范式的试验田。我把三个训练臂写在同一个环境里共享同一套网络结构只改“梯度从哪来、谁提供监督信号”最终得到了一组很有意思的对照结果。这篇文章把实验设计、实现细节、踩坑过程和结论全部摊开来讲无论你是做强化学习的、研究非BP训练范式的还是只想看看棋类AI怎么折腾的应该都能从中挖到点东西。1. 为什么拿黑白棋来“逼”自己放弃反向传播1.1 反向传播的三大痛点先说痛点不然你不会理解我为什么要折腾这些替代方案。反向传播的核心问题有三条。第一是显存瓶颈训练过程中每一层的中间激活值都得存下来供回传时使用网络越深、批量越大显存占用几乎是线性暴涨我手头只有单卡环境稍微把网络加宽一点就爆显存。第二是串行等待前向传播必须全部走完、算出全局损失之后梯度才能从最后一层逐层往回传中间每一层都得干等着这跟流水线质检一模一样——所有工位都要等最后质检结果出来才决定要不要返工。第三是硬伤只要网络里出现离散动作采样、不可微操作反向传播就断了而下棋恰恰每一步都是离散动作。这三点叠加起来逼着我去找“不需要把全局损失从最后一层一路传回第一层”的训练方法。如果能在小棋盘上验证可行那这套思路就有机会迁移到那些网络更大、决策更离散、显存更紧张的真实任务上。1.2 黑白棋为什么适合当试验田黑白棋是我能想到的最合适的验证环境原因很具体。一盘棋最多60步封顶终局分数就是唯一的输赢依据不需要像麻将、扑克那样处理隐藏信息合法着法平均只有10个左右动作空间小到可以暴力枚举但又不是小到没有策略深度8x8的棋盘状态完全可观测网络输入只是一个二维局面连手工特征工程都不用做。更关键的是对局速度极快。我用纯Python写的环境加规则判断单机双进程自我对弈一盘平均也就一两秒一个晚上能攒下上万局训练数据。这意味着我可以快速做三臂反复实验每次改完超参睡一觉起来就有结果。对研究“训练范式”这种问题来说短反馈周期比什么都珍贵。1.3 三个“手臂”代表的三种思路我选的三种方法恰好代表了三种完全不同的改造方向。前向前向算法改的是优化目标本身。它不再依赖全局损失而是给每一层网络都配一个局部的小目标让每一层自己判断“我提取的特征有没有用”。梯度只在该层内部计算不会跨层传播所以根本不需要把整条网络的全局损失算完。GRPO改的是优化流程。它走的是强化学习路线但不搞价值网络而是从当前策略里采样一组动作用这组动作的最终收益之间的相对差异来估计“哪个动作更好”相当于用组内排名代替全局评估。只学赢棋改的是数据流。它最朴素打完一局之后直接把输棋一方的全部轨迹扔掉只保留赢棋方的局面和动作作为监督信号让网络照着“赢棋时自己在做什么”去克隆。三个手臂共享完全相同的棋盘编码和网络结构唯一区别就是“谁给梯度、梯度长什么样”。这样控制了变量之后对比出来的结果才真正有说服力。2. 三种无BP训练方法的核心机制2.1 前向前向让每一层都变成小评委前向前向的思路可以这样理解传统网络像一个层层汇报的单线组织底层发现什么只能通过顶层最终的损失来感知自己有没有做对而前向前向把整条网络拆成一组独立的小评委每一层只对自己的输出负责。具体做法是把一个训练样本同时准备成两份一份是正样本一份是负样本。正样本在黑白棋场景里就是“当前玩家最终会赢”的局面负样本就是把同样局面翻转角色变成“当前玩家最终会输”的视角。网络前向传播跑两次一次喂正样本一次喂负样本每一层都根据“正样本输出能量高、负样本输出能量低”来调整自己那层的权重。层的输出能量通常用激活值的平方和来度量损失函数可以用hinge形式或者二分类交叉熵。我用PyTorch实现时关键是要把每一层的输入detach掉这样才能保证梯度不往更早的层回传真正实现“局部学习”。核心代码大概是这样的for pos_x, neg_x in batch: pos pos_x.detach() neg neg_x.detach() for i, layer in enumerate(model.layers): pos_out layer(pos) neg_out layer(neg) pos_good (pos_out ** 2).mean() neg_good (neg_out ** 2).mean() loss torch.clamp(1.0 - (pos_good - neg_good), min0.0) layer.zero_grad() loss.backward() layer_optimizers[i].step() pos pos_out.detach() neg neg_out.detach()这样每一层都只根据自己这层的输入输出做调整前面层的表征怎么变化都不会直接影响后面层的梯度。我最开始觉得这种“各扫门前雪”的方式会学不到全局协调但实验结果证明只要每层的目标和最终胜负严格挂钩层层筛选出来的特征天然就会向“有助于赢棋”的方向集中。2.2 GRPO没有价值网络也能算优势GRPO的核心思路是从当前策略中采样一组动作让它们分别走完对局拿到最终收益再把这组收益的内部相对差当作优势信号。传统Policy Gradient方法需要一个critic网络来估计状态价值而这本身又是一套要训练的模型还会引入估计偏差。GRPO绕开价值网络直接用采样组的平均值做基线。放到黑白棋里是这样实现的当前局面下策略网络输出所有合法着法的概率分布这个概率分布本身不是拿来直接取argmax的而是从中采样6个动作。每个动作各自往后续走完整盘棋得到一个终局奖励赢是1输是-1平局是0。然后把这6个收益的平均值当作基线减去基线之后得到的差值就是每个动作的相对优势。策略更新的目标和语言模型对齐任务里常用的形式一致包含裁剪和KL惩罚两部分ratio (new_log_probs - old_log_probs).exp() adv (returns - returns.mean()) / (returns.std() 1e-6) pg_loss -torch.min( ratio * adv, ratio.clamp(1 - eps, 1 eps) * adv ).mean() kl_loss (new_log_probs - old_log_probs).mean() total_loss pg_loss beta * kl_loss黑白棋是交替落子的所以每一步动作到底好还是坏其实要等到整局结束才知道。我在实验里把同一个终局奖励复制给这条轨迹上的所有动作相当于用蒙特卡洛回报来近似动作价值。这种方法方差很大但组内相对优势的设计恰好能在一定程度上抵消方差因为同一组动作都经历了差不多的后续环境它们的收益差距更能反映“这一步选择”本身的好坏。2.3 只学赢棋把输棋轨迹直接扔进垃圾桶只学赢棋可能是三个方法里最“反直觉”的别人都在研究怎么从错误中学习它偏偏只看赢棋的对局。实现上极简单每次自我对弈结束按当前玩家视角把整盘轨迹记录下来如果这个玩家的最终净胜子数大于0就把这一整条轨迹上的局面和动作保留下来作为监督信号如果输了就整条丢弃。训练目标就是最大化保留轨迹上每个动作的log概率本质上是在做行为克隆只是克隆的对象是“自己赢棋时的行为”。这种方法其实就是极端版的优先经验回放把优先级直接粗暴地设为“赢棋才有采样资格”。它之所以在黑白棋这种场景里能work是因为黑白棋的终局信号完全确定赢棋轨迹里绝大多数状态-动作对确实是具有正价值的哪怕其中混了一些“赢棋局面下的废棋”整体信噪比也足够高。不过这里有个隐藏风险如果双方都用只学赢棋来训练数据分布就会自我收缩——因为一个人赢棋靠的是对手犯错而对手一旦也开始只学赢棋就不那么容易犯同样的错了于是这个策略很容易锁定在几套固定开局上中盘应变能力偏弱。这一点在后面对战矩阵里体现得非常明显。2.4 三臂机制对照表维度前向前向GRPO只学赢棋监督信号来源局部的正负样本能量差采样组内的相对收益差赢棋轨迹本身是否需要反向传播不需要只做单层局部梯度不需要强化学习采样式更新不需要最大似然更新是否需要价值网络不需要不需要不需要每次更新需要的环境交互量较少只要局面和胜负标签较多每组6个动作都要走完中打完一局筛轨迹对数据多样性的要求中高极高否则过拟合套路从表格能看出三个方法的共同点是完全绕开了“从输出层往输入层传梯度”的全局依赖不同点在于它们各自用什么替代信号来引导网络。理解了这个对照后面看实现细节和结果就顺了。3. 实验环境搭建与数据管道设计3.1 棋盘编码与动作掩码既然要做三臂对照棋盘编码和网络结构必须完全一致否则结果没法比较。我的编码很简单两个8x8通道第一个通道表示当前玩家的棋子位置第二个通道表示对手的棋子位置。当前玩家的坐标永远是1对手永远是0这样切换视角时只需要直接翻转两个通道互换不需要额外标记“轮到谁”。每一局开始前我都把棋盘做随机的旋转和镜像增广黑白棋棋盘有8重对称性这相当于免费把数据量乘了8倍。动作掩码的处理也要单独说。策略网络输出的是8x8共64个位置的logits但只有在合法落子位置的logits才参与计算softmax其余位置一律mask掉。很多新手会直接在全部64个位置上做softmax再过滤这会严重扭曲合法着法的概率分布因为被mask掉的位置的分母并没有消失。正确做法是把非法位置的logits设成负无穷再softmax或者在softmax之后进行掩码归一化。3.2 对局数据从哪来三个手臂的数据来源都是训练中的自我对弈但采样方式略有差异。前向前向不需要完整走完一盘棋才出标签因为它只需要“当前局面最终谁赢”这个标签来构造正负样本所以它可以从一局的任意中间局面取样本只要能等到终局结果即可。GRPO必须在每个决策点都采样一组动作然后为每个动作把对局走下去这一步交互成本最高。只学赢棋最简单打完一整局按胜负结果筛一遍轨迹就行。为了公平我给三个手臂分配了相同的总训练对局预算模型每更新一次就额外完成一盘新的自我对弈采样。实际跑起来后前向前向和只学赢棋一晚上能各跑3000多局GRPO因为每组6个动作都要展开完整对局同样时间只能跑约1200局所以后面评估时我按照“训练对局数”而不是“墙钟时间”来衡量进度。3.3 共享网络结构网络结构我选了非常轻量的小型CNN参数少到单卡CPU都能快速跑但又不至于弱到学不会基本策略网络层配置卷积层13x3卷积32通道LeakyReLU卷积层23x3卷积64通道LeakyReLU卷积层33x3卷积64通道LeakyReLU全连接层1256节点LeakyReLU输出层64节点logits前三层卷积负责提取棋盘局部形状特征比如边角控制、连续子串、可翻转方向等全连接层负责整合全局局面输出层经过合法动作掩码后得到策略分布。前向前向在局部更新时我会把前三层当作“可局部学习”的块每层接一个自己的输出能量全连接层则当作最后一层用同样的正负能量规则更新。3.4 训练循环设计三个手臂各自跑独立进程避免相互干扰。每个训练循环都严格按照“采样对局 → 构造训练样本 → 更新网络 → 重新采样”的顺序推进。关键超参数我统一列一下超参数前向前向GRPO只学赢棋学习率0.001每层独立0.00050.001批量大小25632组128采样组大小G-6-裁剪系数eps-0.2-KL系数beta-0.05-正负样本比例1:1--经验池大小10000无20000前向前向和只学赢棋都加了经验池用来打散前后对局之间的强相关性GRPO因为策略更新后会立刻重新采样就没额外加池而是靠组内采样本身增加多样性。4. 三个训练器的实现细节与踩坑记录4.1 前向前向实现正负样本怎么造、损失怎么算前向前向最微妙的地方是正负样本构造。我试过两种方案。第一种直接把某个局面在最终胜方视角下当作正样本换个视角当作负样本这样同一个局面天然就是一对正负样本非常干净。第二种用最终净胜子数大于某个阈值的局面当正样本净负的子数当负样本中间模糊局面丢掉。实测第一种更稳因为它构造的是对立关系而不是绝对值大小关系正负样本的难度天然匹配。损失函数我一开始用了简单的交叉熵形式把正样本的能量过sigmoid之后希望预测为1负样本预测为0。但跑着跑着发现能量数值容易饱和sigmoid输入太大之后梯度就消失了。后来改成hinge形式即clamp(1 - (pos_good - neg_good), 0)效果稳定很多。这个损失的本质是让正样本输出能量比负样本输出能量至少大1做不到就惩罚。很像经典SVM的间隔思想。踩坑记录前向前向最容易出现的问题是整个网络“躺平”——某层发现无论输入是什么输出的能量都差不多于是正负样本的间隔始终在0附近打转网络完全停止学习。我定位到原因是局部损失的回传范围太窄单层的更新信号太弱。解决办法是给每层配上独立的学习率调度让更靠近输入层的层有更大一些的初始学习率同时给全连接层加了一个很小的Dropout强制它不能只依赖某一条固定路径。4.2 GRPO实现细节组内采样、裁剪和KL的坑GRPO的实现里动作采样和轨迹复用是最关键的部分。我在每个局面下采样6个动作每个动作带着一个copy环境往前走完整盘棋拿到最终收益。这个流程看起来简单但实际跑的时候有几个坑。第一个坑是KL爆炸。策略网络在训练早期概率分布特别尖锐很容易把某个单个动作的概率推到接近1其他动作几乎为0。这时如果组内采样恰好采到一个低概率动作却赢了它的优势会非常大比率乘优势之后更新幅度直接失控策略随后变得极度保守甚至输出概率矩阵出现NaN。解决方法是把KL惩罚系数beta从0.01逐步调到0.05并且对优势做标准化组内收益先减均值再除以标准差把优势拉回到量纲一致的尺度。第二个坑是合法动作掩码与采样概率分布的耦合问题。当我用掩码softmax得到分布再采样时如果某些合法着法的概率太低浮点精度下几乎不可能被采到那组内样本就会长期只有少数几个动作相对优势的估计几乎失效。我在采样时加入了温度参数训练早期温度设为1.5后期降到0.8保证早期探索充分。第三个坑是组内收益抖动过大。黑白棋的胜负经常只差一两个边角同一组6个动作可能打出5胜1负也可能反过来。这种高方差会让优势信号忽正忽负更新方向不稳定。我的办法是除了KL惩罚还给策略更新加了一个最大更新范数裁剪每次更新的参数量变化超过阈值就整体缩放这个trick让GRPO的训练曲线平滑了很多。4.3 只学赢棋实现细节筛选、增广与自我收缩只学赢棋的实现看起来只有一行代码“if score 0: 保留”但真正跑起来问题不少。最大的问题是数据多样性快速收缩。因为自我对弈双方都在进化早期的赢棋轨迹里大量是“对手乱下然后我赢”的样本这些样本很快就被网络学透了再用它们训练就只是在巩固早期套路对中盘的应变更敏感的对局反而少见。我加了两个缓解措施。第一经验池里不只存最新对局而是按固定比例混合不同年代的轨迹让模型不至于完全跟着最新自举分布走。第二对保留的轨迹做棋盘对称增广每一条轨迹变成8条增加了同一个局面下的等价格子变化对抗策略单一化有一定帮助。踩坑记录只学赢棋最容易出现的一种症状是“开局强、中盘崩”——模型对少数几套开局定式记得滚瓜烂熟一旦对手在中盘走出没见过的形状策略输出就变成随机乱下。我观察训练日志发现这种模型的训练loss下降很快但评估胜率不涨反跌。定位之后我临时在训练时加了一部分输棋轨迹做负监督——只加不更新只用于统计分布差异。这个诊断方法让我确认了问题确实出在数据多样性上后来靠经验池混合比例调成“最新轨迹三成、旧轨迹七成”解决了大半。4.4 三臂各自的坑横向对比方法常见故障典型症状定位方式缓解手段前向前向单层躺平正负能量间隔长期为0逐层输出能量可视化每层独立学习率、加Dropout前向前向能量饱和正样本能量超高但梯度消失观察sigmoid输入分布改为hinge损失GRPOKL爆炸策略概率分布尖锐、出现NaN监控KL值和概率熵加大beta、优势标准化GRPO采样失效组内动作总是集中在少数几个统计采样动作覆盖数加温度调度、扩大组数只学赢棋数据自我收缩训练loss降但评估胜率不涨对比新旧轨迹胜率混合旧轨迹、增广棋盘只学赢棋开局过拟合中盘应变能力极差手动下几步非定式棋经验池比例调低最新轨迹这张表是我这次实验最值钱的部分。三种方法分别卡过我两三天时间每次都是日志排查加小实验才定位到根因。如果你以后也要跑类似的非BP训练器直接拿这张表去对照能少走很多弯路。5. 三臂对照测试从对战矩阵看差异5.1 评测协议训练到相同对局数之后我冻结所有策略网络开始两两对战。每个配对打100盘先后手各50盘黑白棋的先手优势不算大但为了公平还是各占一半。评判指标用两个一个是胜率一个是平均净胜子数。净胜子数比胜率更敏感能反映“赢了是险胜还是碾压”。对战过程中所有网络都关闭训练使用确定性动作选择——就是取策略分布里概率最大的合法着法不是为了探索随机采样。这样才能真实反映当前策略的棋力水平而不是靠随机性碰运气。5.2 对战矩阵结果训练臂行方对手前向前向对手GRPO对手只学赢棋对手BP基线前向前向-44%胜率57%39%GRPO56%-48%47%只学赢棋43%52%-41%BP基线61%53%59%-这个结果出来的时候我看了很久。首先三种无BP方法里GRPO的总体水平最高和BP基线打成47%对53%只差6个百分点算得上虽败犹荣。其次前向前向比想象中稳健它能明显压制只学赢棋但被GRPO反压。再次只学赢棋虽然下限不低、能打赢GRPO但面对前向前向和BP基线都明显吃亏。三者之间居然形成了近乎“石头剪刀布”的循环克制——GRPO赢前向前向前向前向赢只学赢棋只学赢棋赢GRPO。这个循环给了我很多启发。5.3 为什么会出现“三角克制”先说GRPO为什么能赢前向前向。GRPO的训练信号来自整局终局胜负所以它的策略偏向“全局性侵略”会主动抢边角、制造翻转链这种打法对前向前向那种“层层层叠特征但缺少全局通盘考虑”的风格有效因为前向前向的每层局部目标再准也没法在全局计划上和真正的收益信号对齐。前向前向为什么能赢只学赢棋。前向前向虽然保守但它的正负样本构造会迫使网络学习“哪些局面容易赢、哪些局面容易输”的边界综合起来就是局面评估能力比较全面。只学赢棋的样本全部来自自我对弈的赢棋轨迹开局打法高度统一前向网络恰恰能捕捉到这些固定套路的弱点比如哪个开局步骤会留下角位隐患于是总能占到便宜。只学赢棋为什么能赢GRPO。这一点最反直觉。GRPO的强势风格会主动制造高风险翻盘局面而只学赢棋由于只见过自己的赢棋轨迹那些轨迹里恰好包含很多“在对方激进下法下怎么稳住优势”的模式。换句话说GRPO的激进撞上了只学赢棋的数据强化区反而被克。5.4 和BP基线的距离到底有多大我额外跑了一个BP基线用的是经典策略梯度加价值网络Actor-Critic。它的棋力是四个里最高的对三个无BP方法都取得了明显优势。这个结果不意外因为反向传播本质上是拿着全局目标对每一层精确问责信息带宽远大于局部学习方法。但差距没有想象中大。GRPO打到47%胜率说明无BP方法在棋类这种中等复杂度任务上已经能逼近传统方法了。前向前向的39%也属于“有一定棋力但不稳定”只学赢棋的41%则是典型的“上限受限”。考虑到BP基线用了额外的价值网络和更成熟的调参经验三臂能到这个水平我觉得已经足够说明“不用反向传播学下棋”这条路是走得通的。6. 从结果反推这三种范式分别适合什么问题6.1 前向前向适合的场景前向前向最吸引人的点不是棋力而是它的“局部性”。在超大网络上反向传播需要保存所有中间激活值显存压力极大而前向前向每层算完就能立刻丢弃输入显存占用只跟单层宽度有关跟网络深度基本无关。如果你的任务里网络特别深、显存又有限制前向算法这种“一层一层处理、算完就扔”的特性就是实打实的优势。另一个适合它的场景是持续学习。传统反向传播在增量学习里容易灾难性遗忘因为全局梯度会把旧任务的权重粗暴覆盖。前向前向的每层局部目标天然对“新任务进来时只更新相关层”更友好至少损失不会跨层扩散。我的实验里前向网络对只学赢棋的克制也说明它的表征相对稳健不容易被单一套路带偏。6.2 GRPO适合的场景GRPO这一趟跑下来给我的感觉是“为稀疏奖励而生的屠龙刀”。它不需要价值网络所以省掉了价值网络训练不稳定的问题它只要你能从策略里采样、能拿到最终收益就能算优势。这特别适合那些奖励信号非常稀疏但要能采样的任务比如对话生成、代码生成、机器人控制里的稀疏奖励设定。它在黑白棋里最有价值的表现是全局进攻性。对比曲线时能明显看到GRPO训练的棋风是“敢下”很多前向算法和只学赢棋根本不敢试的边角争夺GRPO都敢去碰而且碰对了收益巨大。如果你的任务里需要探索动作空间里比较深的支线GRPO这种组内相对排名的思路比纯行为克隆靠谱得多。6.3 只学赢棋适合与不适合的地方只学赢棋的价值在于极度简单一行筛选代码训练目标就是交叉熵谁都能实现。在那些任务比较简单、终局胜负信号明确、数据量容易堆的场景里它会是一个够用的快速基线。我甚至觉得它可以当课程学习的第一课——先让模型学会“赢棋时大概会怎么走”给后续更复杂的训练器提供一份靠谱的初始策略。但它的天花板也很明显。交叉克制的实验结果里只学赢棋成了唯一被其他两个无BP方法都找到明显破绽的选手根源就是数据分布自我收缩。在那些需要长期规划、credit assignment链条很长、或者对手也在不断进化的任务里只学赢棋大概率会在某个固定套路里撞墙。6.4 组合打法三臂联用既然三种方法各自有长短板自然可以组合着用。我在实验收尾阶段试了一个组合先用只学赢棋训练300盘当预热让网络快速建立基本棋感然后切换成前向前向继续训练500盘让每层表征细化最后用GRPO做300盘细调强化全局进攻性。这个组合打BP基线胜率从单个GRPO的47%提到了51%虽然仍然没超过BP但差距已经很小了。这个结果说明三种方法不是互斥的它们分别在“快速入门”“精细表征”“全局优化”三个阶段起作用。如果你要做自己的无BP方案建议优先考虑这种分段切换的路径而不是死磕某一个方法。最后说点实际体会。这次实验让我对反向传播的看法有了实质转变——它确实是一种极其高效的问责机制但它不是唯一的学习机制。前向前向用局部能量差分发问责GRPO用组内相对排名分发问责只学赢棋用数据筛选分发问责本质上都是在回答同一个问题“这一层、这一步、这个样本到底做得好不好”只要你能构造出一个像样的“局部问责信号”你就拿到了绕过反向传播的钥匙。真要在生产环境里选型的话我的建议是显存卡脖子就上前向前向奖励稀疏但有采样条件就上GRPO任务简单求快就只学赢棋当跳板条件允许就三段式组合。这样玩下来离了反向传播照样能把棋下明白。