4.模型训练与验证
4.1 学习目标
- 理解验证集的作用,并使用训练集和验证集完成训练
- 学会使用pytorch环境下的模型读取和加载,并了解调参流程
4.2 验证集
一般情况下,将总样本分成三个部分:
- 训练集(Train Set):用于训练和调整模型参数的数据集。
- 验证集(Validation Set):用于验证模型精度和调整模型参数。
- 测试集(Test Set): 验证模型的泛化能力。
需要注意验证集的分布应该与测试集尽量保持一致,不然模型在验证集上的精度就失去了指导意义。
验证集划分的方法:
- 留出法(Hold-out)
直接将训练集划分成两部分,新的训练集和验证集。这种划分方式的优点是最为直接简单;缺点是只得到了一份验证集,有可能导致模型在验证集上过拟合。留出法应用场景是数据量比较大的情况。 - 交叉验证法(Cross-Validation,CV)
将训练集划分成K份,将其中的K-1份作为训练集,剩余的1份作为验证集,循环K训练。这种划分方式是所有的训练集都是验证集,最终模型验证精度是K份平均得到。这种方式的优点是验证集精度比较可靠,训练K次可以得到K个有多样性差异的模型;CV验证的缺点是需要训练K次,不适合数据量很大的情况。 - 自助采样法(BootStrap)
通过有放回的采样方式得到新的训练集和验证集,每次的训练集和验证集都是有区别的。这种划分方式一般适用于数据量较小的情况。
4.3 baseline的代码解读
- train()函数
def train(train_loader, model, criterion, optimizer, epoch):
model.train() # 切换模型为训练模式,自动计算梯度
train_loss = [] # 记录每一个iteration的的loss,求平均便是整个epoch的误差
for i, (input, target) in enumerate(train_loader): # 从训练样本中取出batch_size个样本训练
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() # loss反向传播,
optimizer.step() # 优化器更新权重
train_loss.append(loss.item())
return np.mean(train_loss)
- validate()函数
def validate(val_loader, model, criterion):
model.eval() # 切换模型为预测模型,不记录模型梯度信息
val_loss = [] # 记录每一个iteration的的loss,求平均便是整个epoch的误差
# 不记录模型梯度信息
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)
- predict()函数
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)
target = target.long()
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) # 将每个batch_size的预测结从上到下连起来
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.CrossEntropyLoss() # 定义损失函数为交叉熵
optimizer = torch.optim.Adam(model.parameters(), 0.001) # 定义优化器
best_loss = 1000.0 # 定义初始loss
use_cuda = True
if use_cuda:
model = model.cuda()
train_loss_all = [] # 记录train每一个epoch的loss
val_loss_all = [] # 记录val每一个epoch的loss
val_acc = [] # 记录val 准确率的变化情况
for epoch in range(25):
train_loss = train(train_loader, model, criterion, optimizer, epoch)
val_loss = validate(val_loader, model, criterion)
train_loss_all.append(train_loss)
val_loss_all.append(val_loss)
val_label = [''.join(map(str, x)) for x in val_loader.dataset.img_label] # 用map函数做类型转换,读取出label
val_predict_label = predict(val_loader, model, 1) # 得到预测的label
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 # 取出预测概率最大的作为预测值, 并将5位连在一起
val_label_pred = []
for x in val_predict_label:
val_label_pred.append(''.join(map(str, x[x!=10]))) # 将预测值为10的剔除
val_char_acc = np.mean(np.array(val_label_pred) == np.array(val_label)) # 求准确率
val_acc.append(val_char_acc)
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(), './model_2.pt')