锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

重学深度学习系列-- AlexNet猫狗分类(TensorFlow2)

时间:2023-10-24 15:37:02 lrn系列热继电器

重学深度学习系列-- AlexNet猫狗分类(TensorFlow2)

文章目录

  • 重学深度学习系列-- AlexNet猫狗分类(TensorFlow2)
    • 一、我的环境
    • 二、工程结构
    • 三、AlexNet介绍
      • 3.1 主要贡献
      • 3.2 网络结构
    • 四、AlexNet的TensorFlow2代码实现
    • 五、训练
      • 5.1 开始训练
      • 5.2 适当的参考优化
      • 5.3 设置动态学习率
    • 六、调用图片预测
    • image-20220301170230560
    • 参考资料

一、我的环境

windows10 pycharm , TensorFlow2.3.0

二、工程结构

image-20220301170431072

百度网盘共享了整个项目:(包括1288个数据集和模型MB)

链接:https://pan.baidu.com/s/114TZ4KTpm0Ha0SGsFhn9fQ
提取码:fnvj
–百度网盘超级会员V4的分享

三、AlexNet介绍

AlexNet论文下载链接:http://www.cs.toronto.edu/~fritz/absps/imagenet.pdf

AlexNet是2012年ImageNet冠军获得者Hinton和他的学生Alex Krizhevsky设计。也是在那一年之后,提出了越来越深的神经网络,比如优秀vgg,GoogLeNet。 这对于传统的机器学习分类算法来说是相当出色的。

AlexNet它还首次包含了几个相对较新的技术点CNN成功应用ReLU、Dropout和LRN(Local Response Normalization)等Trick。同时AlexNet也使用了GPU加速操作。

3.1 主要贡献

AlexNet将LeNet发扬光大的思想CNN应用于深度和宽度网络的基本原理。AlexNet主要使用的新技术点如下:(百度百科总结)

(1)成功使用ReLU作为CNN激活函数,并验证其效果超过深网络Sigmoid,成功解决了Sigmoid当网络较深时,梯度弥散问题。虽然ReLU很久以前就提出了激活函数,但直到AlexNet它的出现才发扬光大。

(2)训练时使用Dropout随机忽略部分神经元,避免模型过拟合。Dropout虽然有单独的论文讨论,但是AlexNet通过实践证实其效果。AlexNet最后几个全连接层主要用于Dropout。
(3)在CNN使用重叠的最大池化。CNN平均池化在中广泛使用,AlexNet最大最大池化,避免平均池化的模糊效果。AlexNet提出让步长小于池化核,使池化层的输出重叠覆盖,增强特征的丰富性。
(4)提出了LRN层,创建局部神经元活动的竞争机制,使响应较大的值相对较大,抑制其他反馈较小的神经元,提高模型的泛化能力。
(5)使用CUDA利用深度卷积网络加快训练GPU并行计算能力强,在神经网络训练中处理大量
矩阵。AlexNet使用了两块GTX?580?GPU单个训练GTX?580只有3GB这限制了可训练网络的最大规模。因此作者将AlexNet分布在两个GPU上,在每个GPU一半的神经元参数储存在显存中。因为GPU通信方便,无需主机内存即可相互访问显存,同时使用多块GPU也很高效。同时,AlexNet的设计让GPU通信只在网络的某些层进行,控制了通信的性能损失。?
(6)数据增强,随机地从256×截取2246原始图像×224大小的区域(以及水平翻转的镜像)相当于增加了2*(256-224)^2=数据量为2048倍。若没有数据增强,仅依靠原始数据量,参数众多CNN使用数据增强后,过拟合可以大大降低,提高泛化能力。进行预测时,则是取图片的四个角加中间共5个位置,并进行左右翻转,一共获得10张图片,对他们进行预测并对10次结果求均值。同时,AlexNet论文中提到了会对图像的问题RGB数据进行PCA处理主要成分的标准差为0.1.高斯扰动,增加噪音Trick可将错误率降低1%。

3.2 网络结构

在论文中AlexNet网络结构图如下:

主要有五个卷积层 三个全连接层:

输入图像尺寸:224×224×3

第一层:卷积层

卷积核数:96

卷积核尺寸:11×11

步长:4

是否使用全零填充:是否

接着使用了3×3最大池化,步长为2,不使用全零填充。

二层:卷积层

卷积核数:256

卷积核尺寸:5×5

步长:1

是否使用全零填充:是否使用全零填充:

接着使用了3×3最大池化,步长为2,不使用全零填充。

三层:卷积层

卷积核数:384

卷积核尺寸:3×3

步长:1

是否使用全零填充:是否使用全零填充:

第四层:卷积层

卷积核数:384

卷积核尺寸:3×3

步长:1

是否使用全零填充:是否使用全零填充:

第五层:卷积层

卷积核数:256

卷积核尺寸:3×3

步长:1

是否使用全零填充:是否使用全零填充:

接着使用了3×3最大池化,步长为2,不使用全零填充。

第六层:全连接层

神经元个数:4096

droput:0.5

