Машинное обучение

Полносвязные нейронные сети (MLP)

В 1989 году математик Джордж Цибенко доказал поразительную теорему: нейронная сеть всего с одним скрытым слоем может приблизить ЛЮБУЮ непрерывную функцию с произвольной точностью. Любую - синус, логарифм, распознавание лиц, перевод текста. Один слой нейронов - и вся математика мира у ваших ног. Но между теоремой существования и работающей моделью - пропасть. Как устроена эта сеть? Как данные проходят через неё? Как она учится из своих ошибок? Сегодня мы построим нейросеть с нуля - от архитектуры слоёв до работающего цикла обучения.

  • **Распознавание рукописного текста** - MLP стал первой архитектурой, решившей задачу MNIST (распознавание цифр 0-9 из картинок 28x28 пикселей) с точностью выше 98%, что позволило автоматизировать обработку почтовых индексов и банковских чеков
  • **Рекомендательные системы** - Netflix, Spotify и YouTube используют MLP для предсказания рейтингов: на вход подаются эмбеддинги пользователя и контента, на выходе - предсказанная оценка или вероятность клика
  • **Предсказание свойств молекул** - фармацевтические компании используют MLP для предсказания биологической активности молекул по их химическим дескрипторам, ускоряя поиск новых лекарств в десятки раз

От искусственного нейрона до многослойных сетей

Идея искусственного нейрона старше большинства компьютеров. В 1943 году нейрофизиолог Уоррен Маккалок и логик Уолтер Питтс предложили математическую модель нейрона: взвешенная сумма входов с пороговой активацией. Они показали, что сети из таких элементов способны вычислять логические функции. В 1958 году Фрэнк Розенблатт превратил эту абстракцию в обучаемый перцептрон. Но настоящий потенциал многослойных сетей раскрылся лишь в 1986 году, когда Дэвид Румельхарт, Джеффри Хинтон и Рональд Уильямс популяризировали алгоритм обратного распространения ошибки, позволивший эффективно обучать скрытые слои. Именно тогда стало ясно: складывая простые нейроны в слои и обучая их градиентным спуском, можно аппроксимировать сколь угодно сложные зависимости.

Предварительные знания

  • Perceptron and the Artificial Neuron

Слои нейронной сети

Один перцептрон может разделить данные только линейной границей. Задача XOR (исключающее ИЛИ) - четыре точки на плоскости, где (0,0) и (1,1) принадлежат классу 0, а (0,1) и (1,0) - классу 1 - не решается никакой одной прямой. В 1969 году Минский и Пейперт доказали это математически, что вызвало первую "зиму" нейронных сетей. Решение пришло через **многослойные сети**: несколько нейронов, организованных в слои, где каждый слой выполняет своё преобразование данных.

MLP (Multi-Layer Perceptron) состоит из трёх типов слоёв. **Input layer** (входной) - принимает исходные данные, по одному нейрону на каждый признак. **Hidden layers** (скрытые) - преобразуют данные, извлекая всё более абстрактные признаки. **Output layer** (выходной) - выдаёт предсказание: один нейрон для регрессии, один с sigmoid для бинарной классификации, или N нейронов с softmax для N классов.

**Подсчёт параметров - ключевой навык:** Для полносвязного слоя: `параметры = входы * выходы + выходы (bias)` Пример: сеть [784, 256, 128, 10] (классификация MNIST) - Слой 1: 784 * 256 + 256 = 200,960 - Слой 2: 256 * 128 + 128 = 32,896 - Слой 3: 128 * 10 + 10 = 1,290 - **Итого: 235,146 параметров** Больше параметров = больше ёмкость модели, но и больше риск overfitting при малых данных.

Зачем нужны скрытые слои? Каждый скрытый слой выполняет нелинейное преобразование данных. Первый скрытый слой может выучить простые паттерны (например, наличие горизонтальной линии в изображении). Второй - комбинации паттернов (буква состоит из линий). Третий - ещё более абстрактные признаки (слово состоит из букв). Это и есть **глубокое обучение (deep learning)** - иерархическое извлечение признаков.

**Width vs Depth - что важнее?** Широкая сеть (1 скрытый слой с 1000 нейронов) теоретически может аппроксимировать любую функцию, но на практике требует экспоненциально больше нейронов. Глубокая сеть (5 слоёв по 64 нейрона) достигает того же результата с меньшим числом параметров за счёт композиции простых преобразований. Правило: начинайте с 1-2 скрытых слоёв. Добавляйте глубину, только если модель недообучается.

Сеть имеет архитектуру [100, 64, 32, 10]. Сколько в ней обучаемых параметров (включая bias)?

Forward pass - прямое распространение

Forward pass - это процесс вычисления выхода сети по входным данным. Данные "текут" от input layer через скрытые слои к output layer. На каждом слое происходят два шага: **линейное преобразование** (матричное умножение + bias) и **нелинейная активация** (ReLU, sigmoid, tanh). Без функций активации вся сеть свелась бы к одному линейному преобразованию, сколько бы слоёв мы ни добавили.

