天池——街景字符编码识别 4

模型的训练与验证

在前面选定了网络结构和训练方式后,我们就要开始训练我们的数据集。

  1. 定义算法公式,也就是神经网络的前向算法。(前向算法的作用是计算输入层结点对隐藏层结点的影响,也就是说,把网络正向的走一遍:(输入层—->隐藏层—->输出层)计算每个结点对其下一层结点的影响。 )
  2. 定义loss,选择优化器,来让loss最小
  3. 对数据进行迭代训练,使loss到达最小
  4. 在测试集或者验证集上对准确率进行评估

baseline中的前向传播在模型中已经定义了

########前向传播算法
def forward(self, img):        
        feat = self.cnn(img)
        # print(feat.shape)
        feat = feat.view(feat.shape[0], -1)
        c1 = self.fc1(feat)
        c2 = self.fc2(feat)
        c3 = self.fc3(feat)
        c4 = self.fc4(feat)
        c5 = self.fc5(feat)
        return c1, c2, c3, c4, c5

(以填充字符来进行定长字符预测的baseline代码为例)

def train(train_loader, model, criterion, optimizer, epoch):
    # 切换模型为训练模式
    model.train()
    train_loss = []
    
    for i, (input, target) in enumerate(train_loader):
        if use_cuda:
            input = input.cuda()
            target = target.cuda()
            
        c0, c1, c2, c3, c4 = model(input)
        loss = criterion(c0, target[:, 0]) + \
                criterion(c1, target[:, 1]) + \
                criterion(c2, target[:, 2]) + \
                criterion(c3, target[:, 3]) + \
                criterion(c4, target[:, 4])
        
        # loss /= 6
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        train_loss.append(loss.item())
    return np.mean(train_loss)

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

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

def predict(test_loader, model, tta=10):
    model.eval()
    test_pred_tta = None
    
    # TTA 次数
    for _ in range(tta):
        test_pred = []
    
        with torch.no_grad():
            for i, (input, target) in enumerate(test_loader):
                if use_cuda:
                    input = input.cuda()
                
                c0, c1, c2, c3, c4 = model(input)
                if use_cuda:
                    output = np.concatenate([
                        c0.data.cpu().numpy(), 
                        c1.data.cpu().numpy(),
                        c2.data.cpu().numpy(), 
                        c3.data.cpu().numpy(),
                        c4.data.cpu().numpy()], axis=1)
                else:
                    output = np.concatenate([
                        c0.data.numpy(), 
                        c1.data.numpy(),
                        c2.data.numpy(), 
                        c3.data.numpy(),
                        c4.data.numpy()], axis=1)
                
                test_pred.append(output)
        
        test_pred = np.vstack(test_pred)
        if test_pred_tta is None:
            test_pred_tta = test_pred
        else:
            test_pred_tta += test_pred
    
    return test_pred_tta

训练集 : 模型用于训练和调整模型参数;
验证集 : 用来验证模型精度和调整模型超参数;
测试集 : 验证模型的泛化能力

网络模型构建好后,需要利用训练数据不断进行学习训练来降低 loss 提高模型性能,使得最后得到的结果误差尽可能小,尽可能满足需要,一个成熟合格的深度学习训练流程应该具备:

  1. 在训练集上进行训练并在验证集上进行验证
  2. 模型可以保留最优权重,并读取权重
  3. 记录下训练集和验证集的精度,便于调参

当训练样本足够大时,则有可能会出现过拟合现象,这时候就需要构建验证集来减少过拟合发生的情况,来验证模型的精度过拟合和欠拟合情况。

使用训练集对模型进行训练及调参,使用验证集用来模型精度及调整模型超参数,使用测试集用来验证模型的泛化能力

验证集的划分方法:

  1. 留出法 直接将训练集划分成两部分 缺点:仅有一份验证集,会可能出现验证集过拟合
  2. 交叉验证法 分成k份 k-1份作为训练集,1份作为验证集,模型验证精度是k份平均,优点是验证集精度可靠,缺点是训练k次
  3. 自助采样
model = SVHN_Model1()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), 0.001)
best_loss = 1000.0

use_cuda = True
if use_cuda:
    model = model.cuda()

for epoch in range(50):
    train_loss = train(train_loader, model, criterion, optimizer, epoch)
    val_loss = validate(val_loader, model, criterion)
    
    val_label = [''.join(map(str, x)) for x in val_loader.dataset.img_label]
    val_predict_label = predict(val_loader, model, 1)
    val_predict_label = np.vstack([
        val_predict_label[:, :11].argmax(1),
        val_predict_label[:, 11:22].argmax(1),
        val_predict_label[:, 22:33].argmax(1),
        val_predict_label[:, 33:44].argmax(1),
        val_predict_label[:, 44:55].argmax(1),
    ]).T
    val_label_pred = []
    for x in val_predict_label:
        val_label_pred.append(''.join(map(str, x[x!=10])))
    
    val_char_acc = np.mean(np.array(val_label_pred) == np.array(val_label))
    
    print('Epoch: {0}, Train loss: {1} \t Val loss: {2}'.format(epoch, train_loss, val_loss))
    print('Val Acc', val_char_acc)
    # 记录下验证集精度
    if val_loss < best_loss:
        best_loss = val_loss
        # print('Find better model in Epoch {0}, saving model.'.format(epoch))
        torch.save(model.state_dict(), '/content/drive/My Drive/model.pt')

训练:迭代进行
损失函数:交叉熵

交叉熵

如果你知道一个变量的真实分布,那么为了使得你使用的平均bit最少,那么你应该给这个变量的第i个取值分配log1/yi个bit,其中yi是变量的第i个取值的概率。如果我们按照这样的方式来分配bit,那么我们就可以得到最优的数据传输方案,在这个方案下,我们为了传输这个分布产生的数据,平均使用的bit数量为:
在这里插入图片描述
但是在大部分真实场景中,我们往往不知道真实y的分布,但是我们可以通过一些统计的方法得到y的一个估计。套用之前的式子,将log中的y替换成为y,我们可以得到,如果使用y作为“工具”来对数据进行编码传输,能够使用的最小平均bit数为:
在这里插入图片描述
这个量,就是所谓的交叉熵(cross entropy),代表的就是使用y^ 来对y进行编码的话,需要使用的最短平均bit串长度。交叉熵永远大于或等于熵,因为交叉熵是在用存在错误的信息来编码数据,所以一定会比使用正确的信息要使用更多的bit。只有当y^和y完全相等时,交叉熵才等于熵。

分类问题的损失函数,从信息论的角度来看,等价于训练出来的模型(分布)与真实模型(分布)之间的交叉熵(两者相差一个只和样本数据量有关的倍数N),而这个交叉熵的大小,衡量了训练模型与真实模型之间的差距,交叉熵越小,两者越接近,从而说明模型越准确。

参考资料:https://zhuanlan.zhihu.com/p/21562401

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值