ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

深度学习之卷积神经网络(4)LeNet-5实战

2021-10-02 11:01:07  阅读:273  来源: 互联网

标签:layers 10 卷积 28 test 神经网络 LeNet tf


深度学习之卷积神经网络(4)LeNet-5实战


 1990年代,Yann LeCun等人提出了用于手写数字和机器打印字符图片识别的神经网络,被命名为LetNet-5 [1]。LetNet-5的提出,使得卷积神经网络在当时能够成功被商用,广泛应用在邮政编码、支票号码识别任务中。下图是LetNet-5的网络结构图,它接受 32 × 32 32×32 32×32大小的数字、字符图片,经过第一个卷积层得到 [ b , 28 , 28 , 6 ] [b,28,28,6] [b,28,28,6]形状的张量,经过一个向下采样层,张量尺寸缩小到 [ b , 14 , 14 , 6 ] [b,14,14,6] [b,14,14,6],经过第二个卷积层,得到 [ b , 10 , 10 , 16 ] [b,10,10,16] [b,10,10,16]形状的张量,同样经过下采样层,张量尺寸缩小到 [ b , 5 , 5 , 16 ] [b,5,5,16] [b,5,5,16],在经过全连接层之前,先将张量打成 [ b , 400 ] [b,400] [b,400]的张量,送入输出节点数分别为120、84的两个全连接层,得到 [ b , 84 ] [b,84] [b,84]的张量,最后通过Gaussian connection层。

[1] Y. Lecun, L. Bottou, Y. Bengio 和 P. Haffner, “Gradient-based learning applied to document recognition,” 出处 Proceedings of the IEEE, 1998.

在这里插入图片描述

LeNet-5网络结构


 现在看来,LeNet-5网络层数较少(2个卷积层和2个全连接层),参数量较少,计算代价较低,尤其在现代GPU的加持下,数分钟即可训练好LeNet-5网络。

 我们在LeNet-5的基础上进行了少许调整,使得它更容易在现代深度学习框架上实现。首先我们将输入 X \boldsymbol X X形状由 32 × 32 32×32 32×32调整为 28 × 28 28×28 28×28,然后将2个下采样层实现为最大池化层(降低特征图的高、宽,后面会介绍),最后利用全连接层替换掉Gaussian connection层。下文统一称修改的网络也为LeNet-5网络。网络结构图如图所示:

在这里插入图片描述

手写数字图片识别模型结构

加载数据集


 我们基于MNIST手写数字图片数据集训练LeNet-5网络,并测试其最终准确度。前面已经介绍了如何在TensorFlow中加载MNIST数据集。详见深度学习(18)神经网络与全连接层一: 数据加载

# 加载MNIST数据集
(x, y), (x_test, y_test) = keras.datasets.mnist.load_data()
# 转换数据类型
# x: [0~255] => [0~1.]
x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.
y = tf.convert_to_tensor(y, dtype=tf.int32)
x_test = tf.convert_to_tensor(x_test, dtype=tf.float32) / 255.
y_test = tf.convert_to_tensor(y_test, dtype=tf.int32)
# 创建数据集
train_db = tf.data.Dataset.from_tensor_slices((x, y)).batch(128)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(128)

创建网络


 首先通过Sequential容器创建LeNet-5,代码如下:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets


network = Sequential([  # 网络容器
    layers.Conv2D(6, kernel_size=3, strides=1),  # 第一个卷积层,6个3×3卷积核
    layers.MaxPooling2D(pool_size=2, strides=2),  # 高宽各减半的池化层
    layers.ReLU(),  # 激活函数
    layers.Conv2D(16, kernel_size=2, strides=1),  # 第二个卷积层,16个3×3卷积核
    layers.MaxPooling2D(pool_size=2, strides=2),  # 高宽各减半的池化层
    layers.ReLU(),  # 激活函数
    layers.Flatten(),  # 打平层,方便全连接层处理

    layers.Dense(120, activation='relu'),  # 全连接层,120个节点
    layers.Dense(84, activation='relu'),  # 全连接层,84个节点
    layers.Dense(10),  # 全连接层,10个节点
])

