背景
2020年5月的DW组队学习选择了天池的街景字符编码识别,在这个入门竞赛中,数据集来自Google街景图像中的门牌号数据集(The Street View House Numbers Dataset, SVHN),并根据一定方式采样得到比赛数据集。评测标准为测试集预测结果的准确率,即编码识别正确的数量占测试集图片数量的比率。
组队学习的第三个任务是用PyTorch搭建一个简单的CNN模型进行训练,熟悉操作后,后续可以替换为使用预训练的Resnet18进行训练。
本章学习手册内容由 张强 编写,而本篇博客则是这章内容的笔记,在这里对作者表示感谢,受益匪浅!
初始化的几个操作
作者在一开始导入包时,插入了以下几条语句:
-
torch.cuda.manual_seed(
0)
-
torch.backends.cudnn.deterministic =
True
-
torch.backends.cudnn.benchmark =
False
看到这几条语句还是挺迷惑的,后来查阅了官方文档,发现这是为了使结果可复现,而不是每次都变动(具体内容可以查看该文档)。
In order to make computations deterministic on your specific problem on one specific platform and PyTorch release, there are a couple of steps to take.
You can use
torch.manual_seed()
to seed the RNG for all devices (both CPU and CUDA)When running on the CuDNN backend, two further options must be set:
torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False
———— pytorch.org/docs
CNN模型简介
卷积神经网络(CNN)是一种应用广泛的神经网络,逐渐成为计算机视觉领域的主流模型,在解决图像分类、图像检索、物体检测等问题上有很好的效果。
CNN的网络结构主要分为:
1.卷积层
在卷积层中,将会用许多尺寸较小的卷积核,在整个图像中做“卷积”运算(准确说是互相关),当完成整个图像的运算后,每个卷积核都会生成一个二维的激活图,他们可能会针对某些特征产生激活,如物体边界、颜色、轮廓等。
每个卷积层上可能会有一系列的卷积核,比如20个,那么就会形成20张二维的激活图,这些图层叠起来就形成了卷积层的输出。
2.池化层
池化层又称作下采样层(downsampling),顾名思义就是对图像进行下采样,从而减小图像的尺寸,这样可以大大降低卷积层的运算时间。池化层通常在卷积层之后加入,其中池化操作常用的算法有最大池化、平均池化等等。
3.全连接层
全连接层一般放在网络的最后,经过了一系列的卷积层和池化层之后,提取出图片的特征图,此时图像大小已经缩小到一定尺寸,此时就可以用一个全连接层完成最后的输出。为了防止过拟合,还会引入Dropout机制,又或者在进入全连接层之前使用全局平均池化。
另外,激活函数可以增加模型的非线性性,所以卷积层之后通常引入一个激活函数,如ReLU。
搭建CNN
首先,可以搭建一个简单的CNN模型试一下,手册提供的CNN共有两层卷积,分别包含16个和32个的3*3卷积核,激活函数选用ReLU( ),池化层窗口大小为2(即2*2),选用最大池化,最后搭配6个并联的全连接层,分别表示定长字符的6个位置。
具体代码如下:
-
class SVHN_Model1(nn.Module):
-
def __init__(self):
-
super(SVHN_Model1, self).__init__()
-
-
self.cnn = nn.Sequential(
-
nn.Conv2d(
3,
16, kernel_size=(
3,
3), stride=(
2,
2)),
-
nn.ReLU(),
-
nn.MaxPool2d(
2),
-
nn.Conv2d(
16,
32, kernel_size=(
3,
3), stride=(
2,
2)),
-
nn.ReLU(),
-
nn.MaxPool2d(
2)
-
)
-
self.fc1 = nn.Linear(
32*
3*
7,
11)
-
self.fc2 = nn.Linear(
32*
3*
7,
11)
-
self.fc3 = nn.Linear(
32*
3*
7,
11)
-
self.fc4 = nn.Linear(
32*
3*
7,
11)
-
self.fc5 = nn.Linear(
32*
3*
7,
11)
-
self.fc6 = nn.Linear(
32*
3*
7,
11)
-
-
def forward(self, img):
-
fout = self.cnn(img)
-
fout = fout.view(fout.shape[
0],
-1)
-
c1 = self.fc1(fout)
-
c2 = self.fc2(fout)
-
c3 = self.fc3(fout)
-
c4 = self.fc4(fout)
-
c5 = self.fc5(fout)
-
c6 = self.fc6(fout)
-
return c1, c2, c3, c4, c5, c6
使用预训练模型
为了使用更加高效的模型,可以直接使用一些现成的模型,pytorch中也集成了不少出名的CNN模型,如Alexnet、Resnet等等,具体可以看官方文档。设置pretrained=True还可以下载已经训练好的参数,在此基础上做一个简单的迁移学习。
手册中使用了Resnet18,并修改了最后的全连接层,具体代码如下:
-
class SVHN_Model2(nn.Module):
-
def __init__(self):
-
super(SVHN_Model2, self).__init__()
-
-
model_conv = models.resnet18(pretrained=
True)
-
model_conv.avgpool = nn.AdaptiveAvgPool2d(
1)
-
model_conv = nn.Sequential(*list(model_conv.children())[:
-1])
-
-
self.cnn = model_conv
-
self.fc0 = nn.Linear(
512,
11)
-
self.fc1 = nn.Linear(
512,
11)
-
self.fc2 = nn.Linear(
512,
11)
-
self.fc3 = nn.Linear(
512,
11)
-
self.fc4 = nn.Linear(
512,
11)
-
self.fc5 = nn.Linear(
512,
11)
-
-
def forward(self, img):
-
feat = self.cnn(img)
-
# print(feat.shape)
-
feat = feat.view(feat.shape[
0],
-1)
-
c0 = self.fc0(feat)
-
c1 = self.fc1(feat)
-
c2 = self.fc2(feat)
-
c3 = self.fc3(feat)
-
c4 = self.fc4(feat)
-
c5 = self.fc5(feat)
-
return c0, c1, c2, c3, c4, c5
开始训练
设定好几个参数,以及优化算法、损失函数后,就可以开始训练了:
-
lr =
0.005
-
criterion = nn.CrossEntropyLoss()
-
optimizer = optim.Adam(model.parameters(), lr)
-
-
loss_plot, c0_plot = [], []
-
-
for epoch
in range(
10):
-
for data
in train_loader:
-
if torch.cuda.is_available():
-
data[
0] = data[
0].cuda()
-
data[
1] = data[
1].cuda().long()
-
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()
-
-
loss_plot.append(loss.item())
-
c0_plot.append((c0.argmax(
1) == data[
1][:,
0]).sum().item()*
1.0 / c0.shape[
0])
-
-
print(epoch)
大致结果
在本机实验下,第一个模型跑一个epoch大概需要155s,第二个模型跑大概需要490s。
在精度方面,经过10个epochs,第一个模型在第一个字符的准确率约为0.55,第二个模型因为运行时间太长,目前跑了两个epoch,第一个字符准确率仍在0.27左右,效果还不是很好。
耗时问题
在运行的过程中,有一个突出的问题是dataloader的预处理时间特别长,占总训练时间的80%左右,这说明在数据读取方面还需要优化。关于耗时的检测,可以参考这篇教程。主要运用了cProfile库同时搭配pstats、snakeviz查看结果。
关键代码如下:
-
import cProfile
as cpro
-
# 放入你要运行的函数
-
cpro.run(
"train(model, train_loader)", filename=
'result.out')
-
-
import pstats
-
from pstats
import SortKey
-
p = pstats.Stats(
'result.out')
-
p.strip_dirs().sort_stats(SortKey.TIME).print_stats(
10)
结果如下:
关于cProfile的更多操作,可以参考这个网站。
具体原因可能是由于机械硬盘读写慢,windows下不能多线程读数据集,dataloader内部构造优化不足等等造成,还需要进一步思考怎么解决这个问题。
另外,在训练中可能会出现显卡利用率低等情况,出现这些情况怎么处理,这里推荐一篇不错的博客:
深度学习PyTorch,TensorFlow中GPU利用率较低,CPU利用率很低,且模型训练速度很慢的问题总结与分析
最后
此次学习的教程由Datawhale提供,学习手册的链接为:点这里。
CNN简介的部分内容参考廖星宇的《深度学习入门之PyTorch》
转载:https://blog.csdn.net/qq_21503139/article/details/106345176