메뉴
HN
Hacker News • 33일 전

AI에게 코드로 그림 그리는 법을 가르치다

IMP
7/10
핵심 요약

저자는 강화학습(RL)을 이용해 언어 모델이 p5.brush JavaScript 코드를 작성해 수채화 이미지를 생성하도록 훈련시켰습니다. 절대 점수 방식의 보상 함수가 모델을 정체시키자, 페어와이즈(pairwise) 판단과 수작업 평가된 참고 이미지 풀로 전환해 성능을 개선했습니다. 이 프로젝트는 검증 가능한 정답이 없는 창작·디자인 작업에 강화학습을 적용하는 방법을 탐구한다는 점에서 중요합니다.

번역된 본문

AI 모델로 이미지를 만들 때, 사용자가 참여할 수 있는 유일한 방법은 프롬프트입니다. 이미지를 직접 수정할 수는 없습니다. 뭔가를 바꾸려면 다시 모델로 돌아가 프롬프트를 입력해야 합니다. 이 제약이 본 프로젝트의 출발점이었습니다. 친구 Cameron과 저는 강화학습(reinforcement learning)을 사용해 언어 모델이 코드를 작성함으로써 이미지를 만들도록 훈련시켰습니다. 코드가 곧 결과물이며, 코드는 편집 가능합니다. 프롬프트로 돌아가지 않고도 모델이 생성한 결과를 더 세밀하게 수정할 수 있습니다.

이 프로젝트가 던지는 더 깊은 질문은 창작 및 디자인 작업에 강화학습을 어떻게 적용할 것인가입니다. RL은 보상이 검증 가능할 때 작동합니다. 수학 문제는 맞거나 틀리고, 게임은 이기거나 집니다. 하지만 미적 품질은 둘 중 어느 쪽도 아닙니다. 디자인 문제는 곧 보상 함수와 심판(judge) 모델이 적용할 기준이 됩니다. 너무 엄격하면 모델이 하나의 양상으로 수렴하고, 너무 느슨하면 모델이 표류합니다.

팀: Surya, Cameron, Franz, Alex Wang. 코드로 만든 무궁화(히비스커스) 수채화.

작동 방식

이 시스템은 훈련 중 수천 번 반복되는 4단계 루프입니다. 모델이 "수채화로 복숭아색 무궁화 그리기" 같은 프롬프트를 받으면, 완전한 p5.brush JavaScript 스케치를 작성합니다. 이 스케치는 샌드박스된 Puppeteer 환경에서 렌더링되어 PNG를 생성합니다. 이 PNG는 수작업으로 평가된 풀에서 무작위로 추출된 두 개의 참고 그림과 비교되며, 별도의 심판 모델이 더 나은 수채화를 선택합니다. 이 판정은 보상 신호로 변환되고, GRPO가 모델을 업데이트한 뒤 루프가 다시 실행됩니다. 자명하지 않은 선택들은 무엇을 판단할지, 판단을 어떻게 내릴지, 참고 풀에 무엇을 넣을지, 시스템 프롬프트를 어떻게 작성할지에 담겨 있으며, 각각이 아래 섹션의 주제입니다. 참고 풀은 581개 참고 이미지(그중 117개는 'love' 등급), 롤아웃마다 2개 샘플링, 수천 회 반복. 모델은 Qwen 3.5 35B.

보상 함수

첫 번째 평가 기준(rubric)에는 아홉 개의 개별 신호가 있었습니다. 컴파일 게이트(컴파일 여부 확인), 네이티브 p5가 아닌 p5.brush를 실제로 사용했는지 검사, 약 3,000 토큰을 목표로 하는 코드 길이 램프, 인간 선호 모델인 HPSv3, GPT-5.4와 Gemini로 구성된 협의체가 판단하는 프롬프트 준수도, 그리고 인지 가능성, 미적 품질, 기법, 깊이라는 네 가지 품질 심판이 그것입니다.

모델은 보상 약 0.65에서 정체되었습니다. 모든 롤아웃이 똑같아 보였습니다. 다섯 개의 둥근 꽃잎을 가진 밋밋한 클립아트풍 꽃이었습니다. 보상은 계속 올라갔지만 능력은 개선되지 않는 듯했습니다.

