04. 神经网络基础

从感知机到多层神经网络,深入理解激活函数、反向传播、损失函数、优化器与 Dropout/批归一化等训练技巧

神经网络是深度学习的基石。本文从感知机出发,逐步构建多层感知机 (MLP),详解反向传播算法、激活函数选择、优化器对比与稳定训练的关键技巧。

1. 感知机与神经元模型

1.1 感知机 (Perceptron)

感知机是最简单的线性分类器:

$$\hat{y} = \sigma(w_1x_1 + w_2x_2 + … + w_nx_n + b) = \sigma(w^Tx + b)$$

import numpy as np

class Perceptron:
    def __init__(self, n_inputs, lr=0.01):
        self.weights = np.zeros(n_inputs)
        self.bias = 0
        self.lr = lr
    
    def predict(self, X):
        return np.where(np.dot(X, self.weights) + self.bias >= 0, 1, 0)
    
    def fit(self, X, y, epochs=100):
        for _ in range(epochs):
            for xi, target in zip(X, y):
                prediction = self.predict(xi.reshape(1, -1))[0]
                error = target - prediction
                self.weights += self.lr * error * xi
                self.bias += self.lr * error

局限:感知机只能处理线性可分问题(如 XOR 问题无法解决)。

1.2 多层感知机 (MLP)

通过隐藏层引入非线性变换,解决线性不可分问题:

输入层 → 隐藏层1 → 隐藏层2 → ... → 输出层
import torch
import torch.nn as nn

class MLP(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(MLP, self).__init__()
        self.layer1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.2)
        self.layer2 = nn.Linear(hidden_size, hidden_size // 2)
        self.layer3 = nn.Linear(hidden_size // 2, num_classes)
    
    def forward(self, x):
        x = self.layer1(x)
        x = self.relu(x)
        x = self.dropout(x)
        x = self.layer2(x)
        x = self.relu(x)
        x = self.layer3(x)
        return x

# 初始化模型
model = MLP(input_size=784, hidden_size=256, num_classes=10)
print(model)

2. 激活函数

激活函数引入非线性,使神经网络能够拟合任意复杂函数。

2.1 常用激活函数对比

激活函数公式优点缺点适用
Sigmoid$\frac{1}{1+e^{-x}}$输出 (0,1),概率解释梯度消失、非零均值二分类输出层
Tanh$\frac{e^x-e^{-x}}{e^x+e^{-x}}$零均值仍可能梯度消失隐藏层
ReLU$\max(0, x)$计算快、缓解梯度消失Dead ReLU 问题隐藏层默认
Leaky ReLU$\max(\alpha x, x)$解决 Dead ReLU增加一个超参ReLU 改进
PReLU$\max(\alpha x, x)$ (可学习)自适应负斜率参数量增加深层网络
ELU$x (x>0), \alpha(e^x-1) (x<0)$平滑、负值区域计算略慢需要平滑输出
Swish$x \cdot \sigma(x)$自门控、平滑计算量稍大Google 推荐
GELU$x \cdot \Phi(x)$Transformer 标配-BERT/GPT
Softmax$\frac{e^{z_i}}{\sum e^{z_j}}$多类概率归一化-多分类输出层
# PyTorch 激活函数
import torch.nn.functional as F

x = torch.randn(2, 5)

# ReLU
h = F.relu(x)

# Leaky ReLU
h = F.leaky_relu(x, negative_slope=0.01)

# GELU(Transformer 使用)
h = F.gelu(x)

# Softmax(输出层)
probs = F.softmax(x, dim=1)

2.2 梯度消失与梯度爆炸

梯度消失:反向传播中梯度逐层衰减(Sigmoid 导数最大 0.25),深层网络前面层几乎不更新。

梯度爆炸:梯度逐层放大,权重更新过大导致 NaN。

解决方案

  • 使用 ReLU 替代 Sigmoid
  • 权重初始化(Xavier / He)
  • 批归一化
  • 梯度裁剪

3. 反向传播算法

3.1 链式法则

反向传播利用链式法则高效计算梯度:

$$\frac{\partial L}{\partial w_i} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z} \cdot \frac{\partial z}{\partial w_i}$$

3.2 手动实现反向传播

class SimpleNN:
    """手动实现两层神经网络,含前向传播和反向传播"""
    
    def __init__(self, input_size, hidden_size, output_size):
        # Xavier 初始化
        self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2.0 / input_size)
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(2.0 / hidden_size)
        self.b2 = np.zeros((1, output_size))
    
    def forward(self, X):
        self.z1 = X @ self.W1 + self.b1
        self.a1 = np.maximum(0, self.z1)  # ReLU
        self.z2 = self.a1 @ self.W2 + self.b2
        return self.z2
    
    def backward(self, X, y, output, lr):
        m = X.shape[0]
        
        # 输出层梯度
        dz2 = output - y  # Softmax + CrossEntropy 的简化梯度
        dW2 = (self.a1.T @ dz2) / m
        db2 = np.sum(dz2, axis=0, keepdims=True) / m
        
        # 隐藏层梯度
        da1 = dz2 @ self.W2.T
        dz1 = da1 * (self.z1 > 0)  # ReLU 导数
        dW1 = (X.T @ dz1) / m
        db1 = np.sum(dz1, axis=0, keepdims=True) / m
        
        # 参数更新
        self.W2 -= lr * dW2
        self.b2 -= lr * db2
        self.W1 -= lr * dW1
        self.b1 -= lr * db1

