[pytorch]通过CNN实现手写数字识别(附完整代码)

CNN实现手写数字识别

  • 卷积神经网络介绍
    • 什么是卷积
    • padding
    • stride(步长)
    • 三维卷积
    • 池化层
    • LeNet-5卷积神经网络
    • LeNet-5实现(pytorch)

卷积神经网络介绍

卷积神经网络它的优点在于,需要调优的参数比全连接神经网络少的多,因此他的训练速度会快很多。一般多用于做视觉识别。

什么是卷积

下面我们来看一下这张图片,从下图我们可以看到

  1. 左边这个二维矩阵表示的是一张661的图片,661意思是一张长为6宽为6通道数为1的图片,一张彩色的图片的通道数是3,所以我们调颜色有三个值分别是RGB。
  2. 中间是一个33的一个二维矩阵,33的大小也不是固定的,可以根据实际情况取值。。在卷积神经网络中称为卷积核或者过滤器,它就是整个神经网络要训练取得的参数。一开始初始化时可以随便赋值。这个卷积核相当于从图像矩阵中不停的移动,每次移动一格,从而得到最右边的另外一张图片。比如从左上角开始计算31+00+1*-1+11+50+8*-1+21+70+2*-1 = -5。
  3. 假设我们有一张图像是NN的,过滤器FF,最后得到(N-F+1)(N-F+1)的图像。下图中输入的是一张66的图像,输出的是一张4*4的图像。**这样就会有两个缺点:一是一张图片经过层层的卷积之后,输出的图片越来越小了。那应该怎么保证输出的图片保持原来的大小呢,请看下一个知识点padding。**二是边缘的值因为被计算的次数较少,因此可能会丢失调边缘的特征。

[pytorch]通过CNN实现手写数字识别(附完整代码)_第1张图片

padding

padding的中文意思是填充,大概意识是要不要在图像周围用0填充。padding有两种,一种是valid,另一种是same。valid模 式即是输出的图像不需要与原图像大小相等,same模式即是输出的图像需要与原图像大小相等。但是要在图像周围填充多少层0才能保证输出的图像与原图像等大小呢。

假设加一圈0则P = 1,那么图像的大小又原来的6变成了吧,即N+2P。上面我已经给出了计算输出图像大小的公式:输出图像大小 : (N-F+1)(N-F+1),现在变成了(N+2P-F+1) (N+2P-F+1).因为输入的大小等于输出的大小所以:
N+2P-F+1)= N,推出 P = (F-1)/2
[pytorch]通过CNN实现手写数字识别(附完整代码)_第2张图片

stride(步长)

上面原图像与卷积核做卷积时是一格一格的移动的,其实我们可以定义2格2格的移动。如下图所示:
[pytorch]通过CNN实现手写数字识别(附完整代码)_第3张图片
同时我们也发现了,如果2格2格移动,输出的图像更小的,77的输入图像,最后输出是33的图像,因此输入与输出图像之间的关系,公式需要更新为:
[pytorch]通过CNN实现手写数字识别(附完整代码)_第4张图片

三维卷积

以上讲的都是CONV2D,即是2维矩阵,那么如果是彩色图片的话,那么它就是一个NN3的三维矩阵,分别代表RGB三个不同的通道。比如下面这个图,输入的图像是三通道的图像,663,对应的卷积核的通道也要等于3,一个卷积核产生一个一维的输出图像。如果有两个卷积核,则产生2维的输出图像,如果有1000个卷积核则输出1000维(即相当于通道数)的图像。

[pytorch]通过CNN实现手写数字识别(附完整代码)_第5张图片
[pytorch]通过CNN实现手写数字识别(附完整代码)_第6张图片

池化层

池化层的作用主要是减小模型的大小,提高运算速度。我们从远处看下面这两个图形,其实右边的图形就是左边的缩小版。那它是怎么运算的呢?下面的运算使用的是最大池化即是Max Pooling,我们从下图可以看到这个最大池化使用的是22的窗口,步长是2.取得是窗口中最大的那个数,而卷积取得是卷积和。我们也能得出一个44的输入图像,输出后大小减半了。
[pytorch]通过CNN实现手写数字识别(附完整代码)_第7张图片

