论文笔记-再看 Capsules 以及 capsules 在文本分类上的应用

参考:

再看 Capsules

苏剑林同学在他的那篇博客中对 capsule 的理解很有道理,胶囊也就是用 “vector in vector out” 取代了 “scaler in scaler out”。

在我的上一篇 blog 中在 PrimaryCaps 模块中,将前一步通过卷积得到的输出是 [batch, 20, 20, 256]. 经过 PrimaryCaps 第一步 affine-transform 转换成 [batch, 6, 6, 32, 8]. 实际上就是在这一步将一个像素点的特征转换成了一个 8d-vector 的胶囊。

事实上,在 NLP 的任务中,这种用向量来表示一维特征的做法确实最基本的。比如 one-hot 向量,word2vec 将词转换成 dense vector.

在传统的神经网络中,从低层次的特征逐步抽象,归纳为高层次的特征,是通过权重加权求和得到的,比如卷积啊,全连接都是这样,然后通过梯度反向传播,更新这些权重参数。

这个过程某种程度上模拟了人的层次分类做法,从而完成对最终目标的输出,并且具有比较好的泛化能力。的确,神经网络应该是这样做的,然而它并不能告诉我们它确确实实是这样做的,这就是神经网络的难解释性,也就是很多人会将深度学习视为黑箱的原因之一。

而 Hiton 提出了 Capsule 就具有很好的可解释性,那么其中的 “抛弃梯度下降” 又是怎么一回事呢?苏神在 blog 中给了很好的解释。

胶囊的计算

在前面的 blog 中我们已经理解了什么是“胶囊”。神经元是标量,胶囊就是向量!Hinton的理解是:每一个胶囊表示一个属性,而胶囊的向量则表示这个属性的“标架”。也就是说,我们以前只是用一个标量表示有没有这个特征(比如有没有羽毛),现在我们用一个向量来表示,不仅仅表示有没有,还表示“有什么样的”(比如有什么颜色、什么纹理的羽毛),如果这样理解,就是说在对单个特征的表达上更丰富了。不仅如此,上一篇 blog 中有提到的 CNN 中的不足,主要在于两点,一是 max pooling 丢失了部分信息(这在低层次的layer中可能影响不大,但是在高层次的layer就会有比较大的影响),二是 CNN 不能提取低维特征与高维特征之间在空间中的相对位置信息。而胶囊的方向能表示这一部分信息。比如下面这张图就很明显的表示出来了。

从低层次胶囊到高层次胶囊的计算细节,主要分为 3 个步骤:

  • affine transform
  • weighting and sum
  • squash

如果考虑更多的胶囊,可以抽象到下面这张图。

我们只关注其中的某一部分就是:

关于从低层次特征如何整合到高层次特征以及这样做的原因是啥,苏同学这里说的是相同透彻了。

动态路由

在前面的blog中我们差不多能理解:动态路由实际上就是 低层次的胶囊将部分的自己交付给高层次的胶囊,而这个部分的权重却又取决于 低层次胶囊和高层次胶囊的相关性。

我们原本也是可以通过反向传播来解决这个问题的(不显示的计算出相关性,而是直接用神经网络来代替,不知是否可用梯度下降来处理胶囊?),而 Hinton 使用的动态路由算法可解释更强。

动态路由算法就是来解决这个权重分配的问题。

对于动态路由的理解,苏同学给上了两道小菜。总的理解就是,高层胶囊在启动阶段,我们并不知道它是多少,那么前面的相似度也就没法计算。于是,我们只能初始化一个值,也就是取低层次胶囊的均值。如同上图中第二步将 \(b_ij\) 设置成 0,那么低层次胶囊分配给高层次胶囊的权重 \(c_{ij}\) 就都是相等的。

\[c_{ij}=\dfrac{exp(b_{ij})}{\sum_k exp(b_ik)}\]

然后反复迭代。说白了,输出是输入的聚类结果,而聚类通常都需要迭代算法,这个迭代算法就称为“动态路由”。至于这个动态路由的细节,其实是不固定的,取决于聚类的算法,比如关于Capsule的新文章《MATRIX CAPSULES WITH EM ROUTING》就使用了Gaussian Mixture Model来聚类。

共享版 or 全连接版

全连接版

