手写数字识别器(认识卷积神经网络)

手写数字识别器(认识卷积神经网络)

毕设,如同平地起高楼,不打地基,直接盖楼,还是比较麻烦的,基础的东西还是挺重要的,这个小实验,也就是用来练练手而已,跟自己的毕设似乎有一点关联,又似乎没有一点关联。这算是新年第一次开工吧,开学之后,面临着毕设,还有毕业多种事情要做,毕设现在还是比较费脑筋的问题。

利用MNIST数据集和pytorch框架,实现一个简单的CNN网络

  • data.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import torch
import torch.nn as nn
from torch.autograd import Variable

import torch.optim as optim
import torch.nn.functional as F
import torchvision.datasets as dsets
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
image_size = 28 # 图像总尺寸是28x28
num_classes = 10 # 标签总类数  1~10
num_epochs = 20 # 训练总周期
batch_size = 64 # 一个批次的大小,64张图片
# 加载数据集
train_dataset = dsets.MNIST(root='./root', # 文件存放路径
                            train=True, # 提取训练集
                            transform=transforms.ToTensor(), # 加载数据时,对图像做预处理
                            download=True) # 数据集不存在时,自动下载
test_dataset = dsets.MNIST(root='./root', # 文件存放路径
                            train=False,
                            transform=transforms.ToTensor(), )# 加载数据时,对图像做预处理)
                            # 定义数据加载器,自动将数据切分成批,顺序随机打乱
                            train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                            batch_size=batch_size,
                            shuffle=True)
                            
#  定义数据集下标
indices = range(len(test_dataset))
indices_val = indices[:5000]
indices_test = indices[5000:]
# 根据下标构造两个数据集的SunsetRandomSampler采样器

sampler_val = torch.utils.data.SubsetRandomSampler(indices_val
nsampler_test = torch.utils.data.SubsetRandomSampler(indices_test)
n# 根据两个采样器,定义加载器
validation_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                               batch_size=batch_size,
                                               shuffle=False,
                                               sampler=sampler_val
                                               )
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                                batch_size=batch_size,
                                                shuffle=False,
                                                sampler=sampler_test
                                                )
# # 随便从数据集中读入一张图片
# idx = 100
# muteimg = train_dataset[idx][0].numpy()
# plt.imshow(muteimg[0, ...])
# plt.show()
# print(\"标签是:\",train_dataset[idx][1])
  • ConvNet.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import torch.nn as nn
