模型训练基本流程:从原始数据到可用模型的完整路径

所属模块:卷三 · 知识体系篇 · 第三部分 模型训练与调优
考试权重:★★★★★(核心主干,理论+实操均为高频考点)
阅读时长:约25分钟


一、模型训练是什么?一句话说清楚

模型训练 = 用数据告诉算法"正确答案长什么样"的过程。

更严格地说:通过反复调整模型内部参数(权重 w 和偏置 b),使模型在训练数据上的预测误差最小化,从而让模型"学会"输入→输出的映射规律。


二、模型训练全流程:10步标准Pipeline

原始数据
   │
   ▼
[Step 1]  数据收集与标注
   │  → 获取足够多、高质量的有标签数据
   ▼
[Step 2]  数据预处理
   │  → 清洗 / 脱敏 / 格式统一(详见V3-14)
   ▼
[Step 3]  数据集划分
   │  → 训练集 / 验证集 / 测试集(详见V3-17)
   ▼
[Step 4]  特征工程
   │  → 归一化 / 编码 / 特征选择 / 特征构造
   ▼
[Step 5]  模型选择
   │  → 根据任务类型选择合适的算法/架构
   ▼
[Step 6]  模型初始化
   │  → 随机初始化权重 / 使用预训练权重
   ▼
[Step 7]  前向传播(Forward Pass)
   │  → 输入→预测输出
   ▼
[Step 8]  损失计算(Loss Calculation)
   │  → 预测值 vs 真实标签 → 误差值
   ▼
[Step 9]  反向传播 + 梯度更新
   │  → 链式求导 → 更新权重
   ▼
[Step 10] 评估与迭代
   │  → 验证集评估 → 调参 → 重复7~9
   ▼
最终模型(在测试集上评估)

三、核心概念详解

3.1 损失函数(Loss Function)

损失函数量化了模型预测值与真实值之间的差距,训练目标就是让损失最小化。

任务类型 常用损失函数 公式说明
二分类 二元交叉熵(BCE) -[y·log§ + (1-y)·log(1-p)]
多分类 交叉熵(CE) -Σ yᵢ·log(pᵢ)
回归 均方误差(MSE) (1/n)·Σ(ŷ - y)²
回归(鲁棒) 平均绝对误差(MAE) (1/n)·Σ|ŷ - y|
目标检测 Focal Loss -(1-pₜ)^γ·log(pₜ),解决类别不平衡
序列生成 CTC Loss 对齐未知的序列损失

3.2 优化器(Optimizer)

优化器决定了如何根据梯度更新权重

梯度下降三种变体:
┌──────────────────────────────────────────────────────────────┐
│  批量梯度下降(BGD)                                           │
│  每次用全部训练数据计算梯度                                   │
│  ✅ 稳定  ❌ 大数据集速度极慢                                │
├──────────────────────────────────────────────────────────────┤
│  随机梯度下降(SGD)                                           │
│  每次用1条样本计算梯度                                       │
│  ✅ 速度快  ❌ 噪声大,震荡明显                              │
├──────────────────────────────────────────────────────────────┤
│  小批量梯度下降(Mini-batch GD)  ← 实际最常用                │
│  每次用batch_size条样本(如32/64/128)                       │
│  ✅ 速度与稳定性折中  ✅ 支持GPU并行                        │
└──────────────────────────────────────────────────────────────┘
优化器 特点 适用场景
SGD 最基础,需手动调lr 简单任务,配合Momentum效果好
SGD+Momentum 加入历史梯度方向,减少震荡 CV任务,收敛稳定
Adam 自适应学习率,综合效果佳 NLP、大多数任务的首选
AdamW Adam+权重衰减解耦 Transformer类模型首选
RMSProp 自适应学习率,适合RNN 时序模型

3.3 学习率(Learning Rate)

学习率过大:              学习率过小:
loss                     loss
  ↑  /\/\/\/\/\            ↑  \_____________
  |                        |   \____________
  └──────────── epoch       └──────────── epoch
  震荡,无法收敛           收敛太慢,训练时间长