# build一次网格模型,给输入x的形状,其中4为随意给的batchsize
network.build(input_shape=(4, 28, 28, 1))
# 统计网格信息
network.summary()


运行结果如下图所示:

在这里插入图片描述


 通过summary()函数统计出每层的参数量,打印出网格结构信息和每层参数数量详情,如下表所示,我们可以与全连接网络的参数量表进行比较。

网络参数量统计表
卷积层1卷积层2全连接层1全连接层2全连接层3
参数量608804812010164850


可以看到,卷积层的参数量非常少,主要的参数量集中在全连接层。由于卷积层将输入层特征维度降低很多,从而使得全连接层的参数量不至于过大,整个模型的参数量约60K,而全连接网络参数量达到了34万个,因此通过卷积神经网络可以显著降低网络参数量,同时增加网络深度。

训练阶段


 在训练阶段,首先将数据集中shape为 [ b , 28 , 28 ] [b,28,28] [b,28,28]的输入 X \boldsymbol X X增加一个维度,调整shape为 [ b , 28 , 28 , 1 ] [b,28,28,1] [b,28,28,1],送入模型进行前向计算,得到输出张量output,shape为 [ b , 10 ] [b,10] [b,10]。我们新建交叉熵损失函数类(没错,损失函数也能使用类方式)用于处理分类任务,通过设定from_logits=True标志位将softmax激活函数实现在损失函数中,不需要手动添加损失函数,提升数值计算稳定性。代码如下:

from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
# 创建损失函数的类,在实际计算时直接调用实例即可
criteon = losses.CategoricalCrossentropy(from_logits=True)


训练部分实现如下:

# 训练部分实现如下
# 构建梯度记录环境
with tf.GradientTape as tape:
    # 插入通道维度,=>[b,28,28,1]
    x = tf.expand_dims(x, axis=3)
    # 向前计算,获得10类别的概率分布,[b,784] => [b,10]
    out = network(x)
    # 真实标签one-hot编码,[b] => [b,10]
    y_onehot = tf.one_hot(y, depth=10)
    # 计算交叉熵损失函数,标量
    loss = criteon(y_onehot, out)


获得损失值后,通过TensorFlow的梯度记录器tf.GradientTape()来计算损失函数loss对网络参数network.trainable_variables之间的梯度,并通过optimizer对象自动更新网络权值参数。代码如下:

# 自动计算梯度
grads = tape.gradient(loss, network.trainable_variables)
# 自动更新参数
optimizer.apply_gradients(zip(grads, network.trainable_variables))


重复上述步骤若干次后即可完成训练工作

测试阶段


 在测试阶段,由于不需要记录梯度信息,代码一般不需要写在with tf.GradientTape() as tape环境中。前向计算得到的输出经过softmax函数后,代表了网络预测当前图片输入 x \boldsymbol x x属于类别i的概率 P ( x 标 签 是 i │ x ) , i ∈ [ 0 , 9 ] P(x标签是i│x),i∈[0,9] P(x标签是i│x),i∈[0,9]。通过argmax函数选取概率最大的元素所在的索引,作为当前x的预测类别,与真实标注y比较,通过计算比较结果中间True的数量并求和来统计预测正确的样本的个数,最后除以总样本的个数,得出网络的测试准确度。代码如下:

# 记录预测正确的数量,总样本数量
correct, total = 0, 0
for x, y in test_db:  # 遍历所有训练集样本
    # 插入通道维度,=>[b,28,28,1]
    x = tf.expand_dims(x, axis=3)
    # 向前计算,获得10类别的概率分布,[b,784] => [b,10]
    out = network(x)
    # 真实的流程时先经过softmax,再argmax
    # 但是由于softmax不改变元素的大小相对关系,故省去
    pred = tf.argmax(out,axis=-1)
    y = tf.cast(y, tf.int64)
    # 统计预测样本总数
    correct += float(tf.reduce_sum(tf.cast(tf.equal(pred, y), tf.float32)))
    # 统计预测样本总数
    total += x.shape[0]
