本系列文章主要是通过手写数字识别这一经典的CNN入门例子,来让大家熟悉深度学习框架Pytorch的基本操作,达到可以实现自己网络结构的目的。
本系列文章目录
Pytorch从放弃到入门系列(一)
Pytorch从放弃到入门系列(二)
本文为该系列文章的第二篇,主要介绍卷积神经网络的相关信息。
一、MNIST数据集下载
1、代码实现
上一篇文章描述的是从mnist官网上下载数据集然后手动进行解析的过程,其实在TensorFlow和Pytorch等框架中已有封装好mnist数据集的下载接口,我看可以通过代码直接下载。先贴出相关代码:
from torchvision import datasets, transforms
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize(mean=[0.5], std=[0.5])])
data_train = datasets.MNIST(root="./data", transform=transform, train=True, download=True)
data_test = datasets.MNIST(root="./data", transform=transform, train=False, download=True)
2、代码详解
torchvision 库是 Pytorch 框架下常用的图像处理包,里面集成了常用的数据集、模型结构和常用的图片转换工具。torchvision的datasets可以很方便的自动下载数据集(包括:MNIST、COCO、ImageNet、CIFCAR等常见数据集)。通过datesets的MNIST函数可以下载该数据集,并将图像预处理后的结果保存下来,下载函数如下:
dataset.MNIST(root, train=True, transform=None, target_transform=None, download=False)
参数说明:
- root: 保存下载数据以及处理后的数据(training.pt、test.pt )的主目录;
- root: 保存下载数据以及处理后的数据(training.pt、test.pt )的主目录;
- train: True表示训练集,False表示测试集 ;
- download: True表示从网上下载数据集,并把数据集放在root目录下;如果数据集之前下载过,将处理过的数据放在processed文件夹下;
transforms是Pytorch中的图像预处理包,包含了归一化、尺寸裁剪、翻转等常见操作,可用于数据增强(data
augmentation)操作。在这里用到的函数为:
- ToTensor():已经把图像数据转成0和1之间的Tensor类型
- Normalize():采用提供的均值、方差对图像中的像素进行归一化操作
需要强调的是在做数据归一化之前必须要转成Tensor,而其他resize或crop操作则不需要。
利用Compose函数可以把多个操作合并到一起。
二、卷积神经网络
1、原始LeNet-5网络
对于手写数字识别,1998年Yann LeCun在论文《Gradient-Based Learning Applied to Document Recognition》中提出的LeNet-5网络结构如下:
2、修改后的网络
由于本文中使用的MNIST数据集的图像大小为28x28,而论文中的图片大小为32x32,所以我们需要对原来的网络结构做一些调整,调整后的网络结构如下:
3、代码实现
先贴出网络结构代码:
class LeNet(torch.nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.init_model()
def init_model(self):
self.__conv1 = torch.nn.Sequential(torch.nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1), torch.nn.ReLU(), torch.nn.MaxPool2d(stride=2, kernel_size=2))
self.__conv2 = torch.nn.Sequential(torch.nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1), torch.nn.ReLU(), torch.nn.MaxPool2d(stride=2, kernel_size=2))
self.__fulllayer = torch.nn.Sequential(torch.nn.Linear(64*7*7, 1024), torch.nn.ReLU(), torch.nn.Linear(1024, 10))
def forward(self, x):
x = self.__conv1(x)
x = self.__conv2(x)
x = x.view(-1, 64*7*7)
x = self.__fulllayer(x)
return x
4、代码详解
整个LeNet模型主要由两个部分组成:
- 网络结构(init_model函数):
- 变量self.__conv1:包含第一层的卷积、激活、池化操作
- 变量self.__conv2:包含第二层的卷积、激活、池化操作
- 变量self.__fulllayer:全连接层
- 前馈计算(forward函数)
这里主要涉及到的关键函数有六个:
(1) 二维卷积函数:torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
- in_channels、out_channels:输入、输出信号的通道数
- kernel_size:卷积核大小
- stride:卷积运算的步长
- padding:输入的每一条边补充0的层数
- dilation:控制卷积窗口中元素步幅的参数
- groups:输入通道到输出通道的阻塞连接数
- bias:是否需要添加偏执
(2) 激活函数:torch.nn.ReLU(inplace=False)
- 计算公式为:relu(x) = max(0,x)
- 参数inplace表示是否采用计算结果覆盖原变量
(3) 池化函数:torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
- kernel_size:maxpool操作的窗口大小
- stride、padding、dilation:同Conv2d函数
- ceil_mode:值为True时,会用向上取整代替向下取整
(4) 线性函数:nn.Linear(in_features, out_features, bias=True)
- 主要用于设置网络中的全连接层,将输入为[batch_size, in_features]的张量变换成[batch_size, out_features]的输出张量
(5) 有序神经网络容器函数(暂且这样称呼😂):torch.nn.Sequential()
- 可以将网络结构中的各个模块有序地加入到构造器并添加到计算图中执行
- 也可以将神经网络模块为元素的有序字典作为传入参数
- 在本文中将卷积层、激活层、池化层组织起来,便于操作
(6) 重构张量的维度函数:torch.view(参数a, 参数b, …)
- 该函数根据传入的参数返回相应的tensor
- view(-1, b)表示返回1xb维度的tensor
三、卷积算子计算
假设传入的图像大小为(width,width),图像补边的大小为padding,经过大小为(kernel,kernel)、步长为stride的卷积核,进行卷积计算后的图像大小为:
w
i
d
t
h
+
2
∗
p
a
d
d
i
n
g
−
k
e
r
n
e
l
s
t
r
i
d
e
+
1
\frac{width+2*padding-kernel}{stride}+1
stridewidth+2∗padding−kernel+1
在本文中传入的图像为28x28,经过第一层的卷积算子及池化算子后的图像大小为14x14,经过第二层的卷积算子及池化算子后的图像大小为7x7,所以最终全连接的大小为:
64
∗
7
∗
7
=
3136
64*7*7=3136
64∗7∗7=3136
其中:64为第二层池化层输出的特征图数量