神经网络正则化:防止过拟合的七种核心手段

一、范数惩罚

当模型参数过多、网络太深,而训练数据量相对不足时,模型极易产生过拟合(Overfitting) 。此时模型会过度拟合训练集里的每一个噪声和细节,导致权重矩阵 www 的数值变得非常大或极端剧烈,从而丧失了泛化能力。

在机器学习和深度学习中,范数惩罚(Norm Penalty)就是我们常说的 L1 / L2 正则化 。它的核心思想是通过在原本的损失函数(Loss)后面加上一个对模型权重 www 的"惩罚项",以此来约束模型的复杂度,达到防止过拟合的目的

根据惩罚方式的不同,最经典的分为 L1 范数L2 范数

3. L1 范数与 L2 范数深度对比

对比维度 L1 范数(Lasso 回归) L2 范数(Ridge 岭回归 / Weight Decay)
数学几何形态 绝对值之和,几何边界呈现带尖角的菱形 平方和开根号,几何边界呈现平滑的圆形
对权重的作用 稀疏化:强行将大量不重要的权重直接抹为 0 平滑化:将权重整体压低、缩小**,使其趋近于 0 但绝不等于 0。
核心效果 能够自动做特征选择,筛选出真正起作用的特征。 让每个特征都分担一点权重,使模型对输入数据的剧烈波动不敏感
反向传播梯度 无论 www 多大,导数绝对值恒为 1,小权重会被一刀切地抹零。 导数与 www 自身大小成正比(梯度为 www),权重越大惩罚越重,越小惩罚越微弱。

补充:如何区分L1正则和L2正则

L1 正则是 1 ----> 联想到绝对值惩罚

L2 正则中有 2 -----> 对应惩罚项上的平方

在 PyTorch 中怎么用?

L2 正则化在 PyTorch 中不需要写出公式 :它已经被高度集成到了优化器(Optimizer)里,叫做 weight_decay(权重衰减)

python 复制代码
# 内部数学等价于在损失函数后面加了 L2 范数惩罚
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, weight_decay=1e-4)

L1 正则化在 PyTorch 中需要手动写:因为 L1 会产生稀疏矩阵,并不适合所有网络层(如全连接或卷积),所以需要手动累加损失:

python 复制代码
 l1_loss = 0
    for param in model.parameters():
        l1_loss += torch.sum(torch.abs(param))
    loss = criterion(output, target) + alpha * l1_loss

二、Dropout(随机失活)

在模型参数较多,数据量不足的情况下,模型很容易过拟合。Dropout 是在训练过程中,让神经元以超参数 p 的概率下停止工作或者被置为零,未被置为0的进行缩放,放大比例为1/(1-p)。由于不被激活的神经元被缩放,保证了训练和测试的一致。

Dropout 主要是让 w(权重 )随机失去活性。Dropout 的目的是 防止权重 www 之间产生"相互依赖" ------不让某几个权重形成小团体包揽所有活,而是逼着所有权重 www 都独立发展出强大的特征提取能力。

在代码结构上,Dropout 作为一个"特殊的网络层",通常放在隐藏层的激活函数之后,下一层计算之前。同时,为了实现可复现性,可以设置随机种子 torch.manual_seed(42)

环境模拟:

数据输入:一条数据,四个特征

网络设计:仅设计一个全连接层,后面有五个隐藏层(输出),一个激活层,一个Dropout

输入层 ---> 全连接层 ---> 激活层 ---> Dropout层 ---> 输出层

激活函数:使用 sigmoid 作为激活函数

代码实现:

python 复制代码
import torch  
import torch.nn as nn  
# 设置固定种子,保证复现性  
torch.manual_seed(42)  
# 设置超参数 Dropout 激活概率  
P = 0.5  
  
# 1. 数据加载,这里采用固定数据输入,传入一条数据,四个特征  
data = torch.tensor([1, 3, 5, 7], dtype=torch.float32)  
print(f"输入的数据为{data},形状为{data.shape}")  
  
# 2. 创建全连接层, 4个输入,7个隐藏神经元,进行加权求和  
linear_layer = nn.Linear(4, 7)         linear_output = linear_layer(data)     print(f"加权求和的数值为{linear_output}")  
  
# 3. 使用激活函数,计算激活值  
linear_sigmoid = torch.sigmoid(linear_output) print(f"激活值为{linear_sigmoid}")             
# 4. 创建随机失活层(设置失活概率 p),对激活值进行dropout处理(只针对训练阶段)  
  
