Когда мы пишем веса — мы растим граф
Продолжение WSP-цикла: матрица весов есть граф, онтология→топология→веса, компилятор вместо руки и четыре рефери, которых нельзя уговорить
📖 Читать ⬇ Скачать PDF версия материала для печати и чтения офлайн
Когда мы пишем веса — мы растим граф
1. От жёсткой логики — к живой вероятности
2. WSP на практике: непобедимая сетка из весов — игра из 473 весов
3. От живой вероятности — к весам, которые пишут как код
4. Когда мы пишем веса — мы растим граф (вы здесь)
5. Характер в полкилобайта: куда встраивать веса в производство — про контроллеры
Продолжение эссе «От живой вероятности — к весам, которые пишут как код». Там веса стали текстом, который можно читать. Здесь мы отвечаем на вопрос, который тогда только наметился: что, собственно, происходит, когда мы «считаем веса»? Оказывается — мы перестаём писать правила и начинаем растить граф.
Пролог. Палимпсест
Первую непобедимую сетку мы не спроектировали. Мы её вырастили по болям.
Сначала была простая идея: клетки, линии, угрозы — пишем в матрицу, и пусть себе течёт. Сеть играла, пока однажды противник не поставил вилку — ход, после которого угрожают сразу две линии и закрыть обе невозможно. Мы залатали одну вилку. Через десять партий вспыхнула другая, устроенная иначе. Каждая правка была точечной: два числа в файле — и поведение менялось, как от патча. Это было волшебно и это был палимпсест — так называют пергамент, с которого веками смывали текст ради нового, пока нижние, стёртые слои не начинали проступать сквозь верхний. Вот и у нас под последней правкой проступали все предыдущие, и никто уже не мог сказать, почему именно эти числа дают именно эту игру.
Мы тогда вывели первый закон: посчитать пару может только слой над одиночными детекторами. Вилка — это пара чистых линий через одну клетку; ни один одиночный детектор её не увидит, нужен новый слой, новые узлы, новые рёбра. Мы построили слой — и вилки кончились. Закон пережил игру и стал частью методики.
Но закон был добыт кровью, а не чертежом. И оставался соблазн, который мы наконец себе разрешили: переписать всё заново — так, чтобы чертёж был первичен.
Раздел 1. Что мы делали, когда «считали веса»
Стоит один раз посмотреть на матрицу весов свежими глазами — и маска падает. Матрица весов это взвешенная матрица смежности. Наш формат логов — связь i→j с весом w, потому что аксиома такая-то — это буквально список рёбер графа. А разреженность, эти гордые десять процентов заполненности, означает странное: нули и есть топология. Узор отсутствующих рёбер решает, что вообще может влиять на что; числа при оставшихся рёбрах — только громкость уже возможных связей.
Значит, меняя логику на веса, мы поменяли её не на «числа». Мы поменяли её на граф и течение по графу.
Вот точная формулировка, до которой мы дошли: веса задают ландшафт, а ход сети — это скатывание. Каждая из двадцати двух тысяч досок скатывается в один из ответов-притягателей. «Если вилка — блокируй» в программе — это ветвь, которую исполнитель перепрыгивает. В сети это горбовина ландшафта: доска с вилкой физически не может скатиться никуда, кроме клетки блока. Правило стало геометрией падения.
Отсюда — онто-цепочка, которую теперь мы проходим всегда, в любой задаче методом весов:
ОНТОЛОГИЯ → ТОПОЛОГИЯ → ВЕСА.
Сначала — что существует: клетки, линии, углы, противоположные углы. Потом — какие связи между сущностями: через клетку проходят такие-то линии; паре нужен общий узел. Это и есть топология, те самые десять процентов. И только потом, в последнюю очередь, — знак и величина каждого ребра. Числа — последняя мишура. Без первых двух слоёв писать нечего: веса являются онтологию в числовую среду.
Раздел 2. Компилятор вместо руки
Вторая перепрошивка была злее: мы запретили себе писать числа.
Каждый узел графа объявляется как предикат: «активен, если из этих родителей со знаками истинных не меньше k». Точка. Ни одного числа в исходнике. А дальше работает компилятор: он перебирает все паттерны входов узла, находит худший истинный и лучший ложный, ставит порог ровно посередине и требует зазор — чтобы в активации тангенс гарантированно давал ±0,995, а не «ну примерно единица». Если зазор получить невозможно, потому что функция пороговой суммой не выражается, — компилятор падает. Не сеть тихо врёт на редкой доске, а сборка немедленно говорит: эта формулировка невозможна в этой топологии, ищи другой слой.
Мы споткнулись об это трижды, и каждое спотыкание было подарком:
Запрет на константные наклоны вскрыл, что «центр лучше угла» — тоже правило, а правило обязано висеть на условии («пока доска открыта»), иначе решатель bias законно съедает его как шум. Так в графе родился узел «доска открыта» — и первый ход сетки, центр, перестал быть хитростью и стал следствием аксиомы: ландшафт устроен так, что пустая доска скатывается в центр. По построению.
Невыразимость «ловушки И центр пуст» одной суммой заставила guard переехать в нижний слой, где входы ещё сырые. А невыразимость «вилки на пустой клетке» — оставить занятым клеткам их безвредный артефакт: их логит всё равно никто не читает. Честность топологии оказалась дороже косметики.
И наконец приоритеты решений мы сделали лексикографикой степеней двойки: блок неминуемого выигрыша весит 8192, ответная вилка — 4096, защита от ловушки — 3072, и так до мелочи. Худшая сумма всех младших классов меньше старшего — значит числа физически не могут переорать иерархию. В первой версии приоритеты были мягкими и работали «по удаче подбор»; теперь арифметика обязана.
Раздел 3. Рефери, который не уговаривается
Новую сетку — сто десять узлов, двести восемьдесят девять рёбер — мы приняли не глазами. Её принимали четыре инстанции, и каждая уже кусала нас раньше:
Булевы двойники. Из того же манифеста, из которого скомпилировались числа, автоматически рождается булева спецификация каждого узла — «истинный ответ без всякой сети». Перебор: все двадцать две тысячи двести пятьдесят легальных досок, обе стороны, каждый из ста десяти узлов против своего двойника. Ноль расхождений.
Карта приоритетов. Для каждой доски и каждой пустой клетки — булев класс («здесь выигрыш», «здесь блок», «здесь вилка»), и проверка: argmax логитов обязан совпасть с лексикографическим победителем карты. Ноль расхождений.
Полный перебор партий. Минимакс против свободного, злого, всезнающего оппонента: существует ли партия, где сетка проигрывает, играя по своим числам. Ответ: не существует, обеими сторонами. Это не «сыграли сто партий и повезло» — это исчерпание дерева.
Паритет со старичком. Та же доска, две реализации, два ответа. Двести с лишним расхождений из четырёх с половиной тысяч — и это не стыдно, а интересно: новая сетка открывает игру центром, старая углом; обе непобедимы, это два разных характера, две разные геометрии падения. Расхождение ≠ ошибка — и рефери обязан это различать, иначе он лжёт.
При этом сама проверка приоритетов сначала выдала тридцать два «отказа» — и это была ошибка рефери, не сети: вилка, лежащая в клетке немедленного выигрыша, — артефакт булевой карты, а не поведение. Мы поймали себя на любимом грешке и сначала починили спецификацию арбитра. Рефери самому нужен рефери — этот канон мы вывели ещё на первой игре, и он продолжает окупаться.
Раздел 4. Что изменилось в существе
Первая сетка отвечала на вопрос «можно ли писать поведение весами?» — можно, и она играла. Вторая отвечает на следующий: «а можно ли получать веса из смысла, а не из болей?» — и это меняет профессию.
Человек больше не пишет числа. Человек пишет онтологию и аксиомы: что существует, что из чего следует, что важнее чего. Компилятор берёт на себя арифметику и честен: где он падает — там топология врала, и это дешевле, чем сеть, которая врёт на редком входе. Рефери берёт на себя приёмку: он не уговаривается, он исчерпывает. Между «стало красиво» и «принято» — перебор.
И странное, тёплое чувство на выходе: граф, построенный из «что такое вилка», начинает играть сильнее графа, построенного из «как ходить». Потому что правила, вписанные в ландшафт, нельзя нарушить случаем — им нельзя пойти против течения. Свобода клетки только в том, куда она скатится, а скатывается она единственно.
Мы перестаём писать правила и растим граф, в котором правила — единственные возможные течения.
Эпилог. Две стены
⬇ Скачать игру (офлайн HTML, 24 КБ)
На портале играют в обе сетки. Старую — палимпсест, выросший по десяти болям, четыреста семьдесят три веса, выстраданные живой партией. Новую — граф из онто-каркаса, где числа скомпилированы, каждый узел имеет булевого двойника, а первый ход — центр — следует из аксиомы, как теорема.
Обе непобедимы. Но разница не в силе — разница в том, что вторую можно объяснить. Любую клетку, любой ход, любое «почему не туда»: спуск по слоям, как по лестнице Онто, — до аксиомы, до строки первоисточника. Машина с характером, который можно прочесть до конца, — не фантастика и не надежда. Это двести восемьдесят девять рёбер, которые лежат в файле и ждут, пока их кто-нибудь прочитает.
Лестница, оказывается, была. Просто её надо было сначала построить из сущностей.
Продолжение и источник
• Начало цикла — «От жёсткой логики — к живой вероятности».
• Прошлое продолжение — «От живой вероятности — к весам, которые пишут как код».
• Практика — «WSP на практике: непобедимая сетка из весов, написанных как код»; игра на новом графе подключается сюда же.
Идеи и эксперименты — автора (Telegram: @Tottop10, pro100.D); литературная и программная огранка — агент. CC BY 4.0.
Продолжение — «Характер в полкилобайта: куда встраивать веса в производство»: куда встраивать веса в живой контроллер — и куда не надо.