TRL과 OpenEnv를 사용해 코드로 수채화 그리는 모델 훈련하기
요약
본 글은 언어 모델을 활용하여 코드로 수채화 그림을 그리는 과정을 TRL과 OpenEnv를 이용해 재현한 기술 보고서입니다. Hugging Face의 Jobs, Spaces 등을 사용하여 엔드투엔드 파이프라인을 구축하고, RL 환경 및 훈련 스크립트를 공개했습니다. 이는 예술적 아이디어를 공학적으로 접근하여 모든 결과물을 재현 가능하게 만드는 과정을 보여줍니다.
핵심 포인트
- 언어 모델 기반의 코딩 아트로 수채화 그림 생성 시연
- TRL과 OpenEnv를 활용한 엔드투엔드 훈련 파이프라인 구축
- Hugging Face Jobs, Spaces 등을 이용해 모든 과정 공개 및 재현 가능성 확보
- 생성형 AI 아트의 초기 탐색적 특성을 공학적으로 구현
![]()
8월 23일, Surya Narreddi가 언어 모델이 그린 아름다운 수채화 영상을 게시했습니다. 이 모델은 'p5.js에 자연스러운 드로잉 도구를 추가하는 라이브러리'인 p5.brush를 통해 JavaScript 코드를 작성합니다. 이 영상은 당시 기준으로 150만 회 이상의 조회수를 기록하며 빠르게 입소문을 탔습니다.
이 영상에는 프로젝트의 이전 단계이자 더 제한적인 범위(영상 속 전체 구도 대신 클로즈업 꽃)에 대한 훈련 과정을 설명하는 블로그 게시물이 함께 올라왔으며, 아쉽게도 아직 공개된 결과물은 없습니다. 그의 사이트에서는 전체 기술 보고서가 곧 나올 것이라고 하니 팔로우할 것을 권장합니다. 이 원래 아이디어는 예술 및 디자인 분야에서 나온 것으로, 그분의 실력이 저보다 훨씬 뛰어납니다. 제 시도는 엔지니어링 측면에서 접근하여 모든 결과물을 공개적으로 재현하는 것입니다.
참고: 프로젝트의 배경에 대한 설명은 Surya 본인이 말한 다음 논문 영상을 참고하세요.
이 글에서는 TRL과 OpenEnv를 사용하여 그의 아이디어를 재현해 보았습니다. 참조 풀 데이터셋, RL 환경, 훈련 스크립트, 그리고 훈련된 모델까지 모두 공개되어 있습니다.
전체 파이프라인은 Hugging Face에서 엔드투엔드로 실행됩니다:
- Jobs를 사용한 훈련
- Spaces로 구현된 RL 환경 및 스코어러 모델
- Inference Providers를 통한 쌍별 평가자(pairwise judge)
- 그리고 모든 결과물은 Hub에 모여 하나의 컬렉션으로 정리됩니다.
두 개의 Space가 준비되면, 이 레시피는 단 하나의 명령어로 완성됩니다. 환경과 스코어러 모델을 복제하고, 보상 혼합을 위한 두 개의 환경 변수를 설정한 다음 실행합니다:
hf jobs uv run train/watercolour_grpo.py --flavor h200 --timeout 48h --secrets HF_TOKEN -- \
--env-url https://<you>-watercolour-env.hf.space \
--model Qwen/Qwen3.5-35B-A3B --lora --all-linear --bf16 --gradient-checkpointing \
...
이 글의 나머지 부분은 그 지점까지 도달하는 과정에 대한 이야기이며, 모든 내용은 레포지토리에 담겨 있습니다.
저는 원본 블로그를 단계별로 따라 했으며, 엄격하게 필요할 때만 내용을 변경했습니다. 저의 모든 아이디어는 실험에 들어가는 대신 목록으로 작성되었고, 이 목록은 최종적으로 출판된 아티팩트 전체 목록 옆에 '다음 시도해 볼 것'이라는 항목이 되었습니다. 이미 그의 게시물을 읽으셨다면, 프레이밍과 보상 설계가 익숙할 것입니다. 새로운 자료는 오픈 구현체, 수동 평가 풀(hand-rated pool), 그리고 훈련 및 비교된 세 가지 보상 혼합물이며, 이는 필요한 RL 환경 구축부터 시작됩니다.
그 그림들은 느슨하고, 불완전하며, 손으로 만든 듯한 느낌을 주는데, 이는 이미지 모델들이 완벽한 (통계적으로 평균적인) 사진을 만들어내는 순간과 대비됩니다. 제 추측으로는 이 대비가 영상이 입소문을 탄 큰 이유 중 하나일 것입니다. 그것은 미디어를 탐구하는 것이 목적이었던 생성형 AI 아트의 초기 시절을 떠올리게 했습니다. DeepDream(2015)은 디버깅 도구였지만 사람들이 예술로 승화시켰고, Edmond de Belamy 같은 작품(2018)은 아티스트들이 GAN이 무엇을 할 수 있는지 탐색하는 과정에서 나왔으며, Mario Klingemann 같은 아티스트들은 그 시절 동안 신경망으로 몽환적인 초상화를 만들었습니다.
이 프로젝트는 그런 초기 시절에 더 가깝게 느껴집니다. 그의 논문에서 Surya는 여기까지 오게 된 경로를 설명합니다. 그는 프롬프트가 유일하게 당길 수 있는 지렛대인 텍스트-이미지 모델을 프롬프팅하는 것부터 시작했고, 디테일을 추가할수록 통제력이 높아지지만 어느 지점까지에 한계가 있습니다. 모델 자체를 훈련시키는 것은 더 나아갑니다. 이 아이디어의 나머지 절반은 매체(medium)입니다. 모델은 이미지를 그리는 약 150줄 분량의 JavaScript 프로그램을 작성합니다. 이 모델 출력물은 코드입니다. 사용자는 그것을 읽고, 편집하고, 다시 실행할 수 있으며, 각 붓놀림 뒤에 숨겨진 결정이 눈에 보입니다. 그리고 스타일은 모델에게 라이브러리의 메서드 중 열 개만 사용할 수 있도록 제한함으로써 나옵니다. 자세한 내용은 아래에서 다룹니다.
같은 기간 동안 Anna Ridler는 수천 송이의 튤립을 사진으로 찍고 각각 라벨링했으며, 이 데이터셋 자체를 예술 작품으로 전시했고, 나중에 이를 기반으로 모델을 훈련시켰습니다. 저는 이 프로젝트를 진행하면서 AI 에이전트가 가져온 참고 자료들을 통해 그녀의 작업을 알게 되었는데, 이 작업은 수동으로 이미지 세트를 선별하고 그 이미지들로 모델을 훈련시킨다는 점에서 매우 유사하여 감명받았습니다.
최근 언어 모델에 대한 대부분의 강화학습(RL) 연구는 검증 가능한 보상(reward)을 사용합니다. 예를 들어, 정답이 알려진 수학 문제, 테스트를 통과하는 코드, 혹은 맞고 틀리며 실행 비용이 저렴한 채점기 등이 있습니다. 하지만 이 프로젝트는 인간의 선호도로부터 모델이 보상 모델을 학습하는 구식 예외였던 RLHF(Reinforcement Learning from Human Feedback)에 더 가깝습니다.
여기서 보상은 미적 선호도입니다. 정답은 없습니다. 이 프로젝트의 진짜 질문은 '취향' 위에서 강화학습을 할 수 있느냐 하는 것입니다.
그가 블로그에서 정의했듯이, 그리고 제가 구축한 RL 환경이 구현하는 대로, 보상(reward)은 다음과 같습니다:
| 용어 | 가중치 | 측정 내용 |
|---|---|---|
gate | 0.05 | 스케치가 컴파일되고 무언가를 그리며, 부정행위를 하지 않는지 |
length | 0.05 | 더 긴 코드 조각으로 부드럽게 유도하는 정도 |
| pairwise judge | 0.60 | 풀(pool)에서 가져온 참고 자료들과 비교한 스타일 |
| HPSv3 | 0.30 | 렌더링된 결과물에 대한 미적 선호도 |
HPSv3는 공개된 7B 규모의 선호도 모델입니다. 이 모델에 이미지와 텍스트 설명을 제공하면, 사람이 그 이미지를 얼마나 선호할지에 대한 점수를 반환합니다. HPSv3는 이미지 쌍에 대한 방대한 양의 인간 선택 데이터로 학습되었기 때문에, 그 점수는 여러 사람들의 취향을 평균한 것입니다. 쌍별 심사관(pairwise judge)은 Qwen3-VL-30B-A3B-Instruct라는 일반적인 비전 모델이며, HF Inference Providers를 통해 호출됩니다. 이 쌍별 심사관은 후보 그림이 풀(pool)에서 무작위로 선택된 4개의 참고 자료 옆에 놓이는 것을 보고, 무엇을 중점적으로 고려해야 하는지(번짐 효과, 반투명 워시, 부드러운 가장자리 등)에 대한 서면 설명을 따라 비교를 수행하며, 이 비교는 양방향 순서로 이루어집니다. 그리고 그 점수는 후보가 승리한 비교의 비율입니다. 이 모델의 유일한 기준은 풀(pool)이므로, 그 점수는 해당 평가에 인코딩된 저의 취향을 의미합니다.

