卷积神经网络学习——第二部分:卷积神经网络训练的基本流程




一、序言


   本文承接第一部分,基于对卷积神经网络网络组成的认识,开始学习如何去使用卷积神经网络进行对应的训练。模型评估作为优化部分,我们将放在第三个部分中再好好讲他的作用以及意义~

   训练的基本流程主要是数据集引入、训练及参数设置、验证及反馈这三个步骤,我们现在分三个步骤来认识一下这个训练的基本流程。

   PS:我更新真是快啊~


——2020年11月20日于北邮教三539



二、训练流程


1、数据集引入


   本文根据对应的实验要求,主要采用的是Pytorch中自带的MNIST数据集。MNIST数据集由于比较基础,历年来都是被各种玩坏的主要对象~

   引入数据集的时候主要需要注意的是预处理的一个操作,在这里主要用的是ToTensor和Normalize两个函数进行归一化处理。其实也不一定需要Normalize这个函数,因为训练其实都是可以进行的。

   但是这里需要注意一下,因为导入数据集的时候操作是固定的。所以为了保证这个操作固定,就最好是用Compose把他们固定起来,不然在后续操作中可能就会添麻烦。

   如果你在做自己的手写图像识别,并且老是正确率比较低,那么一定注意一下这几个点。

   第一个是图像的前后的前后处理的时候是不一样的,很容易直接用自己的图像直接拿去识别了,但是因为之前训练集中的都是经过Compose结合后的组合处理后的图像。但是你直接拿去处理的图像是没有经过处理的,输入到模型中的和此前的格式是不一样的。

   第二个就是因为你手写的时候,导出的文件无论是png还是jpg,他们基本都是彩色图片。(是的,哪怕你看到的都是黑色,但他们本身还都是彩色图片)这个时候可以使用​transforms.Grayscale​函数先将你的图像灰度处理,不然在用Normalize的时候还是会带来问题。由于预处理不同,所以你在前后训练的素材和你最后手写的素材不是一个格式,难免会导致你的准确率很低。预处理函数的设置是后面新增自定义素材时的必要保障。

   (2020.11.22补充:识别率和笔触的关系较大,可以参考训练集中图像的大小和笔触进行书写;在一定程度上,黑底白字比起白底黑字来说,准确率更高——by绚佬)

   关于transforms中包含有多少函数,有什么对应的作用,可以参考:​​二十二种 transforms 图片数据预处理方法​


import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.autograd import Variable
from torchvision import datasets, transforms

# 步骤一:数据载入
# 1.transforms.Compose()将各种预处理操作组合到一起
# 2.transforms.ToTensor()将图片转换成 PyTorch 中处理的对象 Tensor.在转化的过程中 PyTorch 自动将图片标准化了,也就是说Tensor的范用是(0,1)之间
# 3.transforms.Normalize()要传入两个参数:均值、方差,做的处理就是减均值,再除以方差。将图片转化到了(-1,1)之间
# 4.注意因为图片是灰度图,所以只有一个通道,如果是彩色图片,有三个通道,transforms.Normalize([a,b,c],[d,e,f])来表示每个通道对应的均值和方差。

data_tf = transforms.Compose([transforms.ToTensor(),
transforms.Normalize([0.5],[0.5])
])

# PyTorch 的内置函数 torchvision.datasets.MNIST 导入数据集
# 这里存储的还是MNIST数据集的格式,但是不一样的是这个数据集当中的元素是以tensor格式存储的
train_dataset = datasets.MNIST(
root = '/Users/air/Desktop/【2020秋】数据科学基础/第三次作业',
train = True,
transform = data_tf,
download = True
)
test_dataset = datasets.MNIST(
root = '/Users/air/Desktop/【2020秋】数据科学基础/第三次作业',
train = False,
transform = data_tf
)

