基于DenseNet的图像识别

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
简介: 在本文中,我们提出了一种架构,将这种见解提炼成一个简单的连接模式:为了确保网络中各层之间的最大信息流,我们**将所有层(具有匹配的特征图大小)直接相互连接**。为了保持前馈特性,**每一层都从所有前面的层获得额外的输入,并将其自己的特征图传递给所有后续层**。图 1 示意性地说明了这种布局。至关重要的是,与 ResNets 相比,我们在将特征传递到层之前从**不通过求和来组合特征**。相反,我们**通过连接(Concatenate操作)它们来组合特征

1、DenseNet简介

  DenseNet的文章我以前写过,原理篇看这里:DenseNet:Densely Connected Convolutional Networks--CVPR2017最佳论文奖

image-20220823211920935

  在本文中,我们提出了一种架构,将这种见解提炼成一个简单的连接模式:为了确保网络中各层之间的最大信息流,我们将所有层(具有匹配的特征图大小)直接相互连接。为了保持前馈特性,每一层都从所有前面的层获得额外的输入,并将其自己的特征图传递给所有后续层。图 1 示意性地说明了这种布局。至关重要的是,与 ResNets 相比,我们在将特征传递到层之前从不通过求和来组合特征。相反,我们通过连接(Concatenate操作)它们来组合特征

  因此,第 ℓ 层有 ℓ 输入,由所有先前卷积块的特征图组成。它自己的特征图被传递到所有 L−ℓ 后续层。这在 L 层网络中引入了 L(L+1)2 个连接,而不是传统架构中的仅 L 个连接。由于其密集的连接模式,我们将我们的方法称为密集卷积网络(DenseNet)

2、网络结构

image-20220823214539490

表 1:ImageNet 的 DenseNet 架构。前 3 个网络的增长率为 k = 32,对于 DenseNet-161,k = 48。请注意,==表中显示的每个“conv”层都对应于序列 BN-ReLU-Conv==。

3、花朵数据集

训练集1088张图片,17个类别 测试集272张图片,17个类别。 每个文件夹对应一个花朵类别。

image-20220914213832131

image-20220914213925038

4、DenseNet花朵识别

import tensorflow as tf
import numpy as np
from tensorflow.keras import layers
from tensorflow.keras.models import Model
from tensorflow.keras.applications import DenseNet121
import math
import os
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Dropout, Conv2D, MaxPool2D, Flatten, GlobalAvgPool2D, \
    BatchNormalization, Activation, Add, ZeroPadding2D, Multiply,Conv1D,GlobalAveragePooling2D,Reshape,multiply,ReLU,MaxPooling2D,Concatenate,\
    AveragePooling2D
from tensorflow.keras.optimizers import Adam
import matplotlib.pyplot as plt
from tensorflow.keras.callbacks import LearningRateScheduler
from tensorflow.keras.models import Model

4.1 Dense Block

  Dense Block的每一个密集层:$BN+ReLU+1*1Conv+BN+ReLU+3*3Conv$

  每一层产生k个特征图,原论文中让每个1*1个卷积产生4k个特征图

  growth_rate:网络的增长率,原论文中超参数名称为k

  论文中对内核大小为3*3的卷积层,输入的每一边都用一个像素补零,以保持特征图大小固定。(代码中设置padding='same'即可)

# Bottleneck layers
# BN+ReLU+1*1Conv+BN+ReLU+3*3Conv
# 每一层产生k个特征图,原论文中让每个1*1个卷积产生4k个特征图
# growth_rate:网络的增长率,原论文中超参数名称为k
def conv_block(x,growth_rate,name):
    x1=BatchNormalization(name=name+'_0_bn')(x)
    x1=ReLU(name=name+'_0_relu')(x1)
    x1=Conv2D(filters=4*growth_rate,
              kernel_size=(1,1),
              use_bias=False,
              name=name+'_1_conv')(x1)
    x1=BatchNormalization(name=name+'_1_bn')(x1)
    x1=ReLU(name=name+'_1_relu')(x1)
    # 论文原话:对内核大小为3*3的卷积层,输入的每一边都用一个像素补零,以保持特征图大小固定。
    x1=Conv2D(growth_rate,
              kernel_size=(3,3),
              padding='same',
              use_bias=False,
              name=name+'_2_conv')(x1)
    # 将前面所有层的特征堆叠后传到下一层
    out=Concatenate(axis=-1,name=name+'_concat')([x,x1])
    return out

