Прямое и обратное распространение

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

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


Что такое прямое распространение и обратное распространение?

Прежде чем углубляться в детали, давайте сначала сформируем понимание на макроуровне.

Представьте, что вы учите ребёнка распознавать кошек и собак. Вы показываете ему картинку (входные данные), и на основе знаний, уже имеющихся в его мозге (параметры сети, то есть веса и смещения), он делает вывод, а затем говорит вам, что это кошка (выход). Этот процесс: посмотреть на картинку -> обработка мозгом -> выдача ответа — этопрямое распространение。

Но суждение ребёнка может быть ошибочным. Вы говорите ему: нет, это собака. Разница между правильным ответом и ответом ребёнка — этоошибка. Ребёнку нужно поразмышлять на основе этой ошибки: какие знания (параметры) в моём мозге привели к этому неверному суждению? Как мне их скорректировать, чтобы в следующий раз распознать правильно? Этот процесс корректировки знаний от конца к началу на основе ошибки — этообратное распространение。

В нейронных сетях:

  • Прямое распространение: процесс, в котором данные идут от входного слоя, проходят через скрытые слои и в конечном итоге достигают выходного слоя, выдавая результат прогноза. Это процессвывода.
  • Обратное распространение: на основе ошибки между результатом прогноза, полученным при прямом распространении, и истинным значением, начиная с выходного слоя, в обратном порядке вычисляет, слой за слоем, «вклад» каждого параметра (веса и смещения) в общую ошибку (то есть градиент), и соответствующим образом обновляет параметры. Этопроцесс обучения.

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


Прямое распространение: путь вывода нейронных сетей

Прямое распространение — это прямой канал, через который нейронные сети делают прогнозы. Давайте разберёмся на примере простой трёхслойной нейронной сети (входной слой, один скрытый слой, выходной слой).

Ключевые понятия и вычисления

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

  • Входной слой: один нейрон, получающийx。
  • Скрытый слой: один нейрон, с весомw1и смещениемb1。
  • Выходной слой: один нейрон, с весомw2и смещениемb2, выводящий прогнозируемую цену жильяy_pred。

Вычисление прямого распространения делится на два этапа:

1. Вычисление скрытого слоя: входxи весw1, смещениеb1объединяются, затем проходят через функцию активации (например, Sigmoid, обозначаемую какσ), формируя выход скрытого слояa1。

z1 = w1 * x + b1
a1 = σ(z1) = 1 / (1 + exp(-z1))
  • z1является результатом линейного преобразования.
  • a1— это выход после нелинейной активации, что даёт сети возможность изучать сложные закономерности.

2. Вычисление выходного слоя: выход скрытого слояa1в качестве входа, с весом выходного слояw2, смещениеb2объединяются, чтобы получить окончательное предсказаниеy_pred. Здесь для простоты предположим, что выходной слой не использует функцию активации (т. е. линейный выход).

y_pred = w2 * a1 + b2

Пример кода: ручная реализация прямого распространения

Пример

import numpy as np

def sigmoid(x):
    """Функция активации Sigmoid"""
    return 1 / (1 + np.exp(-x))

# Инициализация параметров сети (обычно случайная инициализация, здесь указаны значения для демонстрации)
w1, b1 = 2.0, -1.0  # Параметры скрытого слоя
w2, b2 = 1.5, 0.5   # Параметры выходного слоя

def forward_pass(x):
    """Выполнить одно прямое распространение"""
    # Вычисление скрытого слоя
    z1 = w1 * x + b1
    a1 = sigmoid(z1)  # Применить функцию активации
   
    # Вычисление выходного слоя
    y_pred = w2 * a1 + b2  # Линейный выход
   
    # Вернуть промежуточные результаты и итоговое предсказание для дальнейшего понимания
    return {'z1': z1, 'a1': a1, 'y_pred': y_pred}

