Context Prior for Scene Segmentation-设计注意力机制模块加入上下文先验信息–关键创新点代码复现-tensorflow2.x、keras。
论文
:
Context Prior for Scene Segmentation
代码github
:
暂未开源
最近在研究注意力机制与先验信息,查阅到这篇论文,但是暂未开源代码,便根据对论文内容的理解复现论文方法,使用TensorFlow2.x版本,基于Keras编写代码
论文介绍
摘要
最近的语义分割工作广泛探索了上下文相关性,以实现更准确的细分结果。但是,大多数方法很少区分不同类型的上下文依赖项,这可能会有损场景理解。在这项工作中,我们直接监督特征聚合以清楚地区分类内和类间上下文信息。具体来说,我们在Affinity Loss监督下开发出上下文先验。 给定输入图像和相应的gt,Affinity Loss将构建理想的类似的特征图,以监督上下文先验的学习。 所学习的上下文先验提取属于同一类别的像素,而相反的先验则专注于不同类别的像素。 通过嵌入到传统的深度CNN中,提出的上下文先验层可以选择性地捕获类内和类间上下文相关性,从而实现可靠的特征表示。 为了验证有效性,我们设计了有效的上下文先验网络(CPNet)。大量的定量和定性评估表明,所提出的模型与最新的语义分割方法相比具有良好的表现。 更具体地说,我们的算法在ADE20K上达到46.3%的mIoU,在PASCAL-Context上达到53.9%的mIoU,在Cityscapes上达到81.3%的mIoU【参考
博客
】
关键点
提出以上模块,说的是通过生成的Ideal Affinity Map向网络中加入像素值的先验信息去监督网络对类内和内间学习(
我的理解就是相当于把原标签转为另一种表达方式与网络的某一层输出的特征图求损失,其实相比于其他分割网络最后输入的损失计算,换汤不换药!!!
)。该模块可加在任意主干网络后,用于对主干网络生成的特征图进行再一次的特征提取。
开整代码
导入依赖包
from tensorflow import keras
from tensorflow.keras import layers
import tensorflow.keras.backend as K
from CoordConv2 import Coord_Conv2d
import tensorflow as tf
Aggregation模块
把3X3的卷积核分成3X1和1X3两个,效果相似,参数计算大量减少。
def Aggregation(x, num_classes):
x = layers.Conv2D(filters=num_classes, kernel_size=3, padding='same')(x)
x = layers.BatchNormalization()(x)
x = layers.Activation('relu')(x)
x1 = layers.DepthwiseConv2D(kernel_size=(1, 3), padding='same')(x)
x1 = layers.DepthwiseConv2D(kernel_size=(3, 1), padding='same')(x1)
x2 = layers.DepthwiseConv2D(kernel_size=(3, 1), padding='same')(x)
x2 = layers.DepthwiseConv2D(kernel_size=(1, 3), padding='same')(x2)
x = layers.Add()([x1, x2])
x = layers.BatchNormalization()(x)
x = layers.Activation('relu')(x)
return x
CP过程
def CP(x):
B, H, W, C = x.shape
x_origon = x
x_origon = tf.reshape(x_origon, [-1, H*W, C])
x = layers.Conv2D(filters=H*W, kernel_size=1, padding='same')(x)
x = layers.BatchNormalization()(x)
x = layers.Activation('sigmoid')(x)
x = tf.reshape(x, [-1, H*W, H*W], name='out1') # 论文中P,Context Prior Map
x1 = tf.matmul(x, x_origon)
x1 = tf.reshape(x1, [-1, H, W, C])
x2 = tf.matmul(tf.ones_like(x)-x, x_origon)
x2 = tf.reshape(x2, [-1, H, W, C])
return x, x1, x2
CPModule
def CPModule(x, num_classes):
x1 = Aggregation(x, num_classes)
x2, x3, x4 = CP(x1)
output = layers.Concatenate()([x, x3, x4])
return x2, output
x2用于论文提出的AffinityLoss计算,output用于普通的softmax损失计算
Affinity Loss
loss_1
loss_binary = keras.losses.binary_crossentropy(Ideal_Affinity_Map, Context_Prior_Map)
loss_2
Tp = K.log(K.sum(tf.multiply(Ideal_Affinity_Map, Context_Prior_Map), axis=1) / (K.sum(Context_Prior_Map, axis=1)+K.epsilon())+K.epsilon())
Tr = K.log(K.sum(tf.multiply(Ideal_Affinity_Map, Context_Prior_Map), axis=1) / (K.sum(Ideal_Affinity_Map,
axis=1)+K.epsilon()) + K.epsilon())
Tj = K.log((K.sum((1.0-Ideal_Affinity_Map)*(1.0-Context_Prior_Map), axis=1)+K.epsilon())/(K.sum(1.0-Ideal_Affinity_Map, axis=1)+K.epsilon())+K.epsilon())
loss = -K.mean(T1+T2+T3)
总损失
loss+loss_binary
代码汇总
Ideal Affinity Map的生成论文中是这样描述的
对于DownSample操作并没有清除的说明用的是什么方法,因此我这儿采用的是多次最大值池化,由于我的标签本就是ONE-HOT标签,所以就不存在论文中提及的
one-hot encoding
操作。具体标签制作详见我的另一篇
博客
。
由于我的主干网络输出的特征图是32X32大小,直接将标签降到特征图大小会丢掉很多目标特征(
所以对原论文说的把标签图下采样到特征图尺寸是否保留目标的特征信息持怀疑态度,或者作者使用了巧妙的下采样方式就另说了
),经试验证明,在我使用的标签图中,下采样到128X128大小,目标的特征信息不会损失。但是实验使用cpu做的,[128X128, 128X128]与[128X128, 128X128]的矩阵运算算不了,所以实验采用的是把标签下采样到
64X64
大小。原标签尺寸为
512X512
使用
tf.image
中的resize函数下采样可
避免特征丢失问题
!!
def Affinity_loss(y_true, y_pred):
y_true_down = tf.image.resize(y_true, (256, 256), methord=tf.image.ResizeMethord.NEAREST_NEIGHBOR)
y_true_down = y_true_down = tf.image.resize(y_true_down , (128, 128), methord=tf.image.ResizeMethord.NEAREST_NEIGHBOR)
y_true_down = y_true_down = tf.image.resize(y_true_down , (64, 64), methord=tf.image.ResizeMethord.NEAREST_NEIGHBOR)
y_true_down = y_true_down = tf.image.resize(y_true_down , (32, 32), methord=tf.image.ResizeMethord.NEAREST_NEIGHBOR)
B_down, H_down, W_down, C_down = y_true_down.shape
y_true_down = tf.reshape(y_true_down, [-1, H_down*W_down, C_down])
Ideal_Affinity_Map = tf.matmul(y_true_down, tf.transpose(y_true_down, [0, 2, 1]))
Ideal_Affinity_Map = tf.cast(Ideal_Affinity_Map, dtype=tf.float32)
Context_Prior_Map = tf.cast(y_pred, dtype=tf.float32)
loss_binary = keras.losses.binary_crossentropy(Ideal_Affinity_Map, Context_Prior_Map)
T1 = K.log(K.sum(tf.multiply(Ideal_Affinity_Map, Context_Prior_Map), axis=1) / (K.sum(Context_Prior_Map, axis=1)+K.epsilon())+K.epsilon())
T2 = K.log(K.sum(tf.multiply(Ideal_Affinity_Map, Context_Prior_Map), axis=1) / (K.sum(Ideal_Affinity_Map,
axis=1)+K.epsilon()) + K.epsilon())
T3 = K.log((K.sum((1.0-Ideal_Affinity_Map)*(1.0-Context_Prior_Map), axis=1)+K.epsilon())/(K.sum(1.0-Ideal_Affinity_Map, axis=1)+K.epsilon())+K.epsilon())
loss = -K.mean(T1+T2+T3)
return loss+loss_binary
Context_Prior_Map 和 Ideal_Affinity_Map
Context_Prior_Map

Ideal_Affinity_Map
总结
总的来说,对上下文先验矩阵Ideal_Affinity_Map的预测还是比较接近论文贴出的结果