Перейти к содержанию
Публикация AiManual

Функции активации нейросетей: как работают сигмоида, ReLU, tanh и ступенчатая функция

Разбираем, что делает функция активации в искусственном нейроне и чем отличаются ступенчатая, линейная, сигмоида, tanh и ReLU: диапазоны выхода, производные, за

Коротко

Что будет в материале

  1. 01

    Что делает функция активации в искусственном нейроне

  2. 02

    Ступенчатая функция активации: бинарный выход 0/1 и его пределы

  3. 03

    Линейная функция активации: постоянный градиент и потеря смысла многослойности

  4. 04

    Сигмоида: нелинейность, диапазон [0,1] и затухающий градиент

Что делает функция активации в искусственном нейроне

Искусственный нейрон складывает входы с весами, прибавляет смещение (bias) и пропускает результат через функцию активации. Первые два шага дают линейную комбинацию, третий превращает её в выход. Вся арифметика укладывается в две строки:

Y = w1*x1 + w2*x2 + ... + wn*xn + b
A = f(Y)

Работа f в том, чтобы решить, считать ли нейрон активированным и какое значение он передаёт дальше. Разбор функций активации описывает это как основную операцию нейрона: посчитать взвешенную сумму на входах, добавить смещение и решить, использовать значение дальше или отбросить его.

Без активационной функции Y остаётся любым числом от минус бесконечности до плюс бесконечности. У нейрона нет границы, после которой он срабатывает, потому что границы не существует. Активация задаёт эту границу, диапазон выхода и форму производной, которая пойдёт назад при обучении. Отсюда и разница в сходимости: две сети одинаковой архитектуры с разными активациями учатся с разной скоростью.

Зачем нужна нелинейность: что ломается без функции активации

Возьмите два линейных слоя подряд: h = W₁x + b₁ и y = W₂h + b₂. Подстановка даёт y = (W₂W₁)x + (W₂b₁ + b₂). Ровно то же самое посчитает один слой с матрицей W₂W₁ и смещением W₂b₁ + b₂. Поставьте сто слоёв и получите одно линейное преобразование, только матрицу считать дольше.

Линейная активация A = c·Y даёт постоянный градиент c: ошибка не гаснет и не взрывается, обучение идёт предсказуемо. Плата за стабильность - сеть не может описать нелинейные зависимости. Складывать и умножать можно сколько угодно, изгиба в решении не появится.

Классическая иллюстрация - XOR: одной прямой классы не разделить, а сети с нелинейной активацией хватает одного скрытого слоя. Поэтому в скрытых слоях нужна функция с нелинейным профилем, а конкретный выбор сводится к компромиссу между формой градиента и стоимостью вычислений.

Ступенчатая функция активации: бинарный выход 0/1 и его пределы

Самая старая и простая: A = 1, если Y больше границы, иначе A = 0. При границе 0 нейрон выдаёт 1 при положительном Y и 0 в остальных случаях. В первоисточнике она определена именно так: A = 1 при Y > граница, иначе A = 0.

def step(z, threshold=0.0):
    return 1.0 if z > threshold else 0.0

print([step(z) for z in (-2.0, -0.01, 0.0, 0.5, 3.0)])
# [0.0, 0.0, 0.0, 1.0, 1.0]

Для бинарного классификатора, который отвечает «да» или «нет» на вопрос об активации, ступенька подходит идеально: выход ровно 1 или ровно 0, никаких полутонов.

На этом достоинства заканчиваются. Производная равна нулю везде, кроме точки излома, где она не определена. Градиентный спуск по такому сигналу не работает: веса нечем обновлять. В PyTorch отдельного слоя nn.Step нет, при необходимости порог собирают вручную: (x > 0).float() или torch.heaviside(x, torch.zeros_like(x)).

Почему при нескольких классах ступенчатая функция ломается

Классов три: класс1, класс2, класс3. У каждого свой выход со ступенькой. Объект похож на два класса сразу, оба нейрона выдают 1. Каким должен быть ответ сети? Однозначного ответа нет, и автор разбора формулирует ту же проблему: при нескольких активированных нейронах возникает вопрос, какой класс в итоге должен получиться.

Чтобы класс определялся однозначно, активироваться должен ровно один нейрон, остальные обязаны выдать 0. Такой режим обучения даётся тяжелее, сходимость хуже.

Промежуточные значения снимают проблему: «активирован на 50%», «активирован на 20%». Когда активны несколько нейронов, выбирают наибольшее значение. Выбирать лучше через softmax, а не max: softmax нормирует выходы всех нейронов так, что сумма равна 1, и каждое значение читается как вероятность класса.

Линейная функция активации: постоянный градиент и потеря смысла многослойности

Формула A = c·Y, чаще всего просто A = Y. Производная постоянна и равна c, поэтому градиент не зависит от входа и не насыщается. Обучение стабильное, но каждый слой остаётся линейным преобразованием.

Композиция линейных слоёв сводится к одному: глубина не даёт новых возможностей, только добавляет параметры и время на вычисления. Двадцать слоёв с линейной активацией эквивалентны одному, и никакая инициализация это не исправит.

