[2022-10-31]神经网络与深度学习第4章 - 卷积神经网络(part 3)

contents

  • 神经网络与深度学习第4章 - 卷积神经网络(part 3)
    • 写在开头
    • 基于LeNet实现手写体数字识别实验
      • MNIST数据集
      • 模型构建
      • 模型参数量
      • 模型计算量
      • 模型训练、模型评价
    • 模型测试
    • 一些小题
    • 写在最后

神经网络与深度学习第4章 - 卷积神经网络(part 3)

写在开头

前面几章,我们制造了很多轮子,也已经对卷积神经网络有一个较为透彻的理解。卷积神经网络的理论还是需要放在实际应用中来服务于我们。本次实验我们将实现一个经典的卷积神经网络结构——LeNet,并用其在MNIST数据集上的训练和识别进行体验和研究。

基于LeNet实现手写体数字识别实验

MNIST数据集

MNIST数据集在很早之前就已经介绍过了,这边只讲方便地使用该数据集。
[2022-10-31]神经网络与深度学习第4章 - 卷积神经网络(part 3)_第1张图片
我们当然可以通过官网下载得到这个数据集。但是作为经典的数据集,在torchvision的数据集中就已经被包含了。因此,要使用MNIST数据集,我们可以直接调用torchvision中带自动下载的MNIST数据集的DataLoader:

import os
import numpy as np
import torch
from torchvision.datasets import mnist

dataset_train = mnist.MNIST('./mnist',
    download=False if os.path.exists('./mnist/MNIST') else True, # 避免重复下载
    train=True
)
dataset_test = mnist.MNIST('./mnist',
    download=False,
    train=False
)

print('train size:%s'%(','.join(torch.tensor(dataset_train.data.shape).numpy().astype(np.str_))))
print('test size:%s'%(','.join(torch.tensor(dataset_test.data.shape).numpy().astype(np.str_))))

输出如下,这里已经下载完毕,如果没有下载会有下载内容的输出:
在这里插入图片描述
然后对数据集进行加载,这边我们使用torch自带的DataLoader。

from torch.utils.data import DataLoader
from torch.nn.functional import one_hot

import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline

dataset_train.targets = one_hot(dataset_train.targets)
dataset_test.targets = one_hot(dataset_test.targets)