理想情况:
loss
  ↑  \
  |   \___
  |       \___
  |           \____
  └─────────────── epoch
  平滑下降,趋于稳定

学习率调度策略(LR Scheduler):

策略 描述 效果
固定学习率 lr不变 简单,可能后期震荡
阶梯衰减(StepLR) 每N个epoch×衰减系数 经典,效果稳定
余弦退火(CosineAnnealingLR) 余弦曲线从大到小 广泛使用,收敛好
Warmup+衰减 先从小增大,再衰减 Transformer标准做法
ReduceLROnPlateau 验证集不提升则降lr 自适应,适合调试

3.4 Epoch、Batch、Iteration 三者关系

数据集:1000条样本
batch_size = 100

1个 Epoch = 遍历全部1000条数据一次
1个 Iteration = 处理一个batch(100条)
迭代次数(Iteration) = 1000 / 100 = 10次/Epoch

训练 20个Epoch → 总共 20 × 10 = 200次参数更新

四、前向传播 + 反向传播:神经网络的学习机制

4.1 前向传播(Forward Pass)

输入层       隐藏层1      隐藏层2      输出层
x₁ ──┐                              ┌── ŷ₁
x₂ ──┼→  [线性变换]→[激活函数]→ ┼→ [线性变换]→[Softmax]→ ŷ₂
x₃ ──┘   z=Wx+b    a=σ(z)          └── ŷ₃

步骤:
① 线性变换:z = W·x + b
② 激活函数:a = f(z)(引入非线性)
③ 重复直到输出层
④ 计算损失:L = Loss(ŷ, y)

常用激活函数对比:

激活函数 公式 值域 优点 缺点
Sigmoid 1/(1+e⁻ˣ) (0,1) 输出概率直觉 梯度消失,计算慢
Tanh (eˣ-e⁻ˣ)/(eˣ+e⁻ˣ) (-1,1) 零中心化 梯度消失
ReLU max(0,x) [0,+∞) 计算快,缓解梯度消失 Dead ReLU问题
LeakyReLU max(0.01x,x) (-∞,+∞) 解决Dead ReLU 需调负斜率
GELU x·Φ(x) ≈(-0.17,+∞) Transformer首选 计算稍复杂

4.2 反向传播(Backpropagation)

核心:链式法则(Chain Rule)

∂L/∂W = ∂L/∂ŷ × ∂ŷ/∂z × ∂z/∂W

通俗理解:
损失L对权重W的梯度
= "损失如何因输出变化" × "输出如何因中间值变化" × "中间值如何因权重变化"

权重更新:
W_new = W_old - lr × ∂L/∂W

五、正则化技术:防止过拟合的"刹车"

┌───────────────────────────────────────────────────────────┐
│                  正则化方法全景                           │
├─────────────────────┬─────────────────────────────────────┤
│   L1正则化(Lasso)   │   L2正则化(Ridge/Weight Decay)     │
│   Loss += λΣ|w|     │   Loss += λΣw²                     │
│   产生稀疏权重      │   权重均匀缩小,不产生稀疏          │
│   特征选择效果好    │   防过拟合最常用                    │
├─────────────────────┼─────────────────────────────────────┤
│   Dropout           │   Batch Normalization               │
│   训练时随机置零    │   每层归一化,加速收敛              │
│   p=0.1~0.5         │   减少Internal Covariate Shift      │
│   推理时不Dropout   │   通常在激活函数之前使用            │
├─────────────────────┼─────────────────────────────────────┤
│   Early Stopping    │   数据增强                          │
│   验证集不提升即停  │   扩充训练数据多样性                │
│   防过拟合最简单   │   图像/文本/语音均可用              │
└─────────────────────┴─────────────────────────────────────┘

六、代码示例:PyTorch完整训练循环

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

# ──────────────────────────────────────────
# 1. 构造示例数据(二分类)
# ──────────────────────────────────────────
np.random.seed(42)
X = torch.FloatTensor(np.random.randn(1000, 10))
y = torch.FloatTensor((X[:, 0] + X[:, 1] > 0).numpy().astype(float))