import torch.nn.functional as F
import time
image_size = 28 # 图像总尺寸是28x28
num_classes = 10 # 标签总类数  1~10
num_epochs = 20 # 训练总周期
batch_size = 64 # 一个批次的大小,64张图片
# 定义一个卷积神经网络,4,8为指定两个卷积层厚度
# from MINIST.data import image_size, num_classes
depth = [4, 8]
class ConvNet(nn.Module):
      # 复写构造函数 
      def __init__(self):      
      print('ConvNet 起始时间', time.strftime("%Y-%m-%d %H:%M:%S"))    
      # 该函数在创建一个ConvNet对象时即net=ConvNet()时就会被调用
      # 首先调用父类的构造函数
      super(ConvNet, self).__init__()
      # 定义一个卷积层,输入通道为1,输出通道为4,窗口大小为5,padding为2\r\n        
      self.conv1 = nn.Conv2d(1, 4, 5, padding=2)      
      self.pool = nn.MaxPool2d(2, 2)  # 定义一个池化层,一个窗口为2x2的池化运算、\r\n        
      # 第二层卷积  输入通道为depth[0],输入通道为depth[1],窗口为5,padding为2\r\n        
      self.conv2 = nn.Conv2d(depth[0], depth[1], 5, padding=2)
       # 一个线性连接层,输入尺寸为最后一层立方体的线性平铺 输出层512个节点\r\n        
      self.fc1 = nn.Linear(image_size // 4 * image_size // 4 * depth[1], 512)
      self.fc2 = nn.Linear(512, num_classes)  # 最后一层线性分类单元输入为512,,输出为要做分类的类别数\r\n\r\n    
      def forward(self, x):  # 该函数完成神经网络真正的前向运算,在这里把各个组件进行实际的拼装\r\n        # x的尺寸 (batch_size,image_channels,image_width,image_height)\r\n        
        x = self.conv1(x)  # 第一层卷积\r\n        
        x = F.relu(x)  # 激活函数用ReLU,防止过拟合\r\n        # 此时x的尺寸 (batch_size,num_filters,image_width,image_height)\r\n\r\n        
        x = self.pool(x)  # 第二层是池化,将图片变小\r\n        # x尺寸 (batch_size,depth[0],image_width/2,image_height/2)\r\n\r\n        
        x = self.conv2(x)  # 第三层又是卷积,窗口为5,输入输出通道分别为depth[0]=4,depth[1]=8\r\n        
        x = F.relu(x)  # 非线性函数\r\n        # x尺寸 (batch_size,depth[1],image_width/2,image_height/2)\r\n\r\n        
        x = self.pool(x)  # 第四层是池化,将图片缩小为原来的1/4\r\n        # x尺寸 (batch_size,depth[1],image_width/4,image_height/4)\r\n\r\n        # 将立体的特征图tensor压成一维向量\r\n        
        x = x.view(-1, image_size // 4 * image_size // 4 * depth[1])  # 该函数将x压成一维向量\r\n        
        # x尺寸 (batch_size,image_size//4*image_size//4*depth[1])\r\n\r\n        
        x = F.relu(self.fc1(x))  # 第五层为全连接,ReLUctant激活函数\r\n        
        # x的尺寸,(batch_size,512)\r\n\r\n        # 以0.5的概率对这一层进行dropout操作,防止过拟合\r\n        
        x = F.dropout(x, training=self.training)    
        x = self.fc2(x)  # 全连接\r\n        # x的尺寸(batch_size,num_classes)\r\n\r\n        # 输出层为log_softmax,即概率对数值log(p(x)),采用这个函数,可以使得后面交叉熵计算更快\r\n        
        x = F.log_softmax(x, dim=1)       
        return x    
      
      def retrieve_features(self, x):       # 该函数用于提取网络的特征图,返回feature_map1,feature_map2为前两层卷积层的特征图\r\n        
        feature_map1 = F.relu(self.conv1(x))  # 完成第一次卷积\r\n        
        x = self.pool(feature_map1)  # 完成第一次池化\r\n        
        feature_map2 = F.relu(self.conv2(x))        
        return (feature_map1, feature_map2)
  • init.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import torch
from torch.autograd 
import Variable
import time
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from MINIST.data import num_epochs, train_loader, validation_loader, test_loader
from MINIST.convNet import ConvNet
print("加载完数据\")
net = ConvNet()
criterion = nn.CrossEntropyLoss() #  Loss 函数定义,交叉熵
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9) # 定义优化器,普通的梯度下降算法
record = []  # 记录准确率等数值容器\r\nweights = [] # 每若干步就记录一次卷积核
def rightness(predictions, labels): 
    pred = torch.max(predictions.data, 1)[1] # 对于任意一行(一个样本)的输出值的第1个维度,求最大,得到每一行的最大元素的下标\r\n    
    rights = pred.eq(labels.data.view_as(pred)).sum() #将下标与labels中包含的类别进行比较,并累计得到比较正确的数量\r\n    
    return rights, len(labels) #返回正确的数量和这一次一共比较了多少元素\r\n\r\n# 开始循环训练\r\n
    print('起始时间\',time.strftime(\"%Y-%m-%d %H:%M:%S\"))
  for epoch in range(num_epochs):    
train_rights = [] # 记录训练数据集准确率的容器\r\n    
for batch_idx, (data,target) in enumerate(train_loader):        # 将Tensor转化为Var,data为一批图像,target为一批标签\r\n        
  data,target = Variable(data),Variable(target)\r\n        # 给网络模型做标记,标志着在训练集上训练\r\n        # 这种区分为了打开关闭net的training标志,从而决定是否运行dropout\r\n
  net.train()       
  output = net(data) # 神经网络完成一次前馈计算过程,得到预测输出output\r\n        
  loss = criterion(output,target) # 将output与target相比,计算误差\r\n        
  optimizer.zero_grad() # 清空梯度信息\r\n        
  loss.backward() # 反向传播\r\n        
  optimizer.step() # 一步随机梯度下降算法\r\n        
  right = rightness(output,target) # 计算准确率所需数值,返回值为(正样本样例数,总样本数)\r\n        
  train_rights.append(right) # 将计算结果装到列表容器train_rights中\r\n\r\n        
  if batch_idx % 100 == 0: # 每间隔100个batch执行一次打印操作\r\n            
      net.eval() # 给网络模型做标记,标志着在训练集上训练\r\n            
      val_rights = [] # 记录校验数据集准确率的容器\r\n           
      print('MyEpoch [{}/{}], Step [{}/{}], Loss: {:.4f}'.format(epoch + 1, num_epochs, 100, len(train_loader), loss.item()))            
      print('当前时间\', time.strftime("%Y-%m-%d %H:%M:%S"))            # 开始在校验集上做循环,季孙校验集准确度\r\n            
for (data,target) in validation_loader:              
    data,target = Variable(data),Variable(target)                
    output = net(data)            
    right = rightness(output,target)
    val_rights.append(right)            # 分别计算目前已经计算过的测试集以及全部校验集上模型的表现:分类准确率\r\n            # train_r 为一个二元组,分别记录所有训练集上分类正确的数量和该集合中总的样本数\r\n            
    train_r = (sum([tup[0] for tup in train_rights]),sum(tup[1] for tup in train_rights))           
    val_r = (sum([tup[0] for tup in val_rights]),sum(tup[1] for tup in val_rights))           
    record.append((100-100.*train_r[0]/train_r[1],100-100.*val_r[0]/val_r[1]))          
    weights.append([net.conv1.weight.data.clone(),net.conv1.bias.data.clone(),net.conv2.weight.data.clone(),net.conv2.bias.data.clone()])
    print('训练结束时间',time.strftime("%Y-%m-%d %H:%M:%S"))
    # 在测试集上分批运行,并计算总的正确率
    net.eval()
    vals = []
# 对测试集进行循环\r\n
for data, target in test_loader:    
    with torch.no_grad()     
    data, target = Variable(data),Variable(target)   
    output = net(data) # 将特征数据输入网络,得到分类输出
    val = rightness(output, target)   
    vals.append(val)
    # 计算准确率
    rights = (sum([tup[0] for tup in vals]),sum([tup[1] for tup in vals]))
    right_rate = 1.0*rights[0] / rights[1]
    print("准确率",right_rate)print('最后时间',time.strftime("%Y-%m-%d %H:%M:%S"))
    plt.figure(figsize=(10, 7)
    plt.plot(record) # record 记录了每一个打印周期的训练集和校验集上的准确
    plt.xlabel("Step")
    nplt.ylabel("Erro rate")
    plt.show()
    print"全部结束")