← Назад к вопросам

Почему увеличение сложности нейронных сетей чаще достигается путем добавления новых слоев, а не увеличением количества нейронов в каждом слое?

1.7 Middle🔥 241 комментариев
#Машинное обучение

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему увеличение сложности сетей достигается добавлением слоёв, а не нейронов?

Это фундаментальный вопрос глубокого обучения. Дело в том, что глубина (количество слоёв) и ширина (количество нейронов) имеют совершенно разные эффекты на способность модели к обучению и обобщению.

Теория: Universal Approximation Theorem

Universal Approximation Theorem говорит, что одна полносвязная слой с достаточным количеством нейронов может приблизить любую непрерывную функцию:

# Теоретически, эта сеть может приблизить любую функцию:
model = Sequential([
    Dense(1000, activation=relu),  # один широкий слой
    Dense(1)  # выходной слой
])

Однако на практике это работает плохо. Почему?

1. Экспоненциальное улучшение с глубиной

Глубина = экспоненциальное расширение представительной способности

import numpy as np
import matplotlib.pyplot as plt

# Пример: как сеть учится представлять функции

# Узкая но глубокая сеть (несколько слоёв, мало нейронов)
# Может выучить сложные функции композицией простых преобразований
def narrow_deep(x):
    """Композиция простых нелинейных преобразований"""
    for _ in range(10):  # 10 слоёв
        x = np.tanh(x * 2)  # каждый слой применяет нелинейность
    return x

# Широкая но мелкая сеть (много нейронов, один слой)
# Ограничена линейным преобразованием + одна нелинейность
def wide_shallow(x):
    """Один слой = одно линейное преобразование + одна нелинейность"""
    return np.tanh(x @ np.random.randn(1000, 1))

# narrow_deep может выучить намного более сложные функции

2. Иерархическое представление признаков

В глубоких сетях каждый слой строит всё более абстрактные представления:

import tensorflow as tf
from tensorflow.keras import layers

# Пример: распознавание кошек

# Глубокая иерархическая архитектура
model_deep = tf.keras.Sequential([
    layers.Conv2D(32, 3, activation=relu, input_shape=(224, 224, 3)),
    # Слой 1: находит пиксели, края (детали низкого уровня)
    
    layers.Conv2D(64, 3, activation=relu),
    # Слой 2: находит углы, текстуры
    
    layers.Conv2D(128, 3, activation=relu),
    # Слой 3: находит части (уши, нос, глаза)
    
    layers.Conv2D(256, 3, activation=relu),
    # Слой 4: находит объекты (морда, хвост, лапы)
    
    layers.Flatten(),
    layers.Dense(512, activation=relu),
    # Слой 5: высокоуровневые концепции (это кошка?)
    
    layers.Dense(1, activation=sigmoid)  # Ответ
])

# Неправильный подход: один очень широкий слой
model_wide = tf.keras.Sequential([
    layers.Flatten(input_shape=(224, 224, 3)),  # 150K параметров на вход
    layers.Dense(10000, activation=relu),  # один неявный слой
    layers.Dense(1, activation=sigmoid)
])

print(f"Deep model params: {model_deep.count_params():,}")
print(f"Wide model params: {model_wide.count_params():,}")
# Deep: несколько миллионов (управляемо)
# Wide: может быть десятки миллионов (плохо обобщается)

3. Параметры и обобщение

# Empirical Rule для количества параметров:
# - Нужно как минимум 10-100 примеров на каждый параметр для обучения
# - Если параметров > примеров / 10, риск переобучения

import torch
import torch.nn as nn

# Датасет: 10K изображений
dataset_size = 10_000

# ПЛОХАЯ архитектура: один слой с миллионом нейронов
bad_model = nn.Sequential(
    nn.Flatten(),
    nn.Linear(224*224*3, 1_000_000),  # 150M параметров!
    nn.ReLU(),
    nn.Linear(1_000_000, 1)
)
# Параметров: 150M >> 10K примеров
# Результат: тяжелое переобучение, плохая обобщаемость

# ХОРОШАЯ архитектура: много слоёв, меньше нейронов
good_model = nn.Sequential(
    nn.Conv2d(3, 64, 3, padding=1),
    nn.ReLU(),
    nn.Conv2d(64, 128, 3, padding=1),
    nn.ReLU(),
    nn.Conv2d(128, 256, 3, padding=1),
    nn.ReLU(),
    nn.AdaptiveAvgPool2d((1, 1)),
    nn.Flatten(),
    nn.Linear(256, 128),
    nn.ReLU(),
    nn.Linear(128, 1)
)
# Параметров: ~1M << 10K примеров
# Результат: хорошее обобщение

4. Проблема затухающих/взрывающихся градиентов

Глубокие сети требуют осторожности с инициализацией, но это решаемо:

import tensorflow as tf
from tensorflow.keras import layers

