Перейти к содержимому
Ступень 99.3 Интерпретируемость, безопасность и этика

Суперпозиция и разреженные автоэнкодеры

Почему отдельный нейрон отвечает сразу за несколько понятий, как модель упаковывает признаков больше, чем у неё измерений, и как разреженный автоэнкодер распаковывает их обратно. Игрушечная модель «5 признаков → 2 измерения», мини-SAE своими руками, работы Anthropic от Towards Monosemanticity до графов атрибуции и честный разговор о пределах метода.

УРОКstage-9/9-3-interpretability-safety/02-superposition-sae.mdx2 ч 30 минУрок 2 из 5
После урока вы сможете
  • Объяснить полисемантичность и гипотезу суперпозиции и предсказать, как разреженность признаков меняет число признаков, которые хранит модель
  • Реализовать игрушечную модель из Toy Models of Superposition с ручными градиентами и получить пятиугольник
  • Обучить мини-SAE, читать его метрики (L0, доля объяснённой дисперсии, мёртвые латенты) и объяснить компромисс «разреженность против точности»
  • Пересказать, что показали Towards и Scaling Monosemanticity, steering и графы атрибуции в «On the Biology of a Large Language Model», и назвать пределы этих методов

В прошлом уроке мы разбирали головы внимания. Но большая часть параметров LLM живёт в MLP: в уроке 3.1.1 мы посчитали, что у DeepSeek-V3 это около 98%. Хочется анализировать и их: найти нейрон «отвечает за код на Python», нейрон «отвечает за французский» и собрать из них схемы, как из голов.

Не выходит. В MLP маленького трансформера Anthropic нашла нейрон, который срабатывает на академических цитатах, английских диалогах, HTTP-запросах и корейском тексте. Если нейрон не означает ничего одного, анализ «по нейронам» разваливается. Разберём, почему так происходит (суперпозиция), как это распутать (разреженные автоэнкодеры) и что это дало на настоящих моделях, вплоть до разбора «мыслей» Claude в 2025 году.

Полисемантичность и гипотеза суперпозиции

Заголовок раздела «Полисемантичность и гипотеза суперпозиции»

Полисемантичностьюполисемантичность (многозначность нейрона)EN polysemanticityОдин нейрон срабатывает на несколько несвязанных понятий (например, на цитаты, HTTP-запросы и корейский текст). называют ситуацию, когда один нейрон активируется на несколько не связанных понятий. Это видно не только в языковых моделях: в моделях зрения есть нейрон, который реагирует и на морды кошек, и на передки машин. Противоположность — моносемантичностьмоносемантичностьEN monosemanticityЕдиница анализа (нейрон или признак словаря) отвечает ровно за одно понятное понятие.: единица отвечает ровно за одно понятие.

Почему обучение не делает нейроны моносемантичными? Гипотеза Anthropic: полезных понятий (языков, тем, имён, стилей, синтаксических ролей) миллионы, а нейронов в слое — тысячи. Модели выгодно хранить больше признаковпризнакEN featureОдно измеримое свойство объекта, вход модели. «Feature engineering» — конструирование признаков. (здесь это свойства входа, которые модель распознаёт: «текст на корейском», «внутри кода», «упоминание моста»), чем у неё измерений. Это и есть суперпозициясуперпозиция признаковEN superpositionМодель хранит больше признаков, чем у неё измерений, раскладывая их по почти ортогональным направлениям. Работает, пока признаки редко активны одновременно..

Почти ортогональных направлений очень много

Заголовок раздела «Почти ортогональных направлений очень много»

Как уместить 5 признаков в 2 числа? Если каждому признаку дать своё направление и требовать ортогональностиортогональные (перпендикулярные) векторыEN orthogonal vectorsВекторы, скалярное произведение которых равно нулю. Ортогональная матрица — та, у которой столбцы единичные и попарно ортогональны. (скалярное произведение 0), направлений будет ровно столько, сколько измерений. Но если согласиться на почти ортогональные (угол 85–95° вместо ровно 90°), их число растёт с размерностью экспоненциально. Это следствие леммы Джонсона — Линденштрауса; красиво показано в ролике 3Blue1Brown «How might LLMs store facts» (глава 7 серии о глубоком обучении, в нём есть раздел о суперпозиции). Проверим на случайных векторах:

import numpy as np
rng = np.random.default_rng(0)
for d in (2, 16, 128, 512):
n = 4 * d # векторов в 4 раза больше, чем измерений
v = rng.normal(size=(n, d))
v /= np.linalg.norm(v, axis=1, keepdims=True) # случайные единичные направления
cos = v @ v.T
np.fill_diagonal(cos, 0.0) # косинус вектора с самим собой не считаем
print(f"d = {d:3d}, векторов {n:4d}: средний |cos| = {np.abs(cos).mean():.3f}, "
f"угол обычно {np.degrees(np.arccos(np.abs(cos).mean())):.0f}°")
d = 2, векторов 8: средний |cos| = 0.603, угол обычно 53°
d = 16, векторов 64: средний |cos| = 0.198, угол обычно 79°
d = 128, векторов 512: средний |cos| = 0.070, угол обычно 86°
d = 512, векторов 2048: средний |cos| = 0.035, угол обычно 88°

В 512-мерном пространстве даже 2048 случайных направлений почти перпендикулярны друг другу. А у GPT-2 small поток шириной 768, у Qwen3-8B — 4096. Места для суперпозиции много.

Бесплатно ничего не бывает. Пусть признак ii хранится направлением WiW_i, а модель «читает» его скалярным произведением. Если активны сразу несколько признаков, прочитанное значение

x^i=ReLU(∥Wi∥2xi+∑j≠ixj (Wi⋅Wj)+bi)\hat x_i = \mathrm{ReLU}\Big(\lVert W_i \rVert^2 x_i + \sum_{j \ne i} x_j\, (W_i \cdot W_j) + b_i\Big)

состоит из полезного слагаемого и помех от остальных признаков: каждый активный сосед добавляет xjx_j, умноженное на скалярное произведение направлений (при единичных длинах — на косинус угла между ними). Словами: чем больше соседей активно одновременно и чем они ближе по направлению, тем сильнее искажение. Выход из положения — разреженность. Если признаки редко бывают активны вместе, помехи редки, а небольшие помехи «срезает» отрицательное смещение bib_i перед ReLU.

Игрушечная модель: 5 признаков в 2 измерениях

Заголовок раздела «Игрушечная модель: 5 признаков в 2 измерениях»

В «Toy Models of Superposition» (Elhage и соавт., 2022) эту идею проверили на модели, которую можно понять целиком. Есть 5 признаков x∈[0,1]5x \in [0, 1]^5. Каждый с вероятностью SS (разреженность) равен нулю, иначе — случайному числу. Модель сжимает их в 2 измерения и восстанавливает:

h=Wx,x^=ReLU(W⊤h+b),L=E[∑iIi (x^i−xi)2].h = W x, \qquad \hat x = \mathrm{ReLU}(W^\top h + b), \qquad \mathcal{L} = \mathbb{E}\Big[\sum_i I_i\, (\hat x_i - x_i)^2\Big].

Здесь WW — матрица 2×52 \times 5, её столбец WiW_i — направление признака ii на плоскости; IiI_i — важность признака (у нас Ii=0.9iI_i = 0.9^i: признак 0 важнее всех). Сжатие линейное, как в слое модели, а ReLU на выходе позволяет срезать помехи. Модель сама решает, какие признаки хранить и как их расположить.

Мы обучили её для трёх значений разреженности (код — в задании ниже):

Разреженность SSСколько признаков хранитКак расположены
0 (все признаки активны всегда)2 из 5два самых важных под прямым углом; остальные модель не хранит и выдаёт их среднее (≈ 0.5) через смещение bb
0.74 из 5две пары противоположных направлений, пары перпендикулярны
0.955 из 5правильный пятиугольник: соседи через 72°, b≈−0.24b \approx -0.24

Чем реже признаки, тем больше их модель упаковывает в те же два измерения. Посмотрим, как работает пятиугольник: восстановим обученной при S=0.95S = 0.95 моделью одиночный признак 0, x=(1,0,0,0,0)x = (1, 0, 0, 0, 0), и пару признаков 0 и 1 сразу.

x = (1, 0, 0, 0, 0) → x̂ = (1.07, 0.00, 0.17, 0.14, 0.00)
x = (1, 1, 0, 0, 0) → x̂ = (0.00, 0.00, 0.51, 0.00, 0.00)