진단은 하위 보상들을 개별적으로 살펴보면서 나왔습니다. 네 가지 품질 심판과 프롬프트 준수도는 서로 0.85~0.95의 상관관계를 보였습니다. 같은 것을 다섯 번 측량하고 있었던 겁니다. 총 보상의 약 3분의 1을 차지하던 코드 길이는 30 스텝 만에 포화되어 그 이후로 그래디언트가 0이었습니다. 유일하게 실질적 분산을 보이던 HPSv3는 가중치가 0.10이었습니다. 우리가 만든 평가 기준은 모델에게 같은 말을 반복하고 있었던 것입니다.

해결책은 두 가지였습니다. 첫째, 절대 점수를 페어와이즈 판단으로 대체했습니다. 원래 기준은 심판에게 각 롤아웃에 0부터 10까지 점수를 매기도록 했는데, 점수는 0 근처로 압축되어 돌아왔습니다. 페어와이즈 방식은 다른 질문을 던집니다. 심판에게 롤아웃과 풀의 참고 이미지 두 개를 보여주고 "이 중 어느 것이 더 나은 무궁화 수채화인가?"라는 단일 프롬프트를 줍니다. 보상은 비교에서 이기는 비율입니다. 동적 범위가 열리고, 심판 모델은 추상적 척도보다 상대적 질문을 더 신뢰성 있게 처리합니다.

둘째, 수작업 평가된 참고 예시 풀을 구축했습니다. 1,664개의 이미지를 한 장씩 love, okay, a(등급별로) 평가했습니다.

