- 本文为🔗365天深度学习训练营 中的学习记录博客
- 原作者:K同学啊
任务:
●1. 在DenseNet系列算法中插入SE-Net通道注意力机制,并完成猴痘病识别
●2. 改进思路是否可以迁移到其他地方呢
●3. 测试集accuracy到达89%(拔高,可选)
一、介绍
可参考论文《Squeeze-and-Excitation Networks》
SE-Net 是 ImageNet 2017(ImageNet 收官赛)的冠军模型,是由WMW团队发布。具有复杂度低,参数少和计算量小的优点。且SENet 思路很简单,很容易扩展到已有网络结构如 Inception 和 ResNet 中。
已经有很多工作在空间维度上来提升网络的性能,如 Inception 等,而 SENet 将关注点放在了特征通道之间的关系上。其具体策略为:通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征并抑制对当前任务用处不大的特征,这又叫做“特征重标定”策略。具体的 SE 模块如下图所示:


二、SE 模块应用分析
SE模块的灵活性在于它可以直接应用现有的网络结构中。以 Inception 和 ResNet 为例,我们只需要在 Inception 模块或 Residual 模块后添加一个 SE 模块即可。具体如下图所示:

上图分别是将 SE 模块嵌入到 Inception 结构与 ResNet 中的示例,方框旁边的维度信息代表该层的输出,c(原文是r,不过我觉得应该是c) 表示 Excitation 操作中的降维系数。
三、SE 模型效果对比
SE 模块很容易嵌入到其它网络中,为了验证 SE 模块的作用,在其它流行网络如 ResNet 和 Inception 中引入 SE 模块,测试其在 ImageNet 上的效果,如下表所示:

首先看一下网络的深度对 SE 的影响。上表分别展示了 ResNet-50、ResNet-101、ResNet-152 和嵌入 SE 模型的结果。第一栏 Original 是原作者实现的结果,为了进行公平的比较,重新进行了实验得到 Our re-implementation 的结果。最后一栏 SE-module 是指嵌入了 SE 模块的结果,它的训练参数和第二栏 Our re-implementation 一致。括号中的红色数值是指相对于 Our re-implementation 的精度提升的幅值。
从上表可以看出,SE-ResNets 在各种深度上都远远超过了其对应的没有SE的结构版本的精度,这说明无论网络的深度如何,SE模块都能够给网络带来性能上的增益。值得一提的是,SE-ResNet-50 可以达到和ResNet-101 一样的精度;更甚,SE-ResNet-101 远远地超过了更深的ResNet-152。

上图展示了ResNet-50 和 ResNet-152 以及它们对应的嵌入SE模块的网络在ImageNet上的训练过程,可以明显地看出加入了SE模块的网络收敛到更低的错误率上。
四、SE 模块代码实现
import tensorflow as tf
class Squeeze_excitation_layer(tf.keras.Model):
def __init__(self, filter_sq):
# filter_sq 是 Excitation 中第一个卷积过程中卷积核的个数
super().__init__()
self.filter_sq = filter_sq
self.avepool = tf.keras.layers.GlobalAveragePooling2D()
self.dense = tf.keras.layers.Dense(filter_sq)
self.relu = tf.keras.layers.Activation('relu')
self.sigmoid = tf.keras.layers.Activation('sigmoid')
def call(self, inputs):
squeeze = self.avepool(inputs)
excitation = self.dense(squeeze)
excitation = self.relu(excitation)
excitation = tf.keras.layers.Dense(inputs.shape[-1])(excitation)
excitation = self.sigmoid(excitation)
excitation = tf.keras.layers.Reshape((1, 1, inputs.shape[-1]))(excitation)
scale = inputs * excitation
return scale
SE = Squeeze_excitation_layer(16)
inputs = np.zeros((1, 32, 32, 32), dtype=np.float32)
SE(inputs).shape
代码输出:
TensorShape([1, 32, 32, 32])
五、SE 模块插入到 DenseNet 代码实现
from tensorflow.keras.models import Model
# 56,56,64 -> 56,56,64+32*block[0]
# Densenet121 56,56,64 -> 56,56,64+32*6 == 56,56,256
x = dense_block(x, blocks[0], name='conv2')
# 56,56,64+32*block[0] -> 28,28,32+16*block[0]
# Densenet121 56,56,256 -> 28,28,32+16*6 == 28,28,128
x = transition_block(x, 0.5, name='pool2')
# 28,28,32+16*block[0] -> 28,28,32+16*block[0]+32*block[1]
# Densenet121 28,28,128 -> 28,28,128+32*12 == 28,28,512
x = dense_block(x, blocks[1], name='conv3')
# Densenet121 28,28,512 -> 14,14,256
x = transition_block(x, 0.5, name='pool3')
# Densenet121 14,14,256 -> 14,14,256+32*block[2] == 14,14,1024
x = dense_block(x, blocks[2], name='conv4')
# Densenet121 14,14,1024 -> 7,7,512
x = transition_block(x, 0.5, name='pool4')
# Densenet121 7,7,512 -> 7,7,256+32*block[3] == 7,7,1024
x = dense_block(x, blocks[3], name='conv5')
# 加SE注意力机制
x = Squeeze_excitation_layer(16)(x)
x = layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5, name='bn')(x)
x = layers.Activation('relu', name='relu')(x)
x = layers.GlobalAveragePooling2D(name='avg_pool')(x)
x = layers.Dense(classes, activation='softmax', name='fc1000')(x)
inputs = img_input
if blocks == [6, 12, 24, 16]:
model = Model(inputs, x, name='densenet121')
elif blocks == [6, 12, 32, 32]:
model = Model(inputs, x, name='densenet169')
elif blocks == [6, 12, 48, 32]:
model = Model(inputs, x, name='densenet201')
else:
model = Model(inputs, x, name='densenet')
return model
def DenseNet121(input_shape=[224,224,3], classes=3, **kwargs):
return DenseNet([6, 12, 24, 16], input_shape, classes, **kwargs)
def DenseNet169(input_shape=[224,224,3], classes=3, **kwargs):
return DenseNet([6, 12, 32, 32], input_shape, classes, **kwargs)
def DenseNet201(input_shape=[224,224,3], classes=3, **kwargs):
return DenseNet([6, 12, 48, 32], input_shape, classes, **kwargs)
六、用Pytorch实现的具体代码
1、 基础配置
- 操作系统:ubuntu 22.04
- GPU:RTX 3090(24GB) * 1
- 语言环境:python 3.12.3
- 编译器:Jupyter Notebook
- 深度学习环境:torch 2.3.0+cu121,torchvision 0.18.0+cu121
2、 前期准备
2.1.设置gpu/cpu
import os, PIL, random, pathlib
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
代码输出:
cuda
2.2.导入数据
data_dir = './J5'
data_dir = pathlib.Path(data_dir)
data_paths = list(data_dir.glob('*'))
classeNames = [str(path).split("/")[1] for path in data_paths]
print(classeNames)
image_count = len(list(data_dir.glob('*/*')))
print("图片总数为:", image_count)
代码输出:
['Monkeypox', 'Others']
图片总数为: 2142
2.3.数据预处理
train_transforms = transforms.Compose([
transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸
# transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])
test_transform = transforms.Compose([
transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸
transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])
total_data = datasets.ImageFolder("./J5/", transform=train_transforms)
print(total_data.class_to_idx)
代码输出:
{'Monkeypox': 0, 'Others': 1}
2.4.划分数据集
train_size = int(0.8 * len(total_data))
test_size = len(total_data


1296

被折叠的 条评论
为什么被折叠?