'''
x: input tensor.
    blocks: integer, the number of building blocks.
    name: string, block label.
'''
# DenseNet
def dense_block(x,blocks,name):
    for i in range(blocks):
        x=conv_block(x,32,name=name+'_block'+str(i+1))
    return x

4.2 Transition Layer(过渡层)

  过渡层:$BN+1*1Conv+2*2AvgPool$

  reduction:压缩因子,原论文中名称为$\theta$,设置为0.5。

# 论文原话:使用1*1卷积和2*2平均池化作为两个连续密集块之间的过渡层
# 过渡层:BN+1*1Conv+2*2AvgPool
# reduction:压缩因子,原论文中设置为0.5
def transition_block(x,reduction,name):
    x=BatchNormalization(name=name+'_bn')(x)
    x=ReLU(name=name+'_relu')(x)
    x=Conv2D(filters=int(x.shape[-1]*reduction),
             kernel_size=(1,1),
             use_bias=False,
             name=name+'_conv')(x)
    x=AveragePooling2D(pool_size=(2,2),strides=2,name=name+'_pool')(x)
    return x

4.3 DensNet121网络骨干

DenseNet121(k=32):blocks=[6,12,24,16]
DenseNet169(k=32):blocks=[6,12,32,32]
DenseNet201(k=32):blocks=[6,12,48,32]
DenseNet161(k=48):blocks=[6,12,36,24]
def DenseNet(blocks,include_top=True,weights='imagenet',intput_shape=None,classes=1000):
    img_input=Input(shape=intput_shape)
    x=ZeroPadding2D(padding=((3,3),(3,3)))(img_input)
    # 论文原话:初始卷积层包含2k个大小为7*7的卷积,步长为2(k=32)
    # 其实这里完全可以去掉这两个ZeroPadding2D,直接给这里的卷积和池化设置padding='same'即可,
    # 但是我看谷歌的源码中没用same padding,所以这里我也没用。
    x=Conv2D(filters=64,kernel_size=(7,7),strides=2,use_bias=False,name='conv1/conv')(x)
    x=BatchNormalization(name='conv1/bn')(x)
    x=ReLU(name='conv1/relu')(x)
    x=ZeroPadding2D(padding=((1,1),(1,1)))(x)
    x=MaxPooling2D(pool_size=(3,3),strides=2,name='pool1')(x)

    x=dense_block(x,blocks[0],name='conv2')
    x=transition_block(x,0.5,name='pool2')

    x=dense_block(x,blocks[1],name='conv3')
    x=transition_block(x,0.5,name='pool3')

    x=dense_block(x,blocks[2],name='conv4')
    x=transition_block(x,0.5,name='pool4')

    x=dense_block(x,blocks[3],name='conv5')

    x=BatchNormalization(name='bn')(x)
    x=ReLU(name='relu')(x)
    if include_top:
        x=GlobalAveragePooling2D(name='global_avg_pool')(x)
        x=Dense(classes,activation='softmax')(x)

    model=Model(img_input,x)
    return model
densenet121=DenseNet(blocks=[6,12,24,16],include_top=False,intput_shape=(224,224,3))
densenet121.summary()

image-20220914214441979

# 类别数
num_classes = 17
# 批次大小
batch_size = 32
# 周期数
epochs=100
# 图片大小
image_size = 224

model=tf.keras.Sequential([
    densenet121,
    layers.GlobalAveragePooling2D(),
    layers.Dense(num_classes,activation='softmax')
])
model.summary()

