目录
- 一、前言
- 二、模型训练与验证
- 三、保存模型与调参
一、前言
DL中,当构建了一个CNN模型,只是定义了一个Input、Output接口,无论是单张图片还是Batch多张图片,都需要取训练这个模型以达到目的得参数,训练一个模型一般有三个步骤:
- 分别定义两个数据集trainsets和validsets,分别完成模型的训练与验证
- 保存最优参数(权重、偏置等)
- 记录trainsets和validsets的精度,便于调参。
通过以上的步骤,可以得到CNN模型的参数了,利用训练的参数可以对任意输入图像进行测试了。
二、模型训练与验证
首先明确几个概念:
- 训练集(Trainsets):模型用于训练和调整模型参数
- 验证集(Validsets):用来验证模型精度和调整模型超参数
- 测试集(Testsets):验证模型的泛化能力
训练过程用到Trainsets+Validsets,而他两者是分开的,所以模型在验证集上面的精度一定程度上可以反映模型的泛化能力。为了保证训练参数对于验证集的有效性,应该保持两者的数据分布一致。
较好的模型应该能够随着训练Epoch的增加训练和验证误差同时降低,而在实际训练过程中常会发生两种常见泛化误差高的情况:
- 欠拟合:以字符识别来说,训练样本被提取的特征较少,导致训练出来的模型不能很好在验证集上识别,甚至训练集里面的样本都不能有效识别。这一般是训练样本较少造成的,实际上我们在训练DL模型过程中是需要大量样本的。
- 过拟合:随着模型复杂度和训练Epoch的增加,CNN模型在训练集上的误差降低,但是在测试集上的误差会降低后升高,如下图所示:
这是由于训练过程过于细致,以至于把样本的特有特征也当做识别特征一起训练了,所以在验证集上会表现得较差。
针对过拟合的情况,可以从两个方面来解决(1)模型训练方式,(2)数据集。
1、模型训练方式
- 正则化(Regulation)
- Dropout:随机失活
- 提前停止训练
2、数据集
合理的从给定的数据集中拆分出训练集和验证集,将大大减低模型过拟合的可能,常用的验证集划分的方法有:
- 留出法(Hold-out):按一定比例直接将训练集划分为两部分。训练集和验证集一般是7:3或8:2。
- K折交叉验证(K-flod Cross Validation):将训练集划分为K份,将其中K-1份作为训练集,剩下一份作为验证集,循环K次训练。
- 自助采样(BootStrap):通过有放回的采样方式得到新的训练集和验证集,这样每次的训练集和验证集都是有区别的。这对小数据量的训练较为适用。
Pytorch中模型训练和验证的一般代码为:
# 构造训练集
train_loader = torch.utils.data.DataLoader(
train_dataset,
batch_size=10,
shuffle=True,
num_workers=10, )
# 构造验证集
val_loader = torch.utils.data.DataLoader(
val_dataset,
batch_size=10,
shuffle=False,
num_workers=10, )
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')
其中每个Epoch的训练方法之前已经定义:
def train(train_loader, model, criterion, optimizer, epoch):
# 切换模型为训练模式
model.train()
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()
其中每个Epoch的验证代码如下:
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)
三、保存模型与调参
在Pytorch中模型的保存于加载实现为:
# 模型保存到 ./model.pt文件下
torch.save(model_object.state_dict(), 'model.pt')
#加载模型 ./model.pt
model.load_state_dict(torch.load(' model.pt'))
DL的调参实践性较强,只有通过不断地训练过程才能确定模型的优劣,通常情况下,首先考虑构建最简单的模型进行训练,跑通train、valid和 test的过程,然后从以下几个方面体高精度:
- 数据扩充,提高泛化能力
- 改变Loss function,dropout,BN方式等
- 提高网络深度和复杂度。