文章目录
一、过拟合
过拟合(Overfitting)
是指模型在训练数据上表现得非常好,但在测试数据或者实际应用中表现很差。换句话说,模型“记住了训练数据”,却没有学到真正的规律。
原因:
- 模型过于复杂,参数太多。
- 训练数据量太少,样本不够代表整体。
- 特征选择不合理,噪声太多。
解决方案:
- 简化模型:减少层数或神经元数量。
正则化(Regularization)
:在损失函数里加入额外约束,让模型参数不变得过大,从而降低过拟合风险。
L1/L2 正则
。Dropout(随机丢弃神经元)
:在训练过程中随机“关闭”一部分神经元,防止模型过度依赖某些特征。- 增加训练数据:更多的数据能帮助模型学到真实规律。
提前停止训练(Early Stopping)
:监控验证集损失,避免训练过久。
1.1正则化
在损失函数中加入额外约束,使模型主动 “放弃” 或 “弱化” 对预测贡献小的参数。从而让模型更“简单”,更能泛化到新数据。
总损失 = 原损失 + λ ∗ 正则项 总损失=原损失 + λ * 正则项 总损失=原损失+λ∗正则项其中,λ 控制惩罚强度,>=0。
方法 | 原理 | 效果 | 特点 |
---|---|---|---|
L1正则化 | 正则化项=权重的绝对值之和 | 部分参数 归零 | 稀疏参数 |
L2正则化 | 正则化项=权重的平方之和 | 所有参数被压缩 至接近 0 | 平滑参数 |
Dropout 随机让一部分神经元 “失效”(输出为 0),使模型无法依赖固定的神经元组合,被迫学习更广泛、更具代表性的特征。
在 PyTorch 中,L1 需手动加入损失,对于加了L1正则的神经网络,大部分深度学习框架自带的优化器训练获得不了稀疏解;如果稀疏解的模式是无规律的,这种稀疏意义不大。
L2 可通过 weight_decay,Dropout 内置在模型层中
import torch
import torch.nn as nn
# L1正则化
l1_norm = sum(p.abs().sum() for p in model.parameters())
loss = loss + lambda_l1 * l1_norm
# L2正则化
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=0.001)
# 定义一个带 Dropout 的神经网络
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(100, 200) # 输入层→隐藏层1
self.dropout1 = nn.Dropout(p=0.5) # 隐藏层1的Dropout(50%丢弃率)
self.fc2 = nn.Linear(200, 100) # 隐藏层1→隐藏层2
self.dropout2 = nn.Dropout(p=0.3) # 隐藏层2的Dropout(30%丢弃率)
self.fc3 = nn.Linear(100, 10) # 隐藏层2→输出层(10分类)
1.2早停
在训练过程中,每个 epoch 计算验证集损失。如果验证集损失在 连续若干个 epoch 内没有改善,则停止训练。
- patience(耐心值):允许验证集表现不提升的轮数。
- delta:改善阈值,如果改善小于 delta,则认为没有改善。
- restore_best_weights:训练结束后是否恢复最佳模型参数。
# 早停参数
patience = 10
best_val_loss = float('inf')
counter = 0
best_model_weights = None
# 训练循环
num_epochs = 100
for epoch in range(num_epochs):
model.train()
optimizer.zero_grad()
outputs = model(X_train)
loss = criterion(outputs, y_train)
loss.backward()
optimizer.step()
# 验证集损失
model.eval()
with torch.no_grad():
val_outputs = model(X_val)
val_loss = criterion(val_outputs, y_val)
print(f"Epoch {epoch+1} - Train Loss: {loss.item():.4f}, Val Loss: {val_loss.item():.4f}")
# 早停逻辑
if val_loss < best_val_loss:
best_val_loss = val_loss
counter = 0
best_model_weights = model.state_dict() # 保存最佳模型
else:
counter += 1
if counter >= patience:
print("Early stopping triggered!")
break
# 恢复最佳模型
if best_model_weights is not None:
model.load_state_dict(best_model_weights)
二、欠拟合
欠拟合(Underfitting)
是指模型在训练数据上都表现不好,说明模型太简单,无法捕捉数据中的规律。
原因:
- 模型复杂度不足(层数太少、参数太少)。
- 特征太少或不够有代表性。
- 训练不够,模型还没学会规律。
解决方案:
- 增加模型复杂度:增加网络层数或神经元数量。
- 增加特征:尝试更多有用的特征或者做特征工程。
- 延长训练时间:训练更多轮(epochs)。
- 减少正则化:过强的正则化也可能导致欠拟合。
三、梯度消失 & 梯度爆炸
梯度问题是深度学习训练时常见问题,发生在反向传播(Backpropagation)阶段。
梯度消失
:梯度太小,导致权重更新几乎停滞,模型无法学习。
梯度爆炸
:梯度太大,权重更新幅度过大,模型不收敛甚至发散。
原因:
- 网络太深,连续的矩阵乘积导致梯度指数级衰减或增长。
- 激活函数选择不当,如 sigmoid/tanh 在大输入下梯度趋近零。
解决方案:
- 使用 ReLU 激活函数(不会导致梯度消失)。
- 权重初始化:比如 Xavier 或 He 初始化,防止梯度过大或过小。
- 若初始权重过大:前向传播时,信号经过经多层放大后可能溢出(如激活值趋于无穷大),反向传播时梯度也会急剧增大(梯度爆炸)。
- 若初始权重过小:信号经多层传递后,信号会逐渐衰减至接近 0(梯度消失),导致浅层参数几乎无法更新。
归一化
方法:Batch Normalization、Layer Normalization梯度裁剪(Gradient Clipping)
:限制梯度最大值,避免爆炸。
3.1激活函数
激活函数用于神经网络中,每个神经元的输出经过激活函数才能引入非线性,使神经网络能够拟合复杂函数。
激活函数 | 公式 | 输出范围 | 优点 | 缺点 | PyTorch实现 |
---|---|---|---|---|---|
Sigmoid | σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1+e^{-x}} σ(x)=1+e−x1 | (0,1) | 输出概率,常用于二分类 | 梯度消失,输出非零均值 | nn.Sigmoid() |
Tanh | tanh ( x ) = e x − e − x e x + e − x \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} tanh(x)=ex+e−xex−e−x | (-1,1) | 输出零均值,梯度比 sigmoid 好 | 梯度消失 | nn.Tanh() |
ReLU | ReLU ( x ) = max ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x) | [0,∞) | 简单、高效,缓解梯度消失 | 死神经元(负区间梯度为0) | nn.ReLU() |
Leaky ReLU | LeakyReLU ( x ) = max ( 0.01 x , x ) \text{LeakyReLU}(x) = \max(0.01x, x) LeakyReLU(x)=max(0.01x,x) | (-∞,∞) | 改进 ReLU,避免死神经元 | 输出仍不零均值 | nn.LeakyReLU(0.01) |
ELU | ELU ( x ) = x if x > 0 , α ( e x − 1 ) if x ≤ 0 \text{ELU}(x) = x \text{ if } x>0, \alpha(e^x-1) \text{ if } x≤0 ELU(x)=x if x>0,α(ex−1) if x≤0 | (-α,∞) | 平滑,负值接近零均值 | 计算稍复杂 | nn.ELU(alpha=1.0) |
Softmax | Softmax ( x i ) = e x i ∑ j e x j \text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}} Softmax(xi)=∑jexjexi | (0,1), ∑=1 | 多分类输出概率 | 仅用于输出层 | nn.Softmax(dim=1) |
Swish | Swish ( x ) = x ∗ σ ( x ) \text{Swish}(x) = x * \sigma(x) Swish(x)=x∗σ(x) | (-∞,∞) | 平滑,表现优于 ReLU | 计算稍复杂 | x * torch.sigmoid(x) |
GELU | GELU ( x ) = x ∗ Φ ( x ) \text{GELU}(x) = x * \Phi(x) GELU(x)=x∗Φ(x)(Φ为标准正态CDF) | (-∞,∞) | Transformer 常用 | 计算复杂 | nn.GELU() |
参考:https://blog.csdn.net/qq_42691298/article/details/126590726
3.2权重初始化
# Xavier 初始化(均匀分布),适合 sigmoid/tanh
init.xavier_uniform_(layer.weight)
# He 初始化(正态分布),适合 ReLU
init.kaiming_uniform_(layer.weight, nonlinearity='relu')
3.3批归一化BN
A全连接层 —————激活函数——B全连接层
A全连接层——BN——激活函数——B全连接层
对每一层的输入进行标准化处理,使输入分布稳定在均值为 0、方差为 1 附近,从而在进入激活函数之前数据间的差距能变得更小。
class NetWithBN(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(784, 256),
nn.BatchNorm1d(256), # 添加BN层
nn.ReLU(),
nn.Linear(256, 10)
)
3.4残差连接(Residual Connection)【消失】
在某些层的输出上加上输入x,关键在于求导 “+1”
class ResidualBlock(nn.Module):
def __init__(self, in_features):
super().__init__()
self.fc = nn.Linear(in_features, in_features)
self.relu = nn.ReLU()
def forward(self, x):
out = self.fc(x)
out = self.relu(out)
out += x # 残差连接
return out
3.5梯度裁剪(Gradient Clipping)【爆炸】
当梯度的 “大小” 超过预设阈值时,按比例缩小梯度,使其控制在合理范围内,保证参数更新的稳定性。
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for epoch in range(epochs):
for inputs, targets in dataloader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
# 梯度裁剪(关键步骤)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
四、学习率不合适
学习率
决定每次更新参数的步长。如果学习率不合适,会导致训练问题。
- 学习率太大:训练不稳定,损失震荡或发散。
- 学习率太小:收敛太慢,训练时间长甚至停滞。
当学习率过大时:降低并稳定学习率直接减小学习率:
- 通常
按比例缩小
(如除以 10),例如从 0.1→0.01,0.001→0.0001。初次调整后观察损失是否趋于稳定(不再剧烈波动)。若损失仍波动,继续缩小(如再除以 5),直到损失呈现下降趋势。- 配合
梯度裁剪
:学习率过大可能伴随梯度爆炸,通过梯度裁剪(如设置 L2 范数阈值 1.0)限制梯度幅度,再适当降低学习率,可快速稳定训练。- 使用
学习率预热(Warm-up)
:初始用极小的学习率(如目标学习率的 1/10),逐步增大至目标值(如前 5 个 epoch 线性增长),避免训练初期的剧烈震荡。- 适用场景:大型模型(如 Transformer)、大批次训练(如批大小 > 512)。
当学习率过小时:增大并加速收敛直接增大学习率:
按比例放大
(如乘以 5 或 10),例如从 0.0001→0.0005,0.01→0.05。观察损失是否下降加快,但需避免过大导致震荡。动态调整学习率
(学习率调度):初期用较大学习率快速下降,后期减小学习率精细优化,
- 分段衰减(Step Decay):每训练一定 epoch(如 30 轮),学习率乘以衰减系数(如 0.1)。
- 指数衰减(Exponential Decay):学习率随 epoch 指数下降. lr = lr 0 ⋅ γ epoch \text{lr} = \text{lr}_0 \cdot \gamma^{\text{epoch}} lr=lr0⋅γepoch
- 余弦退火(Cosine Annealing):学习率随 epoch 按余弦曲线周期性下降,适合需要精细收敛的场景。
- 自适应调度(ReduceLROnPlateau):当验证集损失停止下降时,自动减小学习率(如乘以 0.5)。
- 结合批大小调整:若增大批大小(如从 32→128),可按比例增大学习率(如从 0.01→0.04),保持参数更新幅度一致(线性缩放规则)。
五、数据不平衡
数据不平衡
:训练数据中某些类别样本太少,导致模型偏向多的类别,分类效果差。
解决方案:
- 重采样(Resampling):增加少数类别样本(过采样)或减少多数类别样本(欠采样)。
- 类别权重(Class Weight):在损失函数中给少数类更高权重。
- 使用
Focal Loss
:专门关注难分类的样本。
六、泛化不足
模型在训练数据上表现很好,但在新数据上表现差。
原因:
- 过拟合。
- 数据分布变化(训练数据和实际应用数据差异大)。
解决方案:
- 增加训练数据或做数据增强(Data Augmentation)。
- 使用正则化。
- 尝试迁移学习(Transfer Learning),用已有模型在新数据上微调。
七、训练不稳定
训练过程中损失波动大,模型难以收敛。
原因:
- 学习率过大。
- 网络结构不合理或初始化不当。
- 数据噪声大或批量归一化问题。
解决方案:
- 调小学习率,使用自适应优化器。
- 使用 Batch Normalization,稳定各层输入分布。
- 改善数据质量,清理异常值。
八、部署慢
模型训练完成后,在实际应用中运行缓慢,影响效率。
原因:
- 模型太大,参数多。
- 硬件性能不足。
- 推理优化不足。
解决方案:
- 模型压缩:剪枝(Pruning)、量化(Quantization)、知识蒸馏(Knowledge Distillation)。
- 使用 GPU / TPU 或高性能硬件。
- 使用高效推理框架,如 TensorRT、ONNX Runtime。