在 Capsule 中,低层次特征是通过普通的卷积神经网络提取,然后通过一个矩阵变换得到的。其中的 \(W\) 是需要学习的参数。\(v_j\) 是作为输入 \(u_i\) 的某种聚类中心出现的,而从不同角度看输入,得到的聚类结果显然是不一样的。那么为了实现“多角度看特征”,于是可以在每个胶囊传入下一个胶囊之前,都要先乘上一个矩阵做变换.

\[v_j = \text{squash}\sum_i\dfrac{e^{<\hat u_{j|i}, v_j>}}{\sum_k e^{<\hat u_{k|i}, v_k>}}\hat u_{j|i}, \hat u_{j|i}=W_{ij}u_i\]

共享版

全连接层只能处理定长输入,全连接版的Capsule也不例外。而CNN处理的图像大小通常是不定的,提取的特征数目就不定了,这种情形下,全连接层的Capsule就不适用了。因为在前一图就可以看到,参数矩阵的个数等于输入胶囊数目乘以输出胶囊数目,既然输入数目不固定,那么就不能用全连接了。

所以跟CNN的权值共享一样,我们也需要一个权值共享版的Capsule。所谓共享版,是指对于固定的上层胶囊j,它与所有的底层胶囊的连接的变换矩阵是共用的,即 \(W_{ji}≡W_j\).

采用 Hiton 论文中的参数来计算就是:
- 输入 [batch, 6, 6, 32, 8]=[batch, 1152, 8], 输入有 6x6x32=1152 个 capsules.
- 输出 [batch, 16, 10], 输出有 10 个 capsules.

对于全连接版,权重参数是 \(1152\times 8\times 16\times N + 1152\times N + 1152\times N\). N 表示 low-level capsules 的数目。
对于共享版, 权重参数是 \(8\times 16\times 1152 + 1152 + 1152\).

反向传播

现在又有了 \(W_{ji}\),那么这些参数怎么训练呢?答案是反向传播。读者也许比较晕的是:现在既有动态路由,又有反向传播了,究竟两者怎么配合?其实这个真的就最简单不过了。从形式上来看,就是往模型中添加了三层罢了,剩下的该做什么还是什么,最后构建一个loss来反向传播。

这样看来,Capsule里边不仅有反向传播,而且只有反向传播,因为动态路由已经作为了模型的一部分,都不算在迭代算法里边了。

capsules 在文本分类上的应用

Investigating Capsule Networks with Dynamic Routing for Text Classification

Model Architecture

N-gram Convolutional Layer

普通的卷积操作。

输入:[batch, L, embed_size, 1]
输出:[batch, L-k1+1, 1, B]

其中:

  • kernel: [k1, embed_size, 1, B]
  • B 表示卷积核的个数
  • k1 是sentence 长度维度上的 sliding-window 尺寸

Primary Capsule Layer

初始化成 capsules, 但依然只是简单的卷积操作。

输入:[batch, L-k1+1, 1, B]
输出:[batch, L-k1+1, 1, C, d]

其中:

  • d 表示 capsule 的维度
  • 实际上依然是普通的卷积操作,不同的是,原本是从 channels B 到 channels C.现在每个 channels C 对应的有 d 个。也就是初始化的 capsules.
  • kernel: [1, 1, B, C*d], 实现时先生成 C*d channels, 然后 split.

Convolutional Capsule Layer

从低层次 feature 到高层次 feature, 在 Hinton 中是capsules版的全连接,在这里是 capsules 版的卷积操作,其中涉及到动态路由算法。

输入:[batch, L-k1+1, 1, C, d]
输出:[batch, L-k1-k2+2, 1, D, d]

其中:
- 输出的 capsules 维度依旧是 d
- 但是 capsules 的个数发生了变化,在 Hinton 论文中是通过全连接维度的变换,这里是通过卷积的操作来实现 capsules 个数的变换的。

与 Hinton 的论文类似,第一步是 affine transform 矩阵变换操作,在这篇 paper 中,作者提出了两种方式,实际上就是苏同学博客中的全连接版和共享版(低层次的 capsules 是否共享同样的矩阵变换参数)。

  • shared: \(W\in R^{N\times d\times d}\). N 是 capsules 的个数
  • no-shared: \(W\in R^{H\times N\times d\times d}\).H 是低维的 capsules 的个数。