$ cat toc.txt

В прошлой статье мы собрали сеть для распознавания цифр: 784 входа, два скрытых слоя, 10 выходов и 13 002 обучаемых параметра — веса и смещения. И остановились перед главным вопросом: как их найти? Перебирать вручную безнадёжно даже здесь, не говоря о миллиардах весов в LLM.

Эта статья — про алгоритм, который отвечает на этот вопрос. Спойлер: никакой мистики, «обучение» оказывается регулярным занятием из курса математического анализа — находим производные и делаем маленькие шаги вниз.

1. Что значит «сеть ошиблась»: функция потерь

Подаём на вход картинку с тройкой. Идеальный ответ сети: активация выходного нейрона «3» равна 1.0, остальных — 0.0. Реальный ответ необученной сети: что-то невнятное, вроде 0.5, 0.2, 0.1… по всем десяти нейронам.

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

$$ C = \left(a_0 - y_0\right)^2 + \left(a_1 - y_1\right)^2 + \dots + \left(a_9 - y_9\right)^2 $$

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

Важный сдвиг перспективы: теперь мы говорим не «сеть ошиблась», а «функция потерь приняла такое-то значение». Обучение = поиск весов и смещений, минимизирующих это среднее.

2. Ландшафт потерь: долина в 13 002 измерениях

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

У нашей сети параметров 13 002. График уже не нарисуешь, но математика та же: каждая точка этого пространства — один конкретный набор весов, а над ней «высота» — средняя потеря. Где-то там, в невообразимом многомерном тумане, есть глубокая долина — веса, при которых сеть хорошо распознаёт цифры.

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

3. Градиент: компас, указывающий вниз

Как искать долину? Наивно: шевелить параметры по одному и смотреть, что стало с потерями. Для 13 002 параметров это 13 002 вычисления на каждый шаг — и это ещё игрушечная сеть.

Красивое решение даёт матанализ: градиент — вектор из частных производных функции потерь по каждому параметру. Частная производная отвечает на вопрос «насколько чувствительны потери к этому конкретному весу, если сдвинуть его на капельку». Собранные вместе, эти числа показывают направление наискорейшего роста потерь. А значит, противоположный вектор, $-\nabla C$, — направление наискорейшего спуска.

Классическая метафора: вы стоите на склоне долины в густом тумане и хотите спуститься. Вы не видите долину целиком — но чувствуете уклон под ногами. Градиент — это уклон. Делаете шаг против него. Снова чувствуете уклон. Снова шаг. Так, шаг за шагом, туман не помеха.

И ещё одна полезная интерпретация: компоненты градиента показывают, какие изменения дадут «больше всего за потраченное усилие». Вес с большой производной — рычаг: маленький сдвиг сильно меняет потери. Вес с почти нулевой производной — мёртвая ручка, крутить её бессмысленно.

4. Шаг обучения: η, золотая середина

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

$$ \text{новые веса} = \text{старые веса} - \eta , \nabla C $$

где $\eta$ — скорость обучения (learning rate), размер шага. Эта константа — тонкое место:

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

На практике $\eta$ подбирают: начинают с типовых значений вроде 0.1 или 0.01 для простых сетей и смотрят на кривую потерь. Это одна из тех «ручек», которые реально крутят инженеры.

5. От случайных весов к 96% точности

Итак, полный цикл обучения:

  1. Инициализировать все 13 002 веса случайно (маленькие значения).
  2. Прогнать через сеть всю обучающую выборку, посчитать среднюю потерю.
  3. Вычислить градиент — чувствительность потерь к каждому весу.
  4. Сдвинуть все веса против градиента: $w \leftarrow w - \eta \nabla C$.
  5. Повторять, пока потери не перестанут заметно снижаться.

Для нашей игрушечной сети на MNIST этот примитивный процесс даёт порядок 95–96% правильных ответов на тестовых картинках. Не рекорд (state-of-the-art на MNIST давно выше 99%), но поучительно: никто не говорил сети, что такое «петля» или «краешек» — она пришла к работающему распознаванию сама, через минимизацию одной числовой метрики.

Пара честных оговорок. Мы рисуем «спуск по долине», но в 13 002-мерном пространстве долин может быть много: градиентный спуск находит какой-то минимум, не обязательно самый глубокий. Для нейросетей это обычно не катастрофа — достаточно хороший локальный минимум распознаёт цифры вполне прилично. И второе: считать градиент по всей выборке из десятков тысяч примеров на каждом шаге дорого; как это ускоряют мини-батчами — разберём в следующей статье.

6. Почему это важно знать инженеру

Градиентный спуск — не музейный экспонат из мира цифр MNIST, это рабочий алгоритм №1 во всём машинном обучении.

Fine-tuning LLM. Когда вы дообучаете локальную модель через Ollama или LoRA, внутри происходит ровно то, что описано выше: считаются потери (для языковых моделей — cross-entropy, мы разбирали её связь с теорией информации Шеннона), вычисляется градиент, делается шаг с каким-то $\eta$. «Learning rate» в конфиге fine-tuning — это буквально $\eta$ из нашей формулы. Слишком большой — loss начнёт расходиться, слишком маленький — застрянет.

Кривая потерь — ваш главный график. В логах обучения (TensorBoard, wandb, логи LoRA-скриптов) вы всегда видите кривую loss. Теперь вы знаете, как её читать: плавно падает — спуск идёт; скачет вверх — $\eta$ велик; вышла на плато — минимум достигнут (или застряли).

Общий язык с классическим ML. Логистическая регрессия, градиентный бустинг, нейросети, трансформеры — все учатся градиентным спуском по функции потерь. Поняв механику один раз, вы понимаете устройство всей области.

Заключение

Обучение нейросети свелось к честной процедуре: определить числовую меру ошибки (функцию потерь), почувствовать уклон (градиент) и спускаться маленькими шагами (со скоростью обучения $\eta$). Никакой фантастики — скорее упражнение из матанализа.

Но в шаге 3 спрятан нераскрытый вопрос: как вычислить градиент — 13 002 частных производных — не тратя безумное количество вычислений? Ответ — алгоритм backpropagation, и это тема следующей статьи: Backpropagation: как нейросеть решает, кого винить в ошибке.

← все посты [поделиться] [rss]