SENet 是 ImageNet 2017(ImageNet 收官赛)的冠军模型,具有复杂度低,参数少和计算量小的优点。另外,SENet 思路很简单,很其中的 SE 模块很容易扩展在已有网络结构如 Inception 和 ResNet 中。
已经有很多工作在空间维度上来提升网络的性能,如 Inception 等,而 SENet 将关注点放在了特征通道之间的关系上。其具体策略为:通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征并抑制对当前任务用处不大的特征,这又叫做“特征重标定”策略。具体的 SE 模块如下图所示:
给定一个输入 x x x,其特征通道数为 c 1 c_1 c1,通过一系列卷积等一般变换 F t r F_{tr} Ftr 后得到一个特征通道数为 c 2 c_2 c2 的特征。与传统的卷积神经网络不同,我们需要通过下面三个操作来重标定前面得到的特征。
SE模块的灵活性在于它可以直接应用现有的网络结构中。以 Inception 和 ResNet 为例,我们只需要在 Inception 模块或 Residual 模块后添加一个 SE 模块即可。具体如下图所示:
上图分别是将 SE 模块嵌入到 Inception 结构与 ResNet 中的示例,方框旁边的维度信息代表该层的输出,r 表示 Excitation 操作中的降维系数。
SE 模块很容易嵌入到其它网络中,为了验证 SE 模块的作用,在其它流行网络如 ResNet 和 VGG 中引入 SE 模块,测试其在 ImageNet 上的效果,如下表所示:
import tensorflow as tf
class Squeeze_excitation_layer(tf.keras.Model):
def __init__(self, filter_sq, filter_ex):
# filter_sq 是 Excitation 中第一个卷积过程中卷积核的个数
# filter_ex 是 Excitation 中第二个卷积过程中卷积核的个数
super().__init__()
self.filter_sq = filter_sq
self.filter_ex = filter_ex
self.avepool = tf.keras.layers.GlobalAveragePooling2D()
self.dense1 = tf.keras.layers.Dense(filter_sq)
self.relu = tf.keras.layers.Activation('relu')
self.dense2 = tf.keras.layers.Dense(filter_ex)
self.sigmoid = tf.keras.layers.Activation('sigmoid')
def call(self, inputs):
squeeze = self.avepool(inputs)
excitation = self.dense1(squeeze)
excitation = self.relu(excitation)
excitation = self.dense2(excitation)
excitation = self.sigmoid(excitation)
excitation = tf.keras.layers.Reshape((1, 1, self.filter_ex))(excitation)
scale = inputs * excitation
return scale
SE = Squeeze_excitation_layer(16, 32)
inputs = np.zeros((1, 32, 32, 32), dtype=np.float32)
SE(inputs).shape
TensorShape([1, 32, 32, 32])
这里只提供一个简单的实现代码来展示 SE 模块在 ResNet 网络中的应用,暂不涉及 ResNet 中的下采样过程。有关 ResNet 的实现过程可以参考 Tensorflow2.0之自定义ResNet 一文。
import tensorflow as tf
class SEBottleneck(tf.keras.Model):
def __init__(self, stride=1):
super().__init__()
self.conv1 = tf.keras.layers.Conv2D(16, kernel_size=1)
self.bn1 = tf.keras.layers.BatchNormalization()
self.conv2 = tf.keras.layers.Conv2D(16, kernel_size=3, strides=stride, padding='same')
self.bn2 = tf.keras.layers.BatchNormalization()
self.conv3 = tf.keras.layers.Conv2D(32, kernel_size=1)
self.bn3 = tf.keras.layers.BatchNormalization()
self.relu = tf.keras.layers.Activation('relu')
self.se = Squeeze_excitation_layer(16, 32)
def call(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
out = self.se(out)
out += residual
out = self.relu(out)
return out
SEB = SEBottleneck()
inputs = np.zeros((1, 32, 32, 32), dtype=np.float32)
SEB(inputs).shape
TensorShape([1, 32, 32, 32])