# Предположим, площадь дома равна 3 (единица измерения: 100 квадратных метров)
x_input = 3.0
result = forward_pass(x_input)
print(f"Вход x = {x_input}")
print(f"Линейный выход скрытого слоя z1 = w1*x + b1 = {result['z1']:.4f}")
print(f"Выход активации скрытого слоя a1 = sigmoid(z1) = {result['a1']:.4f}")
print(f"Final predicted housing price y_pred = w2*a1 + b2 = {result['y_pred']:.4f}")

Пример вывода:

输入 x = 3.0
隐藏层线性输出 z1 = w1*x + b1 = 5.0000
隐藏层激活输出 a1 = sigmoid(z1) = 0.9933
最终预测房价 y_pred = w2*a1 + b2 = 1.9899

Этотy_pred— это предсказанная сетью цена дома площадью 3. Но, очевидно, это предсказанное значение (основанное на параметрах, которые мы задали произвольно) вероятно далеко от реальной цены дома. Как измерить этот разрыв и улучшить его? Для этого требуетсяфункция потерьи предстоящееобратное распространение。


Функция потерь: мерило для оценки хорошего или плохого

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

Распространённые функции потерь:

  • Среднеквадратичная ошибка: подходит для задач регрессии (например, предсказание цен на жильё, температуры).Loss = (1/N) * Σ (y_true - y_pred)^2
  • Кросс-энтропийная потеря: подходит для задач классификации (например, классификация изображений, обнаружение спама).

Взяв в качестве примера среднеквадратичную ошибку, для одного образца:

Loss = (y_true - y_pred)^2

Наша цель — настроитьw1, b1, w2, b2, чтобы сделать этоLossзначение как можно меньше.


Обратное распространение: движок обучения нейронных сетей

Обратное распространение — это основной алгоритм обучения в глубоком обучении. Его суть —правило цепочки, эффективно применяемое в нейронных сетях. Цель — вычислить функцию потерьLотносительно каждого параметра (w1, b1, w2, b2)частные производные (градиенты), i.e.,∂L/∂w1, ∂L/∂b1и т. д. Эти градиенты указывают, в каком направлении и на какую величину следует скорректировать каждый параметр, чтобы уменьшить потери.

Понимание градиентов: направление и величина шага при спуске

Представьте, что у вас завязаны глаза и вы стоите на горе (поверхность потерь), а цель — найти самую низкую точку долины (минимум потерь). Перед каждым шагом вам нужно ногами почувствовать самое крутое направление спуска вокруг вас. Это самое крутое направление спуска —градиент. Обратное распространение как раз и помогает вычислить градиент в каждой точке под вашими ногами (соответствующей каждому набору параметров).

Шаги вычисления обратного распространения (правило цепочки)

Мы продолжаем использовать предыдущую миниатюрную сеть и предполагаем, что истинная цена жильяy_true = 2.5, и функция потерь — среднеквадратичная ошибкаL = (y_true - y_pred)^2。

Обратное распространение начинается с выходного слоя,в обратном направлениии вычисляет градиенты слой за слоем:

Вычислите градиенты параметров выходного слоя

  • ПотериLпо предсказанному значениюy_predградиент:∂L/∂y_pred = -2 * (y_true - y_pred)
  • Потому чтоy_pred = w2 * a1 + b2, следовательно:∂L/∂w2 = (∂L/∂y_pred) * (∂y_pred/∂w2) = (∂L/∂y_pred) * a1 ∂L/∂b2 = (∂L/∂y_pred) * (∂y_pred/∂b2) = (∂L/∂y_pred) * 1

Вычислите градиенты параметров скрытого слоя

  • Сначала нам нужны потериLпо выходу скрытого слояa1градиент.a1Черезy_predвлияетL: ∂L/∂a1 = (∂L/∂y_pred) * (∂y_pred/∂a1) = (∂L/∂y_pred) * w2
  • Затем,a1 = σ(z1), производная сигмоидной функцииσ'(z) = σ(z)*(1-σ(z))。
  • Наконец, вычислитеLотносительно параметров скрытого слояw1, b1градиенты:∂L/∂w1 = (∂L/∂a1) * (∂a1/∂z1) * (∂z1/∂w1) = (∂L/∂a1) * σ'(z1) * x ∂L/∂b1 = (∂L/∂a1) * (∂a1/∂z1) * (∂z1/∂b1) = (∂L/∂a1) * σ'(z1) * 1

