PyTorch: Шпаргалка¶
~4 минуты чтения
PyTorch -- доминирующий фреймворк для ML research (90%+ NeurIPS 2025 papers) и production (через TorchScript, ONNX, torch.compile). На интервью PyTorch-вопросы проверяют: training loop (zero_grad/backward/step порядок), model.train() vs model.eval(), autograd механику и device management. Три самые частые ошибки: забыть model.eval() при inference, подать probabilities вместо logits в CrossEntropyLoss, и не использовать torch.no_grad() для validation.
Быстрый старт¶
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10)
).to(device) # (1)!
criterion = nn.CrossEntropyLoss() # (2)!
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(10):
for x, y in dataloader:
x, y = x.to(device), y.to(device)
optimizer.zero_grad() # (3)!
loss = criterion(model(x), y)
loss.backward() # (4)!
optimizer.step() # (5)!
.to(device)-- обязательно перенести модель на GPU перед обучением. Без этого данные на GPU, модель на CPU = ошибка.CrossEntropyLossожидает logits (raw output), не probabilities! Внутри уже есть LogSoftmax. Подаватьsoftmax(output)-- двойной softmax, потеря качества.- Порядок критичен:
zero_grad()ПЕРЕД forward pass. Иначе градиенты накапливаются от предыдущих батчей (иногда это нужно, но обычно -- баг). backward()вычисляетdL/dwдля всех параметров сrequires_grad=Trueчерез цепочку autograd.step()обновляет веса:w = w - lr * grad. Вызывать ПОСЛЕbackward(), иначе обновление по старым/нулевым градиентам.
Tensors¶
Создание¶
# Из данных
x = torch.tensor([1, 2, 3])
x = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
# Специальные тензоры
zeros = torch.zeros(3, 4) # Нули
ones = torch.ones(3, 4) # Единицы
rand = torch.rand(3, 4) # Uniform [0, 1)
randn = torch.randn(3, 4) # Normal(0, 1)
arange = torch.arange(0, 10, 2) # [0, 2, 4, 6, 8]
linspace = torch.linspace(0, 1, 5) # 5 точек от 0 до 1
eye = torch.eye(3) # Единичная матрица
# Как другой тензор
x_like = torch.zeros_like(x)
x_new = torch.randn_like(x, dtype=torch.float32)
Атрибуты¶
x.shape # torch.Size([3, 4])
x.dtype # torch.float32
x.device # cpu или cuda:0
x.requires_grad # True/False
x.ndim # Количество измерений
x.numel() # Общее количество элементов
Операции с формой¶
x.view(2, 6) # Изменить форму (contiguous required!)
x.reshape(2, 6) # Изменить форму (безопасно, копирует если надо)
x.squeeze() # Убрать размерности = 1
x.unsqueeze(0) # Добавить размерность
x.transpose(0, 1) # Поменять оси местами
x.permute(2, 0, 1) # Переставить все оси
x.flatten() # В одномерный
x.flatten(1) # Flatten начиная с dim=1
x.contiguous() # Сделать память непрерывной
# Объединение
torch.cat([x, y], dim=0) # Конкатенация по оси
torch.stack([x, y], dim=0) # Новая ось
torch.split(x, 2, dim=0) # Разбить на части
torch.chunk(x, 3, dim=0) # Разбить на n частей
Математика¶
# Поэлементные
x + y, x - y, x * y, x / y
torch.add(x, y)
torch.mul(x, y)
torch.pow(x, 2)
torch.sqrt(x)
torch.exp(x)
torch.log(x)
torch.abs(x)
torch.clamp(x, min=0, max=1)
# Матричные
torch.mm(x, y) # Матричное умножение 2D
torch.bmm(x, y) # Батч матричное умножение [B, N, M] @ [B, M, K]
torch.matmul(x, y) # Универсальное (broadcasting)
x @ y # То же что matmul
# Агрегации
x.sum(), x.mean(), x.std(), x.var()
x.min(), x.max()
x.argmin(), x.argmax()
x.sum(dim=1) # По оси
x.sum(dim=1, keepdim=True) # Сохранить размерность
Индексация¶
x[0] # Первая строка
x[:, 0] # Первый столбец
x[0, 1] # Элемент
x[x > 0] # Boolean indexing
x[[0, 2, 4]] # Fancy indexing
torch.where(x > 0, x, torch.zeros_like(x)) # Условная замена
# Gather / Scatter
torch.gather(x, dim=1, index=indices)
x.scatter_(dim=1, index=indices, src=values)
GPU¶
# Перенос на GPU
x = x.to('cuda')
x = x.cuda()
# Перенос на CPU
x = x.to('cpu')
x = x.cpu()
# Конвертация в numpy (только CPU!)
arr = x.cpu().numpy()
x = torch.from_numpy(arr)
view() падает после transpose/permute
view() требует contiguous memory layout. После transpose() или permute() тензор становится non-contiguous -- view() упадёт с ошибкой. Решение: x.transpose(0, 1).contiguous().view(-1) или сразу x.transpose(0, 1).reshape(-1). reshape() безопасен -- сам скопирует если нужно, но view() быстрее когда данные уже contiguous.
Autograd¶
# Включить градиенты
x = torch.randn(3, requires_grad=True)
# Вычисление
y = x ** 2
z = y.sum()
z.backward() # Вычислить градиенты
print(x.grad) # dz/dx = 2x
# Отключить градиенты
with torch.no_grad():
y = model(x)
# Или через декоратор
@torch.no_grad()
def inference(model, x):
return model(x)
# Detach от графа
x_detached = x.detach()
# Обнулить градиенты
x.grad.zero_()
optimizer.zero_grad()
Neural Network Layers¶
Основные слои¶
# Полносвязный
nn.Linear(in_features, out_features, bias=True)
# Свёрточные
nn.Conv1d(in_channels, out_channels, kernel_size, stride=1, padding=0)
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0)
nn.ConvTranspose2d(...) # Транспонированная (upsampling)
# Пулинг
nn.MaxPool2d(kernel_size, stride=None, padding=0)
nn.AvgPool2d(kernel_size)
nn.AdaptiveAvgPool2d(output_size) # Global Average Pooling
# Нормализация
nn.BatchNorm1d(num_features)
nn.BatchNorm2d(num_features)
nn.LayerNorm(normalized_shape)
nn.GroupNorm(num_groups, num_channels)
# Dropout
nn.Dropout(p=0.5)
nn.Dropout2d(p=0.5) # Для CNN
# Embedding
nn.Embedding(num_embeddings, embedding_dim, padding_idx=None)
Функции активации¶
nn.ReLU()
nn.LeakyReLU(negative_slope=0.01)
nn.PReLU()
nn.GELU()
nn.Sigmoid()
nn.Tanh()
nn.Softmax(dim=-1)
nn.LogSoftmax(dim=-1)
# Функциональный стиль
import torch.nn.functional as F
F.relu(x)
F.gelu(x)
F.softmax(x, dim=-1)
RNN слои¶
nn.RNN(input_size, hidden_size, num_layers=1, batch_first=True, bidirectional=False)
nn.LSTM(input_size, hidden_size, num_layers=1, batch_first=True, bidirectional=False)
nn.GRU(input_size, hidden_size, num_layers=1, batch_first=True, bidirectional=False)
# Использование LSTM
lstm = nn.LSTM(input_size=100, hidden_size=256, num_layers=2, batch_first=True)
output, (h_n, c_n) = lstm(x) # x: [batch, seq_len, input_size]
# output: [batch, seq_len, hidden_size]
# h_n: [num_layers, batch, hidden_size] - последние hidden states
Transformer слои¶
nn.TransformerEncoderLayer(d_model, nhead, dim_feedforward=2048, dropout=0.1)
nn.TransformerDecoderLayer(d_model, nhead, dim_feedforward=2048, dropout=0.1)
nn.TransformerEncoder(encoder_layer, num_layers)
nn.TransformerDecoder(decoder_layer, num_layers)
nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, batch_first=True)
Создание моделей¶
nn.Sequential¶
model = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 10)
)
nn.Module (рекомендуется)¶
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, output_dim)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(0.5)
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
model = MLP(784, 256, 10)
CNN пример¶
class CNN(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(64 * 8 * 8, 256)
self.fc2 = nn.Linear(256, num_classes)
self.relu = nn.ReLU()
def forward(self, x):
x = self.pool(self.relu(self.conv1(x))) # [B, 32, 16, 16]
x = self.pool(self.relu(self.conv2(x))) # [B, 64, 8, 8]
x = x.flatten(1) # [B, 64*8*8]
x = self.relu(self.fc1(x))
x = self.fc2(x)
return x
Loss Functions¶
# Классификация
nn.CrossEntropyLoss() # Multiclass (logits -> softmax -> CE)
nn.BCELoss() # Binary (после sigmoid)
nn.BCEWithLogitsLoss() # Binary (logits, стабильнее)
nn.NLLLoss() # После LogSoftmax
nn.FocalLoss # нет в PyTorch! см. torchvision.ops.sigmoid_focal_loss
# Регрессия
nn.MSELoss()
nn.L1Loss() # MAE
nn.SmoothL1Loss() # Huber loss
nn.HuberLoss(delta=1.0)
# Специальные
nn.CosineEmbeddingLoss()
nn.TripletMarginLoss()
nn.CTCLoss() # Для OCR/ASR
# Использование
criterion = nn.CrossEntropyLoss(
weight=class_weights, # Для дисбаланса
label_smoothing=0.1, # Сглаживание
ignore_index=-100 # Игнорировать padding
)
loss = criterion(logits, targets)
CrossEntropyLoss принимает logits, НЕ вероятности
nn.CrossEntropyLoss внутри делает log_softmax + nll_loss. Если подать вероятности (после softmax), результат будет неправильным -- loss будет маленьким, но модель не учится. Аналогично nn.BCEWithLogitsLoss внутри делает sigmoid. Частая ошибка: loss = nn.BCELoss()(torch.sigmoid(logits), targets) -- численно нестабильно. Правильно: loss = nn.BCEWithLogitsLoss()(logits, targets).
Optimizers¶
# Базовые
optim.SGD(params, lr=0.01, momentum=0.9, weight_decay=1e-4)
optim.Adam(params, lr=0.001, betas=(0.9, 0.999), weight_decay=0)
optim.AdamW(params, lr=0.001, weight_decay=0.01) # Правильный weight decay
optim.RMSprop(params, lr=0.01)
# Использование
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)
# Разный LR для разных частей
optimizer = optim.Adam([
{'params': model.backbone.parameters(), 'lr': 1e-5},
{'params': model.head.parameters(), 'lr': 1e-3}
])
Learning Rate Schedulers¶
from torch.optim.lr_scheduler import (
StepLR,
MultiStepLR,
ExponentialLR,
CosineAnnealingLR,
ReduceLROnPlateau,
OneCycleLR,
CosineAnnealingWarmRestarts
)
# Step decay
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
# Cosine annealing
scheduler = CosineAnnealingLR(optimizer, T_max=100, eta_min=1e-6)
# One Cycle (для быстрого обучения)
scheduler = OneCycleLR(
optimizer,
max_lr=0.01,
epochs=10,
steps_per_epoch=len(train_loader)
)
# Reduce on plateau
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10)
# В training loop
for epoch in range(epochs):
train(...)
val_loss = validate(...)
scheduler.step() # или scheduler.step(val_loss) для ReduceLROnPlateau
Data Loading¶
Custom Dataset¶
from torch.utils.data import Dataset, DataLoader
class CustomDataset(Dataset):
def __init__(self, data, labels, transform=None):
self.data = data
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
x = self.data[idx]
y = self.labels[idx]
if self.transform:
x = self.transform(x)
return x, y
# DataLoader
train_loader = DataLoader(
dataset,
batch_size=32,
shuffle=True,
num_workers=4,
pin_memory=True, # Быстрее для GPU
drop_last=True # Отбросить неполный последний батч
)
for batch_x, batch_y in train_loader:
batch_x = batch_x.to(device)
batch_y = batch_y.to(device)
...
Training Loop¶
model.train() и model.eval() -- НЕ декорация
model.train() и model.eval() переключают поведение BatchNorm и Dropout. В train() -- BatchNorm считает статистику по батчу, Dropout отключает нейроны. В eval() -- BatchNorm использует running statistics, Dropout отключён. Забыть model.eval() перед inference = нестабильные предсказания (особенно заметно при batch_size=1, когда BatchNorm считает mean/std по одному примеру). Забыть model.train() при возобновлении обучения = Dropout не работает.
def train_epoch(model, loader, criterion, optimizer, device):
model.train()
total_loss = 0
correct = 0
total = 0
for batch_x, batch_y in loader:
batch_x = batch_x.to(device)
batch_y = batch_y.to(device)
optimizer.zero_grad()
outputs = model(batch_x)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
total_loss += loss.item()
_, predicted = outputs.max(1)
total += batch_y.size(0)
correct += predicted.eq(batch_y).sum().item()
return total_loss / len(loader), correct / total
@torch.no_grad()
def evaluate(model, loader, criterion, device):
model.eval()
total_loss = 0
correct = 0
total = 0
for batch_x, batch_y in loader:
batch_x = batch_x.to(device)
batch_y = batch_y.to(device)
outputs = model(batch_x)
loss = criterion(outputs, batch_y)
total_loss += loss.item()
_, predicted = outputs.max(1)
total += batch_y.size(0)
correct += predicted.eq(batch_y).sum().item()
return total_loss / len(loader), correct / total
# Main loop
for epoch in range(num_epochs):
train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)
val_loss, val_acc = evaluate(model, val_loader, criterion, device)
scheduler.step()
print(f"Epoch {epoch+1}: Train Loss={train_loss:.4f}, Val Acc={val_acc:.4f}")
Saving & Loading¶
# Сохранить только веса (рекомендуется)
torch.save(model.state_dict(), 'model_weights.pt')
# Загрузить веса
model = MyModel()
model.load_state_dict(torch.load('model_weights.pt'))
# Сохранить checkpoint (для продолжения обучения)
checkpoint = {
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': loss,
}
torch.save(checkpoint, 'checkpoint.pt')
# Загрузить checkpoint
checkpoint = torch.load('checkpoint.pt')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
start_epoch = checkpoint['epoch']
Полезные приёмы¶
Gradient Clipping¶
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# или
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=1.0)
Mixed Precision Training¶
# PyTorch 2.4+: новый API (torch.amp)
from torch.amp import autocast, GradScaler
scaler = GradScaler()
for x, y in loader:
optimizer.zero_grad()
with autocast(device_type='cuda'): # FP16 forward
outputs = model(x)
loss = criterion(outputs, y)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
torch.cuda.amp устарел в PyTorch 2.4+
from torch.cuda.amp import autocast, GradScaler помечен deprecated. Новый импорт: from torch.amp import autocast, GradScaler. Новый autocast требует device_type='cuda' явно. GradScaler тоже переехал в torch.amp. Старый код работает но выдаёт FutureWarning.
Gradient accumulation: забыли поделить loss
При gradient accumulation (имитация большого batch на малом GPU) частая ошибка: loss.backward() без деления на accumulation_steps. Gradients суммируются, не усредняются -- эффективный learning rate в \(N\) раз больше ожидаемого. Правильно: (loss / accumulation_steps).backward() и optimizer.step() каждые \(N\) шагов. Без этого модель diverges, и баг сложно найти потому что loss на экране выглядит нормально.
Freeze layers¶
# Заморозить все параметры
for param in model.parameters():
param.requires_grad = False
# Разморозить последний слой
for param in model.fc.parameters():
param.requires_grad = True
Weight Initialization¶
def init_weights(m):
if isinstance(m, nn.Linear):
nn.init.xavier_uniform_(m.weight)
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
model.apply(init_weights)
Model Summary¶
Вопросы на интервью¶
Q: Объясните порядок операций в training loop PyTorch.
"forward, backward, step" -- неполный ответ, пропущен критический шаг
"1)
optimizer.zero_grad() -- обнулить gradients (иначе accumulate от прошлого batch). 2) output = model(x) -- forward pass. 3) loss = criterion(output, y) -- вычислить loss. 4) loss.backward() -- backprop, заполнить .grad. 5) (optional) clip_grad_norm_ -- предотвратить gradient explosion. 6) optimizer.step() -- обновить веса. Порядок zero_grad ПЕРЕД forward критичен -- после backward gradients нужны для step."
Q: В чём разница между torch.no_grad() и model.eval()?
"Одно и то же -- отключают обучение" -- принципиально разные механизмы
"
model.eval() меняет поведение МОДУЛЕЙ: BatchNorm переключается на running statistics, Dropout отключается. torch.no_grad() отключает AUTOGRAD -- не строится computation graph, экономит ~50% memory. Для inference нужны ОБА: model.eval() + with torch.no_grad(). Без no_grad() = лишний memory. Без eval() = нестабильные предсказания (BatchNorm по batch=1)."
Q: Как реализовать early stopping в PyTorch?
"Остановить когда val_loss перестал падать" -- нет деталей реализации
"Tracking pattern: сохранять best model state + patience counter. Каждую эпоху: если
val_loss < best_loss - min_delta, сбросить patience и сохранить model.state_dict(). Иначе: patience -= 1. При patience == 0 -- загрузить best checkpoint и остановить. Важно: min_delta (обычно 1e-4) предотвращает реакцию на шум. torch.save(model.state_dict()) -- не torch.save(model), для переносимости."
See Also¶
- NumPy Cheatsheet — tensor operations, broadcasting
- Pandas Cheatsheet — data loading, preprocessing
- Deep Learning Interview Q&A — backprop, optimizers, normalization
- Distributed Training — FSDP, DeepSpeed
- Quantization — INT8, GPTQ, AWQ