Task4的目的是学习模型的训练与验证,这几次的学习都是对Baseline的拆分理解,在上一次任务中构建CNN模型的基础上,继续学习如何构建验证集,如何进行模型的训练和验证,如何保存和加载模型,如何进行模型的调参,下面将介绍这几个部分。

1、构建验证集:
众所周知,训练集(Train Set)、测试集(Test Set)、验证集(Validation Set)在深度学习框架的学习中必不可少,训练集的作用是用于训练和调整模型参数;测试集的作用时验证模型的泛化能力;而验证集的作用是什么呢?
训练集和测试集的存在是为了让我们能从众多模型中选出泛化能力较强的模型,而我们除了模型与模型之间的对比,还需要我们对模型本身进行选择,假设我们有两个模型,线性模型和神经网络模型,神经网络的泛化能力要比线性模型要强,我们选择了神经网络模型,但是神经网络中还有很多的需要人工进行选择的参数,比如神经网络的层数和每层神经网络的神经元个数以及正则化的一些参数等等,我们将这些参数称为超参数。这些参数不同选择对模型最终的效果也很重要,我们在开发模型的时候总是需要调节这些超参数,虽然我们可以在测试集上进行调节,但是这会造成“信息泄露”现象,此时我们就可以通过验证集来作为调整模型的依据,这样不至于将测试集中的信息泄露。所以验证集的作用时用来验证模型精度和调整模型超参数;
验证集的划分方式:

  • 留出法(Hold-Out)
    直接将训练集划分成两部分,新的训练集和验证集。这种划分方式的优点是最为直接简单;缺点是只得到了一份验证集,有可能导致模型在验证集上过拟合。留出法应用场景是数据量比较大的情况。
  • 交叉验证法(Cross Validation,CV)
    将训练集划分成K份,将其中的K-1份作为训练集,剩余的1份作为验证集,循环K训练。这种划分方式是所有的训练集都是验证集,最终模型验证精度是K份平均得到。这种方式的优点是验证集精度比较可靠,训练K次可以得到K个有多样性差异的模型;CV验证的缺点是需要训练K次,不适合数据量很大的情况。
  • 自助采样法(BootStrap)
    通过有放回的采样方式得到新的训练集和验证集,每次的训练集和验证集都是有区别的。这种划分方式一般适用于数据量较小的情况。

2、模型的训练和验证:
步骤一:定义训练数据的Dateset

#train_dataset
train_loader = torch.utils.data.DataLoader(    
    SVHNDataset(train_path, train_label,
                transforms.Compose([
                    transforms.Resize((64, 128)),
                    transforms.RandomCrop((60, 120)),
                    transforms.ColorJitter(0.3, 0.3, 0.2),
                    transforms.RandomRotation(5),
                    transforms.ToTensor(),
                    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])), 
    batch_size=10, 
    shuffle=True, 
    #num_workers=10, 
)

步骤二:定义好验证数据的DateSet

#val_dataset
val_loader = torch.utils.data.DataLoader(    
    SVHNDataset(val_path, val_label,
                transforms.Compose([
                    transforms.Resize((60, 120)),
                    # transforms.ColorJitter(0.3, 0.3, 0.2),
                    # transforms.RandomRotation(5),
                    transforms.ToTensor(),
                    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])), 
    batch_size=10, 
    shuffle=False, 
    #num_workers=10, 
)

步骤三:定义好训练模块

def train(train_loader, model, criterion, optimizer, epoch):
    # 切换模型为训练模式
    model.train()
    val_loss = []

    for i, (input, target) in enumerate(train_loader):
        c0, c1, c2, c3, c4, c5 = model(data[0])
        loss = criterion(c0, data[1][:, 0]) + \
                criterion(c1, data[1][:, 1]) + \
                criterion(c2, data[1][:, 2]) + \
                criterion(c3, data[1][:, 3]) + \
                criterion(c4, data[1][:, 4]) + \
                criterion(c5, data[1][:, 5])
        loss /= 6
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

步骤四:定义好验证模块

def validate(val_loader, model, criterion):
    # 切换模型为验证模型
    model.eval()
    val_loss = []

    # 不记录模型梯度信息
    with torch.no_grad():
        for i, (input, target) in enumerate(val_loader):
            c0, c1, c2, c3, c4, c5 = model(data[0])
            loss = criterion(c0, data[1][:, 0]) + \
                    criterion(c1, data[1][:, 1]) + \
                    criterion(c2, data[1][:, 2]) + \
                    criterion(c3, data[1][:, 3]) + \
                    criterion(c4, data[1][:, 4]) + \
                    criterion(c5, data[1][:, 5])
            loss /= 6
            val_loss.append(loss.item())
    return np.mean(val_loss)

步骤五:将模型进行迭代训练

model = SVHN_Model1()
criterion = nn.CrossEntropyLoss (size_average=False)
optimizer = torch.optim.Adam(model.parameters(), 0.001)
best_loss = 1000.0
for epoch in range(20):
    print('Epoch: ', epoch)

    train(train_loader, model, criterion, optimizer, epoch)
    val_loss = validate(val_loader, model, criterion)
    
    # 记录下验证集精度
    if val_loss < best_loss:
        best_loss = val_loss
        torch.save(model.state_dict(), './model.pt')

以上即为如何训练和验证模型。

3、保存和加载模型:
在Pytorch中模型的保存和加载非常简单,比较常见的做法是保存和加载模型参数:
torch.save(model_object.state_dict(), 'model.pt')model.load_state_dict(torch.load(' model.pt'))

4、模型调参:
深度学习原理少但实践性非常强,基本上很多的模型的验证只能通过训练来完成。同时深度学习有众多的网络结构和超参数,因此需要反复尝试。
深度学习有众多的训练技巧

以下有一些调整模型的小Tips

  • 研究理想的预训练结构:了解迁移学习的好处,或浏览一些强大的CNN架构。考虑那些看起来不太合适,但是共享特征的领域。
  • 使用较小的学习率:因为预训练的权重通常比随机初始化的权重要好,你在这里的选择取决于学习环境和预训练的进展情况,在不同的epochs上检测误差,了解你离收敛有多近。
  • 使用dropout:与Ridge和LASSO正则化回归模型一样,没有一个最优的α适合所有的模型,它是一个超级参数,取决于你的具体问题,必须进行测试。从更大的变化开始,就和上面的学习率一样。
  • 限制权值大小:我们可以限制某些层的权值的最大范数(绝对值),以泛化我们的模型。
  • 不要动第一层:神经网络的第一个隐藏层倾向于捕捉通用的和可解释的特征,如形状、曲线或交互,这些特征通常与领域相关。我们通常最好是别动这些,重点优化其他的层。这可能意味着添加隐藏层,所以先着急。
  • 修改输出层:用一个新的激活函数和适合你的领域的输出大小替换模型默认值。然而,不要把自己局限于最明显的解决方案上。虽然MNIST可能看起来想要10个输出类,但是一些数字有共同的变化,12-16个类可能会更好地解决这些变化,并提高模型性能!正如上面的提示一样,越接近输出,深度学习模型应该越来越多地进行修改和定制。