飞道的博客

DataWhale-天池街景数字识别竞赛-task3-模型初步训练

489人阅读  评论(0)

背景

2020年5月的DW组队学习选择了天池的街景字符编码识别,在这个入门竞赛中,数据集来自Google街景图像中的门牌号数据集(The Street View House Numbers Dataset, SVHN),并根据一定方式采样得到比赛数据集。评测标准为测试集预测结果的准确率,即编码识别正确的数量测试集图片数量的比率。

组队学习的第三个任务是用PyTorch搭建一个简单的CNN模型进行训练,熟悉操作后,后续可以替换为使用预训练的Resnet18进行训练。

本章学习手册内容由 张强 编写,而本篇博客则是这章内容的笔记,在这里对作者表示感谢,受益匪浅!

初始化的几个操作

作者在一开始导入包时,插入了以下几条语句:


  
  1. torch.cuda.manual_seed( 0)
  2. torch.backends.cudnn.deterministic = True
  3. 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个位置。

具体代码如下:


  
  1. class SVHN_Model1(nn.Module):
  2. def __init__(self):
  3. super(SVHN_Model1, self).__init__()
  4. self.cnn = nn.Sequential(
  5. nn.Conv2d( 3, 16, kernel_size=( 3, 3), stride=( 2, 2)),
  6. nn.ReLU(),
  7. nn.MaxPool2d( 2),
  8. nn.Conv2d( 16, 32, kernel_size=( 3, 3), stride=( 2, 2)),
  9. nn.ReLU(),
  10. nn.MaxPool2d( 2)
  11. )
  12. self.fc1 = nn.Linear( 32* 3* 7, 11)
  13. self.fc2 = nn.Linear( 32* 3* 7, 11)
  14. self.fc3 = nn.Linear( 32* 3* 7, 11)
  15. self.fc4 = nn.Linear( 32* 3* 7, 11)
  16. self.fc5 = nn.Linear( 32* 3* 7, 11)
  17. self.fc6 = nn.Linear( 32* 3* 7, 11)
  18. def forward(self, img):
  19. fout = self.cnn(img)
  20. fout = fout.view(fout.shape[ 0], -1)
  21. c1 = self.fc1(fout)
  22. c2 = self.fc2(fout)
  23. c3 = self.fc3(fout)
  24. c4 = self.fc4(fout)
  25. c5 = self.fc5(fout)
  26. c6 = self.fc6(fout)
  27. return c1, c2, c3, c4, c5, c6

使用预训练模型

为了使用更加高效的模型,可以直接使用一些现成的模型,pytorch中也集成了不少出名的CNN模型,如Alexnet、Resnet等等,具体可以看官方文档。设置pretrained=True还可以下载已经训练好的参数,在此基础上做一个简单的迁移学习。

手册中使用了Resnet18,并修改了最后的全连接层,具体代码如下:


  
  1. class SVHN_Model2(nn.Module):
  2. def __init__(self):
  3. super(SVHN_Model2, self).__init__()
  4. model_conv = models.resnet18(pretrained= True)
  5. model_conv.avgpool = nn.AdaptiveAvgPool2d( 1)
  6. model_conv = nn.Sequential(*list(model_conv.children())[: -1])
  7. self.cnn = model_conv
  8. self.fc0 = nn.Linear( 512, 11)
  9. self.fc1 = nn.Linear( 512, 11)
  10. self.fc2 = nn.Linear( 512, 11)
  11. self.fc3 = nn.Linear( 512, 11)
  12. self.fc4 = nn.Linear( 512, 11)
  13. self.fc5 = nn.Linear( 512, 11)
  14. def forward(self, img):
  15. feat = self.cnn(img)
  16. # print(feat.shape)
  17. feat = feat.view(feat.shape[ 0], -1)
  18. c0 = self.fc0(feat)
  19. c1 = self.fc1(feat)
  20. c2 = self.fc2(feat)
  21. c3 = self.fc3(feat)
  22. c4 = self.fc4(feat)
  23. c5 = self.fc5(feat)
  24. return c0, c1, c2, c3, c4, c5

开始训练

设定好几个参数,以及优化算法、损失函数后,就可以开始训练了:


  
  1. lr = 0.005
  2. criterion = nn.CrossEntropyLoss()
  3. optimizer = optim.Adam(model.parameters(), lr)
  4. loss_plot, c0_plot = [], []
  5. for epoch in range( 10):
  6. for data in train_loader:
  7. if torch.cuda.is_available():
  8. data[ 0] = data[ 0].cuda()
  9. data[ 1] = data[ 1].cuda().long()
  10. c0, c1, c2, c3, c4, c5 = model(data[ 0])
  11. loss = criterion(c0, data[ 1][:, 0]) + \
  12. criterion(c1, data[ 1][:, 1]) + \
  13. criterion(c2, data[ 1][:, 2]) + \
  14. criterion(c3, data[ 1][:, 3]) + \
  15. criterion(c4, data[ 1][:, 4]) + \
  16. criterion(c5, data[ 1][:, 5])
  17. loss /= 6
  18. optimizer.zero_grad()
  19. loss.backward()
  20. optimizer.step()
  21. loss_plot.append(loss.item())
  22. c0_plot.append((c0.argmax( 1) == data[ 1][:, 0]).sum().item()* 1.0 / c0.shape[ 0])
  23. print(epoch)

大致结果

在本机实验下,第一个模型跑一个epoch大概需要155s,第二个模型跑大概需要490s

在精度方面,经过10个epochs,第一个模型在第一个字符的准确率约为0.55,第二个模型因为运行时间太长,目前跑了两个epoch,第一个字符准确率仍在0.27左右,效果还不是很好。

耗时问题

在运行的过程中,有一个突出的问题是dataloader的预处理时间特别长,占总训练时间的80%左右,这说明在数据读取方面还需要优化。关于耗时的检测,可以参考这篇教程。主要运用了cProfile库同时搭配pstats、snakeviz查看结果。

关键代码如下:


  
  1. import cProfile as cpro
  2. # 放入你要运行的函数
  3. cpro.run( "train(model, train_loader)", filename= 'result.out')
  4. import pstats
  5. from pstats import SortKey
  6. p = pstats.Stats( 'result.out')
  7. 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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场