image-20220914214510977

这里仿照源码搭建的,不太优雅,其实可以一次性直接搭建好的,这里懒得改了。

4.4 数据增强

# 训练集数据进行数据增强
train_datagen = ImageDataGenerator(
    rotation_range=20,  # 随机旋转度数
    width_shift_range=0.1,  # 随机水平平移
    height_shift_range=0.1,  # 随机竖直平移
    rescale=1 / 255,  # 数据归一化
    shear_range=10,  # 随机错切变换
    zoom_range=0.1,  # 随机放大
    horizontal_flip=True,  # 水平翻转
    brightness_range=(0.7, 1.3),  # 亮度变化
    fill_mode='nearest',  # 填充方式
)
# 测试集数据只需要归一化就可以
test_datagen = ImageDataGenerator(
    rescale=1 / 255,  # 数据归一化
)

# 训练集数据生成器,可以在训练时自动产生数据进行训练
# 从'data/train'获得训练集数据
# 获得数据后会把图片resize为image_size×image_size的大小
# generator每次会产生batch_size个数据
train_generator = train_datagen.flow_from_directory(
    '../data/花朵分类(17)/train',
    target_size=(image_size, image_size), # 调整图像尺寸
    batch_size=batch_size,
)

# 测试集数据生成器
test_generator = test_datagen.flow_from_directory(
    '../data/花朵分类(17)/test',
    target_size=(image_size, image_size),
    batch_size=batch_size,
)
# 字典的键为17个文件夹的名字,值为对应的分类编号
print(train_generator.class_indices)

image-20220914214622027

4.5 callback

# 学习率调节函数,逐渐减小学习率
def adjust_learning_rate(epoch):
    # 前40周期
    if epoch<=40:
        lr = 1e-4
    # 前40到80周期
    elif 40 < epoch <= 80:
        lr = 1e-5
    # 80到100周期
    else:
        lr = 1e-6
    return lr

# 定义优化器
adam = Adam(lr=1e-4)

# 读取模型
checkpoint_save_path = "./checkpoint/DenseNet121(不使用预训练)-花朵分类(17)-.ckpt"
if os.path.exists(checkpoint_save_path + '.index'):
    print('-------------load the model-----------------')
    model.load_weights(checkpoint_save_path)
# 保存模型
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                 save_weights_only=True,
                                                 save_best_only=True)

# 定义学习率衰减策略
callbacks = []
callbacks.append(LearningRateScheduler(adjust_learning_rate))
callbacks.append(cp_callback)

4.6 模型训练

# 定义优化器,loss function,训练过程中计算准确率
model.compile(optimizer=adam,loss='categorical_crossentropy',metrics=['accuracy'])

# Tensorflow2.1版本(包括2.1)之后可以直接使用fit训练模型
history = model.fit(x=train_generator,epochs=epochs,validation_data=test_generator,callbacks=callbacks)

image-20220914214742021

这里没有使用预训练,所以效果没有官方的好。

4.7 acc和loss可视化

# 画出训练集准确率曲线图
plt.plot(np.arange(epochs),history.history['accuracy'],c='b',label='train_accuracy')
# 画出验证集准确率曲线图
plt.plot(np.arange(epochs),history.history['val_accuracy'],c='y',label='val_accuracy')
# 图例
plt.legend()
# x坐标描述
plt.xlabel('epochs')
# y坐标描述
plt.ylabel('accuracy')
# 显示图像
plt.show()

image-20220914214823267

# 画出训练集loss曲线图
plt.plot(np.arange(epochs),history.history['loss'],c='b',label='train_loss')
# 画出验证集loss曲线图
plt.plot(np.arange(epochs),history.history['val_loss'],c='y',label='val_loss')
# 图例
plt.legend()
# x坐标描述
plt.xlabel('epochs')
# y坐标描述
plt.ylabel('loss')
# 显示图像
plt.show()

image-20220914214839705

