← Все заметки
// КОНСПЕКТ ЗАМЕТКИ // СИСТЕМЫ · КОНСПЕКТ НА 30 СЕК · ПОЛНЫЙ ТЕКСТ 5 МИН

Мой логотип заживает сам. А потом я стёр его в палку.

Локальная модель не восстановит информацию, которой нет: ей нужен глупый глобальный супервизор, который знает, когда перезапустить.

“Никакое обучение это не исправит: информации просто больше нет.”

  • Нейронный клеточный автомат на 8 320 параметров выращивает монограмму GR на этом сайте и заживляет её; каждый пиксель видит только соседей.
  • Обученный на круглых укусах, после разреза он отращивал на месте G вторую R. Помогло обучение с нуля на разрезах и каракулях, то есть на том, что делают посетители.
  • Стёртый до полоски, он навсегда становился палкой. Исправил это супервизор на ~280 строк: он сравнивает форму с GR и честно выращивает её заново.

На этой неделе я стёр собственный логотип до тонкой полоски и стал ждать, пока он заживёт.

Он какое-то время думал. А потом превратился в палку. Не в G и не в R, а в длинную горизонтальную палку, и так палкой и остался.

Неловко вышло: весь смысл этого логотипа в том, что он заживает.

Что это вообще такое

На rossev.dev есть монограмма GR, которую никто не рисовал. Она вырастает из одного пикселя. Проведите по ней курсором, и она отрастёт обратно.

Под капотом нейронный клеточный автомат по статье Mordvintsev et al., Distill 2020. Я написал его с нуля на PyTorch. Если коротко:

  • Сетка 56×56, в каждой клетке 16 чисел: RGBA и 12 скрытых каналов, которым сеть сама придумывает смысл.
  • На каждом шаге клетка смотрит на соседей 3×3 через identity и фильтры Собеля, и крошечная сеть (48 → 128 → 16) решает, как ей измениться.
  • Обновляется только случайная половина клеток, а клетка без живых соседей умирает.

Это 8 320 параметров. Каждый пиксель запускает одну и ту же сеть и видит только соседей. Картинки «GR» в рантайме нет нигде.

Именно из-за последнего предложения я и получил палку.

Сначала он научился писать «RR»

В статье заживление учат на круглых укусах. Первая модель освоила укусы идеально.

Потом я посмотрел, как с ней обращаются живые люди. Логотип никто не кусает. Его перечёркивают курсором, то есть разрезают.

Один вертикальный разрез между буквами, и модель отрастила на месте G вторую R. Моё портфолио гордо сообщало: «RR».

Дообучение на разрезах не помогло: на двух learning rate модель упрямо растила лаймовый блок из-за R прямо на место G. Помогло только обучение с нуля на повреждениях, похожих на то, что делают пользователи: вертикальные, горизонтальные и диагональные разрезы, каракули и немного укусов. 12 000 итераций, 48 минут на M3 Pro.

Урок первый: учите модель на тех повреждениях, которые делают ваши пользователи, а не на тех, которые удобно сгенерировать. Модель, которая не видела такого сбоя, начнёт импровизировать. Моя сымпровизировала вторую R.

А потом я сделал палку

Возвращаемся к полоске.

Это не баг обучения, это физика. Каждая клетка знает только соседей. В полоске сверху нет ни бита информации о том, где были G и R. Локальное правило нашло другую устойчивую форму, палку, и его всё устроило.

Я замерил это на весах из продакшена:

  • если оставить верхние 30–40%, монограмма никогда не восстанавливается;
  • если потерять 55%, она заживает примерно за 2 000 шагов, сильно позже, чем раньше останавливался цикл (700).

Никакое обучение это не исправит: информации просто больше нет.

Поэтому исправление не в более умной нейросети, а в ~280 строках очень скучного TypeScript.

Глупый супервизор для умной локальной системы

Recovery guard смотрит на автомат снаружи и сравнивает каждый кадр с грубым отпечатком «GR»: сколько клеток живо и, на сетке 8×8, где они и насколько они зелёные. Несколько десятков чисел, а не картинка.

