- Home » 
 
      
								Функции потерь в PyTorch — руководство по обучению нейросетей
Обучение нейросетей — это как настройка хитрого HTTP-сервера: один неправильный конфиг параметр и всё идёт к чертям. Если вы привыкли дебажить Apache или nginx, то с функциями потерь в PyTorch дела обстоят аналогично — нужно знать, какую “потерю” выбрать под конкретную задачу, как её правильно настроить и что делать, когда всё работает не так, как ожидается.
Эта статья — практическое руководство для тех, кто хочет разобраться с loss functions в PyTorch без лишней теории. Мы рассмотрим, как работают основные функции потерь, настроим их пошагово на реальных примерах и увидим, где они могут сломать вашу модель, а где — наоборот, вытащить из безнадёжной ситуации.
Как работают функции потерь — основы без воды
Функция потерь — это метрика, которая показывает, насколько сильно ваша нейросеть “врёт” по сравнению с реальными данными. Представьте, что вы настраиваете мониторинг сервера: чем больше отклонение от нормы, тем хуже. Только здесь мы измеряем не загрузку CPU, а точность предсказаний.
PyTorch предоставляет готовые функции потерь в модуле torch.nn, но понимание их работы критично для выбора правильной стратегии обучения:
- Регрессия — предсказываем числа (как температуру CPU)
 - Классификация — определяем категории (как тип сервера: web, db, cache)
 - Многозадачность — комбинируем несколько задач одновременно
 
Быстрая настройка — пошаговое руководство
Начнём с базовой настройки окружения. Если у вас ещё нет мощного сервера для обучения моделей, можно взять VPS с GPU или выделенный сервер под задачи машинного обучения.
# Установка зависимостей
pip install torch torchvision numpy matplotlib
# Импорты для работы
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
Создадим простую модель для демонстрации:
# Простая нейросеть для тестирования
class SimpleNet(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleNet, self).__init__()
        self.layer1 = nn.Linear(input_size, hidden_size)
        self.layer2 = nn.Linear(hidden_size, output_size)
        self.relu = nn.ReLU()
    
    def forward(self, x):
        x = self.relu(self.layer1(x))
        x = self.layer2(x)
        return x
# Инициализация модели
model = SimpleNet(input_size=10, hidden_size=20, output_size=1)
Функции потерь для регрессии
Когда нужно предсказать числовое значение — используем регрессию. Как мониторинг ресурсов сервера: предсказываем нагрузку, память, трафик.
Mean Squared Error (MSE)
Самая популярная функция потерь для регрессии. Штрафует за большие ошибки гораздо сильнее, чем за маленькие.
# MSE Loss
criterion = nn.MSELoss()
# Пример использования
predictions = torch.tensor([2.5, 3.0, 1.8])
targets = torch.tensor([2.0, 3.2, 1.5])
loss = criterion(predictions, targets)
print(f"MSE Loss: {loss.item():.4f}")
# Обучение с MSE
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(100):
    # Ваши данные
    inputs = torch.randn(32, 10)  # batch_size=32, features=10
    targets = torch.randn(32, 1)   # целевые значения
    
    # Forward pass
    outputs = model(inputs)
    loss = criterion(outputs, targets)
    
    # Backward pass
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    if epoch % 20 == 0:
        print(f'Epoch {epoch}, Loss: {loss.item():.4f}')
