Введение в AI

Модуль 5: Введение в нейросети

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

Что вы узнаете

  • Как работает перцептрон (искусственный нейрон)
  • Зачем нужны функции активации и чем отличаются Sigmoid, ReLU, Tanh
  • Как из нейронов собирается многослойная сеть (MLP)
  • Что такое Forward Pass и как данные проходят через сеть
  • Как реализовать простую нейросеть на Python с нуля

5.1 Перцептрон (искусственный нейрон)

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

Формула перцептрона

output = activation(w₁·x₁ + w₂·x₂ + ... + wₙ·xₙ + b)

  • x₁, x₂, ..., xₙ — входные сигналы (признаки)
  • w₁, w₂, ..., wₙ — веса (обучаемые параметры)
  • b — смещение (bias), позволяет сдвигать порог срабатывания
  • activation — функция активации (нелинейность)

Аналогия с биологическим нейроном

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

Биологический нейронИскусственный нейрон
Дендриты (входы)Входные данные x₁, x₂, ...
Сила синапсаВеса w₁, w₂, ...
Тело нейрона (суммирование)Взвешенная сумма + bias
Порог срабатыванияФункция активации
Аксон (выход)Output нейрона

Один нейрон = линейный классификатор

Один перцептрон с пороговой функцией активации способен решить только линейно разделимые задачи — те, где классы можно разделить прямой (в 2D) или гиперплоскостью. Классический пример невозможной задачи — XOR (исключающее ИЛИ). Именно для решения таких задач нейроны объединяют в многослойные сети.

Историческая справка: Перцептрон был предложен Фрэнком Розенблаттом в 1957 году. В 1969 году Минский и Паперт показали ограничения одного перцептрона (невозможность решить XOR), что привело к «зиме AI». Решение пришло с многослойными сетями и алгоритмом обратного распространения ошибки.

5.2 Функции активации

Функция активации вносит нелинейность в работу нейрона. Без неё любая многослойная сеть была бы эквивалентна одному линейному преобразованию (композиция линейных функций — всё ещё линейная функция).

Sigmoid (Логистическая функция)

σ(x) = 1 / (1 + e⁻ˣ)

  • Выход: от 0 до 1 — удобно интерпретировать как вероятность
  • Гладкая, дифференцируемая
  • Проблема: при больших |x| производная ≈ 0 (насыщение) → затухание градиентов
  • Выход не центрирован (всегда > 0) → медленная сходимость
  • Применение: выходной слой для бинарной классификации

Tanh (Гиперболический тангенс)

tanh(x) = (eˣ - e⁻ˣ) / (eˣ + e⁻ˣ)

  • Выход: от −1 до 1 — центрированный (среднее ≈ 0)
  • Быстрее сходится, чем sigmoid, т.к. выход центрирован
  • Проблема: всё ещё насыщается при больших |x|
  • Применение: скрытые слои RNN (до эпохи трансформеров)

ReLU (Rectified Linear Unit)

ReLU(x) = max(0, x)

  • Проще всего: если x > 0 → пропускаем, иначе → 0
  • Не насыщается при x > 0 → нет затухания градиента
  • Очень быстрое вычисление (просто сравнение с нулём)
  • Проблема: «мёртвые нейроны» (dying ReLU) — если нейрон всегда выдаёт 0, его градиент = 0 и он не обучается
  • Применение: стандарт для скрытых слоёв в большинстве современных сетей

Leaky ReLU и другие варианты

LeakyReLU(x) = x если x > 0, иначе α·x (обычно α = 0.01)

Решает проблему «мёртвых нейронов» — при отрицательных входах всё равно пропускает маленький градиент. Существуют и другие варианты: ELU, GELU (используется в трансформерах), Swish (SiLU).

Сравнение функций активации

ФункцияДиапазонПлюсыМинусыГде используется
Sigmoid[0, 1]Вероятностная интерпретацияЗатухание градиентовВыходной слой (бинарная кл.)
Tanh[−1, 1]Центрированный выходЗатухание градиентовRNN, старые архитектуры
ReLU[0, +∞)Быстро, без насыщенияМёртвые нейроныCNN, MLP (стандарт)
GELU≈[−0.17, +∞)Гладкая, хорошо работаетДороже ReLUТрансформеры (BERT, GPT)

Практический совет

Начинайте с ReLU для скрытых слоёв. Если видите «мёртвые нейроны» (многие выходы = 0) — попробуйте Leaky ReLU. Для выхода: sigmoid (бинарная классификация), softmax (многоклассовая), без активации (регрессия).

