Техники обучения
🎯 Цель
После прочтения этой главы:
- Знаете техники эффективного обучения neural network’ов
- Используете средства борьбы с overfitting (Dropout, BatchNorm, regularization)
- Применяете learning rate scheduling, gradient clipping, mixed precision
- Получаете хорошие результаты даже на маленьких datasets через transfer learning
Что нужно изучить
- Regularization: L1/L2 (weight decay), Dropout, BatchNorm, LayerNorm
- Initialization: Xavier (Glorot), He, Kaiming
- Optimizersглубже: SGD+momentum, Adam, AdamW, LAMB
- Learning rate scheduling: StepLR, CosineAnnealingLR, OneCycleLR, ReduceLROnPlateau
- Gradient clipping — защита от gradient explosion
- Mixed precision training(FP16/BF16) — быстрее + меньше memory
- Data augmentation — искусственное расширение dataset
- Transfer learning — повторное использование pretrained моделей
- Early stopping и checkpointing
- Weights & Biases / TensorBoard — experiment tracking
Библиотеки
pip install torch torchvision wandb tensorboard
Важные темы
Техники Regularization
Dropout
Случайное «отключение» neuron’ов во время обучения — предотвращает overfitting.
import torch.nn as nn
class Model(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.dropout = nn.Dropout(p=0.5) # 50% neuron отключаются
def forward(self, x):
x = torch.relu(self.fc1(x))
x = self.dropout(x)
return x
# В eval mode dropout автоматически отключается (при вызове `.eval()`)
Batch Normalization
Нормализация activation’ов в каждом batch — быстрее сходимость + эффект регуляризации.
class Model(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.bn1 = nn.BatchNorm1d(256) # 1D BN (для MLP)
def forward(self, x):
x = self.fc1(x)
x = self.bn1(x)
x = torch.relu(x)
return x
# Для CNN: nn.BatchNorm2d
# Для Transformer: nn.LayerNorm (LayerNorm подходит лучше)
Weight Decay (L2)
Параметр weight_decay в optimizer.
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
Learning Rate Scheduling
from torch.optim.lr_scheduler import (
StepLR, CosineAnnealingLR, OneCycleLR, ReduceLROnPlateau,
)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
# Variant 1: Step decay (уменьшается в gamma раз каждые N epoch)
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)
# Variant 2: Cosine annealing (плавное снижение)
scheduler = CosineAnnealingLR(optimizer, T_max=EPOCHS)
# Variant 3: OneCycleLR (warmup + decay) — Karpathy's favorite
scheduler = OneCycleLR(optimizer, max_lr=1e-2, total_steps=EPOCHS * len(train_loader))
# Variant 4: ReduceLROnPlateau (если val loss не улучшается)
scheduler = ReduceLROnPlateau(optimizer, mode="min", factor=0.5, patience=3)
# В training loop
for epoch in range(EPOCHS):
train_one_epoch(...)
scheduler.step() # в конце epoch (или для ReduceLROnPlateau: scheduler.step(val_loss))
Gradient Clipping
Чтобы training не «взорвался» при слишком больших gradient’ах:
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
Особенно нужен при training RNN/LSTMи Transformer.
Mixed Precision Training
Уменьшает GPU memory в 2x, увеличивает скорость в 2-3x.
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
for X, y in loader:
X, y = X.cuda(), y.cuda()
with autocast(dtype=torch.float16):
logits = model(X)
loss = criterion(logits, y)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad()
Data Augmentation (для Image)
from torchvision import transforms
train_transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.RandomRotation(15),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
# Для test augmentation НЕ применяется
test_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
Transfer Learning
import torchvision.models as models
# Pretrained ResNet-18
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
# Variant 1: Переобучение только последнего слоя (feature extraction)
for param in model.parameters():
param.requires_grad = False # freeze всё
model.fc = nn.Linear(model.fc.in_features, num_classes) # новый classifier
# Только model.fc.parameters() train
# Variant 2: Fine-tuning (train всё, с маленьким LR)
optimizer = torch.optim.AdamW([
{"params": model.layer1.parameters(), "lr": 1e-5}, # старые layer — низкий LR
{"params": model.layer4.parameters(), "lr": 1e-4},
{"params": model.fc.parameters(), "lr": 1e-3}, # новый layer — высокий LR
])
Примеры кода
Полный training pipeline (production-ready)
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR
from torch.cuda.amp import autocast, GradScaler
def train_model(
model, train_loader, val_loader,
epochs=20, lr=1e-3, weight_decay=1e-4,
grad_clip=1.0, use_amp=True,
save_path="best.pt",
):
device = next(model.parameters()).device
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=lr, weight_decay=weight_decay)
scheduler = CosineAnnealingLR(optimizer, T_max=epochs)
scaler = GradScaler() if use_amp else None
best_val_acc = 0
for epoch in range(epochs):
# Train
model.train()
train_loss = 0
for X, y in train_loader:
X, y = X.to(device), y.to(device)
optimizer.zero_grad()
if use_amp:
with autocast():
logits = model(X)
loss = criterion(logits, y)
scaler.scale(loss).backward()
scaler.unscale_(optimizer)
torch.nn.utils.clip_grad_norm_(model.parameters(), grad_clip)
scaler.step(optimizer)
scaler.update()
else:
logits = model(X)
loss = criterion(logits, y)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), grad_clip)
optimizer.step()
train_loss += loss.item()
scheduler.step()
# Validate
model.eval()
val_correct = 0
val_total = 0
with torch.no_grad():
for X, y in val_loader:
X, y = X.to(device), y.to(device)
logits = model(X)
val_correct += (logits.argmax(dim=1) == y).sum().item()
val_total += y.size(0)
val_acc = val_correct / val_total
print(f"Epoch {epoch+1}/{epochs} "
f"train_loss={train_loss/len(train_loader):.4f} "
f"val_acc={val_acc:.4f} "
f"lr={optimizer.param_groups[0]['lr']:.6f}")
# Save best
if val_acc > best_val_acc:
best_val_acc = val_acc
torch.save(model.state_dict(), save_path)
return best_val_acc
Интеграция с Weights & Biases
import wandb
wandb.init(project="my-ml-project", config={
"lr": 1e-3,
"batch_size": 64,
"epochs": 20,
"architecture": "ResNet-18",
})
# Внутри training loop
wandb.log({
"train_loss": train_loss,
"val_acc": val_acc,
"lr": optimizer.param_groups[0]["lr"],
}, step=epoch)
wandb.finish()
Интеграция с TensorBoard
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("runs/experiment_1")
for epoch in range(epochs):
# ... training ...
writer.add_scalar("Loss/train", train_loss, epoch)
writer.add_scalar("Accuracy/val", val_acc, epoch)
writer.add_histogram("fc.weights", model.fc.weight, epoch)
writer.close()
# $ tensorboard --logdir=runs
Transfer Learning — полный пример
import torch
import torch.nn as nn
import torchvision.models as models
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 1. Pretrained ResNet
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
# Freeze backbone
for param in model.parameters():
param.requires_grad = False
# Новый classifier (10 классов для болезней)
model.fc = nn.Sequential(
nn.Linear(model.fc.in_features, 512),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(512, 10),
)
# 2. Optimizer только для fc-параметров
optimizer = torch.optim.AdamW(model.fc.parameters(), lr=1e-3)
# 3. Train (только fc)
train_model(model, train_loader, val_loader, epochs=5, lr=1e-3)
# 4. Unfreeze и fine-tune (маленький LR)
for param in model.parameters():
param.requires_grad = True
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-5)
train_model(model, train_loader, val_loader, epochs=10, lr=1e-5)
Интеграция с backend
Training service (background job)
from celery import Celery
import torch
celery_app = Celery("training", broker="redis://localhost:6379")
@celery_app.task(bind=True)
def train_model_task(self, dataset_path, hyperparams):
# Progress tracking
def on_epoch_end(epoch, val_acc):
self.update_state(
state="PROGRESS",
meta={"epoch": epoch, "val_acc": val_acc},
)
model = create_model()
train_loader, val_loader = create_loaders(dataset_path, hyperparams["batch_size"])
best_acc = train_model(model, train_loader, val_loader, **hyperparams,
on_epoch_end=on_epoch_end)
# Save to S3 or local
model_path = f"models/run_{self.request.id}.pt"
torch.save(model.state_dict(), model_path)
return {"best_acc": best_acc, "model_path": model_path}
# FastAPI endpoint
@app.post("/train")
def start_training(dataset_path: str, epochs: int = 20):
task = train_model_task.delay(dataset_path, {"epochs": epochs, "batch_size": 64, "lr": 1e-3})
return {"task_id": task.id}
@app.get("/train/{task_id}")
def get_training_status(task_id: str):
task = train_model_task.AsyncResult(task_id)
return {
"state": task.state,
"info": task.info if task.info else {},
}
Ресурсы
- PyTorch tutorials — Training techniques(link)
- “Bag of Tricks for Image Classification with CNNs” — paper (training improvements)
- Andrej Karpathy — “A Recipe for Training Neural Networks”(blog)
- Weights & Biases — Best Practicescourses
- OneCycleLR — Leslie Smith paper
🏋️ Упражнения
🟢 Easy
- Добавьте Dropout в MLP, посмотрите разницу между train accuracy и val accuracy.
- Сравните Adam и SGD на одной и той же модели.
- Добавьте ReduceLROnPlateau, визуально посмотрите plateau.
🟡 Medium
- Mixed precision: запустите одно и то же training с FP32 и AMP, сравните время и memory.
- Augmentation: сравните обычную CNN с augmentation и без (CIFAR-10).
- Transfer learning: получите 90%+ accuracy на маленьком dataset из 100 изображений через pretrained ResNet.
🔴 Hard
- Custom LR scheduler: напишите scheduler с комбинацией warmup + cosine annealing.
- Hyperparameter sweep: 50 trial через Optuna или wandb sweeps, найдите лучшую конфигурацию.
- Production training service: Celery + FastAPI + S3 + W&B — полный pipeline.
Capstone
notebooks/month-03/04_training_techniques.ipynb:
- Сравните 2 варианта на CIFAR-10 dataset:
- Baseline: обычная CNN, Adam, без augmentation
- Improved: BatchNorm + Dropout + augmentation + OneCycleLR + AMP
- Логирование в Wandb или TensorBoard
- Test accuracy: baseline ~70%, improved 85%+
✅ Чек-лист
- Знаю, когда применять Dropout, BatchNorm
- Знаю разницу между Adam и AdamW (weight decay)
- Знаю типы learning rate scheduling
- Знаю, когда нужен gradient clipping
- Умею применять mixed precision training (AMP)
- Использую data augmentation (vision)
- Получаю хорошие результаты на маленьких dataset’ах через transfer learning
- Делаю experiment tracking через W&B или TensorBoard
Переходим к CNN — Convolutional Networks.