用强化学习教 AI 写代码画画

查看原文 HN 讨论

文章摘要

这是设计师 Surya Narreddi 的毕业设计项目记录(合作者 Cameron Franz 负责搭建全部训练基础设施,Alex Wang 提供指导)。项目用强化学习训练一个语言模型(Qwen)通过写代码来作画,而不是直接生成像素。

动机来自一个设计不满:当你用 AI 模型生成图像时,唯一的参与方式就是提示词。你没法直接编辑那张图,想改任何东西都得回到模型重新提示。作者觉得这把创作者降格成了旁观者。而如果模型输出的是代码——具体说是一份完整的 p5.brush JavaScript 手绘笔刷草图——那么代码就是作品,而代码是可编辑的,你可以在不回到提示词的情况下,以更细的粒度修改模型的产出。

更深层的问题是:如何在创意和设计任务上做强化学习。 RL 需要可验证的奖励:数学题非对即错,游戏非胜即负,而审美质量两者都不是。作者的核心命题是——设计问题本身变成了奖励函数,以及你要求评判者应用的标准。太僵硬,模型就收敛(塌缩);太松散,模型就漂移。

系统是一个四步循环,训练中运行数千次:模型收到一个提示(比如「画一朵水彩桃色木槿」),写出一份完整的 p5.brush JavaScript 草图;草图在沙箱化的 Puppeteer 环境中渲染成 PNG;这张 PNG 与从人工评级过的参考池中随机抽取的两张参考画作对比,由一个独立的评判模型选出哪张水彩更好;判断结果转化为奖励信号,用 GRPO 更新模型,循环继续。

奖励函数的失败与修复,是全文最有价值的部分。 第一版评分标准有九个独立信号:编译门禁、检查代码确实用了 p5.brush 而非原生 p5、目标约 3000 token 的代码长度斜坡、人类偏好模型 HPSv3、由 GPT-5.4 与 Gemini 组成的评审团判定提示遵循度,再加四个质量评判维度(可辨识度、美感、技法、层次)。结果模型在奖励 0.65 左右彻底停滞,每次 rollout 长得一模一样——一朵扁平的、五片圆花瓣的剪贴画风格花。奖励一直在涨,能力却没提升。

诊断来自逐个孤立地看子奖励:四个质量评判加提示遵循度彼此的相关系数高达 0.85 到 0.95——它们把同一件事测了五遍;代码长度贡献了约三分之一的总奖励,却在第 30 步就饱和了,此后产生的梯度为零;而 HPSv3 是唯一显示出真实方差的信号,权重却只有 0.10。「我们做的评分标准,是在把同一句话对模型翻来覆去说。」

修复分两半。第一,用成对判断取代绝对打分。 原方案让评判者给每次 rollout 打 0 到 10 分,分数回来都挤在接近零的地方。成对评分问的是另一个问题:把 rollout 和池中两张参考一起给评判者看,只问一句「这几张里哪张是更好的木槿水彩?」奖励是它赢下的对比比例。动态范围一下子打开了——评判模型处理相对问题比处理抽象标尺可靠得多。第二,建立人工评级的参考池。 作者把 1664 张图一张一张地评为 love、okay、nope 三档,其中 117 张 love 级作为对比池的种子。从那以后每一次 rollout 都在与「我认定为好的东西」比较。他提到下一步(没做到)本该是在这些评级本身上训练一个小的奖励模型,也就是正经的 RLHF,这样模型的好坏感就不必每次都靠与池子对比来获得。

新的评分标准把一切收缩成四项:二元的「能编译且用了 brush」门禁(0.05)、二元长度检查(0.05)、HPSv3(0.30)、对参考池的成对评判(0.60)。同样的基座模型、同样的训练数据,下一次训练达到之前的平台期快了三倍,并且继续往上爬,而且生成的代码从 13500 token 压缩到了 2000 以下——模型学会了取胜的构图并不需要冗长的代码

参考池最终有 581 张参考画,全部来自 1664 次生成的人工评级:117 张 love 级、266 张 okay 级,另有 198 张来自单独一轮生成,用于在人工评级样本稀薄的色系上拓宽对比集。作者坦承池中每一张都是模型输出——因为 p5.brush 是艺术家使用的小众工具,实在找不到足够多的人类作品。生成工作走了两条管线:AutoResearch(Opus 4.6、GPT-5.4、Gemini 3.1 Pro 对着参考照片迭代,由一个 VLM 评判者给分和反馈),以及在 Gemini 3.1 Pro 上跑的更大批量。

系统提示词的进化是另一个可推广的发现。早期版本包含一份 400 行的 p5.brush API 参考,结果模型写出自信、格式漂亮、却发明了不存在的 API 的代码。修复用的是 GEPA——一个针对评分函数进化提示词的优化库——跑了 200 轮迭代,对着一个「以品味为锚」的 7-shot 评判者优化。最终收敛出的提示词只有八个笔刷方法的严格白名单,没有 API 文档,没有示例。第一次出现「三次生成里有三次都画出了可见的木槿形体」,正是在把那 400 行参考彻底扔掉之后写的那个版本。作者认为这个发现可以推广:系统提示词里的长篇参考文档会让模型幻觉出 API;一份简短、有主张的白名单比原始规格说明更能约束输出

