目录

  • 一、前言
  • 二、模型训练与验证
  • 三、保存模型与调参


一、前言

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方式等
  • 提高网络深度和复杂度。