基于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

  可以看到,有没有预训练对结果的影响很大,而且预训练的权重都是别人经过大量实验得出的,所以后面训练模型的时候尽量用迁移学习方法去做,自己从头训练的话太慢了,效果还不行。
目录
相关文章
|
机器学习/深度学习 算法 PyTorch
RPN(Region Proposal Networks)候选区域网络算法解析(附PyTorch代码)
RPN(Region Proposal Networks)候选区域网络算法解析(附PyTorch代码)
2733 1
|
监控
险境中的智慧航行:ERP系统的风险管理与应对策略
险境中的智慧航行:ERP系统的风险管理与应对策略
1493 5
|
运维 资源调度 Kubernetes
一文读懂 Kubernetes 大数据平台-CloudEon
Hello folks,我是 Luga,今天我们来分享一下关于 Kubernetes 大数据平台管理工具-CloudEon。作为一款基于 Kubernetes 大数据平台,CloudEon 旨在为管理 Kubernetes 大数据资源提供一种更直观和可视化的方式。
992 0
|
5天前
|
机器学习/深度学习 人工智能 开发框架
智能体来了:零基础学习智能体,从入门到就业的系统路径
智能体来了,国内专注AI智能体教育与落地的品牌,为零基础者、转型者及企业提供系统化学习方案。涵盖认知入门、实操训练到项目实战,八大核心模块助力就业。赋能个人掌握AI技能,助力企业降本增效,推动智能体技术产业化应用。(238字)
104 1
|
4月前
|
SQL 搜索推荐 数据挖掘
数据分析怎么想、怎么用?一文讲透常见思维框架!
在数据分析中,很多人面对数据感到迷茫,主要问题在于缺乏清晰的思维框架。本文介绍了五种常用的数据分析思维框架,如拆解法、对比分析法、5W1H问题导向法等,帮助你在业务场景中理清思路、快速定位问题核心。通过实际案例讲解如何在不同情境下灵活运用这些框架,提升分析效率与逻辑表达能力,真正做到用数据驱动决策。
|
7月前
|
存储 Java 定位技术
SpringBoot整合高德地图完成天气预报功能
本文介绍了如何在SpringBoot项目中整合高德地图API实现天气预报功能。从创建SpringBoot项目、配置依赖和申请高德地图API开始,详细讲解了实体类设计、服务层实现(调用高德地图API获取实时与预报天气数据)、控制器层接口开发以及定时任务的设置。通过示例代码,展示了如何获取并处理天气数据,最终提供实时天气与未来几天天气预报的接口。文章还提供了测试方法及运行步骤,帮助开发者快速上手并扩展功能。
|
11月前
|
存储 关系型数据库 MySQL
MySQL主键谁与争锋:MySQL为何钟爱自增主键ID+UUID?
本文深入探讨了在MySQL中使用自增类型主键的优势与局限性。自增主键通过保证数据的有序性和减少索引维护成本,提升了查询和插入性能,简化了数据库管理和维护,并提高了数据一致性。然而,在某些业务场景下,如跨表唯一性需求或分布式系统中,自增主键可能无法满足要求,且存在主键值易预测的安全风险。因此,选择主键类型时需综合考虑业务需求和应用场景。
352 2
|
架构师 NoSQL 中间件
挑战架构师极限:分布式锁的四种实现方式,优劣对比让你一目了然!
【8月更文挑战第29天】在2024年软考架构师考试中,掌握分布式锁的实现方法极其重要。本文详细介绍了基于数据库、Redis及ZooKeeper三种常见分布式锁方案。数据库锁简单易懂但性能低;Redis锁性能优越且支持自动续期,但需引入中间件;ZooKeeper锁可靠性高,适用于分布式环境,但实现复杂。通过对比各方案优缺点,帮助考生更好地应对考试,选择最适合业务场景的分布式锁策略。
1260 0
|
数据采集 机器学习/深度学习 数据可视化
利用Python进行网络爬虫和数据抓取
在当今数字化时代,数据是无处不在的。从市场趋势到个人偏好,从社交媒体活动到商业智能,数据扮演着关键的角色。然而,访问、处理和利用数据并不总是轻而易举的。幸运的是,Python提供了一套强大而灵活的工具,使得网络爬虫和数据抓取成为可能。本文将深入探讨如何利用Python进行网络爬虫和数据抓取,为您打开数据世界的大门。
|
编解码 芯片 内存技术
数字基带传输系统 1
数字基带传输系统
634 0