结语回到那个设计命题:要在主观工作上做 RL,你必须手工撰写奖励,然后把它设计得足够仔细以便泛化。太具体,模型只学会抄你评过的例子;太松散,它什么都学不到。「为创意任务做 RL 是一个设计问题——创造出能让品味泛化到用户偏好的结构。」作者最后很坦率:「我不认为这是做图像更好的方式。事实上它慢得多。但我开始这个项目时的挫败感在于,参与 AI 图像创作的唯一途径是提示词。这个项目让我把注意力和努力铺在提示词、模型和作品三者之上。」项目仍在进行,还有最后一轮训练用于修复途中发现的问题,完整技术报告计划发布。

HN 评论精华

这条帖子拿了 228 分,但只有 23 条评论——讨论量与分数明显不成比例,气氛以赞叹和联想为主,几乎没有批评或深入的技术辩论。最值得注意的是原作者本人(用户名 kickingkeys)在评论区现身。

作者现身。kickingkeys 说:「谢谢分享这个!没想到会在 HN 上随机看到自己的作品,哈哈。」他补充:「如果你好奇我为什么做这个,可以看我的毕业答辩视频。」并贴出了 Vimeo 链接。有意思的是,帖子并非他自己提交的——当另一位用户 mskogly 向他表达感谢时,他还澄清「这篇不是我发的,但还是谢谢你的美言」。mrmarmac 和 mannycalavera 都在看完视频后专程回帖致谢。

最能呼应作者动机的一条评论来自 mskogly:「非常喜欢你的视频演讲,谢谢分享,尤其是关于图像生成器立刻把我们锁进某个特定语境、把我们从创作者降格为旁观者的那部分。我几年前玩过很多 p5js,也许该重新捡起来试试你的想法。不过强化学习那部分听起来超出我的水平了。」

「为什么不直接生成 SVG」是唯一一条实质性的技术质疑。dolmen 说:「SVG 就是图像即代码,这本质上是一个带图形原语的 DSL。在我看来,教模型直接产出 SVG 比教它写生成像素的 JavaScript 更有用。」他还提到有人在用「画一只骑自行车的鹈鹕的 SVG」来基准测试各家 LLM(指 Simon Willison 的知名测试)。ACCount37 提出了相关疑问:那些生成 SVG 的图像模型是怎么工作的?「是大致这样训练的吗?还是一个以图像为条件的 LLM?还是来自一个被训练成产出 SVG 兼容图像的模型的扩散潜变量?」

一条来自实践者的重要泼冷水:retinaros 说他在 SVG 上做过类似的事,「主要是教它画鹈鹕,但也做了泛化。不过大部分行为其实来自对基座模型的 SFT。RL 在风格上非常低效,至少在生成分布外的新颖性上是这样。」这是全帖唯一对方法论有效性提出保留的声音。

方法论层面的类比。iambenm 一句话点破:「这是通过 LLM 实现的遗传编程,酷!」radarsat1 写了篇幅最长的一条回忆:这让他想起 2018 年一篇启发过他的论文(Ganin 等人关于学习笔刷笔画的工作),当时 GAN 已经很多,但那些是基于像素的方法,而他真正感兴趣的是如何推导出场景生成/理解的描述性方法,那篇工作以富有创意的方式结合了 RL 和 GAN 技术。「我怀念那种研究。当然,现在 VLM 已经证明你可以在广义的序列到序列问题里混合模态,这种事能做到并不让我惊讶,但看到用现代技术做得这么好,还是很棒。」

平行工作。thrance 说他在做类似的东西,但目标是体素:「它能仅凭 Python 代码(调用一个用 Rust 写的自定义原生模块)做出相当好的模型。事实上比我期望的要好,但还不完美。」lindyman 分享了一个他喜欢用来测试模型通用能力的玩法:让模型「以编程方式给一张 png 分配颜色和 alpha 值来生成图像,主题是绘画或此刻让你感兴趣的任何东西,800x800」,他第一次试的时候,Claude 用 Python 生成了一张石英晶体的透明 PNG,其中的内含物会随种子数变化。

教育价值的联想boffin 提出:「我觉得这实际上可能是训练人使用 AI 的最好方式之一。我能看到它磨练人的提示能力和表达力,连同约束和期望结果。可能性太疯狂了。」Brendinooo 的比喻很受欢迎:「这提醒我们,在 AI 如何被用于创作艺术这件事上,我们还处在非常早期。而且它画的是花,这形成了一个相当好的心智模型——你可以播下种子、培育长出来的植物,但你终究无法完全控制结果。」mysterydip 则好奇:有 LLM 会写 Logo 语言吗?可能会产生有趣的设计。

质疑与噪音。Alien1Being 只留了两个词:「AI 炒作」——这是全帖唯一的负面评价,没有展开也无人回应。另有一条提问 accomplishdent「那些 JavaScript 到底在干什么?」由 simonw 简短作答:「用 p5.js 画东西。」