Mean Absolute Error (MAE)
Более робастная к выбросам функция потерь. Если в ваших данных есть аномальные значения (как внезапные пики нагрузки), MAE справится лучше.
# MAE Loss
criterion_mae = nn.L1Loss()
# Сравнение MSE и MAE на данных с выбросами
predictions_outlier = torch.tensor([2.5, 3.0, 10.0])  # 10.0 — выброс
targets_outlier = torch.tensor([2.0, 3.2, 1.5])
mse_loss = nn.MSELoss()(predictions_outlier, targets_outlier)
mae_loss = nn.L1Loss()(predictions_outlier, targets_outlier)
print(f"MSE with outlier: {mse_loss.item():.4f}")
print(f"MAE with outlier: {mae_loss.item():.4f}")
Функции потерь для классификации
Классификация — это когда нужно определить категорию. Например, классифицировать тип атаки на сервер или определить тип файла по метаданным.
Cross-Entropy Loss
Золотой стандарт для классификации. Работает с вероятностями и сильно штрафует за уверенные неправильные ответы.
# Cross-Entropy для многоклассовой классификации
num_classes = 5
criterion_ce = nn.CrossEntropyLoss()
# Создаём модель для классификации
class ClassificationNet(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(ClassificationNet, self).__init__()
        self.layer1 = nn.Linear(input_size, hidden_size)
        self.layer2 = nn.Linear(hidden_size, num_classes)
        self.relu = nn.ReLU()
    
    def forward(self, x):
        x = self.relu(self.layer1(x))
        x = self.layer2(x)
        return x
# Инициализация
clf_model = ClassificationNet(input_size=10, hidden_size=20, num_classes=5)
optimizer_clf = optim.Adam(clf_model.parameters(), lr=0.001)
# Пример обучения
for epoch in range(100):
    inputs = torch.randn(32, 10)
    targets = torch.randint(0, 5, (32,))  # классы от 0 до 4
    
    outputs = clf_model(inputs)
    loss = criterion_ce(outputs, targets)
    
    optimizer_clf.zero_grad()
    loss.backward()
    optimizer_clf.step()
    
    if epoch % 20 == 0:
        print(f'Epoch {epoch}, CE Loss: {loss.item():.4f}')
Binary Cross-Entropy
Для бинарной классификации — когда нужно ответить “да” или “нет”. Например, определить, является ли запрос вредоносным.
# Binary Cross-Entropy
criterion_bce = nn.BCEWithLogitsLoss()
# Модель для бинарной классификации
class BinaryClassificationNet(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(BinaryClassificationNet, self).__init__()
        self.layer1 = nn.Linear(input_size, hidden_size)
        self.layer2 = nn.Linear(hidden_size, 1)
        self.relu = nn.ReLU()
    
    def forward(self, x):
        x = self.relu(self.layer1(x))
        x = self.layer2(x)
        return x
# Обучение бинарной классификации
bin_model = BinaryClassificationNet(input_size=10, hidden_size=20)
optimizer_bin = optim.Adam(bin_model.parameters(), lr=0.001)
for epoch in range(100):
    inputs = torch.randn(32, 10)
    targets = torch.randint(0, 2, (32, 1)).float()  # 0 или 1
    
    outputs = bin_model(inputs)
    loss = criterion_bce(outputs, targets)
    
    optimizer_bin.zero_grad()
    loss.backward()
    optimizer_bin.step()
    
    if epoch % 20 == 0:
        print(f'Epoch {epoch}, BCE Loss: {loss.item():.4f}')
Сравнение функций потерь — когда что использовать
| Функция потерь | Тип задачи | Преимущества | Недостатки | Когда использовать | 
|---|---|---|---|---|
| MSE | Регрессия | Гладкая, быстрая | Чувствительна к выбросам | Чистые данные без аномалий | 
| MAE | Регрессия | Робастная к выбросам | Не дифференцируема в 0 | Данные с аномалиями | 
| Huber Loss | Регрессия | Компромисс MSE/MAE | Нужно настраивать delta | Смешанные данные | 
| Cross-Entropy | Многоклассовая классификация | Работает с вероятностями | Может быть нестабильной | Стандартная классификация | 
| BCE | Бинарная классификация | Простая и эффективная | Только для 2 классов | Бинарные задачи | 
Продвинутые функции потерь
Huber Loss
Золотая середина между MSE и MAE. Ведёт себя как MSE для малых ошибок и как MAE для больших.
# Huber Loss
criterion_huber = nn.HuberLoss(delta=1.0)
# Сравнение всех трёх функций потерь
def compare_losses(predictions, targets):
    mse = nn.MSELoss()(predictions, targets)
    mae = nn.L1Loss()(predictions, targets)
    huber = nn.HuberLoss(delta=1.0)(predictions, targets)
    
    print(f"MSE: {mse.item():.4f}")
    print(f"MAE: {mae.item():.4f}")
    print(f"Huber: {huber.item():.4f}")
    print("-" * 20)
# Тест на нормальных данных
normal_pred = torch.tensor([2.1, 2.9, 3.1])
normal_target = torch.tensor([2.0, 3.0, 3.0])
print("Нормальные данные:")
compare_losses(normal_pred, normal_target)
# Тест на данных с выбросами
outlier_pred = torch.tensor([2.1, 2.9, 10.0])
outlier_target = torch.tensor([2.0, 3.0, 3.0])
print("Данные с выбросом:")
compare_losses(outlier_pred, outlier_target)
Focal Loss
Отличная функция для несбалансированных датасетов. Сосредотачивается на сложных примерах.
# Focal Loss (нужно реализовать самостоятельно)
import torch.nn.functional as F
class FocalLoss(nn.Module):
    def __init__(self, alpha=1, gamma=2, reduction='mean'):
        super(FocalLoss, self).__init__()
        self.alpha = alpha
        self.gamma = gamma
        self.reduction = reduction
    
    def forward(self, inputs, targets):
        ce_loss = F.cross_entropy(inputs, targets, reduction='none')
        pt = torch.exp(-ce_loss)
        focal_loss = self.alpha * (1 - pt) ** self.gamma * ce_loss
        
        if self.reduction == 'mean':
            return focal_loss.mean()
        elif self.reduction == 'sum':
            return focal_loss.sum()
        else:
            return focal_loss
# Использование Focal Loss
focal_criterion = FocalLoss(alpha=1, gamma=2)
# Пример с несбалансированными данными
inputs = torch.randn(100, 5)
# Создаём несбалансированный датасет (90% класса 0, 10% остальных)
targets = torch.cat([torch.zeros(90), torch.randint(1, 5, (10,))]).long()
outputs = clf_model(inputs)
focal_loss = focal_criterion(outputs, targets)
ce_loss = criterion_ce(outputs, targets)
print(f"Focal Loss: {focal_loss.item():.4f}")
print(f"Cross-Entropy Loss: {ce_loss.item():.4f}")
Кастомные функции потерь
Иногда стандартные функции не подходят. Вот как создать свою:
# Кастомная функция потерь
class CustomLoss(nn.Module):
    def __init__(self):
        super(CustomLoss, self).__init__()
    
    def forward(self, predictions, targets):
        # Пример: взвешенная комбинация MSE и MAE
        mse_loss = F.mse_loss(predictions, targets)
        mae_loss = F.l1_loss(predictions, targets)
        
        # 70% MSE + 30% MAE
        combined_loss = 0.7 * mse_loss + 0.3 * mae_loss
        return combined_loss
# Использование кастомной функции
custom_criterion = CustomLoss()
# Тестирование
predictions = torch.randn(32, 1)
targets = torch.randn(32, 1)
custom_loss = custom_criterion(predictions, targets)
print(f"Custom Loss: {custom_loss.item():.4f}")
Мониторинг и дебаг функций потерь
Как любой админ знает — мониторинг критичен. То же самое с функциями потерь:
# Функция для мониторинга обучения
def train_with_monitoring(model, criterion, optimizer, train_loader, epochs=100):
    loss_history = []
    
    for epoch in range(epochs):
        epoch_loss = 0.0
        batch_count = 0
        
        for batch_idx, (inputs, targets) in enumerate(train_loader):
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs, targets)
            loss.backward()
            optimizer.step()
            
            epoch_loss += loss.item()
            batch_count += 1
            
            # Логирование каждые 10 батчей
            if batch_idx % 10 == 0:
                print(f'Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.4f}')
        
        avg_loss = epoch_loss / batch_count
        loss_history.append(avg_loss)
        
        # Проверка на переобучение
        if len(loss_history) > 10:
            recent_losses = loss_history[-10:]
            if all(recent_losses[i] <= recent_losses[i+1] for i in range(len(recent_losses)-1)):
                print(f"Возможное переобучение на эпохе {epoch}")
    
    return loss_history
# Визуализация потерь
def plot_loss_history(loss_history):
    plt.figure(figsize=(10, 6))
    plt.plot(loss_history)
    plt.title('Training Loss History')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.grid(True)
    plt.show()
# Создаём простой датасет для демонстрации
class SimpleDataset(torch.utils.data.Dataset):
    def __init__(self, size=1000):
        self.inputs = torch.randn(size, 10)
        self.targets = torch.randn(size, 1)
    
    def __len__(self):
        return len(self.inputs)
    
    def __getitem__(self, idx):
        return self.inputs[idx], self.targets[idx]
# Запуск мониторинга
dataset = SimpleDataset()
train_loader = torch.utils.data.DataLoader(dataset, batch_size=32, shuffle=True)
model = SimpleNet(10, 20, 1)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
loss_history = train_with_monitoring(model, criterion, optimizer, train_loader, epochs=50)
Автоматизация и скрипты
Создадим скрипт для автоматического выбора функции потерь:
# Автоматический выбор функции потерь
class LossSelector:
    def __init__(self):
        self.loss_functions = {
            'regression': {
                'mse': nn.MSELoss(),
                'mae': nn.L1Loss(),
                'huber': nn.HuberLoss(delta=1.0)
            },
            'classification': {
                'ce': nn.CrossEntropyLoss(),
                'bce': nn.BCEWithLogitsLoss(),
                'focal': FocalLoss()
            }
        }
    
    def select_loss(self, task_type, data_characteristics):
        if task_type == 'regression':
            if data_characteristics.get('has_outliers', False):
                return self.loss_functions['regression']['huber']
            else:
                return self.loss_functions['regression']['mse']
        
        elif task_type == 'classification':
            if data_characteristics.get('binary', False):
                return self.loss_functions['classification']['bce']
            elif data_characteristics.get('imbalanced', False):
                return self.loss_functions['classification']['focal']
            else:
                return self.loss_functions['classification']['ce']
    
    def evaluate_losses(self, predictions, targets, task_type):
        results = {}
        
        if task_type == 'regression':
            for name, loss_fn in self.loss_functions['regression'].items():
                results[name] = loss_fn(predictions, targets).item()
        else:
            for name, loss_fn in self.loss_functions['classification'].items():
                try:
                    results[name] = loss_fn(predictions, targets).item()
                except:
                    results[name] = None
        
        return results
# Использование селектора
selector = LossSelector()
# Пример для регрессии
regression_data = {'has_outliers': True}
selected_loss = selector.select_loss('regression', regression_data)
print(f"Выбранная функция потерь: {selected_loss}")
# Сравнение всех функций потерь
predictions = torch.randn(32, 1)
targets = torch.randn(32, 1)
results = selector.evaluate_losses(predictions, targets, 'regression')
for name, value in results.items():
    print(f"{name.upper()}: {value:.4f}")
Интеграция с другими инструментами
Функции потерь можно интегрировать с популярными инструментами мониторинга:
# Интеграция с TensorBoard
from torch.utils.tensorboard import SummaryWriter
class TrainerWithTensorBoard:
    def __init__(self, model, criterion, optimizer, log_dir='./logs'):
        self.model = model
        self.criterion = criterion
        self.optimizer = optimizer
        self.writer = SummaryWriter(log_dir)
        
    def train_epoch(self, train_loader, epoch):
        self.model.train()
        total_loss = 0.0
        
        for batch_idx, (inputs, targets) in enumerate(train_loader):
            self.optimizer.zero_grad()
            outputs = self.model(inputs)
            loss = self.criterion(outputs, targets)
            loss.backward()
            self.optimizer.step()
            
            total_loss += loss.item()
            
            # Логирование в TensorBoard
            global_step = epoch * len(train_loader) + batch_idx
            self.writer.add_scalar('Loss/Train', loss.item(), global_step)
            
            if batch_idx % 10 == 0:
                print(f'Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.4f}')
        
        avg_loss = total_loss / len(train_loader)
        self.writer.add_scalar('Loss/Epoch', avg_loss, epoch)
        
        return avg_loss
    
    def close(self):
        self.writer.close()
# Использование с TensorBoard
# trainer = TrainerWithTensorBoard(model, criterion, optimizer)
# for epoch in range(100):
#     trainer.train_epoch(train_loader, epoch)
# trainer.close()
# Для запуска TensorBoard:
# tensorboard --logdir=./logs
Интересные факты и нестандартные применения
- Метрическое обучение: Функции потерь типа Triplet Loss используются для обучения эмбеддингов, где важно не абсолютное значение, а расстояние между примерами
 - Adversarial Training: В GAN используются специальные функции потерь, которые заставляют две сети соревноваться друг с другом
 - Контрастивное обучение: Такие функции как InfoNCE Loss позволяют обучать модели на неразмеченных данных
 - Регуляризация через потери: Можно добавлять штрафы за сложность модели прямо в функцию потерь
 
# Пример контрастивной функции потерь
class ContrastiveLoss(nn.Module):
    def __init__(self, margin=2.0):
        super(ContrastiveLoss, self).__init__()
        self.margin = margin
    
    def forward(self, output1, output2, label):
        euclidean_distance = F.pairwise_distance(output1, output2)
        loss_contrastive = torch.mean(
            (1 - label) * torch.pow(euclidean_distance, 2) +
            (label) * torch.pow(torch.clamp(self.margin - euclidean_distance, min=0.0), 2)
        )
        return loss_contrastive
# Пример с регуляризацией
class RegularizedLoss(nn.Module):
    def __init__(self, base_criterion, model, l1_lambda=0.01, l2_lambda=0.01):
        super(RegularizedLoss, self).__init__()
        self.base_criterion = base_criterion
        self.model = model
        self.l1_lambda = l1_lambda
        self.l2_lambda = l2_lambda
    
    def forward(self, predictions, targets):
        # Основная потеря
        base_loss = self.base_criterion(predictions, targets)
        
        # L1 регуляризация
        l1_reg = torch.tensor(0.)
        for param in self.model.parameters():
            l1_reg += torch.norm(param, 1)
        
        # L2 регуляризация
        l2_reg = torch.tensor(0.)
        for param in self.model.parameters():
            l2_reg += torch.norm(param, 2)
        
        # Общая потеря
        total_loss = base_loss + self.l1_lambda * l1_reg + self.l2_lambda * l2_reg
        
        return total_loss
# Использование регуляризованной потери
base_criterion = nn.MSELoss()
regularized_criterion = RegularizedLoss(base_criterion, model, l1_lambda=0.01, l2_lambda=0.01)
Полезные ресурсы и документация
- Официальная документация PyTorch Loss Functions
 - Исходный код PyTorch на GitHub
 - Официальные туториалы PyTorch
 
Заключение и рекомендации
Выбор правильной функции потерь — это как выбор подходящего алгоритма балансировки нагрузки: универсального решения нет, но есть проверенные подходы для типичных ситуаций.
Основные рекомендации:
- Для регрессии начинайте с MSE, переходите на Huber при наличии выбросов
 - Для классификации Cross-Entropy — ваш основной выбор
 - При несбалансированных данных используйте Focal Loss или взвешенные варианты
 - Всегда мониторьте процесс обучения и будьте готовы к экспериментам
 - Кастомные функции потерь — мощный инструмент для специфических задач
 
Помните: хорошая функция потерь может спасти даже посредственную архитектуру модели, а плохая — загубить даже самую продуманную. Экспериментируйте, тестируйте и не бойтесь создавать свои варианты под конкретные задачи.
И да, если вам нужен мощный сервер для экспериментов с нейросетями, помните про VPS с GPU — порой это намного выгоднее, чем арендовать время в облачных ML-платформах.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.