Пример кода: ручная реализация обратного распространения

Пример

# Продолжите с кода и результатов прямого распространения
y_true = 2.5
y_pred = result['y_pred']
a1 = result['a1']
z1 = result['z1']
x = x_input

print(f"Истинное значение y_true = {y_true}")
print(f"Предсказанное значение y_pred = {y_pred:.4f}")
print(f"Начальные потери Loss = {(y_true - y_pred)**2:.4f}")
print("\n--- Начало обратного распространения для вычисления градиентов ---")

# 1. Вычислить градиент функции потерь по отношению к y_pred
dL_dy_pred = -2 * (y_true - y_pred)
print(f"Градиент ∂L/∂y_pred = -2*(y_true - y_pred) = {dL_dy_pred:.4f}")

# 2. Вычислить градиенты параметров выходного слоя w2, b2
dL_dw2 = dL_dy_pred * a1
dL_db2 = dL_dy_pred * 1
print(f"Градиент ∂L/∂w2 = (∂L/∂y_pred) * a1 = {dL_dw2:.4f}")
print(f"Градиент ∂L/∂b2 = (∂L/∂y_pred) * 1 = {dL_db2:.4f}")

# 3. Вычислить градиент функции потерь по отношению к выходу скрытого слоя a1
dL_da1 = dL_dy_pred * w2
print(f"Градиент ∂L/∂a1 = (∂L/∂y_pred) * w2 = {dL_da1:.4f}")

# 4. Вычислить значение производной сигмоидной функции в точке z1
def sigmoid_derivative(x):
    """Производная сигмоидной функции"""
    s = sigmoid(x)
    return s * (1 - s)

sigma_prime_z1 = sigmoid_derivative(z1)
print(f"Производная сигмоиды σ'(z1) = σ(z1)*(1-σ(z1)) = {sigma_prime_z1:.4f}")

# 5. Вычисление градиентов параметров скрытого слоя w1, b1
dL_dw1 = dL_da1 * sigma_prime_z1 * x
dL_db1 = dL_da1 * sigma_prime_z1 * 1
print(f"Градиент ∂L/∂w1 = (∂L/∂a1) * σ'(z1) * x = {dL_dw1:.4f}")
print(f"Градиент ∂L/∂b1 = (∂L/∂a1) * σ'(z1) * 1 = {dL_db1:.4f}")

Пример вывода:

真实值 y_true = 2.5
预测值 y_pred = 1.9899
初始损失 Loss = 0.2602

--- 开始反向传播计算梯度 ---
梯度 ∂L/∂y_pred = -2*(y_true - y_pred) = -1.0202
梯度 ∂L/∂w2 = (∂L/∂y_pred) * a1 = -1.0134
梯度 ∂L/∂b2 = (∂L/∂y_pred) * 1 = -1.0202
梯度 ∂L/∂a1 = (∂L/∂y_pred) * w2 = -1.5303
Sigmoid导数 σ'(z1) = σ(z1)*(1-σ(z1)) = 0.0066
梯度 ∂L/∂w1 = (∂L/∂a1) * σ'(z1) * x = -0.0304
梯度 ∂L/∂b1 = (∂L/∂a1) * σ'(z1) * 1 = -0.0101

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


Обновление параметров: градиентный спуск

После получения градиентов мы используемградиентный спускв качестве алгоритма для обновления параметров:

parameter = parameter - learning rate * the gradient of that parameter

где,скорость обучения— очень важный гиперпараметр, который управляет размером шага при каждом обновлении параметра. Если размер шага слишком мал, обучение идет медленно; если слишком велик, обучение может не сойтись или даже разойтись.

Пример кода: применение градиентного спуска для обновления параметров

Пример

learning_rate = 0.1

# Обновить параметры
w1_new = w1 - learning_rate * dL_dw1
b1_new = b1 - learning_rate * dL_db1
w2_new = w2 - learning_rate * dL_dw2
b2_new = b2 - learning_rate * dL_db2