5.3 Архитектура полносвязной сети (MLP)

MLP (Multi-Layer Perceptron) — это сеть из нескольких слоёв нейронов, где каждый нейрон одного слоя связан со всеми нейронами следующего.

Структура MLP

Три типа слоёв

  • Входной слой — количество нейронов = количество признаков (фичей). Не выполняет вычислений, просто передаёт данные
  • Скрытые слои — один или несколько. Каждый нейрон: взвешенная сумма + активация. Эти слои учатся находить промежуточные представления (абстракции) данных
  • Выходной слой — количество нейронов зависит от задачи:
    • Регрессия: 1 нейрон, без активации (или линейная)
    • Бинарная классификация: 1 нейрон + sigmoid
    • Многоклассовая классификация: N нейронов + softmax

  Входной слой     Скрытый слой 1    Скрытый слой 2    Выходной слой
  (3 признака)      (4 нейрона)       (4 нейрона)       (2 класса)

     [x₁] ─────→ [h₁] ─────→ [h₅] ─────→ [y₁]  (класс A)
           ╲   ╱       ╲   ╱       ╲   ╱
     [x₂] ──╳──→ [h₂] ──╳──→ [h₆] ──╳──→ [y₂]  (класс B)
           ╱   ╲       ╱   ╲       ╱   ╲
     [x₃] ─────→ [h₃] ─────→ [h₇] ─────→
                       │             │
                  [h₄] ─────→ [h₈] ─────→

  Каждый нейрон связан с каждым нейроном следующего слоя (полносвязность)

Почему «глубина» работает?

Каждый скрытый слой учится представлять данные на всё более высоком уровне абстракции:

  • Слой 1: простые паттерны (линии, пороги, базовые комбинации признаков)
  • Слой 2: комбинации паттернов (углы, формы, сложные условия)
  • Слой N: высокоуровневые абстракции (объекты, концепции)

Именно поэтому глубокие сети (deep learning) эффективнее мелких — они строят иерархию абстракций.

Гиперпараметры MLP

  • Количество скрытых слоёв — обычно 1–5 для табличных данных, десятки-сотни для изображений и текста
  • Количество нейронов в слое — типичные значения: 32, 64, 128, 256, 512
  • Функция активации — ReLU для скрытых, sigmoid/softmax для выходного
  • Dropout — случайное «выключение» части нейронов при обучении для предотвращения переобучения

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

5.4 Прямое распространение (Forward Pass)

Forward Pass — это процесс прохождения данных от входа к выходу сети. На каждом слое выполняются два шага:

  1. Линейное преобразование: z = W · x + b (матрица весов × вектор входа + вектор смещений)
  2. Функция активации: a = activation(z)

Выход одного слоя a становится входом x для следующего. В конце получаем предсказание модели.

Пошагово: Forward Pass для сети с 2 скрытыми слоями


Вход:     x = [0.5, 0.3, 0.8]

Слой 1:   z₁ = W₁ · x + b₁        (линейная часть)
          a₁ = ReLU(z₁)            (нелинейная часть)

Слой 2:   z₂ = W₂ · a₁ + b₂
          a₂ = ReLU(z₂)

Выход:    z₃ = W₃ · a₂ + b₃
          ŷ  = softmax(z₃)         (вероятности классов)

Зачем сохранять промежуточные значения?

Все промежуточные значения (z₁, a₁, z₂, a₂, ...) сохраняются в памяти. Они понадобятся на этапе обратного распространения ошибки (backpropagation) для вычисления градиентов и обновления весов. Это один из компромиссов: больше памяти → быстрее обучение.

Ключевая идея

Forward Pass — это просто цепочка матричных умножений + нелинейностей. GPU отлично справляется с матричными умножениями, поэтому нейросети так хорошо ускоряются на видеокартах.

5.5 Код: нейросеть на Python с нуля

Реализуем простую полносвязную сеть только на NumPy, чтобы понять механику. Никаких фреймворков — чистая математика.

Простой нейрон (перцептрон)

import numpy as np

# Функции активации
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def relu(x):
    return np.maximum(0, x)

# Простой нейрон
def neuron(inputs, weights, bias):
    """Один нейрон: взвешенная сумма + активация"""
    z = np.dot(inputs, weights) + bias   # линейная часть
    output = sigmoid(z)                   # нелинейная часть
    return output

# Пример: нейрон с 3 входами
inputs = np.array([0.5, 0.3, 0.8])
weights = np.array([0.4, -0.2, 0.6])
bias = 0.1