Одиночный признак восстановлен почти идеально, с небольшими помехами 0.17 и 0.14 на двух ближайших соседях (косинус с ними около 0.3, и смещение −0.24-0.24 срезало не всё). А пару модель провалила полностью: направления признаков 0 и 1 почти противоположны (косинус −0.83-0.83), их сумма почти обнулилась и вылезла в чужом признаке 2. Модель «знает», что при S=0.95S = 0.95 два признака вместе встречаются в 0.052=0.25%0.05^2 = 0.25\% примеров, и жертвует этим случаем ради хранения всех пяти.

Главные выводы статьи:

  • Переход от «не хранить» к «хранить в суперпозиции» происходит скачком при изменении разреженности или важности — авторы называют это фазовыми переходами.
  • Признаки укладываются в правильные фигуры: двуугольники (пары противоположных направлений), треугольники, пятиугольники, тетраэдры.
  • Суперпозиция объясняет полисемантичность: если признаков больше, чем нейронов, нейрон неизбежно участвует в нескольких признаках сразу. Нейрон — «не та» единица анализа.

В задании вы пишете генератор разреженных признаков, прямой проход и ручные градиенты (как в micrograd из модуля 3.2). Главная тонкость: WW входит в формулу дважды, при сжатии и при восстановлении, и градиенты от двух вхождений складываются. Цикл обучения на Adam уже готов.

Игрушечная модель суперпозиции: 5 признаков → 2 измеренияnumpy · нужен интернет6 тестов
import numpy as np


# ── Ваша часть ─────────────────────────────────────────────────────────────────
def generate_batch(n, n_features, sparsity, rng):
    """Синтетические «признаки»: массив (n, n_features).

    Каждый элемент независимо равен 0 с вероятностью sparsity, а иначе — случайному числу из [0, 1)
    (rng.random). sparsity = 0 — все признаки активны всегда, sparsity = 0.95 — признак «горит» редко.
    """
    # TODO
    ...


def forward(W, b, x):
    """Игрушечная модель из Toy Models of Superposition: n_features → m измерений → обратно.

    W — (m, n_features): столбец W[:, i] — направление признака i в маленьком пространстве.
    h = x @ W.T                      — сжали: (n, m)
    x_hat = ReLU(h @ W + b)          — восстановили: (n, n_features)
    Вернуть (h, x_hat).
    """
    # TODO
    ...


def loss_and_grads(W, b, x, importance):
    """Взвешенная ошибка восстановления и её градиенты (руками, как в micrograd).

    loss = среднее по примерам от sum_i importance[i] * (x_hat[:, i] - x[:, i])**2
    Вернуть (loss, dW, db): dW той же формы, что W, db — что b.
    Подсказка: W входит в формулу ДВАЖДЫ (сжатие и восстановление) — градиенты от двух вхождений складываются.
    """
    # TODO
    ...


def count_represented(W, threshold=0.5):
    """Сколько признаков модель «хранит»: число столбцов W[:, i] с длиной больше threshold."""
    # TODO
    ...


# ── Готовый цикл обучения (писать не нужно) ────────────────────────────────────
N_FEATURES, M = 5, 2
IMPORTANCE = 0.9 ** np.arange(N_FEATURES)        # признак 0 важнее всех, признак 4 — меньше всех


def train_toy(sparsity, seed=0, steps=1000, batch=256, lr=0.03):
    """Обучает W (2×5) и b (5,) оптимизатором Adam (урок 1.4.3) на ваших функциях."""
    rng = np.random.default_rng(seed)
    W = rng.normal(size=(M, N_FEATURES)) * np.sqrt(2 / (M + N_FEATURES))
    b = np.zeros(N_FEATURES)
    state = [np.zeros_like(W), np.zeros_like(W), np.zeros_like(b), np.zeros_like(b)]
    for t in range(1, steps + 1):
        x = generate_batch(batch, N_FEATURES, sparsity, rng)
        loss, dW, db = loss_and_grads(W, b, x, IMPORTANCE)
        for p, g, m_, v_ in ((W, dW, 0, 1), (b, db, 2, 3)):
            state[m_] = 0.9 * state[m_] + 0.1 * g
            state[v_] = 0.999 * state[v_] + 0.001 * g * g
            p -= lr * (state[m_] / (1 - 0.9 ** t)) / (np.sqrt(state[v_] / (1 - 0.999 ** t)) + 1e-8)
    return W, b


