IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> NNDL 实验六 卷积神经网络(3)LeNet实现MNIST -> 正文阅读

[人工智能]NNDL 实验六 卷积神经网络(3)LeNet实现MNIST

文章目录

前言

一、数据集的问题(说了一下两个包细微的区别)

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

5.3.1 数据

5.3.1.1 数据预处理

?5.3.2 模型构建

?5.3.2.2使用pytorch中的相应算子,构建LeNet-5模型

?5.3.2.3? 测试LeNet-5模型,构造一个形状为 [1,1,32,32]的输入数据送入网络,观察每一层特征图的形状变化。

?5.3.2.4测试两个网络的运算速度。

5.3.2.5令两个网络加载同样的权重,测试一下两个网络的输出结果是否一致。?

5.3.2.6统计LeNet-5模型的参数量和计算量。

5.3.3 模型训练

5.3.4 模型评价?

5.3.4 模型评价?

说一下连接表的问题

三、使用前馈神经网络实现MNIST识别,与LeNet效果对比。(选做)?

可视化LeNet中的部分特征图和卷积核,谈谈自己的看法。(选做)

遇到的问题?

总结


前言

? ? ? ?这些还是写的很细,这次真的有点累,我觉得都是值得的,我把可能遇到的问题都写出来了,写的不好,请老师和各位大佬多交交我。

? ? ? ?


一、数据集的问题(说了一下两个包细微的区别)

? ? ? ?首先,咱们先来看一下数据集的问题,对于数据集我研究了好久,一开始以为这是飞浆官方给出一个专门的数据集,因为沐神和各种大佬写着这个都是直接导入,我就去官网的论坛上找了好久发现有人把这个数据发出来了,但是后来老师给我说这个数据并不是飞浆自己弄的。

? ? ? ?先来说一下,官网的数据集,就是真正的minist数据集。

? ? ? ?这个是那几个数据集,前边两个是训练集和训练集的标签,后边两个是验证集和验证集的标签。

? ? ? ?然后就是我找到的那个,我会在这发一下,然后说下那个数据集的情况,我感觉两个数据集还是有点不一样,但是应该是差不多,因为这个是老师测试出来的结果,但是我感觉区别是,它这个压缩包,把我上边全出来的那几个压缩到了一起,所以才能一个包里读出这么多东西,要不然最起码要读取两个包,最好还是四个,所以还是有点区别吧(这是我的感觉,可能是我理解错了,哈哈哈)。

? ? ? ? 下边是那个压缩包的文件,可以看出,这就是一个压缩文件,就是我上边说的神经网络。?

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

LeNet-5虽然提出的时间比较早,但它是一个非常成功的神经网络模型。

基于LeNet-5的手写数字识别系统在20世纪90年代被美国很多银行使用,用来识别支票上面的手写数字。

? ? ? ?这是咱们要是实现的模型,但是刚听了听老师咋讲的,所以咱这个是变化了的LeNet,这个在说完了变化的LeNet之后,我会稍微说说区别以及一些感受。

? ? ? ?好了废话说完了,咱们好好看看这个实现过程。

5.3.1 数据

为了节省训练时间,本节选取MNIST数据集的一个子集进行后续实验,数据集的划分为:

  • 训练集:1,000条样本
  • 验证集:200条样本
  • 测试集:200条样本

MNIST数据集分为train_set、dev_set和test_set三个数据集,每个数据集含两个列表分别存放了图片数据以及标签数据。比如train_set包含:

  • 图片数据:[1 000, 784]的二维列表,包含1 000张图片。每张图片用一个长度为784的向量表示,内容是?28×2828×28?尺寸的像素灰度值(黑白图片)。
  • 标签数据:[1 000, 1]的列表,表示这些图片对应的分类标签,即0~9之间的数字。

观察数据集分布情况,代码实现如下:

# coding=gbk
import json
import gzip


import numpy as np

from PIL import Image
import matplotlib.pyplot as plt
from torchvision.transforms import Compose, Resize, Normalize,ToTensor
import random
import torch.utils.data as data
import torch
import torch.nn.functional as F
import torch.nn as nn
from torch.nn.init import constant_, normal_, uniform_
import time
from torchsummary import summary
from thop import profile
import torch.optim as opt
from nndl import RunnerV3
import metric

?先说一下需要导入的包,记得加上边的编码格式就可以了。

# 打印并观察数据集分布情况
train_set, dev_set, test_set = json.load(gzip.open('./mnist.json.gz'))
train_images, train_labels = train_set[0][:1000], train_set[1][:1000]
dev_images, dev_labels = dev_set[0][:200], dev_set[1][:200]
test_images, test_labels = test_set[0][:200], test_set[1][:200]
train_set, dev_set, test_set = [train_images, train_labels], [dev_images, dev_labels], [test_images, test_labels]
print('Length of train/dev/test set:{}/{}/{}'.format(len(train_set[0]), len(dev_set[0]), len(test_set[0])))

运行结果为:

Length of train/dev/test set:5000/200/200

可视化观察其中的一张样本以及对应的标签,代码如下所示:

image, label = train_set[0][0], train_set[1][0]
image, label = np.array(image).astype('float32'), int(label)
# 原始图像数据为长度784的行向量,需要调整为[28,28]大小的图像
image = np.reshape(image, [28,28])
image = Image.fromarray(image.astype('uint8'), mode='L')
print("The number in the picture is {}".format(label))
plt.figure(figsize=(5, 5))
plt.imshow(image)
plt.savefig('conv-number5.pdf')
plt.show()

?运行结果为:

The number in the picture is 5

5.3.1.1 数据预处理

图像分类网络对输入图片的格式、大小有一定的要求,数据输入模型前,需要对数据进行预处理操作,使图片满足网络训练以及预测的需要。本实验主要应用了如下方法:

  • 调整图片大小:LeNet网络对输入图片大小的要求为?32×3232×32?,而MNIST数据集中的原始图片大小却是?28×2828×28?,这里为了符合网络的结构设计,将其调整为32×3232×32;
  • 规范化: 通过规范化手段,把输入图像的分布改变成均值为0,标准差为1的标准正态分布,使得最优解的寻优过程明显会变得平缓,训练过程更容易收敛。
# 数据预处理
transforms = Compose([Resize(32), ToTensor(),Normalize(mean=[1], std=[1])])

? ? ? ? 这个一定要加上转化为ToTensor(),这个不转化为tensor是无法训练的,并且这的里参数也是有问题的,会导致后边的训练出现问题,这个我在后边会详细说。

将原始的数据集封装为Dataset类,以便DataLoader调用。

# 固定随机种子
random.seed(0)
# 加载 mnist 数据集
train_dataset = MNIST_dataset(dataset=train_set, transforms=transforms, mode='train')
test_dataset = MNIST_dataset(dataset=test_set, transforms=transforms, mode='test')
dev_dataset = MNIST_dataset(dataset=dev_set, transforms=transforms, mode='dev')

?5.3.2 模型构建

LeNet-5虽然提出的时间比较早,但它是一个非常成功的神经网络模型。基于LeNet-5的手写数字识别系统在20世纪90年代被美国很多银行使用,用来识别支票上面的手写数字。LeNet-5的网络结构如图5.13所示。

?我们使用上面定义的卷积层算子和汇聚层算子构建一个LeNet-5模型。

这里的LeNet-5和原始版本有4点不同:

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

网络共有7层,包含3个卷积层、2个汇聚层以及2个全连接层的简单卷积神经网络接,受输入图像大小为32×32=1024,输出对应10个类别的得分。
具体实现如下:

5.3.2.1使用自定义算子,构建LeNet-5模型