第七层:全连接层

神经元数量:4096

droput:0.5

第八层:全连接层

神经元数:10000 (输出1000分类)

值得注意的是,上述参数使用两块GPU目前我只在笔记本上选择了环境。GPU跑,所以选择参数的一半。例如,我的卷积核数量是原来的一半,整个连接层的数量也可以调整为原来的一半或四分之一甚至更小。

四、AlexNet的TensorFlow2代码实现

卷积核和全连接层的数量部分调整,要么是原来的一半,要么是四分之一。

# 构建模型 model = tf.keras.models.Sequential([     # 第一层     tf.keras.layers.(48, (11, 11), input_shape=(224, 224, 3),strides=4,padding='valid',activation='relu'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D((3,3),strides=2,padding='valid'),
    # 第二层
    tf.keras.layers.Conv2D(128, (5, 5),activation='relu',padding='same'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D((3,3),strides=2,padding='valid'),
    # 第三层
    tf.keras.layers.Conv2D(192, (3, 3),padding='same',activation='relu'),
    # 第四层
    tf.keras.layers.Conv2D(192, (3, 3),padding='same',activation='relu'),
    # 第五层
    tf.keras.layers.Conv2D(128, (3, 3),padding='same',activation='relu'),
    tf.keras.layers.MaxPooling2D((3,3),strides=2,padding='valid'),

    tf.keras.layers.Flatten(),
    # 第六层
    tf.keras.layers.Dense(1024,activation='relu'),
    tf.keras.layers.Dropout(0.25),
    # 第七层
    tf.keras.layers.Dense(1024, activation='relu'),
    tf.keras.layers.Dropout(0.25),
    # 第八层
    tf.keras.layers.Dense(1, activation='sigmoid')
])

​ 由于是猫狗分类,所以最后一层可以输出1,也可以输出2,输出1时直接输出是哪一类,输出2时是输出两类各自的概率。

五、训练

5.1 开始训练

​ 以下是train.py:

import os
import warnings

warnings.filterwarnings("ignore")
import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
from tensorflow.keras import regularizers

# 加载数据集
base_dir = './datasets/cats_and_dogs/'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')

train_cats_dir = os.path.join(train_dir, 'cats')
train_dogs_dir = os.path.join(train_dir, 'dogs')
validation_cats_dir = os.path.join(validation_dir, 'cats')
validation_dogs_dir = os.path.join(validation_dir, 'dogs')

# 构建模型
model = tf.keras.models.Sequential([
    # 第一层
    tf.keras.layers.Conv2D(48, (11, 11), input_shape=(224, 224, 3),strides=4,padding='valid',activation='relu'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D((3,3),strides=2,padding='valid'),
    # 第二层
    tf.keras.layers.Conv2D(128, (5, 5),activation='relu',padding='same'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D((3,3),strides=2,padding='valid'),
    # 第三层
    tf.keras.layers.Conv2D(192, (3, 3),padding='same',activation='relu'),
    # 第四层
    tf.keras.layers.Conv2D(192, (3, 3),padding='same',activation='relu'),
    # 第五层
    tf.keras.layers.Conv2D(128, (3, 3),padding='same',activation='relu'),
    tf.keras.layers.MaxPooling2D((3,3),strides=2,padding='valid'),

    tf.keras.layers.Flatten(),
    # 第六层
    tf.keras.layers.Dense(1024,activation='relu'),
    tf.keras.layers.Dropout(0.25),
    # 第七层
    tf.keras.layers.Dense(1024, activation='relu'),
    tf.keras.layers.Dropout(0.25),
    # 第八层
    tf.keras.layers.Dense(1, activation='sigmoid')
])

# 总结输出网络参数
model.summary()

# 配置模型训练的参数
model.compile(loss='binary_crossentropy', optimizer=Adam(lr=1e-4), metrics=['acc'])

# 进行数据增强
train_datagen = ImageDataGenerator(
    rescale=1. / 255,
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest'
)
test_datagen = ImageDataGenerator(rescale=1. / 255)

img_size = (224, 224)

train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=img_size,  # 与网络的固定输入一致
    batch_size=8,
    class_mode='binary'  # one-hot编码格式,在预测时输出也要注意
)

validation_generator = train_datagen.flow_from_directory(
    validation_dir,
    target_size=img_size,
    batch_size=8,
    class_mode='binary'
)

# 加载训练数据
history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,  # 2000 images = batchsize * steps
    epochs=100,
    validation_data=validation_generator,  # 1000 images = batchsize * steps
    validation_steps=50,
    verbose=2
)

# 保存训练好的模型
model.save('./model.h5')