3.3 PyTorch 自动求导

x = torch.randn(3, requires_grad=True)
y = x ** 2 + 3 * x + 1
loss = y.sum()

# 自动反向传播
loss.backward()

# 查看梯度
print(x.grad)  # dy/dx = 2x + 3

4. 损失函数

损失函数公式适用任务PyTorch
MSE$\frac{1}{n}\sum(y-\hat{y})^2$回归nn.MSELoss()
MAE$\frac{1}{n}\sum|y-\hat{y}|$回归(鲁棒)nn.L1Loss()
交叉熵$-\sum y\log(\hat{y})$多分类nn.CrossEntropyLoss()
BCE$-\sum[y\log(\hat{y}) + (1-y)\log(1-\hat{y})]$二分类nn.BCEWithLogitsLoss()
Huber混合 MSE/MAE回归(异常值鲁棒)nn.SmoothL1Loss()
# 分类损失
criterion = nn.CrossEntropyLoss()  # 内置 Softmax
output = model(images)
loss = criterion(output, labels)

# 回归损失
criterion = nn.MSELoss()
loss = criterion(predictions, targets)

# 对不平衡数据加权的交叉熵
weights = torch.tensor([1.0, 2.0, 3.0])  # 少类别的权重更高
criterion = nn.CrossEntropyLoss(weight=weights)

5. 优化器

5.1 梯度下降变体

# SGD + 动量
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

# Adam(自适应学习率,最常用)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, 
                              betas=(0.9, 0.999), eps=1e-8)

# AdamW(权重衰减解耦,Transformer 标配)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)

# RMSprop
optimizer = torch.optim.RMSprop(model.parameters(), lr=0.001, alpha=0.99)

5.2 优化器对比

优化器优点缺点适用
SGD泛化好、收敛稳定慢、需调学习率大规模数据
SGD+Momentum加速收敛、减少震荡动量超参通用
AdaGrad自适应学习率学习率单调递减至 0稀疏梯度
RMSprop解决 AdaGrad 学习率衰减仍需调参RNN
Adam快速收敛,默认好泛化可能不如 SGD默认首选
AdamW更好的权重衰减-Transformer/NLP
LAMB大批量训练稳定-BERT 预训练

5.3 学习率调度

from torch.optim.lr_scheduler import StepLR, CosineAnnealingLR, ReduceLROnPlateau

# 每 10 个 epoch 衰减 0.1 倍
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)

# 余弦退火
scheduler = CosineAnnealingLR(optimizer, T_max=100, eta_min=0)

# 验证 loss 不下降时降低 LR
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, 
                               patience=5, verbose=True)

# Warmup + 余弦退火(Transformer 标准配置)
from transformers import get_cosine_schedule_with_warmup

num_training_steps = len(train_loader) * epochs
scheduler = get_cosine_schedule_with_warmup(
    optimizer, 
    num_warmup_steps=500,
    num_training_steps=num_training_steps
)

6. 训练稳定技巧

6.1 权重初始化

# Xavier 初始化(tanh/sigmoid)
for m in model.modules():
    if isinstance(m, nn.Linear):
        nn.init.xavier_uniform_(m.weight)
        nn.init.zeros_(m.bias)

# He 初始化(ReLU)
for m in model.modules():
    if isinstance(m, nn.Linear):
        nn.init.kaiming_uniform_(m.weight, nonlinearity='relu')

6.2 批归一化 (BatchNorm)

对每个 mini-batch 归一化,稳定训练、允许更大学习率、有轻微正则化效果。