def describe(W):
    """Длины столбцов W и углы направлений признаков в градусах."""
    norms = np.linalg.norm(W, axis=0)
    angles = np.degrees(np.arctan2(W[1], W[0]))
    return " ".join(f"#{i}: |W|={n:.2f} угол={a:+4.0f}°" for i, (n, a) in enumerate(zip(norms, angles)))


# ── Проверьте себя кнопкой «Запустить» ─────────────────────────────────────────
rng = np.random.default_rng(0)
x = generate_batch(10000, N_FEATURES, 0.9, rng)
if x is not None:
    print("доля нулей:", round(float((x == 0).mean()), 3), "(ожидается около 0.9)")
if loss_and_grads(np.ones((M, N_FEATURES)), np.zeros(N_FEATURES), np.ones((4, N_FEATURES)), IMPORTANCE) is not None:
    for s in (0.0, 0.7, 0.95):
        W, b = train_toy(s)
        print(f"sparsity={s}: модель хранит {count_represented(W)} из 5 признаков")
        print("   ", describe(W))

Попробуйте поменять важности (например, все равны 1) и разреженность 0.8–0.9: увидите промежуточные конфигурации.

Разреженные автоэнкодеры: распаковать обратно

Заголовок раздела «Разреженные автоэнкодеры: распаковать обратно»

Если гипотеза суперпозиции верна, вектор активаций aa (размерности dd) — это сумма немногих направлений из огромного «словаря» на m≫dm \gg d признаков: a≈∑jfj dja \approx \sum_j f_j\, d_j, и почти все коэффициенты fjf_j нули. Задача найти такой словарь по одним активациям называется словарным обучениемсловарное обучениеEN dictionary learningПоиск «словаря» направлений, через которые каждый вектор данных выражается разреженной комбинацией. SAE — способ словарного обучения..

Маленький пример. Словарь из трёх направлений на плоскости: d1=(1,0)d_1 = (1, 0), d2=(0,1)d_2 = (0, 1), d3=(0.71,0.71)d_3 = (0.71, 0.71). Вектор a=(0.71,0.71)a = (0.71, 0.71) можно записать как 0.71 d1+0.71 d20.71\, d_1 + 0.71\, d_2 (сумма модулей коэффициентов 1.42) или как 1.0⋅d31.0 \cdot d_3 (сумма 1.0). Штраф за сумму модулей (L1-норму из урока 1.1.1) выбирает второе объяснение: одно слово словаря вместо двух. Так L1 толкает к разреженным, а значит, понятным разложениям.

Учить словарь удобно нейросетью. Разреженный автоэнкодерразреженный автоэнкодер (SAE)EN sparse autoencoderАвтоэнкодер с широким скрытым слоем и штрафом за число активных латентов. Обученный на активациях LLM, раскладывает их в сумму немногих интерпретируемых признаков. (sparse autoencoder, SAE) — автоэнкодер наоборот. Обычный автоэнкодер (урок 9.1.3) сжимает данные через узкое горлышко. У SAE скрытый слой шире входа, а сжатие заменено требованием, чтобы активных латентов было мало:

f=ReLU((a−bdec)Wenc+benc),a^=f Wdec+bdec,L=∥a−a^∥2+λ∥f∥1.f = \mathrm{ReLU}\big((a - b_{dec}) W_{enc} + b_{enc}\big), \qquad \hat a = f\, W_{dec} + b_{dec}, \qquad \mathcal{L} = \lVert a - \hat a \rVert^2 + \lambda \lVert f \rVert_1 .

Словами: энкодер переводит активацию в mm неотрицательных кодов ff, декодер собирает её обратно как взвешенную сумму строк WdecW_{dec}. Строка jj матрицы WdecW_{dec} — направление признака jj, fjf_j — его сила в данном примере. Loss штрафует и за ошибку восстановления, и за сумму кодов; λ\lambda задаёт баланс. Строки WdecW_{dec} после каждого шага нормируют на единицу: иначе модель «обманула» бы штраф, уменьшив ff в 10 раз и увеличив WdecW_{dec} в 10 раз.

flowchart TD
A["активация a<br/>d = 768"] --> E["энкодер + ReLU"]
E --> F["коды f: m = 24 576<br/>активны лишь десятки"]
F --> D["декодер W_dec<br/>строка j — направление признака j"]
D --> R["восстановление â"]
SAE «распаковывает» плотный вектор в широкий разреженный код (размеры для примера)