# 划分训练集/验证集
split = int(0.8 * len(X))
train_dataset = TensorDataset(X[:split], y[:split])
val_dataset   = TensorDataset(X[split:], y[split:])
train_loader  = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader    = DataLoader(val_dataset,   batch_size=32, shuffle=False)

# ──────────────────────────────────────────
# 2. 定义模型
# ──────────────────────────────────────────
class BinaryClassifier(nn.Module):
    def __init__(self, input_dim=10, hidden_dim=64):
        super().__init__()
        self.network = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.BatchNorm1d(hidden_dim),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(hidden_dim, hidden_dim // 2),
            nn.ReLU(),
            nn.Linear(hidden_dim // 2, 1),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.network(x).squeeze()

model = BinaryClassifier()

# ──────────────────────────────────────────
# 3. 定义损失函数、优化器、调度器
# ──────────────────────────────────────────
criterion = nn.BCELoss()
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20)

# ──────────────────────────────────────────
# 4. 训练循环(标准范式)
# ──────────────────────────────────────────
def train_one_epoch(model, loader, criterion, optimizer):
    model.train()
    total_loss, correct, total = 0, 0, 0
    for X_batch, y_batch in loader:
        optimizer.zero_grad()           # 梯度清零
        pred = model(X_batch)           # 前向传播
        loss = criterion(pred, y_batch) # 计算损失
        loss.backward()                 # 反向传播
        # 梯度裁剪(防梯度爆炸)
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        optimizer.step()                # 更新权重

        total_loss += loss.item() * len(y_batch)
        correct    += ((pred > 0.5) == y_batch).sum().item()
        total      += len(y_batch)

    return total_loss / total, correct / total

def evaluate(model, loader, criterion):
    model.eval()
    total_loss, correct, total = 0, 0, 0
    with torch.no_grad():
        for X_batch, y_batch in loader:
            pred = model(X_batch)
            loss = criterion(pred, y_batch)
            total_loss += loss.item() * len(y_batch)
            correct    += ((pred > 0.5) == y_batch).sum().item()
            total      += len(y_batch)
    return total_loss / total, correct / total

# ──────────────────────────────────────────
# 5. 完整训练流程(含Early Stopping)
# ──────────────────────────────────────────
best_val_loss = float('inf')
patience = 5
patience_counter = 0
history = {'train_loss': [], 'val_loss': [], 'val_acc': []}

for epoch in range(50):
    train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer)
    val_loss, val_acc     = evaluate(model, val_loader, criterion)
    scheduler.step()

    history['train_loss'].append(train_loss)
    history['val_loss'].append(val_loss)
    history['val_acc'].append(val_acc)

    print(f"Epoch {epoch+1:3d} | "
          f"Train Loss: {train_loss:.4f} | "
          f"Val Loss: {val_loss:.4f} | "
          f"Val Acc: {val_acc:.4f} | "
          f"LR: {scheduler.get_last_lr()[0]:.6f}")

    # Early Stopping
    if val_loss < best_val_loss:
        best_val_loss = val_loss
        torch.save(model.state_dict(), 'best_model.pth')  # 保存最佳模型
        patience_counter = 0
    else:
        patience_counter += 1
        if patience_counter >= patience:
            print(f"\nEarly stopping at epoch {epoch+1}!")
            break

# 加载最佳模型
model.load_state_dict(torch.load('best_model.pth'))
print(f"\n训练完成!最佳验证损失:{best_val_loss:.4f}")

七、迁移学习:站在巨人的肩膀上训练

7.1 迁移学习四种模式

┌──────────────────────────────────────────────────────────┐
│                   迁移学习策略对比                       │
├────────────────┬─────────────────────────────────────────┤
│ 策略           │ 描述                                   │
├────────────────┼─────────────────────────────────────────┤
│ 冻结全部预训   │ 只训练新增分类头,速度极快             │
│ 练层           │ 适合:数据极少(<100条),与源任务相似   │
├────────────────┼─────────────────────────────────────────┤
│ 冻结底层,解冻 │ 训练后几层+分类头                     │
│ 顶层           │ 适合:中等数据量,任务有差异           │
├────────────────┼─────────────────────────────────────────┤
│ 全量微调       │ 所有层用小学习率微调                   │
│ (Fine-tuning)  │ 适合:数据较充足,任务差异大           │
├────────────────┼─────────────────────────────────────────┤
│ 特征提取后     │ 提取中间层特征→新模型训练              │
│ 再训练         │ 适合:领域迁移,特征工程场景           │
└────────────────┴─────────────────────────────────────────┘