Правила простые:

  • Осталось меньше 40%? Не притворяться. Вырастить заново из одной клетки и честно сказать посетителю: «Не зажило — отрастает заново из одной клетки».
  • К шагу 380 не похоже на GR? Сдаться и вырастить заново. Обычные разрезы и укусы выглядят выросшими к ~200 шагу, а безнадёжные случаи и к 400 далеки от цели.
  • Похоже, но ещё меняется? Дать ещё 400 шагов, а не замораживать на полпути.
  • Первый рост вышел кривым? Такое бывает примерно на 6% случайных сидов. Он идёт за статичным SVG, поэтому его молча перевыращивают, и никто этого не видит.

Пороги не взяты с потолка. Они откалиброваны на ~400 ростах и заживлениях и обязаны ловить старые веса «RR», которые лежат в репозитории как регрессионная фикстура. Гард, который не ловит заведомо плохую модель, не выкатывается.

Урок второй, и его я как раз применяю в работе: локальная модель не может восстановить информацию, которой нет. Дайте ей глупый глобальный супервизор, который понимает, когда она потеряла нить, и честно её перезапускает.

Это pod и liveness probe. Это AI-агент и детерминированная проверка вокруг него. Та же форма, логотип поменьше.

Сколько это стоит странице

Это портфолио, и никто не должен платить за фокус до того, как до него доскроллит.

  • Единственный код, который грузится сразу, это загрузчик на 0,47 KB. Он ждёт, пока блок вот-вот появится на экране.
  • Веса во float16 в крошечном бинарном формате: 15,5 KB в gzip. Скрипт обучения перезаписывает веса из продакшена, только если новые лучше после округления до float16, так что переобучение не может тихо выкатить что-то хуже.
  • Инференс это ~240 строк обычного TypeScript в Web Worker. Без WebGL и без ML-рантайма, и работает он, только пока блок виден.
  • Пока модель не загрузилась, место держит статичный SVG на 0,4 KB. С Save-Data, на 2G или без JavaScript вы получите только SVG.

На запуске было Lighthouse 1.0 на мобильных и десктопе, CLS 0.

Написали агенты, проверяли агенты

Я собрал это так же, как сейчас собираю фичи в продакшене: агент-писатель, потом независимые агенты-судьи, которые пытаются всё сломать, и каждая находка сначала становится красным тестом и только потом фиксом.

Судьи нашли «RR», мёртвый пустой квадрат после полного стирания, заблокированный тач-скролл на телефонах, второй цикл таймера, который мог стартовать внутри первого, и обучение, которое могло перезаписать веса получше. Тесты гоняются на тех же float16-весах, что отдаёт страница, а не на чекпойнте обучения.

А вот палку не поймал никто. Для неё понадобился живой человек с мышкой, который пытался сломать собственный логотип.

Идеально до сих пор не стало: на 90 сидах × 8 видах повреждений 11 из 720 заживлений не дотягивают до планки. Кнопка Reset их чинит.

Четыре дня спустя

Я писал это в день запуска сайта. С тех пор было около пятидесяти PR. Гард не менялся, а работы у GR прибавилось.

  • Он читает вместе с вами. На каждой странице поста, включая эту, в углу сидит маленький GR и растёт из одной клетки по мере чтения: зародыш на четверти, половина GR на середине, весь логотип в конце. Выросший, он заживает так же, как большой, вместе с гардом. С 23:00 до 06:00 по вашим часам он спит.
  • Он есть на 404. На неверном адресе GR перечёркнут через обе буквы и заживает, пока страница подсказывает, куда вы, скорее всего, хотели попасть.
  • Существом поста он стать не смог. Я хотел, чтобы у каждого поста вырастал свой вариант логотипа. Потом проверил модель из продакшена: из любого сида она выращивает тот же GR, это единственная форма, к которой её учили возвращаться. Поэтому существ на карточках постов растит обычный клеточный автомат 11×11 с сидом из адреса поста. Без нейросети, около 140 строк.
  • У симуляций свой CI. Рост, укус, разрез и отрастание на весах из продакшена занимают 30–45 с одного ядра на тестовый файл, и рядом с остальными тестами они начали падать по таймауту. Теперь у каждого файла свой раннер, а ещё они гоняются по ночам на main.

Код открыт: github.com/Grumbash/rossev.dev. Обучение в scripts/nca/train.py, модель и гард в src/scripts/nca/.

Попробуйте сломать её: rossev.dev. Если у вас получится, чтобы она застыла в чём-то, что не GR, мне правда интересно как.

#Neural Cellular Automata#Self-Healing Systems
СКОПИРОВАНО