result = neuron(inputs, weights, bias)
print(f"Вход: {inputs}")
print(f"Веса: {weights}, bias: {bias}")
print(f"Выход нейрона: {result:.4f}")
# Выход нейрона: 0.6457

Полносвязная сеть (MLP) с нуля

import numpy as np

class SimpleNN:
    """Простая нейросеть: вход → скрытый слой → выход"""
    
    def __init__(self, input_size, hidden_size, output_size):
        # Инициализация весов (случайные малые числа)
        self.W1 = np.random.randn(input_size, hidden_size) * 0.01
        self.b1 = np.zeros(hidden_size)
        self.W2 = np.random.randn(hidden_size, output_size) * 0.01
        self.b2 = np.zeros(output_size)
    
    def relu(self, x):
        return np.maximum(0, x)
    
    def sigmoid(self, x):
        return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
    
    def forward(self, X):
        """Forward Pass: данные проходят через сеть"""
        # Скрытый слой
        self.z1 = X @ self.W1 + self.b1   # линейное преобразование
        self.a1 = self.relu(self.z1)        # активация ReLU
        
        # Выходной слой
        self.z2 = self.a1 @ self.W2 + self.b2
        self.a2 = self.sigmoid(self.z2)     # sigmoid для вероятности
        
        return self.a2
    
    def predict(self, X):
        """Предсказание: 0 или 1"""
        probabilities = self.forward(X)
        return (probabilities >= 0.5).astype(int)


# Пример: классификация XOR (задача, неразрешимая одним перцептроном!)
X = np.array([[0, 0],
              [0, 1],
              [1, 0],
              [1, 1]])

y = np.array([[0], [1], [1], [0]])  # XOR

# Создаём сеть: 2 входа → 4 скрытых → 1 выход
nn = SimpleNN(input_size=2, hidden_size=4, output_size=1)

# Forward pass (без обучения — веса случайные)
predictions = nn.forward(X)
print("Предсказания (случайные веса):")
for i in range(len(X)):
    print(f"  {X[i]} → {predictions[i][0]:.4f}")

Важно: Этот код показывает только forward pass. Для обучения нужен ещё backward pass (backpropagation) — вычисление градиентов и обновление весов. Это тема следующего модуля.

То же самое на PyTorch (2 строки)

import torch
import torch.nn as nn

# Та же сеть, но средствами PyTorch
model = nn.Sequential(
    nn.Linear(2, 4),     # входной → скрытый (2 → 4)
    nn.ReLU(),            # активация
    nn.Linear(4, 1),     # скрытый → выходной (4 → 1)
    nn.Sigmoid()          # вероятность на выходе
)

# Forward pass
X = torch.tensor([[0., 0.], [0., 1.], [1., 0.], [1., 1.]])
output = model(X)
print(output)  # 4 вероятности (случайные веса)

# Посмотрим количество параметров
total_params = sum(p.numel() for p in model.parameters())
print(f"Всего параметров: {total_params}")
# 2×4 + 4 + 4×1 + 1 = 17 параметров

Сравнение: с нуля vs фреймворк

Писать с нуля полезно для понимания. В реальных проектах используйте PyTorch или TensorFlow — они автоматически вычисляют градиенты (autograd), поддерживают GPU и имеют готовые слои, оптимизаторы, даталоадеры.

5.6 Практические упражнения

Упражнение 1: Поиграйте с нейроном

Скопируйте код «простого нейрона» выше и попробуйте:

  • Измените веса — как меняется выход?
  • Замените sigmoid на relu — что происходит?
  • Поставьте bias = −10 — какой будет выход sigmoid?

Упражнение 2: Посчитайте параметры

Сеть: 784 входов (изображение 28×28) → 128 нейронов → 64 нейрона → 10 выходов (цифры 0–9).

  • Сколько весов в каждом слое?
  • Сколько bias'ов?
  • Общее число параметров?

Ответ: 784×128 + 128 + 128×64 + 64 + 64×10 + 10 = 109 386 параметров.

Упражнение 3: TensorFlow Playground

Откройте playground.tensorflow.org и попробуйте:

  • Решить задачу «спираль» одним скрытым слоём — получится?
  • Добавьте 2–3 скрытых слоя — как меняется граница решения?
  • Попробуйте разные функции активации

Что дальше: В следующем модуле разберём Backpropagation — как сеть учится, вычисляя градиенты и обновляя веса. Это замыкает цикл: Forward Pass (предсказание) → Loss (ошибка) → Backward Pass (градиенты) → Update (обновление весов).

Настройки

Тема