이것들이 Narreddi가 수렴한 가중치들입니다. 여기서 풀(pool)이 취향을 정의합니다. 이는 작업의 초점을 하이퍼파라미터 튜닝에서 무엇이 아름다운지를 결정하는 세트를 구축하는 것으로 옮겨갑니다.
저는 이 보상(reward)으로 세 번의 실행을 진행했습니다. 이들은 두 모델 심사관 간에 가중치가 어떻게 분할되는지에서만 다릅니다:
| run | pairwise judge | HPSv3 | role |
|---|---|---|---|
judge-led | 0.60 | 0.30 | 원래의 혼합 방식, 110단계에서 중단 |
hps-led | 0.30 | 0.60 | 중간 지점, 110단계에서 중단 |
hps-only | 0.00 | 0.90 | 검증 실행(validation run), 60단계에서 중단 |
저는 hps-only로 시작하여 파이프라인이 학습할 수 있는지 먼저 확인했습니다. 보상이 상승하고 지표들이 건강해지자 더 이상 길게 실행할 이유가 없었기 때문에, 대신 두 번의 긴 실행을 진행했습니다. 이 긴 실행들이 던지는 질문은 HPSv3의 힘 중 얼마나 많은 부분을 쌍별 심사관에게 넘겨줄 수 있느냐는 것입니다. 심사관이 더 많은 가중치를 지닐수록, 보상은 모두의 취향이라기보다는 저의 취향을 의미하게 되고, 오르기가 더 어려워져야 합니다. 참고로, 충분히 밀어붙이거나 스타일이 평균에서 너무 멀 경우, 모델은 완전히 멈출 수도 있습니다.
다행히도 모델은 멈추지 않았고, 학습된 (learned) 쌍별 평가자(pairwise judge)를 사용한 두 번의 실행 모두 그랬습니다. 수동으로 평가된 풀(pool)은 적어도 지표와 최종 그림에서 보여주듯이 정책을 유도할 수 있습니다. 숫자는 아래에 나와 있습니다.
면책 조항. 만약 최첨단 모델(frontier model)을 사용한다면, 이미 프롬프트로부터 수채화로 그리는 JavaScript 코드를 생성할 수 있습니다. 이것이 시작점입니다. 여기서의 작업은 더 작은 모델에게 사람 고유의 예술적 선호도와 결합하여 이를 수행하도록 가르치는 것입니다.
환경(environment)은 모델과 보상 사이에 존재하는 모든 것, 즉 모델이 그림을 그리는 데 사용하는 JavaScript 라이브러리, 제한하는 시스템 프롬프트, 각 스케치를 렌더링하는 헤드리스 Chromium, 그리고 부정행위를 거부하는 게이트를 포함합니다.
이 라이브러리는 보이는 것보다 더 많은 일을 합니다.
p5.brush는 @acamposuribe에 의해 제작되었으며, 단순히 도형을 그리는 것이 아니라 매체(medium)를 시뮬레이션합니다: 안료가 채움 영역의 가장자리를 넘어 번지고, 종이는 질감을 가지고 있으며, 획은 무게를 가집니다. 흐름장(flow fields)이 브러시 작업을 끌고 갑니다. 모델이 brush.fillBleed(0.25)를 호출할 때,
그것은 잉크가 얼마나 멀리 번질지 결정하는 것입니다.
참고. p5.brush의 저자는 이 모든 것보다 훨씬 전에 기계에게 그림을 그리도록 가르치려고 노력해 왔습니다. 2022년에 그는 p5.js를 아이처럼 그리게 하는 것에 대한 일기를 숨긴 생성 예술 시리즈를 만들었습니다: "크레용을 사용하는 것이 겨우 가능합니다 [...] 간단한 명령을 따를 수 없습니다. 오늘은 끝입니다, 정말 짜증 납니다." 이 시리즈는 세 점으로 구성될 예정이었고 그는 두 점을 만들었습니다. Surya의 영상이 입소문을 타자, 그는 그 일기를 인용하며 이 작업이 스스로 도착한 세 번째 작품이라고 말했습니다.
p5.brush는 47개의 메서드를 노출합니다. 프롬프트가 허용하는 것은 10개입니다: scaleBrushes, noStroke, fill, noFill, fillBleed, fillTexture, beginShape, vertex, endShape, 그리고 circle. 나머지 서른일곱 개(선, 해칭, 사용자 지정 브러시)는 수채화 느낌을 망가뜨릴 것입니다. 이 열 개만으로는 모델이 채워진 도형만 그릴 수 있으며, 라이브러리가 각각에 번짐 효과를 추가합니다.

