28 KiB
Задача классификации изображений: метрики, loss-функции, семплирование
Учебно-исследовательский раздел методички (научный руководитель — мнс Павленко Б.В.). Контекст применения: HPO (Optuna) для EdgeNeXt на датасете NarutoSign (multi-class классификация знаков-печатей).
1. Постановка задачи multi-class классификации
Дана обучающая выборка \{(x_i, y_i)\}_{i=1}^{N}, где x_i — изображение, y_i \in \{1, \dots, C\} — метка одного из C взаимоисключающих классов (multi-class, в отличие от multi-label, где классы не исключают друг друга). Модель f_\theta выдаёт вектор логитов (необработанных скоров) z = f_\theta(x) \in \mathbb{R}^{C}.
Softmax
Логиты переводятся в распределение вероятностей по классам функцией softmax:
p_k = \mathrm{softmax}(z)_k = \frac{e^{z_k}}{\sum_{j=1}^{C} e^{z_j}}, \qquad \sum_{k=1}^{C} p_k = 1,\; p_k \in (0,1).
Softmax инвариантен к сдвигу: \mathrm{softmax}(z) = \mathrm{softmax}(z + c), поэтому на практике вычитают \max_k z_k для численной устойчивости (log-sum-exp trick).
One-hot и целевое распределение
Истинная метка y кодируется one-hot вектором q \in \{0,1\}^{C}: q_k = \mathbb{1}[k = y]. Это «жёсткое» (hard) целевое распределение. «Мягкие» (soft) цели q_k \in [0,1] возникают при label smoothing, mixup/cutmix и knowledge distillation (см. §3).
Top-1 / top-k accuracy
Предсказание top-1: \hat{y} = \arg\max_k p_k. Top-k считает пример верным, если истинная метка попала в множество k наибольших по вероятности классов:
\text{top-}k\text{-acc} = \frac{1}{N}\sum_{i=1}^{N} \mathbb{1}\!\left[\, y_i \in \mathrm{TopK}_k\big(p^{(i)}\big)\,\right].
Top-5 — историческая метрика ImageNet (1000 классов). Для задач с малым C (например, 12 знаков-печатей) top-k при k \geq 2 малоинформативна; основная метрика — top-1.
2. Метрики
Базовые величины для класса c из confusion matrix (one-vs-rest): TP_c (true positive), FP_c, FN_c, TN_c. Обозначим n_c — число истинных объектов класса c, N = \sum_c n_c.
| Метрика | Формула | Диапазон | Чувствительность к дисбалансу |
|---|---|---|---|
| Accuracy (top-1) | \dfrac{\sum_c TP_c}{N} |
[0,1] |
Высокая (доминирует majority-класс) |
| Balanced accuracy | \dfrac{1}{C}\sum_c \dfrac{TP_c}{n_c} = \dfrac{1}{C}\sum_c \mathrm{Recall}_c |
[0,1] |
Низкая (макро-усреднение recall) |
| Top-k accuracy | см. §1 | [0,1] |
Высокая |
Precision (класс c) |
P_c = \dfrac{TP_c}{TP_c + FP_c} |
[0,1] |
— |
Recall (класс c) |
R_c = \dfrac{TP_c}{TP_c + FN_c} |
[0,1] |
— |
F1 (класс c) |
F1_c = \dfrac{2 P_c R_c}{P_c + R_c} |
[0,1] |
— |
| Macro-F1 | \dfrac{1}{C}\sum_c F1_c |
[0,1] |
Низкая (классы равновесны) |
| Micro-F1 | \dfrac{2\sum_c TP_c}{2\sum_c TP_c + \sum_c FP_c + \sum_c FN_c} |
[0,1] |
Высокая (= accuracy в multi-class) |
| Weighted-F1 | \sum_c \dfrac{n_c}{N} F1_c |
[0,1] |
Высокая (вес \propto частоте) |
| MCC | см. ниже | [-1,1] |
Низкая (учитывает все 4 клетки) |
Cohen's \kappa |
см. ниже | [-1,1] |
Низкая (поправка на случай) |
| ROC-AUC (OvR) | площадь под ROC, усреднённая | [0,1] |
Зависит от усреднения |
| PR-AUC (OvR) | площадь под precision-recall | [0,1] |
Низкая (лучше для редких классов) |
| ECE | см. §2.5 | [0,1], ниже лучше |
— (про калибровку) |
2.1 Macro / micro / weighted — когда какая
- Micro: агрегирует TP/FP/FN по всем классам, затем считает метрику. В multi-class с single-label micro-F1 = micro-precision = micro-recall = accuracy. Отражает производительность «на объект», доминирует majority-класс.
- Macro: метрика считается по каждому классу, затем простое среднее. Каждый класс весит одинаково → редкие классы влияют наравне с частыми. Главный выбор при дисбалансе.
- Weighted: macro со взвешиванием по
n_c→ возвращает доминирование majority-класса, маскирует провал на редких классах.
| Сценарий | Рекомендуемое усреднение |
|---|---|
| Сбалансированные классы, важна общая доля верных | micro / accuracy |
| Дисбаланс, все классы одинаково важны | macro |
Дисбаланс, но важность \propto частоте в проде |
weighted |
scikit-learn — precision_recall_fscore_support / averaging
2.2 Confusion matrix
M \in \mathbb{N}^{C \times C}, M_{ij} = число объектов истинного класса i, предсказанных как j. Диагональ — верные. Нормировка по строкам (M_{ij}/n_i) даёт per-class accuracy = recall класса i. Самый информативный диагностический инструмент: показывает какие классы путаются (для NarutoSign — визуально похожие печати). sklearn.confusion_matrix
2.3 Matthews Correlation Coefficient (MCC)
Корреляция Пирсона между предсказанным и истинным классом. Multi-class форма (sklearn.matthews_corrcoef):
\mathrm{MCC} = \frac{\sum_k\sum_l\sum_m (M_{kk}M_{lm} - M_{kl}M_{mk})}
{\sqrt{\sum_k\big(\sum_l M_{kl}\big)\big(\sum_{k'\neq k}\sum_{l'} M_{k'l'}\big)}\;\sqrt{\sum_k\big(\sum_l M_{lk}\big)\big(\sum_{k'\neq k}\sum_{l'} M_{l'k'}\big)}}.
\mathrm{MCC}=1 — идеал, 0 — случай, -1 — полное расхождение. Высокий MCC требует хороших результатов по всем классам, поэтому это надёжная сводная метрика при дисбалансе.
2.4 Cohen's kappa
\kappa = \frac{p_o - p_e}{1 - p_e},
где p_o — наблюдаемая accuracy, p_e = \sum_c \frac{(\text{предсказано } c)\cdot(\text{истинно } c)}{N^2} — ожидаемое согласие «по случайности». Учитывает базовый уровень случайного угадывания. sklearn.cohen_kappa_score
2.5 ROC-AUC / PR-AUC (one-vs-rest)
Для multi-class каждый класс берётся как «positive», остальные — «negative» (OvR), по вероятностям p_c строится кривая, площади усредняются (macro / weighted). ROC-AUC = P(p_c^{+} > p_c^{-}) для случайной пары. При сильном дисбалансе ROC-AUC оптимистична (огромное число TN распухает specificity), поэтому для редких классов предпочтительна PR-AUC (average precision), не использующая TN. sklearn.roc_auc_score (multi_class='ovr') · average_precision_score
2.6 Калибровка (Expected Calibration Error, ECE)
Уверенность \hat{p}_i = \max_k p_k^{(i)} должна совпадать с фактической accuracy. Разбиваем [0,1] на B бинов \{b_m\}:
\mathrm{ECE} = \sum_{m=1}^{B} \frac{|b_m|}{N}\,\big|\,\mathrm{acc}(b_m) - \mathrm{conf}(b_m)\,\big|.
Современные сети переуверены (over-confident); label smoothing и temperature scaling снижают ECE (Müller et al., 2019, «When Does Label Smoothing Help?»; Guo et al., 2017, «On Calibration of Modern Neural Networks»).
2.7 Главная метрика при дисбалансе
При дисбалансе accuracy вводит в заблуждение: модель, предсказывающая только majority-класс, даёт высокую accuracy при нулевом recall на редких классах. Главными метриками выбирают macro-F1 или balanced accuracy (= macro-recall, sklearn.balanced_accuracy_score), т.к. они усредняют по классам с равным весом. Различие: balanced accuracy смотрит только на recall; macro-F1 балансирует precision и recall — предпочтительна, когда важны и пропуски, и ложные срабатывания. MCC — надёжная вторичная сводка. Для целей Optuna в качестве objective рекомендуется macro-F1 (или balanced accuracy) на валидации.
3. Loss-функции
Везде p_k — softmax-вероятность, y — истинный класс, p_y — вероятность истинного класса.
3.1 Cross-Entropy (CE)
\mathcal{L}_{\mathrm{CE}} = -\sum_{k=1}^{C} q_k \log p_k = -\log p_y \quad (\text{для one-hot } q).
Базовый loss. В PyTorch nn.CrossEntropyLoss принимает логиты (применяет log-softmax внутри).
3.2 Label Smoothing CE
Smooth-цель: q_k^{LS} = (1-\varepsilon)\,\mathbb{1}[k=y] + \dfrac{\varepsilon}{C} (равномерно размазывает \varepsilon по всем классам).
\mathcal{L}_{\mathrm{LS}} = -\sum_{k=1}^{C} q_k^{LS}\log p_k = (1-\varepsilon)\big(-\log p_y\big) + \frac{\varepsilon}{C}\sum_{k=1}^{C}\big(-\log p_k\big).
Типично \varepsilon \in [0.05, 0.1]. Уменьшает переуверенность, улучшает калибровку и обобщение (Szegedy et al., 2016, Inception-v3/ Rethinking the Inception Architecture; анализ — Müller et al., 2019, arXiv:1906.02629). Не борется напрямую с дисбалансом.
3.3 Focal Loss
Снижает вклад «лёгких» (хорошо классифицированных) примеров фактором (1-p_y)^\gamma (Lin et al., 2017, «Focal Loss for Dense Object Detection» / RetinaNet, arXiv:1708.02002):
\mathcal{L}_{\mathrm{FL}} = -\alpha_y\,(1 - p_y)^{\gamma}\,\log p_y.
\gamma— focusing parameter (типично\gamma = 2); при\gamma=0сводится к CE.\alpha_y— балансирующий вес класса (опционально).
При p_y \to 1 множитель (1-p_y)^\gamma \to 0 → лёгкие примеры почти не дают градиента, обучение фокусируется на трудных. Хорош при дисбалансе и большом числе «лёгких» фоновых примеров.
3.4 Class-Balanced Loss (effective number)
Переход от «числа сэмплов» к «эффективному числу» — учёт перекрытия близких изображений (Cui et al., 2019, CVPR, arXiv:1901.05555):
E_{n_c} = \frac{1 - \beta^{\,n_c}}{1 - \beta}, \qquad \beta = \frac{N-1}{N} \in [0,1).
Вес класса \propto 1/E_{n_c}, нормированный так, что \sum_c w_c = C:
w_c = \frac{1 - \beta}{1 - \beta^{\,n_c}}.
Подставляется как мультипликатор в любой базовый loss (CB-CE, CB-Focal):
\mathcal{L}_{\mathrm{CB}} = w_y \cdot \mathcal{L}_{\mathrm{base}}(p, y).
При \beta \to 0 → нет ребалансировки; \beta \to 1 → вес \propto 1/n_c (обратная частота). Типично \beta \in \{0.99, 0.999, 0.9999\}.
3.5 Weighted CE (class weights)
Частный случай: вес \propto обратной частоте, w_c = \dfrac{N}{C\, n_c} (нормировка опциональна):
\mathcal{L}_{\mathrm{wCE}} = -w_y \log p_y.
В PyTorch — аргумент weight в nn.CrossEntropyLoss. Простейшая мера против дисбаланса.
3.6 LDAM (кратко)
Label-Distribution-Aware Margin (Cao et al., 2019, NeurIPS, arXiv:1906.07413) добавляет класс-зависимый отступ в логит истинного класса перед softmax, увеличивая margin для редких классов:
\mathcal{L}_{\mathrm{LDAM}} = -\log \frac{e^{\,z_y - \Delta_y}}{e^{\,z_y - \Delta_y} + \sum_{k \neq y} e^{z_k}}, \qquad \Delta_c = \frac{K}{n_c^{1/4}}.
Маленький n_c → больший margin \Delta_c → строже decision boundary для minority. Обычно в связке с DRW (deferred re-weighting). Теоретически обоснован margin-based generalization bound.
3.7 Soft-target / KD-loss (кратко)
Knowledge Distillation (Hinton et al., 2015, arXiv:1503.02531): студент учится на «мягких» вероятностях teacher с температурой T:
\mathcal{L}_{\mathrm{KD}} = (1-\lambda)\,\mathcal{L}_{\mathrm{CE}}(p^{S}, y) + \lambda\,T^2\,\mathrm{KL}\!\big(p^{T}_{/T}\,\|\,p^{S}_{/T}\big),
где p_{/T} = \mathrm{softmax}(z/T). Множитель T^2 компенсирует масштаб градиентов. Soft-target несёт «dark knowledge» о близости классов (релевантно для визуально похожих печатей).
3.8 Таблица «когда какой loss»
| Loss | Когда применять | Гиперпараметры | Борется с дисбалансом |
|---|---|---|---|
| CE | baseline, сбалансированные данные | — | нет |
| Label Smoothing CE | переуверенность, регуляризация, калибровка | \varepsilon |
нет |
| Focal | дисбаланс + много «лёгких» примеров | \gamma, \alpha |
частично (по трудности) |
| Weighted CE | умеренный дисбаланс, быстрый фикс | веса w_c |
да (по частоте) |
| Class-Balanced (eff. number) | сильный/long-tail дисбаланс | \beta, базовый loss |
да (эфф. число) |
| LDAM (+DRW) | long-tail, нужен margin для minority | K, расписание DRW |
да (margin) |
| KD / soft-target | есть teacher, похожие классы | T, \lambda |
косвенно |
4. Семплирование и борьба с дисбалансом
4.1 Random shuffle vs WeightedRandomSampler
- Random shuffle (
shuffle=True): равномерная выборка без замены → батчи отражают исходное распределение классов → редкие классы редко попадают в батч. - WeightedRandomSampler: семплирование с вероятностью
\proptoвесу объекта. Для класс-балансировки вес объектаiклассаc:w_i = \dfrac{1}{n_c}(обратная частота). Тогда вероятность вытянуть любой класс одинакова → батчи приблизительно сбалансированы. Семплирование с заменой (replacement=True) → minority-объекты повторяются (фактический oversampling). PyTorch WeightedRandomSampler
4.2 Class-balanced batch sampling
Гарантирует фиксированное число объектов на класс в каждом батче (например, P классов × K объектов). Полезно для metric learning и когда нужна детерминированная балансировка батча, а не вероятностная как у WeightedRandomSampler.
4.3 Oversampling / undersampling
- Oversampling minority (дублирование / random oversampling): риск переобучения на повторённых картинках — смягчается аугментацией.
- Undersampling majority: теряется информация, но ускоряет эпоху.
- Библиотека imbalanced-learn (
RandomOverSampler,RandomUnderSampler) — для табличных/признаковых данных.
4.4 Почему SMOTE плохо подходит для сырых изображений
SMOTE (Chawla et al., 2002, JAIR) генерирует синтетические объекты линейной интерполяцией между соседями в признаковом пространстве. На сырых пикселях это даёт нефизичные, «призрачные» изображения (наложение/двоение): пиксельное пространство невыпукло и не семантично — линейная смесь двух картинок печатей не является валидной картинкой. Если применять — то в пространстве эмбеддингов предобученной сети, не на пикселях. Для изображений предпочтительнее аугментация и sampler-балансировка.
4.5 Аугментация как «семплирование»
Аугментация расширяет эффективное распределение, заменяя грубое дублирование. Особенно полезна для редких классов как «умный oversampling».
| Метод | Суть | Цель | Источник |
|---|---|---|---|
| RandAugment | случайный выбор N операций силой M из набора |
сильная регуляризация, 2 гиперпараметра | Cubuk et al., 2019, arXiv:1909.13719 |
| mixup | \tilde{x}=\lambda x_i+(1-\lambda)x_j, \tilde{y}=\lambda y_i+(1-\lambda)y_j, \lambda\sim\mathrm{Beta}(\alpha,\alpha) |
линейная регуляризация, soft-target | Zhang et al., 2018, arXiv:1710.09412 |
| CutMix | вырезает патч из x_j и вставляет в x_i, метка \propto площади |
локальные признаки + регуляризация | Yun et al., 2019, arXiv:1905.04899 |
mixup/cutmix дают мягкие метки → совместимы с CE по soft-target и сами по себе улучшают калибровку.
4.6 Стратифицированный split
Train/val/test делятся с сохранением пропорций классов (stratified) — иначе редкий класс может полностью отсутствовать в валидации, и метрика по нему неопределена. sklearn StratifiedShuffleSplit / train_test_split(stratify=y). Для маленьких датасетов — StratifiedKFold.
5. Связка loss × sampler (не дублировать компенсацию)
Дисбаланс компенсируют либо на уровне данных (sampler/oversampling), либо на уровне loss (class weights / CB / focal \alpha) — но не обоими сразу с одинаковой силой, иначе minority-классы переусиливаются: модель переобучается на них, recall редких растёт ценой обвала precision и общего качества.
| Комбинация | Эффект | Рекомендация |
|---|---|---|
| shuffle + CE | без компенсации | baseline |
| WeightedSampler + CE | компенсация в данных | OK |
| shuffle + weighted/CB/focal CE | компенсация в loss | OK |
| WeightedSampler + weighted CE | двойная компенсация | избегать |
| WeightedSampler + focal ($\alpha$=1) | sampler + фокус по трудности | приемлемо (разные механизмы) |
| mild sampler + mild class weights | частичная × частичная | допустимо при настройке |
Влияние на метрики: ребалансировка (любым способом) поднимает macro-F1 / balanced accuracy / recall редких классов, но обычно слегка снижает overall accuracy / micro-F1. Это ожидаемый и приемлемый trade-off при дисбалансе. Решение — какую метрику оптимизировать (см. §2.7).
6. Гиперпараметры для Optuna
| Гиперпараметр | Тип Optuna | Диапазон / значения | Комментарий |
|---|---|---|---|
loss_type |
categorical | {ce, ls_ce, focal, weighted_ce, cb_focal} |
выбор семейства loss |
label_smoothing |
float | [0.0, 0.2] |
активен для ce/ls_ce |
focal_gamma |
float | [0.5, 5.0] |
активен для focal/cb_focal |
focal_alpha |
float | [0.25, 1.0] |
балансирующий вес |
cb_beta |
categorical / float | \{0.99, 0.999, 0.9999\} |
для class-balanced |
use_weighted_sampler |
bool (categorical) | {True, False} |
sampler-балансировка |
mixup_alpha |
float | [0.0, 0.4] (0 = off) |
\mathrm{Beta}(\alpha,\alpha) |
cutmix_alpha |
float | [0.0, 1.0] (0 = off) |
вероятность через отд. флаг |
randaug_N / randaug_M |
int | N\in[1,3], M\in[5,15] |
сила аугментации |
Важно: задать условную логику (Optuna conditional / pruning), чтобы не подбирать одновременно use_weighted_sampler=True и loss_type=weighted_ce (см. §5). Целевая функция (direction='maximize') — macro-F1 или balanced accuracy на валидации. Optuna docs.
7. Практические PyTorch-сниппеты
7.1 WeightedRandomSampler
import numpy as np
import torch
from torch.utils.data import DataLoader, WeightedRandomSampler
labels = np.array(train_labels) # shape (N,), class indices
class_counts = np.bincount(labels) # n_c per class
class_weights = 1.0 / class_counts # w_c = 1 / n_c (inverse freq)
sample_weights = class_weights[labels] # per-sample weight
sampler = WeightedRandomSampler(
weights=torch.as_tensor(sample_weights, dtype=torch.double),
num_samples=len(sample_weights), # one epoch length
replacement=True, # required for oversampling
)
# NB: shuffle must be False when a sampler is passed
loader = DataLoader(train_ds, batch_size=64, sampler=sampler)
7.2 Focal Loss (multi-class)
import torch
import torch.nn as nn
import torch.nn.functional as F
class FocalLoss(nn.Module):
"""Multi-class focal loss; expects raw logits."""
def __init__(self, gamma: float = 2.0, alpha: torch.Tensor | None = None):
super().__init__()
self.gamma = gamma
self.alpha = alpha # per-class weights, shape (C,)
def forward(self, logits: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
logp = F.log_softmax(logits, dim=1) # (B, C)
logp_t = logp.gather(1, target[:, None]).squeeze(1) # log p_y
p_t = logp_t.exp() # p_y
loss = -((1 - p_t) ** self.gamma) * logp_t
if self.alpha is not None:
loss = self.alpha.to(logits.device)[target] * loss
return loss.mean()
7.3 Label smoothing в nn.CrossEntropyLoss
import torch.nn as nn
# Built-in since PyTorch 1.10; optional per-class weight for imbalance.
criterion = nn.CrossEntropyLoss(label_smoothing=0.1, weight=class_weight_tensor)
loss = criterion(logits, target) # logits: raw (B, C)
7.4 Расчёт macro-F1 / balanced accuracy (sklearn)
from sklearn.metrics import f1_score, balanced_accuracy_score, classification_report
y_pred = logits.argmax(dim=1).cpu().numpy()
y_true = target.cpu().numpy()
macro_f1 = f1_score(y_true, y_pred, average="macro")
bal_acc = balanced_accuracy_score(y_true, y_pred) # = macro-recall
# Per-class breakdown (precision / recall / F1 / support):
print(classification_report(y_true, y_pred, digits=4))
Источники
- Lin et al., 2017 — Focal Loss / RetinaNet: arXiv:1708.02002
- Cui et al., 2019 — Class-Balanced Loss (effective number): arXiv:1901.05555
- Cao et al., 2019 — LDAM: arXiv:1906.07413
- Szegedy et al., 2016 — Label Smoothing (Inception-v3): arXiv:1512.00567
- Müller et al., 2019 — When Does Label Smoothing Help?: arXiv:1906.02629
- Guo et al., 2017 — Calibration of Modern Neural Networks (ECE): arXiv:1706.04599
- Hinton et al., 2015 — Knowledge Distillation: arXiv:1503.02531
- Zhang et al., 2018 — mixup: arXiv:1710.09412
- Yun et al., 2019 — CutMix: arXiv:1905.04899
- Cubuk et al., 2019 — RandAugment: arXiv:1909.13719
- Chawla et al., 2002 — SMOTE: JAIR
- scikit-learn — Model evaluation metrics, balanced_accuracy_score, matthews_corrcoef
- imbalanced-learn · PyTorch data sampling · Optuna