Производная: скорость изменения

⏱ 35 мин · 🧩 заданий: 6

Скорость изменения

Вы едете на машине. Путь — функция времени s(t). Спидометр показывает скорость — насколько быстро меняется путь прямо сейчас. Это и есть производная: s'(t).

В ML вместо пути — функция потерь L(w), а вместо времени — параметр модели w. Производная L'(w) отвечает на главный вопрос обучения: если чуть-чуть увеличить вес, ошибка вырастет или упадёт — и насколько сильно?

Определение

Сдвинем вход на крошечное h и посмотрим, во сколько раз изменился выход:

f'(x) = \lim_{h \to 0} \frac{f(x + h) - f(x)}{h}

Дробь — наклон прямой через две близкие точки графика. Когда h \to 0, эта прямая превращается в касательную, а производная — в её наклон.

import numpy as np
import vis

f = lambda x: x ** 2
x0 = 1.5
for h in (1, 0.1, 0.01, 0.001, 1e-6):
    print(f"h = {h:<8} наклон секущей = {(f(x0 + h) - f(x0)) / h:.6f}")
print("точная производная 2·x₀ =", 2 * x0)

x = np.linspace(-1, 3, 100)
slope = 2 * x0
vis.line(x, {"f(x) = x²": f(x), "касательная в x₀ = 1.5": f(x0) + slope * (x - x0)},
         title="Производная — наклон касательной", xlabel="x", marker=(x0, f(x0)))

Касательная — лучшее линейное приближение функции рядом с точкой:

f(x_0 + \Delta) \approx f(x_0) + f'(x_0) \cdot \Delta

Эта формула — суть всего обучения: зная производную, мы предсказываем, как изменится ошибка при малом шаге.

Численная производная

Даже не зная формулы, производную можно посчитать: взять маленькое h и вычислить дробь. Точнее работает центральная разность:

f'(x) \approx \frac{f(x + h) - f(x - h)}{2h}
import math

def derivative(f, x, h=1e-5):
    return (f(x + h) - f(x - h)) / (2 * h)

print(derivative(lambda x: x ** 3, 2))      # ≈ 12
print(derivative(math.exp, 0))             # ≈ 1
print(derivative(math.log, 10))            # ≈ 0.1

Численная производная медленная (два вычисления функции на каждый параметр — для сети с миллионом весов это два миллиона прогонов) и неточная. Поэтому сети обучают с аналитическими производными, а численную используют для проверки: когда мы напишем свой движок автоматического дифференцирования, будем сверять его с численной производной — это называется gradient check.

Правила дифференцирования

Правило Формула
Константа (c)' = 0
Сумма (f + g)' = f' + g'
Множитель (c \cdot f)' = c \cdot f'
Произведение (f g)' = f' g + f g'
Частное \left(\frac{f}{g}\right)' = \frac{f'g - fg'}{g^2}
Композиция (f(g(x)))' = f'(g(x)) \cdot g'(x) — правило цепочки, ему посвящён следующий урок

Таблица производных

f(x) f'(x)
x^n n x^{n - 1}
e^x e^x — экспонента не меняется!
\ln x 1/x
\sin x, \cos x \cos x, -\sin x
\sigma(x) = \frac{1}{1 + e^{-x}} \sigma(x)(1 - \sigma(x))
\tanh x 1 - \tanh^2 x
\text{ReLU}(x) = \max(0, x) 1 при x > 0, 0 при x < 0

Пример: f(x) = 3x^2 - 4x + 1. По правилам суммы и множителя f'(x) = 3 \cdot 2x - 4 = 6x - 4.

Производные для нейросетей

Квадрат ошибки. Модель предсказала p, правильный ответ y, потеря L = (p - y)^2. Производная по p: L' = 2(p - y). Если предсказали больше, чем нужно (p > y), производная положительна: уменьшите p. Чем больше ошибка — тем больше производная.

Сигмоида. Её производная красиво выражается через неё саму: \sigma' = \sigma(1 - \sigma). Максимум — 0.25 в нуле, а при больших |z| производная почти ноль. Это важно: если нейрон с сигмоидой «насытился», градиент через него почти не проходит.

ReLU. Производная — 1 там, где нейрон активен, и 0, где нет. В нуле производной формально нет (излом), на практике берут 0.

import numpy as np
import vis