class Model_LeNet(nn.Module):
    def __init__(self, in_channels, num_classes=10):
        super(Model_LeNet, self).__init__()
        # 卷积层:输出通道数为6,卷积核大小为5×5
        self.conv1 = Conv2D(in_channels=in_channels, out_channels=6, kernel_size=5)
        # 汇聚层:汇聚窗口为2×2,步长为2
        self.pool2 = Pool2D(size=(2,2), mode='max', stride=2)
        # 卷积层:输入通道数为6,输出通道数为16,卷积核大小为5×5,步长为1
        self.conv3 = Conv2D(in_channels=6, out_channels=16, kernel_size=5, stride=1)
        # 汇聚层:汇聚窗口为2×2,步长为2
        self.pool4 = Pool2D(size=(2,2), mode='avg', stride=2)
        # 卷积层:输入通道数为16,输出通道数为120,卷积核大小为5×5
        self.conv5 = Conv2D(in_channels=16, out_channels=120, kernel_size=5, stride=1)
        # 全连接层:输入神经元为120,输出神经元为84
        self.linear6 = nn.Linear(120, 84)
        # 全连接层:输入神经元为84,输出神经元为类别数
        self.linear7 = nn.Linear(84, num_classes)

    def forward(self, x):
        # C1:卷积层+激活函数
        output = F.relu(self.conv1(x))
        # S2:汇聚层
        output = self.pool2(output)
        # C3:卷积层+激活函数
        output = F.relu(self.conv3(output))
        # S4:汇聚层
        output = self.pool4(output)
        # C5:卷积层+激活函数
        output = F.relu(self.conv5(output))
        # 输入层将数据拉平[B,C,H,W] -> [B,CxHxW]
        output = torch.squeeze(output, dim=3)
        output = torch.squeeze(output, dim=2)
        # F6:全连接层
        output = F.relu(self.linear6(output))
        # F7:全连接层
        output = self.linear7(output)
        return output

这里说一下,这里用到是咱们之前的卷积神经网络改好的算子,也就是下边的这两个

class Conv2D(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
        weight_attr = constant_(torch.empty(size=(out_channels, in_channels, kernel_size, kernel_size)),val=1.0)
        bias_attr = constant_(torch.empty(size=(out_channels,1)), val=0.0)
        super(Conv2D, self).__init__()
        # 创建卷积核
        self.weight = torch.nn.parameter.Parameter(weight_attr,requires_grad=True)
        # 创建偏置
        self.bias = torch.nn.parameter.Parameter(bias_attr,requires_grad=True)
        self.stride = stride
        self.padding = padding
        # 输入通道数
        self.in_channels = in_channels
        # 输出通道数
        self.out_channels = out_channels

    # 基础卷积运算
    def single_forward(self, X, weight):
        # 零填充
        new_X = torch.zeros([X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding])
        new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X
        u, v = weight.shape
        output_w = (new_X.shape[1] - u) // self.stride + 1
        output_h = (new_X.shape[2] - v) // self.stride + 1
        output = torch.zeros([X.shape[0], output_w, output_h])
        for i in range(0, output.shape[1]):
            for j in range(0, output.shape[2]):
                output[:, i, j] = torch.sum(
                    new_X[:, self.stride * i:self.stride * i + u, self.stride * j:self.stride * j + v] * weight,
                    dim=[1, 2])
        return output

    def forward(self, inputs):
        """
        输入:
            - inputs:输入矩阵,shape=[B, D, M, N]
            - weights:P组二维卷积核,shape=[P, D, U, V]
            - bias:P个偏置,shape=[P, 1]
        """
        feature_maps = []
        # 进行多次多输入通道卷积运算
        p = 0
        for w, b in zip(self.weight, self.bias):  # P个(w,b),每次计算一个特征图Zp
            multi_outs = []
            # 循环计算每个输入特征图对应的卷积结果
            for i in range(self.in_channels):
                single = self.single_forward(inputs[:, i, :, :], w[i])
                multi_outs.append(single)
                # print("Conv2D in_channels:",self.in_channels,"i:",i,"single:",single.shape)
            # 将所有卷积结果相加
            feature_map = torch.sum(torch.stack(multi_outs), dim=0) + b  # Zp
            feature_maps.append(feature_map)
            # print("Conv2D out_channels:",self.out_channels, "p:",p,"feature_map:",feature_map.shape)
            p += 1
        # 将所有Zp进行堆叠
        out = torch.stack(feature_maps, 1)
        return out

?上边的是卷积的,后边的池化的

class Pool2D(nn.Module):
    def __init__(self, size=(2, 2), mode='max', stride=1):
        super(Pool2D, self).__init__()
        # 汇聚方式
        self.mode = mode
        self.h, self.w = size
        self.stride = stride

    def forward(self, x):
        output_w = (x.shape[2] - self.w) // self.stride + 1
        output_h = (x.shape[3] - self.h) // self.stride + 1
        output = torch.zeros([x.shape[0], x.shape[1], output_w, output_h])
        # 汇聚
        for i in range(output.shape[2]):
            for j in range(output.shape[3]):
                # 最大汇聚
                if self.mode == 'max':
                    output[:, :, i, j] = torch.max(
                        x[:, :, self.stride * i:self.stride * i + self.w, self.stride * j:self.stride * j + self.h])
                # 平均汇聚
                elif self.mode == 'avg':
                    output[:, :, i, j] = torch.mean(
                        x[:, :, self.stride * i:self.stride * i + self.w, self.stride * j:self.stride * j + self.h],
                        dim=[2, 3])

        return output

?5.3.2.2使用pytorch中的相应算子,构建LeNet-5模型

? ? ? ?考虑到自定义的Conv2DPool2D算子中包含多个for循环,所以运算速度比较慢。飞桨框架中,针对卷积层算子和汇聚层算子进行了速度上的优化,这里基于paddle.nn.Conv2Dpaddle.nn.MaxPool2Dpaddle.nn.AvgPool2D构建LeNet-5模型,对比与上边实现的模型的运算速度。代码实现如下:

class Paddle_LeNet(nn.Module):
    def __init__(self, in_channels, num_classes=10):
        super(Paddle_LeNet, self).__init__()
        # 卷积层:输出通道数为6,卷积核大小为5*5
        self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5)
        # 汇聚层:汇聚窗口为2*2,步长为2
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        # 卷积层:输入通道数为6,输出通道数为16,卷积核大小为5*5
        self.conv3 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        # 汇聚层:汇聚窗口为2*2,步长为2
        self.pool4 = nn.AvgPool2d(kernel_size=2, stride=2)
        # 卷积层:输入通道数为16,输出通道数为120,卷积核大小为5*5
        self.conv5 = nn.Conv2d(in_channels=16, out_channels=120, kernel_size=5)
        # 全连接层:输入神经元为120,输出神经元为84
        self.linear6 = nn.Linear(in_features=120, out_features=84)
        # 全连接层:输入神经元为84,输出神经元为类别数
        self.linear7 = nn.Linear(in_features=84, out_features=num_classes)

    def forward(self, x):
        # C1:卷积层+激活函数
        output = F.relu(self.conv1(x))
        # S2:汇聚层
        output = self.pool2(output)
        # C3:卷积层+激活函数
        output = F.relu(self.conv3(output))
        # S4:汇聚层
        output = self.pool4(output)
        # C5:卷积层+激活函数
        output = F.relu(self.conv5(output))
        # 输入层将数据拉平[B,C,H,W] -> [B,CxHxW]
        output = torch.squeeze(output, dim=3)
        output = torch.squeeze(output, dim=2)
        # F6:全连接层
        output = F.relu(self.linear6(output))
        # F7:全连接层
        output = self.linear7(output)
        return output

?5.3.2.3? 测试LeNet-5模型,构造一个形状为 [1,1,32,32]的输入数据送入网络,观察每一层特征图的形状变化

