当前位置:   article > 正文

深度学习课程作业——手写数字识别(卷积神经网络)_随便从数据集中读入一张图片,并绘制出来。dataset支特下标索引,其中提取出:米的元

随便从数据集中读入一张图片,并绘制出来。dataset支特下标索引,其中提取出:米的元

本实验过程需要用到torchvision包,没有安装的小伙伴,windows用户可直接使用cmd命令,输入命令行pip install torchvision即可。【仍安装不了的,建议csdn直接查找安装教程】


一、加载数据集

1.1  导入实验可能用到的包、库等

  1. #导入所需要的包
  2. import torchvision.datasets as dsets
  3. import torchvision.transforms as transforms
  4. import torch
  5. import torch.nn as nn
  6. import torch.optim as optim
  7. import torch.nn.functional as F
  8. import matplotlib.pyplot as plt
  9. import numpy as np
  10. import pandas as pd
  11. from torch.autograd import Variable
  12. %matplotlib inline

1.2  加载数据集

使用pytorch自带的数据加载器,包括dataset(装载数据集),sampler(采样数据集),以及dataloader(迭代循环数据集)

  1. #定义超参数
  2. image_size = 28 #图像的总尺寸28*28
  3. num_classes =10 #标签的种类数
  4. num_epochs = 20 #训练的总循环周期
  5. batch_size = 64 #一个撮批次的的等待小,64张图片
  6. #加载MNIST数据,如果没有下载过,就会在当前路径下新建、data目录,并把文件存放在其中
  7. #MNIST数据是属于torchvision包自带的数据,可以直接调用
  8. #下载训练数据集
  9. train_dataset=dsets.MNIST(root='./data', #文件存储路径
  10. train=True, #提取训练集
  11. transform=transforms.ToTensor(), #转为tensor类型,便于数据预处理
  12. download=True) #找不到文件时,自动下载
  13. #加载测试数据集
  14. test_dataset = dsets.MNIST(root='./data',train=False,
  15. transform=transforms.ToTensor())
  16. #训练数据集的加载器,DataLoader方法,可以自动将数据分(批)割成batch,顺序随机打乱
  17. train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
  18. batch_size = batch_size,
  19. shuffle=True)
  20. '''我们希望将测试数据分成两部分,一部分作为验证(校验)数据,一部分作为测试数据。
  21. 验证数据用于检测模型是否过拟合,并调整参数,测试数据检验整个模型的工作'''
  22. #首先,我们定义下标数组indices,它相对于所有test_dataset中数据的代码
  23. indices = range(len(test_dataset))
  24. indices_val = indices[:5000] #取前5000份作为验证集数据的下标
  25. indices_test = indices[5000:] #取后5000份作为测试集的下标
  26. #根据这些下标,构造两个数据集的SubsetRandomSampler采样器
  27. #校验集采样器的作用是:从数据集中indices_val,从indices_test抽取数据,它们会对下标进行采样
  28. sampler_val = torch.utils.data.sampler.SubsetRandomSampler(indices_val)
  29. sampler_test = torch.utils.data.sampler.SubsetRandomSampler(indices_test)
  30. #根据两个采样器来定义加载器,注意将sampler_val和sampler_test分别赋值给validation_load和test_loadad
  31. #采样器和加载器连接到一起,就可以在加载数据的时候随机抽取
  32. validation_loader = torch.utils.data.DataLoader(dataset =test_dataset,
  33. batch_size =batch_size,
  34. sampler = sampler_val)
  35. test_loader = torch.utils.data.DataLoader(dataset =test_dataset,
  36. batch_size =batch_size,
  37. sampler = sampler_test)

 这里提一个概念,在此作为笔记

下采样和上采样的区别:下采样就是缩小图像,在卷积神经网络中,池化层就是下采样,即对原图像进行减缩,使得原图像的某些信息被增强,丢弃多余的,冗余的信息