# 定义超参数
BATCH_SIZE = 128 # 训练的包的大小,通过将训练包分为2的倍数以加快训练过程的方式
LR = 1e-2 # 学习率,学习率太小会减慢训练效果,学习率太高会导致准确率降低
EPOCHS = 5 # 定义循环次数,避免因为次数太多导致时间过长

# torch.utils.data.DataLoader 建立一个数据迭代器,传入数据集和 batch size, 通过 shuffle=True 来表示每次迭代数据的时候是否将数据打乱。
# 测试集无需打乱顺序;训练集打乱顺序,为了增加训练模型的泛化能力
# 但是由于这里的训练集本身就已经满足要求,所以打乱顺序对于泛化能力本身的提升并不是必要的
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size = BATCH_SIZE,
shuffle = True)
test_loader = torch.utils.data.DataLoader(test_dataset,
batch_size = BATCH_SIZE,
shuffle = False)



2、构建网络


   我们在第一部分的基础上,我们再重新定义一个网络,这里我们分别定义一个全连接层网络,再定义一个三层卷积神经网络。也借此复习一下网络定义的相关注意事项。



(1)四层卷积神经网络


   我们在第一部分的基础上,我们再重新定义一个网络,这里我们分别定义一个全连接层网络,再定义一个三层卷积神经网络。也借此复习一下网络定义的相关注意事项。


class ConvNet(nn.Module):
def __init__(self):
super(ConvNet, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(1,10,3) ,
nn.ReLU(inplace=True))

self.conv2 = nn.Sequential(
nn.Conv2d(10,20,3) ,
nn.ReLU(inplace=True) ,
nn.MaxPool2d(kernel_size=2 , stride=2))

self.conv3 = nn.Sequential(
nn.Conv2d(20,40,3) ,
nn.ReLU(inplace=True))

self.conv4 = nn.Sequential(
nn.Conv2d(40,80,3) ,
nn.ReLU(inplace=True) ,
nn.MaxPool2d(kernel_size=2 , stride=2))

self.fc = nn.Sequential(nn.Linear(80*4*4,1600) ,
nn.ReLU(inplace=True) ,
nn.Linear(1600,400) ,
nn.ReLU(inplace=True) ,
nn.Linear(400,10) )

