机器学习——正则化、权重衰减、暂退法
正则化
正则化(Regularization)是机器学习中的一类技术,其通过对模型添加惩罚项来解决过拟合问题,从而提高模型的泛化能力。
正则化的主要思想是在损失函数中引入模型复杂度的惩罚项,强制模型保持一定的简单性和平滑性。
比较常见的正则化方法包括:
- L1正则化:对权重参数的绝对值之和进行惩罚,可以产生稀疏权重矩阵。
- L2正则化:又称权重衰减,对权重参数的平方和进行惩罚,可以减少过拟合。
- 数据增强:通过对训练数据进行变换来增加样本数量,一定程度上也起到正则化效果。
- Early Stopping:在验证集误差不再下降时提前结束训练,防止过度拟合。
- DropOut:在训练过程中随机丢弃一部分神经元,减少模型依赖性。
🤔️L1正则化和L2正则化
使用范数的一个原因是它对权重向量的大分量施加了巨大的惩罚。 这使得我们的学习算法偏向于在大量特征上均匀分布权重的模型。 在实践中,这可能使它们对单个变量中的观测误差更为稳定。 相比之下,<span class="math notranslate nohighlight">惩罚会导致模型将权重集中在一小部分特征上, 而将其他权重清除为零。 这称为<em>特征选择</em>(feature selection),这可能是其他场景下需要的。
🤔️正则化权重衰减是如何保持模型的简单性和平滑性的呢?(简单性的另一个角度是平滑性,即函数不应该对其输入的微小变化敏感。 例如,当我们对图像进行分类时,我们预计向像素添加一些随机噪声应该是基本无影响的。)
权重绝对值越大,对应的输入特征对输出的影响也越大(对输入数据更加敏感)。这会使得模型对训练数据的依赖性越强。
较大的权重会使激活函数工作在非线性区间,增大模型的非线性特征,使得模型函数更复杂。
较小的权重倾向于让模型学到简单的线性映射,而较大的权重则会促使模型学到复杂的非线性。
权重的平方和可以表示模型复杂度的大小,这是正则化的基础。
权重衰减
权重衰减(Weight Decay)是正则化的一种技术,是针对神经网络权重参数的正则化手段。其通过为损失函数添加权重参数的L2范数来实现。在优化神经网络时,权重衰减会惩罚权重参数值过大,从而达到正则化的效果。
常见的权重衰减在损失函数中以如下形式添加:
loss = 损失函数 + λ * 权重L2范数
其中λ是超参数,控制权重衰减的力度。权重L2范数即各个权重参数的平方和。
由于权重衰减在神经网络优化中很常用, 深度学习框架为了便于我们使用权重衰减, 将权重衰减集成到优化算法中,以便与任何损失函数结合使用。 此外,这种集成还有计算上的好处, 允许在不增加任何额外的计算开销的情况下向算法中添加权重衰减。(以pyTorch框架为例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | def train_concise(wd): net = nn.Sequential(nn.Linear(num_inputs, 1 )) for param in net.parameters(): param.data.normal_() loss = nn.MSELoss(reduction = 'none' ) num_epochs, lr = 100 , 0.003 # 偏置参数没有衰减 trainer = torch.optim.SGD([ { "params" :net[ 0 ].weight, 'weight_decay' : wd}, { "params" :net[ 0 ].bias}], lr = lr) animator = d2l.Animator(xlabel = 'epochs' , ylabel = 'loss' , yscale = 'log' , xlim = [ 5 , num_epochs], legend = [ 'train' , 'test' ]) for epoch in range (num_epochs): for X, y in train_iter: trainer.zero_grad() l = loss(net(X), y) l.mean().backward() trainer.step() if (epoch + 1 ) % 5 = = 0 : animator.add(epoch + 1 , (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print ( 'w的L2范数:' , net[ 0 ].weight.norm().item()) |
暂退法
其主要思想是:
1. 在每次迭代中,随机选择一部分神经元,暂时不让其激活,即dropout。
2. 剩下的神经元则需要完成原任务,避免过拟合。
3. 每次迭代dropout的节点不同,强迫整个网络协作,避免对特定节点的依赖。
4. 在预测时则不使用dropout。
通过这种“暂退”的随机排除节点,可以正则化模型,减少过拟合。这就是深度学习中常用的Dropout正则化技术。
暂退法的具体实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import torch from torch import nn from d2l import torch as d2l def dropout_layer(X, dropout): assert 0 < = dropout < = 1 # 在本情况中,所有元素都被丢弃 if dropout = = 1 : return torch.zeros_like(X) # 在本情况中,所有元素都被保留 if dropout = = 0 : return X mask = (torch.rand(X.shape) > dropout). float () return mask * X / ( 1.0 - dropout) |
这里输出时除以(1.0 - dropout)是为了使得随机dropout后输出的期望保持不变
例如,一个层有4个神经元,dropout 概率为0.5。则有:- 神经元1:以0.5概率dropout,否则原始值a1
- 神经元2:以0.5概率dropout,否则原始值a2
- ...所以整个层的输出为一个随机向量[r1, r2, r3, r4],其中ri是0或ai。这个随机向量的期望就是原始的输出[a1, a2, a3, a4]。
我们可以将暂退法应用于每个隐藏层的输出(在激活函数之后), 并且可以为每一层分别设置暂退概率: 常见的技巧是在靠近输入层的地方设置较低的暂退概率。 下面的模型将第一个和第二个隐藏层的暂退概率分别设置为0.2和0.5, 并且暂退法只在训练期间有效。
(以pyTorch为例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | dropout1, dropout2 = 0.2 , 0.5 class Net(nn.Module): def __init__( self , num_inputs, num_outputs, num_hiddens1, num_hiddens2, is_training = True ): super (Net, self ).__init__() self .num_inputs = num_inputs self .training = is_training self .lin1 = nn.Linear(num_inputs, num_hiddens1) self .lin2 = nn.Linear(num_hiddens1, num_hiddens2) self .lin3 = nn.Linear(num_hiddens2, num_outputs) self .relu = nn.ReLU() def forward( self , X): H1 = self .relu( self .lin1(X.reshape(( - 1 , self .num_inputs)))) # 只有在训练模型时才使用dropout if self .training = = True : # 在第一个全连接层之后添加一个dropout层 H1 = dropout_layer(H1, dropout1) H2 = self .relu( self .lin2(H1)) if self .training = = True : # 在第二个全连接层之后添加一个dropout层 H2 = dropout_layer(H2, dropout2) out = self .lin3(H2) return out net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2) |
对于深度学习框架的高级API,我们只需在每个全连接层之后添加一个Dropout
层, 将暂退概率作为唯一的参数传递给它的构造函数。 在训练时,Dropout
层将根据指定的暂退概率随机丢弃上一层的输出(相当于下一层的输入)。 在测试时,Dropout
层仅传递数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | net = nn.Sequential(nn.Flatten(), nn.Linear( 784 , 256 ), nn.ReLU(), # 在第一个全连接层之后添加一个dropout层 nn.Dropout(dropout1), nn.Linear( 256 , 256 ), nn.ReLU(), # 在第二个全连接层之后添加一个dropout层 nn.Dropout(dropout2), nn.Linear( 256 , 10 )) def init_weights(m): if type (m) = = nn.Linear: nn.init.normal_(m.weight, std = 0.01 ) net. apply (init_weights); |
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
2022-10-30 1040 有几个PAT