1.SENet模块
def SE_moudle(input_xs,reduction_ratio = 16.):
shape = input_xs.get_shape().as_list()
se_module = tf.reduce_mean(input_xs,[1,2])
#第一个Dense:shape[-1]/reduction_ratio:即把input_channel再除以reduction_ratio,使channel下降到指定维度数
se_module = tf.keras.layers.Dense(shape[-1]/reduction_ratio,activation=tf.nn.relu)(se_module)
#第二个Dense:重新回升到与input_channel相同的原始维度数
se_module = tf.keras.layers.Dense(shape[-1], activation=tf.nn.relu)(se_module)
se_module = tf.nn.sigmoid(se_module)
se_module = tf.reshape(se_module,[-1,1,1,shape[-1]])
out_ys = tf.multiply(input_xs,se_module)
return out_ys
2.加载SENet模块的Resnet
def identity_block(input_xs, out_dim, with_shortcut_conv_BN=False):
if with_shortcut_conv_BN:
pass
else:
#返回与input的形状和内容均相同的张量,即shortcut等同于input_xs
shortcut = tf.identity(input_xs)
#input输入的channel数
input_channel = input_xs.get_shape().as_list()[-1]
#如果输入的channel数不等于输出的channel数的话
if input_channel != out_dim:
#求输出的channel数减去输入的channel数的绝对值,作为pad填充值
pad_shape = tf.abs(out_dim - input_channel)
#name="padding"表示给该填充操作赋予名称为"padding"。使用了默认参数mode='CONSTANT'和constant_values=0,表示填充默认值0。
#第二个参数为paddings填充的形状:即分别的批量维度、高、宽的维度上都不作填充,在channel维度上填充pad_shape//2的数量。
shortcut = tf.pad(shortcut, [[0, 0], [0, 0], [0, 0], [pad_shape // 2, pad_shape // 2]], name="padding")
#残差卷积块中的3个Conv2D卷积的卷积核大小分别为1x1、3x3、1x1
conv = tf.keras.layers.Conv2D(filters=out_dim // 4, kernel_size=1, padding="SAME", activation=tf.nn.relu)(input_xs)
conv = tf.keras.layers.BatchNormalization()(conv)
conv = tf.keras.layers.Conv2D(filters=out_dim // 4, kernel_size=3, padding="SAME", activation=tf.nn.relu)(conv)
conv = tf.keras.layers.BatchNormalization()(conv)
conv = tf.keras.layers.Conv2D(filters=out_dim // 4, kernel_size=1, padding="SAME", activation=tf.nn.relu)(conv)
conv = tf.keras.layers.BatchNormalization()(conv)
#下面开始加载SENet模块
#返回的为[批量维度、高、宽、channel维度]
shape = conv.get_shape().as_list()
#默认参数为keepdims=False的话,不会再保留运算所在的维度。设置keepdims=True的话,会保留运算所在的维度为1。
#[批量维度、高、宽、channel维度]经过reduce_mean后转换为[批量维度、channel维度]
se_module = tf.reduce_mean(conv, [1, 2])
#第一个Dense:shape[-1]/reduction_ratio:即把input_channel再除以reduction_ratio,使channel下降到指定维度数
se_module = tf.keras.layers.Dense(shape[-1] / 16, activation=tf.nn.relu)(se_module)
#第二个Dense:重新回升到与input_channel相同的原始维度数
se_module = tf.keras.layers.Dense(shape[-1], activation=tf.nn.relu)(se_module)
se_module = tf.nn.sigmoid(se_module)
#把[批量维度、channel维度]重新转换为[批量维度、高、宽、channel维度],即[批量维度、1、1、channel维度]
se_module = tf.reshape(se_module, [-1, 1, 1, shape[-1]])
#multiply元素乘法:SENet模块输出值se_module 和 残差卷积输出conv(即SENet模块输入值conv)
se_module = tf.multiply(conv, se_module)
#残差连接:对残差的原始输入shortcut(即input_xs) 与 SENet模块输出值se_module 进行求和
output_ys = tf.add(shortcut, se_module)
output_ys = tf.nn.relu(output_ys)
return output_ys
3. CBAM模块
def cbam_module(input_xs, reduction_ratio=0.5):
#分别获取批量大小、通道数,通道数作为隐藏层的神经元数量
batch_size, hidden_num = input_xs.get_shape().as_list()[0], input_xs.get_shape().as_list()[3]
### 第一步:Channel Attention 模块 ###
#1.默认参数为keepdims=False的话,不会再保留运算所在的维度。设置keepdims=True的话,会保留运算所在的维度为1。
# 连续两次的reduce_max/reduce_mean,实际都是先将[批量维度、高、宽、channel维度]转换为[批量维度、1、宽、channel维度],
# 再转换为[批量维度、1、1、channel维度]。
#2.首先对Channel Attention 模块的输入数据分别进行全局池化(reduce_max)和平均池化(reduce_mean)两种操作。
maxpool_channel = tf.reduce_max(tf.reduce_max(input_xs, axis=1, keepdims=True), axis=2, keepdims=True)
avgpool_channel = tf.reduce_mean(tf.reduce_mean(input_xs, axis=1, keepdims=True), axis=2, keepdims=True)
maxpool_channel = tf.keras.layers.Flatten()(maxpool_channel)
avgpool_channel = tf.keras.layers.Flatten()(avgpool_channel)
#使用2个连续的全连接层对全局池化(reduce_max)后的特征进行提取。
#reduction_ratio为0.5,即第1个全连接层的神经元数量为输入数据的输入数据的通道数的一半,然后第2个全连接层神经元数量重新恢复为输入数据的通道数
mlp_1_max = tf.keras.layers.Dense(units=int(hidden_num * reduction_ratio), activation=tf.nn.relu)(maxpool_channel)
mlp_2_max = tf.keras.layers.Dense(units=hidden_num)(mlp_1_max)
#[批量维度、1、1、channel维度],此处的隐藏层的神经元数量hidden_num等于输入数据的通道数
mlp_2_max = tf.reshape(mlp_2_max, [-1, 1, 1, hidden_num])
#使用2个连续的全连接层对平均池化(reduce_mean)后的特征进行提取。
#reduction_ratio为0.5,即第1个全连接层的神经元数量为输入数据的输入数据的通道数的一半,然后第2个全连接层神经元数量重新恢复为输入数据的通道数
mlp_1_avg = tf.keras.layers.Dense(units=int(hidden_num * reduction_ratio), activation=tf.nn.relu)(avgpool_channel)
mlp_2_avg = tf.keras.layers.Dense(units=hidden_num, activation=tf.nn.relu)(mlp_1_avg)
#[批量维度、1、1、channel维度],此处的隐藏层的神经元数量hidden_num等于输入数据的通道数
mlp_2_avg = tf.reshape(mlp_2_avg, [-1, 1, 1, hidden_num])
#把“对全局池化(reduce_max)提取后的”特征和“对平均池化(reduce_mean)提取后的”特征进行求和,然后通过sigmoid激活归一化到0到1之间
channel_attention = tf.nn.sigmoid(mlp_2_max + mlp_2_avg)
#把“通过sigmoid激活归一化的”值和Channel Attention 模块的输入数据进行内积计算,其最终计算结果值作为后面的Spatial Attention 模块的输入
channel_refined_feature = input_xs * channel_attention
### 第二步:Spatial Attention 模块 ###
#1.首先把Channel Attention 模块的输出作为Spatial Attention 模块的输入,然后对输入数据分别进行全局池化(reduce_max)和平均池化(reduce_mean)两种操作。
#2.默认参数为keepdims=False的话,不会再保留运算所在的维度。设置keepdims=True的话,会保留运算所在的维度为1。
#3.全局池化(reduce_max):把[批量维度、高、宽、channel维度]转换为[批量维度、高、宽、1]
maxpool_spatial = tf.reduce_max(channel_refined_feature, axis=3, keepdims=True)
#平均池化(reduce_mean):把[批量维度、高、宽、channel维度]转换为[批量维度、高、宽、1]
avgpool_spatial = tf.reduce_mean(channel_refined_feature, axis=3, keepdims=True)
#把全局池化(reduce_max)和平均池化(reduce_mean)后的数据在channel维度上进行合并,最终转换为[批量维度、高、宽、2]
max_avg_pool_spatial = tf.concat([maxpool_spatial, avgpool_spatial], axis=3)
#目的是将数据的维度降维为1,以便后面的sigmoid激活归一化计算
conv_layer = tf.keras.layers.Conv2D(filters=1, kernel_size=(3, 3), padding="same", activation=None)(max_avg_pool_spatial)
#通过sigmoid激活归一化到0到1之间
spatial_attention = tf.nn.sigmoid(conv_layer)
#将Spatial Attention 模块的输出channel_refined_feature 和Spatial Attention 模块的输出spatial_attention 进行内积计算,
#其最终计算结果值作为CBAM注意力机制模块的输出值。
refined_feature = channel_refined_feature * spatial_attention
#将CBAM注意力机制模块的输入值input_xs和输出值refined_feature进行求和得出最终的结果
output_layer = refined_feature + input_xs
return output_layer
4.加载了CBAM模块的Resnet
def identity_block(input_xs, out_dim, with_shortcut_conv_BN=False):
layer_depth=20 #层的深度
if with_shortcut_conv_BN:
pass
else:
#返回与input的形状和内容均相同的张量,即shortcut等同于input_xs
shortcut = tf.identity(input_xs)
#input输入的channel数
input_channel = input_xs.get_shape().as_list()[-1]
#如果输入的channel数不等于输出的channel数的话
if input_channel != out_dim:
#求输出的channel数减去输入的channel数的绝对值,作为pad填充值
pad_shape = tf.abs(out_dim - input_channel)
#name="padding"表示给该填充操作赋予名称为"padding"。使用了默认参数mode='CONSTANT'和constant_values=0,表示填充默认值0。
#第二个参数为paddings填充的形状:即分别的批量维度、高、宽的维度上都不作填充,在channel维度上填充pad_shape//2的数量。
shortcut = tf.pad(shortcut, [[0, 0], [0, 0], [0, 0], [pad_shape // 2, pad_shape // 2]], name="padding")
#残差卷积块中的3个Conv2D卷积的卷积核大小分别为1x1、3x3、1x1
conv = tf.keras.layers.Conv2D(filters=out_dim // 4, kernel_size=1, padding="SAME", activation=tf.nn.relu)(input_xs)
conv = tf.keras.layers.BatchNormalization()(conv)
conv = tf.keras.layers.Conv2D(filters=out_dim // 4, kernel_size=3, padding="SAME", activation=tf.nn.relu)(conv)
conv = tf.keras.layers.BatchNormalization()(conv)
conv = tf.keras.layers.Conv2D(filters=out_dim // 4, kernel_size=1, padding="SAME", activation=tf.nn.relu)(conv)
conv = tf.keras.layers.BatchNormalization()(conv)
conv = tf.layers.conv2d(conv, out_dim, [1, 1], strides=[1, 1], kernel_initializer=tf.variance_scaling_initializer,
bias_initializer=tf.zeros_initializer, name="conv{}_2_1x1".format(str(layer_depth)))
conv = tf.layers.batch_normalization(conv)
# ResNet中加载的CBAM模块
conv = cbam_module(conv)
#残差连接:对残差的原始输入shortcut(即input_xs) 与 CBAM模块输出值conv进行求和
output_ys = shortcut + conv
output_ys = tf.nn.relu(output_ys)
return output_ys