Computer Vision Engineer: от детекции и Doc-AI до продакшнаДанные и разметкаСбор данных и схема классов

Сбор данных и схема классов

Уроки курсаСбор данных и схема классов

Источники данных, баланс классов, сплиты по камерам/сюжетам, стратификация

Данные — основа 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, или вручную).
  • Преимущества: точно соответствуют задаче, можно контролировать качество.

Что проверять при выборе:

  1. Качество: размытие, освещение, артефакты. Плохие данные → плохая модель.
  2. Разнообразие: разные ракурсы, фоны, условия съёмки. Иначе модель переобучится на конкретный сценарий.
  3. Лицензия: можно ли использовать коммерчески. 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')

Что делать при дисбалансе:

  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 раз
    
  2. Взвешенный loss: редким классам больший вес, частым — меньший.
    import torch.nn as nn
    
    # class_weights: [0.5, 2.0, 1.0] — второй класс редкий, вес выше
    criterion = nn.CrossEntropyLoss(weight=torch.tensor(class_weights))
    
  3. Focal Loss: автоматически фокусируется на сложных (редких) примерах. Популярен в детекции.
  4. 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), затем внутри каждого сплита стратифицируем по классам. Сложнее, но надёжнее.

Частые проблемы

  1. Мало данных: < 1000 изображений на класс — модель переобучится. Решение: transfer learning (берём модель, обученную на ImageNet, fine-tune'им), аугментации.
  2. Сильный дисбаланс (100:1): взвешенный loss и аугментации не помогут. Нужно собрать больше данных редкого класса или использовать Focal Loss.
  3. Забыли про камеры/сюжеты: модель отлично работает на валидации (те же камеры), но проваливается в продакшене (новые камеры). Всегда делайте сплит по источникам.
  4. Стратификация без учёта групп: стратифицировали по классам, но все изображения одной камеры попали в 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 и картой классов. Научимся конвертировать форматы и валидировать аннотации.