Место линейной активации - выходной слой регрессии. Прогноз цены, температуры или спроса не ограничен диапазоном, и сигмоида с tanh здесь только испортят результат. В PyTorch отсутствие активации записывают явно:

import torch.nn as nn

regressor = nn.Sequential(
    nn.Linear(32, 64),
    nn.ReLU(),
    nn.Linear(64, 1),
    nn.Identity(),  # линейный выход для регрессии
)

Разница между осознанным Identity на выходе и забытой активацией в скрытом слое принципиальна: во втором случае сеть теряет нелинейность целиком.

Сигмоида: нелинейность, диапазон [0,1] и затухающий градиент

σ(z) = 1 / (1 + e^(−z)). Гладкая, монотонная, дифференцируемая, сжимает любое число в интервал (0, 1). Отсюда её главное применение: выход бинарного классификатора, где значение читается как вероятность класса.

import math
import torch
import torch.nn as nn

def sigmoid(z):
    return 1.0 / (1.0 + math.exp(-z))

x = torch.tensor([-4.0, -1.0, 0.0, 1.0, 4.0])
print(nn.Sigmoid()(x))
# tensor([0.0180, 0.2689, 0.5000, 0.7311, 0.9820])

Производная: σ'(z) = σ(z)·(1 − σ(z)). Максимум достигается при z = 0 и равен 0,25, а при насыщении, когда σ(z) близка к 0 или 1, производная стремится к нулю.

Почему производная сигмоиды не больше 0,25 и что это значит на практике

При обратном распространении градиент проходит через каждый слой и умножается на локальную производную. Если на каждом шаге множитель не больше 0,25, то через десять слоёв останется порядка 0,25¹⁰ ≈ 9,5·10⁻⁷ от исходного сигнала. Градиент практически исчезает, и ранние слои почти не обновляются. Механика этого умножения подробно разобрана в статье про обратное распространение ошибки.

Симптомы на практике: loss падает медленно, веса первых слоёв меняются в разы слабее, чем последних, а увеличение числа слоёв не улучшает, а ухудшает результат.

Выход сигмоиды всегда положителен и не центрирован вокруг нуля, что добавляет зигзагов при оптимизации. Поэтому в скрытых слоях глубоких сетей её сегодня берут редко, а на выходе бинарного классификатора она остаётся стандартом.

Гиперболический тангенс (tanh): диапазон (−1,1) и более крутой градиент

tanh(z) = (e^z − e^(−z)) / (e^z + e^(−z)). Диапазон (−1, 1), выход центрирован вокруг нуля: отрицательные входы дают отрицательные значения. Это заметно упрощает оптимизацию по сравнению с сигмоидой, у которой все выходы положительные.

Производная: tanh'(z) = 1 − tanh²(z). Максимум равен 1 при z = 0, то есть градиент в четыре раза круче, чем у сигмоиды в её лучшей точке.

import torch
import torch.nn as nn

x = torch.tensor([-2.0, -0.5, 0.0, 0.5, 2.0])
print(nn.Tanh()(x))
# tensor([-0.9640, -0.4621,  0.0000,  0.4621,  0.9640])

Проблема насыщения никуда не делась: при больших |z| производная стремится к нулю, и затухающий градиент возвращается. На десяти слоях tanh держится лучше сигмоиды, на сотне - уже нет.

Сильные стороны tanh раскрываются в рекуррентных сетях, где значения нужно хранить в диапазоне около нуля: LSTM и GRU построены на связке tanh и сигмоиды. Эти активации пробуют и заменять: в разборе собственной gated RNN вместо LSTM и GRU автор заменил tanh и сигмоиду на softsign, и результат оказался чувствительным к seed и потребовал правок формул.

ReLU: разреженная активация, простота и риск «умирающих» нейронов

ReLU(z) = max(0, z). Всё, что ниже нуля, обнуляется, всё положительное проходит без изменений. Производная равна 1 при z > 0 и 0 при z < 0: в положительной области насыщения нет, градиент не гаснет даже на глубоких сетях.

import torch
import torch.nn as nn

def relu(z):
    return max(0.0, z)

x = torch.tensor([-3.0, -0.5, 0.0, 0.5, 3.0])
print(nn.ReLU()(x))
# tensor([0., 0., 0., 0.5000, 3.0000])

Вычислять ReLU дешевле, чем сигмоиду или tanh: это сравнение с нулём, без экспонент. Отрицательные входы дают ноль, поэтому активации получаются разреженными: часть нейронов в конкретном проходе не участвует, и это экономит вычисления.

Обратная сторона - умирающие нейроны. Если нейрон ушёл в область z < 0 и производная там нулевая, градиент через него не течёт, веса не обновляются, и нейрон выпадает из обучения навсегда. Типичные причины: слишком большой learning rate, из-за которого веса улетают в отрицательную зону, и неудачная инициализация. Диагностируется просто: доля нулевых активаций по слою растёт от эпохи к эпохе.

При неизвестной природе функции ReLU - рабочая стартовая точка для скрытых слоёв: дешёвая, без насыщения в плюсе и хорошо обучается на практике.

