Files
PracticeClassif/_research/SECTION_mona.md
2026-06-30 15:17:40 +03:00

19 KiB
Raw Permalink Blame History

Mona-layer (Multi-cognitive Visual Adapter) — adapter-based PEFT для зрения

Источники: статья Yin, Hu, Li, Zhang, Yang — «5%>100%: Breaking Performance Shackles of Full Fine-Tuning on Visual Recognition Tasks», arXiv:2408.08345 (CVPR 2025); официальная реализация GitHub Leiyi-Hu/mona. Все формулы блока и псевдокод ниже выверены по файлу swin_transformer_mona.py (классы Mona и MonaOp).


1. Проблема: full fine-tuning против PEFT

Классическая схема переноса — pre-train & fine-tune: большой backbone обучается на крупном корпусе (ImageNet-21k, LVD-142M и т.п.), затем все его веса дообучаются на целевой задаче. На больших целевых датасетах это работает, но имеет два дефекта на малых данных:

  • Стоимость. Для Swin-L это ~198M обучаемых параметров и отдельная полная копия весов на каждую задачу/чекпойнт (Yin et al., 2024).
  • Переобучение. При малом числе примеров обновление всех параметров подгоняет backbone под шум обучающей выборки; обобщение падает.

PEFT (parameter-efficient fine-tuning, она же delta-tuning) решает обе проблемы: backbone заморожен (\nabla_\theta = 0), обучается лишь малая доля новых параметров \Delta (обычно 0.15 %). Семейства PEFT:

Семейство Что обучается Представитель
Bias-tuning только bias-векторы BitFit
Low-rank низкоранговые добавки BA к линейным слоям LoRA
Adapter (последовательный) bottleneck-MLP внутри блока Houlsby Adapter, AdaptFormer
Prompt-tuning обучаемые prompt-токены на входе VPT
Side-tuning внешняя «боковая» сеть LST

Главная претензия статьи Mona: до неё ни один visual delta-tuning не превосходил full fine-tuning на сложных плотных задачах (detection, segmentation). Mona — первый метод, который это делает на всех протестированных задачах (arXiv:2408.08345, abstract).


2. Что такое Mona-блок

Mona — это последовательный adapter (вставляется в residual-ветку трансформерного блока), но с двумя ключевыми отличиями от языковых адаптеров:

  1. Multi-cognitive фильтры — внутри bottleneck стоят не линейные слои, а набор depth-wise свёрток разных масштабов (3×3, 5×5, 7×7), которые усредняются. Это даёт «vision-friendly» обработку (локальность, разные рецептивные поля).
  2. Scaled LayerNorm на входе — нормализация с двумя обучаемыми покомпонентными масштабами \gamma и \gamma_x, регулирующая распределение признаков перед визуальными фильтрами.

2.1. Структура (по реализации)

Внутренний модуль MonaOp (multi-scale conv-агрегатор), работает на тензоре z \in \mathbb{R}^{B\times C_b\times H\times W} с C_b = 64 (bottleneck), все свёртки depth-wise (groups = C_b):


\mathrm{MonaOp}(z) = z' + \mathrm{Proj}_{1\times1}(z'),\qquad
z' = \frac{\mathrm{DW}_{3}(z) + \mathrm{DW}_{5}(z) + \mathrm{DW}_{7}(z)}{3} + z

где \mathrm{DW}_{k} — depth-wise conv с ядром k\times k и padding k//2, \mathrm{Proj}_{1\times1} — pointwise conv 1\times1. Обе агрегации обёрнуты в residual.

Внешний модуль Mona (вход — последовательность токенов x \in \mathbb{R}^{B\times N\times C}, N = H\cdot W):


\hat{x} = \mathrm{LayerNorm}(x)\odot \gamma + x \odot \gamma_x \qquad\text{(scaled norm, обучаемые } \gamma,\gamma_x\in\mathbb{R}^{C})

