Add protocol

This commit is contained in:
2026-06-30 15:44:41 +03:00
parent 4ab397c452
commit 521c1f2e38

View File

@@ -0,0 +1,307 @@
---
type: protocol
status: draft
date: 2026-06-26
component: backbone
supervisor: "мнс Павленко Б.В."
task_type: student-research
topic: hyperparameter-optimization
model: EdgeNeXt
dataset: Naruto Sign
tools: [Optuna, timm, UMAP]
related:
- "[[B134_BB_2022_EdgeNeXt_Efficient_CNN-Transformer_Mobile_Vision]]"
- "[[PROTOCOL_text_encoder_benchmark_StripNet_GTA_UAV]]"
- "[[ПРАКТИКА_2026-07-01_задания_16_студентов]]"
- "[[HYP_SOFIA_v77_design]]"
tags:
- protocol
- methodology
- hpo
- optuna
- edgenext
- mona
- transfer-learning
- classification
- component/backbone
- task/experiment
author: claude
---
# PROTOCOL: Подбор гиперпараметров для классификации изображений (EdgeNeXt × Naruto Sign × Optuna)
> Учебно-исследовательская методичка для студента под руководством **мнс Павленко Б.В.**
> Тема: **оптимизация/подбор гиперпараметров (HPO)** для задачи классификации изображений на наборе **Naruto Sign** с компактным энкодером визуальных признаков **EdgeNeXt**. Сквозные инструменты: **Optuna** (поиск), **timm** (модель), **UMAP** (анализ признаков). Дополнительно изучаются **заморозка / частичная заморозка / MONA-адаптер** как точки на оси «ёмкость адаптации ↔ риск переобучения», подбираемые наравне с прочими гиперпараметрами.
---
## §0. TL;DR
| Что | Решение |
| :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Цель** | Освоить и применить методы HPO: на малом датасете Naruto Sign подобрать гиперпараметры дообучения EdgeNeXt так, чтобы максимизировать **macro-F1** на валидации, и обосновать выбор экспериментально |
| **Энкодер (фикс. семейство)** | **EdgeNeXt** (timm): `edgenext_xx_small.in1k` (1.3M) / `edgenext_x_small.in1k` (2.3M) / `edgenext_small.usi_in1k` (5.6M), ImageNet-pretrained — вариант сам по себе является гиперпараметром |
| **Датасет** | **Naruto Sign** — классификация ручных печатей. Verified (сиблинг-трек, см. §3): **13 классов** (12 печатей + `zero`), **~2245 изобр.** (train 2159 / test 86, **val нет**), PNG+JPG, лицензия «Unknown». Источник — [Kaggle vikranthkanumuru](https://www.kaggle.com/datasets/vikranthkanumuru/naruto-hand-sign-dataset) (ImageFolder). Числа **пересчитать после скачивания** |
| **Режимы переноса** | `full` (full fine-tuning) · `partial` (разморозить последние `n` стадий, `n=0` ⇒ linear probing) · `mona` (frozen backbone + Conv-MONA адаптеры, PEFT) |
| **Поиск** | **Optuna**, default = `TPESampler(multivariate=True)` + `MedianPruner`/`HyperbandPruner`; затем multi-objective (`NSGAIISampler`): macro-F1 ↑ vs trainable-params ↓ |
| **Главная метрика** | **macro-F1** (валидация). Доп.: balanced accuracy, top-1, MCC, confusion matrix. При дисбалансе accuracy — вводит в заблуждение |
| **Анализ данных** | Статистика датасета + **UMAP** эмбеддингов EdgeNeXt, раскраска по классам, кластеризация на промежуточном (1020-мерном) вложении, ARI/NMI/silhouette → предсказание «трудных» классов |
| **Гипотезы** | H1 (TPE+pruning ≫ random/grid при равном бюджете) · H2 (оптимум — partial freeze, не full FT и не linear probing) · H3 (MONA ≥ full FT при <5% параметров) · H4 (loss/sampler против дисбаланса ↑ macro-F1) · H5 (multi-objective находит Pareto-лучшие edge-конфигурации) · H6 (UMAP-перекрытие классов предсказывает ошибки) — см. §8 |
| **Численные эксперименты** | B0 baseline → E1 ablation режимов → E2 Optuna single-objective → E3 Optuna multi-objective → E4 UMAP-анализ → (опц.) E5 sampler/random vs TPE — см. §9 |
| **Сроки** | 14 дней (MVP ~810 дней): рабочий честный baseline > богатство экспериментов |
| **Compute** | 1×RTX 4090; EdgeNeXt малый → прогон минуты; десятки trial-ов Optuna реалистичны |
---
## §1. Контекст, цель и зачем проекту
### 1.1 Учебная цель
Студент осваивает полный цикл HPO на «чистой» автономной CV-задаче: (1) что такое кодирование визуальных признаков и почему backbone = feature encoder; (2) какие бывают методы подбора гиперпараметров и чем Bayesian/TPE лучше grid/random; (3) как устроена Optuna; (4) что подбирать в DL-классификации (lr, wd, loss, аугментации, **глубина заморозки**, PEFT); (5) как честно мерить качество при дисбалансе; (6) как анализировать признаковое пространство (UMAP) и превращать наблюдения в гипотезы; (7) как провести и описать численные эксперименты.
### 1.2 Зачем это проекту MERIDIAN/SOFIA
Задача автономна (публичные данные, не трогает проприетарный Triple-Teacher), но методически прямо питает проект:
- **EdgeNeXt** — критически релевантный для SOFIA-Tiny edge-backbone (см. конспект [[B134_BB_2022_EdgeNeXt_Efficient_CNN-Transformer_Mobile_Vision]]: 256×256 вход, Jetson-деплой, 1.35.6M params, SDTA channel-attention). Этот эксперимент — песочница для SDTA/adaptive-kernels интуиций.
- **MONA** — у проекта уже есть **Conv-MONA** в text-encoder бенчмарке ([[PROTOCOL_text_encoder_benchmark_StripNet_GTA_UAV]] §2.1): здесь студент реализует и измеряет тот же класс PEFT на чистой классификации.
- **Optuna-методология** переиспользуема для всего проекта (E0E9 recipe-tuning), а decision-rule-культура (равный бюджет, фикс. seed, p<0.05, честные caveat) — общая для практики ([[ПРАКТИКА_2026-07-01_задания_16_студентов]] §«Общие правила»).
### 1.3 Что НЕ изучаем
- ❌ Детекцию/сегментацию печатей (только классификация, ImageFolder→softmax).
- ❌ Архитектурный NAS (варьируем только пресет EdgeNeXt XXS/XS/S, не перестраиваем блоки).
- ❌ Дистилляцию от Teacher (это E2 MERIDIAN, не эта задача).
---
## §2. Карта теоретических глав (читать перед экспериментами)
Подробная теория вынесена в самостоятельные главы (папка `_research/`). Каждая — самодостаточна, с формулами, таблицами и проверенными источниками:
| Глава | Файл | О чём |
|:--|:--|:--|
| **Кодирование признаков и EdgeNeXt** | [[SECTION_edgenext]] | feature encoding, архитектура EdgeNeXt (stem/4 стадии/SDTA/adaptive kernels), варианты, **timm-практика** (`create_model`, `forward_features`, `forward_head(pre_logits=True)`, `resolve_model_data_config`) |
| **Методы HPO (таксономия)** | [[SECTION_hpo_methods]] | гиперпараметры vs параметры, grid/random ([Bergstra&Bengio 2012]), Bayesian (GP/TPE/SMAC, EI/UCB), CMA-ES/PBT, multi-fidelity (Successive Halving/Hyperband/ASHA/BOHB), фреймворки, методология, типичные ошибки |
| **Optuna (полный разбор)** | [[SECTION_optuna]] | Study/Trial/define-by-run, **samplers** (TPE/GP/CMA-ES/NSGA-II/III/QMC/**AutoSampler**), **pruners** (Median/ASHA/Hyperband/...), multi-objective+constraints, v4.5/v4.6, storage, визуализация, интеграция с PyTorch, шпаргалка «sampler+pruner» |
| **Заморозка / частичная заморозка** | [[SECTION_freezing]] | full FT vs linear probing vs partial freeze, **LP-FT** ([Kumar 2022]), механика `requires_grad`/`eval()`/FrozenBN, gradual unfreezing, discriminative LR, глубина заморозки как гиперпараметр |
| **MONA-layer (PEFT)** | [[SECTION_mona]] | adapter-PEFT, разбор Mona ([arXiv:2408.08345], CVPR 2025) по коду репозитория `Leiyi-Hu/mona`: scaled-LayerNorm + MonaOp (DW 3/5/7) + down/up-proj; «5%>100% full FT»; vs LoRA/Adapter; PyTorch-скелет |
| **UMAP (анализ признаков)** | [[SECTION_umap]] | интуиция, PCA/t-SNE/UMAP, параметры (`n_neighbors`/`min_dist`/`metric`), пайплайн EdgeNeXt→UMAP, кластеризация на промежуточном вложении, ARI/NMI/AMI/silhouette, предостережения |
| **Классификация: метрики/loss/sampling** | [[SECTION_classification]] | softmax/top-k, метрики (macro/micro/weighted-F1, balanced acc, MCC, ECE), loss (CE/LS/Focal/Class-Balanced/LDAM/KD), семплирование (WeightedRandomSampler, mixup/cutmix), связка loss×sampler |
---
## §3. Датасет Naruto Sign — протокол подготовки
Полное описание и источники — в [[SECTION_naruto_dataset]].
> [!info] Verified-факты (из параллельного студенческого трека под рук. Устенко В.Ю. — тот же датасет; зафиксированы в рабочей памяти проекта)
> Тот же Kaggle-набор `vikranthkanumuru/naruto-hand-sign-dataset` уже верифицирован параллельным треком: **13 классов** (12 печатей + `zero`/покой), **2245 изображений** (train 2159 / test 86, **val отсутствует**), 1797 PNG + 448 JPG, реальные фото рук, лицензия **«Unknown»**, числа из notebook 2020. Поэтому: `num_classes=13` по умолчанию; **val нет** → выделяется стратифицированно из train (реализовано в [`data.py`](code/src/data.py), ветка `use_predefined_split`); **не** переоткрывать эти факты заново — только пересчитать `dataset_stats.py` после скачивания (числа могли измениться). Документ трека Устенко может находиться в другой ветке репозитория.
Ключевые решения протокола:
1. **Источник по умолчанию:** Kaggle `vikranthkanumuru/naruto-hand-sign-dataset` (формат ImageFolder, заточен под классификацию). Roboflow-наборы — детекционные, использовать как запасные (crop по bbox).
2. **Сверить статистику после скачивания** скриптом [`dataset_stats.py`](code/src/dataset_stats.py): число классов (12 или 13?), всего изображений, per-class, разрешения, дисбаланс. Это **первый артефакт** студента — таблица статистики в отчёте.
3. **Стратифицированный split** (seed=42), доли 70/15/15. Реализация — [`data.py`](code/src/data.py) (`_stratified_indices`).
4. **Утечка из видео — главный риск честности.** Кадры извлечены из видео покадрово ⇒ соседние кадры почти идентичны. Случайный per-frame split **завышает** метрику. Если в именах файлов есть id видео/серии — делать **group-split** (`StratifiedGroupKFold`, см. [[SECTION_naruto_dataset]] §5). Если id нет — отметить риск в отчёте и сделать дедупликацию near-duplicate (perceptual hash). Базовый `data.py` делает per-frame split — это допустимо для учебного MVP, **но факт и риск утечки обязательно фиксируется в REPORT.md**.
5. **Размер входа** — 256×256 (дефолт EdgeNeXt), нормализация ImageNet (берётся из `timm.data.resolve_model_data_config`).
> [!danger] Horizontal flip — отключён по умолчанию
> Печать — это **конкретная конфигурация левой/правой руки**; зеркалирование меняет семантику жеста ⇒ label noise. В [`data.py`](code/src/data.py) `use_hflip=False` зафиксировано и **не подаётся в Optuna**. См. [[SECTION_naruto_dataset]] §6.
---
## §4. Задача классификации — метрики, loss, семплирование (решения)
Теория — [[SECTION_classification]]. Решения протокола:
| Аспект | Решение | Обоснование |
|:--|:--|:--|
| **Главная метрика (objective Optuna)** | **macro-F1** на валидации | равный вес классам; accuracy при дисбалансе вводит в заблуждение |
| Сопутствующие метрики | balanced accuracy, top-1, MCC, Cohen κ, per-class F1, confusion matrix | полная картина; confusion matrix — диагностика похожих печатей |
| Loss (категориальный гиперпараметр) | `{ce, ce_ls, weighted_ce, focal}` | от baseline до imbalance-aware; LS для калибровки, focal/weighted/CB для дисбаланса |
| Веса классов | effective-number ([Cui 2019], `cb_beta`) | мягче, чем грубая обратная частота |
| Семплирование | `WeightedRandomSampler` (вкл/выкл — гиперпараметр) | альтернатива loss-ребалансировке |
| ⚠️ Двойная компенсация | **не** включать `weighted_ce/focal` + `WeightedRandomSampler` одновременно с полной силой | переусиление minority → падение precision (см. [[SECTION_classification]] §5) |
| Аугментации (гиперпараметры) | RandomResizedCrop, лёгкий rotation, color jitter, RandAugment(M), mixup(α) | mixup/RandAugment — «умный oversampling»; **без hflip** |
Реализация: [`metrics.py`](code/src/metrics.py) (sklearn), [`losses.py`](code/src/losses.py) (`FocalLoss`, `effective_number_weights`, `build_criterion`), [`data.py`](code/src/data.py) (`_make_sampler`, `compute_class_weights`).
---
## §5. Модель и режимы переноса (заморозка / частичная / MONA)
Теория — [[SECTION_edgenext]], [[SECTION_freezing]], [[SECTION_mona]]. Три режима реализованы в [`model.py`](code/src/model.py) (`build_model`, `ModelConfig.regime`):
| Режим | Что обучается | Когда ожидаемо лучше | Параметризация в Optuna |
|:--|:--|:--|:--|
| `full` | весь backbone + голова (2 LR-группы) | данных достаточно / домен далёк | — |
| `partial` | последние `n_unfrozen_stages` стадий + голова; `n=0` ⇒ linear probing | малый датасет, нужна частичная адаптация | `n_unfrozen_stages ∈ [0,4]` |
| `mona` | frozen backbone + **Conv-MONA** адаптеры + голова (PEFT, <5% весов) | малый датасет, адаптация без переобучения | `mona_bottleneck`, `mona_kernels`, `mona_last_only` |
Ключевые инженерные детали (см. [[SECTION_freezing]] §2):
- Замороженные параметры **исключаются** из optimizer (`build_param_groups` берёт только `requires_grad=True`).
- Замороженные **norm-слои переводятся в `eval()`** каждый train-шаг (`freeze_consistency`) — иначе running-статистика BN «уезжает». EdgeNeXt в основном на LayerNorm (этой ловушки нет), но правило соблюдаем для гибридных модулей.
- **Discriminative LR:** backbone получает `base_lr × backbone_lr_mult` (по умолчанию 0.1), голова/адаптеры — `base_lr`.
MONA-адаптер ([`mona.py`](code/src/mona.py)) — conv-вариант (вход `[B,C,H,W]`), верный коду репозитория `Leiyi-Hu/mona`: scaled-LayerNorm (`γ·LN(x)+γx·x`) → down 1×1 → `MonaOp` (DW 3/5/7, два residual) → GELU → up 1×1 (zero-init ⇒ старт = identity). Вставляется forward-hook'ом после выбранных стадий (число каналов определяется dry-run'ом, без зависимости от `feature_info`).
---
## §6. Анализ признаков через UMAP
Теория и предостережения — [[SECTION_umap]]. Реализация — [`umap_analysis.py`](code/src/umap_analysis.py).
Пайплайн: `forward_features``forward_head(pre_logits=True)` → L2-норма → матрица `[N,D]`
(а) UMAP→**2D** (раскраска по истинному классу, `random_state=42`, `metric=cosine`) для визуализации;
(б) UMAP→**1020D** для **кластеризации** (KMeans с `k`=число классов) → silhouette, **ARI/NMI** относительно истинных меток.
Что извлечь:
- Какие печати образуют изолированные «острова» (легко различимы), какие перекрываются (Ram/Snake/Dragon — сложенные ладони).
- **Сверка с confusion matrix**: перекрытие в эмбеддинге ⇒ off-diagonal масса. Это валидирует, что ошибки идут от признаков, а не от головы.
- Выполнить анализ **дважды**: на frozen ImageNet-EdgeNeXt (до дообучения) и на лучшей дообученной модели (после) → показать, как дообучение «растягивает» классы.
> [!warning] UMAP — инструмент генерации гипотез, не доказательство. Не интерпретировать абсолютные расстояния/размеры кластеров; фиксировать seed; выводы подтверждать метриками (§5 [[SECTION_umap]]).
---
## §7. Выбор метода и настройка Optuna
Обоснование выбора метода — [[SECTION_hpo_methods]]; разбор Optuna — [[SECTION_optuna]]. Решения:
- **Single-objective (E2):** `TPESampler(multivariate=True, seed=42)` + `MedianPruner` (или `HyperbandPruner`, если бюджет на trial неясен). Прунинг по эпохам через `trial.report(macro_f1, epoch)` + `trial.should_prune()` — реализовано в [`train.py`](code/src/train.py).
- **Multi-objective (E3):** `NSGAIISampler` или `AutoSampler` (OptunaHub), `directions=["maximize","minimize"]` = macro-F1 ↑ vs trainable-params ↓ → Pareto-фронт edge-конфигураций.
- **Baseline для честного сравнения метода (E5, опц.):** `RandomSampler` при **равном** бюджете trial-ов и **том же** пространстве поиска.
- **Storage:** SQLite (`--storage`) для персистентности и дашборда; `gc_after_trial=True` (VRAM).
- **Пространство поиска** (define-by-run, [`optuna_search.py`](code/src/optuna_search.py) `suggest_configs`): `model_name`, `regime` (+условные `n_unfrozen`/MONA-параметры), `lr` (log), `weight_decay` (log), `optimizer`, `backbone_lr_mult` (log), `loss_name` (+условный `focal_gamma`), `label_smoothing`, `weighted_sampler`, `img_size`, `batch_size`, `mixup_alpha`, `rrc_scale_min`, `randaug_magnitude`, `drop_rate`, `drop_path_rate`.
- **После поиска:** `plot_optimization_history`, `plot_param_importances` (fANOVA — какие гиперпараметры реально важны), `plot_slice`, `plot_pareto_front`.
> [!important] Лог-шкала для `lr`/`weight_decay` обязательна. Их эффект мультипликативен; линейный сэмплинг почти не пробует малые значения (см. [[SECTION_hpo_methods]] §8.1).
---
## §8. Гипотезы
Формат: «Если [метод], то [результат], потому что [обоснование]». Каждую проверяем численно (§9) при **равном бюджете** и фикс. seed; вывод — со стат-проверкой (bootstrap-CI / 3 seed), а не по разнице в 0.1%.
| ID | Гипотеза | Проверка | Основание |
|:--|:--|:--|:--|
| **H1** | **Если** искать TPE+pruning, **то** при равном бюджете trial-ов он найдёт конфиг с macro-F1 не хуже (обычно лучше) random/grid, **потому что** TPE моделирует $\ell(\lambda)/g(\lambda)$ и концентрирует бюджет в перспективной зоне, а pruner отсеивает плохих рано | E2 vs E5 (random), та же сетка, тот же бюджет | [Bergstra 2011/2012], [Akiba 2019] — [[SECTION_hpo_methods]], [[SECTION_optuna]] |
| **H2** | **Если** варьировать глубину заморозки, **то** оптимум — **partial freeze** (разморозить 12 верхние стадии), а не full FT и не linear probing, **потому что** на малом датасете full FT переобучается/искажает признаки (feature distortion), а linear probing не хватает ёмкости адаптации | E1 ablation (`n_unfrozen ∈ {0,1,2,3,4}`) + важность параметра `regime`/`n_unfrozen` в E2 | [Kumar 2022 LP-FT], CS231n — [[SECTION_freezing]] |
| **H3** | **Если** применить **MONA** (frozen backbone + адаптеры, <5% параметров), **то** macro-F1 ≥ full FT при многократно меньшем числе обучаемых параметров, **потому что** multi-scale conv-фильтры дают vision-friendly адаптацию, а заморозка регуляризует на малых данных | E1 (`full` vs `partial` vs `mona`) при равном бюджете; E3 Pareto (F1 vs params) | [Yin 2024 Mona] — [[SECTION_mona]] |
| **H4** | **Если** включить imbalance-aware loss/sampler (weighted/focal/CB или WeightedRandomSampler), **то** вырастет **macro-F1 / balanced accuracy / recall редких классов** (ценой лёгкого падения overall accuracy), **потому что** редкие классы получают больший вклад в обучение | E2: важность `loss_name`/`weighted_sampler`; целевой контраст на самых редких классах | [Cui 2019], [Lin 2017] — [[SECTION_classification]] |
| **H5** | **Если** оптимизировать multi-objective (macro-F1 ↑, trainable-params ↓), **то** Pareto-фронт даст **edge-предпочтительные** конфигурации (например MONA/partial-малый вариант) при незначимой потере F1, **потому что** PEFT/малые варианты EdgeNeXt дают высокую точность при низкой ёмкости | E3 (`NSGAIISampler`/`AutoSampler`), анализ `plot_pareto_front` | [[SECTION_optuna]] §4, [[SECTION_edgenext]] §3 |
| **H6** | **Если** классы перекрываются в UMAP-эмбеддинге, **то** именно эти пары дают наибольшую off-diagonal массу в confusion matrix, **потому что** ошибки классификатора порождаются неразделимостью в признаковом пространстве | E4: сверка UMAP-перекрытий с confusion matrix лучшей модели; ARI/NMI | [McInnes 2018] — [[SECTION_umap]] |
---
## §9. План численных экспериментов и decision-rules
Все прогоны: seed=42 (финальные сравнения — 42/123/456), **равный бюджет** на сравниваемые конфигурации, меняется **только один фактор**. Эталон метрик — [`metrics.py`](code/src/metrics.py).
```mermaid
flowchart TD
D[Naruto Sign<br/>ImageFolder] --> S[dataset_stats.py<br/>STATS.md]
D --> SP[Стратиф. split seed=42<br/>data.py]
SP --> B0[B0 baseline<br/>partial n=1, ce_ls]
B0 --> E1{E1 ablation режимов<br/>full / partial 0..4 / mona}
E1 -->|лучший режим| E2[E2 Optuna single-obj<br/>TPE + MedianPruner<br/>objective = val macro-F1]
E2 -->|best_params| FT[Финал: 3 seed<br/>+ test + bootstrap-CI]
E2 --> E3[E3 Optuna multi-obj<br/>NSGA-II: F1 up vs params down<br/>Pareto-front]
E2 -. контроль .-> E5[E5 random vs TPE<br/>равный бюджет, H1]
FT --> E4[E4 UMAP до/после<br/>2D + кластеризация<br/>ARI/NMI]
E4 --> CM[Сверка с confusion matrix<br/>H6]
E1 --> HYP[Гипотезы H2/H3]
E3 --> HYP5[Гипотеза H5]
E2 --> HYP4[Важность параметров fANOVA<br/>H4]
HYP --> R[REPORT.md<br/>вердикты H1-H6 + caveat]
HYP4 --> R
HYP5 --> R
CM --> R
```
| Этап | Что делаем | Команда | Артефакт | Decision-rule |
|:--|:--|:--|:--|:--|
| **B0** baseline | Один честный прогон `partial, n=1, ce_ls`, eval на test + confusion | `python -m src.run_baseline --data-root <...> --regime partial --n-unfrozen 1` | `report.json`, `confusion.png` | Зафиксировать baseline macro-F1, который HPO обязан побить |
| **E1** ablation режимов | `full` vs `partial(n=0..4)` vs `mona` при равном recipe/бюджете | `run_baseline` × режимы (скрипт-обёртка) | таблица режим×(macro-F1, params), кривые | H2/H3: лучший режим; если `mona``full` при <5% params — H3 поддержана |
| **E2** Optuna single-obj | TPE+pruner, objective=val macro-F1, 4080 trial | `python -m src.optuna_search --data-root <...> --sampler tpe --pruner median --n-trials 60 --storage` | `*_best.json`, `*_trials.csv`, importance/history/slice png | Лучший trial должен побить B0; зафиксировать best_params |
| **E3** Optuna multi-obj | NSGA-II/AutoSampler, F1↑ vs params↓, 60100 trial | `python -m src.optuna_search --data-root <...> --multi-objective --sampler nsga --n-trials 80` | Pareto-front png + таблица недоминируемых | H5: выбрать edge-точку (max F1 при params ≤ бюджета) |
| **E4** UMAP-анализ | эмбеддинги до/после дообучения, 2D + кластеризация | `python -m src.umap_analysis --data-root <...> [--checkpoint best.pt]` | `umap_2d.png`, `cluster_report.json` | H6: сверить перекрытия с confusion matrix |
| **E5** (опц.) метод-контроль | random vs TPE при равном бюджете и пространстве | `--sampler random` vs `--sampler tpe` | кривые «best-so-far vs число trial» | H1: TPE ≥ random; вывод со стат-проверкой |
**Финальная переоценка:** топ-конфиг(и) из E2/E3 переобучить на 3 seed (42/123/456), сравнить по среднему ± std, оценить на **test** (трогается один раз) + bootstrap-CI. Только после этого делать выводы.
> Абсолютные числа могут быть скромными при малом/«грязном» датасете — оценивается **корректность методологии** (равный бюджет, фикс. seed, отсутствие утечки, честный eval, признание caveat), а не голый macro-F1.
---
## §10. Воспроизводимость, честность, типичные ловушки
- **Seed** 42 везде (`set_seed` в [`train.py`](code/src/train.py)); финал — 3 seed.
- **Test трогать один раз**, в самом конце; подбор — только на val (иначе утечка test в HPO).
- **Не сравнивать при разном бюджете** методы/конфиги; единое пространство поиска.
- **Лог-шкала** для lr/wd.
- **Заморозка BN** — `freeze_consistency` после каждого `model.train()`.
- **Двойная компенсация дисбаланса** — не включать sampler + weighted-loss одновременно на полную (см. [[SECTION_classification]] §5).
- **Утечка из видео** — главный риск Naruto Sign; зафиксировать в REPORT.md, при наличии id — group-split.
- **UMAP** — не «доказывать» кластерами; фиксировать seed, подтверждать метриками.
- **Атомарная запись** результатов (`os.replace`), `gc_after_trial` для VRAM.
- Отрицательные результаты и подтверждённые caveat фиксировать явно — это плюс.
---
## §11. Что сдаёт студент
1. **Код** (папка [`code/`](code/)) с `README.md` и `requirements.txt` — запускается на чужой машине по инструкции.
2. **`STATS.md`** — таблица статистики Naruto Sign по факту скачивания ([`dataset_stats.py`](code/src/dataset_stats.py)) + источник/версия/лицензия.
3. **`REPORT.md`** — результаты всех этапов: таблицы (B0/E1/E2/E3), кривые обучения, importance-график Optuna, Pareto-фронт, UMAP-картинки (до/после) + confusion matrix, проверка каждой гипотезы H1H6 с вердиктом (поддержана/опровергнута/неубедительно), анализ ошибок (35 примеров), честные caveat.
4. **Артефакты:** `*_trials.csv`, `*_best.json`, `umap_2d.png`, `cluster_report.json`, лучший чекпойнт + config, папка PNG.
**MVP (≈810 дней):** статистика + B0 baseline + E1 ablation 3 режимов + E2 Optuna (≥40 trial, TPE+pruner) с побитием baseline + E4 UMAP (2D + ARI) + REPORT с проверкой ≥3 гипотез.
**Stretch:** E3 multi-objective + Pareto-анализ; E5 random vs TPE со стат-проверкой; 3-seed переоценка + bootstrap-CI; MONA-абляция (bottleneck/ядра) внутри Optuna; group-split без утечки; AutoSampler.
---
## §12. Календарь (14 дней)
| Дни | Задача |
|:--|:--|
| 12 | Скачать датасет, `dataset_stats.py`, `STATS.md`; прочитать главы [[SECTION_edgenext]], [[SECTION_classification]] |
| 3 | Поднять окружение (`requirements.txt`), smoke-тест `build_model` для 3 режимов; прочитать [[SECTION_freezing]], [[SECTION_mona]] |
| 4 | **B0** baseline + confusion matrix |
| 56 | **E1** ablation режимов (full/partial 04/mona); таблица + кривые |
| 6 | Прочитать [[SECTION_hpo_methods]], [[SECTION_optuna]] |
| 79 | **E2** Optuna single-objective (TPE+pruner), importance/history/slice; побить B0 |
| 9 | **E4** UMAP (до/после), сверка с confusion matrix |
| 10 | Воспроизводимый прогон зафиксирован; черновик REPORT |
| 1112 | **E3** multi-objective + Pareto (stretch); 3-seed переоценка |
| 13 | **E5** random vs TPE (stretch); анализ ошибок |
| 14 | Финализация REPORT.md/README.md, графики, выводы, caveat |
---
## §13. Карта кода (`code/`)
| Файл | Назначение |
|:--|:--|
| [`src/data.py`](code/src/data.py) | ImageFolder, стратифицированный split, transforms (hflip OFF), WeightedRandomSampler, class weights |
| [`src/model.py`](code/src/model.py) | EdgeNeXt (timm), режимы full/partial/mona, freeze-логика, param-groups (discriminative LR), `extract_features` |
| [`src/mona.py`](code/src/mona.py) | Conv-MONA адаптер (по коду `Leiyi-Hu/mona`), вставка forward-hook'ом после стадий |
| [`src/losses.py`](code/src/losses.py) | CE/LS/weighted/Focal, effective-number weights, `build_criterion` |
| [`src/metrics.py`](code/src/metrics.py) | macro-F1, balanced acc, top-k, MCC, κ, confusion (sklearn) |
| [`src/train.py`](code/src/train.py) | train/eval цикл, mixup (timm), early-stop, **Optuna pruning** (`report`/`should_prune`), VRAM-hygiene |
| [`src/optuna_search.py`](code/src/optuna_search.py) | пространство поиска (define-by-run), single/multi-objective, sampler+pruner, экспорт best/trials |
| [`src/umap_analysis.py`](code/src/umap_analysis.py) | эмбеддинги → UMAP 2D + кластеризация → silhouette/ARI/NMI + 2D-картинка |
| [`src/run_baseline.py`](code/src/run_baseline.py) | одиночный прогон + test-метрики + confusion-heatmap (B0/E1) |
| [`src/dataset_stats.py`](code/src/dataset_stats.py) | статистика ImageFolder (число классов, per-class, дисбаланс, разрешения) |
> Конфигурация: задача управляется Optuna (define-by-run) + argparse/dataclass, поэтому здесь сознательное отступление от gin-strict (`.claude/rules/python-code.md`) — пространство гиперпараметров описывается в `suggest_configs`, а не в статическом `.gin`. Остальные конвенции (type hints, Google-docstrings, `from __future__ import annotations`, seeds, atomic writes, `inference_mode`) соблюдены.
---
## §14. Чем задание выявляет сильного студента
Слабый студент сделает MVP буквально (посчитает числа, прогонит Optuna). Сильный проявит **критическое мышление**:
- осознает и измерит **утечку из видео** (group-split vs per-frame), не примет завышенный R/F1 за чистую монету;
- проверит H2/H3 честно (равный бюджет) и свяжет вывод с теорией (LP-FT, «5%>100%»), а не просто выберет «что победило»;
- покажет **важность гиперпараметров** (fANOVA) и сузит пространство во второй итерации;
- сверит UMAP-перекрытия с confusion matrix (H6) — увяжет признаковый анализ с ошибками;
- докажет H1 со стат-проверкой (random vs TPE при равном бюджете), а не на одном seed;
- честно зафиксирует отрицательные результаты и caveat (hflip, двойная компенсация, малый/грязный датасет).
- **Сквозной маркер:** способность связать эксперимент с проектом (EdgeNeXt ↔ SOFIA-Tiny [[B134_BB_2022_EdgeNeXt_Efficient_CNN-Transformer_Mobile_Vision]]; Conv-MONA ↔ [[PROTOCOL_text_encoder_benchmark_StripNet_GTA_UAV]]) и сделать честный вывод с признанием caveat.