下面测试一下上面的LeNet-5模型,构造一个形状为 [1,1,32,32]的输入数据送入网络,观察每一层特征图的形状变化。代码实现如下:

# 这里用np.random创建一个随机数组作为输入数据
inputs = np.random.randn(*[1,1,32,32])
inputs = inputs.astype('float32')

# 创建Model_LeNet类的实例,指定模型名称和分类的类别数目
model = Model_LeNet(in_channels=1, num_classes=10)
# 通过调用LeNet从基类继承的sublayers()函数,查看LeNet中所包含的子层
c=[]
for a ,b in model.named_children():
    c.append(a)
print(c)
x = torch.tensor(inputs)
for a,item in model.named_children():
    # item是LeNet类中的一个子层
    # 查看经过子层之后的输出数据形状
    try:
        x = item(x)
    except:
        # 如果是最后一个卷积层输出,需要展平后才可以送入全连接层
        x = torch.reshape(x, [x.shape[0], -1])
        x = item(x)
    d=[]
    e=[]
    for b,c in item.named_parameters():
        d.append(b)
        e.append(c)
    if len(e)==2:
        # 查看卷积和全连接层的数据和参数的形状,
        # 其中item.parameters()[0]是权重参数w,item.parameters()[1]是偏置参数b
        print(a, x.shape, e[0].shape,
                e[1].shape)
    else:
        # 汇聚层没有参数
        print(a, x.shape)

运行结果为:

['conv1', 'pool2', 'conv3', 'pool4', 'conv5', 'linear6', 'linear7']
conv1 torch.Size([1, 6, 28, 28]) torch.Size([6, 1, 5, 5]) torch.Size([6, 1])
pool2 torch.Size([1, 6, 14, 14])
conv3 torch.Size([1, 16, 10, 10]) torch.Size([16, 6, 5, 5]) torch.Size([16, 1])
pool4 torch.Size([1, 16, 5, 5])
conv5 torch.Size([1, 120, 1, 1]) torch.Size([120, 16, 5, 5]) torch.Size([120, 1])
linear6 torch.Size([1, 84]) torch.Size([84, 120]) torch.Size([84])
linear7 torch.Size([1, 10]) torch.Size([10, 84]) torch.Size([10])?

? ? ? 强烈建议大家看一下,我调用的方法,这个是研究了好久以后,才用pytorch重写的paddle方法。

? ? ??大家可以看看,moudle和Layer两个函数的官方文档,直接按照官方文档重写即可,但是真的有点感觉费劲(当然是我菜的原因,哈哈哈)。

从输出结果看:

  • 对于大小为32×32的单通道图像,先用6个大小为5×5的卷积核对其进行卷积运算,输出为6个28×28大小的特征图;
  • 6个28×28大小的特征图经过大小为2×2,步长为2的汇聚层后,输出特征图的大小变为14×14;
  • 6个14×14大小的特征图再经过16个大小为5×5的卷积核对其进行卷积运算,得到16个10×10大小的输出特征图;
  • 16个10×10大小的特征图经过大小为2×2,步长为2的汇聚层后,输出特征图的大小变为5×5;
  • 16个5×5大小的特征图再经过120个大小为5×5的卷积核对其进行卷积运算,得到120个1×1大小的输出特征图;
  • 此时,将特征图展平成1维,则有120个像素点,经过输入神经元个数为120,输出神经元个数为84的全连接层后,输出的长度变为84。
  • 再经过一个全连接层的计算,最终得到了长度为类别数的输出结果。

?5.3.2.4测试两个网络的运算速度。

测试两个网络的运算速度。

# 这里用np.random创建一个随机数组作为测试数据
inputs = np.random.randn(*[1,1,32,32])
inputs = inputs.astype('float32')
x = torch.tensor(inputs)

# 创建Model_LeNet类的实例,指定模型名称和分类的类别数目
model = Model_LeNet(in_channels=1, num_classes=10)
# 创建Paddle_LeNet类的实例,指定模型名称和分类的类别数目
paddle_model = Paddle_LeNet(in_channels=1, num_classes=10)

# 计算Model_LeNet类的运算速度
model_time = 0
for i in range(60):
    strat_time = time.time()
    out = model(x)
    end_time = time.time()
    # 预热10次运算,不计入最终速度统计
    if i < 10:
        continue
    model_time += (end_time - strat_time)
avg_model_time = model_time / 50
print('Model_LeNet speed:', avg_model_time, 's')

# 计算Paddle_LeNet类的运算速度
paddle_model_time = 0
for i in range(60):
    strat_time = time.time()
    paddle_out = paddle_model(x)
    end_time = time.time()
    # 预热10次运算,不计入最终速度统计
    if i < 10:
        continue
    paddle_model_time += (end_time - strat_time)
avg_paddle_model_time = paddle_model_time / 50

print('Paddle_LeNet speed:', avg_paddle_model_time, 's')

运行结果为:

Model_LeNet speed: 0.6000284671783447 s
Paddle_LeNet speed: 0.0004587459564208984 s?

可以发现自己定义的和框架时间差好多,当数据量大了以后,相差将会特别明显。?

5.3.2.5令两个网络加载同样的权重,测试一下两个网络的输出结果是否一致。?

这里还可以令两个网络加载同样的权重,测试一下两个网络的输出结果是否一致。

# 这里用np.random创建一个随机数组作为测试数据
inputs = np.random.randn(*[1,1,32,32])
inputs = inputs.astype('float32')
x = torch.tensor(inputs)

# 创建Model_LeNet类的实例,指定模型名称和分类的类别数目
model = Model_LeNet(in_channels=1, num_classes=10)
# 获取网络的权重
params = model.state_dict()
# 自定义Conv2D算子的bias参数形状为[out_channels, 1]
# paddle API中Conv2D算子的bias参数形状为[out_channels]
# 需要进行调整后才可以赋值
for key in params:
    if 'bias' in key:
        params[key] = params[key].squeeze()
# 创建Paddle_LeNet类的实例,指定模型名称和分类的类别数目
paddle_model = Paddle_LeNet(in_channels=1, num_classes=10)
# 将Model_LeNet的权重参数赋予给Paddle_LeNet模型,保持两者一致
paddle_model.load_state_dict(params)

# 打印结果保留小数点后6位
torch.set_printoptions(6)
# 计算Model_LeNet的结果
output = model(x)
print('Model_LeNet output: ', output)
# 计算Paddle_LeNet的结果
paddle_output = paddle_model(x)
print('Paddle_LeNet output: ', paddle_output)

?运行结果为:

Model_LeNet output: ?tensor([[-64599.476562, -15951.255859, ? 7711.699219, ?32008.751953,
? ? ? ? ?-24956.275391, ?33238.070312, ?38279.644531, ?-4486.134766,
? ? ? ? ?-94658.718750, ?55836.691406]], grad_fn=<AddmmBackward0>)
Paddle_LeNet output: ?tensor([[-64599.414062, -15951.238281, ? 7711.685547, ?32008.726562,
? ? ? ? ?-24956.255859, ?33238.050781, ?38279.613281, ?-4486.125000,
? ? ? ? ?-94658.632812, ?55836.636719]], grad_fn=<AddmmBackward0>)

可以看到,输出结果是一致的。

5.3.2.6统计LeNet-5模型的参数量和计算量。

参数量

按照公式(5.18)进行计算,可以得到:

  • 第一个卷积层的参数量为:6×1×5×5+6=1566×1×5×5+6=156;
  • 第二个卷积层的参数量为:16×6×5×5+16=241616×6×5×5+16=2416;
  • 第三个卷积层的参数量为:120×16×5×5+120=48120120×16×5×5+120=48120;
  • 第一个全连接层的参数量为:120×84+84=10164120×84+84=10164;
  • 第二个全连接层的参数量为:84×10+10=85084×10+10=850;