Рассмотрим forward pass по шагам на конкретном примере. Пусть у нас сеть [2, 2, 1] с ReLU в скрытом слое и sigmoid на выходе. Входные данные: x = [1.0, 0.5]. Пройдём вычисления вручную, чтобы убедиться, что "магия" нейросети - это просто последовательность арифметических операций.

**Почему batch processing быстрее?** Обработка 1000 примеров по одному - это 1000 отдельных умножений матрица-вектор. Batch processing - одно умножение матрица-матрица. GPU оптимизированы именно для больших матричных операций: 1000 примеров за раз могут обрабатываться почти так же быстро, как 1. Типичные размеры batch: 32, 64, 128, 256.

Ключевой момент: forward pass - это **детерминированная функция**. При одинаковых весах и входах результат всегда одинаков. Вся "интеллектуальность" сети закодирована в весах W и bias b. Обучение нейросети - это поиск таких значений W и b, которые дают правильные предсказания. Но чтобы искать лучшие веса, нужно сначала понять, насколько текущие веса плохи - для этого нужна функция потерь.

Что произойдёт, если убрать все функции активации из MLP, оставив только линейные преобразования z = W @ x + b?

Функция потерь

Функция потерь (loss function) - это число, которое показывает, **насколько плохо** модель предсказывает. Чем больше loss - тем хуже. Цель обучения - минимизировать loss, подбирая веса сети. Выбор функции потерь зависит от задачи: для регрессии обычно используют **MSE** (Mean Squared Error), для классификации - **Cross-Entropy**. Неправильный выбор loss приведёт к тому, что сеть оптимизирует не то, что нам нужно.

**MSE (Mean Squared Error)** для регрессии: L = (1/n) * sum((y_i - y_hat_i)^2). Квадрат ошибки штрафует большие промахи непропорционально сильно: ошибка 10 даёт loss 100, ошибка 1 - loss 1. Это хорошо, если выбросы нежелательны. Если выбросы допустимы, используют **MAE** (Mean Absolute Error): L = (1/n) * sum(|y_i - y_hat_i|), который штрафует все ошибки линейно.

**Cross-Entropy** для классификации: L = -(1/n) * sum(y_i * log(y_hat_i)). Здесь y_i - правильный класс (one-hot вектор), y_hat_i - предсказанные вероятности. Почему именно логарифм? Если модель предсказывает правильный класс с вероятностью 0.99, loss = -log(0.99) = 0.01 (почти ноль, всё отлично). Если с вероятностью 0.01, loss = -log(0.01) = 4.6 (огромный штраф). Cross-Entropy жёстко наказывает уверенные неправильные предсказания.

LossЗадачаФормулаКогда использовать
MSEРегрессия(1/n) * sum((y - y_hat)^2)Стандартный выбор; штрафует выбросы
MAEРегрессия(1/n) * sum(|y - y_hat|)Робастна к выбросам; не дифференцируема в 0
Binary CE2 класса-mean(y*log(p) + (1-y)*log(1-p))Бинарная классификация; sigmoid на выходе
Categorical CEN классов-mean(sum(y_k * log(p_k)))Многоклассовая классификация; softmax на выходе
Huber LossРегрессияMSE при малых ошибках, MAE при большихКомпромисс MSE и MAE; параметр delta

**Выбор loss определяет поведение модели:** Loss - это то, что модель действительно оптимизирует. Если вы используете MSE для классификации, модель будет пытаться предсказать числа, а не вероятности, и результат будет плохим. Cross-Entropy для классификации неразрывно связана с softmax/sigmoid на выходе - они математически дополняют друг друга, обеспечивая стабильные градиенты.

Модель классификации на 3 класса предсказывает вероятности [0.1, 0.8, 0.1] для примера, правильный класс - 0 (первый). Какой будет Cross-Entropy loss для этого примера?

Цикл обучения

Обучение нейросети - это итеративный процесс из четырёх шагов, повторяемый тысячи раз: **forward pass** (получить предсказание) -> **compute loss** (измерить ошибку) -> **backward pass** (вычислить градиенты) -> **update weights** (обновить веса в направлении уменьшения ошибки). Backward pass (обратное распространение ошибки, backpropagation) вычисляет, насколько каждый вес повлиял на ошибку - это тема следующего урока. Здесь мы сосредоточимся на общей картине цикла обучения.

Ключевые термины: **эпоха (epoch)** - один полный проход по всему обучающему датасету. **Batch (мини-батч)** - подмножество данных, обрабатываемое за одну итерацию (обычно 32-256 примеров). **Итерация (step)** - обработка одного batch. Если в датасете 10000 примеров и batch size = 100, то одна эпоха = 100 итераций. Типичное обучение: 10-100 эпох, то есть модель видит каждый пример 10-100 раз.

