模型的训练与验证
在前面选定了网络结构和训练方式后,我们就要开始训练我们的数据集。
- 定义算法公式,也就是神经网络的前向算法。(前向算法的作用是计算输入层结点对隐藏层结点的影响,也就是说,把网络正向的走一遍:(输入层—->隐藏层—->输出层)计算每个结点对其下一层结点的影响。 )
- 定义loss,选择优化器,来让loss最小
- 对数据进行迭代训练,使loss到达最小
- 在测试集或者验证集上对准确率进行评估
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 提高模型性能,使得最后得到的结果误差尽可能小,尽可能满足需要,一个成熟合格的深度学习训练流程应该具备:
- 在训练集上进行训练并在验证集上进行验证
- 模型可以保留最优权重,并读取权重
- 记录下训练集和验证集的精度,便于调参
当训练样本足够大时,则有可能会出现过拟合现象,这时候就需要构建验证集来减少过拟合发生的情况,来验证模型的精度过拟合和欠拟合情况。
使用训练集对模型进行训练及调参,使用验证集用来模型精度及调整模型超参数,使用测试集用来验证模型的泛化能力
验证集的划分方法:
- 留出法 直接将训练集划分成两部分 缺点:仅有一份验证集,会可能出现验证集过拟合
- 交叉验证法 分成k份 k-1份作为训练集,1份作为验证集,模型验证精度是k份平均,优点是验证集精度可靠,缺点是训练k次
- 自助采样
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),而这个交叉熵的大小,衡量了训练模型与真实模型之间的差距,交叉熵越小,两者越接近,从而说明模型越准确。