所以,LeNet-5总的参数量为6170661706。

在torch中,还可以使用torchsummaryAPI自动计算参数量。

device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # PyTorch v0.4.0
model1 = Paddle_LeNet(in_channels=1, num_classes=10).to(device)
summary(model1, (1,32, 32))

运行结果为:

----------------------------------------------------------------
? ? ? ? Layer (type) ? ? ? ? ? ? ? Output Shape ? ? ? ? Param #
================================================================
? ? ? ? ? ? Conv2d-1 ? ? ? ? ? ?[-1, 6, 28, 28] ? ? ? ? ? ? 156
? ? ? ? ?MaxPool2d-2 ? ? ? ? ? ?[-1, 6, 14, 14] ? ? ? ? ? ? ? 0
? ? ? ? ? ? Conv2d-3 ? ? ? ? ? [-1, 16, 10, 10] ? ? ? ? ? 2,416
? ? ? ? ?AvgPool2d-4 ? ? ? ? ? ? [-1, 16, 5, 5] ? ? ? ? ? ? ? 0
? ? ? ? ? ? Conv2d-5 ? ? ? ? ? ?[-1, 120, 1, 1] ? ? ? ? ?48,120
? ? ? ? ? ? Linear-6 ? ? ? ? ? ? ? ? ? [-1, 84] ? ? ? ? ?10,164
? ? ? ? ? ? Linear-7 ? ? ? ? ? ? ? ? ? [-1, 10] ? ? ? ? ? ? 850
================================================================
Total params: 61,706
Trainable params: 61,706
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.06
Params size (MB): 0.24
Estimated Total Size (MB): 0.30
----------------------------------------------------------------

[INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
[INFO] Register zero_ops() for <class 'torch.nn.modules.pooling.MaxPool2d'>.
[INFO] Register count_avgpool() for <class 'torch.nn.modules.pooling.AvgPool2d'>.
[INFO] Register count_linear() for <class 'torch.nn.modules.linear.Linear'>.

这要说一下一个报错也就是

?RuntimeError: Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same

? ? ? ? 这个报错,是因为torchsummary这个函数本身的性质的原因,所以,只需要强制的把它有cuda变量变成没有cuda就好了。

计算量

按照公式(5.19)进行计算,可以得到:

  • 第一个卷积层的计算量为:28×28×5×5×6×1+28×28×6=12230428×28×5×5×6×1+28×28×6=122304;
  • 第二个卷积层的计算量为:10×10×5×5×16×6+10×10×16=24160010×10×5×5×16×6+10×10×16=241600;
  • 第三个卷积层的计算量为:1×1×5×5×120×16+1×1×120=481201×1×5×5×120×16+1×1×120=48120;
  • 平均汇聚层的计算量为:16×5×5=40016×5×5=400
  • 第一个全连接层的计算量为:120×84=10080120×84=10080;
  • 第二个全连接层的计算量为:84×10=84084×10=840;

所以,LeNet-5总的计算量为423344423344。

在torch中,还可以使用thop库API自动统计计算量。

model = Paddle_LeNet(in_channels=1, num_classes=10)
dummy_input = torch.randn(1, 1, 32, 32)
flops, params = profile(model,(dummy_input,))
print(flops)

?运行结果为:

[INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
[INFO] Register zero_ops() for <class 'torch.nn.modules.pooling.MaxPool2d'>.
[INFO] Register count_avgpool() for <class 'torch.nn.modules.pooling.AvgPool2d'>.
[INFO] Register count_linear() for <class 'torch.nn.modules.linear.Linear'>.
416920.0

? ? ? ?这个方法在pytorch中有很多,我查到的一种是感觉比较贴合的一种也就是thop库中的profile函数。?

5.3.3 模型训练

使用交叉熵损失函数,并用随机梯度下降法作为优化器来训练LeNet-5网络。
用RunnerV3在训练集上训练5个epoch,并保存准确率最高的模型作为最佳模型。

torch.manual_seed(100)
# 学习率大小
lr = 0.1
# 批次大小
batch_size = 64
# 加载数据


train_loader = data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = data.DataLoader(dev_dataset, batch_size=batch_size)
test_loader = data.DataLoader(test_dataset, batch_size=batch_size)
# 定义LeNet网络
# 自定义算子实现的LeNet-5
#model = Model_LeNet(in_channels=1, num_classes=10)
# 飞桨API实现的LeNet-5
model = Paddle_LeNet(in_channels=1, num_classes=10)
# 定义优化器
optimizer = opt.SGD(lr=lr, params=model.parameters())
# 定义损失函数
loss_fn = F.cross_entropy
# 定义评价指标
metric = Accuracy(is_logist=True)
# 实例化 RunnerV3 类,并传入训练配置。
runner = RunnerV3(model, optimizer, loss_fn, metric)
# 启动训练
log_steps = 15
eval_steps = 15
runner.train(train_loader, dev_loader, num_epochs=5, log_steps=log_steps,
            eval_steps=eval_steps, save_path="best_model.pdparams")

? ? ? ? 这个还用到了好多之前写过的函数,我就放在下边了,在我前边的博客里也有会有详细的解释,大家感兴趣可以去找一找。

class RunnerV3(object):
    def __init__(self, model, optimizer, loss_fn, metric, **kwargs):
        self.model = model
        self.optimizer = optimizer
        self.loss_fn = loss_fn
        self.metric = metric  # 只用于计算评价指标

        # 记录训练过程中的评价指标变化情况
        self.dev_scores = []

        # 记录训练过程中的损失函数变化情况
        self.train_epoch_losses = []  # 一个epoch记录一次loss
        self.train_step_losses = []  # 一个step记录一次loss
        self.dev_losses = []

        # 记录全局最优指标
        self.best_score = 0

    def train(self, train_loader, dev_loader=None, **kwargs):
        # 将模型切换为训练模式
        self.model.train()

        # 传入训练轮数,如果没有传入值则默认为0
        num_epochs = kwargs.get("num_epochs", 0)
        # 传入log打印频率,如果没有传入值则默认为100
        log_steps = kwargs.get("log_steps", 100)
        # 评价频率
        eval_steps = kwargs.get("eval_steps", 0)

        # 传入模型保存路径,如果没有传入值则默认为"best_model.pdparams"
        save_path = kwargs.get("save_path", "best_model.pdparams")

        custom_print_log = kwargs.get("custom_print_log", None)

        # 训练总的步数
        num_training_steps = num_epochs * len(train_loader)

        if eval_steps:
            if self.metric is None:
                raise RuntimeError('Error: Metric can not be None!')
            if dev_loader is None:
                raise RuntimeError('Error: dev_loader can not be None!')

        # 运行的step数目
        global_step = 0

        # 进行num_epochs轮训练
        for epoch in range(num_epochs):
            # 用于统计训练集的损失
            total_loss = 0
            for step, data in enumerate(train_loader):
                X, y = data
                # 获取模型预测
                logits = self.model(X)
                loss = self.loss_fn(logits, y)  # 默认求mean
                total_loss += loss

                # 训练过程中,每个step的loss进行保存
                self.train_step_losses.append((global_step, loss.item()))

                if log_steps and global_step % log_steps == 0:
                    print(
                        f"[Train] epoch: {epoch}/{num_epochs}, step: {global_step}/{num_training_steps}, loss: {loss.item():.5f}")

                # 梯度反向传播,计算每个参数的梯度值
                loss.backward()

                if custom_print_log:
                    custom_print_log(self)

                # 小批量梯度下降进行参数更新
                self.optimizer.step()
                # 梯度归零
                self.optimizer.zero_grad()

                # 判断是否需要评价
                if eval_steps > 0 and global_step > 0 and \
                        (global_step % eval_steps == 0 or global_step == (num_training_steps - 1)):

                    dev_score, dev_loss = self.evaluate(dev_loader, global_step=global_step)
                    print(f"[Evaluate]  dev score: {dev_score:.5f}, dev loss: {dev_loss:.5f}")

                    # 将模型切换为训练模式
                    self.model.train()

                    # 如果当前指标为最优指标,保存该模型
                    if dev_score > self.best_score:
                        self.save_model(save_path)
                        print(
                            f"[Evaluate] best accuracy performence has been updated: {self.best_score:.5f} --> {dev_score:.5f}")
                        self.best_score = dev_score

                global_step += 1

            # 当前epoch 训练loss累计值
            trn_loss = (total_loss / len(train_loader)).item()
            # epoch粒度的训练loss保存
            self.train_epoch_losses.append(trn_loss)

        print("[Train] Training done!")

    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def evaluate(self, dev_loader, **kwargs):
        assert self.metric is not None

        # 将模型设置为评估模式
        self.model.eval()

        global_step = kwargs.get("global_step", -1)

        # 用于统计训练集的损失
        total_loss = 0

        # 重置评价
        self.metric.reset()

        # 遍历验证集每个批次
        for batch_id, data in enumerate(dev_loader):
            X, y = data

            # 计算模型输出
            logits = self.model(X)

            # 计算损失函数
            loss = self.loss_fn(logits, y).item()
            # 累积损失
            total_loss += loss

            # 累积评价
            self.metric.update(logits, y)

        dev_loss = (total_loss / len(dev_loader))
        dev_score = self.metric.accumulate()

        # 记录验证集loss
        if global_step != -1:
            self.dev_losses.append((global_step, dev_loss))
            self.dev_scores.append(dev_score)

        return dev_score, dev_loss

    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def predict(self, x, **kwargs):
        # 将模型设置为评估模式
        self.model.eval()
        # 运行模型前向计算,得到预测值
        logits = self.model(x)
        return logits

    def save_model(self, save_path):
        torch.save(self.model.state_dict(), save_path)

    def load_model(self, model_path):
        state_dict = torch.load(model_path)
        self.model.load_state_dict(state_dict)
class Accuracy():
    def __init__(self, is_logist=True):
        """
        输入:
           - is_logist: outputs是logist还是激活后的值
        """

        # 用于统计正确的样本个数
        self.num_correct = 0
        # 用于统计样本的总数
        self.num_count = 0

        self.is_logist = is_logist

    def update(self, outputs, labels):
        """
        输入:
           - outputs: 预测值, shape=[N,class_num]
           - labels: 标签值, shape=[N,1]
        """

        # 判断是二分类任务还是多分类任务,shape[1]=1时为二分类任务,shape[1]>1时为多分类任务
        if outputs.shape[1] == 1:  # 二分类
            outputs = torch.squeeze(outputs, dim=-1)
            if self.is_logist:
                # logist判断是否大于0
                preds = torch.tensor((outputs >= 0), dtype=torch.float32)
            else:
                # 如果不是logist,判断每个概率值是否大于0.5,当大于0.5时,类别为1,否则类别为0
                preds = torch.tensor((outputs >= 0.5), dtype=torch.float32)
        else:
            # 多分类时,使用'torch.argmax'计算最大元素索引作为类别
            preds = torch.argmax(outputs, dim=1)

        # 获取本批数据中预测正确的样本个数
        labels = torch.squeeze(labels, dim=-1)
        batch_correct = torch.sum(torch.tensor(preds == labels, dtype=torch.float32)).numpy()
        batch_count = len(labels)

        # 更新num_correct 和 num_count
        self.num_correct += batch_correct
        self.num_count += batch_count

    def accumulate(self):
        # 使用累计的数据,计算总的指标
        if self.num_count == 0:
            return 0
        return self.num_correct / self.num_count

    def reset(self):
        # 重置正确的数目和总数
        self.num_correct = 0
        self.num_count = 0

    def name(self):
        return "Accuracy"

?运行结果为:

[Train] epoch: 0/5, step: 0/395, loss: 2.31706
[Train] epoch: 0/5, step: 15/395, loss: 2.30935
C:/Users/LENOVO/PycharmProjects/pythonProject/深度学习/第二十五个 5.3.py:425: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).
? batch_correct = torch.sum(torch.tensor(preds == labels, dtype=torch.float32)).numpy()
[Evaluate] ?dev score: 0.11500, dev loss: 2.30396
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.11500
[Train] epoch: 0/5, step: 30/395, loss: 2.28952
[Evaluate] ?dev score: 0.11000, dev loss: 2.30277
[Train] epoch: 0/5, step: 45/395, loss: 2.30488
[Evaluate] ?dev score: 0.11500, dev loss: 2.29885
[Train] epoch: 0/5, step: 60/395, loss: 2.27570
[Evaluate] ?dev score: 0.11000, dev loss: 2.29628
[Train] epoch: 0/5, step: 75/395, loss: 2.30163
[Evaluate] ?dev score: 0.11000, dev loss: 2.29252
[Train] epoch: 1/5, step: 90/395, loss: 2.27942
[Evaluate] ?dev score: 0.28000, dev loss: 2.28219
[Evaluate] best accuracy performence has been updated: 0.11500 --> 0.28000
[Train] epoch: 1/5, step: 105/395, loss: 2.22334
[Evaluate] ?dev score: 0.35500, dev loss: 2.23480
[Evaluate] best accuracy performence has been updated: 0.28000 --> 0.35500
[Train] epoch: 1/5, step: 120/395, loss: 1.88724
[Evaluate] ?dev score: 0.47000, dev loss: 1.76655
[Evaluate] best accuracy performence has been updated: 0.35500 --> 0.47000
[Train] epoch: 1/5, step: 135/395, loss: 2.03232
[Evaluate] ?dev score: 0.38500, dev loss: 2.05925
[Train] epoch: 1/5, step: 150/395, loss: 1.33496
[Evaluate] ?dev score: 0.61500, dev loss: 1.15862
[Evaluate] best accuracy performence has been updated: 0.47000 --> 0.61500
[Train] epoch: 2/5, step: 165/395, loss: 0.82530
[Evaluate] ?dev score: 0.62000, dev loss: 0.88475
[Evaluate] best accuracy performence has been updated: 0.61500 --> 0.62000
[Train] epoch: 2/5, step: 180/395, loss: 0.68669
[Evaluate] ?dev score: 0.70500, dev loss: 0.78978
[Evaluate] best accuracy performence has been updated: 0.62000 --> 0.70500
[Train] epoch: 2/5, step: 195/395, loss: 0.43953
[Evaluate] ?dev score: 0.80500, dev loss: 0.46733
[Evaluate] best accuracy performence has been updated: 0.70500 --> 0.80500
[Train] epoch: 2/5, step: 210/395, loss: 0.36928
[Evaluate] ?dev score: 0.74500, dev loss: 0.64256
[Train] epoch: 2/5, step: 225/395, loss: 0.41296
[Evaluate] ?dev score: 0.81000, dev loss: 0.44623
[Evaluate] best accuracy performence has been updated: 0.80500 --> 0.81000
[Train] epoch: 3/5, step: 240/395, loss: 0.60877
[Evaluate] ?dev score: 0.80000, dev loss: 0.47359
[Train] epoch: 3/5, step: 255/395, loss: 0.56669
[Evaluate] ?dev score: 0.88000, dev loss: 0.34526
[Evaluate] best accuracy performence has been updated: 0.81000 --> 0.88000
[Train] epoch: 3/5, step: 270/395, loss: 0.47709
[Evaluate] ?dev score: 0.88000, dev loss: 0.36502
[Train] epoch: 3/5, step: 285/395, loss: 0.31761
[Evaluate] ?dev score: 0.89000, dev loss: 0.31570
[Evaluate] best accuracy performence has been updated: 0.88000 --> 0.89000
[Train] epoch: 3/5, step: 300/395, loss: 0.16157
[Evaluate] ?dev score: 0.88000, dev loss: 0.28939
[Train] epoch: 3/5, step: 315/395, loss: 0.49641
[Evaluate] ?dev score: 0.34500, dev loss: 3.28853
[Train] epoch: 4/5, step: 330/395, loss: 0.83258
[Evaluate] ?dev score: 0.80000, dev loss: 0.57646
[Train] epoch: 4/5, step: 345/395, loss: 0.67352
[Evaluate] ?dev score: 0.72000, dev loss: 0.76016
[Train] epoch: 4/5, step: 360/395, loss: 0.22322
[Evaluate] ?dev score: 0.91000, dev loss: 0.25140
[Evaluate] best accuracy performence has been updated: 0.89000 --> 0.91000
[Train] epoch: 4/5, step: 375/395, loss: 0.12384
[Evaluate] ?dev score: 0.89000, dev loss: 0.26731
[Train] epoch: 4/5, step: 390/395, loss: 0.17907
[Evaluate] ?dev score: 0.92500, dev loss: 0.25235
[Evaluate] best accuracy performence has been updated: 0.91000 --> 0.92500
[Evaluate] ?dev score: 0.92000, dev loss: 0.25603
[Train] Training done!

? ? ? ?这里会有一点问题,在下边我会和大家说一说,也请各位大佬批评指正。

可视化观察训练集与验证集的损失变化情况。

from nndl import plot
plot(runner, 'cnn-loss1.pdf')

关于这个画图的函数,大家直接看下边这个吧。

# 可视化
def plot(runner, fig_name):
    plt.figure(figsize=(10, 5))

    plt.subplot(1, 2, 1)
    train_items = runner.train_step_losses[::30]
    train_steps = [x[0] for x in train_items]
    train_losses = [x[1] for x in train_items]

    plt.plot(train_steps, train_losses, color='#8E004D', label="Train loss")
    if runner.dev_losses[0][0] != -1:
        dev_steps = [x[0] for x in runner.dev_losses]
        dev_losses = [x[1] for x in runner.dev_losses]
        plt.plot(dev_steps, dev_losses, color='#E20079', linestyle='--', label="Dev loss")
    # 绘制坐标轴和图例
    plt.ylabel("loss", fontsize='x-large')
    plt.xlabel("step", fontsize='x-large')
    plt.legend(loc='upper right', fontsize='x-large')

    plt.subplot(1, 2, 2)
    # 绘制评价准确率变化曲线
    if runner.dev_losses[0][0] != -1:
        plt.plot(dev_steps, runner.dev_scores,
                 color='#E20079', linestyle="--", label="Dev accuracy")
    else:
        plt.plot(list(range(len(runner.dev_scores))), runner.dev_scores,
                 color='#E20079', linestyle="--", label="Dev accuracy")
    # 绘制坐标轴和图例
    plt.ylabel("score", fontsize='x-large')
    plt.xlabel("step", fontsize='x-large')
    plt.legend(loc='lower right', fontsize='x-large')

    plt.savefig(fig_name)
    plt.show()

运行结果为:

5.3.4 模型评价?

使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失变化情况。

# 加载最优模型
runner.load_model('best_model.pdparams')
# 模型评价
score, loss = runner.evaluate(test_loader)
print("[Test] accuracy/loss: {:.4f}/{:.4f}".format(score, loss))

运行结果为:

?[Test] accuracy/loss: 0.9000/0.3142

5.3.4 模型评价?

使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失变化情况。

# 获取测试集中第一条数据
X, label = next(iter(test_loader))
logits = runner.predict(X)
# 多分类,使用softmax计算预测概率
pred = F.softmax(logits,dim=1)
# 获取概率最大的类别
pred_class = torch.argmax(pred[2]).numpy()
label = label[2].numpy()
# 输出真实类别与预测类别
print("The true category is {} and the predicted category is {}".format(label, pred_class))
# 可视化图片
plt.figure(figsize=(2, 2))
image, label = test_set[0][2], test_set[1][2]
image= np.array(image).astype('float32')
image = np.reshape(image, [28,28])
image = Image.fromarray(image.astype('uint8'), mode='L')
plt.imshow(image)
plt.savefig('cnn-number2.pdf')

运行结果为:

The true category is 1 and the predicted category is 1

??

说一下连接表的问题

?

连接数:10*10*6*{(3*5*5+1)+6*(4*5*5+1)+3*(4*5*5+1)+1*(6*5*5+1)}=151600

? ? ? ? C3特征图不是直接由S2和16个卷积核卷积运算直接得来的,而是采取特征图组合方式得出的,C3中前6个特征图来自于S2中任意连续的3个特征图作为输入与5* 5 *3大小的卷积核运算得来,S2中连续的3个特征图共有6种组合,所以得出C3中6个特征图则需要6个5* 5* 3的卷积核。C3中后6个特征图来自于S2中任意连续的4个特征图,类似的最终需要6个5 *5 *4大小的卷积核。C3中再后面的3个特征图来自于S2中两两不相邻的4个特征图,共3种组合所以需要3个5* 5 *4大小的卷积核。C3最后的一个特征图来自于S2中所有的特征图,因此需要的1个大小为5* 5* 6的卷积核。S4与S2的下采样方式类似,在C3上选择的单位处理区域大小为2*2,最后得到5* 5*16大小的特征图。

? ? ? ?说明:第一次池化之后是第二次卷积,第二次卷积的输出是C3,16个10x10的特征图,卷积核大小是 5*5. 我们知道S2 有6个 14*14 的特征图,怎么从6 个特征图得到 16个特征图了? 这里是通过对S2 的特征图特殊组合计算得到的16个特征图。具体如下:

?

三、使用前馈神经网络实现MNIST识别,与LeNet效果对比。(选做)?

# coding=gbk
import numpy as np
import torch
import matplotlib.pyplot as plt
from torchvision.datasets import mnist
from torchvision import transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
 
train_batch_size = 64#超参数
test_batch_size = 128#超参数
learning_rate = 0.01#学习率
nums_epoches = 20#训练次数
lr = 0.1#优化器参数
momentum = 0.5#优化器参数
train_dataset = mnist.MNIST('./data', train=True, transform=transforms.ToTensor(), target_transform=None, download=True)
test_dataset = mnist.MNIST('./data', train=False, transform=transforms.ToTensor(), target_transform=None, download=False)
train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
 
class model(nn.Module):
    def __init__(self, in_dim, hidden_1, hidden_2, out_dim):
        super(model, self).__init__()
        self.layer1 = nn.Sequential(nn.Linear(in_dim, hidden_1, bias=True), nn.BatchNorm1d(hidden_1))
        self.layer2 = nn.Sequential(nn.Linear(hidden_1, hidden_2, bias=True), nn.BatchNorm1d(hidden_2))
        self.layer3 = nn.Sequential(nn.Linear(hidden_2, out_dim))
 
    def forward(self, x):
        # 注意 F 与 nn 下的激活函数使用起来不一样的
        x = F.relu(self.layer1(x))
        x = F.relu(self.layer2(x))
        x = self.layer3(x)
        return x
 
#实例化网络
model = model(28*28,300,100,10)
#定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
#momentum:动量因子
optimizer = optim.SGD(model.parameters(),lr=lr,momentum=momentum)
def train():
    # 开始训练 先定义存储损失函数和准确率的数组
    losses = []
    acces = []
    # 测试用
    eval_losses = []
    eval_acces = []
 
    for epoch in range(nums_epoches):
        # 每次训练先清零
        train_loss = 0
        train_acc = 0
        # 将模型设置为训练模式
        model.train()
        # 动态学习率
        if epoch % 5 == 0:
            optimizer.param_groups[0]['lr'] *= 0.1
        for img, label in train_loader:
            # 例如 img=[64,1,28,28] 做完view()后变为[64,1*28*28]=[64,784]
            # 把图片数据格式转换成与网络匹配的格式
            img = img.view(img.size(0), -1)
            # 前向传播,将图片数据传入模型中
            # out输出10维,分别是各数字的概率,即每个类别的得分
            out = model(img)
            # 这里注意参数out是64*10,label是一维的64
            loss = criterion(out, label)
            # 反向传播
            # optimizer.zero_grad()意思是把梯度置零,也就是把loss关于weight的导数变成0
            optimizer.zero_grad()
            loss.backward()
            # 这个方法会更新所有的参数,一旦梯度被如backward()之类的函数计算好后,我们就可以调用这个函数
            optimizer.step()
            # 记录误差
            train_loss += loss.item()
            # 计算分类的准确率,找到概率最大的下标
            _, pred = out.max(1)
            num_correct = (pred == label).sum().item()  # 记录标签正确的个数
            acc = num_correct / img.shape[0]
            train_acc += acc
        losses.append(train_loss / len(train_loader))
        acces.append(train_acc / len(train_loader))
 
        eval_loss = 0
        eval_acc = 0
        model.eval()
        for img, label in test_loader:
            img = img.view(img.size(0), -1)
 
            out = model(img)
            loss = criterion(out, label)
 
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
 
            eval_loss += loss.item()
 
            _, pred = out.max(1)
            num_correct = (pred == label).sum().item()
            acc = num_correct / img.shape[0]
            eval_acc += acc
        eval_losses.append(eval_loss / len(test_loader))
        eval_acces.append(eval_acc / len(test_loader))
 
        print('epoch:{},Train Loss:{:.4f},Train Acc:{:.4f},Test Loss:{:.4f},Test Acc:{:.4f}'
              .format(epoch, train_loss / len(train_loader), train_acc / len(train_loader),
                      eval_loss / len(test_loader), eval_acc / len(test_loader)))
    plt.title('trainloss')
    plt.plot(np.arange(len(losses)), losses)
    plt.legend(['Train Loss'], loc='upper right')
#测试
from sklearn.metrics import confusion_matrix
import seaborn as sns
def test():
    correct = 0
    total = 0
    y_predict=[]
    y_true=[]
    with torch.no_grad():
        for data in test_loader:
            input, target = data
            input = input.view(input.size(0), -1)
            output = model(input)#输出十个最大值
            _, predict = torch.max(output.data, dim=1)#元组取最大值的下表
            #
            #print('predict:',predict)
            total += target.size(0)
            correct += (predict == target).sum().item()
            y_predict.extend(predict.tolist())
            y_true.extend(target.tolist())
    print('正确率:', correct / total)
    print('correct=', correct)
    sns.set()
    f, ax = plt.subplots()
    C2 = confusion_matrix(y_true, y_predict, labels=[0, 1, 2,3,4,5,6,7,8,9])
    print(C2)
    plt.imshow(C2, cmap=plt.cm.Blues)
    plt.xticks(range(10),labels=[0, 1, 2,3,4,5,6,7,8,9] , rotation=45)
    plt.yticks(range(10),labels=[0, 1, 2,3,4,5,6,7,8,9])
    plt.colorbar()
    plt.xlabel('True Labels')
    plt.ylabel('Predicted Labels')
    plt.title('Confusion matrix (acc=' + str(correct / total)+ ')')
 
    plt.show()
 
train()
test()

运行结果为:

epoch:0,Train Loss:0.3542,Train Acc:0.9152,Test Loss:0.1281,Test Acc:0.9617
epoch:1,Train Loss:0.1271,Train Acc:0.9665,Test Loss:0.0822,Test Acc:0.9767
epoch:2,Train Loss:0.0865,Train Acc:0.9769,Test Loss:0.0619,Test Acc:0.9825
epoch:3,Train Loss:0.0646,Train Acc:0.9825,Test Loss:0.0504,Test Acc:0.9857
epoch:4,Train Loss:0.0536,Train Acc:0.9853,Test Loss:0.0423,Test Acc:0.9891
epoch:5,Train Loss:0.0362,Train Acc:0.9912,Test Loss:0.0265,Test Acc:0.9940
epoch:6,Train Loss:0.0328,Train Acc:0.9924,Test Loss:0.0269,Test Acc:0.9940
epoch:7,Train Loss:0.0312,Train Acc:0.9935,Test Loss:0.0258,Test Acc:0.9941
epoch:8,Train Loss:0.0306,Train Acc:0.9930,Test Loss:0.0258,Test Acc:0.9937
epoch:9,Train Loss:0.0293,Train Acc:0.9938,Test Loss:0.0251,Test Acc:0.9941
epoch:10,Train Loss:0.0276,Train Acc:0.9943,Test Loss:0.0249,Test Acc:0.9944
epoch:11,Train Loss:0.0277,Train Acc:0.9944,Test Loss:0.0242,Test Acc:0.9941
epoch:12,Train Loss:0.0277,Train Acc:0.9945,Test Loss:0.0243,Test Acc:0.9948
epoch:13,Train Loss:0.0277,Train Acc:0.9943,Test Loss:0.0241,Test Acc:0.9947
epoch:14,Train Loss:0.0280,Train Acc:0.9941,Test Loss:0.0237,Test Acc:0.9946
epoch:15,Train Loss:0.0275,Train Acc:0.9945,Test Loss:0.0242,Test Acc:0.9952
epoch:16,Train Loss:0.0278,Train Acc:0.9942,Test Loss:0.0242,Test Acc:0.9950
epoch:17,Train Loss:0.0272,Train Acc:0.9945,Test Loss:0.0248,Test Acc:0.9944
epoch:18,Train Loss:0.0274,Train Acc:0.9946,Test Loss:0.0236,Test Acc:0.9947
epoch:19,Train Loss:0.0273,Train Acc:0.9950,Test Loss:0.0240,Test Acc:0.9946
正确率: 0.9946
correct= 9946

? ? ? ?设置一个time函数,即可比较出两个的区别,会发现卷积神经网络会快很多。

? ? ? ?并且需要的轮数也会较多,需要的数据上也是较多的,所以直接就可以提现出卷积神经网络的优越性。

可视化LeNet中的部分特征图和卷积核,谈谈自己的看法。(选做)

?首先,先看代码,再说感受。

class Paddle_LeNet1(nn.Module):
    def __init__(self, in_channels, num_classes=10):
        super(Paddle_LeNet1, self).__init__()
        # 卷积层:输出通道数为6,卷积核大小为5*5
        self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5)
        # 汇聚层:汇聚窗口为2*2,步长为2
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        # 卷积层:输入通道数为6,输出通道数为16,卷积核大小为5*5
        self.conv3 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        # 汇聚层:汇聚窗口为2*2,步长为2
        self.pool4 = nn.AvgPool2d(kernel_size=2, stride=2)
        # 卷积层:输入通道数为16,输出通道数为120,卷积核大小为5*5
        self.conv5 = nn.Conv2d(in_channels=16, out_channels=120, kernel_size=5)
        # 全连接层:输入神经元为120,输出神经元为84
        self.linear6 = nn.Linear(in_features=120, out_features=84)
        # 全连接层:输入神经元为84,输出神经元为类别数
        self.linear7 = nn.Linear(in_features=84, out_features=num_classes)

    def forward(self, x):
        image=[]
        # C1:卷积层+激活函数
        output = F.relu(self.conv1(x))
        image.append(output)
        # S2:汇聚层
        output = self.pool2(output)

        # C3:卷积层+激活函数
        output = F.relu(self.conv3(output))
        image.append(output)
        # S4:汇聚层
        output = self.pool4(output)
        # C5:卷积层+激活函数
        output = F.relu(self.conv5(output))
        image.append(output)
        # 输入层将数据拉平[B,C,H,W] -> [B,CxHxW]
        output = torch.squeeze(output, dim=3)
        output = torch.squeeze(output, dim=2)
        # F6:全连接层
        output = F.relu(self.linear6(output))
        # F7:全连接层
        output = self.linear7(output)
        return image