draw()
그리고 그것이 렌더링되는 방식까지 보여줍니다. 주석은 모델 자체의 것입니다. 보상(Reward) 0.864, 129줄, 스텝 22. 모든 그림의 전체 소스는 rollouts 데이터셋에 있습니다. 그의 블로그 게시물 덕분에 프롬프트 반복 작업에 낭비했을 수 있는 많은 시간을 절약할 수 있었습니다. 긴 API 레퍼런스는 모델이 존재하지 않는 메서드를 상상하게 만들고, 그가 진행한 200번의 GEPA 반복은 문서화 없이 엄격한 허용 목록(allowlist)으로 수렴했습니다. 저도 같은 실패를 보았고, 손으로 허용 목록을 작성했습니다. 제가 추가한 유일한 내용은 한 문장입니다: 각 꽃잎을 두세 번씩 칠하세요. 먼저 큰 터치를 하고 그 안에 더 작고 불투명한 터치를 하세요. 이 작은 변화가 제 결과물을 훨씬 더 다채롭게 만들었습니다.
참고. GEPA에 대해 처음 들으셨다면, 이는 자동 프롬프트 최적화기입니다. 언어 모델이 현재 프롬프트가 어디에서 실패했는지 평범한 말로 반영하고 더 나은 것을 제안하며, 이 루프가 반복됩니다.
게이트(gate)는 마지막 조각입니다. 스케치는 컴파일되어야 하고, p5 직접 호출 대신 라이브러리를 사용해야 하며, 캔버스에 실제 안료를 칠해야 하고, 예를 들어 텍스트를 캔버스에 작성하여 채점기(scorer)를 속이려고 해서는 안 됩니다.
풀(pool)은 제가 개인적으로 선호하는 바에 따라 love와 okay 두 등급으로 나뉜 178개의 그림으로 구성되어 있습니다. 이들 모두 실제로 모델에 의해 생성되었습니다. Inference Providers를 통해 호출된 네 개의 오픈 가중치(open-weight) 모델이 iNaturalist의 실제 공개 라이선스 사진인 히비스커스를 기반으로 p5.brush 스케치를 작성했습니다. 비전 모델은 세 번 이상의 개선 반복을 거쳐 모든 스케치에 대해 서면 피드백을 제공했습니다. 최종 렌더링물은 하나씩 저에 의해 평가되었고, 그중 178개가 선정되었습니다.
여기서는 네 가지 다른 종류의 모델을 선택하여 각기 다른 스타일을 테스트했습니다. 이 네 개는 빠른 신뢰성 검사에서 항상 유효한 스케치를 생성했던 오픈 모델이었으며, 두 개의 다른 후보군은 이를 통과하지 못해 제외되었습니다. 만약 자신만의 풀을 만들고 싶다면, 다른 것을 선택할 수도 있습니다.
이 등급들은 보상(reward)에 실제적인 영향을 미칩니다. 쌍별 심사위원(pairwise judge)이 네 가지 레퍼런스를 그릴 때, 절반은 love에서 나옵니다.
그리고 절반은 okay에서 나옵니다. 따라서 정책(policy)은 항상 이길 수 있는 경쟁자들을 마주하게 되며, 승리는 어느 티어에 대해서도 동일한 보상으로 지급됩니다. 이것이 제가 의도적으로 변경한 몇 가지 사항 중 하나입니다: 원래 버전은 최고 티어와만 비교했지만, 저는 초기 정책이 약하더라도 신호(signal)를 받을 수 있도록 쉬운 티어를 드로우에 남겨두었습니다.
여기에는 인간이 만든 그림은 없는데, 이것이 실제적인 한계점입니다. p5.brush는 틈새 라이브러리이며, 이 라이브러리에 접근 가능한 코드로 존재하는 인간의 작품은 몇 점에 불과하여, 학습 코퍼스(training corpus)가 필요로 하는 양과는 거리가 멉니다. 이는 블로그에서도 언급했듯이 말입니다.
여기서 흥미로운 아이디어는 제가 이전에 논의했던 것처럼, 모델이 풀(pool)이 담고 있는 것을 모방하는 법을 배울 것이라는 점입니다. 만약 우리가 환경(environment)을 다른 데이터셋으로 지정한다면, 코드를 한 줄도 건드리지 않고 보상(reward)이 자동으로 변경될 것입니다. 제가 생성하여 공개적으로 공유한 데이터셋의 경우, 원본 스케치까지 포함했습니다.
심사위원들(judges)을 더 자세히 살펴보면, 두 심사위원이 서로 다른 질문에 답합니다. HPSv3는 그것이 꽃인지 아닌지를 결정하고, 쌍별 심사위원(pairwise judge)은 제가 선택한 스타일에 따라 잘 그려졌는지 여부를 결정합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Hugging Face Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기