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)