Стаття
Машинне навчання · ⏱ ≈ 14 хв читання · Останнє оновлення: 23 червня 2026 р.

Зворотне поширення — розгорнуте ланцюгове правило

Зворотне поширення — алгоритм, що уможливлює глибоке навчання. Це ланцюгове правило математичного аналізу, систематично застосоване до обчислювального графа — і воно перетворило галузь після 1986 року. Простежуємо математику від скалярних правил до якобіанів і будуємо мінімальний рушій autograd, який диференціює будь-який вираз.

1. Ланцюгове правило

Припустимо, f(x) = g(h(x)). Ланцюгове правило стверджує, що похідна f за x дорівнює:

df/dx = dg/dh · dh/dx Приклад: f = sin(x²) h = x² → dh/dx = 2x g = sin(h) → dg/dh = cos(h) df/dx = cos(x²) · 2x

Для ланцюга з n складених функцій це узагальнюється до добутку n частинних похідних. Зворотне поширення — це саме цей добуток, обчислений шар за шаром, починаючи з виходу функції втрат.

2. Обчислювальні графи

Обчислювальний граф — це спрямований ациклічний граф (DAG), де вузли — це операції (+, ·, exp, …), а ребра несуть значення. Будь-який математичний вираз можна подати таким графом:

L = (a·w + b − y)² Граф: m = a·w (множення) s = m + b (додавання) e = s − y (віднімання) L = e² (піднесення до квадрата)

Під час прямого проходу ми обчислюємо значення зліва направо. Під час зворотного проходу ми перемножуємо локальні частинні похідні справа наліво, щоб накопичити внесок кожного вузла в сумарний градієнт.

3. Прямий і зворотний проходи

Для графа вище зворотний прохід (із початковим значенням dL/dL = 1) поширює:

dL/de = 2e dL/ds = dL/de · de/ds = 2e · 1 = 2e dL/dm = dL/ds · ds/dm = 2e · 1 = 2e dL/db = dL/ds · ds/db = 2e · 1 = 2e dL/dw = dL/dm · dm/dw = 2e · a dL/da = dL/dm · dm/da = 2e · w

Кожен вузол накопичує градієнт від усіх вузлів, що від нього залежать (багатовимірне ланцюгове правило). Там, де значення використовується в кількох наступних вузлах, його внески в градієнт підсумовуються.

4. Якобіани та матричні градієнти

Коли входи й виходи є векторами, «похідною» є матриця Якобі J, де Jᵢⱼ = ∂yᵢ/∂xⱼ. Для лінійного шару y = Wx + b:

∂L/∂W = δ · xᵀ (зовнішній добуток) ∂L/∂x = Wᵀ · δ (передається на попередній шар) ∂L/∂b = δ (сума за виміром пакета) де δ = ∂L/∂y (вектор градієнта з виходу)

Поелементні операції

Для поелементного y = σ(z) якобіан діагональний: ∂L/∂z = ∂L/∂y ⊙ σ'(z). Просто поточковий добуток.

Якобіан Softmax

Для y = softmax(z), Jᵢⱼ = yᵢ(δᵢⱼ − yⱼ) — повна щільна матриця. У поєднанні з функцією втрат перехресної ентропії спрощується до ŷ − y.

Вимір пакета

За міні-пакета з m зразків градієнти ваг усереднюються по пакету: dW = (δ · Aᵀ) / m, щоб градієнти не залежали від масштабу розміру пакета.

Перевірка градієнта

Перевірте аналітичні градієнти чисельно: порівняйте dL/dw із [L(w+ε)−L(w−ε)]/(2ε). Якщо вони збігаються до ~6 знаків після коми, ваше зворотне поширення правильне.

5. Згасання та вибух градієнтів

