知识体系篇-数据标注与处理(01)模型训练与调优:模型训练基本流程,从原始数据到可用模型的完整路径
·
模型训练基本流程:从原始数据到可用模型的完整路径
所属模块:卷三 · 知识体系篇 · 第三部分 模型训练与调优
考试权重:★★★★★(核心主干,理论+实操均为高频考点)
阅读时长:约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对每层输入归一化 |
十、本章思维导图
下一篇预告:《数据集划分与交叉验证》——如何科学地分割数据,让模型评估结果真实可信?K折交叉验证的原理与实现、留出法的陷阱,下一篇全面讲透。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)