def forward(self, x):
in_size = x.size(0)
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = x.view(in_size , -1)
out = self.fc(x)
out = F.log_softmax(out, dim = 1)
return out


   在定义的时候我们只需要注意几个点,一个是我们在定义的时候,务必保证我们的每一个Linear之间存在着输入输出通道对应的关系要相对应。第一个Linear函数的输入需要符合 ​深度x高度x宽度​ 的相关信息。

   其实这里还有几个没有解决的问题:Linear函数的数量该如何确定,他们数目会不会影响训练效果;log_softmax函数对于整体效果影响有多大等~(如果之后解决了我再写上去(嗯!



(2)两层全连接层网络


   同卷积神经网络不太一样的是,全连接层网络中就只含有Linear映射。从我们此前的文字,我们可以知道:全连接层是不含Conv2d、relu这些函数的,它的组成仅是简单的Linear映射而已。所以我们定义全连接网络如下:


class Net(nn.Module):
def __init__(self, in_dim, n_hidden_1, out_dim):
super(Net,self).__init__()
self.layer1 = nn.Linear(in_dim,n_hidden_1)
self.layer2 = nn.Linear(n_hidden_1,out_dim)

def forward(self,x):
hidden_1_out = self.layer1(x)
out = self.layer2(hidden_1_out)
return out


   该网络包含的参数有三个,第一个是输入图像的大小,第二个是中间层,最后一个是输出。很明显,输入的大小就是28*28,并不需要我们再做过多的设计,输出也是十通道输出,所以也是固定的。中间层则是根据自己的需求进行定义的。





3、模型训练


   我们在第一部分的基础上,我们再重新定义一个网络,这里我们分别定义一个全连接层网络,再定义一个三层卷积神经网络。也借此复习一下网络定义的相关注意事项。这一部分,也可以参考链接:​​PyTorch卷积神经网络学习笔记​​进一步了解一下~博主写的也是真的好

   首先,按照国际惯例,我们先用一个流程图来展示一下每一次训练过程。


Created with Raphaël 2.2.0 开始 将训练集输入到模型进行训练 对结果采用交叉熵巡视计算模型 误差,并将预测结果提取出来 预测结果等于实际标签 成功预测数n += 1 反向传播,更新参数 输出准确率和实验误差 yes


# 训练模型
for epoch in range(EPOCHS):
running_loss = 0.0
running_accuracy = 0.0
# 训练
for i, data in enumerate(train_loader, 1):
img, label = data
# 如果使用全连接网络,需要加上下面这一句话,以让整个网络正常工作,因为全连接网络的输入是一维列向量,如果不降维,很可能是无法正常运行的
# img = img.view(img.size(0), -1)
if torch.cuda.is_available():
img = img.cuda()
label = label.cuda()
else:
img = Variable(img)
label = Variable(label)

# 向前传播
out = model(img)
loss = criterion(out, label) # 这个损失是当前批次的平均损失
running_loss += loss.item() * label.size(0) # 累计损失大小,乘积表示当前批次的总损失
_ , pred = torch.max(out, 1)
num_correct = (pred == label).sum()
accuracy = (pred == label).float().mean()
running_accuracy += num_correct.item()

# 向后传播
optimizer.zero_grad()
loss.backward()
optimizer.step()

# 用于存储训练后的参数
torch.save(model.state_dict(), './params.pth')


   如果是想要利用已经有的参数进行多次训练,还可以使用如下语句。



torch.save(model.state_dict(), ‘./params.pth’)



   为了加深对于整段代码的理解,我们可以先了解一下其中比较重要但是又不太常见的几个语句块和函数:

  • _ , pred = torch.max(out, 1)​:这句话需要先了解torch.max的用法,不太熟悉的可以参考​​torch.max用法​​先看一下。torch.max的定义格式为:


out = torch.max(input, dim)


   输入为input以及一个dim。dim指的是维度,0代表索引每列的最大值,1代表索引每行的最大值。他的输出为最大值以及其索引。在这里的作用就是,在多分类问题的类别取概率最大的类别。

   对于我们而言,经过模型输出后,我们需要的是结果的第二列,也就是预测值。所以用 ​_ , pred​ 就可以只存下pred。除了这种方式以外,也可以用如下语句表示同样的意思:


pred = torch.max(out, 1)[1]


  • torch.cuda.is_available()​:看你的电脑的GPU是否可以被PyTorch调用
  • item()​:得到一个元素张量里面的元素值,常用于将一个零维张量转换成浮点数。
  • optimizer.zero_grad()​:遍历模型的所有参数,将上一次的梯度记录被清空。
  • loss.backward()​:进行误差反向传播。
  • optimizer.step()​:执行一次优化步骤,通过梯度下降法来更新参数的值。以上三个函数均为反向传播当中的必要函数,详细可以参考链接​​反向传播函数的理解​​进一步了解,这三个函数之间是相辅相成的。


4、模型评估


   模型评估大体上的效果和步骤同模型训练一致,只需要将部分代码进行替换即可~这里就不贴代码了,就将评估当成是基于以上的又一次训练即可。



三、总结


   总的来说,这一部分也是比较重要的。需要梳理流程之后再加深对于函数的理解才行。最难最难的就是,网上的大部分代码都是来回搬运的(让人极为无语)而可能最开始写的那个大佬认为一些小的语句没有必要写,但是对于我们这样的小白来说就处于:基本看不懂的状态。所以最麻烦的还是:为什么有这个语句?他为什么在这里?总的来说是比较诡异且麻烦的。



更多内容详见微信公众号:Python研究所

卷积神经网络学习——第二部分:卷积神经网络训练的基本流程_数据集