7.2 代码示例:ResNet迁移学习

import torchvision.models as models

# 加载预训练ResNet18
model = models.resnet18(pretrained=True)

# 策略一:冻结全部预训练层
for param in model.parameters():
    param.requires_grad = False

# 替换最后的分类头(假设目标类别数=5)
num_features = model.fc.in_features
model.fc = nn.Sequential(
    nn.Dropout(0.5),
    nn.Linear(num_features, 5)
)
# 此时只有model.fc的参数会被更新

# 策略二:解冻最后两个残差块(layer3, layer4)
for param in model.layer3.parameters():
    param.requires_grad = True
for param in model.layer4.parameters():
    param.requires_grad = True

# 使用差异学习率(预训练层用小lr,新层用大lr)
optimizer = optim.AdamW([
    {'params': model.layer3.parameters(), 'lr': 1e-4},
    {'params': model.layer4.parameters(), 'lr': 1e-4},
    {'params': model.fc.parameters(),     'lr': 1e-3},
])

八、训练过程监控与调试

8.1 训练曲线四种典型形态

① 正常训练           ② 过拟合
loss                  loss
  ↑  \  train           ↑  \
  |   \___               |   \_ train
  |   val \___           |   val /──── ← val开始上升
  └─────────            └─────────

③ 欠拟合              ④ 学习率过大
loss                  loss
  ↑  \_____train        ↑  \/\/\/\/\
  |  val \_______        |
  |  两者都很高          └─────────
  └─────────            震荡不收敛

8.2 常见训练问题诊断

现象 可能原因 解决方案
Loss不下降 lr太小/太大 尝试lr=1e-3,绘制loss曲线
Loss剧烈震荡 lr过大 降低lr,加梯度裁剪
验证集loss上升 过拟合 增加Dropout/正则化/数据增强
GPU利用率低 batch_size太小/IO瓶颈 增大batch,使用prefetch
NaN Loss 梯度爆炸/数值溢出 梯度裁剪,检查数据中的NaN
训练速度慢 未使用GPU/数据加载慢 检查device,增加num_workers

九、考试高频考点速览

必背公式:

权重更新:w = w - lr × ∂L/∂w
MSE损失:L = (1/n)Σ(ŷ-y)²
交叉熵:L = -Σ y·log(ŷ)
L2正则:Loss_total = Loss + λΣw²

易混淆概念:

概念对 区别要点
Epoch vs Iteration Epoch=过一遍全量数据;Iteration=一个batch的更新
过拟合 vs 欠拟合 过拟合=训练好验证差;欠拟合=两者都差
SGD vs Adam SGD学习率固定;Adam自适应调整各参数的lr
L1 vs L2正则 L1产生稀疏(部分权重变0);L2均匀缩小权重
Dropout vs BN Dropout训练时随机关闭;BN对每层输入归一化

十、本章思维导图

模型训练基本流程

训练Pipeline

数据准备

收集标注

预处理

划分数据集

模型选择

任务类型匹配

参数量评估

训练循环

前向传播

损失计算

反向传播

权重更新

评估迭代

验证集监控

调参优化

核心组件

损失函数

BCE二分类

CE多分类

MSE回归

Focal Loss

优化器

SGD+Momentum

Adam

AdamW

学习率

固定学习率

余弦退火

Warmup策略

ReduceLROnPlateau

正则化

参数正则

L1 Lasso

L2 Ridge

结构正则

Dropout

BatchNorm

训练策略

Early Stopping

数据增强

迁移学习

冻结预训练层

微调顶层

全量微调

差异学习率


下一篇预告:《数据集划分与交叉验证》——如何科学地分割数据,让模型评估结果真实可信?K折交叉验证的原理与实现、留出法的陷阱,下一篇全面讲透。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