dataloader_train = DataLoader(dataset_train, 8, True)
dataloader_test = DataLoader(dataset_test, 8, False)
fig, *ax = plt.subplots(2,4)
for i in range(8):
    ax[0][i//4][i%4].set_xticks([])
    ax[0][i//4][i%4].set_yticks([])
    ax[0][i//4][i%4].imshow(dataset_train.data[i],cmap='gray')

显示一个批中的八张数字图片:
[2022-10-31]神经网络与深度学习第4章 - 卷积神经网络(part 3)_第2张图片
我们发现,图像大小为28×28,然而我们后面使用的LeNet - 5 模型输入模型大小为32×32。这时,就需要我们使用PIL中的图像大小变换函数。我们在创建DataLoader前插入如下代码:

import torchvision.transforms as transforms
dataset_train.data = transforms.Resize((32,32),transforms.InterpolationMode.BICUBIC)(dataset_train.data)
dataset_test.data = transforms.Resize((32,32),transforms.InterpolationMode.BICUBIC)(dataset_test.data)

我们再输出一下,可以发现图像大小均变为32×32.这边使用双三次插值是因为我觉得这个缩放方法听起来很牛。
在这里插入图片描述

模型构建

自定义算子计算方法为矩阵运算,效率仅仅受到python这一解释型语言的速度限制:

model = [
	Conv2D(in_channels, 6, (5, 5), padding='valid'),
	ReLU(),
	MaxPool2D((2,2), 2),
	Conv2D(6, 16, (5, 5), padding='valid'),
	ReLU(),
	MaxPool2D((2,2), 2),
	Conv2D(16, 120, (5, 5), padding='valid'),
	ReLU(),
	View(-1),
	Linear(120, 84),
	ReLU(),
	Linear(84, o_classes)
]

模型结构可视化图像如下:
[2022-10-31]神经网络与深度学习第4章 - 卷积神经网络(part 3)_第3张图片
我们的模型构建使用LeNet-5的在原始版本上有所变化的模型结构:

  • C3层没有使用连接表来减少卷积数量。
  • 汇聚层使用了简单的平均汇聚,没有引入权重和偏置参数以及非线性激活函数。
  • 卷积层的激活函数使用ReLU函数。
  • 最后的输出层为一个全连接线性层。

可以非常方便地得到模型构建代码。这边由于可能有多个通道,我们使用之前介绍过的LazyConv2d来自适应地获得输入通道:

class LeNet(torch.nn.Module):
    def __init__(self, o_classes=10):
        super(LeNet, self).__init__()
        self.conv1 = torch.nn.Sequential(
            torch.nn.LazyConv2d(6, (5, 5)),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d((2, 2), 2)
        )
        self.conv2 = torch.nn.Sequential(
            torch.nn.Conv2d(6, 16, (5, 5)),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d((2, 2), 2)
        )
        self.conv3 = torch.nn.Sequential(
            torch.nn.Conv2d(16, 120, (5, 5)),
            torch.nn.ReLU()
        )
        self.linear1 = torch.nn.Linear(120, 84)
        self.activa1 = torch.nn.ReLU()
        self.linear2 = torch.nn.Linear(84, o_classes)

    def forward(self, x):
        x = self.conv3(self.conv2(self.conv1(x)))
        x = x.view(x.shape[0], -1)
        return self.linear2(self.activa1(self.linear1(x)))

模型参数量

pytorch中本身并不包含对于参数的summary功能,但是我们使用第三方库torchsummary则可以得到这些数据:

from torchsummary import summary
model = model()
params_info = summary(model, (1, 32, 32))
print(params_info)

[2022-10-31]神经网络与深度学习第4章 - 卷积神经网络(part 3)_第4张图片

模型计算量

计算参数量这一操作在pytorch中也没有。我们使用torchstat来进行输出:

from torchstat import stat
print(stat(model, (1, 32,32)))

[2022-10-31]神经网络与深度学习第4章 - 卷积神经网络(part 3)_第5张图片

模型训练、模型评价

模型训练和之前的模型没有任何区别,构建完损失函数(CrossEntropy)、优化器(SGD)后,我们使用runner类进行训练。

import runner

runner = runner.Runner(LeNet, torch.nn.CrossEntropyLoss, torch.optim.SGD, lr=0.01)
runner.train(xs_train, ys_train, 100, None, None, 10, mertics=[{'accuracy' : calc_acc}])
runner.eval(xs_test, ys_test)

由于量大,这次的训练是一个非常非常非常缓慢的过程。在经过漫长的等待之后,我们得到如下输出:
[2022-10-31]神经网络与深度学习第4章 - 卷积神经网络(part 3)_第6张图片
在这里插入图片描述

模型测试

我们通过cv2读入摄像头进行手写数字的测试:

import cv2 
from PIL import Image
import numpy as np
cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)
FPS = 24
cap.set(cv2.CAP_PROP_FPS, FPS)


while True:
    ret, frame = cap.read()
    if not ret:
        print("Can't receive frame (stream end?). Exiting ...")
        break

    frame = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    
    frame = Image.fromarray(frame)
    frame = frame.resize((28,28),Image.BICUBIC)
    frame = np.asarray(frame)
    img2=np.zeros((28,28,1),dtype='uint8')
    frame=cv2.addWeighted(frame,-1,img2,0,255,0)

    print(datetime.datetime.now(),torch.argmax(model(torch.tensor([[frame / 255.]],dtype=torch.float))),end='\r')

    cv2.namedWindow('frame', cv2.WND_PROP_FULLSCREEN)
    cv2.imshow('frame', frame)
    if cv2.waitKey(1) == ord('q'):  break
cap.release()
cv2.destroyAllWindows()

手写一个数字8进行测试:
[2022-10-31]神经网络与深度学习第4章 - 卷积神经网络(part 3)_第7张图片

一些小题

  • 使用前馈神经网络实现MNIST识别,与LeNet效果对比

前面已经完成了使用前馈神经网络实现MNIST手写数字识别的实验(传送门),和LeNet相比:

  • 1.速度慢了很多:原因分析为FNN中为全连接,参数量大,因此计算量更大,速度会慢很多;
  • 2.训练完成FNN准确率更高,但是实际测试效果不好:分析原因为FNN尽管全连接,但是正是因为全连接,所以导致模型容易过拟合,从而只能在测试集上看似很准确。但是减少训练次数会导致欠拟合,调参过于困难。

写在最后

本次实验,我们实现了最为经典的一个卷积神经网络:LeNet。通过实际应用,我领略到了神经网络在特征提取和分类等操作上的强大力量。同时,我发现在模型训练的过程中还是容易出现过拟合的情况。这时我们可能就会用到Dropout层来进行优化。
另外,这边还找到一个网页,在里面你可以自定义网络,并将所有的卷积核和特征图可视化。网站还包含训练过程的可视化,最终效果如下:
[2022-10-31]神经网络与深度学习第4章 - 卷积神经网络(part 3)_第8张图片

你可能感兴趣的:([DL]神经网络与深度学习,深度学习,神经网络,cnn)