Regularizace — Dropout, L1/L2 a boj s overfittingem
Overfitting je jeden z nejčastějších problémů při trénování neuronových sítí. Když se model naučí trénovací data příliš dokonale, ztrácí schopnost generalizace na nová data. Regularizační techniky jako Dropout, L1 a L2 regularizace jsou osvědčené nástroje pro řešení tohoto problému.
Co je to overfitting a proč škodí
Overfitting je jedním z nejčastějších problémů v machine learningu. Model se naučí tréninková data příliš dobře – zapamatuje si každý detail včetně náhodného šumu, ale pak selhává na nových datech. Představte si studenta, který se nazpaměť naučí učebnici, ale nerozumí principům a u zkoušky propadne.
Regularizace je soubor technik, které tomuto problému předcházejí. Přidávají do tréninku "tření", které brání modelu v přílišné specializaci na tréninková data. Podívejme se na tři nejdůležitější techniky.
Dropout – náhodné "vypínání" neuronů
Dropout je elegantní technika, která během tréninku náhodně deaktivuje část neuronů. Je to jako kdyby jste při učení náhodně zavírali oči nebo si zacpávali uši – nutíte mozek spoléhat na různé kombinace vstupů.
Jak dropout funguje
Během forward pass náhodně nastavíme vybrané neurony na nulu s pravděpodobností p (typicky 0,2-0,5). Zbývající neurony vynásobíme faktorem 1/(1-p), aby se zachovala celková síla signálu.
import torch
import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size, dropout_rate=0.3):
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.fc2 = nn.Linear(hidden_size, hidden_size)
self.fc3 = nn.Linear(hidden_size, output_size)
self.dropout = nn.Dropout(dropout_rate)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.dropout(x) # Dropout po aktivaci
x = self.relu(self.fc2(x))
x = self.dropout(x)
x = self.fc3(x)
return x
# Použití
model = SimpleNet(784, 256, 10, dropout_rate=0.4)
model.train() # Důležité: dropout funguje jen v train módu
Klíčové je pamatovat, že dropout používáme pouze během tréninku. Při inferencí voláme model.eval(), čímž dropout deaktivujeme.
L1 a L2 regularizace – trestání velkých vah
L1 a L2 regularizace přidávají do loss funkce penalty za velikost vah modelu. Princip je jednoduchý: velké váhy často vedou k overfittingu, takže je "potrestáme".
L2 regularizace (Weight Decay)
L2 regularizace přidává do loss funkce člen λ∑w², kde λ je regularizační koeficient. Trestá kvadraticky velké váhy, ale nezahání je úplně k nule.
import torch.optim as optim
# L2 regularizace přes weight_decay v optimizeru
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
# Nebo manuálně v loss funkci
def l2_regularization(model, lambda_reg=1e-4):
l2_reg = 0
for param in model.parameters():
l2_reg += torch.norm(param, p=2) ** 2
return lambda_reg * l2_reg
# Použití v tréninku
criterion = nn.CrossEntropyLoss()
output = model(inputs)
loss = criterion(output, targets) + l2_regularization(model)
L1 regularizace – vytváření sparse modelů
L1 regularizace používá λ∑|w| a má zajímavou vlastnost – dokáže vynulovat méně důležité váhy, čímž vytváří sparse (řídké) modely.
def l1_regularization(model, lambda_reg=1e-4):
l1_reg = 0
for param in model.parameters():
l1_reg += torch.norm(param, p=1)
return lambda_reg * l1_reg
# Kombinovaná L1 + L2 regularizace (Elastic Net)
def elastic_net_regularization(model, l1_lambda=1e-4, l2_lambda=1e-4):
l1_reg = sum(torch.norm(p, p=1) for p in model.parameters())
l2_reg = sum(torch.norm(p, p=2) ** 2 for p in model.parameters())
return l1_lambda * l1_reg + l2_lambda * l2_reg
Praktické tipy pro použití regularizace
Ladění hyperparametrů
Síla regularizace se musí pečlivě vyladit. Příliš slabá nepomůže, příliš silná model "udusí" a nebude se učit.
- Dropout rate: Začněte s 0,2-0,3 pro skryté vrstvy, 0,1-0,2 pro vstupní vrstvu
- Weight decay: Typicky 1e-4 až 1e-6, závisí na velikosti datasetu
- L1 regularizace: Obvykle slabší než L2, začněte s 1e-5
Kombinování technik
class RegularizedNet(nn.Module):
def __init__(self, input_size, hidden_sizes, output_size, dropout_rates):
super().__init__()
self.layers = nn.ModuleList()
self.dropouts = nn.ModuleList()
# Vstupní vrstva
prev_size = input_size
for i, (hidden_size, dropout_rate) in enumerate(zip(hidden_sizes, dropout_rates)):
self.layers.append(nn.Linear(prev_size, hidden_size))
self.dropouts.append(nn.Dropout(dropout_rate))
prev_size = hidden_size
# Výstupní vrstva (bez dropout)
self.output_layer = nn.Linear(prev_size, output_size)
self.relu = nn.ReLU()
def forward(self, x):
for layer, dropout in zip(self.layers, self.dropouts):
x = self.relu(layer(x))
x = dropout(x)
return self.output_layer(x)
# Model s postupně se snižujícím dropout
model = RegularizedNet(
input_size=784,
hidden_sizes=[512, 256, 128],
output_size=10,
dropout_rates=[0.2, 0.3, 0.4] # Vyšší dropout v hlubších vrstvách
)
# Optimizer s weight decay
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
Monitoring účinnosti
Sledujte rozdíl mezi training a validation loss. Regularizace funguje, když se tento rozdíl zmenšuje, aniž by se validation loss výrazně zhoršovala.
# Sledování během tréninku
train_losses = []
val_losses = []
for epoch in range(num_epochs):
# Trénink
model.train()
train_loss = 0
for batch in train_loader:
# ... standardní tréninkový cyklus
pass
# Validace
model.eval()
val_loss = 0
with torch.no_grad():
for batch in val_loader:
# ... validace bez gradientů
pass
train_losses.append(train_loss)
val_losses.append(val_loss)
print(f"Epoch {epoch}: Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}")
Shrnutí
Regularizace je nezbytná pro trénink robustních modelů. Dropout náhodně deaktivuje neurony a nutí model učit se redundantní reprezentace. L1/L2 regularizace trestají velké váhy a podporují jednodušší modely. Klíčem k úspěchu je správné vyladění hyperparametrů a kombinování technik. Pamatujte: lepší je mírně underfit model, který generalizuje, než perfektně overfit model, který selhává v praxi.