# Бинарная классификация: как машина отличает кота от не-кота

Продолжение статьи [«Как работает LLM: путь одного токена»](/notes/how-llm-works/) — там мы прошли путь токена через уже обученную сеть. Теперь откатываемся к самому началу: как вообще устроена задача «посмотреть на данные и ответить да/нет» и что нужно, чтобы сеть могла этому научиться. Все формулы разобраны до числа: каждую можно пересчитать в столбик.

## Оглавление

- [Задача: кот или не кот](#задача)
- [Картинка превращается в строку чисел](#картинка-в-числа)
- [Нотация: как записываются данные](#нотация)
- [Матрица примеров X и матрица ответов Y](#матрицы)
- [Логистическая регрессия: предсказание](#логистическая-регрессия)
- [Функция ошибки L: штраф за один пример](#функция-ошибки)
- [Функция стоимости J: штраф на всём наборе](#функция-стоимости)
- [Почему не квадрат разницы](#почему-не-квадрат)
- [Градиентный спуск: как учатся веса](#градиентный-спуск)
- [Backprop: откуда берутся производные](#backprop)
- [Что дальше](#что-дальше)

## Задача: кот или не кот {#задача}

Бинарная классификация — это задача с двумя возможными ответами. На вход даётся картинка, на выходе нужно одно из двух: **1** — кот на картинке есть, **0** — кота нет. Никаких промежуточных ответов задача не допускает: либо «да», либо «нет».

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

Прежде чем учить модель, надо понять, что она вообще видит. Компьютер не видит кота — он видит числа.

## Картинка превращается в строку чисел {#картинка-в-числа}

Возьмём картинку 64 на 64 пикселя, цветную. Цветной пиксель — это три числа: сколько красного, сколько зелёного, сколько синего (RGB). Считаем размер:

$64 \times 64 \times 3 = 12288$

Двенадцать тысяч двести восемьдесят восемь чисел — вот и вся «картинка» для компьютера. Все эти числа выстраивают в одну длинную строку — **вектор признаков** и обозначают `x`. Слово «признак» здесь точное: каждое число — один признак картинки, яркость одного канала одного пикселя. Размер этого вектора называют $n_x$ ($x$ — потому что это вход; просто $n$ — общий жаргон): число входных признаков. У нас $n_x = 12288$.

Если можешь пересказать: картинка — это не изображение, а вектор из 12288 чисел, — двигаемся дальше.

## Нотация: как записываются данные {#нотация}

Эти обозначения будут с нами до самого конца, поэтому зафиксируем их один раз и внимательно.

Один обучающий пример — это пара `(x, y)`:

- `x` — вход: вектор из $n_x$ чисел (у нас — 12288-численная строка пикселей);
- `y` — правильный ответ, метка: `1` (кот) или `0` (не кот).

Верхний индекс в скобках — номер примера: $(x^{(1)}, y^{(1)})$ — первый пример, $(x^{(2)}, y^{(2)})$ — второй, и так до $(x^{(m)}, y^{(m)})$. Это не возведение в степень! Скобки в индексе выбраны ровно для того, чтобы отличать номер примера от степени: $x^{(2)}$ — «второй пример», а $x^2$ было бы «икс в квадрате».

- `m` — сколько всего обучающих примеров. 

Пара `(x, y)` — это один «фотоальбом»: вход и подписанный к нему правильный ответ (y). Обучающий набор — стопка из `m` таких пар. Осталось собрать стопку в одну таблицу.

Если можешь пересказать: один пример = пара (вход, правильный ответ), x — вход, y — метка 1/0, m — сколько примеров, — листай дальше.

## Матрица примеров X и матрица ответов Y {#матрицы}

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

**Матрица X**: каждый обучающий пример $x^{(i)}$ кладётся в матрицу **столбцом**, слева направо. Первый пример — первый столбец, второй — второй, последний `m`-й — последний столбец. Получается матрица высотой $n_x$ (число признаков) и шириной `m` (число примеров). Мини-пример: два примера по три признака, первый — кот ($y=1$), второй — не кот ($y=0$):

$$X = \begin{bmatrix} 0.5 & 1.0 \\ 0.3 & 0.2 \\ 0.9 & 0.4 \end{bmatrix}$$

Первый столбец $[0.5, 0.3, 0.9]$ — это пример №1 целиком, второй столбец $[1.0, 0.2, 0.4]$ — пример №2:

$X = [\, x^{(1)} \;|\; x^{(2)} \;|\; \dots \;|\; x^{(m)} \,]$ — размер $n_x \times m$.

В Python: $X.shape = (n_x, m)$. У нас: 12288 строк, `m` столбцов.

Неожиданность в том, что интуитивно хочется положить примеры **в строки** — как в таблице Excel, один пример на строку. Но примеры кладут именно столбцами: с примерами-столбцами операции нейросети записываются одной матричной операцией без транспонирований и циклов. Это соглашение — columns are examples, «примеры — это столбцы» — основа всей реализации нейросетей; запомни его сейчас, чтобы не путаться дальше.

**Матрица Y**: правильные ответы складываются в одну строку — матрицу $1 \times m$:

$Y = [\, y^{(1)} \; y^{(2)} \; \dots \; y^{(m)} \,]$ — размер $1 \times m$.

В Python: `Y.shape == (1, m)`. Позиция в Y отвечает позиции столбца в X: столбец №7 матрицы X и седьмой элемент Y — вход и ответ одного и того же примера.

![Путь данных: картинка 64×64×3 → вектор 12288 чисел → m столбцов собираются в матрицу X (12288 × m); ответы ложатся в строку Y (1 × m)](/notes/binary-classification/notation-map.png)

Если можешь пересказать: примеры лежат в матрице X столбцами, ответы — в строке Y, столбец №X и элемент №Y — один и тот же пример, — листай дальше.

## Логистическая регрессия: предсказание {#логистическая-регрессия}

Дано: вектор `x` из 12288 чисел. Нужно: вероятность того, что на картинке кот, — число от 0 до 1. Обозначается $\hat{y}$ («y с крышечкой») — предсказание, в отличие от `y` — правильного ответа.

$\hat{y} = P(y = 1 \mid x)$ — «вероятность того, что y равен 1, при данном входе x». Если модель выдала $\hat{y} = 0.7$, она говорит: «на 70% уверен, что это кот». То есть 0.7 — это уверенность на шкале от 0 («точно не кот») до 1 («точно кот»), просто записанная долей, а не процентами: 0.7 = 70 из 100.

Внутри предсказания два шага.

**Шаг 1 — посчитать число.** Каждому признаку назначается свой **вес** (w) (это не «сколько килограммов» — так называют **важность** признака) — число, показывающее, насколько этот признак важен для ответа «кот» (яркие усы важны — вес большой; цвет неба не важен — вес около нуля). Веса собираются в вектор `w` — по одному на каждый из 12288 признаков.

Отдельно живёт одно число `b` (bias, сдвиг) — добавка, которая двигает черту между «скорее не кот» и «скорее кот»: чем больше b, тем легче модели сказать «кот» при тех же весах (w). Считаем взвешенную сумму (z):

$$z = w^T x + b$$

Расшифровка до числа: $w^T x$ — это $w_1 x_1 + w_2 x_2 + \dots + w_{n_x} x_{n_x}$. Каждый признак умножили на свой вес (w) и всё сложили. `T` («транспонирование») — техническая поворачивалка. `w` хранится столбцом, `x` тоже столбцом. Столбец на столбец умножать нельзя. Поворачиваем `w` в строку (записываем $w^T$) — и произведение «строка × столбец» даёт одно число. На смысл формулы поворот не влияет — это просто правила записи.

Пример на трёх признаках (не на 12288 — механика та же): $x = [0.5,\; 0.3,\; 0.9]$, $w = [1.2,\; -0.4,\; 2.0]$, $b = -0.9$. Тогда:

$z = 1.2 \cdot 0.5 + (-0.4) \cdot 0.3 + 2.0 \cdot 0.9 + (-0.9) = 0.6 - 0.12 + 1.8 - 0.9 = 1.38$

**Шаг 2 — сжать число в вероятность.** Проблема: `z` — любое число, от −∞ до +∞, а вероятность должна жить от 0 до 1. Ответ −3.7 не может быть «вероятностью кота». Решение — сигмоида (σ), функция, которая принимает любое число и выдает число от 0 до 1. В формуле встретится число $e \approx 2.718$ — математическая константа роста, второй после $\pi$ знаменитости; вручную считать $e^{-z}$ не нужно — вся сигмоида уже посчитана в таблице ниже:

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

Таблица значений, чтобы почувствовать поведение:

| z | σ(z) |
|---|------|
| −4 | 0.018 |
| −2 | 0.119 |
| 0 | 0.500 |
| +2 | 0.881 |
| +4 | 0.982 |

Сильный минус — почти 0 («точно не кот»), ноль — ровно 0.5 («не знаю»), сильный плюс — почти 1 («точно кот»). По краям сигмоида (σ) выходит на плато: сколько ни добавляй к z, ответ уже почти не меняется.

Итоговая формула предсказания (ŷ) — оба шага в одном:

$$\hat{y} = \sigma(w^T x + b) = \sigma(z)$$

Для нашего примера: $\hat{y} = \sigma(1.38) = 0.799$ — модель на 80% уверена, что это кот. Если правильный ответ $y = 1$, модель молодец; если $y = 0$ — ошиблась, и сейчас научимся измерять насколько.

Если можешь пересказать: предсказание (ŷ) — это σ от взвешенной суммы (z): веса (w) задают важность признаков, сигмоида (σ) сжимает сумму в вероятность 0..1, — листай дальше.

## Функция ошибки L: штраф за один пример {#функция-ошибки}

**Функция ошибки** (loss function) $L(\hat{y}, y)$ берёт предсказание (ŷ) и правильный ответ (y) и возвращает одно число — насколько велика ошибка. Больше число — хуже ответ. Ноль — идеально.

Нужна функция, которая: при правильном ответе (y) даёт почти ноль, при уверенно-неправильном — огромное число, и при этом плавно растёт между ними.

**Кросс-энтропия** — стандартная функция ошибки (L) для логистической регрессии:

$$L(\hat{y}, y) = -\big[\, y \log(\hat{y}) + (1 - y) \log(1 - \hat{y}) \,\big]$$

Формула выглядит страшно, но внутри всего два случая. Подставь $y = 1$ (кот) — второе слагаемое умножается на ноль и исчезает:

$L = -\log(\hat{y})$

Подставь $y = 0$ (не кот) — исчезает первое:

$L = -\log(1 - \hat{y})$

Минутка о самом **логарифме**: log отвечает на вопрос «в какую степень возвести число $e$, чтобы получить то, что в скобках». Чем меньше число в скобках, тем больше по модулю ответ: $\ln(0.01) \approx -4.6$, $\ln(0.7) \approx -0.357$, $\ln(0.98) \approx -0.02$. Числа для обоих случаев (хочешь проверить — жми на калькуляторе кнопку **ln**, натуральный логарифм, не **log** — обычный **log** считает по основанию 10 и даст другое число):

- Кот, и модель уверена ($y = 1$, $\hat{y} = 0.98$): $L = -\log(0.98) = 0.02$ — штраф крошечный.
- Кот, и модель дала 0.7: $L = -\log(0.7) = 0.357$ — небольшой штраф.
- Кот, и модель уверена в обратном ($y = 1$, $\hat{y} = 0.01$): $L = -\log(0.01) \approx 4.6$ — штраф огромный.
- Не кот, и модель дала 0.7: $L = -\log(0.3) = 1.204$ — тот же ответ 0.7, но правильный ответ другой, поэтому штраф втрое больше, чем у «кота при 0.7».

Обрати внимание на последний пункт: одна и та же уверенность «на 70% кот» для настоящего кота стоит 0.357, а для настоящей не-кота — 1.204. Функция ошибки (L) смотрит не на уверенность, а на уверенность **в правильную сторону**.

Почему внутри логарифм? Он превращает «штраф за уверенную ошибку» в обрыв: при ŷ → 1 и y = 1 штраф стремится к нулю плавно, а при ŷ → 0 и y = 1 — улетает в бесконечность. Модели дорого ошибаться уверенно и неправильно — именно этого мы и хотим от штрафа (L).

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

## Функция стоимости J: штраф на всём наборе {#функция-стоимости}

Loss (L) измеряет ошибку **одного** примера. Но обучать модель нужно по всем `m` примерам сразу. Для этого усредняем loss по всему набору — получаем **функцию стоимости** (J) (cost function):

$$J(w, b) = \frac{1}{m} \sum_{i=1}^{m} L(\hat{y}^{(i)}, y^{(i)})$$

Читается: «сумма ошибок всех примеров, делённая на их количество» — средний штраф (J) на пример. Сумма $\Sigma$ — просто компактная запись сложения: возьми loss первого, прибавь loss второго, … до `m`-го, раздели на `m`.

Разница терминов: **loss** (L) — про один пример, **cost** (J) — про весь набор. Дальше ищем не «где меньше ошибка на одной картинке», а «где меньше средняя ошибка на всех картинках».

Числовой пример. Три примера, модель всюду дала $\hat{y} = 0.7$, правильные ответы — $y = 1,\; 0,\; 1$:

$L(1, 0.7) = 0.357$, $L(0, 0.7) = 1.204$, $L(1, 0.7) = 0.357$

$J = \frac{0.357 + 1.204 + 0.357}{3} = \frac{1.918}{3} = 0.639$

Средний штраф (J) 0.639. Обучение — попытка сделать это число меньше.

Если можешь пересказать: J — среднее арифметическое всех loss'ов (L), один пример на все m не влияет сильно, — листай дальше.

## Почему не квадрат разницы {#почему-не-квадрат}

Самый естественный кандидат в ошибки — квадрат разницы: $L = (\hat{y} - y)^2 / 2$ (деление на 2 — просто чтобы производная вышла без лишнего множителя; на форму кривой не влияет). Простая, знакомая по школьной алгебре. Но для логистической регрессии она не годится — и почему, одно из самых важных мест всей темы.

Проблема — форма поверхности ошибки. Если нарисовать средний штраф `J` как функцию весов `w` и `b`, для кросс-энтропии получается **чаша**: любая точка на стенке чаши имеет наклон, ведущий вниз, к единственному дну. Такая форма называется **выпуклой** (convex) — и это не красота ради красоты: вся идея градиентного спуска из следующего раздела держится на том, что чаша имеет одно дно. Спуск прост: куда ни поставь шарик, скатится в то же дно.

С квадратичной ошибкой на логистической регрессии чаши не получается — поверхность становится «волнистой», с несколькими впадинами и горбами (**невыпуклая**, non-convex). Градиентный спуск, поставленный в случайную точку, может застрять в мелкой яме, которой далеко до настоящего дна. Кросс-энтропия даёт ровно одну яму — глобальный минимум — и спуск из любой точки приводит в неё.

Можно убедиться и числом: кривизна кросс-энтропии по $z$ равна $\hat{y}(1 - \hat{y})$... умножь два соседних значения из таблицы выше: $0.119 \times (1 - 0.119) = 0.105$, положительно. У квадрата разницы на сигмоиде (σ) при $z = -2$ выходит $-0.059$ — знак сменился, там горб. Пока просто запомни: у кросс-энтропии чаша, у квадрата — горб (что такое производная и откуда эти формулы — следующий раздел). Именно горбы и ловят спуск.

## Градиентный спуск: как учатся веса (w) {#градиентный-спуск}

Итак, есть число `J(w, b)` — средний штраф. Обучение — поиск весов (w), при которых `J` минимальна. Перебрать все значения весов (w) невозможно (их 12289). Но у чаши есть свойство, которое всё упрощает: в каждой точке известен **наклон** — направление наискорейшего спуска.

**Наклон** в точке — это производная. Обозначения: $dJ/dw$ — «насколько изменится J при малом изменении w». В коде её называют коротко `dw`. Отдельно — $db$ для сдвига $b$.

Алгоритм **градиентного спуска** (gradient descent):

- Поставить веса (w) в любое начальное значение (для логистической регрессии чаша выпуклая, поэтому работает любая точка — обычно начинают с нулей). Сам процесс как в обучении кататься на велосипеде: сначала едешь криво (модель угадывает), чувствуешь наклон в сторону падения (градиент ошибки), поправляешь руль на чуть-чуть (шаг спуска) — и снова едешь. Тысячи попыток — и едешь ровно.
- Повторять: сдвинуть каждый вес (w) **против** наклона на маленький шаг.

$$w := w - \alpha \, dw, \qquad b := b - \alpha \, db$$

Двоеточие со знаком равно (`:=`) читается «присвоить новое значение»: посчитай правую часть и запиши результат в `w`. Это не уравнение — поэтому `w` и стоит по обе стороны.

$\alpha$ (альфа) — **скорость обучения** (learning rate): размер шага. Маленькая α — ползём медленно, но точно. Большая — шагами перепрыгиваем через дно туда-сюда. Типичные значения — от 0.001 до 0.1.

Почему «минус»: производная (dJ/dw) указывает, где J **растёт**. Чтобы штраф уменьшать, идём в обратную сторону.

Числовой пример одного шага. Продолжаем трёхпризнаковый пример: $x = [0.5,\; 0.3,\; 0.9]$, правильный ответ $y = 1$, модель дала $\hat{y} = 0.799$ (из раздела про предсказание). Ошибка есть — значит, веса (w) стоит поправить.

1. Сигнал ошибки: $dz = \hat{y} - y = 0.799 - 1 = -0.201$ (модель недобрала уверенности — сигнал отрицательный, веса (w) должны подрасти; откуда формула — выведем в разделе про backprop, пока принимай как готовую).
2. Градиенты — тот же наклон из чаши, собранный по всем весам сразу (в коде так и называют: dw, db) — по весам: $dw = x \cdot dz = [\, 0.5 \cdot (-0.201),\; 0.3 \cdot (-0.201),\; 0.9 \cdot (-0.201) \,] = [-0.10,\; -0.06,\; -0.18]$, и $db = -0.201$.
3. Шаг при $\alpha = 0.1$: $w := w - 0.1 \cdot dw = [1.2,\; -0.4,\; 2.0] - 0.1 \cdot [-0.10,\; -0.06,\; -0.18] = [1.21,\; -0.394,\; 2.018]$, $b := -0.9 - 0.1 \cdot (-0.201) = -0.88$.

Что произошло: признак №1 («важный для кота») получил вес (w) 1.2 → 1.21 — подрос, следующий предсказание будет увереннее. Ровно это, миллионы раз, на 12288 признаках и m примерах, и есть обучение.

Если можешь пересказать: спуск поправляет веса (w) против наклона чаши маленькими шагами α, — листай дальше.

## Backprop: откуда берутся производные {#backprop}

Остался последний вопрос: откуда алгоритм берёт `dw` и `db` для любой пары `(x, y)`? Вычислять их честным определением («пошевелил вес (w), посмотрел, как сдвинулся J») — миллионы прогонов на каждый шаг. Выход — **цепное правило** из матанализа: производная сложной функции = произведение производных по цепочке.

Предсказание — цепочка из трёх шагов: $z = w^T x + b$, затем $a = \sigma(z)$ (активация; `a` и есть $\hat{y}$), затем `L(a, y)`. Ошибка течёт по этой цепочке назад: от L к a, от a к z, от z к каждому весу (w).

Механику удобно видеть на **вычислительном графе** (computation graph): кружки-операции, стрелки-зависимости. В качестве игрушечного примера — функция $J = 3(a + bc)$. Внимание: буквы здесь ничего не значат — это не «активация» и не bias из предыдущих разделов, просто три входных числа. Возьмём $a = 2,\; b = 1,\; c = 5$:

- Прямой ход: $u = bc = 5$, $v = a + u = 7$, $J = 3v = 21$.
- Обратный ход: $dJ/dv = 3$ (производная тройки-умножения); $dv/da = 1$, $dv/du = 1$; $du/db = c = 5$, $du/dc = b = 1$.
- Собираем по цепочке (перемножаем вдоль пути): $dJ/da = 3 \cdot 1 = 3$; $dJ/db = 3 \cdot 1 \cdot 5 = 15$; $dJ/dc = 3 \cdot 1 \cdot 1 = 3$.

Для логистической регрессии вся обратная цепочка сворачивается в одну изящную формулу:

$$dz = a - y$$

Производная (dz) ошибки по z — это просто «предсказание минус ответ». Проверим честно на числах: $z = 0.8$, значит $a = \sigma(0.8) = 0.69$, ответ $y = 1$. Численная производная (посчитать L при $z = 0.800001$ и $z = 0.799999$, взять разницу) даёт $-0.31$; формула даёт $a - y = 0.69 - 1 = -0.31$. Совпало до четвёртого знака — вместо честного перемножения всей цепочки производных по графу достаточно одного вычитания.

Дальше по цепочке от z к весам — производные линейного шага: чуть меняешь вес $w_i$ — вся взвешенная сумма $z$ сдвигается ровно на $x_i$ (сам признак), поэтому производная $dw_i = x_i \cdot dz$ — признак, умноженный на сигнал ошибки и $db = dz$. Все три производные нашего числового примера выше посчитаны именно этими формулами.

На `m` примерах всё то же самое, усреднённое: $dw = \frac{1}{m} \sum_i x^{(i)} \, dz^{(i)}$, $db = \frac{1}{m} \sum_i dz^{(i)}$. Как аккуратно вывести и запрограммировать это усреднение, а затем убрать оставшиеся циклы через матричные операции (**векторизацию**) — тема следующих разборов.

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

## Что дальше {#что-дальше}

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

картинка → вектор 12288 → $z = w^T x + b$ → $\hat{y} = \sigma(z)$ → $L(\hat{y}, y)$ → $J$ по всем $m$ → градиенты $dz = a - y$ → шаг спуска $w := w - \alpha \, dw$ → повторить

Каждый элемент этой цепочки останется без изменений в большой нейросети: сигмоида (σ) станет одной из функций активации, $dz = a - y$ — частным случаем общего backprop, один вес (w) и сдвиг (b) — миллионами весов в слоях. Нейросеть — это та же логистическая регрессия, повторённая и соединённая много раз.

Если можешь пересказать: картинка — числа; предсказание (ŷ) — сигмоида (σ) от взвешенной суммы (z); ошибка (L) — кросс-энтропия; обучение — спуск против наклона чаши; производные — одним вычитанием, — значит, тема усвоена.