# 计算准确率
print('test acc:', correct/total)


 在数据集上面循环训练30个Epoch后,网络的训练准确度达到了98.1%,测试准确度也达到了97.7%。对于非常简单的手写数字识别任务,古老的LeNet-5网络已经可以取得很好地效果,但是稍微复杂一点的任务,比如色彩动物图片识别,LeNet-5性能就会急剧下降。

完整代码

import os

from Chapter08 import metrics
from Chapter08.metrics import loss_meter

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'


import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets

# 加载MNIST数据集
def preprocess(x, y):
    # 预处理函数
    x = tf.cast(x, dtype=tf.float32) / 255
    y = tf.cast(y, dtype=tf.int32)

    return x, y


# 加载MNIST数据集
(x, y), (x_test, y_test) = keras.datasets.mnist.load_data()
# 创建数据集
batchsz = 128
train_db = tf.data.Dataset.from_tensor_slices((x, y))
train_db = train_db.map(preprocess).shuffle(60000).batch(batchsz).repeat(10)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_db = test_db.batch(batchsz)

network = Sequential([  # 网络容器
    layers.Conv2D(6, kernel_size=3, strides=1),  # 第一个卷积层,6个3×3卷积核
    layers.MaxPooling2D(pool_size=2, strides=2),  # 高宽各减半的池化层
    layers.ReLU(),  # 激活函数
    layers.Conv2D(16, kernel_size=2, strides=1),  # 第二个卷积层,16个3×3卷积核
    layers.MaxPooling2D(pool_size=2, strides=2),  # 高宽各减半的池化层
    layers.ReLU(),  # 激活函数
    layers.Flatten(),  # 打平层,方便全连接层处理

    layers.Dense(120, activation='relu'),  # 全连接层,120个节点
    layers.Dense(84, activation='relu'),  # 全连接层,84个节点
    layers.Dense(10),  # 全连接层,10个节点
])

# build一次网格模型,给输入x的形状,其中4为随意给的batchsize
network.build(input_shape=(4, 28, 28, 1))
# 统计网络信息
network.summary()

# 创建损失函数的类,在实际计算时直接调用实例即可
criteon = losses.CategoricalCrossentropy(from_logits=True)

optimizer = optimizers.Adam(lr=0.01)

# 训练部分实现如下
# 构建梯度记录环境
# 训练20个epoch


def train_epoch(epoch):
    for step, (x, y) in enumerate(train_db):  # 循环优化
        with tf.GradientTape() as tape:
            # 插入通道维度,=>[b,28,28,1]
            x = tf.expand_dims(x, axis=3)
            # 向前计算,获得10类别的概率分布,[b,784] => [b,10]
            out = network(x)
            # 真实标签one-hot编码,[b] => [b,10]
            y_onehot = tf.one_hot(y, depth=10)
            # 计算交叉熵损失函数,标量
            loss = criteon(y_onehot, out)
        # 自动计算梯度
        grads = tape.gradient(loss, network.trainable_variables)
        # 自动更新参数
        optimizer.apply_gradients(zip(grads, network.trainable_variables))
        if step % 100 == 0:
            print(step, 'loss:', loss_meter.result().numpy())
            loss_meter.reset_states()

        # 计算准确度
        if step % 100 == 0:
            # 记录预测正确的数量,总样本数量
            correct, total = 0, 0
            for x, y in test_db:  # 遍历所有训练集样本
                # 插入通道维度,=>[b,28,28,1]
                x = tf.expand_dims(x, axis=3)
                # 向前计算,获得10类别的概率分布,[b,784] => [b,10]
                out = network(x)
                # 真实的流程时先经过softmax,再argmax
                # 但是由于softmax不改变元素的大小相对关系,故省去
                pred = tf.argmax(out,axis=-1)
                y = tf.cast(y, tf.int64)
                # 统计预测样本总数
                correct += float(tf.reduce_sum(tf.cast(tf.equal(pred, y), tf.float32)))
                # 统计预测样本总数
                total += x.shape[0]
            # 计算准确率
            print('test acc:', correct/total)

def train():

    for epoch in range(30):

        train_epoch(epoch)

if __name__ == '__main__':
    train()

标签:layers,10,卷积,28,test,神经网络,LeNet,tf
来源: https://blog.csdn.net/weixin_43360025/article/details/120583884

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有