1.3  读取绘制手写数字图像

  1. #随便从数据集中读入一张图片,并绘制出来
  2. idx = 1001
  3. #dataset支持下标索引,其中提取出来的每一个元素为features,target格式,即属性和标签
  4. #因此train_dataset[idx][0] 表示取该元素中的features,即取第idx批次的features中第0个图像
  5. muteimg = train_dataset[idx][0].numpy()
  6. #由于一般的图像包含rgb三个通道,而MNIST数据集的图像都是灰度的,只有一个通道。
  7. #用imshow画图,会将灰度矩阵自动展现为彩色,不同灰度对应不同颜色,从黄到紫
  8. plt.imshow(muteimg[0,...]) #取muteimg中的28*28像素点画图
  9. print('标签是:',train_dataset[idx][1]) #[idx][1]对应的是target
  10. print(muteimg.shape) #(1,28,28)

二、基本的卷积神经网络

2.1  构建网络

我们将要调用PyTorch强大的nn.Module这个类来构建卷积神经网络,我们分成如下几个步骤:
1.首先,我们构造ConvNet类,它是对类nn.Module的继承
2.接着,复写init,以及forward两个函数,第一个为构造函数,每当类ConvNet被具体化一个实例时,会被调用,forward则是在运行神经网络正向的时候被自动调用
3.自定义自己的方法
ConvNet其实也是一个大容器,它里面有Conv2d,MaxPool2d等组件

