九月初有个东西在推特上炸了:果蝇的大脑在玩 Doom。
接着是 Minecraft、超级马里奥 64、赛车、拳皇、Chrome 小恐龙。Google AI 官方账号转了一波。全都跑在同一份数据上——MaleCNS v1.0,一只雄果蝇的完整中枢神经系统,166,700 个神经元、3.1 亿个突触接触,六月放出来,九月三号论文上 Cell。
我扒了十四个项目的仓库文档。所有人都在问同一个问题:生物进化调出来的连线,比随机接线强吗?
没有一个人跑完那个对照。
不是没想到。FlyDoom 把保度随机重连的代码都写好了,跑完的结果表没有。Flyhard 把它列在「E08 未来计划」。Connectome Fighter 的协议文档里写了,状态页写着「尚未证明生物结构优势」。
我跑了。两个游戏、十组对照、三层判据。打平。
想直接看数字的去第 3 章。想知道我怎么被自己的探针骗了两次、以及第二次骗得多离谱,看第 5 章。
1. 十四个项目,和那个共同的空白
先说这批项目在干什么。它们的形状惊人地一致:拿连接组的连线当固定网络,前面接一个手写的感官编码器(把游戏画面变成神经元输入),后面接一个手写的动作解码器(把神经元活动变成按键),中间那坨真实连线不动。
然后问:这坨真实连线,有用吗?
要回答这个问题只有一种方法——把连线打乱,其他全不变,再跑一遍。打乱也有讲究,不能瞎连:得保住每个神经元的入度和出度(谁收多少条、发多少条不变),只换「接给谁」。这叫保度随机重连。不这么做的话,真实连线赢了你也说不清是拓扑的功劳,还是单纯因为某几个神经元连得特别多。
我逐个读了十四个仓库的 README 和方法文档。按「有没有跑完这个对照」分类:
代码写了,结果没有:FlyDoom。仓库里实现了保度重连和匹配的随机图基线,README 写着「A null or negative result is a valid answer; this project does not assume that biological topology helps」。这是研究态度,不是已得结果。
列在未来计划:Flyhard。原文写得很清楚:「It does not establish that fly topology is better than a shuffled graph or an ordinary policy.」
协议有了,成绩没有:Connectome Fighter。状态文档承认尚未证明塑性带来可复现的行为改善,也未证明生物结构优势。
主动声明没测:Swat 的作者写了一句我很欣赏的话——「No graph advantage over shuffled wiring has been claimed or tested.」
真的跑了,但只跑了零样本:fly-craftax。这是唯一一个有真实对照数字的:
真实图 生存 154.8 步
打乱图 生存 164.9 步
断开连接 生存 168.0 步
作者自己的判词是 no separation。三档打平,甚至断开连接的还最高。但他只做了零样本(不训练直接跑),没做「真实图和重连图各自等预算训练一遍」。
剩下九个,要么在比「塑性开/关」,要么是固定网络的交互演示,要么在做输入输出的因果检查(把视觉输入掐掉,看动作会不会变)。都是有价值的工作,但都不回答拓扑那个问题。
这里有个我自己差点搞混的地方,值得单独说:「学习规则有没有用」和「拓扑有没有用」是两件事。
FlyPong 报告了负结论——它的多巴胺塑性实验没让球拍打得更好。Doomfly 报告得更狠,学习不但没用,还有害:一个测试起点上,学习分支和时序打乱分支都在 3.657 秒死掉,冻结权重的分支活到了 8 秒上限,擦除记忆之后又恢复成冻结分支的表现。原文用词是 the observed difference was harmful。
但这两个比的都是塑性开关,不是拓扑。它们证明「这套局部学习规则没收益」,不证明「这坨连线没收益」。我第一次看列表描述的时候就混淆了这两件事,扒完仓库才纠正过来。
2. 我接的那个回路
我没接全脑,只接了蘑菇体——果蝇的学习中枢,苍蝇记住「这个气味之后会挨电击」就靠它。
从 MaleCNS 里提出来是这样:
投射神经元 ALPN 686 个 ← 感官输入进来
凯尼恩细胞 KC 4,064 个 ← 中间层,稀疏编码
输出神经元 MBON 97 个 ← 决策输出
多巴胺神经元 DAN 340 个 ← 奖惩信号
─────────
5,187 个节点
中间那两条连线是真数据:PN→KC 22,586 条边、KC→MBON 61,210 条边。
生物学上这套回路的工作方式很直观:气味进来激活一批 KC(每次只有 200 个左右被激活,非常稀疏),如果这时候挨了电击,多巴胺神经元就会把「刚才活跃的那批 KC」到「趋近型输出神经元」之间的连接压低。下次同样的气味进来,趋近的驱动变弱,苍蝇就躲开了。
学习规则长这样:
dw = -eta × KC活跃度 × (多巴胺 - 基线)
三个因子相乘,全是局部信息——突触前的活动、突触后的通道、一个全局的多巴胺信号。没有反向传播。 这是这类项目最吸引人的地方:它是真的按生物学的方式学,不是把连接组当成一个初始化矩阵然后拿梯度下降去训。
我把它接到了 CartPole(那个平衡杆子的经典任务)和 LunarLander(登月着陆)上。
这里必须先说清楚一件事,因为它是所有这类项目的共同软肋:前后两端是我手搓的。 状态怎么变成神经元输入、神经元活动怎么变成按键,全是人工设计的。上游那个开赛车的项目在这一层做得更露骨——它的代码里有个函数叫 calibrate(target_action_overlap=0.5),意思是「把不同动作的神经编码重叠率调到 50%」,这是个手调的目标值。作者自己在文档里写了,把这个参数调好之后完赛率从个位数涨到了每分钟 100 圈。
所以接口层可以调出巨大的性能差异,而它跟连接组一点关系都没有。
这是我整个实验设计里最较真的一条:随机重连组必须用真实组算出来的那套接口参数,一个字都不许重新校准。 我把这条写进了参数校验里——脚本检测到你换了回路但没提供冻结接口文件,直接拒绝运行:
if args.circuit != DEFAULT and not args.interface:
p.error('alternative circuit requires --interface; no independent recalibration')
写进注释是提醒,写进校验才是约束。
3. 两个游戏,十组对照,打平
先看随机重连做得干不干净。保度重连的质量有个直接判据——重连之后还剩多少条原来的边:
PN→KC 原边保留 4.72%
KC→MBON 原边保留 25.64%
每个神经元的入度、出度、以及全部权重的分布完全没变,但接给谁基本全换了。不是换了个名字的原图。
CartPole,五组初始化各训 20,000 步(分数是杆子不倒的步数,越高越好):
真实连线 随机重连
seed 1014 51.2 37.2
seed 1015 31.1 32.7
seed 1016 21.6 34.4
seed 1017 11.2 20.0
seed 1018 18.1 17.3
──────── ────────
均值 26.64 28.32
真实赢 2/5,均值还略低。
我当时的判断是 CartPole 太简单了——两个动作、四维输入,可能根本区分不出来。所以换了 LunarLander:八维输入、四个动作、奖励密集且有正有负。
真实连线 随机重连
seed 1014 -434.7 → -359.6 -459.2 → -339.0
seed 1015 -375.9 → -265.0 -304.9 → -376.1
seed 1016 -450.1 → -263.6 -345.3 → -319.4
seed 1017 -792.4 → -368.9 -438.4 → -363.7
seed 1018 -786.1 → -344.1 -249.1 → -226.3
────────────── ──────────────
均值 -567.8 → -320.2 -359.4 → -324.9
提升 +247.6 +34.5
这里有个陷阱,我差点掉进去:提升量差了七倍,看起来真实连线大胜。
但终点是 -320.2 vs -324.9,几乎一模一样。真实组涨了 248 分,只是因为它起点低了 208 分——它涨那么多,只是爬回到随机组几乎不用训练就在的位置。
同一组数据,选「提升量」当指标真实连线大胜,选「终点」当指标打平。 起点不同的时候,这两个口径会给出相反的结论,而两个都不算撒谎。这种时候唯一诚实的做法是两个都报出来,并说清楚为什么它们不一致。
还有一条比这更重要:两组都远差于随机乱按(-201.1)。 LunarLander 里乱点引擎会一直烧油扣分,贴着地不动反而比乱飞强。两组很可能都只学会了「少动」,谁都没学会着陆。在两个人都没做出这道题的时候比谁分高,意义有限。
4. 第三层判据:编码可分性
成绩打平有个可能的解释:瓶颈在学习规则,不在拓扑。那我绕过学习,直接测那坨连线产生的编码本身。
方法是这样:给它一堆游戏状态,看它的 KC 编码。然后问——从这个编码里,能不能读出「这时候该往左还是往右」? 用交叉验证训一个线性分类器,准确率越高说明编码保住的任务信息越多。
关键是标签必须跟被测模型完全无关。我用的是 gymnasium 库自带的 heuristic 控制器——上游库的代码,跟我这套东西零关系。
CartPole 上:
原始四维输入 98.64%
真实连线 KC 编码 98.87%
随机重连 KC 编码 98.86%
纯随机投影对照 93.59%
真实和随机差 0.01 个百分点。但注意那个纯随机投影落后 5 个百分点——说明稀疏编码本身是有用的,只是真实连线相比随机重连没有额外收益。
LunarLander 上,真实组的数字先跑出来,我当时写下了这么一句判断:真实 KC 编码 82.98%,比原始八维输入的 80.26% 高了 2.7 个百分点,任务变难终于有区分空间了。
然后随机组跑完:
多数类基线 33.32%
原始八维输入 80.26%
真实连线 KC 编码 82.98%
随机重连 KC 编码 82.96%
纯随机投影对照 82.94%
三个打平。连纯随机投影都追上来了。
那 2.7 个百分点是任何高维稀疏投影都能拿到的,跟连接组、跟生物拓扑全都没关系。我在只看到实验组的时候就急着解释现象,被自己的数据打了脸。
对照组没出来之前,任何差异都可能是方法本身的性质。
唯一稳定存在的差异是编码的稀疏度,这个不依赖任何拟合,是纯统计量:
真实连线 随机重连
从未激活的 KC 1,601 2,778
状态间编码重叠 0.175 0.365
真实连线用掉了更多的编码空间,不同状态之间的编码更不容易混淆。这是个真实存在的结构优势,它在我测的任何一个功能指标上都没有兑现。
5. 我的探针骗了我两次
这部分比上面的结论有用,因为它可迁移。
第一次:那个叫「共识」的字段。
MaleCNS 的神经递质表里有好几列:predicted_nt(单个神经元的预测)、ground_truth(实验真值)、celltype_predicted_nt(细胞型层面的预测),还有一列叫 consensus_nt。
我想验证一件事:神经递质的预测到底有多准?因为递质决定这个神经元是兴奋性还是抑制性,也就是连接的正负号——而符号错了,整个网络的行为就变了。
我挑了 consensus_nt 去跟 ground_truth 对账。8.5 万个样本,准确率 1.0000。完美的对角混淆矩阵,一个错都没有。
这不是好消息,这是警报。同一批数据上 predicted_nt 只有 88.61%,凭什么「共识」列能满分?
查下来是这样:有真值的时候 consensus_nt 100% 等于真值,没真值的时候 99.6% 等于细胞型预测。 它是个「有答案抄答案、没答案才预测」的复合字段。我拿它验证真值,等于拿答案验证答案。
这个诱饵特别甜,因为它叫「共识」——听起来正该是最可信的那一列。
换成干净的预测列重测,真实的符号翻转率是 0.1077%(细胞型粒度)。顺带说,这个数字让我下调了之前一个结论的措辞:我之前做过一个扰动实验,发现人为翻转 20% 的连接符号会让网络打哑,10% 是悬崖,当时写下「符号恰恰是连接组里唯一不是测量出来的东西,折扣正好打在最要命的地方」。现在看,符号确实是最脆弱的维度,但真实误差比致命阈值低了两个数量级。这句话说重了。
第二次:判据取错了层。
我在另一个全脑仿真实验里写了个防呆断言:提高输入速率,总放电数应该显著增加。跑出来:
50Hz 输入 → 全脑总 spike 447,012
300Hz 输入 → 全脑总 spike 469,164 只涨 5%
断言报警:速率参数没生效。
我差点就去修参数传递的代码了。拆开看:
被刺激的那 60 个神经元: 50.0 Hz → 291.7 Hz 完全生效
其余的 8,386 个神经元: 52.9 Hz → 53.9 Hz 纹丝不动
参数工作得好好的。全脑 99.3% 的活动是递归自激,跟外部输入无关。 那 60 个神经元的贡献只占总放电数的 0.6%,被淹得干干净净。
我拿全脑总量当判据,等于让 99.3% 的噪声去淹没 0.6% 的信号。
判据要取在被测变量直接作用的那一层,不是它所在的最大容器。 测某组神经元的驱动就量那组自己的放电率,不是全网总量。测某个接口的改动就量经过那个接口的请求,不是全站 QPS。
而这个误报本身比原本要测的东西更值钱——驱动涨 6 倍,全网响应只涨 1.9%,这是第三条独立证据链证实那个模型根本没有工作区间,只有自激态。别急着修掉一个误报探针,先问它为什么误报。
6. 怎么看这类 demo
这波浪潮里的项目质量差别很大,但有几个作者的自我描述相当诚实,值得学。
Chrome 小恐龙那个项目(Fly Dino)的作者写得最清楚。他的做法是取 80 个神经元的固定回路,外面接一个 243 参数的小网络当读出器,用交叉熵方法训练。结果是训练后 99/100 通关,把回路静默掉之后 0/100。看起来是个漂亮的正面结果,但他在文档里明确写了一句:not superiority of biological topology。
他证明的是「外接的读出器能学会 + 神经活动确实被用上了」,不是「生物拓扑更强」。 这两件事之间隔着一个他没做的对照——把那 80 个神经元换成随机连的 80 个,同样训练一遍。
所以看这类 demo 的时候,四个问题按顺序问:
第一,那坨神经元是不是真的参与了决策? 判据是把它掐掉,行为会不会变。这一关大部分项目都过了,也是最容易过的一关。
第二,学会的东西存在哪? 如果存在一个外接的读出器里(PPO、CEM、bandit 训出来的那些参数),那学习全发生在人工零件上,连接组只是个不动的特征提取器。
第三,有没有随机重连对照? 没有这一步,前面全部结论都只能说到「这个系统能工作」,说不到「生物连线有贡献」。
第四,重连对照用的参数是重新调过的吗? 两组各自调参的话,比的就是「谁调得好」,跟连线没关系。这一步最容易漏,因为调参那个动作通常藏在一个叫 calibrate 的函数里,看着特别无辜。
7. 这个实验有哪些地方不能信
训练量严重不足。 20,000 步对 LunarLander 来说什么都不是。两组都卡在 -320 左右而随机乱按是 -201,说明谁都没学会着陆。在两边都没解出这道题的前提下比高下,结论的强度很有限。
只有五个种子。 配对差的均值是 +4.7,组内波动比这个数大得多。这点样本量只够说「我没看出差别」,撑不起「我证明了没差别」。
编码可分性只在 CartPole 和 LunarLander 上测过。 两个都是低维控制任务。蘑菇体本职是处理嗅觉——高维、稀疏、类别化的输入,跟这两个任务的结构完全不同。我可能在用错误的题目考它。
LunarLander 那个探针自己判自己没通过。 我设了三道门禁:已知可分的对照要高分、标签打乱后要掉回瞎猜、所有拟合必须收敛。四分类的主判据全过了(比基线高 47 个百分点,打乱后掉到 26-33%),但我另外加的一个二分类辅助标签没过——它比多数类基线只高 2.6 个百分点,置信下界是负的。探针因为这一项不合格就把整体标记成了 conclusion_permitted: false。上面那些数字我按它的标准称为观测值,不是获批结论。
接口层的所有参数都来自真实组。 这个设计保证了对照的公平(唯一变量是拓扑),但也意味着我测的是「在为真实连线调好的接口下,换成随机连线会怎样」。真实连线可能因此占了便宜,也可能吃了亏——两种方向都有可能,我没有测。
神经递质到兴奋/抑制的映射是建模假设。 不是逐突触测出来的。整个网络的符号结构建立在这个假设上。
8. 下次做这种对照,按这个顺序来
第一,先确认你要测的那个变量真的是个决策点。问自己一句:这一步完全不做,系统会不会照样跑对?会的话,测它等于给自己发奖。
第二,对照组的参数必须冻结,而且要把这条写进参数校验,不是写进注释。
第三,判据取在被测变量直接作用的那一层。量之前先问一句:这个分母里有多大一块跟我要测的东西根本没关系?
第四,标签或者参考答案必须来自跟被测系统完全无关的地方。名字听起来最权威的那一列,很可能是掺了答案的复合字段。
第五,给探针加双向自检——已知能分开的对照要接近满分,标签打乱之后要掉回瞎猜。只验一头,等于什么都没验。
第六,把「我有没有资格下结论」写成一个布尔字段。门禁不过就只报观测数字,明确拒绝下结论。调预算可以,放宽判据不行。
第七,任何要跑十分钟以上的批量任务,先真跑一个单元,测出耗时乘以总数,跟你的预算比。我有一次因为跳过这一步,选了个比基线大 130 倍的刺激群,跑了 2 小时 51 分,完成组合数 0。
第八,看到实验组的漂亮数字先别解释。对照组出来之前,那个差异可能只是方法本身的性质。