Настроить self-training с псевдо-метками
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить self-training с псевдо-метками
1. Цель задачи
Разработать и реализовать пайплайн self-training (самообучения) для задачи классификации, в котором начальная модель обучается на небольшом размеченном наборе данных, затем используется для генерации псевдо-меток на большом неразмеченном наборе, после чего дообучается вместе с размеченными данными. Цикл повторяется несколько итераций. Ключевой результат: улучшение качества классификации (F1-score) не менее чем на 10% относительно baseline, обученного только на исходных размеченных данных.
2. Исходные данные
| Что нужно | Откуда взять |
|---|---|
| Набор изображений для классификации (CIFAR-10) | torchvision.datasets.CIFAR10 (встроен в PyTorch) |
| Размеченная подвыборка (20% от всего набора) | Случайная стратифицированная выборка из train set |
| Неразмеченная подвыборка (оставшиеся 80% от train set, метки убраны) | Искусственно скрыть метки (удалить из DataFrame / заменить на -1) |
| Baseline модель | ResNet-18, предобученная на ImageNet (через torchvision.models) |
| Метрика качества | F1-score (weighted) на полном тестовом наборе CIFAR-10 |
Если нет реального инструмента — симулируем:
- Загрузить CIFAR-10 через torchvision.datasets.CIFAR10(..., train=True, download=True).
- Разделить train set на labeled (20%) и unlabeled (80%) с сохранением пропорций классов (stratify).
- Для unlabeled части заменить метки на -1 и использовать torch.utils.data.Dataset, который возвращает только изображение (метку не используем).
- Создать отдельный тестовый набор (стандартный test split CIFAR-10).
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Фреймворк глубокого обучения | PyTorch 2.x, torchvision | Построение, обучение, инференс модели |
| Работа с данными | numpy, scikit-learn (train_test_split) | Разделение данных, расчёт метрик |
| Метрики | sklearn.metrics.f1_score, classification_report | Оценка качества |
| Визуализация | matplotlib, seaborn | Динамика метрик по итерациям |
| Управление экспериментами | tensorboard (опционально) или print-логи | Отслеживание loss, accuracy, F1 |
| Код | Python 3.10+, Jupyter Notebook или .py скрипты | Реализация пайплайна |
4. Этапы выполнения
Этап 1: Подготовка данных и baseline (1.5 часа)
Действия
- Загрузить CIFAR-10, применить нормализацию (mean=(0.4914, 0.4822, 0.4465), std=(0.2023, 0.1994, 0.2010)).
- Сформировать labeled_loader и unlabeled_loader (для unlabeled — без меток, только изображения). Для unlabeled можно создать класс
UnlabeledDataset, возвращающий (image, 0) или использоватьignore_index. - Создать test_loader (стандартный 10k изображений).
- Загрузить предобученную ResNet-18, заменить последний fully connected слой на nn.Linear(512, 10).
- Обучить baseline только на labeled_loader: 10 эпох, оптимизатор SGD lr=0.01, momentum=0.9, stepLR, CrossEntropyLoss.
- Замерить F1 на test_loader — это baseline.
# Пример фрагмента
model = torchvision.models.resnet18(pretrained=True)
model.fc = nn.Linear(512, 10)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)
Ожидаемый результат этапа
- Получен baseline-скор (например F1≈0.55-0.65 на 20% labeled).
- Код разделения данных и загрузчиков готов.
Этап 2: Реализация цикла self-training (2 часа)
Действия
- Написать функцию generate_pseudo_labels(model, unlabeled_loader, threshold=0.9):
- Написать функцию
update_dataset(original_unlabeled_indices, selected_indices, pseudo_labels): расширяет labeled набор, удаляя отобранные образцы из unlabeled. - Реализовать основной цикл (3-5 итераций):
- Генерация псевдо-меток.
- Объединение labeled + новые псевдо-меченые данные.
- Дообучение модели на расширенном наборе (2-3 эпохи на каждой итерации, чтобы не переобучиться).
- Оценка на test set.
- Использовать threshold = 0.95 на первой итерации, затем можно снизить до 0.85.
# Псевдо-код цикла
for iteration in range(num_iterations):
model.eval()
selected = []
with torch.no_grad():
for images, _ in unlabeled_loader:
outputs = model(images)
probs = torch.softmax(outputs, dim=1)
max_probs, preds = torch.max(probs, dim=1)
mask = max_probs > threshold
selected.append((images[mask], preds[mask]))
# Добавить выбранные в labeled_loader
# Дообучить модель
Ожидаемый результат этапа
- Полный пайплайн self-training, который исполняется без ошибок.
- Логирование F1 после каждой итерации.
Этап 3: Эксперименты с порогом и количеством итераций (1 час)
Действия
- Проверить 3 варианта порога: 0.95, 0.90, 0.85.
- Для каждого порога выполнить цикл из 5 итераций.
- Сохранить динамику F1 и размер накопленного labeled набора.
- Построить график: номер итерации vs F1-test для каждого порога.
- Выбрать лучший порог по конечному F1.
Ожидаемый результат этапа
- Таблица с результатами (порог, F1 после каждой итерации, итоговый F1).
- График.
Этап 4: Финальная оценка и анализ ошибок (1 час)
Действия
- На лучшей конфигурации обучить финальную модель на всех размеченных + псевдо-меченых данных (10 эпох с lr=0.001).
- Вычислить F1-score на тесте, сравнить с baseline.
- Построить confusion matrix (heatmap) для выявления классов, которые улучшились/ухудшились.
- Посчитать, сколько образцов реально добавлено (соотношение добавленных к общему числу unlabeled).
Ожидаемый результат этапа
- Итоговый F1 на тесте (должен быть >= baseline * 1.10).
- Confusion matrix.
- Вывод: какие классы выиграли от self-training.
Этап 5: Оформление отчёта (0.5 часа)
Действия
- Записать результаты эксперимента в формате: используемый датасет, baseline, порог, итерации, итоговый F1.
- Приложить код и графики.
- Указать выводы и возможные улучшения (например, изменение порога по итерациям, использвание consistency regularization).
Ожидаемый результат этапа
- Файл
report.mdили report.ipynb с финальными метриками и графиками.
5. Критерии приемки (Definition of Done)
- Baseline (только 20% размеченных) вычислен и зафиксирован.
- Реализована функция
generate_pseudo_labelsс поддержкой порога уверенности. - Цикл self-training исполняется не менее 3 итераций.
- F1 на тестовом наборе улучшился не менее чем на 10% относительно baseline.
- Построен график зависимости F1 от итерации для как минимум двух порогов.
- Confusion matrix подтверждает улучшение на большинстве классов.
- Код пайплайна оформлен в виде Python-скрипта или ноутбука с комментариями.
- Отчёт содержит baseline, итоговую метрику, выбранный порог и выводы.
6. Ожидаемый результат
- Основной артефакт Jupyter Notebook или Python-скрипт с полной реализацией self-training, включая раздел с экспериментом.
- Содержание ноутбука/скрипта
- Загрузка и разделение CIFAR-10.
- Baseline обучение.
- Функции генерации псевдо-меток и обновления датасета.
- Цикл self-training с разными порогами.
- Финальная оценка (F1, confusion matrix).
- Дополнительные результаты
- Графики динамики метрик.
- Таблица сравнения порогов.
- Файл отчёта
.mdс текстовыми выводами.
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| Псевдо-метки низкого качества (много шума) | Установить высокий порог (≥0.95) на первых итерациях, постепенно снижать. Добавить фильтрацию по энтропии. |
| Модель запоминает ошибки (confirmation bias) | Использовать отдельную модель для генерации меток (teacher-student). Либо дообучать с очень низким lr и малым числом эпох. |
| Дисбаланс классов после добавления псевдо-меток | Применять weighted loss или class-balanced sampling в DataLoader. |
| Переобучение на маленьком labeled наборе | Использовать аугментацию данных (RandomCrop, HorizontalFlip). |
| Память GPU не хватает для большого unlabeled | Использовать DataLoader с batch_size ≤ 256, накапливать градиенты. |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| Подготовка данных и baseline | 1.5 часа |
| Реализация цикла self-training | 2 часа |
| Эксперименты с порогом и итерациями | 1 час |
| Финальная оценка и анализ | 1 час |
| Оформление отчёта | 0.5 часа |
| Итого | 6 часов |
Примечание для первого раза: возможны задержки на отладку DataLoader с неразмеченными данными, а также на подбор оптимального числа эпох на итерацию. Рекомендуется выделить дополнительный час на отладку.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 45 | Как разделить данные на labeled/unlabeled с сохранением стратификации? |
| 112 | Какие метрики использовать при дисбалансе классов в semi-supervised обучении? |
| 203 | Реализация порога уверенности для псевдо-меток |
| 278 | Confirmation bias в self-training и способы борьбы |
| 345 | Аугментация данных для CIFAR-10 в PyTorch |
| 401 | Как построить confusion matrix с помощью sklearn? |
| 512 | Динамика обучения: почему self-training может ухудшить качество? |
| 623 | Teacher-student vs self-training: сравнение подходов |
| 714 | Weighted CrossEntropyLoss для несбалансированных данных |
| 899 | Оптимизация hyperparams для дообучения на расширенном датасете |
10. Чек-лист самопроверки
- Я убедился, что baseline модель обучена корректно и метрики зафиксированы до начала self-training.
- Я правильно организовал unlabeled DataLoader — он не использует метки, но хранит исходные индексы.
- В цикле self-training после добавления псевдо-меток я не использую эти же данные повторно как unlabeled (удаляю из unlabeled набора).
- Я проверил, что порог уверенности применяется к вероятностям softmax (а не к logits).
- Я построил хотя бы один график динамики F1 по итерациям и убедился, что результат не хуже baseline.