Как происходит обучение классических нейросетей?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как происходит обучение классических нейросетей
Обучение нейросетей основано на алгоритме обратного распространения ошибки (Backpropagation) и градиентном спуске. Разберу весь процесс пошагово.
Архитектура простой нейросети
Классическая нейросеть состоит из:
- Входной слой (input layer) — принимает признаки
- Скрытые слои (hidden layers) — обрабатывают информацию
- Выходной слой (output layer) — выдает предсказание
Вход (x1, x2, x3) → Скрытый слой 1 → Скрытый слой 2 → Выход (y)
Каждый нейрон выполняет операцию:
z = w1*x1 + w2*x2 + ... + wn*xn + b
a = activation_function(z)
Где:
- w — веса (weights)
- b — смещение (bias)
- activation_function — функция активации (ReLU, Sigmoid, Tanh)
Функция потерь (Loss Function)
Для классификации используем cross-entropy, для регрессии — MSE:
# Cross-entropy (классификация)
from tensorflow.keras.losses import CategoricalCrossentropy
loss = CategoricalCrossentropy()
# loss = -sum(y_true * log(y_pred))
# MSE (регрессия)
from tensorflow.keras.losses import MeanSquaredError
loss = MeanSquaredError()
# loss = mean((y_true - y_pred)^2)
Алгоритм обучения: Backpropagation (Обратное распространение)
Шаг 1: Forward Pass (прямой проход)
Данные проходят через сеть от входа к выходу:
import numpy as np
class NeuralNetwork:
def __init__(self, layer_sizes):
self.weights = []
self.biases = []
# Инициализация весов случайно (Xavier initialization)
for i in range(len(layer_sizes) - 1):
w = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * 0.01
b = np.zeros((1, layer_sizes[i+1]))
self.weights.append(w)
self.biases.append(b)
def relu(self, z):
return np.maximum(0, z)
def relu_derivative(self, z):
return (z > 0).astype(float)
def softmax(self, z):
exp_z = np.exp(z - np.max(z, axis=1, keepdims=True))
return exp_z / np.sum(exp_z, axis=1, keepdims=True)
def forward(self, X):
self.activations = [X] # Сохраняем для backprop
self.z_values = [] # Сохраняем z для backprop
a = X
# Скрытые слои
for i in range(len(self.weights) - 1):
z = np.dot(a, self.weights[i]) + self.biases[i]
a = self.relu(z)
self.z_values.append(z)
self.activations.append(a)
# Выходной слой (softmax для классификации)
z = np.dot(a, self.weights[-1]) + self.biases[-1]
a = self.softmax(z)
self.z_values.append(z)
self.activations.append(a)
return a
Шаг 2: Вычисление потерь
def compute_loss(self, y_pred, y_true):
m = y_true.shape[0]
# Cross-entropy
epsilon = 1e-7 # Избегаем log(0)
loss = -np.sum(y_true * np.log(y_pred + epsilon)) / m
return loss
Шаг 3: Backward Pass (обратный проход)
Вычисляем градиенты функции потерь по каждому весу:
def backward(self, y_true, learning_rate=0.01):
m = y_true.shape[0]
# Выходной слой
delta = (self.activations[-1] - y_true) / m # dL/dz
# Идем слой за слоем в обратном направлении
for i in range(len(self.weights) - 1, -1, -1):
# Градиент по весам
dW = np.dot(self.activations[i].T, delta)
# Градиент по смещению
db = np.sum(delta, axis=0, keepdims=True)
# Обновляем веса (Gradient Descent)
self.weights[i] -= learning_rate * dW
self.biases[i] -= learning_rate * db
# Если не последний слой — считаем градиент для предыдущего слоя
if i > 0:
delta = np.dot(delta, self.weights[i].T) * \
self.relu_derivative(self.z_values[i-1])
Полный цикл обучения (Training Loop)
def train(self, X, y, epochs=100, batch_size=32, learning_rate=0.01):
n_samples = X.shape[0]
for epoch in range(epochs):
# Перемешиваем данные
indices = np.random.permutation(n_samples)
X_shuffled = X[indices]
y_shuffled = y[indices]
total_loss = 0
# Мини-батчи (Stochastic Gradient Descent)
for i in range(0, n_samples, batch_size):
X_batch = X_shuffled[i:i+batch_size]
y_batch = y_shuffled[i:i+batch_size]
# Forward pass
y_pred = self.forward(X_batch)
# Compute loss
loss = self.compute_loss(y_pred, y_batch)
total_loss += loss
# Backward pass
self.backward(y_batch, learning_rate)
avg_loss = total_loss / (n_samples // batch_size)
print(f"Epoch {epoch+1}/{epochs} - Loss: {avg_loss:.4f}")
Визуализация процесса
Epoch 1: Loss = 2.3
Epoch 2: Loss = 1.8
Epoch 3: Loss = 1.4
Epoch 4: Loss = 1.1
Epoch 5: Loss = 0.8 ← Веса обновляются, ошибка уменьшается
...
Epoch 100: Loss = 0.1
Оптимизаторы (Optimizers)
1. SGD (Stochastic Gradient Descent)
# Базовый вариант
weights -= learning_rate * gradient
2. Momentum
# Учитывает направление движения
velocity = momentum * velocity + learning_rate * gradient
weights -= velocity
3. Adam (Adaptive Moment Estimation)
from tensorflow.keras.optimizers import Adam
# Комбинирует идеи Momentum и RMSprop
# Адаптивная скорость обучения для каждого параметра
optimizer = Adam(learning_rate=0.001)
Пример с TensorFlow/Keras
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.losses import SparseCategoricalCrossentropy
# Построение модели
model = models.Sequential([
layers.Dense(128, activation='relu', input_shape=(28*28,)),
layers.Dropout(0.2),
layers.Dense(64, activation='relu'),
layers.Dropout(0.2),
layers.Dense(10, activation='softmax')
])
# Компиляция модели
model.compile(
optimizer=Adam(learning_rate=0.001),
loss=SparseCategoricalCrossentropy(),
metrics=['accuracy']
)
# Обучение
history = model.fit(
X_train, y_train,
epochs=50,
batch_size=32,
validation_split=0.2,
verbose=1
)
# Оценка
test_loss, test_acc = model.evaluate(X_test, y_test)
print(f"Test accuracy: {test_acc:.4f}")
Важные концепции
1. Learning Rate
Очень маленький (0.00001): медленное обучение, может застрять
Оптимальный (0.01): быстрое и стабильное обучение
Очень большой (1.0): скачки и нестабильность
2. Batch Size
Маленький (1): шумный градиент, но быстрее адаптация
Оптимальный (32-128): баланс скорости и стабильности
Большой (1000+): гладкий градиент, но риск застрять в локальном минимуме
3. Epochs vs Iterations
# Epoch — один проход через весь датасет
# Iteration — один обновление весов на одном батче
n_samples = 10000
batch_size = 32
epochs = 50
iterations_per_epoch = n_samples // batch_size # 312
total_iterations = epochs * iterations_per_epoch # 15600
4. Vanishing Gradient Problem
При обратном распространении градиенты могут становиться очень маленькими в глубоких сетях:
# Решение 1: ReLU вместо Sigmoid
model.add(layers.Dense(128, activation='relu')) # ✓ Хорошо
model.add(layers.Dense(128, activation='sigmoid')) # ✗ Плохо для глубоких сетей
# Решение 2: Batch Normalization
model.add(layers.Dense(128, activation='relu'))
model.add(layers.BatchNormalization())
# Решение 3: Residual connections (Skip connections)
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(128))
model.add(layers.Add()) # Добавляем оригинальный вход
Итог процесса обучения
- Forward pass — примеры проходят через сеть
- Loss calculation — вычисляем ошибку
- Backward pass — считаем градиенты
- Weight update — обновляем веса на основе градиентов
- Повторяем — 50-100 эпох пока loss не сойдется
Ключевые этапы:
- Инициализация весов случайно
- Перемешивание данных для каждой эпохи
- Использование батчей для эффективности
- Мониторинг loss на валидационном наборе
- Ранняя остановка (early stopping) если валидация не улучшается