在上一task任务中,我们介绍了字符识别模型,并介绍了很多关于CNN模型,一个成熟合格的深度学习训练流程应具备以下三个功能:
1.训练集上进行训练,并在验证集上进行验证;
2.模型可以保存最优的权重,并读取权重;
3.记录下训练集和验证集的精度,便于调参。
为此我们本任务将构建验证集,模型训练和验证,模型保存与模型加载和模型调参。
学习目标
1.理解验证集的作用,并使用训练集和验证集完成训练。
2.学会使用pytorch环境下的模型读取和加载,并了解调参流程。
构造验证集
在机器学习模型(特别是深度学习模型)的训练过程中,模型是非常容易过拟合的。深度学习模型在不断的训练过程中训练误差会逐渐降低,但测试误差走势不一定。
在模型的训练过程中,模型只能够利用训练数据来进行训练,模型并不能接触到测试集上的样本。因此模型如果将训练集学的过好,模型就会记住训练样本的细节,导致模型在测试集的泛化误差较差,这种现象称为过拟合(即在训练集表现非常好,在测试集表现较差)。与过拟合相对应的是欠拟合(即模型在训练集上拟合比较差,同时不能达到训练预期,对数据学习拟合程度不够,并且提取数据规则不好)。
如图所示L随着模型复杂度和模型训练轮数的增加,CNN模型在训练集上的误差会降低,但在测试集上误差也会降低,然后逐渐身高,而我们为了追求模型在测试集上的精度越高越好。
导致模型过拟合的情况有很多原因,其中最为常见的情况是模型复杂度太高,导致模型学习到了训练集数据上的细微信息,并且学习到细枝末节或者异常值规律。
在这次竞赛中,竞赛数据已经分成训练集和测试集2部分了,我们只需要在训练集上建立模型,并在测试集上进行测试观察loss发生的变化来验证模型的泛化能力。
在一般情况下,我们可以将训练集划分成训练集和验证集:
1.训练集:模型用于训练和调整模型参数;
2.验证集:用于验证模型精度和调整模型超参数;
3.测试集:验证模型的泛化能力。
因为训练集和验证集是分开的,所以模型在验证集上面的精度在一定程度上可以反映模型泛化能力。在划分验证集的时候,需要注意验证集的分布应该与测试集分布保持一致,不然模型在验证集上的精度失去指导意义。
既然验证集这么重要,那么如何划分本地验证集呢?在一些比赛中,赛题方给定验证集:如果赛提分没有给定验证集,那么参赛选手就需要从训练集中拆分一部分得到验证集。验证集的划分有如下几种方式:
1.留一法
直接将训练集划分为2部分,新的训练集和验证集。这种划分方式的有点是为直接简单:缺点是只得到了一份验证集,有可能导致模型在验证集过拟合。留一法应用场景是数据量比较大的情况。
2.交叉验证
将训练集划分为K份,其中K-1份作为训练集,剩余的一份作为验证集,循环K训练。这种划分方式是所有的训练集都是验证集,最终模型验证精度是K份平均得到。这种方式的有点是验证集精度比较可靠稳定,训练K次得到K个多样性差异模型,CV验证缺点是需要训练K次,不适合数据量恒大情况。
3.自助采样法
通过有放回的采样的方式得到新的训练集和验证集。每次的训练集和验证集都有区别的。这种划分方式一般适用于数据量较小情况。
解决过拟合和欠拟合方法
降低过拟合方法:
(1)从数据入手,获得更多的训练数据。使用更多的训练数据是解决过拟合问题最有效方法,因为更多的样本能够让模型学习到更多有效特征,减小噪声的影响。当然,直接加入实验数据一般是非常困难的,但是可以通过一定的规则来扩充训练数据。比如在图像分类问题上,可以通过图像的平移,旋转,缩放,擦除等方式扩充数据,更进一步可以使用生成对抗网络来合成大量的性训练数据。
(2)降低模型的复杂度。在数据比较少的时候,模型过于复杂是产生过拟合的主要因素,适当的降低模型复杂度可以避免模型拟合过多的采样噪声。例如,在神经网络模型中可以减少模型网络层数,神经元个数,dropout,在决策书中,我们可以降低数的深度,降低叶子节点数,进行剪枝等。
(3)加入正则化(L1范式,L2范式)。给模型的参数加入一定的正则约束,比如将权值的大小加入到损失函数中。
(4)集成学习方法。集成学习是把多个模型集成在一起,来降低单一模型的过拟合风险。如bagging方法(RF),boosting方法(adaboosting,GBDT,XGBOOST等)。
降低欠拟合风险方法:
(1)添加新特征。当特征不足或者现有特征与样本标签的相关性不强时,模型容易出现欠拟合。通过挖掘“上下文特征”“文本特征”“数据长度特征”“组合特征”等新特征,往往能够得到更好的效果。在深度学习潮流中,有跟多模型可以帮助完成特征工程,如因子分解机,梯度提升决策树,Deep-crossing等都可以成为丰富特征的方法。
(2)增加模型的复杂度。简单模型的学习能力比较差,通过增加模型的复杂度可以使模型拥有更强的拟合能力。例如,在线性模型中添加高次项,在神经网络中增加网络的层数,或者神经元个数等。
(3)减少正则化系数。正则化是用来防止过拟合的,但是当模型出现欠拟合现象时,则需要有针对性地减少正则化系数。
模型训练与验证
1.构造训练集和验证集。
2.每轮进行训练和验证,并根据最优验证集精度保存模型。
定义好训练,验证和预测模块。
def train(train_loader, model, criterion, optimizer):
# 切换模型为训练模式
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)
target=target.long()
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()
if i % 100 == 0:
print(loss.item())
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)
target=target.long()
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)
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
迭代训练和验证模型
model = SVHN_Model1()
criterion = nn.NLLLoss2d()
optimizer = torch.optim.Adam(model.parameters(), 0.0005)
best_loss = 1000.0
use_cuda = False
if use_cuda:
model = model.cuda()
for epoch in range(35):
train_loss = train(train_loader, model, criterion, optimizer)
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_char_acc)
# 记录下验证集精度
if val_loss < best_loss:
best_loss = val_loss
torch.save(model.state_dict(), './model_tzzq.pt')
模型保存与加载
在pytorch中模型的保存和价值非常简单,比较常见的做法是保存和加载模型参数:
torch.save(model_object,state_dict(),'model.pt')
model.load_state_dict(torch.load('model.pt'))
模型调参流程
深度学习原来少但是实践性非常强,基本上很多的模型的验证只能通过训练完成。同时深度学习有众多的网络结果和超参数,因此需要反复尝试。训练深度学习模型需要GPU硬件支持。也需要较多的训练时间,如何有效的训练深度学习模型逐渐成为一门学问。
深度学习有众多的训练技巧,比较推荐阅读链接有:
- http://lamda.nju.edu.cn/weixs/project/CNNTricks/CNNTricks.html
- . . http://karpathy.github.io/2019/04/25/recipe/
与传统的机器学习模型不同,深度学习模型的精度和模型的复杂度,数据量,正则化,数据扩增等因素直接相关,所以当深度学习模型处于不同的阶段(欠拟合,过拟合和完美拟合)的情况下,可以从什么角度进行模型继续优化。
在本次比赛中,模型调参可以从简单构建CNN开始进行模型参数认识:
1.初步构建简单CNN模型,2层CNN,跑通训练,验证,和预测过程。
2.简单CNN模型的损失会比较大,尝试着增加CNN层数,增加模型复杂度,并观察验证集精度变化。
3.在增加模型复杂度同时增加数据扩增方法,资质验证集精度不变。