Вариации ReLU для борьбы с нулевым градиентом

Leaky ReLU оставляет маленький наклон для отрицательных входов, обычно 0,01, поэтому градиент там не обнуляется. ELU сглаживает переход у нуля и даёт отрицательные значения на отрицательных входах, приближаясь к −1 асимптотически. GELU заменяет жёсткий излом гладкой кривой и часто встречается в трансформерных архитектурах.

import torch.nn as nn

leaky = nn.LeakyReLU(negative_slope=0.01)
elu = nn.ELU(alpha=1.0)
gelu = nn.GELU()

Порядок действий простой: обучили сеть на ReLU, увидели, что доля мёртвых нейронов растёт или loss встал, попробовали Leaky ReLU. Если задача связана с трансформерами, разумно сразу смотреть в сторону GELU.

Сравнение функций активации: таблица и ключевые различия

ФункцияФормулаДиапазон выходаМаксимум производнойГлавная проблемаГде применять
Ступенчатая1 при Y > граница, иначе 0{0, 1}0 (в точке излома не определена)Нулевой градиент, неоднозначность при нескольких классахБинарный порог, учебные примеры
Линейнаяc·Y(−∞, +∞)c (постоянна)Сеть вырождается в одно линейное преобразованиеВыход регрессии
Сигмоида1 / (1 + e^(−z))(0, 1)0,25Затухающий градиент, выход не центрированВыход бинарной классификации
tanh(e^z − e^(−z)) / (e^z + e^(−z))(−1, 1)1Насыщение при больших |z|RNN, LSTM, небольшие сети
ReLUmax(0, z)[0, +∞)1Умирающие нейроны при z < 0Скрытые слои глубоких сетей

Таблица сводит выбор к трём параметрам: насколько быстро гаснет градиент, какие значения выходят из нейрона и сколько стоит вычисление. Сигмоида и tanh проигрывают по первому пункту, ступенька по всем трём, ReLU платит за скорость риском мёртвых нейронов.

Как выбрать функцию активации: практические ориентиры

Скрытые слои глубоких сетей прямого распространения: ReLU как первый кандидат, Leaky ReLU или GELU при проблемах с нулевым градиентом. Рекуррентные архитектуры: tanh внутри ячейки, сигмоида на гейтах. Небольшие сети и задачи, где важна гладкость выхода: tanh.

Активация выходного слоя определяется типом задачи, а не модой. Ошибка здесь ломает и интерпретацию, и функцию потерь: ReLU на выходе классификатора выдаст нули вместо вероятностей, а сигмоида в регрессии обрежет прогноз диапазоном (0, 1).

Активация выходного слоя: как тип задачи определяет выбор

  • Бинарная классификация: сигмоида, один выход, вероятность от 0 до 1.
  • Многоклассовая классификация с взаимоисключающими классами: softmax, сумма выходов равна 1.
  • Несколько независимых меток: сигмоида на каждом выходе отдельно.
  • Регрессия: линейный выход или nn.Identity().
import torch.nn as nn

model = nn.Sequential(
    nn.Linear(64, 128),
    nn.ReLU(),
    nn.Linear(128, 32),
    nn.ReLU(),
    nn.Linear(32, 1),
    nn.Sigmoid(),  # вероятность для бинарной классификации
)

# для многоклассовой задачи последний блок выглядит иначе:
classifier = nn.Sequential(
    nn.Linear(64, 128),
    nn.ReLU(),
    nn.Linear(128, 10),
    # softmax применяют через nn.CrossEntropyLoss, который ждёт логиты
)

С nn.CrossEntropyLoss softmax отдельным слоем не ставят: функция потерь уже включает нормировку и ожидает сырые логиты. Лишний softmax перед ней только ухудшит численную устойчивость.

Как активации влияют на обучение и сходимость: что проверять в своей сети

Обучение стоит, loss почти не меняется: первым делом проверяют насыщение сигмоиды или tanh и мёртвые нейроны ReLU. Быстрый тест - посмотреть на распределение активаций по слоям после нескольких эпох. Если у сигмоиды значения прижаты к 0 или 1, а у tanh к ±1, градиент там близок к нулю.

Доля нулевых активаций после ReLU растёт от слоя к слою и приближается к единице: нейроны умирают, стоит снизить learning rate, проверить инициализацию или перейти на Leaky ReLU. Рост нормы градиента, наоборот, указывает на слишком большой шаг оптимизации.

Полезно логировать по каждому слою три числа: среднее активаций, их стандартное отклонение и долю нулей. Резкий сдвиг любого из них между эпохами сигнализирует о проблеме раньше, чем итоговая метрика. Механику вычисления самих градиентов и то, как они распространяются по слоям, разбирает материал про цепное правило и повторное использование вычислений.

Практический вывод короткий: ReLU в скрытых слоях как точка отсчёта, сигмоида или softmax на выходе под тип задачи, tanh в рекуррентных блоках. Дальше смотрят на кривые обучения и меняют функцию только под конкретный симптом, а не ради новизны.

Подписаться на канал