**Overfitting vs Underfitting во время обучения:** - **Underfitting:** и train loss, и val loss высокие. Модель слишком простая - добавьте слоёв/нейронов, обучайте дольше. - **Хорошее обучение:** train loss и val loss снижаются вместе, val loss немного выше. - **Overfitting:** train loss падает, val loss начинает расти. Модель запоминает обучающие данные. Применяйте Early Stopping: остановите обучение, когда val loss перестал улучшаться.

  • **Learning rate** - самый важный гиперпараметр. Слишком большой - loss прыгает и не сходится. Слишком маленький - обучение занимает вечность. Начинайте с 0.001 для Adam, 0.01 для SGD.
  • **Инициализация весов** - не ставьте все веса в 0 (нейроны станут одинаковыми). PyTorch и TensorFlow используют разумные значения по умолчанию (Xavier/He initialization).
  • **Batch size** - маленький (32) даёт шумные, но частые обновления. Большой (256+) - стабильные, но редкие. Для большинства задач 32-128 хорошо работает.
  • **Adam optimizer** - хороший default выбор. Адаптирует learning rate для каждого веса отдельно, сходится быстрее SGD в большинстве случаев.
  • **Regularization** - Dropout (случайно отключает нейроны), weight decay (штраф за большие веса), early stopping (остановка при росте val loss). Используйте при overfitting.

**Критичные ошибки новичков:** 1. Забыть `optimizer.zero_grad()` перед backward - градиенты накапливаются и обновления становятся непредсказуемыми. 2. Не нормализовать входные данные - разные масштабы признаков замедляют обучение в разы. 3. Использовать sigmoid на выходе с CrossEntropyLoss - в PyTorch `nn.CrossEntropyLoss` уже включает softmax внутри, двойной softmax искажает вероятности.

Больше слоёв и нейронов = лучше модель. Нужно делать сеть как можно глубже и шире.

Архитектура сети должна соответствовать сложности задачи. Слишком большая сеть для простой задачи переобучается, расходует вычисления и сложнее в отладке.

Сеть с миллионом параметров для задачи с 1000 примерами запомнит обучающие данные вместо обобщения. На практике: начинайте с маленькой сети, увеличивайте только если train loss не снижается (underfitting). Добавляйте regularization (dropout, weight decay), если val loss растёт. Правильная сеть для задачи XOR - 2 скрытых нейрона, а не 2000.

Ключевые идеи

  • **Слои MLP:** input layer принимает данные, hidden layers извлекают иерархические признаки, output layer выдаёт предсказание - количество параметров полносвязного слоя = входы * выходы + bias
  • **Forward pass:** данные проходят через сеть слой за слоем: z = W @ x + b, a = activation(z) - без нелинейных активаций многослойная сеть сворачивается в одно линейное преобразование
  • **Функция потерь:** MSE для регрессии, Cross-Entropy для классификации - loss количественно измеряет ошибку модели и задаёт направление оптимизации
  • **Цикл обучения:** forward pass -> loss -> backward pass -> update weights, повторяется тысячи раз по батчам внутри эпох - overfitting диагностируется по расхождению train и val loss
  • **Теорема универсальной аппроксимации** - одного скрытого слоя достаточно для приближения любой функции, но правильный размер сети определяется сложностью задачи, а не желанием "взять побольше"

Связанные темы

MLP - фундамент глубокого обучения, связывающий линейные модели с современными архитектурами:

  • Перцептрон — Один нейрон MLP - это перцептрон с нелинейной активацией. MLP решает проблему линейной неразделимости (XOR), которую перцептрон не мог преодолеть, за счёт скрытых слоёв
  • Backpropagation — Алгоритм обратного распространения ошибки - сердце шага backward pass в цикле обучения. Он вычисляет градиент loss по каждому весу через chain rule, делая обучение глубоких сетей возможным

Вопросы для размышления

  • Теорема универсальной аппроксимации говорит, что одного скрытого слоя достаточно. Почему тогда на практике глубокие сети (много слоёв) обычно работают лучше неглубоких (один широкий слой)?
  • Как выбрать архитектуру MLP (количество слоёв и нейронов) для новой задачи, если у вас нет опыта с похожими данными?
  • В цикле обучения мы обновляем веса после каждого batch, а не после всего датасета. Какие преимущества и недостатки у такого подхода по сравнению с обновлением после полного прохода?

Связанные уроки

  • ml-24-perceptron — MLP - сеть из составленных перцептронов
  • ml-26-backpropagation — Бэкпроп - то, как эти сети реально учатся
  • ml-27-activation-functions — Нелинейные активации делают слои выразительными
  • la-07-matrix-multiply — Каждый слой - умножение матриц
  • dl-01 — Фундамент для всего трека глубокого обучения
  • aie-03-llm-fundamentals
Полносвязные нейронные сети (MLP)

0

1

Войти

В датасете 60000 примеров, batch size = 256. Сколько итераций (weight updates) произойдёт за 5 эпох?