В лекции престижного курса Стэнфордского университета CS231N рассматриваются фундаментальные основы глубокого обучения — нейронные сети и алгоритм обратного распространения ошибки (backpropagation). Преподаватель подробно разбирает математический аппарат, который позволяет искусственным сетям учиться на собственных ошибках, подобно человеку, но в строго организованной математической форме. Этот материал закладывает базу для всех последующих алгоритмов компьютерного зрения, обсуждаемых в рамках учебного курса.
🔄 Повторение пройденного: функции потерь и оптимизация 1:01
Лекция традиционно начинается с краткого обзора материала прошлых занятий. Ранее были сформулированы целевые функции, известные как функции потерь (loss functions), а также концепция регуляризации. Все вычисления строились вокруг пар данных $(X, Y)$ и линейной функции подсчета очков (scoring function).
Лектор подчеркивает, что Softmax не является единственной доступной функцией потерь. Хотя она широко применяется в глубоком обучении для задач классификации, существуют и другие альтернативы. В качестве примера приводится функция Hinge Loss (исторически известная как SVM loss), которая подробно рассматривалась в дополнительных материалах ко второй лекции.
В отличие от Softmax, Hinge Loss не превращает полученные баллы в вероятности. Она мотивирует сеть увеличивать оценку правильного класса $s_{y_i}$ по сравнению с оценками других классов $s_j$ как минимум на величину заданного зазора (margin), равного единице. Если это условие нарушается, штраф растет пропорционально величине нарушения.
Для поиска оптимальных параметров весов $W$ используется метафора ландшафта потерь (loss landscape), представляющего собой огромную долину. Каждая точка в этой долине соответствует определенному набору весов, и задача оптимизации сводится к поиску минимума. Ключом к решению является вычисление градиента функции потерь по отношению к весам $\nabla_W L$ и пошаговое движение в противоположном градиенту направлении — так устроен алгоритм градиентного спуска (Gradient Descent). Шаг оптимизации регулируется размером шага (step size) или скоростью обучения (learning rate).
В реальной практике вычисление точного аналитического градиента на всем датасете слишком затратно, поэтому данные разбиваются на мини-батчи (mini-batches). Типичный размер батча составляет 32, 64, 128 или 256 примеров. На основе этих подвыборок рассчитываются градиенты для шагов оптимизации.
Помимо классического стохастического градиентного спуска (SGD), лектор напоминает о существовании продвинутых оптимизаторов:
- SGD с моментумом (импульсом)
- RMSprop
- Adam
Важным аспектом является настройка расписания скорости обучения (learning rate scheduling). В некоторых оптимизаторах начинают с большого шага, постепенно уменьшая его на определенный коэффициент. Однако в современных алгоритмах, таких как Adam и его вариациях, ручное снижение скорости обучения зачастую не требуется, поскольку этот механизм уже встроен внутрь самого оптимизатора.
⚡ Переход к нейронным сетям и сила нелинейности 9:53
Самая базовая нейросеть представляет собой простую линейную функцию произведения весов на входные данные $f = W \cdot x$. В такой архитектуре есть лишь один слой. Входные данные имеют размерность $D$ (количество признаков), а выходные — размерность $C$ (количество предсказываемых классов).
Чтобы построить двухслойную нейронную сеть, вводится второй набор весов $W_2$. Он применяется к результату первого слоя $W_1 \cdot x$. При этом задается скрытый слой размерностью $H$, определяющий число скрытых нейронов. В формуле также используется функция max, которая выполняет критически важную роль — создание нелинейности между линейными трансформациями первого и второго слоев.
Полная формула принимает вид:
$$f = W_2 \cdot \max(0, W_1 \cdot x)$$
Для простоты изложения в формулах опускается вектор смещения (bias), хотя в реальной практике он обязательно добавляется для обеспечения полноты математической модели.
Без нелинейной операции max построение многослойной сети теряет смысл. Если убрать ее, выражение превратится в $W_2 \cdot W_1 \cdot x$. Матричное произведение двух весов можно легко заменить единой матрицей $W_3$, и вся сеть схлопнется обратно в обычную линейную функцию.
Нелинейность необходима, так как в реальном мире огромное количество данных невозможно разделить одной прямой линией; требуется нелинейное преобразование признакового пространства. Например, перевод декартовых координат $(x, y)$ в полярные $(r, \theta)$ делает классы разделимыми с помощью простой линии.
В литературе сети, где выполняются только последовательные матричные умножения и нелинейные активации слой за слоем, называют полносвязными сетями (fully connected networks) или многослойными перцептронами (MLP). Разработчики могут наслаивать их друг на друга, создавая огромные глубокие архитектуры.
С увеличением количества слоев сеть получает возможность формировать многоуровневые шаблоны. Если линейный классификатор строит всего 10 шаблонов для 10 классов изображений, то скрытый слой, например, со 100 нейронами, позволяет обучить 100 промежуточных шаблонов. На интуитивном уровне это дает сети способность распознавать не объект целиком, а его отдельные составные части (например, глаза, которые есть и у птиц, и у кошек, и у собак).
🧪 Зоопарк функций активации: от ReLU до современных вариантов 17:37
Функция нелинейности в терминологии нейросетей называется функцией активации (activation function). Она играет стержневую роль в обучении моделей. Наиболее популярным выбором долгое время остается ReLU (Rectified Linear Unit) — выпрямленный линейный элемент. Однако у ReLU есть существенный недостаток: она может приводить к появлению «мертвых нейронов», так как полностью зануляет любые отрицательные входные значения.
Чтобы решить проблему «отмирания» нейронов, исследователи разработали альтернативные варианты функций активации:
- Leaky ReLU: Пропускает небольшой отрицательный градиент вместо полного зануления.
- ELU (Exponential Linear Unit): Экспоненциальная функция, которая обеспечивает лучшую центрированность значений вокруг нуля и плавность.
- GELU (Gaussian Error Linear Unit): Современная модификация (произносится как «джелло» или «елло»), завоевавшая популярность в архитектурах трансформеров.
- SiLU (Sigmoid Linear Unit) / Swish: Используется в современных сверточных сетях, например, в разработанном Google семействе моделей EfficientNet.
Классические функции, такие как сигмоида (Sigmoid) и гиперболический тангенс (Tanh), сжимают выходные значения в очень узкий диапазон. По словам лектора, это часто приводит к затуханию градиентов (vanishing gradients), поэтому их практически никогда не используют во внутренних скрытых слоях. Сигмоиду и тангенс обычно оставляют для финальных слоев, где необходимо выполнить бинаризацию выходов или получить вероятности.
Отвечая на вопрос аудитории о принципах выбора функции активации под новую задачу, лектор поясняет, что этот процесс носит преимущественно эмпирический характер. На практике инженеры обычно начинают со стандартной ReLU или берут те функции активации, которые уже успешно зарекомендовали себя в аналогичных архитектурах (например, в CNN или трансформерах). Главным общим свойством всех этих функций является обеспечение нелинейности, дифференцируемость и содействие более быстрой сходимости сети за счет гладкости или центрирования данных.
📐 Размер нейросети и тонкости регуляризации 26:06
Реализация двухслойной нейросети на Python занимает всего около 20 строк кода. В коде фиксируются параметры: количество образцов $N$, размерность входа $D_{in}$, количество нейронов в скрытом слое $H$ и размерность выхода $D_{out}$. Процесс обучения включает прямой проход (forward pass) для расчета предсказаний и вычисления функции потерь, а также обратный проход (backward pass) для расчета аналитических градиентов и обновления весов методом градиентного спуска.
Количество скрытых нейронов напрямую определяет емкость (capacity) модели: чем больше нейронов, тем более сложные и детальные разделяющие границы способна строить сеть. Избыточная емкость может приводить к переобучению (overfitting), когда сеть просто зазубривает обучающую выборку и теряет способность к генерализации на новых данных.
Лектор озвучивает важное золотое правило: никогда не используйте размер нейросети (количество нейронов или слоев) в качестве регуляризатора. Вместо манипулирования размером сети для борьбы с переобучением, правильный подход заключается в выборе достаточно большой сети и последующей точной настройке коэффициента регуляризации $\lambda$ (лямбда).
Параметр $\lambda$ управляет тем, какой вклад штраф за величину весов вносит в общую функцию потерь. Чем выше значение $\lambda$, тем жестче ограничения, накладываемые на веса $W$, и тем меньше у них свободы. Слишком сильная регуляризация делает разделяющие границы избыточно простыми и размытыми, что приводит к недообучению (underfitting) модели.
Оптимальный регуляризатор всегда ищет баланс между минимизацией ошибки предсказания и удержанием весов в разумных пределах. Поиск идеального количества слоев и нейронов всегда завязан на проведение экспериментов и анализ аналогичных архитектур, созданных для похожих типов данных.
🧠 Биологические аналогии: вдохновение, а не точная копия 35:47
Существуют определенные биологические предпосылки создания нейросетей, однако лектор призывает относиться к ним с крайней осторожностью. Проводя очень свободную аналогию, можно заметить сходство с устройством реального биологического нейрона, имеющего тело клетки (сому), дендриты и аксон.
Дендриты собирают поступающие от других клеток импульсы и передают их в тело клетки, где сигналы агрегируются. Затем через аксон этот суммированный импульс уходит к следующим нейронам. В искусственных нейросетях эту роль выполняет математическая функция: она собирает активации предыдущего слоя, обрабатывает их внутри «тела» нейрона и с помощью функции активации формирует выходной сигнал для передачи дальше.
Тем не менее, биологические нейроны устроены несопоставимо сложнее, чем их математические абстракции. Искусственные нейросети организуются в строгие регулярные структуры и слои главным образом для обеспечения высокой вычислительной эффективности при программной реализации на компьютерах. Лектор отдельно предостерегает студентов от увлечения глубокими аналогиями с работой человеческого мозга, подчеркивая сугубо прикладной математический характер обучаемых моделей.
📊 Вычислительные графы и концепция Backpropagation 39:01
В полносвязных сетях оптимизация параметров $W_1$ и $W_2$ требует вычисления частных производных функции потерь $L$ по отношению к этим весам. Считать аналитические градиенты вручную на бумаге — занятие крайне утомительное и подверженное ошибкам, сопряженное с громоздкими матричными вычислениями. Любое минимальное изменение в архитектуре или функции потерь вынуждает инженера пересчитывать все формулы заново, что делает ручной подход нежизнеспособным при усложнении моделей.
Эффективным решением этой проблемы стали вычислительные графы (computational graphs) и алгоритм обратного распространения ошибки (backpropagation). Вычислительный граф раскладывает сложную функцию на цепочку простых последовательных шагов, где на каждом узле выполняется элементарная математическая операция, а финальным выходом является значение потерь. К графу подходят входные данные $X$ и параметры весов $W$, которые перемножаются для вычисления финального счета, а затем суммируются со штрафом регуляризатора.
Ручной расчет производных для комплексных систем, таких как нейронные машины Тьюринга (Neural Turing Machines), работающие с последовательными данными, был бы абсолютно невозможен без автоматизации через граф.
Для демонстрации работы алгоритма лектор приводит простейший пример функции:
$$f(x, y, z) = (x + y) \cdot z$$
Пусть заданы начальные значения: $x = -2, y = 5, z = -4$. Процесс вычислений разбивается на этапы. Сначала выполняется операция сложения, результат которой обозначается промежуточной переменной $q = x + y$. В числовом выражении $q = -2 + 5 = 3$.
Локальные частные производные для этого узла тривиальны: $\frac{\partial q}{\partial x} = 1$ и $\frac{\partial q}{\partial y} = 1$. Следующим шагом выполняется умножение: $f = q \cdot z$, что дает итоговое значение $3 \cdot (-4) = -12$. Локальные производные этого узла также легко находятся через правила алгебры: $\frac{\partial f}{\partial q} = z = -4$ и $\frac{\partial f}{\partial z} = q = 3$.
🔗 Пошаговый разбор: локальные градиенты и цепное правило 46:28
Чтобы найти производные финальной функции $f$ по отношению ко всем трем исходным переменным $x, y, z$, алгоритм обратного распространения начинает движение с самого конца сети, двигаясь в обратную сторону. Это рекурсивный процесс. Производная функции по отношению к самой себе всегда равна единице: $\frac{\partial f}{\partial f} = 1$.
Двигаясь назад, мы первыми встречаем переменные $z$ и $q$, напрямую связанные с выходом. Градиент для $z$ равен локальной производной, помноженной на единицу, то есть $\frac{\partial f}{\partial z} = q = 3$. Аналогично, градиент для промежучного узла $q$ составляет $\frac{\partial f}{\partial q} = z = -4$.
Сложнее обстоит дело с переменными $x$ и $y$, которые не имеют прямой связи с финальным выходом $f$. Здесь в силу вступает математическое цепное правило (chain rule), позволяющее расщепить вычисление через промежуточную переменную $q$:
$$\frac{\partial f}{\partial y} = \frac{\partial f}{\partial q} \cdot \frac{\partial q}{\partial y}$$
Лектор вводит два ключевых термина, обязательных для понимания backpropagation:
- Входящий (апстрим) градиент (upstream gradient): Градиент, который пришел в текущий узел с последующих слоев из конца сети.
- Локальный градиент (local gradient): Собственная производная выхода текущего узла по отношению к его непосредственным входам.
Для переменной $y$ входящий градиент равен $-4$ (значение $\frac{\partial f}{\partial q}$), а локальный градиент равен $1$ (значение $\frac{\partial q}{\partial y}$). Перемножая их, мы получаем финальный исходящий (даунстрим) градиент, равный $-4$.
Точно такая же логика применяется к переменной $x$: локальный градиент $1$ умножается на входящий градиент $-4$, давая на выходе $-4$.
Каждый узел графа работает изолированно и модульно: ему достаточно знать свои локальные производные и дождаться входящего градиента сверху, чтобы перемножить их и передать результат дальше назад по сети. Интуитивно этот процесс означает, что мы пробрасываем градиент функции потерь ко всем переменным и весам без необходимости выписывать гигантскую аналитическую формулу для всей нейросети целиком.
🛠️ Модульный подход и типовые математические «ворота» 1:00:47
На основе анализа элементарных операций лектор выделяет несколько устойчивых паттернов поведения узлов (или «ворот», gates) в вычислительном графе, которые полезно запомнить:
- Суммирующие ворота (add gate): Выступают в роли «дистрибьютора градиента». Поскольку локальная производная сложения всегда равна единице, этот узел просто берет входящий градиент и без изменений передает его на все свои входы.
- Умножающие ворота (multiply gate): Работают как «свап-функция» (переключатель). Они берут входящий градиент и умножают его на значение соседнего входа (меняют значения переменных местами при вычислении производной).
- Ворота копирования (copy gate): Суммируют градиенты, приходящие от разных ветвей графа, куда раздваивались исходные данные.
- Ворота максимума (max gate): Действуют как направленный роутер. Поскольку операция максимума (как в ReLU) выбирает только один наибольший элемент, узел направляет весь входящий градиент исключительно в сторону этого максимального входа, а остальные пути зануляет.
Этот паттерн позволяет инженерам создавать чистые модульные программные API с четко определенными методами forward() и backward() для каждого оператора. Метод forward вычисляет выходные значения и кэширует (запоминает) входные данные, так как они понадобятся для вычисления локальных градиентов на обратном пути.
Метод backward принимает входящий градиент и возвращает производные по входам. Именно по такой модульной схеме построены современные библиотеки глубокого обучения, включая PyTorch.
🧮 Масштабирование алгоритма до векторов и матриц 1:04:31
Все рассмотренные ранее примеры оперировали скалярными величинами, однако реальные нейросети обрабатывают данные в виде векторов, матриц и многомерных тензоров. Если в скалярном режиме производная показывает, как изменится число $y$ при минимальном сдвиге числа $x$, то при переходе к векторам размерностью $N$ градиент функции по отношению к вектору сам становится вектором аналогичной длины. В случае отображения вектора в вектор производные выстраиваются в многомерную матрицу Якоби (Jacobian).
При работе с мини-батчами размерностью, например, 64 и скрытыми слоями с размерностью признаков 4096, попытка рассчитать и сохранить матрицу Якоби в явном виде приведет к катастрофической нехватке памяти. Для одного-единственного матричного умножения размер такой промежуточной матрицы превысил бы 256 ГБ.
Чтобы избежать вычисления гигантских разреженных матриц Якоби, в backward-методах библиотек используются оптимизированные правила матричного умножения. Зная, как именно элементы влияют друг на друга, операцию можно свести к двум лаконичным формулам.
Исходящий градиент по отношению к матрице входов $X$ рассчитывается через умножение входящего градиента на транспонированную матрицу весов $W$. Градиент по отношению к весам $W$ находится через перемножение транспонированной матрицы $X$ на входящий градиент. Этот элегантный математический аппарат позволяет эффективно обучать нейросети колоссальных масштабов.