# create model
model1 = Paddle_LeNet1(in_channels=1, num_classes=10)


# model_weight_path ="./AlexNet.pth"
model_weight_path = 'best_model.pdparams'
model1.load_state_dict(torch.load(model_weight_path))


# forward正向传播过程
out_put = model1(X)
print(out_put[0].shape)
for i in range(0,3):
 for feature_map in out_put[i]:
    # [N, C, H, W] -> [C, H, W]    维度变换
    im = np.squeeze(feature_map.detach().numpy())
    # [C, H, W] -> [H, W, C]
    im = np.transpose(im, [1, 2, 0])
    print(im.shape)

    # show 9 feature maps
    plt.figure()
    for i in range(6):
        ax = plt.subplot(2, 3, i + 1)  # 参数意义:3:图片绘制行数,5:绘制图片列数,i+1:图的索引
        # [H, W, C]
        # 特征矩阵每一个channel对应的是一个二维的特征矩阵,就像灰度图像一样,channel=1
        # plt.imshow(im[:, :, i])i,,
        plt.imshow(im[:, :, i], cmap='gray')
    plt.show()
    break

?运行结果为:

? ? ? ?这个代码的大致思想来自于之前的XO识别中的代码,大家感兴趣的可以看看,那个博客中写的很详细。? ?