class BN_MLP(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super().__init__()
        self.layer1 = nn.Linear(input_size, hidden_size)
        self.bn1 = nn.BatchNorm1d(hidden_size)
        self.relu = nn.ReLU()
        self.layer2 = nn.Linear(hidden_size, num_classes)
    
    def forward(self, x):
        x = self.layer1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.layer2(x)
        return x

注意

  • 批归一化在 CNN 中接卷积后;在 NLP 中常用 LayerNorm 替代
  • 推理时保存运行均值/方差,eval 模式自动切换

6.3 Dropout 正则化

随机丢弃神经元,防止共适应,是强正则化手段。

# Dropout 层
self.dropout = nn.Dropout(p=0.5)  # 50% 丢弃概率

# Dropout 仅在训练时生效
def forward(self, x):
    x = self.fc(x)
    x = self.dropout(x)  # 训练时丢弃,推理时自动缩放
    return x

变体

  • Spatial Dropout(CNN):按通道丢弃
  • Dropout2d/3d:特征图空间维度一致丢弃
  • DropBlock:结构化丢弃,更适合 CNN

6.4 梯度裁剪

防止 RNN/Transformer 中的梯度爆炸。

# 全局梯度范数裁剪
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()

7. 完整训练流程

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset

# 超参数
EPOCHS = 50
BATCH_SIZE = 64
LR = 0.001
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 数据
X_train = torch.randn(1000, 784)
y_train = torch.randint(0, 10, (1000,))
train_dataset = TensorDataset(X_train, y_train)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)

# 模型、损失、优化器
model = MLP(784, 256, 10).to(DEVICE)
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=0.01)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)

# 训练循环
for epoch in range(EPOCHS):
    model.train()
    total_loss = 0
    correct = 0
    total = 0
    
    for batch_x, batch_y in train_loader:
        batch_x, batch_y = batch_x.to(DEVICE), batch_y.to(DEVICE)
        
        optimizer.zero_grad()
        outputs = model(batch_x)
        loss = criterion(outputs, batch_y)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        optimizer.step()
        
        total_loss += loss.item()
        _, predicted = outputs.max(1)
        total += batch_y.size(0)
        correct += predicted.eq(batch_y).sum().item()
    
    scheduler.step()
    
    acc = 100. * correct / total
    avg_loss = total_loss / len(train_loader)
    print(f"Epoch {epoch+1}/{EPOCHS}: Loss={avg_loss:.4f}, Acc={acc:.2f}%")

# 评估
model.eval()
with torch.no_grad():
    outputs = model(X_test.to(DEVICE))
    _, predicted = outputs.max(1)
    test_acc = predicted.eq(y_test.to(DEVICE)).sum().item() / len(y_test)
    print(f"Test Accuracy: {test_acc*100:.2f}%")

8. 训练诊断

现象原因解决
Loss 不降学习率太小/梯度消失增大 LR / 换激活函数 / 检查初始化
Loss 震荡学习率太大 / batch 太小降低 LR / 增大 batch / 加 momentum
Loss 为 NaN梯度爆炸 / 学习率过大梯度裁剪 / 降低 LR / 检查数据归一化
训练 loss 降,测试 loss 升过拟合Dropout / 正则化 / 早停 / 更多数据
训练测试 loss 都高欠拟合增加模型容量 / 更多特征 / 减少正则化
# 早停 Early Stopping
class EarlyStopping:
    def __init__(self, patience=7, min_delta=0):
        self.patience = patience
        self.min_delta = min_delta
        self.counter = 0
        self.best_loss = None
        self.early_stop = False
    
    def __call__(self, val_loss):
        if self.best_loss is None:
            self.best_loss = val_loss
        elif val_loss > self.best_loss - self.min_delta:
            self.counter += 1
            if self.counter >= self.patience:
                self.early_stop = True
        else:
            self.best_loss = val_loss
            self.counter = 0

总结

神经网络训练的关键环节:

  1. 架构设计:输入/输出维度对齐,隐藏层数与宽度由任务复杂度决定
  2. 激活函数:隐藏层默认 ReLU,输出层用 Softmax/Sigmoid 匹配任务
  3. 初始化:Xavier/He 初始化防止梯度消失/爆炸
  4. 归一化:BatchNorm 加速训练,LayerNorm 用于 NLP
  5. 正则化:Dropout + Weight Decay 防止过拟合
  6. 优化器:Adam/AdamW 为默认选择,SGD+动量泛化更好
  7. 学习率:Warmup + 余弦退火是 Transformer 标准配置
  8. 监控:同时看训练/验证 Loss 与准确率,设置早停

理解这些原理后,可以灵活调参,使神经网络稳定收敛到理想性能。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai-ml」更多文章

  1. 13. 大语言模型应用开发
  2. 12. MLOps 与实验管理
  3. 11. 模型部署与推理优化