自愈标志变成直线:神经元胞自动机实测
作者用 PyTorch 实现仅含 8,320 个参数的神经元胞自动机,让标志从单个像素生长并修复擦除区域。模型能处理训练中的圆形缺口,却在标志被擦成细条后无法恢复,暴露出损伤形态泛化的局限。
AI 深度解读
一个能被鼠标擦掉、再自己长回来的 Logo,最后竟然长成了一根横棍。作者在个人网站上用神经元胞自动机生成「GR」字标,却发现:学会修补缺口,不等于能应付用户随手乱划,更不等于丢掉大部分信息后还能恢复原样。
这篇最有用的结论是:局部模型负责生长,确定性规则负责判断是否失败、何时重启。再配上延迟加载、静态降级和针对真实故障的测试,一个 AI 视觉实验才有机会成为能上线的前端功能。
为什么能自愈,却会把 GR 长成 RR
作者参考 Mordvintsev 等人在 Distill 2020 发表的工作,用 PyTorch 从头实现了神经元胞自动机(NCA)。
它运行在 56×56 网格上,每个格子保存 16 个数:RGBA 加上 12 个隐藏通道。每一步,格子通过 identity 和 Sobel 滤波器感知 3×3 邻域,再由一个 48 → 128 → 16 的小网络决定如何更新。每轮只有随机一半格子更新,没有存活邻居的格子会死亡。
整个模型只有 8,320 个参数。所有格子共享同一个网络,运行时没有一张「GR」目标图供它描摹。你看到的字母,是局部更新规则不断执行后形成的结果。
问题出在训练损伤与真实操作不同。论文用圆形缺口训练自愈,作者最初也沿用这种方式,模型修补缺口很熟练。但访客会拖动鼠标,把字母切开。一次位于两字母之间的竖切,就让 G 长成了第二个 R。
作者尝试用两种学习率微调,仍然失败。最后改为从头训练,把竖切、横切、斜切、涂划和部分圆形缺口放进训练,跑了 12,000 次迭代,在 M3 Pro 上耗时 48 分钟。
训练要覆盖用户实际制造的损伤,不能只覆盖方便生成的损伤。
但这仍没解决所有情况。对上线权重的测试显示,只保留顶部 30–40% 时,模型无法恢复;损失 55% 时可以恢复,却需要约 2,000 步,远超旧循环的 700 步上限。
一条残留的细带,可能不足以提供原来两个字母的位置关系。局部规则于是停在另一个稳定形状:横棍。此时,「还在运行」和「正在恢复」已经是两回事。
模型不知道自己错了,就在外面加一道检查
作者没有继续堆模型,而是加了约 280 行 TypeScript,作为外部恢复守卫。
守卫拿每一帧与「GR」的粗粒度指纹比较:存活格子总量,以及在 8×8 分区中,格子在哪里、有多绿。它保存的是几十个统计数值,而非完整目标图。
处理规则很直接:
- 剩余量低于 40%,就停止尝试修补,从单个格子重新生长,并告诉访客这次无法自愈。
- 到第 380 步还不像 GR,就放弃并重新生长。正常切割和缺口约 200 步已基本成形,无望的案例到 400 步仍相差很远。
- 外观已接近目标但仍在变化,就额外运行 400 步,避免把半成品冻结。
- 首次生长也可能歪掉,约占随机种子的 6%。初始化发生在静态 SVG 后方,失败后可以重新生长。
这些阈值来自约 400 次生长和修复的校准。作者还把旧版会生成「RR」的权重留在仓库里,作为回归测试样本:守卫连已知错误都识别不了,就不能上线。
这里最关键的是区分「修复」与「重置」。从残余状态继续长,才是自愈;清空后从单格重新生成,是另一条恢复路径。产品应该把两者讲清楚。
作者把这种结构类比为 Pod 与 liveness probe,也类比为 AI agent 与外层确定性检查。类比成立的部分,是执行系统之外还需要一个判定者,知道什么时候该停止等待。
前端上线,成本和故障都得算进去
这个 Logo 的页面成本被拆得很细。首屏只急切加载一个 0.47 KB 的 loader,等模块即将进入视口才加载后续资源。权重使用 float16 二进制格式,gzip 后为 15.5 KB。
推理由约 240 行普通 TypeScript 在 Web Worker 中执行,不依赖 WebGL 或 ML runtime,且只在模块可见时运行。加载前由 0.4 KB 的静态 SVG 占位;启用 Save-Data、使用 2G 或禁用 JavaScript 时,只展示 SVG。作者报告上线时移动端和桌面端 Lighthouse 为 1.0,CLS 为 0,但没有给出完整测试条件。
训练脚本也加了发布门槛:新权重经过 float16 转换往返后,评分优于现有权重才允许覆盖。测试针对页面实际使用的 float16 权重,而不是训练 checkpoint,避免训练结果好看、发布产物退化。
开发过程由 writer agent 编写,再让独立 judge agents 尝试破坏,每个发现先变成失败测试,再修复。它们找到了「RR」、完全擦除后留下空白、手机触摸滚动被阻断、循环内部启动第二个定时循环,以及更好权重被覆盖等问题。
但横棍问题仍是作者亲手试出来的。即便加入守卫,90 个种子 × 8 种损伤的 720 次修复中,仍有 11 次未达标,需要重置。
后续约 50 个 pull request 中,守卫没有变化,Logo 被用到了阅读进度和 404 页面。阅读到四分之一时出现种子,中途长出半个 GR,读完形成完整 Logo;本地时间 23:00 到 06:00 会休眠。
作者也发现模型从不同种子出发都会长成同一个 GR,无法直接给每篇文章生成不同生物。因此文章卡片改用按文章地址播种的普通 11×11 元胞自动机,约 140 行代码。需要不同结果时,并非一定要继续使用神经网络。
仿真测试每个文件要占用一个 CPU 核心 30–45 秒,后来被拆成独立 runner,并在 main 上每晚运行。训练代码位于 scripts/nca/train.py,模型与守卫位于 src/scripts/nca/。
我的判断
我认同作者把重点从「模型会不会」转向「系统如何处理不会」。对前端而言,静态占位、按可见性执行、失败重置,与模型本身同样决定交互能否交付。
但「信息没了,多少训练都没用」需要收窄理解。原文展示了当前模型和上线权重的失败,没有证明其他架构或引入全局信息后也无法恢复。更稳妥的结论是:不能期待这个只看邻域的模型,在严重损伤后始终找回目标。
同样,380 步、40% 和额外 400 步都是这个 Logo 的经验阈值。作者做了校准和回归测试,但没提供与其他守卫策略的对照数据,不能把这些参数当通用配方。
如果需求只是一个可擦除、可恢复的品牌图形,静态 SVG 配合确定性动画也是替代方案。这篇的价值在于展示如何约束一个有不确定性的交互,并没有证明 NCA 比传统动画更划算。
能拿来做什么
- 给 AI 编程任务增加可执行的验收条件,例如构建成功、关键测试通过、输出格式符合要求,并明确失败后的停止或重试路径。
- 做 Canvas 或其他模拟交互时,把真实拖动、连续切割、完全擦除和手机滚动纳入验证,不要只测预设演示动作。
- 发布经过压缩、量化或格式转换的模型资源时,验证最终产物,并保留会触发已知错误的旧产物作为回归样本。
- 给页面中的实验功能设置延迟加载、可见性暂停和静态降级;若只需要确定性图案变化,先评估普通元胞自动机或 SVG 动画是否足够。