У 20-шаровій мережі градієнт першого шару містить добуток 20 матриць ваг і 20 похідних активацій. Якщо кожен множник має величину < 1 (сигмоїда насичується до σ' ≈ 0), добуток експоненційно зменшується → згасання градієнта. Якщо кожен множник > 1, він експоненційно зростає → вибух градієнта.

Згасання: σ'(z) ≤ 0.25 для сигмоїди → (0.25)²⁰ ≈ 10⁻¹² Вибух: ||W|| > 1 на шар → ||W||²⁰ → ∞

Рішення:

6. Поза межами SGD — Adam і компанія

SGD оновлює кожну вагу однаково, незалежно від історії її градієнта. Адаптивні оптимізатори відстежують статистику градієнтів для кожного параметра:

Adam (Kingma & Ba 2014): m_t = β₁ m_{t-1} + (1−β₁) g_t (1-й момент — середнє) v_t = β₂ v_{t-1} + (1−β₂) g_t² (2-й момент — дисперсія) m̂_t = m_t / (1−β₁ᵗ) (корекція зсуву) v̂_t = v_t / (1−β₂ᵗ) θ_t = θ_{t-1} − lr · m̂_t / (√v̂_t + ε) Типово: β₁=0.9, β₂=0.999, ε=1e-8, lr=0.001

Adam збігається швидше за SGD у більшості випадків і стійкий до вибору швидкості навчання. Для доналаштування мовних моделей AdamW додає належний спад ваг L2, відокремлений від кроку градієнта.

7. Мінімальний рушій autograd

// Скалярний autograd — автоматичне диференціювання у зворотному режимі
class Value {
  constructor(data, _children = [], _op = '') {
    this.data = data;
    this.grad = 0;
    this._backward = () => {};
    this._prev = new Set(_children);
    this._op = _op;
  }
  add(other) {
    other = other instanceof Value ? other : new Value(other);
    const out = new Value(this.data + other.data, [this, other], '+');
    out._backward = () => { this.grad += out.grad; other.grad += out.grad; };
    return out;
  }
  mul(other) {
    other = other instanceof Value ? other : new Value(other);
    const out = new Value(this.data * other.data, [this, other], '*');
    out._backward = () => {
      this.grad  += other.data * out.grad;
      other.grad += this.data  * out.grad;
    };
    return out;
  }
  pow(n) {
    const out = new Value(this.data ** n, [this], `**${n}`);
    out._backward = () => { this.grad += n * (this.data ** (n - 1)) * out.grad; };
    return out;
  }
  relu() {
    const out = new Value(this.data > 0 ? this.data : 0, [this], 'relu');
    out._backward = () => { this.grad += (out.data > 0 ? 1 : 0) * out.grad; };
    return out;
  }
  backward() {
    // Топологічне сортування, потім виклик _backward у зворотному порядку
    const topo = []; const visited = new Set();
    const build = v => {
      if (!visited.has(v)) {
        visited.add(v);
        for (const child of v._prev) build(child);
        topo.push(v);
      }
    };
    build(this);
    this.grad = 1;
    for (const v of topo.reverse()) v._backward();
  }
}

// Приклад: L = (x·w + b - y)²
const x = new Value(2.0);
const w = new Value(-3.0);
const b = new Value(6.88);
const y = new Value(1.0);
const L = x.mul(w).add(b).add(y.mul(-1)).pow(2);
L.backward();
console.log(w.grad); // ∂L/∂w  ≈ 2*(x*w+b-y)*x

8. Режими автоматичного диференціювання

Зворотний режим (backprop)

Один зворотний прохід обчислює ∂L/∂θ для УСІХ параметрів одночасно. Ідеально, коли виходів ≪ входів — саме випадок нейронної мережі.

Прямий режим

Обчислює добуток якобіана на вектор Jv — один прямий прохід на кожен вимір входу. Ефективний, коли входів ≪ виходів (рідко в ML).

Символьне диференціювання

Виводить вирази у замкненій формі (Mathematica, SymPy). Точне, але може породжувати експоненційно великі вирази («розбухання виразу»).

Чисельне диференціювання

Скінченні різниці [f(x+h)−f(x)]/h. Просте, але повільне (один прохід на параметр) і піддається похибкам скорочення чисел з рухомою комою.

PyTorch і JAX використовують AD у зворотному режимі з динамічним обчислювальним графом (define-by-run). JAX додатково підтримує AD у прямому режимі та оператори композиції функцій (jit, vmap, grad), які чисто компонуються завдяки його функціональному дизайну.

🧠 Відкрити «Нейронну мережу» →