1、capsule 网络

Hinton团队在2017年 发表 Dynamic Routing Between Capsules 一文。
capsule是使用向量来表示参数的一组神经元,或者说是特征.文中,capsule是为了改善CNN而提出。更高层的capsule代表了更大局域的图片。capsule network 没有像 CNN 池化层那样丢失信息。

胶囊网络 cnn 胶囊网络为什么不流行_聚类


对于CNN而言,两张图片是类似的,因为它们包含相似的部件,这是胶囊网络希望解决的事情。Reddit上有 Geoffrey Hitton关于pooling的看法。

胶囊网络 cnn 胶囊网络为什么不流行_聚类_02


胶囊网络解决方案:

较为底层的特征(手、眼睛、嘴巴等)将只被传送到与之匹配的高层。如果,底层特征包含的是类似于眼睛或者嘴巴的特征,它将传递到“面部”的高层,如果底层特征包含的是类似手指、手掌等特征,它将传递到“手”的高层。

实现对空间信息进行编码同时也计算物体的存在概率。这可以用向量来表示,向量的模表示特征存在的概率,向量的方向表示特征的姿态信息。

2、模型

2.1 特征聚类
特征聚类是对输入的特征(每个特征都使用向量表示,而不是标量)进行聚类。聚类评价方式有欧氏距离,但是 capsule网络使用内积方式。(使用内积的理由是,考量一个特征和其他特征的关系,欧氏距离每次只能是一个特征和另外一个特征的关系)。

2.2 特征显著性
特征显著性是,表示特征的强弱,因为特征是向量,这里使用向量除了其本身的范数,然后乘于一个与范数相关的非线性函数。

使用输出向量的长度代表这个capsule的存在概率。 并且使用squashing函数(类似激活函数)使得向量的长度在 0~1。

输出向量的压缩:
胶囊网络 cnn 胶囊网络为什么不流行_初始化_03

2.3动态路由
值得注意的是,capsule网络不使用梯度下降的方式,也就是公式中不包含 argmax,而是使用迭代法来进行求解,一般来说,迭代次数K越多,精度越高,同时,会面临梯度消失问题。迭代次数少,那么,就造成精度不够。实际中,K选取1,后面会解释。

2.4 变换矩阵
变换矩阵可以解决,capsule网络提取的信息局限于类中心向量信息,以及为迭代提供更好的特征初始化(如果初始化都一样,迭代就无法收敛了)。

三种包含变换矩阵的capsule网络(左上,没有初始化矩阵;右上,包含初始化矩阵,以及变换阵共享;下,变换矩阵权重不共享,矩阵数目是len(u)*len(v).)

胶囊网络 cnn 胶囊网络为什么不流行_聚类_04

2.5算法

以下是动态路由算法。

v 是输出的胶囊; u是输入的胶囊,不断更新输出的胶囊。

胶囊网络 cnn 胶囊网络为什么不流行_聚类_05


胶囊网络 cnn 胶囊网络为什么不流行_聚类_06

3、实现

参考 苏神开源的capsule 网络。

def squash(x, axis=-1):
    s_squared_norm = K.sum(K.square(x), axis, keepdims=True)
    scale = K.sqrt(s_squared_norm + K.epsilon())
    return x / scale

