Сбор данных и схема классов
Уроки курсаСбор данных и схема классов
Источники данных, баланс классов, сплиты по камерам/сюжетам, стратификация
Данные — основа CV-модели. Источники: публичные датасеты (COCO, ImageNet), краудсорсинг, собственная съёмка. Баланс классов критичен: если "кошек" в 100 раз больше "собак", модель научится предсказывать только кошек. Сплиты по камерам/сюжетам предотвращают overfitting на конкретную камеру. Стратификация сохраняет пропорции классов в train/val/test — иначе в тесте может не быть редких классов.
Ключевые идеи
- Источники: публичные датасеты (бесплатно, размечены) vs собственные (дорого, специфичны).
- Баланс классов: если дисбаланс > 10:1, нужны аугментации или взвешенный loss.
- Сплиты по камерам: train на камере A, val на камере B — проверяем generalization.
- Стратификация: в train/val/test одинаковые пропорции классов.
Источники данных
Откуда брать данные для обучения:
Публичные датасеты:
- COCO (Common Objects in Context): 330K изображений, 80 классов (person, car, dog...). Стандарт для детекции.
- ImageNet: 14M изображений, 21K категорий. Для классификации и transfer learning.
- Open Images: 9M изображений, 600 классов. Больше COCO, но аннотации хуже.
- Pascal VOC: 20 классов, 11K изображений. Старый, но всё ещё используется для бенчмарков.
Собственные данные:
- Нужны, если задача специфична (медицина, промышленность, спутниковые снимки).
- Дорого: съёмка, разметка (краудсорсинг типа Toloka, или вручную).
- Преимущества: точно соответствуют задаче, можно контролировать качество.
Что проверять при выборе:
- Качество: размытие, освещение, артефакты. Плохие данные → плохая модель.
- Разнообразие: разные ракурсы, фоны, условия съёмки. Иначе модель переобучится на конкретный сценарий.
- Лицензия: можно ли использовать коммерчески. COCO — можно, некоторые Kaggle датасеты — нельзя.
Баланс классов
Если в датасете класс "фон" 90%, а "объект" 10%, модель научится предсказывать фон — accuracy 90%, но модель бесполезна.
Пример проверки баланса:
import matplotlib.pyplot as plt
from collections import Counter
# Подсчёт классов
class_counts = Counter(labels) # labels = ['cat', 'dog', 'cat', ...]
classes = list(class_counts.keys())
counts = list(class_counts.values())
plt.figure(figsize=(10, 5))
plt.bar(classes, counts)
plt.xlabel('Класс')
plt.ylabel('Количество изображений')
plt.title('Распределение классов')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# Проверка дисбаланса
max_count = max(counts)
min_count = min(counts)
imbalance_ratio = max_count / min_count
print(f'Дисбаланс: {imbalance_ratio:.1f}:1')
Что делать при дисбалансе:
- Аугментации для редких классов: повороты, flip, crop, изменение яркости. Искусственно увеличиваем число примеров.
from albumentations import Compose, HorizontalFlip, Rotate, RandomBrightnessContrast aug = Compose([ HorizontalFlip(p=0.5), Rotate(limit=15, p=0.5), RandomBrightnessContrast(p=0.3) ]) # Для редких классов применяем 5-10 раз - Взвешенный loss: редким классам больший вес, частым — меньший.
import torch.nn as nn # class_weights: [0.5, 2.0, 1.0] — второй класс редкий, вес выше criterion = nn.CrossEntropyLoss(weight=torch.tensor(class_weights)) - Focal Loss: автоматически фокусируется на сложных (редких) примерах. Популярен в детекции.
- Undersampling частых классов: случайно удаляем часть примеров частого класса. Теряем данные, но баланс улучшается.
Когда баланс не критичен: если задача — детекция редких событий (дефекты на производстве, опухоли на МРТ), дисбаланс ожидаем. Метрики: precision/recall для редкого класса, не общий accuracy.
Сплиты по камерам и сюжетам
Проблема: обучили модель на изображениях с камеры Canon, она плохо работает на Sony (другие цвета, шум).
Решение: разделяем данные по источникам:
- Train: камеры A, B, C, сюжеты 1-10.
- Val: камера D, сюжеты 11-12.
- Test: камера E, сюжеты 13-15.
Так проверяем, что модель generalize на новые условия, а не просто запомнила конкретную камеру.
Пример сплита по камерам:
import pandas as pd
from sklearn.model_selection import GroupShuffleSplit
# metadata: [image_path, label, camera_id]
df = pd.read_csv('metadata.csv')
# Сплит по camera_id: train 70%, val 15%, test 15%
gss = GroupShuffleSplit(n_splits=1, test_size=0.15, random_state=42)
train_val_idx, test_idx = next(gss.split(df, groups=df['camera_id']))
train_val_df = df.iloc[train_val_idx]
test_df = df.iloc[test_idx]
# Разделяем train/val тоже по камерам
gss2 = GroupShuffleSplit(n_splits=1, test_size=0.176, random_state=42) # 15/(70+15)
train_idx, val_idx = next(gss2.split(train_val_df, groups=train_val_df['camera_id']))
train_df = train_val_df.iloc[train_idx]
val_df = train_val_df.iloc[val_idx]
print(f'Train: {len(train_df)}, Val: {len(val_df)}, Test: {len(test_df)}')
Стратификация
Стратификация сохраняет пропорции классов в сплитах. Если в датасете 70% класса A и 30% класса B, в train/val/test тоже должно быть 70/30.
Без стратификации: случайно все редкие классы попали в train, в test их нет — метрики завышены.
Пример:
from sklearn.model_selection import train_test_split
X = df['image_path'].values
y = df['label'].values
# stratify=y сохраняет пропорции классов
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
# Проверка пропорций
from collections import Counter
print('Train:', Counter(y_train))
print('Test:', Counter(y_test))
# Должны быть примерно одинаковые пропорции
Комбинация со сплитом по камерам: сначала делим по камерам (GroupShuffleSplit), затем внутри каждого сплита стратифицируем по классам. Сложнее, но надёжнее.
Частые проблемы
- Мало данных: < 1000 изображений на класс — модель переобучится. Решение: transfer learning (берём модель, обученную на ImageNet, fine-tune'им), аугментации.
- Сильный дисбаланс (100:1): взвешенный loss и аугментации не помогут. Нужно собрать больше данных редкого класса или использовать Focal Loss.
- Забыли про камеры/сюжеты: модель отлично работает на валидации (те же камеры), но проваливается в продакшене (новые камеры). Всегда делайте сплит по источникам.
- Стратификация без учёта групп: стратифицировали по классам, но все изображения одной камеры попали в train — modель запомнила камеру. Нужна комбинация GroupShuffleSplit + stratify.
Связанные понятия
- Data Augmentation: повороты, flip, crop, изменение цветов — увеличиваем разнообразие данных.
- Transfer Learning: используем модель, обученную на большом датасете (ImageNet), дообучаем на своих данных.
- K-Fold Cross-Validation: делим данные на K частей, обучаем K моделей (каждая на K-1 частях), усредняем метрики.
Итоги
- Источники: COCO/ImageNet для общих задач, собственные для специфичных.
- Баланс классов: дисбаланс > 10:1 требует аугментаций или взвешенного loss.
- Сплиты по камерам/сюжетам: проверяем generalization, не overfitting на конкретный источник.
- Стратификация: сохраняет пропорции классов в train/val/test.
- Комбинируйте: GroupShuffleSplit (по камерам) + stratify (по классам) для надёжных сплитов.
Дальше по курсу
В следующем шаге разберём форматы данных для детекции: YOLO (txt с координатами), COCO (JSON с аннотациями), Pascal VOC (XML). Создадим data.yaml с путями к train/val/test и картой классов. Научимся конвертировать форматы и валидировать аннотации.