print("--- Обновленные параметры ---")
print(f"w1: {w1:.4f} -> {w1_new:.4f}")
print(f"b1: {b1:.4f} -> {b1_new:.4f}")
print(f"w2: {w2:.4f} -> {w2_new:.4f}")
print(f"b2: {b2:.4f} -> {b2_new:.4f}")

# Выполнить прямой проход с новыми параметрами, чтобы проверить, уменьшилась ли потеря
def forward_pass_with_params(x, w1, b1, w2, b2):
    z1 = w1 * x + b1
    a1 = sigmoid(z1)
    y_pred = w2 * a1 + b2
    return y_pred

y_pred_new = forward_pass_with_params(x_input, w1_new, b1_new, w2_new, b2_new)
loss_new = (y_true - y_pred_new)**2
print(f"\n"Предсказание с новыми параметрами: y_pred_new = {y_pred_new:.4f}")
print(f"Обновлённые потери New Loss = {loss_new:.4f}")
print(f"Изменение потерь: {loss_new - (y_true-y_pred)**2:.4f} (отрицательное значение означает, что потери уменьшились)")

Пример вывода:

--- 更新后的参数 ---
w1: 2.0000 -> 2.0030
b1: -1.0000 -> -0.9990
w2: 1.5000 -> 1.6013
b2: 0.5000 -> 0.6020

用新参数预测: y_pred_new = 2.1933
更新后的损失 New Loss = 0.0940
损失变化: -0.1662 (负值表示损失减小)

Отлично! Послепрямого распространения -> вычисления потерь -> обратного распространения -> обновления градиентным спускомполного цикла, наше предсказанное значениеy_predfrom1.99ближе к истинному значению2.5, а потери также снизились с0.260снизилась до0.094. Повторяя этот цикл тысячи или миллионы раз (на больших объёмах данных), нейронная сеть может выучить эффективные параметры и делать точные предсказания.


Практическое упражнение: закрепите понимание

Теперь пришло время перейти к практике и закрепить то, что вы узнали.

Упражнение 1: Расширьте сетьИзмените код выше, чтобы увеличить число нейронов скрытого слоя до 2. Вам нужно инициализироватьw1как массив формы(2,)массив (два веса),b1является(2,)массива.

Упражнение 2: Измените функцию активацииЗамените функцию активации Sigmoid на функцию ReLU (f(x) = max(0, x)). Вам нужно заново вывести и реализовать производную ReLU (f'(x) = 1 if x>0 else 0). Сравните, как отличается процесс обучения при использовании разных функций активации.

Упражнение 3: Реализуйте цикл обученияНапишите полный цикл обучения, чтобы обучить простой набор данных (например, создайте собственныеy = 2x + 1 + 噪声данные), обучив крошечную сеть подгонке под них. Задайте количество эпох, выводите значение функции потерь после каждой итерации и наблюдайте, продолжает ли функция потерь уменьшаться по мере обучения.

Упражнение 4: Поймите влияние скорости обученияНа основе упражнения 3 попробуйте разныеlearning_rate(например, 0.01, 0.1, 0.5, 1.0). Наблюдайте, как меняется кривая функции потерь, когда скорость обучения слишком велика или слишком мала (колеблется ли она, расходится или медленно сходится), и глубоко поймите важность скорости обучения как размера шага.


Итог

Прямое распространение и обратное распространение — это основная динамика обучения нейронной сети:

  1. Прямое распространениеДа— это путь вывода, который использует текущие параметры для отображения входов в выходы и вычисляет оценку (потерю) текущей производительности.
  2. Обратное распространениеДа— это алгоритм обучения, который использует правило цепочки для эффективного вычисления градиента функции потерь по каждому параметру в сети, указывая направление для оптимизации параметров.
  3. Градиентный спускДа— это стратегия оптимизации, которая фактически обновляет параметры, используя градиенты, предоставленные обратным распространением, с скоростью обучения в качестве размера шага, постепенно улучшая производительность сети.
Другие расширения