机器学习——正则化、权重衰减、暂退法

正则化

正则化(Regularization)是机器学习中的一类技术,其通过对模型添加惩罚项来解决过拟合问题,从而提高模型的泛化能力。

正则化的主要思想是在损失函数中引入模型复杂度的惩罚项,强制模型保持一定的简单性和平滑性

比较常见的正则化方法包括:

- L1正则化:对权重参数的绝对值之和进行惩罚,可以产生稀疏权重矩阵。

- L2正则化:又称权重衰减,对权重参数的平方和进行惩罚,可以减少过拟合。

- 数据增强:通过对训练数据进行变换来增加样本数量,一定程度上也起到正则化效果。

- Early Stopping:在验证集误差不再下降时提前结束训练,防止过度拟合。

- DropOut:在训练过程中随机丢弃一部分神经元,减少模型依赖性。

 

🤔️L1正则化和L2正则化

使用范数的一个原因是它对权重向量的大分量施加了巨大的惩罚。 这使得我们的学习算法偏向于在大量特征上均匀分布权重的模型。 在实践中,这可能使它们对单个变量中的观测误差更为稳定。 相比之下,<span class="math notranslate nohighlight">惩罚会导致模型将权重集中在一小部分特征上, 而将其他权重清除为零。 这称为<em>特征选择</em>(feature selection),这可能是其他场景下需要的。

 

🤔️正则化权重衰减是如何保持模型的简单性和平滑性的呢?(简单性的另一个角度是平滑性,即函数不应该对其输入的微小变化敏感。 例如,当我们对图像进行分类时,我们预计向像素添加一些随机噪声应该是基本无影响的。)

权重绝对值越大,对应的输入特征对输出的影响也越大(对输入数据更加敏感)。这会使得模型对训练数据的依赖性越强。

较大的权重会使激活函数工作在非线性区间,增大模型的非线性特征,使得模型函数更复杂。

较小的权重倾向于让模型学到简单的线性映射,而较大的权重则会促使模型学到复杂的非线性。

权重的平方和可以表示模型复杂度的大小,这是正则化的基础。

 

 

权重衰减

权重衰减(Weight Decay)是正则化的一种技术,是针对神经网络权重参数的正则化手段。其通过为损失函数添加权重参数的L2范数来实现。在优化神经网络时,权重衰减会惩罚权重参数值过大,从而达到正则化的效果。

常见的权重衰减在损失函数中以如下形式添加:

loss = 损失函数 + λ * 权重L2范数 

其中λ是超参数,控制权重衰减的力度。权重L2范数即各个权重参数的平方和。

 

由于权重衰减在神经网络优化中很常用, 深度学习框架为了便于我们使用权重衰减, 将权重衰减集成到优化算法中,以便与任何损失函数结合使用。 此外,这种集成还有计算上的好处, 允许在不增加任何额外的计算开销的情况下向算法中添加权重衰减。(以pyTorch框架为例)

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正则化技术

 

 

暂退法的具体实现代码

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为例)

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层仅传递数据。

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);

 

posted @ 2023-10-30 20:43  Yohoc  阅读(237)  评论(0编辑  收藏  举报