训练模型涉及两个2个关键步骤:

  1.优化,减少训练集上的损失

  2.泛化,提高对没见过的数据如验证机和测试集的泛化能力

而正则化可以有效的帮助我们的模型收敛和泛化。本文提供三个正则化的方法。

一、检查参数:权重惩罚

    稳定泛化的第一种方法实在损失中添加一个正则化项。这个术语的设计是为了减小模型本身的权重,从而限制训练对它们增长的影响。换句话说,这是对较大权重的惩罚。这使得损失更平滑,并且从拟合单个样本中获得的收益相对较小。

    比较流行的正则化项是L2正则化和L1正则化。L2正则化是模型中所有权重的平方和,L1正则化是模型中所有权中的绝对值之和。它们都通过一个小因子进行缩放,这个因子是我们在训练前设置的超参数。

    L2正则化也成为权重衰减。叫这个名字的原因是考虑到SGD和反向传播,L2正则化对参数weight的负梯度为-2*lambda*weight,其中lambda也是超参数,在Pytorch中简称为权重衰减。因此,在损失函数中加入L2正则化,相当于在优化步骤中将每个权重按照其当前值的比例递减。要注意,权重衰减适用于网络的所有参数,例如偏置。

import datetime

def training_loop_l2reg(n_epochs,optimizer,model,loss_fn,train_loader):
    for epoch in range(1, _epochs + 1)
        loss_train = 0.0
        for imgs, labels in train_loader:
            imgs = imgs.to(device=device)
            labels = labels.to(device=device)
            outputs = model(imgs)
            loss = loss_fn(outputs, labels)
            
            l2_lambda = 0.001
            l2_norm = sum(p.pow(2.0).sum() for p in model.parameters())
            loss = loss + l2_lambda * l2_norm

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            loss_train += loss.item()
        
        if epoch == 1or epoch % 10 == 0:
           print('{} Epoch {} , Training loss {}'.format(
               datetime.datetime.now(), epoch,
               loss_train / len(train_loader)))

二、不太依赖于单一输入:Dropout

    Dropout背后的思想其实很简单:将网络每轮训练迭代中的神经元随机部分清零。

    Dropout在每次迭代中有效地生成具有不同神经元拓扑的模型,使得模型中的神经元在过拟合过程中协调记忆过程的机会更少。

    在Pytorch中,我们可以通过在非线性激活与后面的线性或卷积模块之间添加一个nn.Dropout模块在模型中实现Dropout。作为一个参数,我们需要制定输入归零的概率。如果是卷积,我们将使用专门的nn.Dropout2d或者nn.Dropout3d,将输入的所有通道归零:

import torch.nn.functional as F  # 提供许多和nn模块类似的函数

class NetDropout(nn.Module):
    def __init__(self, n_chansl=32):
        super().__init__()
        self.n_chansl = n_chansl
        self.conv1 = nn.Conv2d(3, n_cahnsl, kernel_size=3, padding=1)
        self.conv1_dropout = nn.Dropout2d(p=0.4)
        self.conv1 = nn.Conv2d(n_cahnsl, n_cahnsl // 2, kernel_size=3, padding=1)
        self.conv1_dropout = nn.Dropout2d(p=0.4)
        self.fc1 = nn.Linear(8*8*n_cahnsl//2, 32)
        self.fc2 = nn.Linear(32, 2)

    def forward(self, x):
        out = F.max_pool2d(torch.tanh(self.conv1(x)), 2)
        out = self.conv1_dropout(out)
        out = F.max_pool2d(torch.tanh(self.conv1(out)), 2)
        out = self.conv1_dropout(out)
        out = out.view(-1, 8*8*self.n_chansl // 2)
        out = self.fc2(out)
        return out

    注意:在训练过程中Dropout通常是活跃的,而在生产过程中评估一个模型的时候,会绕过Dropout,或者等效地给其分配一个0的概率。

三、保持激活检查:批量归一化Batch Normlization(BN)

    Batch Normlization允许我们提高学习率,减少训练对初始化的依赖,并充当正则化器,是一种代替Dropout的方法。

    BN背后的主要思想是将输入重新调整到网络的激活状态,从而使小批量具有一定的理想分布。实际上,批量归一化使用在该中间位置收集的小批量样本的平均值和标准差来对中间输入进行移位和缩放。

    Pytorch提供了nn.BatchNorm1d、nn.BatchNorm2d、nn.BatchNorm3d来实现批量归一化,使用哪种模块取决于输入的维度。

    由于批量归一化的目的是重新调整激活的输入,因此其位置是在线性变换(卷积)和激活函数之后:

import torch.nn.functional as F  # 提供许多和nn模块类似的函数

class NetBatchNorm(nn.Module):
    def __init__(self, n_chansl=32):
        super().__init__()
        self.n_chansl = n_chansl
        self.conv1 = nn.Conv2d(3, n_cahnsl, kernel_size=3, padding=1)
        self.conv1_batchnorm = nn.BatchNorm2d(num_features=n_chansl)
        self.conv1 = nn.Conv2d(n_cahnsl, n_cahnsl // 2, kernel_size=3, padding=1)
        self.conv1_batchnorm = nn.BatchNorm2d(num_features=n_chansl // 2)
        self.fc1 = nn.Linear(8*8*n_cahnsl//2, 32)
        self.fc2 = nn.Linear(32, 2)

    def forward(self, x):
        out = self.conv1_batchnorm(self.conv1(x))
        out = F.max_pool2d(torch.tanh(out), 2)
        out = self.conv1_batchnorm(self.conv2(out))
        out = F.max_pool2d(torch.tanh(out), 2)
        out = out.view(-1, 8*8*self.n_chansl // 2)
        out = torch.tanh(self.fc1(out))
        out = self.fc2(out)
        return out