Как глубина нейронной сети влияет на ее обучение?

«Как глубина нейронной сети влияет на ее обучение?» — вопрос из категории Нейронные сети и Deep Learning, который задают на 26% собеседований Data Scientist / ML Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Глубина сети (количество скрытых слоев) — это ключевой параметр, который определяет ее способность к обучению сложным иерархическим представлениям, но требует тщательного баланса.

Преимущества увеличения глубины:

  • Иерархия признаков: Каждый последующий слой комбинирует признаки предыдущего, выявляя все более абстрактные закономерности. В CNN первый слой может детектировать края, второй — текстуры и простые формы, а более глубокие — целые объекты (глаза, уши, колеса).
  • Выразительная мощность: Теорема универсальной аппроксимации гласит, что даже неглубокая сеть может аппроксимировать любую функцию, но глубокая сеть сделает это с экспоненциально меньшим числом параметров и более эффективно для многих реальных задач.

Проблемы и вызовы глубоких сетей:

  • Исчезающие/взрывающиеся градиенты: При обратном распространении ошибки через множество слоев градиенты могут стать либо астрономически большими, либо невероятно малыми, что останавливает обучение. Решение: Использование функций активации с неисчезающими градиентами (ReLU, Leaky ReLU), техник инициализации весов (He, Xavier) и архитектур с skip-connections (ResNet).
  • Переобучение: Глубокая сеть с большим числом параметров легко запоминает шум в тренировочных данных. Решение: Регуляризация (Dropout, L2), аугментация данных и использование больших датасетов.
  • Вычислительная сложность и время обучения: Увеличение глубины напрямую влияет на потребление памяти и время обучения.

Пример сравнения архитектур в PyTorch:

import torch.nn as nn

# Мелкая сеть (3 линейных слоя)
class ShallowNet(nn.Module):
    def __init__(self, input_size=784, num_classes=10):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Linear(input_size, 256),
            nn.ReLU(),
            nn.Linear(256, num_classes)
        )
    def forward(self, x):
        return self.layers(x)

# Глубокая сеть с Residual Connection (6 слоев, как в ResNet блоке)
class DeepResidualBlock(nn.Module):
    def __init__(self, in_features, out_features):
        super().__init__()
        self.linear1 = nn.Linear(in_features, out_features)
        self.relu = nn.ReLU()
        self.linear2 = nn.Linear(out_features, out_features)
        # Skip-connection: если размерности не совпадают, нужен проекционный слой
        self.shortcut = nn.Linear(in_features, out_features) if in_features != out_features else nn.Identity()
    def forward(self, x):
        residual = self.shortcut(x)
        out = self.linear1(x)
        out = self.relu(out)
        out = self.linear2(out)
        out += residual  # Ключевой элемент: добавление входа к выходу
        out = self.relu(out)
        return out

Вывод: Современные глубокие архитектуры (ResNet, Transformer, DenseNet) стали возможны именно благодаря методам, которые решают проблемы обучения глубоких сетей, таким как residual connections и layer normalization.