u = W_{\text{down}}\,\hat{x} \in \mathbb{R}^{B\times N\times 64}\quad\text{(down-projection, Linear } C\to 64)

u' = \mathrm{MonaOp}\big(\mathrm{reshape}(u)\big)\quad\text{(токены} \to \text{карта } H\times W \to \text{токены)}

y = W_{\text{up}}\,\mathrm{Dropout}\big(\mathrm{GELU}(u')\big)\in\mathbb{R}^{B\times N\times C}\quad\text{(up-projection, Linear } 64\to C)

\boxed{\;\mathrm{Mona}(x) = x + y\;}\quad\text{(внешний residual)}

Инициализация масштабов в коде: \gamma = 10^{-6}\cdot\mathbf{1} (LayerNorm-ветка стартует почти «выключенной»), \gamma_x = \mathbf{1} (тождественный проброс входа). Это делает Mona при инициализации близкой к identity — стабильный старт для дообучения.

Замечание по терминологии: «scaled LayerNorm» здесь = LayerNorm(x)*γ + x*γx, то есть выпуклоподобная смесь нормированного и исходного признака с обучаемыми покомпонентными весами, а не нормировка с фиксированным множителем.


3. Куда вставляется и сколько добавляет

В Swin-блоке Mona вставляется дважды на каждый трансформерный блок — после attention-ветки и после FFN-ветки (см. SwinTransformerBlock.forward):

shortcut → (W-MSA/SW-MSA) → x = shortcut + drop_path(x)
x = Mona_1(x, (H,W))            # adapter после attention
identity = x; x = norm2(x); x = MLP(x)
x = identity + drop_path(x)
x = Mona_2(x, (H,W))            # adapter после FFN
  • Замороженным остаётся весь backbone (attention, MLP, patch-merging, embeddings). Обучаются: параметры всех Mona-модулей (down/up Linear, 3 depth-wise conv + 1×1 projector, \gamma, \gamma_x) и task-head/decoder.
  • Доля обучаемых параметров backbone: ~2.56 % на Swin-L и ~4.67 % на Swin-B (см. таблицы §4) (arXiv:2408.08345, Tables). Bottleneck фиксирован на 64 каналов, поэтому стоимость adapter не растёт квадратично с C.

4. Результаты статьи

Сравнение проведено на Swin-B/Swin-L (ImageNet-21k pre-train) против full fine-tuning и набора PEFT. Ниже — выверенные числа из arXiv:2408.08345.

4.1. COCO instance segmentation (Cascade Mask R-CNN, Swin-B)

Метод Обуч. параметры % AP$_{box}$ AP$_{mask}$
Full fine-tuning 89.14M 100.0 52.40 45.10
Fixed (frozen) 0M 0.0 48.00 41.60
BitFit 0.21M 0.23 50.10 43.60
LoRA 3.06M 3.43 50.40 43.90
Adapter (Houlsby) 3.19M 3.58 52.10 45.00
AdaptFormer 1.60M 1.79 51.70 44.60
Mona 4.16M 4.67 53.40 46.00

4.2. ADE20K semantic segmentation (UperNet, Swin-L)

Метод Обуч. параметры % mIoU
Full fine-tuning 198.58M 100.0 51.18
Fixed 0M 0.0 46.84
BitFit 0.30M 0.15 48.37
LoRA 4.57M 2.31 50.34
Adapter 4.61M 2.33 50.78
AdaptFormer 2.34M 1.18 50.83
Mona 5.08M 2.56 51.36

4.3. Pascal VOC detection (RetinaNet, Swin-L) и DOTA oriented (Oriented R-CNN, Swin-B)

Метод VOC AP$_{box}$ DOTA mAP
Full fine-tuning 83.70 78.31
BitFit 85.40 76.05
LoRA 85.40 75.91
Adapter 86.70 78.27
AdaptFormer 86.60 77.43
Mona 87.30 78.44

4.4. Image classification (Swin-L, усреднение по датасетам)

Метод Top-1 Acc Top-5 Acc
Full fine-tuning 92.79 98.81
BitFit 93.51 99.69
LoRA 93.47 99.69
Adapter 93.99 99.61
AdaptFormer 93.70 99.65
Mona 94.04 99.76

4.5. Почему «5 % > 100 % full FT»

  • На плотных задачах (detection, segmentation) full FT — сильный baseline, и линейные PEFT (LoRA, BitFit, AdaptFormer) его не достают. Mona обходит full FT на всех задачах: +1.0 AP${box}$ / +0.9 AP${mask}$ на COCO, +0.18 mIoU на ADE20K, +3.6 AP на VOC.
  • Причина: conv-фильтры разных масштабов дают индуктивное смещение, подходящее под изображения (локальность, многомасштабность), которого нет у линейных адаптеров; а заморозка backbone регуляризует — на малых данных полная подгонка переобучается, а 5 % параметров «достаточно гибки, но не настолько, чтобы запомнить шум».

5. Mona vs LoRA vs обычный Adapter

Критерий LoRA Adapter (Houlsby) Mona
Куда вставляется добавка \Delta W = \frac{\alpha}{r}BA к линейным слоям (q,v) bottleneck-MLP внутри блока bottleneck с conv в residual-ветке (после attn и после FFN)
Внутренние операторы две линейные матрицы A,B down-Linear → нелинейность → up-Linear down-Linear → 3× depth-wise conv (3/5/7) + 1×1 → GELU → up-Linear
Учёт пространства нет (работает потокенно) нет да (reshape в карту H\times W, multi-scale свёртки)
Нормализация входа нет обычно нет scaled LayerNorm с обучаемыми \gamma,\gamma_x
Слияние в инференсе можно слить в веса (zero-cost) нет (доп. слой) нет (доп. слой)
Сильная сторона дёшево, мерджится универсален vision-friendly, превосходит full FT на dense-задачах
Доля параметров ~23 % ~2.33.6 % ~2.54.7 %

Ключевое отличие: LoRA и Houlsby-Adapter оперируют только линейными преобразованиями признаков и не используют 2D-структуру; Mona добавляет визуальное индуктивное смещение (multi-scale depth-wise conv) и adaptive scaled norm, поэтому выигрывает именно на изображениях.


6. Минимальный PyTorch-скелет Mona

Рабочий минимум, эквивалентный официальному коду (bottleneck=64 по умолчанию, как в репозитории; здесь параметризован).

import torch
import torch.nn as nn
import torch.nn.functional as F


class MonaOp(nn.Module):
    """Multi-cognitive conv aggregator: depthwise 3x3/5x5/7x7 + 1x1 projector."""
    def __init__(self, dim: int):
        super().__init__()
        self.conv1 = nn.Conv2d(dim, dim, 3, padding=1, groups=dim)  # depthwise 3x3
        self.conv2 = nn.Conv2d(dim, dim, 5, padding=2, groups=dim)  # depthwise 5x5
        self.conv3 = nn.Conv2d(dim, dim, 7, padding=3, groups=dim)  # depthwise 7x7
        self.projector = nn.Conv2d(dim, dim, 1)                     # pointwise 1x1

    def forward(self, x):                       # x: (B, dim, H, W)
        identity = x
        x = (self.conv1(x) + self.conv2(x) + self.conv3(x)) / 3.0 + identity
        identity = x
        return identity + self.projector(x)     # second residual


class Mona(nn.Module):
    """Multi-cognitive visual adapter (Yin et al., CVPR 2025)."""
    def __init__(self, in_dim: int, bottleneck: int = 64):
        super().__init__()
        self.norm = nn.LayerNorm(in_dim)
        self.gamma = nn.Parameter(torch.ones(in_dim) * 1e-6)  # scaled-norm branch
        self.gammax = nn.Parameter(torch.ones(in_dim))        # identity branch
        self.project1 = nn.Linear(in_dim, bottleneck)         # down-projection
        self.adapter_conv = MonaOp(bottleneck)                # multi-scale conv
        self.nonlinear = F.gelu
        self.dropout = nn.Dropout(0.1)
        self.project2 = nn.Linear(bottleneck, in_dim)         # up-projection

    def forward(self, x, hw_shape):             # x: (B, N, C), hw_shape=(H, W)
        identity = x
        x = self.norm(x) * self.gamma + x * self.gammax       # scaled LayerNorm
        u = self.project1(x)                                  # (B, N, bottleneck)
        b, n, c = u.shape
        h, w = hw_shape
        u = u.reshape(b, h, w, c).permute(0, 3, 1, 2)         # -> (B, c, H, W)
        u = self.adapter_conv(u)
        u = u.permute(0, 2, 3, 1).reshape(b, n, c)            # -> (B, N, c)
        u = self.dropout(self.nonlinear(u))
        return identity + self.project2(u)                    # outer residual

Вставка в трансформерный блок (frozen backbone, обучается только Mona):

# внутри SwinBlock.forward после attention и после FFN:
x = shortcut + drop_path(attn_out)
x = mona_after_attn(x, (H, W))
x = x + drop_path(mlp_out)
x = mona_after_ffn(x, (H, W))

7. Применимость к маленькому датасету Naruto Sign на EdgeNeXt

Naruto Sign (распознавание ручных «печатей»/жестов) — малый датасет, EdgeNeXt — компактный backbone. Здесь Mona особенно уместен:

  • Снижение переобучения. Backbone EdgeNeXt замораживается (предобученные веса сохраняют общие визуальные признаки), обучается лишь ~2.55 % параметров — меньше степеней свободы → меньше шансов запомнить шум малой выборки.
  • Доменная адаптация через conv. Жесты рук — локальные многомасштабные паттерны (пальцы, контуры). Multi-scale depth-wise свёртки (3/5/7) подстраивают предобученные признаки под этот домен лучше линейных адаптеров.
  • Дёшево по памяти. Хранится один frozen backbone + лёгкие Mona-веса; для edge-сценария это критично.

Замечание по совместимости: EdgeNeXt — гибрид conv+SDTA, а не чистый Swin. Mona-блок (Mona/MonaOp) переносим почти без изменений: ему нужны лишь токенизированные признаки (B, N, C) + (H, W) для reshape, либо его можно применять прямо к feature-map (B, C, H, W), опустив permute. Точку вставки (после каждой стадии / после SDTA-блоков) выносим в гиперпараметры.

Гиперпараметры для Optuna

Гиперпараметр Диапазон / варианты поиска Комментарий
bottleneck (down-dim) {16, 32, 64, 96} меньше → сильнее регуляризация, дешевле
набор ядер conv {(3), (3,5), (3,5,7), (5,7)} trade-off рецептивное поле vs параметры
агрегация ветвей {mean, learnable-weighted sum} взвешенная сумма даёт ещё ~k обучаемых скаляров
init gamma {1e-6, 1e-4, 1e-2} насколько «включена» scaled-norm-ветка на старте
init gammax {1.0, 0.5} сила identity-проброса
dropout в bottleneck [0.0, 0.3] регуляризация под малый датасет
стадии вставки подмножество {stage1..stage4} в ранние/поздние стадии или только в часть
позиции в блоке {after-attn, after-ffn, both} both = как в оригинале
LR для Mona-параметров log-uniform [1e-4, 5e-3] adapter-веса часто требуют LR выше backbone
weight_decay Mona log-uniform [1e-5, 1e-2]

Эмпирическое правило (из статьи): на малых/плотных задачах лучше вставлять Mona во все стадии и обе позиции (after-attn + after-ffn); сокращать набор ядер до (3,5) или bottleneck до 32 имеет смысл, если Optuna показывает переобучение или упор в бюджет параметров.


Источники