Функции, экспонента и логарифм

⏱ 30 мин · 🧩 заданий: 7

Функция — это машинка

Функция f берёт вход x и выдаёт выход f(x). Нейросеть — это тоже функция: на вход картинка (64 числа), на выход 10 вероятностей. Просто очень сложная функция, собранная из простых. В этом модуле мы научимся понимать, как выход функции меняется при изменении входа, — это ключ к обучению.

import numpy as np
import vis

x = np.linspace(-3, 3, 200)
vis.line(x, {"2x + 1 (линейная)": 2 * x + 1, "x² (парабола)": x ** 2, "x³": x ** 3},
         title="Простые функции", xlabel="x")
  • Линейная f(x) = wx + b — прямая: w задаёт наклон, b — сдвиг. Это модель из первого урока.
  • Квадратичная x^2 — парабола с минимумом в нуле. Квадрат ошибки, который мы минимизируем, выглядит именно так.
  • Кусочные функции задаются разными формулами на разных участках. Самая знаменитая в ML — ReLU:
\text{ReLU}(x) = \max(0, x) = \begin{cases} x, & x > 0 \\ 0, & x \le 0 \end{cases}

Экспонента

Экспонента e^x, где e \approx 2.718, — функция, которая растёт со скоростью, равной самой себе. Её свойства:

  • e^x > 0 всегда — поэтому её используют, чтобы превратить любое число в положительное (так получаются вероятности в softmax);
  • e^0 = 1, e^{a + b} = e^a e^b;
  • растёт очень быстро: e^{10} \approx 22\,026, e^{100} \approx 2.7 \cdot 10^{43}, а e^{1000} уже не помещается в float.
import numpy as np
import vis

x = np.linspace(-3, 3, 200)
vis.line(x, {"eˣ": np.exp(x), "2ˣ": 2 ** x, "e⁻ˣ": np.exp(-x)}, title="Экспоненты", xlabel="x")

print(np.exp([0, 1, 10, 100]))
print(np.exp(1000))          # inf — переполнение!

Почему именно e

Число e — то основание, при котором скорость роста функции в каждой точке в точности равна её значению. Наклон графика e^x в точке x равен e^x. Из-за этого формулы с e получаются самыми простыми — увидим это в уроке о производных.

import numpy as np

for n in (1, 10, 100, 10_000, 1_000_000):
    print(f"(1 + 1/{n})^{n} = {(1 + 1 / n) ** n:.6f}")
print("e =", np.e)

Логарифм

Натуральный логарифм \ln x — обратная функция к экспоненте: \ln x — это степень, в которую нужно возвести e, чтобы получить x. \ln(e^x) = x, e^{\ln x} = x.

  • Определён только для x > 0; \ln 1 = 0; при x \to 0 уходит в -\infty.
  • Растёт очень медленно: \ln 1\,000\,000 \approx 13.8.
  • Превращает произведения в суммы: \ln(ab) = \ln a + \ln b, и степени — в множители: \ln a^k = k \ln a.
import numpy as np
import vis

x = np.linspace(0.01, 5, 300)
vis.line(x, {"ln x": np.log(x), "log₂ x": np.log2(x)}, title="Логарифмы", xlabel="x")
print(np.log([1, np.e, 10, 1e6]))
print(np.log(2 * 3), np.log(2) + np.log(3))

Зачем логарифм в ML

1. Произведения вероятностей. Вероятность того, что модель права на всём наборе из N объектов, — произведение N вероятностей. При N = 1000 и вероятностях около 0.9 это 0.9^{1000} \approx 10^{-46}, а дальше — просто ноль в компьютере. Логарифм превращает произведение в сумму вполне нормальных чисел:

import numpy as np

p = np.full(5000, 0.9)
print("произведение:", np.prod(p))               # 0.0 — исчезло
print("сумма логарифмов:", np.log(p).sum())      # −526.8 — вполне считаемо

2. Функции потерь. Кросс-энтропия, главная функция потерь для классификации, — это -\ln p, где p — вероятность правильного ответа. Она сильно штрафует уверенные ошибки: при p = 0.01 потеря 4.6, а при p = 0.9 — всего 0.1.

import numpy as np
import vis

p = np.linspace(0.01, 1, 200)
vis.line(p, -np.log(p), title="−ln p: штраф за вероятность правильного ответа", xlabel="p", ylabel="потеря")