? ? ? ?首先,说一说我的看法,也是我听老师上课讲的之后的想法。

? ? ? ?首先,前边的卷积核卷出来可能就是就是一些点、线、边,这根本看不出来啥,但是中间的卷积核可能就的就是这些点、线、边的组合了,会组合出一些小的特征,但是最后的卷积核就会是这些小得多特征的组合了,这个就能看出图像了。

? ? ? ?就像上边的卷积核,一开始看不出来啥,但是最后的卷积核已经基本能看出来这是一个7了。

?

? ? ? ? 这个图大概就是能最解释得了,后来看了看老师发的神书鱼书上的东西个感觉真的有明白了不少,下边放一下,鱼书真的太厉害了,解释的太详细了,拜一拜。

?

?

详解深度学习之经典网络架构(一):LeNet_chenyuping666的博客-CSDN博客

遇到的问题?

这里说两个问题,一个是模型参数的问题,一个是模型的训练的问题

首先说一下,参数的问题。

# 打印并观察数据集分布情况
train_set, dev_set, test_set = json.load(gzip.open('mnist.json.gz'))
train_images, train_labels = train_set[0][:5000], train_set[1][:5000]
dev_images, dev_labels = dev_set[0][:200], dev_set[1][:200]
test_images, test_labels = test_set[0][:200], test_set[1][:200]
train_set, dev_set, test_set = [train_images, train_labels], [dev_images, dev_labels], [test_images, test_labels]
print('Length of train/dev/test set:{}/{}/{}'.format(len(train_set[0]), len(dev_set[0]), len(test_set[0])))
# 数据预处理
transforms = Compose([Resize(32), ToTensor(),Normalize(mean=[1], std=[1])])
torch.manual_seed(100)
# 学习率大小
lr = 0.1
# 批次大小
batch_size = 64
# 加载数据