원문 보기
원문 보기 (영어)
Home Work Blog About Training AI to Paint with Code March 2026 · Research, Design, Development. March 2026 · Research, Design, Development. When you make an image with an AI model, the only way to participate is the prompt. You cannot edit the image directly. To change anything you go back to the model and prompt again. That limitation is what started this project. My friend Cameron and I trained a language model to make images by writing code, using reinforcement learning. The code is the artefact, and the code is editable. You can change what the model produced more granularly without going to prompt. The deeper question this project asks is how to do reinforcement learning on creative and design tasks. RL works when the reward is verifiable. A math problem is right or wrong. A game is won or lost. Aesthetic quality is neither. The design problem becomes the reward function and the criteria a judge is asked to apply. Too rigid, and the model converges. Too loose, and the model drifts. [Video of my thesis presentation, for the context behind this project.] My contributions Design Development RL Research The team Surya Cameron Franz Alex Wang A Watercolour painting of a Hibiscus flower made in code. A Watercolour painting of a Hibiscus flower made in code. How it works The system is a four-step loop, run thousands of times during training. The model receives a prompt, something like draw a peach hibiscus in watercolour , and writes a complete p5.brush JavaScript sketch. The sketch is rendered in a sandboxed Puppeteer environment, which produces a PNG. The PNG is judged against two random reference paintings sampled from a hand-rated pool, with a separate judge model picking the better watercolour. The judgment is converted into a reward signal, GRPO updates the model, and the loop runs again. The non-obvious choices live in what is being judged, how the judgment is made, what is in the reference pool, and how the system prompt is written. Each is the subject of a section below. reference pool · 581 refs · 117 love-tier two sampled per rollout thousands of iterations Prompt peach hibiscus in watercolour Model Qwen 3.5 35B writes JS Render Puppeteer → PNG Judge pairwise vs 2 refs Reward GRPO update prompt model tooling judge update The training loop. Reward Functions The first rubric had nine separate signals. A compilation gate. A check that the code actually used p5.brush rather than native p5. A code length ramp targeting around 3,000 tokens. HPSv3, a human preference model. Prompt adherence, judged by a council of GPT-5.4 and Gemini. And four more quality judges: recognisability, aesthetics, technique, depth. The model plateaued around 0.65 reward and stayed there. Every rollout looked the same. A flat, clip-art flower with five rounded petals. The reward kept going up but the capabilities didn't seem to improve. The diagnosis came from looking at the sub-rewards in isolation. The four quality judges plus prompt adherence were correlated with each other at 0.85 to 0.95. They were measuring the same thing five times. Code length, contributing roughly a third of the total reward, had saturated by step thirty and was producing zero gradient afterward. HPSv3, the one signal showing real variance, was weighted at 0.10. The rubric we made was telling the model the same thing over and over again. The fix had two halves. Replace absolute scoring with pairwise judgment. The original rubric asked the judge to score each rollout from zero to ten. The scores came back compressed near zero. Pairwise scoring asks a different question. The judge is shown the rollout, two references from the pool, and a single prompt: which of these is the better hibiscus watercolour? The reward is the fraction of comparisons it wins. The dynamic range opens up. The judge model handles a relative question more reliably than an abstract scale. Build a reference pool of hand-rated examples. 1,664 images, rated one at a time into love, okay, and nope. The 117 love-tier examples seeded the comparison pool. Every rollout from that point onward was being judged against the things I had decided were good. The next step, which we did not get to, would have been training a small reward model on the ratings themselves, (proper RLHF) so the model's sense of good could be applied without needing to compare against the pool every time. The new rubric collapsed all of it into four components: a binary compile-and-uses-brush gate (0.05), a binary length check (0.05), HPSv3 (0.30), and the pairwise judge against the reference pool (0.60). Same base model, same training data. The next run reached the previous plateau three times faster, kept climbing past it, and produced code that compressed from 13,500 tokens to under 2,000. The model learned that winning compositions did not need verbose code. The first rubric had nine separate signals. A compilation gate. A check that the code actually used p5.brush rather than native p5. A code length ramp targeting around 3,000 tokens. HPSv3, a human preference model. Prompt adherence, judged by a council of GPT-5.4 and Gemini. And four more quality judges: recognisability, aesthetics, technique, depth. The model plateaued around 0.65 reward and stayed there. Every rollout looked the same. A flat, clip-art flower with five rounded petals. The reward kept going up but the capabilities didn't seem to improve. The diagnosis came from looking at the sub-rewards in isolation. The four quality judges plus prompt adherence were correlated with each other at 0.85 to 0.95. They were measuring the same thing five times. Code length, contributing roughly a third of the total reward, had saturated by step thirty and was producing zero gradient afterward. HPSv3, the one signal showing real variance, was weighted at 0.10. The rubric we made was telling the model the same thing over and over again. The fix had two halves. Replace absolute scoring with pairwise judgment. The original rubric asked the judge to score each rollout from zero to ten. The scores came back compressed near zero. Pairwise scoring asks a different question. The judge is shown the rollout, two references from the pool, and a single prompt: which of these is the better hibiscus watercolour? The reward is the fraction of comparisons it wins. The dynamic range opens up. The judge model handles a relative question more reliably than an abstract scale. Build a reference pool of hand-rated examples. 1,664 images, rated one at a time into love, okay, and nope. The 117 love-tier examples seeded the comparison pool. Every rollout from that point onward was being judged against the things I had decided were good. The next step, which we did not get to, would have been training a small reward model on the ratings themselves, (proper RLHF) so the model's sense of good could be applied without needing to compare against the pool every time. The new rubric collapsed all of it into four components: a binary compile-and-uses-brush gate (0.05), a binary length check (0.05), HPSv3 (0.30), and the pairwise judge against the reference pool (0.60). Same base model, same training data. The next run reached the previous plateau three times faster, kept climbing past it, and produced code that compressed from 13,500 tokens to under 2,000. The model learned that winning compositions did not need verbose code. 0 50 100 150 200 0.00 0.25 0.50 0.65 0.75 1.00 training reward · 200 steps training step → reward old rubric · 9 signals new rubric · 4 signals 0.65 — old rubric ceiling what changed in the reward function ↓ OLD 9 signals · 5 redundant compile 5 % brush use 5 % length ramp 32 % HPSv3 10 % council 8 % recognise 10 % aesthetics 15 % technique 8 % depth 7 % 5 judges, ρ = 0.85–0.95 NEW 4 signals · pairwise dominates compile gate 5 % length 5 % HPSv3 30 % pairwise 60 % Old rubric vs new rubric, reward curves on the same axes. The reference pool The