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

Почему ReLU лучше Sigmoid для глубоких сетей?

2.2 Middle🔥 141 комментариев
#Глубокое обучение#Машинное обучение

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

ReLU vs Sigmoid: почему ReLU лучше для глубоких сетей?

Это один из самых важных моментов в истории глубокого обучения. Переход от Sigmoid к ReLU позволил обучать намного более глубокие и сложные сети. Но почему?

Определение

Sigmoid — классическая функция активации:

sigmoid(x) = 1 / (1 + exp(-x))

Отображает любой input в диапазон [0, 1]. Гладкая, дифференцируемая везде.

ReLU (Rectified Linear Unit) — функция активации нового поколения:

ReLU(x) = max(0, x)

Очень просто: если x > 0, выводит x. Если x <= 0, выводит 0.

Проблема 1: Vanishing Gradient

Самая критическая проблема Sigmoid — vanishing gradient problem.

Производная Sigmoid:

d(sigmoid)/dx = sigmoid(x) * (1 - sigmoid(x))

Максимальное значение производной = 0.25 (когда x = 0). Минимальное = близко к 0.

Представим 50-слойную сеть:

# При обратном распространении градиент перемножается 50 раз
gradient = 0.25^50  # примерно 10^-30

# Это практически ноль! Веса не обновляются.

В глубоких сетях это означает:

  • Нижние слои учатся очень медленно
  • Иногда не учатся вообще
  • Сеть не может сойтись

ReLU производная:

d(ReLU)/dx = 1 если x > 0
d(ReLU)/dx = 0 если x < 0

Производная = 1! При обратном распространении градиент не умножается на tiny число, он проходит целиком:

gradient = 1^50 = 1

Все слои получают полный градиент.

Практический пример

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-np.clip(x, -500, 500)))

def sigmoid_derivative(x):
    s = sigmoid(x)
    return s * (1 - s)

def relu_derivative(x):
    return (x > 0).astype(float)

x = np.linspace(-5, 5, 100)

plt.figure(figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.plot(x, sigmoid_derivative(x), label='Sigmoid', linewidth=2)
plt.plot(x, relu_derivative(x), label='ReLU', linewidth=2)
plt.xlabel('Input')
plt.ylabel('Gradient')
plt.title('Производная активации')
plt.legend()
plt.grid()

# Градиент через 10 слоёв
plt.subplot(1, 2, 2)
depths = range(1, 51)
sigmoid_grads = [0.25**d for d in depths]
relu_grads = [1.0**d for d in depths]

plt.semilogy(depths, sigmoid_grads, label='Sigmoid', linewidth=2)
plt.semilogy(depths, relu_grads, label='ReLU', linewidth=2)
plt.xlabel('Глубина сети')
plt.ylabel('Градиент (log scale)')
plt.title('Vanishing Gradient Problem')
plt.legend()
plt.grid()
plt.tight_layout()
plt.show()

Проблема 2: Вычислительная эффективность

Sigmoid требует:

  • Вычисления exp(-x) — дорого
  • Деления — дорого
  • Медленнее на GPU

ReLU требует:

  • Просто сравнение с 0
  • Один branch (if)
  • Практически бесплатно на современных GPU

Вот реальная разница в скорости:

import torch
import time

batch_size = 1000000
x = torch.randn(batch_size)

# Sigmoid
start = time.time()
for _ in range(1000):
    y = torch.sigmoid(x)
print(f"Sigmoid: {time.time() - start:.4f} сек")

# ReLU
start = time.time()
for _ in range(1000):
    y = torch.relu(x)
print(f"ReLU: {time.time() - start:.4f} сек")

# Результат: ReLU примерно в 10x раз быстрее

Проблема 3: Интенсивность градиента

Sigmoid выводит значения в [0, 1]. Это означает:

  • Большинство выходов в середине [0.2, 0.8]
  • Выходы редко близки к 0 или 1
  • Это создаёт "мягкие" дневные сигналы — градиенты размазаны

ReLU выводит:

  • 0 (выключено) или положительное число (включено)
  • Более "чёткие" сигналы
  • Сеть может выучить более острые децизионные граници

Проблема 4: Инициализация весов

Для Sigmoid критична правильная инициализация (Xavier initialization):

# Неправильно инициализировать — веса большие
# Тогда sigmoid(большое число) ≈ 1 или 0
# gradient ≈ 0 везде — сеть не учится

ReLU более robust к инициализации. Можно использовать He initialization:

# He initialization для ReLU
std = np.sqrt(2.0 / fan_in)

Недостатки ReLU

1. Dead ReLU problem — если вес инициализирован плохо, и перед ним всегда отрицательные числа, то выход всегда 0:

# Вес застрял на -10
# Всегда: ReLU(любой_input - 10) = 0
# Градиент = 0 — вес никогда не обновляется

Решение: Leaky ReLU

Leaky_ReLU(x) = max(0.01*x, x)  # Позволяет малый градиент для x<0

2. Не ограничен сверху — может выводить очень большие числа

# Sigmoid: выход всегда [0, 1] — численно стабилен
# ReLU: выход может быть [0, бесконечность]
# Может быть нестабильно в рекуррентных сетях (RNN)

Практическое сравнение

import torch
import torch.nn as nn
from torch.optim import Adam

# Модель с Sigmoid
model_sigmoid = nn.Sequential(
    nn.Linear(10, 100),
    nn.Sigmoid(),
    nn.Linear(100, 100),
    nn.Sigmoid(),
    nn.Linear(100, 1),
    nn.Sigmoid()
)

# Модель с ReLU
model_relu = nn.Sequential(
    nn.Linear(10, 100),
    nn.ReLU(),
    nn.Linear(100, 100),
    nn.ReLU(),
    nn.Linear(100, 1),
    nn.Sigmoid()  # Для бинарной классификации
)

# Training: ReLU будет учиться намного быстрее!
# Особенно заметно с глубокими сетями (100+ слоёв)

Результаты исторически

Этот переход 2011-2012 годов (Hinton и др.) был ключевым:

  • До: максимум 5-6 слоёв, Sigmoid
  • После: 100+ слоёв, ReLU

Это позволило:

  • AlexNet (2012) — победить ImageNet
  • VGG, ResNet, DenseNet
  • Трансформеры (тоже ReLU или GELU)
  • Все современные LLM

Современные варианты

  • ReLU — стандарт, baseline
  • Leaky ReLU — избегает Dead ReLU
  • ELU (Exponential Linear Unit) — smooth, лучше CIFAR-10
  • GELU (Gaussian Error Linear Unit) — в трансформерах, smooth ReLU
  • Swish / SiLU — x * sigmoid(x), более гладкий

Вывод: ReLU победил Sigmoid потому что:

  1. Решает vanishing gradient problem
  2. В 10x раз быстрее
  3. Проще инициализировать для глубоких сетей
  4. Эмпирически работает лучше

Это не только деталь архитектуры — это революция, позволившая глубокое обучение существовать вообще.