train_loader = data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = data.DataLoader(dev_dataset, batch_size=batch_size)
test_loader = data.DataLoader(test_dataset, batch_size=batch_size)
# 定义LeNet网络
# 自定义算子实现的LeNet-5
#model = Model_LeNet(in_channels=1, num_classes=10)
# 飞桨API实现的LeNet-5
model = Paddle_LeNet(in_channels=1, num_classes=10)
# 定义优化器
optimizer = opt.SGD(lr=lr, params=model.parameters())
# 定义损失函数
loss_fn = F.cross_entropy
# 定义评价指标
metric = Accuracy(is_logist=True)
# 实例化 RunnerV3 类,并传入训练配置。
runner = RunnerV3(model, optimizer, loss_fn, metric)
# 启动训练
log_steps = 15
eval_steps = 15
runner.train(train_loader, dev_loader, num_epochs=5, log_steps=log_steps,
            eval_steps=eval_steps, save_path="best_model.pdparams")

? ? ? ?上边这三个是模型的超参数,如果调不好,会导致模型训练的时候正确率极低,并且以后会导致损失几乎没有变化,按我上边的参数即可。

? ? ? ?说一下影响,训练集最好不要少于2000,因为太小,会导致各个数字出现的次数不够,根本无法完成训练的要求,会导致正确率极低。

? ? ? ? 上边的归一化,不要按一开始模型中的,一开始的数太大,过于离散会让模型中的损失过大。

? ? ? ? 最后是学习率,学习率最好不要大于1,小于0.001,过大和过小都会无法优化出极小点。

第二个问题是模型训练的问题

import paddle.optimizer as opt
from nndl import RunnerV3, metric

paddle.seed(100)
# 学习率大小
lr = 0.1    
# 批次大小
batch_size = 64
# 加载数据
train_loader = io.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = io.DataLoader(dev_dataset, batch_size=batch_size)
test_loader = io.DataLoader(test_dataset, batch_size=batch_size)
# 定义LeNet网络
# 自定义算子实现的LeNet-5
model = Model_LeNet(in_channels=1, num_classes=10)
# 飞桨API实现的LeNet-5
# model = Paddle_LeNet(in_channels=1, num_classes=10)
# 定义优化器
optimizer = opt.SGD(learning_rate=lr, parameters=model.parameters())
# 定义损失函数
loss_fn = F.cross_entropy
# 定义评价指标
metric = metric.Accuracy(is_logist=True)
# 实例化 RunnerV3 类,并传入训练配置。
runner = RunnerV3(model, optimizer, loss_fn, metric)
# 启动训练
log_steps = 15
eval_steps = 15
runner.train(train_loader, dev_loader, num_epochs=5, log_steps=log_steps, 
                eval_steps=eval_steps, save_path="best_model.pdparams")          

? ? ? ?大家,看上边的训练代码,是无法训练出结果的,因为损失几乎不变,我再长时间的debug之后发现,这时候因为一开始用的是自定义的模型,而自定义的模型中的算子是没有定义反向传播的,这会导致参数无法更新,所以损失会几乎不变。

? ? ? ?大家只要用框架训练就会得出正确结果。


总结

? ? ? 首先,这次交的比以往交早,以后争取,并且这次的这个真累,研究了好几天,重写输出各层的函数,写了一会,研究各个参数研究了1天,研究为啥损失没变化研究了一会,真的累,但是感觉是值得的(当然是因为我菜,哈哈哈)。

? ? ? ?其次,深入的研究了一下反向传播,在debug的时候,深入的研究了一下反向传播,然后正好发现问题之后,刚好谢上一个作业,正好手写反向传播试了一下。

? ? ? 其次,有学习了一下调参,感觉开始向着调参侠转变了,这次是真的研究了好久这个参数(哈哈哈)。

? ? ? 其次,终于稍微研究了研究含义,不像之前写的时候一样,就是瞎跑模型啥也不懂了。

? ? ? 其次,感觉自己真的啥也不懂,我还是要学呀(我菜的很呀,哈哈哈),也请老师,和各位大佬多教教我。

? ? ? 最后,当然是谢谢老师,谢谢老师在学习和生活上的关心。

? ??

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:28:48  更:2022-11-05 00:29:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:28:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码