Пять синтетических признаков, сжатых в два числа, и сеть, которая после обучения раскладывает их векторы на плоскости почти идеальным пятиугольником: угол между соседними направлениями равен 72°. Так выглядит ключевой эксперимент из статьи Anthropic 2022 года «Toy Models of Superposition», воспроизведённый на чистом NumPy, без PyTorch и autograd: разбор с кодом и графиками опубликован на Towards Data Science.
Все происходящее описывается одним термином: суперпозиция. Сеть хранит больше признаков, чем у неё есть скрытых измерений, и сознательно допускает взаимные помехи между ними. Точность восстановления при этом падает, но плата оказывается выгодной: если признаки активны редко и почти никогда не встречаются в одном примере одновременно, интерференция возникает редко и обходится дешево.
Именно этот механизм объясняет, почему нейроны реальных LLM реагируют сразу на несколько несвязанных тем. Полисемантичность не сбой обучения, а следствие сжатия при разреженных данных. Из этого вывода выросла целая линия работ по интерпретируемости, включая sparse autoencoders.
| Эксперимент | Конфигурация | Что видно |
|---|---|---|
| 1 | 20 признаков, m=2, разные веса важности | Ошибка резко растёт примерно после 12-го признака |
| 2 | 5 равнозначных признаков, m=2, высокая разреженность | Правильный пятиугольник, угол между соседними 72° |
| 3 | Те же обученные векторы | Матрица WᵀW показывает интерференцию между признаками |
| 4 | 30 признаков, m=2, разреженность от 0 до 0.99 | Число представленных признаков растёт с 5 до 24 |
Что такое суперпозиция признаков и почему нейросеть вынуждена сжимать
Постановка задачи простая. Есть n синтетических признаков, каждый это число от 0 до 1. Есть узкое горло из m скрытых измерений, причём m меньше n. Энкодер сжимает вход в m чисел, декодер восстанавливает исходные признаки, а на выходе стоит ReLU.
Если бы признаки были плотными, то есть активными в каждом примере, сеть могла бы точно восстановить не больше m из них. В m-мерном пространстве максимум m взаимно ортогональных направлений, а всё, что сверх этого, неизбежно перекрывается. Ошибки при плотных данных складывались бы во всех примерах без исключения, и сжатие теряло бы смысл.
Почему разреженность - ключ к суперпозиции
Разреженность - это доля нулей во входе. При sparsity=0 каждый признак активен всегда, при sparsity=0.99 признак ненулевой примерно в 1% примеров. Чем выше разреженность, тем реже признаки встречаются одновременно, а значит, тем реже сеть платит за интерференцию между ними.
Отсюда неочевидный вывод: в узкое горло удаётся втиснуть намного больше признаков, чем m. В четвёртом эксперименте при 30 признаках и узком горле в 2 измерения сеть при разреженности около 0.99 удерживает 24 признака из 30, тогда как при разреженности 0 представляет только 5. Никакой магии: оптимизация взвешенной квадратичной ошибки находит конфигурацию, в которой редкие помехи дешевле, чем полный отказ от признака.
Интерференция и полисемантичность: не баг, а стратегия
Интерференция - это взаимное влияние признаков при восстановлении: вектор одного признака проецируется на вектор другого, и сигнал одного подмешивается в выход другого. Полисемантичность - следствие того же процесса на уровне отдельных нейронов: когда концептов больше, чем измерений, одно направление обслуживает сразу несколько признаков.
Аналогия: в переполненном автобусе люди стоят в проходе, потому что сидячих мест меньше, чем пассажиров. Большинство едет не весь маршрут, а выходит на разных остановках, поэтому стоящие мешают друг другу лишь часть времени. Разреженность в этой картинке и есть расписание остановок.
Как воспроизвести эксперимент без PyTorch: архитектура и ручной backward pass
Архитектура минимальна. Вход x это вектор из n чисел, матрица W имеет размер m×n при m < n, скрытое представление считается как h = Wx, восстановление как x_hat = ReLU(Wᵀh). Декодер использует транспонированную матрицу энкодера, поэтому веса называются связанными. Такая симметрия сокращает число параметров вдвое и при этом не мешает появлению суперпозиции.
Функция потерь - взвешенная квадратичная ошибка. Каждый признак входит в неё со своим весом a_i, что позволяет сделать одни признаки важнее других и посмотреть, кого сеть решит сохранить.
Вывод градиентов для сети с связанными весами
Обозначим через x_hat выход декодера, через a вектор весов важности. Шаги вывода такие: сначала производная потери по выходу, затем протаскивание её через ReLU, затем вклад обеих ветвей в градиент по W.
# прямой проход
h = W @ x # (m,)
x_hat = np.maximum(W.T @ h, 0.0) # (n,), ReLU
# потеря L = sum(a_i * (x_i - x_hat_i)^2)
r = x - x_hat
L = np.sum(a * r**2)
# обратный проход
dx_hat = (-2.0 * a * r) * (x_hat > 0) # градиент через ReLU-маску
dh = W @ dx_hat # dL/dh
dW = np.outer(dh, x) + np.outer(h, dx_hat) # вклад энкодера и декодера
W -= lr * dW
Логика вычислений по шагам. Производная потери по x_hat равна -2*a_i*(x_i - x_hat_i). ReLU обнуляет градиент там, где выход был отрицательным, поэтому вектор dx_hat умножается на индикатор x_hat > 0. Дальше градиент идёт в скрытое представление: dL/dh = W @ dx_hat.
Связанные веса дают два слагаемых в градиенте по W. Первое, np.outer(dh, x), приходит от энкодера h = Wx. Второе, np.outer(h, dx_hat), от декодера x_hat = ReLU(Wᵀh), потому что там та же матрица стоит транспонированной. Оба вклада складываются в одну матрицу, и это принципиальный момент: забыть про второе слагаемое значит получить неверный градиент и не увидеть суперпозицию. Если хочется освежить цепное правило и обратный проход в целом, есть отдельный материал про обратное распространение ошибки на конкретных числах.
Весь цикл обучения - это обычный градиентный спуск на NumPy, без autograd и без вычислительного графа. Достаточно numpy и нескольких десятков строк.
Настройка разреженности и генерация синтетических данных
Данные генерируются прямо в коде: ни одного внешнего датасета.
def sample_features(n, sparsity, rng):
values = rng.random(n) # значения от 0 до 1
active = rng.random(n) > sparsity # маска активности
return values * active
Параметр sparsity задаёт долю нулей: при 0.9 примерно девять из десяти признаков в каждом примере обнуляются. В экспериментах использовались n=20 и n=30 при m=2, этого достаточно, чтобы геометрия суперпозиции была видна глазом.
Эксперимент 1: как сеть отказывается восстанавливать малозначимые признаки
Берём 20 признаков, узкое горло в 2 измерения и разные веса важности. Упорядочим признаки по весу и посмотрим на ошибку восстановления каждого. Результат: примерно до 12-го признака ошибка держится низкой, а дальше резко подскакивает. Признаки с малым весом сеть просто не восстанавливает.
В функции потерь нет ничего, что предписывало бы такой отсев: каждый признак входит в неё с положительным весом. Отсев появляется как побочный эффект оптимизации. Если отдать часть дефицитного измерения малозначимому признаку, общая ошибка вырастет сильнее за счёт потери точности у важных. Сеть жертвует меньшим, чтобы сохранить большее, и делает это без всяких явных правил.
Практический смысл: при заданных весах и разреженности модель сама ранжирует признаки по значимости, а граница отсева определяется задачей, а не разработчиком.
Эксперимент 2: пятиугольник из 5 признаков в 2 измерениях
Теперь пять равнозначных признаков, все веса одинаковы, узкое горло по-прежнему 2 измерения, разреженность высокая. После обучения столбцы W, то есть векторы признаков, выстраиваются в правильный пятиугольник. Угол между соседними направлениями равен 72°, скалярное произведение соседних векторов единичной длины составляет cos(72°) ≈ 0.309, а для векторов, стоящих через один, cos(144°) ≈ -0.809. Сумма всех пяти векторов равна нулю.
Почему именно так. В плоскости пять направлений нельзя сделать взаимно ортогональными, максимум два. Любая упаковка оставляет помехи, и задача сводится к тому, чтобы минимизировать худшую пару. При равномерном распределении по окружности интерференция одинакова для всех соседей. Сдвиньте один вектор, и угол с одним соседом уменьшится, помеха с ним вырастет, и именно она начнёт определять итоговую ошибку. Пятиугольник уравнивает всех участников, поэтому и оказывается оптимумом. Автор разбора отмечает, что не ожидал увидеть геометрию в такой маленькой сети.
Ключевая деталь: пятиугольник нигде не задан в коде. В коде есть матрица W, прямой проход, градиент и шаг спуска. Геометрия возникает как следствие оптимизации, и это самый наглядный аргумент в пользу того, что внутренние представления сети стоит изучать отдельно.
Эксперимент 3: матрица WᵀW как карта интерференции
Матрица WᵀW имеет размер n×n, и её элемент (i, j) это скалярное произведение векторов признаков i и j. Диагональные элементы показывают квадраты норм, внедиагональные - интерференцию. Для пятиугольника структура читается с первого взгляда: 0.309 у соседних признаков и -0.809 у признаков через один.
Практическая ценность в том, что такую матрицу можно отрисовать как тепловую карту и по ней сразу увидеть, какие признаки конкурируют за одно направление, а какие разошлись почти ортогонально. В реальных моделях похожий анализ помогает искать полисемантичные нейроны: сильная внедиагональная интерференция подсказывает, что за одним направлением закреплено несколько признаков.
Эксперимент 4: фазовый переход при росте разреженности
Последний эксперимент меняет только один параметр. 30 признаков, узкое горло в 2 измерения, разреженность прогоняется от 0 до 0.99. При нулевой разреженности сеть представляет около 5 признаков из 30, остальные игнорирует. По мере роста разреженности число представленных признаков увеличивается и при 0.99 достигает 24 из 30.
Кривая получается нелинейной: основная часть роста укладывается в узкий диапазон значений, поэтому результат и описывают как фазовый переход в стратегии сети. Логика простая. При низкой разреженности признаки часто активны одновременно, помехи складываются, и упаковка почти всех признаков становится невыгодной. При высокой разреженности совпадения редки, ошибка возникает лишь в отдельных примерах, и сеть может позволить себе держать почти весь набор.
Вывод, который стоит запомнить: число признаков, которые модель готова хранить, зависит не только от соотношения n и m, но и от статистики данных. Один и тот же по размеру слой при разных данных превращается из жадного отборщика в универсальный упаковщик.
Что это значит для интерпретируемости больших моделей и sparse autoencoders
В LLM концептов заведомо больше, чем нейронов, а активации разрежены: конкретный нейрон срабатывает далеко не на каждом токене. Это те же условия, что в игрушечной модели, и результат тот же. Отдельный нейрон реагирует на кошек, математику и юридические тексты одновременно, и это не дефект весов, а способ уложить больше признаков в фиксированную ширину слоя.
Sparse autoencoders решают обратную задачу. На активациях модели обучают автоэнкодер с сильным ограничением на разреженность и получают словарь направлений, каждое из которых активно редко. Логика такая: если исходная активация это плотная смесь концептов, разреженное представление разделяет смесь обратно на отдельные признаки. Anthropic и другие исследовательские группы используют этот подход для поиска концептов в LLM, и игрушечный эксперимент объясняет, почему он вообще может работать.
Ограничение тоже видно из модели. Если за интерференцию платят ошибкой, часть концептов может остаться смешанной и после разложения, а значит полной гарантии монопонятности словаря нет. Размер словаря, коэффициент разреженности и качество реконструкции приходится подбирать, и это отдельная инженерная работа, а не разовое вычисление.
Ограничения воспроизведения и стоит ли повторять
Переносить результаты на реальные сети нужно аккуратно. Что именно ограничивает выводы:
- Данные полностью синтетические, признаки независимы и лишены структуры настоящих данных, а шума в них нет вообще.
- Модель крошечная: m=2, n от 20 до 30, линейный энкодер, ReLU на выходе. Ни глубоких слоёв, ни внимания, ни нелинейностей между признаками.
- Градиенты выведены вручную, так как доступа к PyTorch и autograd у автора разбора не было. Это ограничение стоит держать в голове, если вы сверяете свой код с чужим.
Качественные выводы переносятся на большие модели: суперпозиция и полисемантичность там действительно наблюдаются. Количественные числа переносить нельзя. Угол 72° и «24 из 30» относятся к конкретной конфигурации с её разреженностью и весами, а не к нейросетям в целом.
Повторить стоит, и это дешево: 30-40 строк NumPy, ни одной тяжёлой зависимости, обучение занимает секунды. Меняйте n, m и sparsity, смотрите на нормы столбцов W, стройте матрицу WᵀW и сравнивайте конфигурации между собой. Такой опыт даёт более трезвое понимание интерпретируемости, чем чтение выводов из чужих статей: видно, что геометрия представлений возникает из оптимизации, и видно, насколько хрупкими могут быть объяснения, построенные на одной маленькой модели.