# 人工智能实验四:深度学习算法及应用
## 一、实验目的
了解深度学习的基本原理
能够使用深度学习开源工具识别图像中的数字
了解图像识别的基本原理
## 二、实验要求
解释深度学习原理;
对实验性能进行分析;
回答思考题;
## 三、实验的硬件、软件平台
硬件:计算机
软件:操作系统:WINDOWS
应用软件:PyTorch with CUDA, Python, matplotlib
## 四、实验内容与步骤
安装开源深度学习工具设计并实现一个深度学习模型,它能够学习识别图像中的数字序列。然后使用数据训练它:你可以使用人工合成的数据(推荐),或直接使用现实数据。
## 五、思考题
深度算法参数的设置对算法性能的影响?
## 实验步骤
本次实验我使用的是MNIST数据集,使用PyTorch来搭建卷积神经网络实现图像识别。
原理
深度学习是在神经网络的基础上发展而来,其搭建的神经网络的隐层不止有一个,而是有多个。通过将多个线性函数进行组合,并且采用激活函数使其不再仅仅表示线性模型,而是可以做到无限逼近任意的模型,从而完成分类任务。其是在庞大的数据集和强大的计算能力的支持下,学习得到数据内部的关系,从而拟合出相应的能够表示其模型的函数。
本次实验由于是要解决图像识别问题,所以我才用的是卷积神经网络。
卷积神经网络是通过卷积层来对原本图像的特征进行提取,从而得到图像的特征图,再经过全连接神经网络进行学习,进行误差反向传播,更新权值。经过多次训练得到一个效果较好的分类器。
卷积层

考虑一个简单的 4 x 4 的图像(上左图),其每个像素的值即当前像素的灰度值,使用一个 3 x 3 的卷积核(上右图)对其进行卷积操作:使用卷积核覆盖原图像的一个范围,将对应的点的值相乘,最后将这9个像素的值相加,作为一个新的值放入到新的图像中。这一个新的图像就被称为特征图。此步骤之后,移动卷积核,对下一个 3 x 3 的方阵进行同样的操作,直到全部完毕。得到如下的特征图:

卷积核每次移动的范围称为步长,上述的情况中步长为1。
但是对于这种卷积方式,很显然,位于图像边缘的一些点的特征会被丢弃,所以,可以对图像周围用0进行填充:

填充的范围被称作Padding,上图的Padding = 0。
对于一个图像,可以经过不止一个卷积核的卷积,得到多个特征图,特征图的个数与卷积核的个数相等。
记输入图像为 X * X,卷积核为 C * C,步长为 S,Padding为 P ,那么,得到的特征图的尺寸则为N = (W-F+2P)/S + 1当然,上述的卷积核仅仅是一个最简单的卷积核,除此之外,还有扩张卷积:包含一个叫做扩张率的参数,定义了卷积核内参数间的行(列)间隔数,或者又如转置卷积。
对于要处理的二维图像而言,其不仅有长和宽两个变量,还有一个称为“通道”的变量,如上述的例子,输入的图像可表示为(4, 4, 1),表示长和宽都是4,有1个通道,经过N个上述的 3 x 3 卷积核后,其可以变成(2, 2, N),即有了N个通道。在这里,可以把其看作是N个二维的图像组合而成的一个三维图像。
对于多通道的图像,假定其有N个通道,经过一个 3 x 3 的卷积核,那么这个卷积核也应该为(3, 3, N),使用这一个卷积核对每一个通道相同的位置进行卷积,得到了N个值,将这N个值求和,作为输出图像中的一个值。所以,得到的通道的数目只与卷积核的个数有关。
池化层
从上面卷积层可以看到,我们的目的是对一个图像进行特征提取,最终得到了一个N通道的图像,但是,如果卷积核的数量太多,那么得到的特征图数量也是非常多。这时就需要池化层来降低卷积层输出的特征维度,同时可以防止过拟合现象。
由于图像具有一种“静态性”的属性,也就是在一个图像区域有用的特征极有可能在另一个区域同样有用,所以通过池化层可以来降低图像的维度。
这里只介绍我使用的一般池化的方法。
一般池化包括平均池化和最大池化两种。平均池化即计算图像区域的平均值作为该区域池化后的值;最大池化则是选图像区域的最大值作为该区域池化后的值。
如果选取的池化层为 2 x 2,那么对于一个 4 x 4 的图像能够得到如下的结果:

将一个 4 x 4 的图像降低成了 2 x 2 的图像。其中对应的单元的值,取决于池化的方法。
值得注意的是,池化层不包含需要学习的参数。
学习
最后是学习部分。CNN的学习也是和神经网络一样,先经过前向传播,得到当前的预测值,再计算误差,将误差反向传播,更新全连接层的权值和卷积核的参数。这里存在一个问题,如果卷积核的参数都不同,那么需要更新的参数数量会非常多。为了解决这个问题,采用了参数共享的机制,即对于每一个卷积核,其对每个通道而言权值都是相同的。
这里卷积层的反向传播推导过于复杂,所以我也就不再罗列公式了。
实验代码
本次实验通过PyTorch进行CNN的搭建以及数据的处理。
数据读取
采用了MNIST数据集:
```
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.1307, ), (0.3081, ))])
# 读入
data_train = datasets.MNIST(root="./data",
transform=transform,
train=True,
download=True)
data_test = datasets.MNIST(root="./data", transform=transform, train=False)
# 按 batch = 128 加载
data_loader_train = torch.utils.data.DataLoader(dataset=data_train,
batch_size=128,
shuffle=True)
data_loader_test = torch.utils.data.DataLoader(dataset=data_test,
batch_size=128,
shuffle=True)
```
将数据读入后转变为张量,对其进行正则化,正则化的均值和方差的选择则是依据MNIST的标准均值方差。加载之后使用dataloader将其读入,每128个图像作为一个batch加载,使用shuffle将其随机打乱。
##### CNN的搭建
```
def __init__(self) -> None:
super(Model, self).__init__()
self.conv1 = nn.Conv2d(1, 8, kernel_size=3, stride=1) # 卷积层1
self.conv2 = nn.Conv2d(8, 32, kernel_size=4, stride=1) # 卷积层2
self.hidden1 = nn.Linear(5 * 5 * 32, 150) # 隐层1 输入5 * 5 * 32(之后会进行解释)
self.hidden2 = nn.Linear(150, 40) # 隐层2 输入150 输出40
self.hidden3 = nn.Linear(40, 10) # 输出层 10分类 输出为10
```
这里采用了两层卷积层进行特征的提取,第一个卷积层的kernel选择为 3 x 3,步长为1,输出为8通道,对于其输入,由于图像转换为了灰度值为非RGB值,所以输入的图像为1通道,如果是RGB值,则要有3通道。第二个卷积层的kernel则是 4 x 4,步长为1,输出32通道,输入则是8通道。
```
def forward(self, x): # 28 * 28
x = self.conv1(x) # 26 * 26 * 8
x = F.relu(x)
x = F.max_pool2d(x, 2) # 13 * 13 * 8
x = self.conv2(x) # 10 * 10 * 32
x = F.relu(x)
x = F.max_pool2d(x, 2) # 5 * 5 * 32
x = x.view(-1, 5 * 5 * 32) # 展开 变成一维的张量
# 全连接神经网络
x = self.hidden1(x