3. Шкалы. Потери при обучении часто падают в тысячи раз — их рисуют в логарифмическом масштабе (vis.line(..., logy=True)).

Сигмоида и tanh

Скомбинируем экспоненту с дробью и получим сигмоиду — гладкую «ступеньку» от 0 до 1:

\sigma(z) = \frac{1}{1 + e^{-z}}

Она превращает любое число в «вероятность»: большие положительные z → почти 1, большие отрицательные → почти 0, \sigma(0) = 0.5. Её близкий родственник tanh — такая же ступенька, но от −1 до 1: \tanh z = 2\sigma(2z) - 1.

import numpy as np
import vis

z = np.linspace(-6, 6, 200)
sigmoid = 1 / (1 + np.exp(-z))
vis.line(z, {"σ(z)": sigmoid, "tanh z": np.tanh(z), "ReLU(z)": np.maximum(0, z) / 6},
         title="Функции активации (ReLU сжата в 6 раз для масштаба)", xlabel="z")

Все три — функции активации, та самая нелинейность между слоями нейросети. Подробно сравним их в отдельном уроке.

Композиция функций

Композиция — применение функций одна за другой: h(x) = g(f(x)). Нейросеть — композиция слоёв:

\text{сеть}(x) = f_3(\,f_2(\,f_1(x)\,)\,)

Например, нейрон — это композиция линейной функции и сигмоиды: \sigma(wx + b). Меняя w и b, мы двигаем и растягиваем ступеньку:

import numpy as np
import vis

x = np.linspace(-5, 5, 200)
sig = lambda z: 1 / (1 + np.exp(-z))
vis.line(x, {"σ(x)": sig(x), "σ(3x) — круче": sig(3 * x), "σ(x − 2) — сдвиг": sig(x - 2), "σ(−x) — отражение": sig(-x)},
         title="Нейрон σ(w·x + b) при разных w и b", xlabel="x")

Чтобы обучать такие композиции, нам понадобится узнать, как меняется выход при малом изменении входа или параметра, — это производная. Ей посвящён следующий урок.

Итог

  • Нейросеть — функция, собранная из простых функций (композиция).
  • Экспонента e^x всегда положительна, быстро растёт и легко переполняется.
  • Логарифм — обратная к экспоненте, превращает произведения в суммы; на нём построены функции потерь.
  • Сигмоида \sigma(z) = 1/(1 + e^{-z}) сжимает любое число в (0, 1); tanh — в (−1, 1); ReLU — \max(0, z).

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

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

Чему равно \ln(e^5)?

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

Чему равно \log_2 1024?

Задание 3 Несколько вариантов

Какие равенства верны (при положительных a, b)?

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

Какие значения может принимать сигмоида \sigma(z) = \dfrac{1}{1 + e^{-z}}?

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

Чему равно \sigma(0)?

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

Напишите функции для чисел (модуль math):

  • relu(z) — \max(0, z);
  • sigmoid(z) — 1/(1 + e^{-z});
  • tanh(z) — через экспоненты: \dfrac{e^z - e^{-z}}{e^z + e^{-z}} (без math.tanh).
Какие тесты проверяют код (4)
assert relu(-3) == 0 and relu(2.5) == 2.5
assert sigmoid(0) == 0.5 and abs(sigmoid(2) - 0.8807970779778823) < 1e-12 and abs(sigmoid(-2) + sigmoid(2) - 1) < 1e-12
assert all(abs(tanh(z) - math.tanh(z)) < 1e-12 for z in (-2, -0.5, 0, 1, 3))
import inspect
assert "math.tanh" not in inspect.getsource(tanh), "Не используйте math.tanh"

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

Логарифм спасает от исчезновения чисел. Модель дала 2000 объектам вероятности правильного ответа probs (числа от 0 до 1). Нужна вероятность того, что модель права на всех сразу — произведение вероятностей. Но произведение 2000 чисел меньше 1 превращается в 0.0.

Напишите log_likelihood(probs) — логарифм этого произведения, вычисленный как сумма логарифмов. Верните float.

Какие тесты проверяют код (2)
assert abs(log_likelihood(np.array([0.5, 0.5])) - np.log(0.25)) < 1e-12
p = np.full(2000, 0.6)
ll = log_likelihood(p)
assert np.isfinite(ll), "Получилось −∞: произведение обнулилось — суммируйте логарифмы"
assert abs(ll - 2000 * np.log(0.6)) < 1e-6

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