【CV】Learning CNN

已实现的功能简述及运行简要说明

功能简述

​ 利用CNN进行手写数字识别与物体分类:

  1. 使用PyTorch工具实现最基本的卷积神经网络(CNN) LeNet-5以及一个物体分类的CNN
  2. 用MNIST手写数字数据集(0-9一共十个数字)6万样本实现对LeNet-5的训练,对MNIST的1万测试样本进行测试,获得识别率。
  3. 用CIFAR-10数据库实现CNN物体分类功能的训练与测试。

运行简要说明

​ 由于本次实验使用的两个测试集都比较经典,在torchvision库中都已经做了封装,因此在本实验中选择了直接调用。运行程序后,将先将指定数据集下载到指定路径中,然后再进行训练。为了节省下载时间,也可以将下载好的数据集添加到对应路径(./data/)中直接进行训练。

MNIST.py用于训练MNIST测试集,CIFAR.py用于训练CIFAR-10测试集。


开发与运行环境

​ 操作系统:Windows 10,64位
​ 开发环境:Python 3.7.3
​ 库环境:numpy 1.16.4, torch 1.1.0, torchvision 0.3.0

算法基本思路、原理

CNN

​ 卷积神经网络(Convolutional Neural Network, CNN)是深度学习技术中极具代表的网络结构之一,在图像处理领域取得了很大的成功。CNN相较于传统的图像处理算法的优点之一在于,避免了对图像复杂的前期预处理过程(提取人工特征等),可以直接输入原始图像。

​ CNN中主要有两种类型的网络层,分别是卷积层和池化/采样层。

​ 卷积层的作用是提取图像的各种特征,是卷积核在上一级输入层上通过逐一滑动窗口计算而得,卷积核中的每一个参数都相当于传统神经网络中的权值参数,与对应的局部像素相连接,将卷积核的各个参数与对应的局部像素值相乘之和,(通常还要再加上一个偏置参数),得到卷积层上的结果。

​ 池化层的作用是对原始特征信号进行抽象,从而大幅度减少训练参数,另外还可以减轻模型过拟合的程度。池化/采样的方式通常有以下两种:

  • Max-Pooling: 选择Pooling窗口中的最大值作为采样值;
  • Mean-Pooling: 将Pooling窗口中的所有值相加取平均,以平均值作为采样值;

LeNet-5

​ LeNet-5是一种经典的CNN网络结构,其结构如下图:

image-20191231174428209

输入层

​ 将输入数据的尺寸统一归一化为$32 \times 32$

卷积层1

​ 使用6个大小为$5 \times 5$的卷积核对输入图像进行第一次卷积运算,得到6个大小为28 * 28的feature map

池化层1

​ 使用$2 \times 2$的核对第一次卷积之后得到的结果进行池化,得到6个$14\times14$的map

卷积层2

​ 对上一层池化得到的输出进行组合,将6个特征增加为16个,使用16种$5\times 5$的卷积核进行卷积操作,得到16个$10\times10$的特征图。特征的组合如下:

image-20191231225943896

池化层2

​ 对输入的$10\times10$的特征图,使用$2\times2$的核进行池化,得到16个$5\times5$的feature map

卷积层3

​ 使用120种$5\times5$的卷积核进行卷积,输出120个$1\times1$的feature map

全连接层

​ 使用全连接层将上一层输出的120个节点与本层的84个节点进行全连接

输出层

​ 将前一层的84个节点转换为输出的大小(与输出大小进行全连接)

MNIST手写数据集

​ MNIST数据集是机器学习领域中非常经典的一个数据集,由60000个训练样本和10000个测试样本组成,每个样本都是一张28 * 28像素的灰度手写数字图片。

​ 图片示例如下:

image-20200101014446671

CIFAR-10数据集

​ CIFAR-10数据集由10个类的60000个32x32彩色图像组成,每个类有6000个图像。有50000个训练图像和10000个测试图像。
​ 数据集分为五个训练批次和一个测试批次,每个批次有10000个图像。测试批次包含来自每个类别的恰好1000个随机选择的图像。训练批次以随机顺序包含剩余图像,但一些训练批次可能包含来自一个类别的图像比另一个更多。总体来说,五个训练集之和包含来自每个类的正好5000张图像。
​ 以下是数据集中的类,以及来自每个类的10个随机图像:

image-20200101014334677


具体实现

LeNet5网络搭建

​ 根据上述LeNet5的每一层结构,搭建LeNet5神经网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class leNet5(nn.Module):
def __init__(self, input_size, output_size):
super(leNet5, self).__init__()
padding = (int)((32 - np.sqrt(input_size) )/2)
channel_num = 1
self.c1 = nn.Conv2d(channel_num, 6, 5, padding = padding)
self.s2 = nn.MaxPool2d(kernel_size = 2, stride = 2)
self.c3 = nn.Conv2d(6, 16, 5)
self.s4 = nn.MaxPool2d(2,2)
self.c5 = nn.Conv2d(16, 120, 5)
self.f6 = nn.Linear(120, 84)
self.output = nn.Linear(84, output_size)
self.relu = nn.ReLU()

def forward(self, x):
x = self.relu(self.c1(x))
x = self.relu(self.s2(x))
x = self.relu(self.c3(x))
x = self.relu(self.s4(x))
x = self.relu(self.c5(x))
x = x.view(x.size()[0], -1)
x = self.relu(self.f6(x))
x = self.output(x)
return x

MNIST手写数据集测试

​ 相关参数设置

1
2
3
4
5
6
input_size = 28 * 28
output_size = 10
channel_num = 1
num_epochs = 20
batch_size = 64
learning_rate = 0.4

​ 下载MNIST数据集并进行读取

1
2
3
4
train_dataset = torchvision.datasets.MNIST(root='./data',train=True, transform=transforms.ToTensor(), download=True) 
test_dataset = torchvision.datasets.MNIST(root='./data',train=False, transform=transforms.ToTensor(), download=True)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)

​ 损失函数及优化器设计

1
2
lossFunction = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

​ 训练网络

1
2
3
4
5
6
7
8
9
10
for epoch in range(num_epochs):
for i, (images,labels) in enumerate(train_loader):
out = model(images)
loss = lossFunction(out,labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()

if (i+1) % 100 == 0:
print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}' .format(epoch+1, num_epochs, i+1, total_step, loss.item()))

CIFAR-10数据集测试

​ 相关参数设置

1
2
3
4
5
6
input_size = 32 * 32
output_size = 10
channel_num = 3
num_epochs = 20
batch_size = 128
learning_rate = 0.001

​ 下载数据集并进行读取

1
2
3
4
train_dataset = torchvision.datasets.CIFAR10(root='./data',train=True, transform=transforms.ToTensor(), download=True) 
test_dataset = torchvision.datasets.CIFAR10(root='./data',train=False, transform=transforms.ToTensor(), download=True)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)

​ 损失函数及优化器设计

​ 由于CIFAR-10数据集与MNIST相比更为复杂,因此使用Adam作为优化器,实验证明识别率确实比SGD更高

1
2
lossFunction = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

​ 网络训练部分与MNIST类似