z = np.linspace(-6, 6, 300)
s = 1 / (1 + np.exp(-z))
vis.line(z, {"σ(z)": s, "σ′(z) = σ(1 − σ)": s * (1 - s)}, title="Сигмоида и её производная", xlabel="z")
vis.line(z, {"ReLU(z)": np.maximum(0, z), "ReLU′(z)": (z > 0).astype(float)}, title="ReLU и её производная", xlabel="z")

Знак производной и минимум

  • f'(x) > 0 — функция растёт: чтобы уменьшить её, двигайтесь влево.
  • f'(x) < 0 — функция убывает: двигайтесь вправо.
  • f'(x) = 0 — плоское место: минимум, максимум или «полочка».

То есть, чтобы уменьшить функцию, двигайтесь против знака производной: x \leftarrow x - \eta f'(x) с маленьким шагом \eta. Это градиентный спуск в одну строку, и он сработает даже там, где уравнение f'(x) = 0 не решить на бумаге.

import numpy as np
import vis

f = lambda x: x ** 4 - 3 * x ** 2 + x
df = lambda x: 4 * x ** 3 - 6 * x + 1

x = np.linspace(-2.2, 2.2, 300)
vis.line(x, {"f(x)": f(x), "f′(x)": df(x)}, title="Минимумы f там, где f′ пересекает ноль снизу вверх", xlabel="x")

w = 2.0
for step in range(30):
    w -= 0.02 * df(w)               # шаг против производной
print(f"нашли минимум: x = {w:.4f}, f = {f(w):.4f}, f′ = {df(w):.6f}")

Обратите внимание: у этой функции два минимума, и спуск из точки 2.0 нашёл ближайший (локальный), а не самый глубокий. Об этом мы ещё поговорим.

Итог

  • Производная f'(x) — скорость изменения функции и наклон касательной: f(x + \Delta) \approx f(x) + f'(x)\Delta.
  • Численная производная \frac{f(x+h) - f(x-h)}{2h} медленная, но годится для проверки.
  • Правила: сумма, множитель, произведение, частное; таблица: x^n, e^x, \ln x, \sigma, \tanh, ReLU.
  • Чтобы уменьшить функцию, двигайтесь против знака производной.

Практика решено 0 из 6

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Ввод ответа

Найдите производную f(x) = x^3 в точке x = 2.

Задание 2 Ввод ответа

Найдите производную f(x) = 5x^2 - 3x + 7 в точке x = 1.

Задание 3 Один вариант ответа

В точке x_0 производная функции потерь L'(x_0) = -4. Куда нужно сдвинуть x, чтобы потери уменьшились?

Задание 4 Заполнить таблицу

Сопоставьте функцию и её производную.

e^x1/x2x\sigma(x)(1 - \sigma(x))0 или 1
x^2
e^x
\ln x
\sigma(x) (сигмоида)
\text{ReLU}(x), при x \ne 0
Отметьте ячейку — «+», пустая — «−».
Задание 5 Программа на Python

Численная производная. Напишите derivative(f, x, h=1e-5) — производную функции f в точке x по центральной разности: \dfrac{f(x + h) - f(x - h)}{2h}.

Какие тесты проверяют код (2)
assert abs(derivative(lambda x: x ** 3, 2) - 12) < 1e-6
import math
assert abs(derivative(math.exp, 1.0) - math.e) < 1e-6
assert abs(derivative(math.log, 4.0) - 0.25) < 1e-6
assert abs(derivative(math.sin, 0.0) - 1) < 1e-6

Задание 6 Программа на Python

Производные функций активации. Напишите аналитические (по формулам) производные:

  • d_sigmoid(z) — \sigma(z)(1 - \sigma(z));
  • d_tanh(z) — 1 - \tanh^2 z (можно math.tanh);
  • d_relu(z) — 1 при z > 0, иначе 0.

Тесты сравнят их с численной производной.

Какие тесты проверяют код (3)
sig = lambda z: 1 / (1 + math.exp(-z))
num = lambda f, x: (f(x + 1e-6) - f(x - 1e-6)) / 2e-6
for z in (-3, -0.5, 0, 0.7, 4):
    assert abs(d_sigmoid(z) - num(sig, z)) < 1e-6, f"d_sigmoid({z}) неверно"
    assert abs(d_tanh(z) - num(math.tanh, z)) < 1e-6, f"d_tanh({z}) неверно"

assert d_relu(2.5) == 1 and d_relu(-1) == 0
assert abs(d_sigmoid(0) - 0.25) < 1e-12, "Максимум производной сигмоиды — 0.25 в нуле"

Войдите, чтобы отмечать пройденные уроки.