dropout = nn.Dropout(p=P)  
dropout.train()                        # 开启训练模式,否则 Dropout 不会生效  
drop = dropout(linear_sigmoid)  
print(f"展示随机失活神经元为{drop}")    
# 5. 原理验证  
print('原理验证', linear_sigmoid * 1/(1-p)) # 【修正】将未定义的 h 改为你的激活值 linear_sigmoid

================输出===========================
输入的数据为tensor([1., 3., 5., 7.]),形状为torch.Size([4])
加权求和的数值为tensor([ 4.3039,  0.5376,  2.6199,  0.8591,  0.7182,  0.9833, -4.0589],
       grad_fn=<ViewBackward0>)
激活值为tensor([0.9867, 0.6313, 0.9321, 0.7025, 0.6722, 0.7278, 0.0170],
       grad_fn=<SigmoidBackward0>)
展示随机失活神经元为tensor([1.9733, 1.2625, 0.0000, 1.4050, 0.0000, 0.0000, 0.0339],
       grad_fn=<MulBackward0>)
原理验证 tensor([1.9733, 1.2625, 1.8643, 1.4050, 1.3444, 1.4555, 0.0339],
       grad_fn=<DivBackward0>)

代码补充:

  • dropout.train() 确保了Dropout 激活生效
  • drop 中结果会不一致,因为概率的不同
  • 原理验证中,不失活的神经元和激活值是一致的

PyTorch 为什么要对未失活神经元乘以 11−p\frac{1}{1-p}1−p1?

这种机制是现代深度学习框架标准的 Inverted Dropout(反向随机失活) 机制。

它的核心目的,是为了在模型'训练'和'推理'之间,维持统计学上的期望守恒(能量守恒)

  • 传统的 Vanilla Dropout 做法很死板 :它在训练时把部分神经元抹零,导致能量缩水;等到了推理测试时model.eval()),全员复活,它又必须在代码里把整层输出集体乘以 1−p1-p1−p 来压低能量。这严重污染了推理阶段,导致算法和下游的部署工程严重耦合。
  • 而 PyTorch 颠覆了这一逻辑 :它的哲学是把工程代价全压在训练期,换取推理期极致的纯粹与高效 。既然训练时有概率为 ppp 的人'死去了',那剩下 (1−p)(1-p)(1−p) 活着的人就必须在训练时原地打鸡血,强行把业绩背起来 。所以 PyTorch 训练时直接对未抹零的神经元乘以 11−p\frac{1}{1-p}1−p1(比如 p=0.5p=0.5p=0.5 时,存活的数值直接放大 222 倍)。

这样在数学上,训练期的输出期望就是:存活率(1−p)×放大倍数11−p=1\text{存活率} (1-p) \times \text{放大倍数} \frac{1}{1-p} = 1存活率(1−p)×放大倍数1−p1=1。

带来的好处是:当线上推理调用 model.eval() 时,Dropout 直接关闭,网络不需要做任何多余的乘法缩放,输入是多少,输出期望就是多少。这保证了推理的高性能,实现了算法与跨平台部署的完美解耦。

三、批量归一化

批量归一化是为了解决内部协变量偏移问题,加速模型收敛,先对数据标准化,再对数据重构(缩放+平移),即,最后的输出是标准化的数值之后加上缩放和平移。

批量归一化是针对网络中间隐藏神经元的输出,功能类似实现了标准化和归一化的操作,让数值收敛在一个范围内。

归一化的生效位置是在:全连接层/卷积层计算之后,激活函数(如 Relu/Sigmoid)之前

批量归一化层在计算机视觉领域使用较多。

因此针对不同的维度使用不同的批量归一化函数

python 复制代码
BatchNorm1d:处理一维样本(散点特征数据),它接收形状为(N, num_features=C)的张量作为输入  
BatchNorm2d:处理二维样本(图片或特征图),它接收形状为(N, C, H, W)的张量作为输入。  
BatchNorm3d:处理三维样本(视频或图片组),它接收形状为(N, C, D, H, W)的张量作为输入。
  • C 代表有几个特征通道,H代表高,W代表宽,N代表一个批次有多少样本,D代表特征通道有多长(深度)

环境模拟:

输入两个样本,每个样本四个特征(一维)

网络架构:设计一个全连接层,输出为五个隐藏层,一个BN层进行数据标准化处理,一个激活层

输入层 ---> 全连接层 ---> 隐藏层 ---> BN层 ---> 激活层 ---> 输出层

使用 relu 作为激活函数

代码实现:

python 复制代码
# 导包  
import torch  
import torch.nn as nn  
  
  
# 1. 创建散点特征样本(最少需要2个样本,4个特征)  
data = torch.randn(size=(2, 4))            # 一维批处理,必须要导入大于两个的样本  
print(f"数据展示{data}")  
  
# 2. 创建全连接层,处理输入数据(加权求和),输出为5个  
linear = nn.Linear(4, 5)  
linear_output = linear(data)               print(f'加权平均后数据{linear_output}')  
print(f'加权平均后数据均值{linear_output.mean()}')  
print(f'加权平均后数据标准差{linear_output.std()}')  
  
  
# 3. 创建BN层  
bn = nn.BatchNorm1d(num_features=5)         # num_features 相当是【通道】C  
bn.train()                                 # 让 BN 层处于训练状态,否则动态均值无法生效  
  
# 对线性层的结果进行标准化处理  
bn_output = bn(linear_output)              print(f'bn层数据{bn_output}')  
  
# 4. 进行激活处理  
out_relu = torch.relu(bn_output)           print(f'激活后数据{out_relu}')

===================输出=======================
数据展示tensor([[-0.6441, -0.6061, -0.1425,  0.9727],
        [ 2.0038,  0.6622,  0.5332,  2.7489]])
加权平均后数据tensor([[ 0.0178, -0.3218, -0.1438,  0.1689, -0.4014],
        [ 1.0908, -1.8289, -0.1211, -2.2368, -0.2730]],
       grad_fn=<AddmmBackward0>)
加权平均后数据均值-0.4049370288848877
加权平均后数据标准差0.9604332447052002
bn层数据tensor([[-1.0000,  1.0000, -0.9630,  1.0000, -0.9988],
        [ 1.0000, -1.0000,  0.9630, -1.0000,  0.9988]],
       grad_fn=<NativeBatchNormBackward0>)
激活后数据tensor([[0.0000, 1.0000, 0.0000, 1.0000, 0.0000],
        [1.0000, 0.0000, 0.9630, 0.0000, 0.9988]], grad_fn=<ReluBackward0>)

代码补充:

  • 对于批量归一化,传入数据最少是两个

批量归一化可以和Dropout 一块使用吗?

可以一块用,但不能盲目乱加。

在数学原理上,Dropout 的随机性会干扰 BN 对当前批次均值和方差的稳定计算,从而引发特征方差偏移(Variance Shift),导致模型性能下降。

为了解决这个冲突,我在工程实践中会采取两种策略:

一是严格控制拓扑顺序坚持'先做 BN 格式化,后做 Dropout 随机失活 ';

二是分工隔离,在前面的特征提取层主要靠 BN 稳定数据并加速收敛,而在网络末端的高维全连接层,专门引入 Dropout 来抑制过拟合。这样就能让两尊大佛各司其职,发挥出 1+1 > 2 的效果。

为什么 BN 层在标准化之后,还要做仿射变换(加 γ\gammaγ 和 β\betaβ)?

标准化的目的是为了稳定数据分布,避免梯度消失和爆炸

而加上缩放 γ\gammaγ 和 平移 β\betaβ 的目的是为了恢复网络的非线性表达能力 。它通过两个可学习的参数,允许网络在'享受数据稳定性'的同时,能够自适应地将数据分布调整到激活函数最合理的表达区间,防止多层网络退化为线性模型。 这本质上是一种先破后立、张弛有度的数学平衡。

相关推荐
代码不接地1 小时前
【时间序列预测】0.时间序列任务知识地图
python·ai编程
许彰午2 小时前
77_Python数据清洗实战技巧
开发语言·python
Sam09272 小时前
【AI 算法精讲 16】BPE 分词:从字节对到子词
人工智能·python·算法·ai
江华森2 小时前
04-python-面向对象
开发语言·python
梦帮科技2 小时前
基于EVM架构的Web3音频确权与RNS Token智能合约设计深度解析
人工智能·python·架构·web3·区块链·音视频·智能合约
AI行业学习2 小时前
Notepad++ 官方纯净下载+完整安装教程(Windows)【2026.7.5】
开发语言·windows·python·前端框架·html·notepad++
伏 念3 小时前
Hermes Agent
python
江华森3 小时前
从零构建搜索引擎:Python 异步爬虫 + 倒排索引 + Sanic 前后端实战
python
大模型最新论文速读3 小时前
RL 的 token 选择之争:高熵词到底重不重要?
论文阅读·人工智能·深度学习·神经网络·机器学习