У SAE нет одного числа качества, смотрят сразу несколько:

  • L0 — среднее число активных латентов на пример. Меньше — понятнее разложение.
  • Точность восстановления — доля объяснённой дисперсии (FVE, fraction of variance explained) или, что важнее для LLM, насколько вырастет loss модели, если подставить в неё восстановление a^\hat a вместо настоящей активации.
  • Мёртвые латенты — признаки, которые не включаются ни разу. Зря потраченная ёмкость словаря.
  • Интерпретируемость — понятны ли признаки. Смотрят примеры с сильнейшей активацией; в больших работах описания пишет другая LLM (auto-interpretation) и проверяет, предсказывает ли описание активации.

Главный компромисс — между разреженностью и точностью. Вот что дал мини-SAE из задания ниже: 24 истинных признака в 16-мерном пространстве, у SAE 48 латентов, 600 шагов обучения:

λ\lambdaL0FVEмёртвых латентовнайдено признаков (cos > 0.9)
021.50.99865 из 24
0.055.60.982218 из 24
0.22.40.950724 из 24
1.01.20.6721724 из 24

Без штрафа восстановление почти идеальное, но коды плотные: на пример включается 21 латент, и истинных признаков среди направлений не видно. При λ=0.2\lambda = 0.2 найдены все 24. При λ=1\lambda = 1 направления по-прежнему правильные, но точность упала до 0.67: L1 занижает силу всех активных кодов (эффект «усадки», shrinkage), и треть латентов умерла. Именно с этими проблемами борются современные варианты: TopK SAE (OpenAI) оставляет kk самых сильных латентов вместо L1-штрафа, а JumpReLU (Google DeepMind) обнуляет коды ниже обучаемого порога.

Практика: мини-SAE находит истинные признаки

Заголовок раздела «Практика: мини-SAE находит истинные признаки»

Данные устроены как гипотеза суперпозиции: 24 случайных направления в R16\mathbb{R}^{16}, в каждом примере горит в среднем 1.2 признака. Вы пишете прямой проход, loss с ручными градиентами (здесь bdecb_{dec} входит дважды), поиск совпадений по косинусу и метрики. Тест проверит, что SAE нашёл почти все истинные направления.

Мини-SAE: разреженный автоэнкодер на синтетических активацияхnumpy · нужен интернет6 тестов
import numpy as np


# ── Ваша часть ─────────────────────────────────────────────────────────────────
def sae_forward(params, a):
    """Разреженный автоэнкодер. a — активации (n, d); m — число латентов SAE (m > d).

    f     = ReLU((a - b_dec) @ W_enc + b_enc)    — коды признаков, (n, m)
    a_hat = f @ W_dec + b_dec                    — восстановление, (n, d)
    params — словарь: W_enc (d, m), b_enc (m,), W_dec (m, d), b_dec (d,).
    Вернуть (f, a_hat).
    """
    # TODO
    ...


def sae_loss_and_grads(params, a, lam):
    """loss = среднее по примерам ||a_hat - a||² + lam * среднее по примерам sum_j f_j.

    Вернуть (loss, grads), где grads — словарь с теми же ключами и формами, что params.
    Подсказки: производная L1-штрафа по f_j равна lam / n там, где f_j > 0 (ReLU всё равно обнулит остальное);
    b_dec входит в формулу ДВАЖДЫ — вычитается на входе энкодера и прибавляется на выходе.
    """
    # TODO
    ...


def match_features(D_true, W_dec):
    """Для каждого истинного признака (строка D_true, форма (k, d)) — лучшее косинусное сходство
    с каким-нибудь направлением декодера (строка W_dec, форма (m, d)). Вернуть массив (k,).
    Строки не обязательно единичной длины — нормируйте сами."""
    # TODO
    ...


def sae_metrics(params, a):
    """Три стандартные метрики SAE на данных a — словарь:
    "l0"   — среднее число активных (f > 0) латентов на пример;
    "dead" — сколько латентов не включились ни на одном примере (int);
    "fve"  — доля объяснённой дисперсии: 1 - sum((a_hat - a)²) / sum((a - a.mean(axis=0))²).
    """
    # TODO
    ...


# ── Готовые данные и цикл обучения (писать не нужно) ───────────────────────────
D, K, M = 16, 24, 48          # 24 истинных признака живут в 16-мерном пространстве; у SAE 48 латентов


