Ответ
При построении нейросетевой модели для регрессии я фокусируюсь на архитектуре, функции потерь и методах стабилизации обучения.
Мой типичный рабочий процесс:
-
Подготовка данных:
- Стандартизую или нормализую числовые признаки (использую
StandardScalerизsklearn). - Разделяю данные на train/validation/test.
- Стандартизую или нормализую числовые признаки (использую
-
Проектирование архитектуры:
- Для табличных данных начинаю с полносвязной сети (MLP).
- В качестве активаций в скрытых слоях использую ReLU или его вариации (Leaky ReLU).
- Выходной слой — один нейрон без функции активации для предсказания непрерывного значения.
- Добавляю Batch Normalization и Dropout слои для ускорения сходимости и борьбы с переобучением.
-
Выбор функции потерь и оптимизатора:
- Функция потерь: Чаще всего
Mean Squared Error (MSE). Если в данных есть выбросы, используюMean Absolute Error (MAE)илиHuber Loss, которая менее чувствительна к ним. - Оптимизатор:
Adamс learning rate по умолчанию или подобранным с помощьюReduceLROnPlateau.
- Функция потерь: Чаще всего
-
Обучение с регуляризацией:
- Использую раннюю остановку (EarlyStopping) по валидационной потере.
- Добавляю L2-регуляризацию (weight decay) в оптимизатор.
Пример реализации на PyTorch:
import torch
import torch.nn as nn
import torch.optim as optim
class RegressionNN(nn.Module):
def __init__(self, input_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, 128),
nn.BatchNorm1d(128),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(128, 64),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(64, 1) # Выходной слой без активации
)
def forward(self, x):
return self.net(x)
# Инициализация
model = RegressionNN(input_dim=X_train.shape[1])
criterion = nn.MSELoss() # Функция потерь MSE
optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5) # L2-регуляризация
# Цикл обучения с ранней остановкой
best_val_loss = float('inf')
patience = 10
for epoch in range(100):
model.train()
optimizer.zero_grad()
predictions = model(X_train_tensor)
loss = criterion(predictions, y_train_tensor)
loss.backward()
optimizer.step()
# Валидация
model.eval()
with torch.no_grad():
val_pred = model(X_val_tensor)
val_loss = criterion(val_pred, y_val_tensor)
# Логика ранней остановки
if val_loss < best_val_loss:
best_val_loss = val_loss
patience_counter = 0
else:
patience_counter += 1
if patience_counter >= patience:
print(f'Early stopping at epoch {epoch}')
break
Оценка модели: Для итоговой оценки на тестовом наборе использую не только MSE, но и MAE (среднюю абсолютную ошибку) и R² score, чтобы понять абсолютную ошибку и долю объяснённой дисперсии.
Ответ 18+ 🔞
Давай разберём, как из говна и палок собрать нейросеть для регрессии, чтобы она не просто тупо запомнила данные, а реально что-то предсказывала. Терпения ноль ебать, когда она учится, но результат того стоит.
Как я обычно это делаю, чтобы не накрыться медным тазом:
-
Готовим данные, а то будет пиздец.
- Все эти цифры в признаках нужно привести к общему знаменателю, иначе один признак с огромными значениями будет всех доминировать. Использую
StandardScaler— он делает так, чтобы среднее было ноль, а разброс — единица. Проще говоря, приводит всё к нормальному виду. - Дальше делю данные на три кучки: для обучения, для проверки в процессе (валидация) и финальный тест, куда смотреть нельзя, пока всё не готово.
- Все эти цифры в признаках нужно привести к общему знаменателю, иначе один признак с огромными значениями будет всех доминировать. Использую
-
Собираем архитектуру, как лего.
- Для табличных данных — обычная полносвязная сетка (MLP), куда ж без неё.
- Внутри ставим ReLU (или его более хитрожопого брата Leaky ReLU), чтобы сеть могла учиться нелинейным штукам.
- Самое важное: выходной слой — это один нейрон БЕЗ ВСЯКОЙ АКТИВАЦИИ. Совсем. Никакого sigmoid или tanh. Мы же число предсказываем, а не вероятность. Если поставить активацию — всё, предсказания будут зажаты в её диапазон, и модель станет пиздапроебильной.
- Чтобы не переобучалась и училась быстрее, впихиваем Batch Normalization и Dropout. Первый выравнивает внутренние распределения, второй — случайно отключает нейроны, чтобы сеть не стала слишком зависимой от каких-то конкретных связей.
-
Выбираем, как наказывать за ошибки, и кто будет исправлять.
- Функция потерь: Чаще всего
Mean Squared Error (MSE). Она квадратом штрафует за большие ошибки, поэтому модель их очень боится. Но если в данных есть дикие выбросы (какой-нибудь один ебанутый заказ в сто раз больше всех), то MSE с ума сойдёт, пытаясь под него подстроиться. Тогда беруMean Absolute Error (MAE)илиHuber Loss— они поспокойнее. - Оптимизатор: Беру
Adam. Он как умный универсальный солдат, сам подстраивает шаги обучения. Иногда уменьшаю learning rate, если модель перестаёт улучшаться.
- Функция потерь: Чаще всего
-
Учим, но не даём заучиться.
- Обязательно ставлю раннюю остановку (EarlyStopping). Слежу за ошибкой на валидации. Если она несколько эпох подряд не уменьшается — всё, стоп машина, дальше учить бесполезно, только переобучаться начнёт.
- Добавляю L2-регуляризацию (weight decay в оптимизаторе). Это такой мелкий штраф за слишком большие веса в нейронах. Не даёт сети стать слишком самоуверенной и зазубрить тренировочные данные.
Вот как это выглядит в коде на PyTorch, если делать по-человечески:
import torch
import torch.nn as nn
import torch.optim as optim
class RegressionNN(nn.Module):
def __init__(self, input_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, 128),
nn.BatchNorm1d(128),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(128, 64),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(64, 1) # Выходной слой без активации — запомни, ёпта!
)
def forward(self, x):
return self.net(x)
# Инициализация
model = RegressionNN(input_dim=X_train.shape[1])
criterion = nn.MSELoss() # Функция потерь MSE
optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5) # L2-регуляризация
# Цикл обучения с ранней остановкой
best_val_loss = float('inf')
patience = 10
for epoch in range(100):
model.train()
optimizer.zero_grad()
predictions = model(X_train_tensor)
loss = criterion(predictions, y_train_tensor)
loss.backward()
optimizer.step()
# Валидация
model.eval()
with torch.no_grad():
val_pred = model(X_val_tensor)
val_loss = criterion(val_pred, y_val_tensor)
# Логика ранней остановки
if val_loss < best_val_loss:
best_val_loss = val_loss
patience_counter = 0
else:
patience_counter += 1
if patience_counter >= patience:
print(f'Early stopping at epoch {epoch}')
break
Ну и как понять, что всё получилось? На тестовых данных смотрю не только на MSE. Ещё обязательно гляну на MAE — это средняя абсолютная ошибка, она понятнее, потому что в тех же единицах, что и целевая переменная. И R² score — он показывает, какую долю дисперсии моя модель объяснила. Если R² близок к 1 — я молодец, если к 0 — пора пересматривать подход, потому что нихуя не вышло.