源码看的是基于keras实现的版本,代码在这里。CRFasRNN把fcn作为骨干网络,然后把fcn的输出作为CRF的二元势的初始化和一元势,前面fcn的部分不再赘述,文章中提出的CRFasRNN作为单独一层用TensorFlow实现,使用起来很简单:
output = CrfRnnLayer(image_dims=(height, weight),
num_classes=21,
theta_alpha=160.,
theta_beta=3.,
theta_gamma=3.,
num_iterations=10,
name='crfrnn')([upscore, img_input])
高斯核的参数theta_alpha
,theta_beta
,theta_gamma
作为超参数直接给出,没有参与训练。
CrfRnnLayer
的定义在crfrnn_layer.py
中,继承了layer
类,其中,build
方法通过add_weight
函数定义了该层可以训练的参数:
def build(self, input_shape):
# Weights of the spatial kernel
self.spatial_ker_weights = self.add_weight(name='spatial_ker_weights',
shape=(self.num_classes, self.num_classes),
initializer=_diagonal_initializer,
trainable=True)
# Weights of the bilateral kernel
self.bilateral_ker_weights = self.add_weight(name='bilateral_ker_weights',
shape=(self.num_classes, self.num_classes),
initializer=_diagonal_initializer,
trainable=True)
# Compatibility matrix
self.compatibility_matrix = self.add_weight(name='compatibility_matrix',
shape=(self.num_classes, self.num_classes),
initializer=_potts_model_initializer,
trainable=True)
super(CrfRnnLayer, self).build(input_shape)
其中,spatial_ker_weights
表示spatial kernel的权重,bilateral_ker_weights
表示bilateral kernel的权重 compatibility_matrix
表示相似度矩阵。
将DenseCRF变分推断的迭代求解过程表示成RNN的形式,方便和CNN骨干网络共同进行参数更新。要学习的参数是:Gaussian kernels的参数,label compatibility function等等。使用随机梯度下降学习参数。
Q i ( l ) ← 1 Z i exp ( U i ( l ) ) for all i Q_{i}(l) \leftarrow \frac{1}{Z_{i}} \exp \left(U_{i}(l)\right) \text { for all } i Qi(l)←Zi1exp(Ui(l)) for all i
CRF的一元势的初始化由CNN网络的输出结果直接给出: Q i ( l ) ← 1 Z i exp ( U i ( l ) ) Q_{i}(l) \leftarrow \frac{1}{Z_{i}} \exp \left(U_{i}(l)\right) Qi(l)←Zi1exp(Ui(l)),其中 Z i = ∑ l exp ( U i ( l ) ) Z_{i}=\sum_{l} \exp \left(U_{i}(l)\right) Zi=∑lexp(Ui(l))对每分配给 i i i像素的每个标签的概率值进行归一化。
这个操作可以直接由 s o f t m a x softmax softmax完成,不需要参数。
softmax_out = tf.nn.softmax(q_values, 0)
Q ~ i ( m ) ( l ) ← ∑ j ≠ i k ( m ) ( f i , f j ) Q j ( l ) for all m \tilde{Q}_{i}^{(m)}(l) \leftarrow \sum_{j \neq i} k^{(m)}\left(\mathbf{f}_{i}, \mathbf{f}_{j}\right) Q_{j}(l) \text { for all } m Q~i(m)(l)←j̸=i∑k(m)(fi,fj)Qj(l) for all m
massage passing是用M个高斯滤波器在 Q Q Q上进行滤波操作来实现的。使用high dimensional Gaussian filtering加速。
# Spatial filtering
spatial_out = custom_module.high_dim_filter(softmax_out, rgb, bilateral=False,
theta_gamma=self.theta_gamma)
spatial_out = spatial_out / spatial_norm_vals
# Bilateral filtering
bilateral_out = custom_module.high_dim_filter(softmax_out, rgb, bilateral=True,
theta_alpha=self.theta_alpha,
theta_beta=self.theta_beta)
bilateral_out = bilateral_out / bilateral_norm_vals
其中,spatial_norm_vals
和bilateral_norm_vals
是归一化系数:
c, h, w = self.num_classes, self.image_dims[0], self.image_dims[1]
all_ones = np.ones((c, h, w), dtype=np.float32)
# Prepare filter normalization coefficients
spatial_norm_vals = custom_module.high_dim_filter(all_ones, rgb, bilateral=False,
theta_gamma=self.theta_gamma)
bilateral_norm_vals = custom_module.high_dim_filter(all_ones, rgb, bilateral=True,
theta_alpha=self.theta_alpha,
theta_beta=self.theta_beta)
Q ˇ i ( l ) ← ∑ m w ( m ) Q ~ i ( m ) ( l ) \check{Q}_{i}(l) \leftarrow \sum_{m} w^{(m)} \tilde{Q}_{i}^{(m)}(l) Qˇi(l)←m∑w(m)Q~i(m)(l)
mean-field iteration的下一步是对上一步用M个滤波器滤波的结果进行求和,对每个类别标签 l l l,如果每个类别单独考虑,就相当于在M个通道上进行1*1的卷积操作,并且得到一个通道数为1的输出。这里的参数也可以用反向传播来学习。为了得到更多的可调节参数,对每个类别 l l l,我们使用独立的核权重。这样做的动机是spatial kernel 与 bilateral kernel的相对重要性取决于目标的类别。例如,bilateral kernel在自行车检测中可能具有很高的重要性,因为颜色的相似性是决定性的;另一方面,由于电视屏幕内的任何东西可能有不同的颜色,它们对电视检测的重要性可能较低。
张量的维度从w* h* M–>w* h* L
在实现时直接使用等效的全连接操作替代。
# Weighting filter outputs
message_passing = (tf.matmul(self.spatial_ker_weights,
tf.reshape(spatial_out, (c, -1))) +
tf.matmul(self.bilateral_ker_weights,
tf.reshape(bilateral_out, (c, -1))))
# tf.matmul矩阵乘法
Q ^ i ( l ) ← ∑ l ′ ∈ L μ ( l , l ′ ) Q ˇ i ( l ) \hat{Q}_{i}(l) \leftarrow \sum_{l^{\prime} \in \mathcal{L}} \mu\left(l, l^{\prime}\right) \check{Q}_{i}(l) Q^i(l)←l′∈L∑μ(l,l′)Qˇi(l)
标签的相似性函数 μ ( l , l ′ ) \mu (l,l') μ(l,l′)参数化表示了两个标签 l l l和 l ′ l' l′的相似性,使用Potts model: μ ( l , l ′ ) = [ l ≠ l ′ ] \mu(l,l')=[l \neq l'] μ(l,l′)=[l̸=l′],其中, [ . ] [.] [.]表示Iverson bracket:[P] has the value 1 if P is true, and 0 if P is false,如果将不同的标签分配给具有相似属性的像素,则指定固定的惩罚。该模型的一个局限性是,它为所有不同的标签分配相同的惩罚。一个直观的理解是,通过考虑不同标签对之间的兼容性并相应地惩罚赋值,可以获得更好的结果。
Compatibility Transform可以用另一个卷积操作来表示,卷积核尺寸是1*1,输入和输出的通道数都为L,学习到的卷积核的权重就等于学习标签的兼容性函数 μ \mu μ。
张量的维度从w* h* L–>w* h* L
在实现时直接使用等效的全连接操作替代。
# Compatibility transform
pairwise = tf.matmul(self.compatibility_matrix, message_passing)
Q ˘ i ( l ) ← U i ( l ) − Q ^ i ( l ) \breve{Q}_{i}(l) \leftarrow U_{i}(l)-\hat{Q}_{i}(l) Q˘i(l)←Ui(l)−Q^i(l)
直接让两个张量相减。
张量的维度从w* h* L–>w* h* L
# Adding unary potentials
pairwise = tf.reshape(pairwise, (c, h, w))
q_values = unaries - pairwise
Q i ← 1 Z i exp ( Q ˘ i ( l ) ) Q_{i} \leftarrow \frac{1}{Z_{i}} \exp \left(\breve{Q}_{i}(l)\right) Qi←Zi1exp(Q˘i(l))
再进行一次softmax操作。
在文章的其他部分,我们可以知道变分推断的一个迭代流程可以表示为一系列CNN层的堆叠,我们使用 f θ f_{\theta} fθ函数表示变分推断一个流程的迭代:
输入:
输入图像 I I I,像素级别的一元势的值 U U U,和边缘概率的估计值(即为每个像素属于每个类别的概率) Q i n Q_{in} Qin,这个可以从上次迭代或者初始化过程中得到。
输出:
下次变分推断的迭代结果可以表示为: f θ ( U , Q i n , I ) f_{\theta}(U,Q_{in},I) fθ(U,Qin,I),向量 θ = { w m , μ ( l , l ′ ) } , m ∈ { 1 , . . . , M } , l , l ′ ∈ { l 1 , . . . , l L } \theta =\{ w^{m},\mu(l,l') \},m \in \{1,...,M \},l,l' \in \{l_1,... , l_L \} θ={wm,μ(l,l′)},m∈{1,...,M},l,l′∈{l1,...,lL}表示 C R F CRF CRF的参数。
可以通过重复实现多个平均场迭代,上述叠层以这样的方式堆叠,每个层迭代从先前的迭代获取Q值估计,和它们的原始形式的一元值。这是等价的将迭代平均场推断处理为循环的神经网络(RNN)。网络的行为由以下公式给出,其中H1、H2是隐藏的。状态和T是平均场迭代次数:
H 1 ( t ) = { softmax ( U ) , t = 0 H 2 ( t − 1 ) , 0 < t ≤ T H_{1}(t)=\left\{\begin{array}{ll}{\operatorname{softmax}(U),} & {t=0} \\ {H_{2}(t-1),} & {0<t \leq T}\end{array}\right. H1(t)={softmax(U),H2(t−1),t=00<t≤T
H 2 ( t ) = f θ ( U , H 1 ( t ) , I ) , 0 ≤ t ≤ T H_{2}(t)=f_{\boldsymbol{\theta}}\left(U, H_{1}(t), I\right), \quad 0 \leq t \leq T H2(t)=fθ(U,H1(t),I),0≤t≤T
Y ( t ) = { 0 , 0 ≤ t < T H 2 ( t ) , t = T Y(t)=\left\{\begin{array}{ll}{0,} & {0 \leq t<T} \\ {H_{2}(t),} & {t=T}\end{array}\right. Y(t)={0,H2(t),0≤t<Tt=T
FCN输出的结果作为二元势的初始化和一元势输入,使用SGD更新参数。