def make_dictionary(seed=0):
    """Истинные направления признаков: K случайных единичных векторов в R^D (почти, но не совсем ортогональных)."""
    rng = np.random.default_rng(seed)
    d_true = rng.normal(size=(K, D))
    return d_true / np.linalg.norm(d_true, axis=1, keepdims=True)


def make_activations(n, d_true, rng, p=0.05):
    """«Активации модели»: каждый признак горит с вероятностью p и силой из [0.5, 1.5]; a = c @ d_true."""
    c = (rng.random((n, K)) < p) * rng.uniform(0.5, 1.5, size=(n, K))
    return c @ d_true


def train_sae(d_true, lam, steps=600, batch=128, lr=0.02, seed=0):
    """Adam на ваших градиентах; после каждого шага строки W_dec нормируются на единицу (стандартный приём)."""
    rng = np.random.default_rng(seed)
    w_dec = rng.normal(size=(M, D))
    w_dec /= np.linalg.norm(w_dec, axis=1, keepdims=True)
    params = {"W_enc": w_dec.T.copy(), "b_enc": np.zeros(M), "W_dec": w_dec, "b_dec": np.zeros(D)}
    m1 = {k: np.zeros_like(v) for k, v in params.items()}
    m2 = {k: np.zeros_like(v) for k, v in params.items()}
    for t in range(1, steps + 1):
        a = make_activations(batch, d_true, rng)
        _, grads = sae_loss_and_grads(params, a, lam)
        for k in params:
            m1[k] = 0.9 * m1[k] + 0.1 * grads[k]
            m2[k] = 0.999 * m2[k] + 0.001 * grads[k] ** 2
            params[k] -= lr * (m1[k] / (1 - 0.9 ** t)) / (np.sqrt(m2[k] / (1 - 0.999 ** t)) + 1e-8)
        params["W_dec"] /= np.linalg.norm(params["W_dec"], axis=1, keepdims=True)
    return params


# ── Проверьте себя кнопкой «Запустить» ─────────────────────────────────────────
d_true = make_dictionary()
a_test = make_activations(4000, d_true, np.random.default_rng(99))
print(f"в среднем на пример горит {K * 0.05:.1f} истинных признака из {K}, а измерений всего {D}")
probe = {"W_enc": np.eye(D, M), "b_enc": np.zeros(M), "W_dec": np.eye(M, D), "b_dec": np.zeros(D)}
if sae_loss_and_grads(probe, a_test[:8], 0.1) is not None:
    params = train_sae(d_true, lam=0.2)
    best = match_features(d_true, params["W_dec"])
    if best is not None:
        print(f"найдено признаков с cos > 0.9: {(best > 0.9).sum()} из {K}; худшее совпадение {best.min():.2f}")
    met = sae_metrics(params, a_test)
    if met is not None:
        print(f"L0 = {met['l0']:.2f}, мёртвых латентов {met['dead']} из {M}, FVE = {met['fve']:.3f}")

Первая большая работа Anthropic с SAE. Модель — однослойный трансформер с 512 нейронами в MLP. Словари обучали размером от 512 до 131 072 признаков на 8 млрд векторов активаций; основной прогон — 4096 признаков, в 8 раз больше нейронов. Получилось более 4000 понятных признаков: последовательности ДНК, base64, арабский, иврит и корейский текст, HTTP-запросы, юридический язык, данные о пищевой ценности. Важное наблюдение — расщепление признаков: в словаре побольше один признак «распадается» на несколько более узких. Значит, единственного «правильного» набора признаков нет, гранулярность зависит от размера словаря.

Через полгода метод масштабировали на продакшен-модель Claude 3 Sonnet: SAE до 34 млн признаков на остаточном потоке среднего слоя. Признаки оказались абстрактными. Признак «мост Золотые Ворота» срабатывает на упоминания моста на разных языках и даже на его изображения, хотя словарь учили только на тексте. Нашлись признаки для ошибок в коде, скрытности, гендерных стереотипов, льстивой похвалы, мошеннических писем. Авторы честно пишут об ограничениях: найти полный набор признаков текущими методами непомерно дорого, а найденные покрывают лишь малую часть понятий модели.