# Проблема: широкие мелкие сети нестабильны
model_unstable = tf.keras.Sequential([
    layers.Dense(5000, activation=sigmoid),  # Sigmoid вызывает затухание
    layers.Dense(5000, activation=sigmoid),
    layers.Dense(1)
])

# Решение 1: Правильная инициализация
model_good = tf.keras.Sequential([
    layers.Dense(256, activation=relu, kernel_initializer=he_normal),
    layers.BatchNormalization(),
    layers.Dense(256, activation=relu, kernel_initializer=he_normal),
    layers.BatchNormalization(),
    layers.Dense(256, activation=relu, kernel_initializer=he_normal),
    layers.BatchNormalization(),
    layers.Dense(1)
])

# Решение 2: Skip connections (ResNet)
class ResidualBlock(tf.keras.layers.Layer):
    def __init__(self, units):
        super().__init__()
        self.dense1 = layers.Dense(units, activation=relu)
        self.dense2 = layers.Dense(units)
    
    def call(self, x):
        residual = x
        x = self.dense1(x)
        x = self.dense2(x)
        return x + residual  # Skip connection помогает градиентам

model_skip = tf.keras.Sequential([
    layers.Dense(256, activation=relu),
    ResidualBlock(256),
    ResidualBlock(256),
    ResidualBlock(256),
    layers.Dense(1)
])

5. Эмпирические результаты: Deep vs Wide

import tensorflow as tf
from tensorflow.keras import layers, models

# Эксперимент: обучаем две модели

# Модель A: Глубокая (11 слоёв, 32-64 нейронов)
model_deep = models.Sequential([
    layers.Dense(64, activation=relu, input_shape=(28*28,)),
    *[layers.Dense(64, activation=relu) for _ in range(10)],
    layers.Dense(10, activation=softmax)
])
# Параметров: ~64*64*11 = 45K

# Модель B: Широкая (3 слоя, 1024 нейронов)
model_wide = models.Sequential([
    layers.Dense(1024, activation=relu, input_shape=(28*28,)),
    layers.Dense(1024, activation=relu),
    layers.Dense(10, activation=softmax)
])
# Параметров: ~785K*1024 + 1024*1024 + 1024*10 = ~800K

print(f"Deep: {model_deep.count_params():,} params")
print(f"Wide: {model_wide.count_params():,} params")

# На датасете MNIST (70K примеров):
# Deep обучается быстро, хорошо обобщается (>99% accuracy)
# Wide требует больше памяти, медленнее, хуже обобщается

6. Практический пример: Архитектура ResNet vs VGG

# VGG: много слоёв, одинаковый размер слоёв (широкая)
# 16-19 слоёв, каждый ~512 нейронов
# 138M параметров (тяжело, медленнее)

# ResNet: много слоёв, меньше параметров благодаря skip connections
# 50-152 слоя, прогрессивно растущие
# 25M параметров (легче, быстрее, лучше)

from torchvision import models
import torch

vgg = models.vgg16()  # 138M параметров
resnet = models.resnet50()  # 25.5M параметров

# ResNet ГЛУБЖЕ (50 слоёв vs 16) но МЕНЬШЕ параметров
# Результат: ResNet работает лучше!

7. Теория: Lottery Ticket Hypothesis

Lottery Ticket Hypothesis предполагает, что большие сети содержат подсети («выигрышные билеты»), которые обучаются хорошо. Узкая сеть может найти эту подсеть, но только если она достаточно глубокая:

# Узкая мелкая сеть: может не содержать нужную подсеть
# Узкая глубокая сеть: содержит через композицию нелинейностей
# Широкая мелкая сеть: требует большого количества параметров

Ключевые выводы

  1. Глубина = экспоненциальная способность

    • Каждый слой может делать нелинейное преобразование
    • Композиция слоёв даёт экспоненциальный рост выразительности
  2. Ширина = линейный рост параметров

    • Добавление 1000 нейронов = добавление ~1М параметров
    • Больше параметров = выше риск переобучения
  3. Иерархическое обучение

    • Нижние слои учат низкоуровневые признаки
    • Верхние слои строят абстракции
    • Одна широкая сеть не может это сделать эффективно
  4. Эффективность памяти и вычислений

    • Глубокая узкая сеть: меньше параметров, меньше памяти
    • Может быть быстрее благодаря BatchNormalization
  5. Практика Modern Deep Learning

    • ResNet: 50-152 слоя (глубокая и узкая)
    • Transformer: 12-96 слоёв, 768-3072 размер скрытого слоя
    • Vision Transformer: 12-24 слоя

Итого: Глубина — путь к сложности. Ширина — путь к переобучению.

Почему увеличение сложности нейронных сетей чаще достигается путем добавления новых слоев, а не увеличением количества нейронов в каждом слое? | PrepBro