【Python日记】用Pytorch搭建一个LeNet-5网络模型

LeNet-5网络堪称卷积神经网络(CNN)的经典之作,它最早是在1998年被Yann LeCun等人发表在一篇名为《Gradient-Based Learning Applied to Document Recognition》的论文上。从论文标题中我们也可以看出,LeNet-5主要是用作识别手写字符,虽然它的识别性能很高,但是在其发表之后的数十年里LeNet-5并没有流行起来,最主要的原因还是因为当时计算机的计算能力很有限,人们对于训练规模如此之小的网络也会感到略显艰难。
Lenet-5网络的规模虽然很小,但是它却已经基本蕴含了本书目前所讲到过的所有知识,卷积层、池化层、全连接层一样不少,这在论文发表的年代,已然是一件很了不起的事情。
今天我来带着大家一步一步用pytorch搭建起属于你自己的LeNet-5。

第一步:引入必要的包

代码如下:

import torch.nn as nn

这里我们直接引入torch包中的nn模块,从它的名字也能看出,它就是专门搭建神经网络的一个模块。

第二步:搭建卷积层

代码如下:

class LeNet5(nn.Module):
    def __init__(self):
        super(LeNet5, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(  # (1, 32, 32)
                in_channels=1,
                out_channels=16,
                kernel_size=5,
                stride=1,
                padding=0
            ),  # ->(16, 28, 28)
            nn.ReLU(),
            nn.AvgPool2d(kernel_size=2)  # ->(16, 14, 14)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(6, 16, 5, 1, 0),  # (16, 10, 10)
            nn.ReLU(),
            nn.AvgPool2d(2)              # (16, 5, 5)
        )

搭建的方法和搭建一个普通的神经网络没什么区别,还是用到创建类的方法。nn.Conv2d是2D卷积的意思,in_channels定义输入通道的数量,out_channels定义输出通道的数量,kernel_size定义卷积核的尺寸,stride定义卷积步长,由于LeNet-5网络并没有加padding,所以padding设置为0.这里self.conv1定义的时候,我把每个参数都用等号的形式写出来,但是其实可以简写,像是self.conv2中的这种形式,代表卷积输入6个,有16个卷积核输出16个特征图,卷积核的尺寸是 5 × 5 5\times5 5×5,步长为1,padding同样为0.然后每个卷积层后面连接一个ReLU激活函数,其实原文中用的是Sigmoid或者tanh函数,但是现在用ReLU性能可能更好一点,接着每个激活函数后面连接一个平均池化(现在用的比较多的是最大池化),这样,我们这两层卷积层就搭建完成了。

第三步:搭建全连接层

代码如下:

		##注意这段代码还是在上面的类中
        self.out = nn.Sequential(
            nn.Linear(16 * 5 * 5 , 120),
            nn.ReLU(),
            nn.Linear(120, 84),
            nn.ReLU(),
            nn.Linear(84, 10),
        )

这段代码跟我之前文章里讲过的搭建神经网络的代码几乎完全一样,这里不做赘述。

第四步:搭建前向传播接口

代码如下:

   ## 这段代码同样在上面那个类中
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.view(x.size(0), -1)  # 将输出化成一维向量
        output = self.out(x)
        return output

这段代码其实就是你网络结构的一个梳理,也是网络对外的一个接口,其中的x就是你传入网络的输入,知道最后的return返回了最后的输出。这里比较难理解的可能就是x = x.view(x.size(0), -1)这句,其实这句的意思就是把卷积层最后的输出化成一个一维向量,这样才可以将输出传入全连接层继续进行运算。所以你完全可以用numpy包中的reshape函数来代替它。

第五步:看一看你的网络吧!

代码如下:

myNet = LeNet5()
print(myNet)

我们这里直接将我们的网络打印出来,你就可以看到显示下面的结果:

LeNet5(
  (conv1): Sequential(
    (0): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
    (1): ReLU()
    (2): AvgPool2d(kernel_size=2, stride=2, padding=0)
  )
  (conv2): Sequential(
    (0): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
    (1): ReLU()
    (2): AvgPool2d(kernel_size=2, stride=2, padding=0)
  )
  (out): Sequential(
    (0): Linear(in_features=400, out_features=120, bias=True)
    (1): ReLU()
    (2): Linear(in_features=120, out_features=84, bias=True)
    (3): ReLU()
    (4): Linear(in_features=84, out_features=10, bias=True)
  )
)

每一层的输入和输出都很清晰的显示了出来,说明你成功地在你的Python里搭建起来了LeNet-5网络!
那么这个网络怎么使用呢?我会在我下一篇博客里讲一下运用LeNet-5网络训练兵识别字符的方法~
最后奉上完整代码:

import torch.nn as nn


class LeNet5(nn.Module):
    def __init__(self):
        super(LeNet5, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(  # (1, 32, 32)
                in_channels=1,
                out_channels=6,
                kernel_size=5,
                stride=1,
                padding=0
            ),  # ->(16, 28, 28)
            nn.ReLU(),
            nn.AvgPool2d(kernel_size=2)  # ->(16, 14, 14)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(6, 16, 5, 1, 0),  # (16, 10, 10)
            nn.ReLU(),
            nn.AvgPool2d(2)              # (16, 5, 5)
        )
        self.out = nn.Sequential(
            nn.Linear(16 * 5 * 5 , 120),
            nn.ReLU(),
            nn.Linear(120, 84),
            nn.ReLU(),
            nn.Linear(84, 10),
        )

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.view(x.size(0), -1)  # 将输出化成一维向量
        output = self.out(x)
        return output

myNet = LeNet5()
print(myNet)

你可能感兴趣的:(【Python日记】用Pytorch搭建一个LeNet-5网络模型)