Признак — направление в остаточном потоке, поэтому его можно не только читать, но и добавлять: a′=a+α dja' = a + \alpha\, d_j, где djd_j — строка WdecW_{dec}, α\alpha — сила. Такой вектор управлениявектор управления (steering)EN steering vectorНаправление в остаточном потоке (например, вектор признака SAE), которое прибавляют к активациям во время генерации, чтобы сдвинуть поведение модели. (steering vector) сдвигает поведение модели без дообучения. Самый известный пример — Golden Gate Claude: 23 мая 2024 года Anthropic на 24 часа открыла версию Claude 3 Sonnet, в которой признак моста был усилен до 10× его максимальной активации. Модель сводила к мосту любой разговор, а на вопрос о своём облике отвечала, что она и есть мост. В той же работе усиление признака мошеннических писем заставило модель написать такое письмо вопреки обучению безопасности. Значит, признаки причинно управляют поведением, а steering — и инструмент контроля, и потенциальный риск.

  • OpenAI (Gao и соавт., 2024) обучила TopK SAE на 16 млн латентов на активациях GPT-4, вывела законы масштабирования для SAE; мёртвых латентов в самом большом — всего 7%.
  • Google DeepMind выложила Gemma Scope (2024): более 400 открытых JumpReLU SAE на все слои и подслои Gemma 2 2B и 9B (и часть слоёв 27B), в сумме больше 30 млн признаков. На обучение ушло около 15% вычислений претрейна Gemma 2 9B.
  • Интерактивно смотреть признаки открытых моделей (в том числе из Gemma Scope) можно на Neuronpedia: у признаков есть примеры активаций и автоописания, а для части моделей — steering прямо в браузере.

Признаки SAE описывают один слой. Чтобы увидеть, как модель приходит к ответу, нужны связи между признаками разных слоёв. В марте 2025 года Anthropic выпустила две статьи: о методе («Circuit Tracing: Revealing Computational Graphs in Language Models», Ameisen и соавт.) и о находках («On the Biology of a Large Language Model», Lindsey и соавт.).

  1. MLP всех слоёв заменяют межслойным транскодероммежслойный транскодер (CLT)EN cross-layer transcoderРазреженная замена MLP-слоёв. Каждый признак читает остаточный поток на одном слое и пишет в выходы всех последующих MLP; основа графов атрибуции. (CLT): каждый его признак читает остаточный поток на одном слое и пишет в выходы всех последующих MLP. Так получается «заменяющая модель», которая приближает исходную, но состоит из понятных признаков. Для Claude 3.5 Haiku использовали CLT на 30 млн признаков.
  2. Для конкретного промпта строят граф атрибуцииграф атрибуцииEN attribution graphГраф для одного промпта, где узлы — активные признаки заменяющей модели, а рёбра — их прямые влияния друг на друга и на ответ (Anthropic, 2025).: узлы — активные признаки, токены промпта и логиты ответа, рёбра — прямые линейные влияния между ними (паттерны внимания при этом заморожены). То, что транскодер не смог восстановить, попадает в отдельные «узлы ошибки».
  3. Граф прореживают до самых влиятельных путей, похожие признаки группируют в «супер-узлы» и подписывают.
  4. Гипотезу проверяют вмешательством в исходную модель: подавляют или подставляют признаки и смотрят на ответ — та же логика, что у activation patching из прошлого урока.
  • Многошаговое рассуждение. На вопрос «столица штата, в котором находится Даллас» внутри активируются признаки «Даллас находится в Техасе», затем — «столица Техаса — Остин». Промежуточный шаг «Техас» виден в графе, хотя модель его не произносит.
  • Планирование рифмы. В двустишии «He saw a carrot and had to grab it, / His hunger was like a starving rabbit» модель выбирает рифму «rabbit» до того, как пишет вторую строку. Если подавить этот план, она рифмует с «habit»; если подставить понятие «green», строка закончится на «green».
  • Устный счёт. Для 36 + 59 работают параллельные пути: один грубо оценивает величину суммы, другой точно вычисляет последнюю цифру.
  • Многоязычность. Понятия «маленький» и «противоположность» используют одни и те же признаки в английском, французском и китайском. Доля общих для языков признаков растёт с размером: у Claude 3.5 Haiku она более чем вдвое выше, чем у модели поменьше.
  • Галлюцинации. По умолчанию включена схема «не могу ответить». Признаки «знакомая сущность» (например, Майкл Джордан) её подавляют. Галлюцинация — это осечка: «знакомость» срабатывает на незнакомом имени.
  • Недобросовестное рассуждение. На трудной задаче с косинусом модель выдала правдоподобную цепочку рассуждений, а в графе не нашлось никаких следов заявленного вычисления — «мотивированное рассуждение» под желаемый ответ. Это связывает интерпретируемость с выравниванием из следующего урока: словам модели о себе нельзя верить без проверки.
  • Понятную картину графы дают примерно для четверти промптов, на которых их пробовали; в статье показаны удачные случаи.
  • Заменяющая модель описывает исходную неполно, узлы ошибки бывают большими, а внимание заморожено: почему голова смотрит туда, куда смотрит, граф не объясняет.
  • Даже для промпта из нескольких десятков слов на разбор уходит несколько часов работы человека.