LeNet-5卷积神经网络

为什么是-5而不是-6呢,其实5是代表了网络层数,卷积层+池化层算一层。所以我们可以看到:
**第一层:**输入是32321的图像,卷积输出的是6个2828的图像,卷积核为6个55大小的卷积核,步长为1。通过池化层图片缩小为6个1414的图像。窗口大小为22,步长为2.
**第二层:**输入的是14146的图像,卷积输出的是16个1010的图像,卷积核为16个55大小的卷积核,步长为1。通过池化层图片缩小为16个55图像。窗口大小为22,步长为2.
**第三层:**输入为1655 = 400个神经元,输出120个神经元。
**第四层:**输入为120个神经元,输出84个神经元。
**第五层:**输入为84个神经元,输出为10个标签值。

[pytorch]通过CNN实现手写数字识别(附完整代码)_第8张图片

LeNet-5实现(pytorch)

import torch
from torch.autograd import Variable
import numpy as np
import torch.nn as nn
from torchvision import datasets,transforms


#MNIST Dataset

train_dataset = datasets.MNIST(root='./mnist_data/',
                               train=True,
                               transform=transforms.ToTensor(),
                               download=False)

test_dataset = datasets.MNIST(root='./mnist_data/',
                              train=False,
                              transform=transforms.ToTensor())


# Data Loader (Input Pipeline)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size=128,
                                           shuffle=True)

test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size=100,
                                          shuffle=False)


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=2)
        self.conv2 = nn.Conv2d(6, 16, kernel_size=5)
        self.mp = nn.MaxPool2d(2)
        self.relu = nn.ReLU()
        self.fc1 = nn.Linear(16*5*5,120)  # 必须为16*5*5
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
        self.logsoftmax = nn.LogSoftmax()

    def forward(self, x):
        in_size = x.size(0)
        out = self.relu(self.mp(self.conv1(x)))
        out = self.relu(self.mp(self.conv2(out)))
        out = self.relu(self.conv3(out))
        out = out.view(in_size, -1)
        out = self.relu(self.fc1(out))
        out = self.relu(self.fc2(out))
        out = self.fc3(out)
        return self.logsoftmax(out)
      

model = Net()
 #定义损失
loss_func = torch.nn.CrossEntropyLoss()
#定义优化器
opt = torch.optim.Adam(model.parameters(),lr=0.001)

def train(epoch):
    model.train()
    for batch_index,(data,target) in enumerate(train_loader):
        data,target = Variable(data), Variable(target)
        opt.zero_grad()
        output = model(data)
        loss = loss_func(output, target)
        #误差反向传播
        loss.backward()
        #将参数更新值施加到net的parmeters上
        opt.step()
        if batch_index%20 == 0:
             print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
               epoch, batch_index * len(data), len(train_loader.dataset),
               100. * batch_index / len(train_loader), loss.item()))


def test():
        model.eval()
        test_loss = 0
        correct = 0
        for data,target in test_loader:
            data , target = Variable(data,volatile=True) ,Variable(target)
            output = model(data)
            # sum up batch loss
            test_loss += loss_func(output, target).item()
            # get the index of the max log-probability
            pred = torch.max(output.data,1)[1]
            correct += pred.eq(target.data.view_as(pred)).cpu().sum()
            test_loss /= len(test_loader.dataset)

            print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
                test_loss, correct, len(test_loader.dataset),
                100. * correct / len(test_loader.dataset)))




for epoch in range(1, 10):
    train(epoch)
    test()

参考资料:
https://zhuanlan.zhihu.com/p/30117574
吴恩达系列卷积神经网络视频:
https://www.bilibili.com/video/BV1F4411y7o7?p=9

你可能感兴趣的:(pytorch,python,python,卷积神经网络)