Продолжение идеи DTG-MA
Если вы помните мою предыдущую статью про DTG-MA, то знаете, что там была идея не «лечить» катастрофическое забывание регуляризациями, а архитектурно запретить нейросети портить уже выученные задачи. Вот что получилось, когда я пошел от этой идеи дальше. Эта технология решает коммерческую задачу в моем стартапе (делюсь с вами решением). Все полноценные тесты и библиотека на pyton есть в git (Split/Permuted MNIST + CIFAR-100, FCDCNN, ResNet-18 + FCD, GPT-2 + FCD-LoRA) можно подключать к своим проектам и использовать.
Суть
Frozen Core Decomposition (FCD) — это архитектурный подход, который математически гарантирует, что нейросеть не забудет старые задачи. Вместо регуляризаций и штрафов мы просто замораживаем общую базу нейросети после первой задачи и даём моделям учиться только через крошечные задачи-специфичные векторы (по 16-32 параметра на задачу).
Результат: забывание < 1%, расход памяти O(k × количество_задач) вместо линейного роста с размером сети, и это работает с CNN, LLM, и любыми другими архитектурами.
Почему нейросети вообще забывают
Проблема, которая стоит в начале, проста как день: одни и те же параметры используются для разных задач.
Представьте, что вы учили сеть распознавать кошек. Какие-то веса стали очень важны — они захватили паттерны усов, ушей, глаз кошек. Потом вы даёте ей новую задачу: распознавать собак. Стандартный SGD спокойно переписывает эти веса, потому что они помешали точности на собаках. И вот кошки забыты.
Это катастрофическое забывание.
Как другие методы пытались это лечить
Метод |
Подход |
Проблема |
|---|---|---|
EWC |
Штраф Fisher за изменение важных весов |
Мягкие ограничения → всё равно забывает (10-50% потери) |
LwF |
Knowledge Distillation из старой модели |
Нужно хранить старые модели или данные |
Replay |
Буфер старых примеров на каждой итерации |
Требует памяти для данных, нарушает приватность |
PNN |
Новый столбец параметров на каждую задачу |
O(T × N) память, становится невыносимо тяжело |
PackNet |
Маски + pruning к новым задачам |
Деградирует при 5+ задачах |
Все эти методы работают через компромиссы. Либо мягкие ограничения (которые иногда ломаются), либо требование хранить прошлое.
Шаг назад: DTG-MA и полное переосмысление
В своей предыдущей статье я представил Dynamic Task-Graph Masked Attention (DTG-MA) — подход, который говорит:
Не пытайтесь штрафовать и регуляризировать. Просто архитектурно запретите нейросети менять веса старых задач.
DTG-MA делал это на уровне графа вычислений:
Для каждой задачи строилась своя подсеть
Маски внимания перекрывали пути между задачами
Градиенты физически не могли попасть туда, куда им нельзя
Результат: забывание → 0%, но...
Цена: параметры растут с каждой новой задачей.
Логичный вопрос: а можно ли сделать то же самое, но без раздувания модели?
Ключевая идея: факторизация весов вместо маскирования
Вместо того чтобы хранить отдельные веса для каждой задачи, мы используем Tucker-стиль тензорную факторизацию.
Как это устроено (и почему это работает)
Вместо обычной матрицы весов слоя:
W ∈ ℝ^(входы × выходы)
Мы генерируем веса как комбинацию разложенных компонент:
W_t = U · (S ×₃ v_t) · V
Где:
U ∈ ℝ^(входы × r) — общая матрица признаков на входе (замораживается)
V ∈ ℝ^(r × выходы) — общая матрица признаков на выходе (замораживается)
S ∈ ℝ^(r × r × k) — «ядро» (core tensor), хранит общее пространство (замораживается)
v_t ∈ ℝ^k — крошечный задача-специфичный вектор (обучается)
Пример размеров (Split MNIST)
Компонента |
Размер |
Параметры |
Обучается? |
|---|---|---|---|
U (784 → 32) |
784 × 32 |
25,088 |
❌ Заморозка после задачи 0 |
V (32 → 10) |
32 × 10 |
320 |
❌ Заморозка после задачи 0 |
S (ядро) |
32 × 32 × 16 |
16,384 |
❌ Заморозка после задачи 0 |
v₀ |
16 |
16 |
✅ Обучается |
v₁ |
16 |
16 |
✅ Обучается |
... |
... |
... |
... |
v₁₉ |
16 |
16 |
✅ Обучается |
Всего для 20 задач: |
41,792 |
Только 320 параметров обучаемых |
Заморозка ядра — главный трюк
Обучение устроено в три этапа:
Этап 1: Первая задача (Task 0)
Обучаем всё: U, V, S и v₀ обычным способом.
L = CrossEntropy(model(x), y)
optimizer.step() # обновляются U, V, S, v₀
Этап 2: Заморозка (один раз)
После первой задачи:
U.requires_grad = False
V.requires_grad = False
S.requires_grad = False
v₀.requires_grad = False # Тоже замораживаем
Этап 3: Все остальные задачи
Для задачи t > 0:
# Инициализируем v_t ортогонально предыдущим
v_t = gram_schmidt_orthogonal(v₀, ..., v_{t-1})
# Обучаем ТОЛЬКО v_t
while training:
loss = CrossEntropy(...) + λ_sep * separation_loss(v_t, [v₀, ..., v_{t-1}])
optimizer.step() # обновляется ТОЛЬКО v_t
# Замораживаем v_t
v_t.requires_grad = False
Почему забывание исчезает (математика)
Вот почему это работает не эмпирически, а по конструкции:
Вес для старой задачи p был:
W_p = U · (S ×₃ v_p) · V
Когда мы обучаем новую задачу t+1:
Параметр |
Заморожен? |
Может ли градиент туда попасть? |
|---|---|---|
U |
✅ Да |
❌ Нет |
V |
✅ Да |
❌ Нет |
S |
✅ Да |
❌ Нет |
v_p (p < t) |
✅ Да |
❌ Нет |
v_t |
✅ Да (после обучения) |
❌ Нет |
Поскольку ни один параметр в формуле W_p не обновляется, вес W_p остаётся абсолютно неизменным.
Это не эмпирическое совпадение. Это математическое следствие архитектуры.
Ключевое отличие: EWC и другие регуляризации говорят: «пожалуйста, не меняй эти веса». FCD говорит: «тебе физически невозможно изменить эти веса».
Сравнение с DTG-MA: в чём разница?
Критерий |
DTG-MA |
FCD |
|---|---|---|
Блокирует что? |
Пути вычислений |
Пути изменения весов |
На каком уровне? |
Граф (маски внимания) |
Параметризация (факторизация) |
Рост памяти |
O(T × N) |
O(k × T) |
Для каких архитектур? |
Transformer-ориентирован |
Любые слои (Dense, Conv, RNN) |
Сложность реализации |
Высокая (изменяет архитектуру) |
Низкая (drop-in адаптер) |
Аналогия:
DTG-MA: запрещаем ходить по определённым дорогам
FCD: запрещаем менять фундамент здания
Практические результаты
Split MNIST (5 задач, 2 класса каждая)
Метод |
Финальная точность |
Забывание |
Параметры на задачу |
|---|---|---|---|
FCD (наш) |
96.1% ± 0.4% |
0.2% ± 0.2% |
16 |
HAT |
82.9% ± 4.1% |
19.3% ± 5.1% |
~100K (маски) |
PackNet |
56.6% ± 2.6% |
40.6% ± 2.3% |
маски |
DER++ |
56.6% ± 2.2% |
52.5% ± 2.9% |
~100K (буфер) |
EWC |
57.0% ± 3.4% |
52.2% ± 4.3% |
~2× параметров |
Fine-tuning (базовый) |
55.8% ± 1.9% |
54.0% ± 2.4% |
0 (но забывает!) |
Что здесь интересно:
Забывание 0.2% vs 52.5% — это разница между архитектурной защитой и попыткой её обойти.
И это при том, что мы добавляем всего 16 параметров на задачу, в то время как другие методы либо добавляют полные маски (~100K), либо требуют буферов данных.
Permuted MNIST (10 задач)
Это жёстче — каждая задача это случайная перестановка пикселей MNIST:
Метод |
Точность |
Забывание |
|---|---|---|
FCD |
82.2% |
0.2% |
EWC |
65.7% |
2.6% |
HAT |
49.2% |
35.5% |
Fine-tuning |
30.3% |
68.6% |
Split CIFAR-100 (10 задач по 10 классов)
Более сложный датасет:
Метод |
Точность |
Забывание |
|---|---|---|
FCD + ResNet-18 адаптер |
59.8% |
0.3% |
ResNet-18 + Fine-tuning |
16.2% |
61.3% |
Это 200× меньше забывания при 3.7× выше точности.
FCDCNN — CNN обученная с нуля
Чтобы показать, что метод работает не только с pretrained моделями:
Метод |
Точность |
Забывание |
|---|---|---|
FCD + CNN (с нуля) |
57.6% |
1.3% |
Fine-tuning CNN |
17.1% |
72.7% |
56× меньше забывания — метод работает даже когда backbone обучается с нуля.
Масштабируемость: когда задач больше чем размер вектора
Что если T > k? (т.е. задач больше, чем размерность v_t)
Количество задач T |
k = 16 |
Точность |
Забывание |
|---|---|---|---|
5 |
16 |
98.4% |
0.0% |
10 |
16 |
97.6% |
0.5% |
16 |
16 |
97.6% |
0.9% |
17 |
16 |
97.4% |
1.1% |
20 |
16 |
96.6% |
1.3% |
Ключевой момент: метод изящно деградирует. При k=16 и 20 задачах точность падает на 1.8%, а забывание только 1.3%. Это не обрыв — это плавное расширение границ.
Почему? Потому что даже когда T > k, вектора всё равно находят почти-ортогональные направления в k-мерном пространстве (это свойство концентрации меры в высоких размерностях).
Большие модели:
GPT-2 + FCD-LoRA для continual learning
Тестировали на трёх последовательных задачах:
Sentiment Analysis (отзывы)
Topic Classification (классификация по темам)
Question Answering (ответы на вопросы)
Конфигурация |
Забывание |
Обучаемые параметры |
% от модели |
|---|---|---|---|
Hard FCD (core freezing) |
0% |
576 |
< 0.01% |
Soft FCD (только separation loss) |
5.4% |
848K |
0.7% |
Стандартный LoRA |
~30% |
768K |
0.6% |
Что произошло:
Стандартный LoRA добавляет матрицы B и A, которые обновляются для каждой новой задачи. Это позволяет максимально использовать параметры, но ломает задачу 1.
FCD-LoRA заменяет LoRA-веса на Tucker-факторизованные, где core замораживается. Результат: 0% забывания (hard FCD) или приемлемые 5.4% с полной ёмкостью (soft FCD).
Ограничения (они есть, не буду скрывать)
1. Нужен ID задачи при инференсе
model.set_task(task_id=0) # "Сеточка, перед тобой задача про кошек"
output = model(x)
Это task-aware setting. Если вы не знаете, какая это задача, метод не поможет.
2. Размерность k ограничивает число идеально ортогональных задач
Если k = 16, то идеально ортогональными могут быть максимум 16 задач. После этого деградация.
Но она плавная (1.3% забывания на 20-й задаче), а не обрыв.
3. Нужно хранить v_t для каждой задачи
Если у вас 1000 задач, нужно хранить 1000 × 16-байтных векторов. Это пустяк по сравнению с полной сетью, но всё равно оверхед O(T×k).
Таблица сравнения со всеми методами
Метод |
Забывание |
Память на задачу |
Задачи-осведомлен? |
Нужны старые данные? |
Легко реализовать? |
|---|---|---|---|---|---|
Fine-tuning |
30-80% |
O(N) |
❌ |
❌ |
✅ Да |
EWC |
10-50% |
O(2N) |
❌ |
❌ |
✅ Да |
LwF |
5-30% |
O(N) |
❌ |
✅ |
⚠️ Условно |
Replay |
1-5% |
O(buffer) |
❌ |
✅ (буфер) |
⚠️ Условно |
PNN |
~0% |
O(T × N) |
✅ |
❌ |
❌ Сложно |
PackNet |
~0% |
O(N) |
✅ |
❌ |
❌ Очень сложно |
FCD |
< 1% |
O(T × k) |
✅ |
❌ |
✅ Да |
Как это реализуется
Есть полная реализация на PyTorch 2.0+:
https://github.com/infosave2007/fcd
Главные компоненты:
1. Tucker-факторизация
def factorized_weight(U, S, v, V):
"""
W_t = U · (S ×₃ v) · V
"""
# S ×₃ v — mode-3 контракция
M = torch.einsum('ijk,k->ij', S, v) # (r, r)
# U · M
P = U @ M # (d_in, r)
# P · V
W = P @ V # (d_in, d_out)
return W
2. Separation loss (для ортогональности)
def separation_loss(v_t, v_prev_list):
"""
Штраф за то, чтобы новый вектор был ортогонален старым
"""
loss = 0
for v_p in v_prev_list:
cos_sim = F.cosine_similarity(v_t.unsqueeze(0), v_p.unsqueeze(0))
loss += cos_sim ** 2
return loss
3. Обучающий цикл
def continual_learning_protocol():
U, V, S = init_tensors()
v_list = []
for task_id in range(num_tasks):
# Инициализируем v_t ортогонально
v_t = gram_schmidt_orthogonal(v_list)
v_t.requires_grad = True
# Обучение
for epoch in range(epochs):
for x, y in dataloader[task_id]:
W_t = factorized_weight(U, S, v_t, V)
logits = linear_layer(x, W_t)
loss = ce_loss(logits, y)
loss += λ_sep * separation_loss(v_t, v_list)
loss.backward()
optimizer.step()
# Заморозка
v_t.requires_grad = False
v_list.append(v_t)
if task_id == 0:
U.requires_grad = False
V.requires_grad = False
S.requires_grad = False
Почему FCD — логичное продолжение DTG-MA
DTG-MA: Как запретить нейросети менять уже выученные веса?
Ответ: маски на графе вычисления.
FCD: Как сделать это эффективно и универсально?
Ответ: заморозить общую основу, учиться через крошечные задачи-специфичные параметры.
Оба подхода исходят из одной революционной идеи:
Катастрофическое забывание — это не проблема оптимизации (которую нужно регуляризировать), а архитектурная проблема.
Если архитектура позволяет градиентам менять критичные веса — забывание неизбежно.
Когда это имеет смысл использовать
✅ Хорошо подходит для:
Robotic continual learning — робот учится выполнять задачи одну за другой
Многоязычные модели — добавляем язык за языком без переобучения
Edge ML — нужна модель, которая учится на устройстве без переполнения памяти
Personalization — система адаптируется к юзеру, не забывая про других
Domain incremental learning — новые домены приходят один за другим
❌ Плохо подходит для:
Task-agnostic setting — если вы не знаете, какая это задача при инференсе
Задач совсем мало (T < 3) — не имеет смысла
Очень большой k — если вам нужна очень высокая ёмкость на задачу
Абляция: что важно в методе?
Конфигурация |
Точность |
Забывание |
|---|---|---|
Полный метод |
96.1% |
0.2% |
Без separation loss |
95.8% |
0.2% |
Без core freezing |
93.2% |
6.7% |
Никаких тактик |
92.2% |
8.1% |
Главный вывод: Core freezing — это 99% успеха. Separation loss добавляет косметику.
Заключение
Frozen Core Decomposition — это не очередной «лосс с штрафом».
Это переосмысление того, как должны выглядеть параметры модели, если мы хотим, чтобы она:
Училась всю жизнь
Не теряла память
Не раздувалась экспоненциально
Ключевая магия: вместо того, чтобы штрафовать изменения весов (мягко) или запрещать пути в графе (для трансформеров), мы просто делаем невозможным для градиентов менять старые веса.
Результат:
< 1% забывания (не 10%, не 5%, а < 1%)
O(k × T) памяти вместо O(T × N)
Универсальность — работает с CNN, RNN, LLM, любыми слоями
Простота — drop-in адаптер, легко добавить к существующему коду
Если вам интересна идея управляемого, математически гарантированного continual learning — рекомендую попробовать.
Ресурсы
Repo: https://github.com/infosave2007/fcd
Научное изложение: doi 18006952
Автор: Олег Кириченко — urevich55@gmail.com
Если вам полезен этот проект: Поддержать через Tribute ?
Orazbay74
Очень крутой и, главное, своевременный подход. Поразительно, как чисто архитектурное решение — Tucker-факторизация и заморозка ядра — позволяет добиться забывания < 1 % при добавлении всего пары десятков параметров.
Этот материал отлично ложится в канву глобального тренда на Lean Engineering. Сегодня вопрос уже не в том, как бесконечно наращивать мощности, а в том — на каких условиях и какой ценой для планеты это делается. ИИ перестал быть просто кодом — это новая форма тяжёлой индустрии. Исследования VU Amsterdam показывают, что по энергопотреблению отрасль уже обгоняет майнинг и даже целые страны.
Бездумно внедряя избыточные модели и тратя ресурсы на бесконечные переобучения там, где может сработать элегантная заморозка ядра (FCD), мы напрямую вносим вклад в те самые гигаватт-часы и миллиарды литров воды на охлаждение.
Подходы вроде Frozen Core Decomposition хорошо демонстрируют, что технологии могут и должны усиливать интеллект, а не только нагрузку на экологию. Эффективность архитектуры сегодня — это уже не просто экономия памяти ради быстродействия, а вопрос нового социального договора и ответственности инженеров за ресурсы планеты.
infosave Автор
Да энергии потребляет не мало, за время тестирования не на самых больших моделях можно было несколько яишниц пожарить ) примерно 4 дня тестов чтобы все проверить, сравнить улучшить и выложить в удобном для понимания виде