神经网络正则化:防止过拟合的七种核心手段
一、范数惩罚当模型参数过多、网络太深而训练数据量相对不足时模型极易产生过拟合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权重衰减。# 内部数学等价于在损失函数后面加了 L2 范数惩罚optimizertorch.optim.SGD(model.parameters(),lr0.01,weight_decay1e-4)L1 正则化在 PyTorch 中需要手动写因为 L1 会产生稀疏矩阵并不适合所有网络层如全连接或卷积所以需要手动累加损失l1_loss0forparaminmodel.parameters():l1_losstorch.sum(torch.abs(param))losscriterion(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 作为激活函数代码实现importtorchimporttorch.nnasnn# 设置固定种子保证复现性torch.manual_seed(42)# 设置超参数 Dropout 激活概率P0.5# 1. 数据加载这里采用固定数据输入传入一条数据四个特征datatorch.tensor([1,3,5,7],dtypetorch.float32)print(f输入的数据为{data},形状为{data.shape})# 2. 创建全连接层, 4个输入7个隐藏神经元进行加权求和linear_layernn.Linear(4,7)linear_outputlinear_layer(data)print(f加权求和的数值为{linear_output})# 3. 使用激活函数计算激活值linear_sigmoidtorch.sigmoid(linear_output)print(f激活值为{linear_sigmoid})# 4. 创建随机失活层设置失活概率 p对激活值进行dropout处理只针对训练阶段dropoutnn.Dropout(pP)dropout.train()# 开启训练模式否则 Dropout 不会生效dropdropout(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_fnViewBackward0)激活值为tensor([0.9867,0.6313,0.9321,0.7025,0.6722,0.7278,0.0170],grad_fnSigmoidBackward0)展示随机失活神经元为tensor([1.9733,1.2625,0.0000,1.4050,0.0000,0.0000,0.0339],grad_fnMulBackward0)原理验证 tensor([1.9733,1.2625,1.8643,1.4050,1.3444,1.4555,0.0339],grad_fnDivBackward0)代码补充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比如p0.5p0.5p0.5时存活的数值直接放大222倍。这样在数学上训练期的输出期望就是存活率(1−p)×放大倍数11−p1\text{存活率} (1-p) \times \text{放大倍数} \frac{1}{1-p} 1存活率(1−p)×放大倍数1−p11。带来的好处是当线上推理调用model.eval()时Dropout 直接关闭网络不需要做任何多余的乘法缩放输入是多少输出期望就是多少。这保证了推理的高性能实现了算法与跨平台部署的完美解耦。三、批量归一化批量归一化是为了解决内部协变量偏移问题加速模型收敛先对数据标准化再对数据重构缩放平移即最后的输出是标准化的数值之后加上缩放和平移。批量归一化是针对网络中间隐藏神经元的输出功能类似实现了标准化和归一化的操作让数值收敛在一个范围内。归一化的生效位置是在全连接层/卷积层计算之后激活函数如 Relu/Sigmoid之前。批量归一化层在计算机视觉领域使用较多。因此针对不同的维度使用不同的批量归一化函数BatchNorm1d处理一维样本散点特征数据它接收形状为N,num_featuresC的张量作为输入 BatchNorm2d处理二维样本图片或特征图它接收形状为N,C,H,W的张量作为输入。 BatchNorm3d处理三维样本视频或图片组它接收形状为N,C,D,H,W的张量作为输入。C 代表有几个特征通道H代表高W代表宽N代表一个批次有多少样本D代表特征通道有多长深度环境模拟输入两个样本每个样本四个特征一维网络架构设计一个全连接层输出为五个隐藏层一个BN层进行数据标准化处理一个激活层输入层 — 全连接层 — 隐藏层 — BN层 — 激活层 — 输出层使用 relu 作为激活函数代码实现# 导包importtorchimporttorch.nnasnn# 1. 创建散点特征样本最少需要2个样本4个特征datatorch.randn(size(2,4))# 一维批处理必须要导入大于两个的样本print(f数据展示{data})# 2. 创建全连接层处理输入数据加权求和输出为5个linearnn.Linear(4,5)linear_outputlinear(data)print(f加权平均后数据{linear_output})print(f加权平均后数据均值{linear_output.mean()})print(f加权平均后数据标准差{linear_output.std()})# 3. 创建BN层bnnn.BatchNorm1d(num_features5)# num_features 相当是【通道】Cbn.train()# 让 BN 层处于训练状态否则动态均值无法生效# 对线性层的结果进行标准化处理bn_outputbn(linear_output)print(fbn层数据{bn_output})# 4. 进行激活处理out_relutorch.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_fnAddmmBackward0)加权平均后数据均值-0.4049370288848877加权平均后数据标准差0.9604332447052002bn层数据tensor([[-1.0000,1.0000,-0.9630,1.0000,-0.9988],[1.0000,-1.0000,0.9630,-1.0000,0.9988]],grad_fnNativeBatchNormBackward0)激活后数据tensor([[0.0000,1.0000,0.0000,1.0000,0.0000],[1.0000,0.0000,0.9630,0.0000,0.9988]],grad_fnReluBackward0)代码补充对于批量归一化传入数据最少是两个批量归一化可以和Dropout 一块使用吗可以一块用但不能盲目乱加。在数学原理上Dropout 的随机性会干扰 BN 对当前批次均值和方差的稳定计算从而引发特征方差偏移Variance Shift导致模型性能下降。为了解决这个冲突我在工程实践中会采取两种策略一是严格控制拓扑顺序坚持‘先做 BN 格式化后做 Dropout 随机失活’二是分工隔离在前面的特征提取层主要靠 BN 稳定数据并加速收敛而在网络末端的高维全连接层专门引入 Dropout 来抑制过拟合。这样就能让两尊大佛各司其职发挥出 11 2 的效果。为什么 BN 层在标准化之后还要做仿射变换加γ\gammaγ和β\betaβ标准化的目的是为了稳定数据分布避免梯度消失和爆炸而加上缩放γ\gammaγ和 平移β\betaβ的目的是为了恢复网络的非线性表达能力。它通过两个可学习的参数允许网络在‘享受数据稳定性’的同时能够自适应地将数据分布调整到激活函数最合理的表达区间防止多层网络退化为线性模型。 这本质上是一种先破后立、张弛有度的数学平衡。