# 将训练结果可视化
acc = history.history['acc']
val_acc = history.history['val_acc']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'b', label='Training accuracy')
plt.plot(epochs, val_acc, 'r', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'b', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

以下是训练100轮后的结果:

从上面的结果看出有些过拟合了。

5.2 适当调参优化

​ 由于有些过拟合了,所以我加入了正则化和BN,(读者可以尝试relu放在BN之前和之后,效果可能会不一样)并重新训练:

# 构建模型
model = tf.keras.models.Sequential([
    # 第一层
    tf.keras.layers.Conv2D(48, (11, 11), input_shape=(224, 224, 3), strides=4, padding='valid',activation='relu',
                           kernel_regularizer=regularizers.l2(0.001)),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D((3, 3), strides=2, padding='valid'),
    # 第二层
    tf.keras.layers.Conv2D(128, (5, 5), padding='same', activation='relu',kernel_regularizer=regularizers.l2(0.001)),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D((3, 3), strides=2, padding='valid'),
    # 第三层
    tf.keras.layers.Conv2D(192, (3, 3), padding='same',activation='relu', kernel_regularizer=regularizers.l2(0.001)),
    tf.keras.layers.BatchNormalization(),
    # 第四层
    tf.keras.layers.Conv2D(192, (3, 3), padding='same', activation='relu',kernel_regularizer=regularizers.l2(0.001)),
    tf.keras.layers.BatchNormalization(),

    # 第五层
    tf.keras.layers.Conv2D(128, (3, 3), padding='same', activation='relu',kernel_regularizer=regularizers.l2(0.001)),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D((3, 3), strides=2, padding='valid'),

    tf.keras.layers.Flatten(),
    # 第六层
    tf.keras.layers.Dense(1024, activation='relu'),
    tf.keras.layers.Dropout(0.25),
    # 第七层
    tf.keras.layers.Dense(1024, activation='relu'),
    tf.keras.layers.Dropout(0.25),
    # 第八层
    tf.keras.layers.Dense(1, activation='sigmoid')
])

​ 可以看到过拟合有了一定程度的缓解。但是这中间过程有点抖动得厉害,所以如果要是可以设置动态的学习率那么应该会好一点。

5.3 设置动态的学习率

​ 设置动态的学习率有多种办法,我这里采用ReduceLROnPlateau这个API。

官网给出的例子:

reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=5, min_lr=0.001)
model.fit(X_train, Y_train, callbacks=[reduce_lr])

ReduceLROnPlateau()参数说明:

monitor:监测的值,可以是loss,acc,val_loss,val_acc,lr
factor:缩放学习率的值,学习率将以lr = lr*factor的形式被减少
patience:当patience个epoch过去而模型性能不提升时,学习率减少的动作会被触发
mode:‘auto’,‘min’,‘max’之一 默认‘auto’就行
epsilon:阈值,用来确定是否进入检测值的“平原区”
cooldown:学习率减少后,会经过cooldown个epoch才重新进行正常操作
min_lr:学习率最小值,能缩小到的下限
verbose: 详细信息模式,0 或者 1 。

值得注意的是,TensorFlow2.3.0版本中官网有介绍说model.fit_generator这个API在后续版本中不再支持,建议使用model.fit(),所以我后面也改为使用model.fit()这个API。(参看下图warning:)

设置动态的学习率:

from tensorflow.keras.callbacks import ReduceLROnPlateau

reduce_lr = ReduceLROnPlateau(monitor='val_acc', factor=0.5, patience=5, min_lr=1e-5, verbose=1)

history = model.fit(
    train_generator,
    steps_per_epoch=100,  # 2000 images = batchsize * steps
    epochs=100,
    validation_data=validation_generator,  # 1000 images = batchsize * steps
    validation_steps=50,
    callbacks=[reduce_lr],  # 设置动态的学习率
    verbose=2)

​ 设置完成后再训练,可以看到曲线更平滑了一些:

​ 但是准确率却有所下降:

​ 但是,不难看出模型还能进一步收敛,读者可以尝试延长epoch,或者把学习率适当调大一点。

六、调用图片进行预测

import numpy as np
from tensorflow.keras.models import load_model
import cv2

# 种类字典
class_dict = { 
        0: '猫', 1: '狗'}


def predict(img_path):
    # 载入模型
    model = load_model('./model.h5')
    # 载入图片,并处理
    img = cv2.imread(img_path)
    img = cv2.resize(img, (224, 224))
    img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img_nor = img_RGB / 255
    img_nor = np.expand_dims(img_nor, axis=0)

    # 预测
    # print((np.argmax(model.predict(img_nor))))
    # print(model.predict(img_nor))
    y = model.predict_classes(img_nor)
    print(class_dict.get(y[0][0]))  # 直接输出种类 0是猫 1是狗


if __name__ == "__main__":
    predict('./datasets/cats_and_dogs/test/cat.1500.jpg')  # 0
    predict('./datasets/cats_and_dogs/test/dog.1500.jpg')  # 1
    predict('./datasets/cats_and_dogs/test/dog.1504.jpg')  # 1

参考资料

1.AlexNet论文:http://www.cs.toronto.edu/~fritz/absps/imagenet.pdf

2.动态学习率参数说明参考:https://blog.csdn.net/qq_40128284/article/details/117018755

3.动态学习率设置官方API文档:https://tensorflow.google.cn/versions/r2.3/api_docs/python/tf/keras/callbacks/ReduceLROnPlateau

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章