现在数据、模型结构都已经搞定了,接下来我们要做的就是训练我们的模型啦,大家鼓掌庆祝下呗(๑¯∀¯๑)
实际上训练函数部分没有什么难点,大致的内容我在浙大AI口罩作业里面基本都说得比较详细了,所以基本没什么难点呢。总之我们来一点点看一下我们的代码吧。
这一部分主要是设置全局变量,来保存我们在训练过程中得到的损失函数值以及在测试集上的错误率,其实看一眼变量名就能猜出来是干啥的了嘛
接下来使我们的训练函数:
def train(epochs, model, optimizer, scheduler:bool, loss_fn, trainSet, testSet): ···epochs:训练的代数
model:我们定义的模型对象
optimizer:定义的优化器对象
scheduler:这就是和之前内容不太一样的东西啦,确定是否进行学习率的变化
loss_fn:lossfunction,损失函数
trainSet:训练集
testSet:测试集
在训练函数部分,我们需要做的就是一下几点:
训练:
将数据从训练集里面取出来
将标签转换为one-hot格式
将数据放到之前指定的device中,GPU优先,没有就放CPU
计算输出、损失并进行梯度下降
测试:
暂时取消梯度更新
在测试集上数据处理、设备指定
输出,和标签进行比较
学习率的改变:这一部分主要是为了之后的LeNet-5的复现做准备,因为在LeNet-5中,使用的学习率是和当前的训练轮数相关的,具体的代码内容我会放在这里,但是我不会解释,等到后面的LeNet-5再进行详细地说明
好了我们来吧代码放上来吧:
trainNum = len(trainSet) testNum = len(testSet) for epoch in range(epochs): lossSum = 0.0 #训练部分 for idx, (img, label) in enumerate(trainSet): x = img.unsqueeze(0).to(device) #将标签转化为one-hot向量 y = torch.zeros(1, 10) y[0][label] = 1.0 y = y.to(device) #梯度下降与参数更新 out = model(x) optimizer.zero_grad() loss = loss_fn(out, y) loss.backward() optimizer.step() lossSum += loss.item() lossList.append(lossSum / trainNum) #测试部分,每一个epoch训练完毕后就对测试集进行一下测试,保存一个正确率 with torch.no_grad(): errorNum = 0 for img, label in testSet: x = img.unsqueeze(0).to(device) out = model(x) _, pred_y = out.max(dim = 1) if(pred_y != label): errorNum += 1 testError.append(errorNum / testNum) #这一段的代码就是用来改变学习率的,但是先放着,这里还不讲,因为里面涉及到判断,如果觉得会影响性能可以把这里全都注释掉 if scheduler == True: if epoch < 2: for param_group in optimizer.param_groups: param_group['lr'] = 5.0e-4 elif epoch < 5: for param_group in optimizer.param_groups: param_group['lr'] = 2.0e-4 elif epoch < 8: for param_group in optimizer.param_groups: param_group['lr'] = 1.0e-4 elif epoch < 12: for param_group in optimizer.param_groups: param_group['lr'] = 5.0e-5 else: for param_group in optimizer.param_groups: param_group['lr'] = 1.0e-5和之前的浙大AI口罩识别作业的博客里面有明显区别的地方出现啦,不知道细心的小伙伴们有没有发现呢?好啦好啦不卖关子了,出现差异的代码是在训练部分里面:
x = img.unsqueeze(0).to(device)回顾一下之前的代码,我们会发现我们当时就直接写的to(device),根本没有出现这个unsqueeze(0)吖,这啥意思啊?别急别急,马上就说呀。
在之前的口罩识别的作业里面,我们提到Pytorch接收数据的格式是有要求的,并且我们当时还介绍了一下view的原理,事实上unsqueeze这个函数也是用于改变数据的维度的。
我们曾经提到,Pytorch的接收的数据维度中,第0维一定是batch_size,然后后面才是我们每一个样本的真实数据维度。之前的口罩作业中,我们使用了DataLoader,并且设置了batch_size = 32,在进行处理之后我们输入网络的维度就是[batch_size = 32, channel, height, width],但是这一次由于我们并没有采用DataLoader,而是直接从trainSet里面取的数据,此时取出的数据就是[channel = 1, height = 16, width = 16]的img以及int类型的label,img中根本没有batch_size的维度。