(按我自己理解就是,先构造定义卷积神经网络中的各种模块、函数,然后再拼接调用、实现真正的运算)

  1. #定义卷积神经网络;4和8为人为指定的两个卷积层的厚度(feature map 的数量)
  2. depth =[4,8]
  3. class ConvNet(nn.Module):
  4. def __init__(self):
  5. #该函数在创建一个ConvNet对象的时候,即调用该语句:net=ConvNet()时,就会被调用
  6. #首先调用父类相应的构造函数
  7. super(ConvNet,self).__init__()
  8. #其次构造ConvNet需要用到的各个神经模块
  9. """注意,定义组件并没有真正搭建这些组件,只是把基本建筑砖块先找好"""
  10. self.conv1 = nn.Conv2d(1,4,5,padding=2) #定义一个卷积层,输入通道为1,输出通道为4,窗口为5*5,填充为2
  11. self.pool = nn.MaxPool2d(2,2) #定义一个Pooling层,一个窗口为2*2的pooling运算
  12. self.conv2 = nn.Conv2d(depth[0],depth[1],5,padding=2) #第二层卷积,输入通道为depth[0]=4
  13. #输出通道为depth[1]=8,窗口为5*5,填充为2
  14. #一个线性连接层,输入尺寸为最后一层立方体
  15. self.fc1 = nn.Linear(image_size//4*image_size//4*depth[1], 512)
  16. #最后一层线性分类单元,输入为512,输出为要做分类的类别
  17. self.fc2 = nn.Linear(512,num_classes)
  18. def forward(self,x):
  19. #该函数完成神经网络真正的前向运算,我们会在这里把各个组件进行实际的拼装
  20. #x的尺寸:(batch_size,num_filters,image_width,image_height)
  21. x = F.relu(self.conv1(x)) #第一层卷积,激活函数用Relu,为了防止过拟合
  22. #x的尺寸:(batch_size,num_filters,image_width,image_height)
  23. x = self.pool(x)
  24. #x的尺寸:(batch_size,depth[0],image_width,image_height)
  25. x = F.relu(self.conv2(x)) #第三层又是卷积,窗口为5,输入输出通道分别是depth[0]=4,depth[1]=8
  26. #x的尺寸:(batch_size,depth[1],image_width/2,image_height/2)
  27. x = self.pool(x) #第四层poolings,将图片缩小到原大小的1/4
  28. #将立体的特征图Tensor压成一个一维向量
  29. #view用于tensor的指定重新排列
  30. #让x按照batch_size*(image_size//4)^2 * depth[1]的方式来排布向量
  31. x = x.view(-1,image_size//4*image_size//4*depth[1])
  32. #x尺寸:(batch_size,depth[1]*image_size//4*image_size//4)
  33. x = F.relu(self.fc1(x)) #第五层为全链接,Relu激活函数
  34. #x的尺寸:(batch_size,num_classes)
  35. x = F.dropout(x,training=self.training) #以默认为0.5的概率为对这一层进行dropout操作
  36. x = self.fc2(x) #全链接
  37. x = F.log_softmax(x,dim=1) #输出层为log_softmax,即概率对数值log(p(x))
  38. return x
  39. def retrieve_features(self,x):
  40. #用于提取卷积神经网络的特征图,返回feature_map1,feature_map2为前两层卷积层的特征图
  41. feature_map1 = F.relu(self.conv1(x)) #完成第一层卷积
  42. x = self.pool(feature_map1) #完成第一层pooling
  43. feature_map2 = F.relu(self.conv2(x)) #第二层卷积,两层特征图都存储到了feature_map1,feature_map2中
  44. return (feature_map1,feature_map2)
  45. def rightness(predictions,labels):
  46. '''计算预测错误率的函数,其中predictions是模型给出的一组预测结果,batch_size、num_classes列的矩阵'''
  47. pred = torch.max(predictions.data,1)[1] #对任意一行(一个样本)的输出值的第一个维度,求最大
  48. rights = pred.eq(labels.data.view_as(pred)).sum() #将下标与labels中包含的类别进行比较,并累计得到总值
  49. return rights,len(labels) #返回正确的数量和这一次一共比较了多少元素

(之前记混或看不懂的地方)解释说明:

1.激活函数需作用于第五层全连接层,即fc1,而fc2不需被激活函数作用 。

2.经过两层的polling(池化),原图像变为原来的1/4,故有image_size//4

各函数作用:ConvNet定义卷积神经网络;forward完成前馈神经网络运算;retrieve_features存储特征图

2.2  运行模型

  1. net=ConvNet() #此时会调用__init__()函数
  2. criterion=nn.CrossEntropyLoss() #交叉熵损失函数
  3. optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
  4. record=[] #记录准确率等数值
  5. weights=[] #每若干步就记录一次卷积核
  6. for epoch in range(num_epochs): #循环周期设定为20次
  7. train_rights=[] #记录训练集准确率
  8. '''下面的enumerate是构造一个枚举器的作用。就是我们在对train_loader做循环迭代的时候,
  9. enumerate会这个数字就被记录在了batch_idx之中,它就等于0,1,2,……
  10. train_loader每迭代一次,就会吐出来一对数据data和target,分别对应着一个batch中的手写数字'''
  11. for batch_idx,(data,target) in enumerate(train_loader):
  12. #Tensor转化为Variable,data为一批图像,target为一批标签
  13. data,target=Variable(data),Variable(target)
  14. net.train() #打开dropout,防止过拟合
  15. output = net(data) #神经网络完成一次前馈的计算过程,得到预测输出output
  16. #将output与target比较,计算误差,如标签是数字7,而通过net预测输出的output却是5,7与5之间存在误差
  17. loss=criterion(output,target)
  18. optimizer.zero_grad() #清空梯度,因为Variable中requires_grad=True会默认累加梯度
  19. loss.backward() #反向传播
  20. optimizer.step() #一步随机梯度下降算法
  21. right=rightness(output,target)
  22. train_rights.append(right)
  23. if batch_idx %100 == 0:
  24. net.eval()
  25. val_rights=[]
  26. '''开始在校验数据集上做循环,计算校验集上面的准确度'''
  27. for (data,target)in validation_loader:
  28. data,target=data.clone().requires_grad_(),target.clone().detach()
  29. output=net(data)
  30. right=rightness(output,target)
  31. val_rights.append(right)
  32. #train_r[0]/trai_r[1]是训练集的分类准确度,另一个是校验集的
  33. train_r=(sum([tup[0] for tup in train_rights]),sum([tup[1] for tup in train_rights]))
  34. val_r=(sum([tup[0] for tup in val_rights]),sum([tup[1] for tup in val_rights]))
  35. print(val_r)
  36. print('训练周期:{}[{}/{}({:.0f}%)]\tLoss:{:.6f}\t训练正确率:{:.2f}%\t校验正确率:{:.2f}%'.format(
  37. epoch,batch_idx * batch_size,len(train_loader.dataset),
  38. 100.*batch_idx/len(t。rain_loader),
  39. loss.data,
  40. 100.*train_r[0].numpy()/train_r[1],
  41. 100.*val_r[0].numpy()/val_r[1]))
  42. #将准确率和权重等数值加载到容器中,方便后续处理
  43. record.append((100-100.*train_r[0]/train_r[1],100-100.*val_r[0]/val_r[1]))
  44. #weight记录了训练周期中所有卷积核的演化过程,net.conv1.weight提出了第一层卷积和的权重
  45. #clone备份weight.data中得到数据,否则weight.data变化时,列表中的每一项数据也会联动
  46. #所以这里使用clone函数十分重要
  47. weights.append([net.conv1.weight.data.clone(),net.conv1.bias.data.clone(),
  48. net.conv2.weight.data.clone(),net.conv2.bias.data.clone()])

以下是部分训练结果截图:

 

 绘制训练过程中的误差曲线

  1. #绘制训练过程的误差曲线,校验集和测试集上的错误率
  2. plt.figure(figsize=(10,7))
  3. plt.plot(record) #record记录了每一个打印周期记录的训练和校验数据集上的准确度
  4. plt.xlabel('Steps')
  5. plt.ylabel('Error rate')

结果如图:

2.3  在测试集上进行分类

  1. #在测试集上分批运行,并计算总的正确率
  2. net.eval() #关闭dropout,标志模型当前为运行阶段
  3. vals = [] #记录准确率
  4. #对测试数据集进行循环
  5. for data,target in test_loader:
  6. data.target = data.clone().detach().requires_grad_(True),target.clone().detach()
  7. output =net(data) #将特征数据喂入网络,得到分类的输出
  8. val = rightness(output,target) #获得正确样本数以及总样本数
  9. vals.append(val) #记录结果
  10. #计算准确率
  11. rights = (sum([tup[0] for tup in vals]),sum([tup[1] for tup in vals]))
  12. right_rate = 100.*rights[0].numpy() / rights[1]
  13. print(right_rate)
  14. #随便从测试集中读取一张图片,检验模型的分类结果,并绘制出来
  15. idx = 2000
  16. muteimg = test_dataset[idx][0].numpy()
  17. plt.imshow(muteimg[0,...]) #test_dataset的每一个数据由data和target组成,其中data为像素点矩阵,target为该图像标签数字
  18. print('标签是:',test_dataset[idx][1])
  19. print(test_dataset[idx])
  20. #提取第一层卷积层的卷积核
  21. plt.figure(figsize=(10,7))
  22. for i in range(4):
  23. plt.subplot(1,4,i+1)
  24. plt.axis('off') #关闭坐标轴
  25. plt.imshow(net.conv1.weight.data.numpy()[i,0,...])
  26. #第0个的第一行的所有数据为一个特征图,第1个的第一行的所有数据为第二个特征图,以此类推
  27. #上面的卷积核我们不能够很好地解读,因此将其对应的特征图输出,便于理解
  28. #先定义读入的图片,其中unsqueeze作用是在最前面添加一维
  29. #目的是让input_x的tensor为四维,才能输入给net,添加的维是batch那一维
  30. input_x = test_dataset[idx][0].unsqueeze(0)
  31. #features是有两个元素的列表,分别表示第一层和第二层的所有特征图
  32. feature_maps = net.retrieve_features(Variable(input_x))
  33. plt.figure(figsize=(10,7))
  34. #打印出4个特征图
  35. for i in range(4):
  36. plt.subplot(1,4,i+1)
  37. plt.imshow(feature_maps[0][0,i,...].data.numpy())

2.4  滤波器(卷积核)的演化过程

  1. #将记录在容器中的卷积核权重历史演化数据打印出来
  2. i = 0
  3. #tup是tuple元组的意思
  4. for tup in weights:
  5. if i % 10 ==0:
  6. layer1 = tup[0]
  7. fig = plt.figure(figsize=(10,7))
  8. for j in range(4):
  9. plt.subplot(1,4,j+1)
  10. plt.axis('off')
  11. plt.imshow(layer1.numpy()[j,0,...])
  12. i +=1

部分结果如图所示:

        由上图,我们可以看到,卷积核的演化过程共有4列,即以第一行的四个特征图为首,
依次不断地往下演化,即每一列对应一个卷积核 。

 

   【绘制第二层卷积核】

  1. #绘制第二次的卷积核,每一列对应一个卷积核,一共8个卷积核(人为设定的4,8两个层的卷积厚度)
  2. plt.figure(figsize=(15,10))
  3. for i in range(4):
  4. for j in range(8):
  5. plt.subplot(4,8,i*8+j+1)
  6. plt.axis('off')
  7. plt.imshow(net.conv2.weight.data.numpy()[j,i,...])
  8. #绘制第二层的特征图,一共八个
  9. plt.figure(figsize=(10,7))
  10. for i in range(8):
  11. plt.subplot(2,4,i+1)
  12. plt.axis('off')
  13. plt.imshow(feature_maps[1][0,i,...].data.numpy())

 

结论:从上述图像看出,图像的抽象程度变得更高了。由于池化的作用,即对图像进行模糊处理,  
一些多余的图像信息被丢弃了,这也证明了卷积网络的抽象提取能力 

2.5  卷积神经网络的健壮性

所谓健壮性,就是模型消除局部相关性的能力。即数字在图像平移后,模型是否仍能很好地正确预测出该数字

  1. #随机挑选一张图片,把它往左平移w个单位,然后考察分类结果是否有变化
  2. #提取test_dataset中第idx个批次第0个图像第0个通道对应的图像,定义为c
  3. #原图像即为28*28, 因为这个是单通道图像
  4. '''test_data[idx][0]的维度是(1,28,28),即1个28行28列的矩阵,当我们
  5. 取第1个时,即test_dataset[idx][0][0],可得到28行28列的二维张量'''
  6. c = test_dataset[idx][0][0] #shape为28*28
  7. d = torch.zeros(c.size()) #全0的28×28的矩阵
  8. w = 3 #平移的长度为3个像素
  9. #对于d中的任意像素i,j ,等于c中的i,j+w位置的像素(即d为c向左平移后的位置)
  10. for i in range(c.size()[0]):
  11. for j in range(0,c.size()[1]-w):
  12. d[i,j] = c[i,j+w]
  13. feature_maps = net.retrieve_features(Variable(c.unsqueeze(0).unsqueeze(0))) #
  14. plt.figure(figsize=(10,7))
  15. #打印出4个特征图
  16. for i in range(4):
  17. plt.subplot(1,4,i+1)
  18. plt.imshow(feature_maps[0][0,i,...].data.numpy())
  19. #将d输入神经网络,得到分类结果pred,并打印
  20. prediction = net(d.unsqueeze(0).unsqueeze(0))
  21. ''' torch.max(input, dim, keepdim=False, out=None) -> (Tensor, LongTensor)
  22. 按维度dim 返回最大值,并且返回索引'''
  23. pred = torch.max(prediction.data,1)[1] #[1]即返回最大值对应的索引
  24. print(pred)
  25. #提取d对应的features特征图结果
  26. feature_maps = net.retrieve_features(Variable(d.unsqueeze(0).unsqueeze(0)))
  27. plt.figure(figsize=(10,7))
  28. #打印出4个特征图
  29. for i in range(4):
  30. plt.subplot(1,4,i+1)
  31. plt.imshow(feature_maps[0][0,i,...].data.numpy())
  32. plt.figure(figsize=(10,7))
  33. for i in range(8):
  34. plt.subplot(2,4,i+1)
  35. plt.imshow(feature_maps[1][0,i,...].data.numpy())

结论:平移数字输入后得到第二层特征图 。首先,网络打印出来的结果是6,说明我们的模型训练得能正确得预测出输入的数字,  并且对3个像素点的平移具有很好干抗干扰性,体现出卷积神经网络很强的健壮性。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/372509
推荐阅读
相关标签
  

闽ICP备14008679号