Библиотеку для построения таких графов на открытых моделях, circuit-tracer, Anthropic выложила 29 мая 2025 года вместе с исследователями программы Anthropic Fellows и командой Decode Research (Neuronpedia). Без установки её можно попробовать в браузере: neuronpedia.org/gemma-2-2b/graph. Там есть готовый граф для того же вопроса про Даллас и Остин.

SAE — самый популярный инструмент интерпретируемости 2023–2025 годов, но не волшебная линза:

  • Восстановление неточное: подстановка a^\hat a вместо настоящей активации ухудшает модель, часть вычислений словарь не описывает.
  • Признаки зависят от словаря: размер словаря и λ\lambda меняют набор признаков. Это удобная оптика, а не «истинные атомы» модели.
  • Описания субъективны: подпись по десятку примеров легко сделать слишком красивой, нужна проверка вмешательством.
  • Польза на практике спорная. В марте 2025 года команда интерпретируемости Google DeepMind опубликовала отрицательный результат: в распознавании вредных намерений на промптах, непохожих на обучающие, зонды на признаках SAE работали хуже обычных линейных зондов. Команда снизила приоритет фундаментальной работы над SAE, оговорившись, что считает их полезными, но не прорывными.
  • Дорого: Gemma Scope потребовала около 15% вычислений претрейна модели.

Открытые вопросы: суперпозиция внутри голов внимания, признаки, «размазанные» по нескольким слоям, и насколько вообще верна гипотеза, что понятия — это линейные направления.

Проверь себя6 вопросов
1 Модель сжимает 5 признаков в 2 измерения. Когда она выгоднее всего хранит все пять в суперпозиции?
2 Пятиугольник на плоскости: угол между соседними направлениями признаков 72°. Чему равна помеха, которую активный признак единичной силы вносит в чтение соседа при единичных длинах векторов (до смещения и ReLU)? Ответ — cos⁡72°\cos 72°, до двух знаков.
3 Зачем после каждого шага обучения SAE нормировать строки WdecW_{dec} на единицу?
4 У SAE с λ=0\lambda = 0 доля объяснённой дисперсии 0.998, но L0 = 21 и истинные признаки почти не найдены. Какие выводы верны?Можно выбрать несколько ответов
5 Что показал Golden Gate Claude?
6 Какие ограничения графов атрибуции называют сами авторы «On the Biology of a Large Language Model»?Можно выбрать несколько ответов
  • Нейроны полисемантичны, потому что признаков больше, чем измерений: модель хранит их в суперпозиции, по почти ортогональным направлениям. Это выгодно, когда признаки разреженные; цена — помехи, которые срезает ReLU со смещением.
  • Игрушечная модель «5 → 2» показывает это целиком: при плотных признаках — 2 признака под 90°, при редких — пятиугольник.
  • SAE раскладывает активацию в сумму немногих направлений широкого словаря: ∥a−a^∥2+λ∥f∥1\lVert a - \hat a\rVert^2 + \lambda \lVert f\rVert_1. Качество — это компромисс L0, точности восстановления и мёртвых латентов; TopK и JumpReLU смягчают усадку L1.
  • На Claude 3 Sonnet SAE нашли миллионы абстрактных признаков, а steering превратил их в рычаги (Golden Gate Claude). Графы атрибуции 2025 года связали признаки в схемы: промежуточные шаги, планирование, параллельный счёт.
  • Пределы реальны: неполное восстановление, зависимость от словаря, четверть промптов с понятной картиной, спорная практическая польза.

Дальше: урок 9.3.3 «Выравнивание: чего мы хотим от модели и как этого добиться». Мы научились заглядывать внутрь модели; теперь — как задать, каким должно быть её поведение, и почему это сложнее, чем кажется.