class Capsule(Layer):
    def __init__(self, num_capsule, dim_capsule, routings = 3, kernel_size= (9,1),
                 share_weights = True, activation = 'default', **kwargs):
        super(Capsule, self).__init__(*kwargs)
        self.num_capsule = num_capsule
        self.dim_capsule = dim_capsule
        self.routings = routings
        self.kernel_size = kernel_size
        self.share_weights = share_weights
        if activation == 'default':
            self.activation = squash
        else:
            self.activation = Activation(activation)
    def build(self, input_shape):
        super(Capsule, self).build(input_shape)
        input_dim_capsule = input_shape[-1]
        if self.share_weights:
            self.W = self.add_weight(name = 'capsule_kernel', shape = 
              (1, input_dim_capsule, self.num_capsule *self.dim_capsule),
            initializer = 'glorot_uniform', trainable = True)
        else:
            input_num_capsules = input_shape[-2]
            self.W = self.add_weight(name = 'capsule_kernel', shape =(input_num_capsule,
                    input_dim_capsule, self.num_capsule * self.dim_capsule),
                    initializer = 'glorot_uniform', trainable = True)
    def call(self, u_vecs):
        if self.share_weights:
            u_hat_vecs = K.conv1d(u_vecs, self.W)
        else: 
            u_hat_vecs = K.local_conv1d(u_vecs, self.W, [1], [1])
        batch_size = K.shape(u_vecs)[0]
        input_num_capsule = K.shape(u_vecs)[1]
        u_hat_vecs =K.reshape(u_hat_vecs, (batch_size, input_num_capsule, 
                                           self.num_capsule,self.dim_capsule))
        u_hat_vecs = K.permute_dimensions(u_hat_vecs, (0, 2, 1, 3))
        b = K.zeros_like(u_hat_vecs[:, :, :, 0])
        for i in range(self.routings):
            b = K.permute_dimensions(b, (0, 2, 1))
            c = K.softmax(b)
            c = K.permute_dimensions(c, (0,2,1))
            b = K.permute_dimensions(b, (0, 2, 1))
            outputs = self.activation(K.batch_dot(c, u_hat_vecs, [2, 2]))
            if i < self.routings -1:
                b = K.batch_dot(outputs, u_hat_vecs, [2,3])
        return outputs 
    def compute_output_shape(self, input_shape):
        return (None, self.num_capsule, self.dim_capsule)

调用

input_image = Input(shape=(None, None, 1))
input_image1 = Reshape((-1, 52))(input_image)
print(input_image1.shape)
capsule = Capsule(10, 52, 3, True)(input_image1)
print(capsule.shape)
output_shape=(num_classes,))(capsule)
capsule = Flatten()(capsule)

x = Dropout(0.2)(Activation(activation="relu")(BatchNormalization()(Dense(220)(capsule))))

output = Dense(num_classes, activation="softmax")(x)
print(output.shape)

model = Model(inputs=input_image, outputs=output)
model.compile(loss= 'categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

model.summary()

4 感想

胶囊网络是一种用一个向量表示一个特征,并称作为胶囊,希望输入胶囊能聚成几类,每类用一个输出胶囊表示。为了实现聚类,使用内积来衡量每个输出胶囊和各个输入胶囊的相似度,并且这个过程使用迭代的方式来完成,也称作动态路由算法。
capsule 抛弃了卷积和池化的观念,引入 聚类的概念。 一些博文说到, capsule具有更好的表示输入数据的结构的性能。
细节:输入胶囊经过一个变换矩阵,得到更加丰富表示,防止输入胶囊只能学习到输入胶囊的中心向量。

Q1:为什么使用迭代方法,而不是梯度下降法?
输出是输入的聚类,而聚类通常都需要迭代算法。引入的变换阵还是需要梯度下降法来完成。
Q2:为什么使用变换阵,好处?
丰富学习输入胶囊信息,防止只学习到所有输入胶囊的中心向量。
输入向量与权重矩阵的矩阵乘法。这编码了图像中低级特征和高级特征之间非常重要的空间关系。
Q3:为什么使用胶囊网络,好处?
基于聚类思想来代替池化完成特征整合的方案,特征表达能力更加强大。
Q4:既然胶囊网络是想把比较相近的特征放到同一层,那么,胶囊网络设计时是否也是由多层网络组成?看到开源的代码都只有两层网络,一层输入层,一层输入层,这样是不是代表所有相关的特征都提取到最后那一层?
应该是高层的一个胶囊,代表它那类的特征信息。