4.8 与预训练的DenseNet结果对比

这里直接利用迁移学习,将在ImageNet模型上预训练的DenseNet模型用来训练,结果如下:

image-20220914215005295

acc:

image-20220914215014394

loss:

image-20220914215029243

  可以看到,有没有预训练对结果的影响很大,而且预训练的权重都是别人经过大量实验得出的,所以后面训练模型的时候尽量用迁移学习方法去做,自己从头训练的话太慢了,效果还不行。
目录
相关文章
|
6月前
|
机器学习/深度学习 数据可视化 算法框架/工具
深度学习第3天:CNN卷积神经网络
深度学习第3天:CNN卷积神经网络
|
6天前
|
机器学习/深度学习 人工智能 算法框架/工具
深度学习中的卷积神经网络(CNN)及其在图像识别中的应用
【10月更文挑战第36天】探索卷积神经网络(CNN)的神秘面纱,揭示其在图像识别领域的威力。本文将带你了解CNN的核心概念,并通过实际代码示例,展示如何构建和训练一个简单的CNN模型。无论你是深度学习的初学者还是希望深化理解,这篇文章都将为你提供有价值的见解。
|
1天前
|
机器学习/深度学习 自然语言处理
深入理解深度学习中的卷积神经网络(CNN)
深入理解深度学习中的卷积神经网络(CNN)
11 0
|
7天前
|
机器学习/深度学习 人工智能 自动驾驶
深入解析深度学习中的卷积神经网络(CNN)
深入解析深度学习中的卷积神经网络(CNN)
21 0
|
16天前
|
机器学习/深度学习 自然语言处理 TensorFlow
深度学习中的卷积神经网络(CNN)及其应用
【10月更文挑战第26天】在这篇文章中,我们将深入探讨卷积神经网络(CNN)的基本原理、结构和应用。CNN是深度学习领域的一个重要分支,广泛应用于图像识别、语音处理等领域。我们将通过代码示例和实际应用案例,帮助读者更好地理解CNN的概念和应用。
|
2月前
|
机器学习/深度学习 自动驾驶 TensorFlow
深入理解卷积神经网络(CNN)在图像识别中的应用
【9月更文挑战第20天】本文旨在通过直观的解释和代码示例,向初学者介绍卷积神经网络(CNN)的基本概念及其在图像识别领域的应用。文章将首先解释什么是CNN以及它如何工作,然后通过一个简单的Python代码示例展示如何构建一个基本的CNN模型。最后,我们将讨论CNN在现实世界问题中的潜在应用,并探讨其面临的挑战和发展方向。
173 2
|
2月前
|
机器学习/深度学习 人工智能 TensorFlow
深入探索深度学习中的卷积神经网络(CNN)
【9月更文挑战第19天】本文将深入浅出地介绍卷积神经网络(CNN)在深度学习领域的应用和原理,旨在为初学者提供一个清晰的理解框架。通过实例演示,我们将展示如何利用Python和TensorFlow库构建一个简单的CNN模型,用于图像分类任务。此外,文章还将探讨CNN在不同应用场景下的优化策略和挑战。
|
6月前
|
机器学习/深度学习 人工智能 算法
【AI 初识】什么是卷积神经网络 (CNN)?
【5月更文挑战第2天】【AI 初识】什么是卷积神经网络 (CNN)?
|
5月前
|
机器学习/深度学习 自动驾驶 TensorFlow
图像识别:卷积神经网络(CNN)的应用
【6月更文挑战第2天】卷积神经网络(CNN)是图像识别的得力工具,能识别物体、人脸等。广泛应用于安防、医疗和自动驾驶等领域。通过学习图像特征,CNN实现智能识别。示例代码展示了使用TensorFlow构建简单CNN识别MNIST手写数字。尽管实际应用更复杂,但CNN已显著改变生活,并将持续带来惊喜。
117 0
|
6月前
|
机器学习/深度学习 自然语言处理 搜索推荐