Capsule Network

Capsule Network最大的特色在于vector in vector out & 动态路由算法。

vector in vector out

所谓vector in vector out指的是将原先使用标量表示的神经元变为使用向量表示的神经元。这也即是所谓的“Capsule”,“vector in vector out”或者“胶囊”所要表达的意思。按照Hinton的理解,每一个胶囊表示一个属性,而胶囊的向量则表示该特征的某些“含义”。比如,之前我们使用标量表示有没有羽毛,现在我们使用向量来表示,不仅表示有没有,还表示了有什么颜色,什么材料的特征。也就是说将神经元从标量改为向量后,在特征提取时,对单个特征的表达更为丰富了。

这有些像NLP中的词向量,之前使用的是one hot表示一个词,只能表示有没有该词而已,引入了word2vec不但表示有没有,而且能够表示该词的“意思”,表意更加丰富了。

部分人叫“Capsule Network胶囊网络”为“张量网络”

  • 层层抽象,层层分类

    上图展示了特征$u1$的连接,目前从上一层传来的特征$u1$假设表示羽毛,下一层抽取得到的$v1,v2,v3,v4$分别表示猫、狗、兔、鸟4个种类。可以很容易想到softmax: $$ (p{1|1},p{2|1},p{3|1},p{4|1})=\frac{1}{Z1}(e^{u1^T v1},e^{u1^T v2},e^{u1^T v3},e^{u1^T v4}),\quad Z1=\sum{i=0}^4e^{u1^T vi} $$ 我们一般选择最大的那个概率就行了,但是单靠这一个特征是不够的,我们需要综合各个特征,可以把上面的softmax对各个特征都做一遍,获得$(p{1|2},p{2|2},p{3|2},p{4|2}),(p{1|3},p{2|3},p{3|3},p{4|3})...(p{1|5},p{2|5},p{3|5},p_{4|5})$。这时就需要融合这些特征,很容易想到“加权和”

    对于特征$ui$,获得的特征分布为$(p{1|i},p{2|i},p{3|i},p{4|i})$,加权和就是:$\sumi uip{j|i}(j=1,2,3,4)$。对于希望获得向上一层的特征$vj$,这里paper中使用了一种*squash*函数处理后,就变成了向上一层的特征$vj$,也即: $$ vj=squash(\sumi p{j|i}ui)=squash(\sumi\frac{e^{ui^T vj}}{Zi}u_i) $$ 所谓squash函数就是Capsule Network中的激活函数,和ReLU, tanh, sigmoid函数作用类似,原文中的函数如下: $$ squash(x)=\frac{||x||^2}{1+||x||^2}\frac{x}{||x||} $$ 后半部分$\frac{x}{||x||}$就是将向量模变为1,前半部分$\frac{||x||^2}{1+||x||^2}$是一个缩减函数,函数值始终小于1。在$||x||$近于0时有放大作用,$||x||$越大,越没有影响。

    关于前半部分$\frac{||x||^2}{1+||x||^2}$,将模长压缩至0~1有很多方法,比如$tan||x||,1-e^{-||x||}$,是不是$\frac{||x||^2}{1+||x||^2}$这种压缩方式最好;如果在中间层,这个压缩处理是否有必要,由于有动态路由在里面,即使去掉squash函数也具有非线性;另外分母上的常数1哪来的?可不可以尝试其他常数实验效果,这会是一个超参数吗

    动态路由

    注意到: $$ vj=squash(\sumi p{j|i}ui)=squash(\sumi\frac{e^{ui^T vj}}{Zi}ui) $$ 为了求$vj$需要求softmax,而为了求softmax又需要知道$v_j$,似乎陷入了鸡生蛋,蛋生鸡的问题,这就是动态路由(Dynamic Routing)要解决的问题,它能够“自主更新”参数,从而达到Hinton放弃梯度下降的目标。

    看一个NLP的例子,考虑一个向量$(x1,x2,...,xn)$,现在希望将n个向量整合成一个向量$x$(encoder)。简化下情况,仅仅使用原来向量的线性组合,也就是: $$ x=\sum{i=1}^n \lambdaixi $$ 这里的$\lambdai$相当于衡量了$x$与$xi$的相似度,衡量了$xi$在最终的$x$中的重要程度,这也是一个*鸡生蛋,蛋生鸡*的问题,解决方法就是*迭代*。定义一个基于softmax的相似度指标,然后“加权和”: $$ x=\sum{i=1}^n\frac{e^{x^Txi}}{Z}xi $$ 一开始,我们将$x$初始化为各个$x_i$的均值,然后将$x$带入右侧,左侧得到一个新的$x$,然后再将其带入右侧,如此反复,一般迭代有限次即可收敛。

    为了得到得到各个$vj$,一开始先将它们初始化为$ui$的均值,然后带入softmax迭代求解即可。事实上,输出是输入的聚类结果,而聚类通常都需要迭代算法,这个迭代算法即是“动态路由”。至于这个动态路由的细节,其实是不固定的,取决于聚类的算法。在Dynamic Routing Between Capsules. NIPS 2017 一文中,路由算法为: $$ \begin{aligned} &动态路由算法version1\ \ &初始化b{ij}=0 \ &迭代r次:\ &\qquad ci \gets softmax(bi);\ &\qquad sj\gets\sumic{ij}ui;\ &\qquad vj\gets squash(sj);\ &\qquad b{ij}\gets b{ij}+ui^Tvj \end{aligned} $$ 这里的$c{ij}$即是前文中的$p_{j|i}$。

    这里有人认为原文中的$b{ij}\gets b{ij}+ui^Tvj$有误,应为$b{ij}\gets ui^Tv_j$。没有看懂什么意思,不作评价。

    补充下聚类算法K-Means的伪代码: $$ \begin{aligned} 输入:&样本集D={x1,x2,...,xm};\ &聚类簇数k.\ 过程:\ &从D中随机选择k个样本作为初始均值向量{\mu1,\mu2,...,\muk}\ &repeat\ &\quad 令Ci=\phi(1\leq i \leq k)\ &\quad for\ j=1,2,...,m\ do\ &\qquad 计算样本xj 与各个均值向量\mui(1\leq i\leq k)的距离:d{ij}=||xj-\mui||2;\ &\qquad 根据距离最近的均值向量确定xj的簇标记:\lambdaj=argmax{i\in{1,2,...,k}}d{ij};\ &\qquad 将样本xj划入相应的簇:C{\lambdaj}=C{\lambdaj}\cup{xj};\ &\quad end\ for\ &\quad for\ j=1,2,...,k\ do\ &\qquad 计算新的均值向量:\mu{i}'=\frac{1}{|Ci|}\sum{x\in Ci}x;\ &\qquad 更新均值向量:\mui\gets\mu{i}'\ & until\ 当前所有均值向量均不再更新 \end{aligned} $$ 类比下,这里的$x1,x2,...,xm$可以看作是上一层传来的特征$ui$;而获得的聚类中心,即均值向量$\mui$可以看作是这层抽取得到的特征$v_j$。

    按照这个算法,$vj$能够迭代的算出来,那就真的抛弃反向传播了,但事实上$vj$是作为输入$ui$的某种聚类中心出现的,如果如此,各个$vj$就都一样了。上面类比的K-Means算法获得聚类中心是需要一个参数“聚类簇数k”,这在动态路由中是没有的,类似的,神经元需要从多个角度看输入,从而得到不同的聚类中心。可以想象一个立方体,从正面获得一个中心点,从侧面又获得一个,从上面的面又可以获得一个中心点。为了实现“多角度看特征”,可以在上一层胶囊传入下一层之前,乘上一个矩阵做变换,这种变换就是所谓的仿射变换,它给神经元看特征提供了“观察角度”,这个矩阵现在还是需要反向传播训练得到的。那么, $$ vj=squash(\sumi p{j|i}ui)=squash(\sumi\frac{e^{ui^T vj}}{Zi}ui) $$ 就要变为: $$ vj=squash(\sumi\frac{e^{\hat{u{j|i}}^Tvj}}{Zi}\hat{u{j|i}}),\ 其中\hat{u{j|i}}=W{ji}ui $$ 这里的$W_{ji}$是待训练的矩阵,这里的乘法是矩阵乘法,矩阵乘向量,因此,Capsule Network变成了:

    完整的动态路由算法(Dynamic Routing Between Capsules. NIPS 2017中的动态路由): $$ \begin{aligned} &动态路由算法version2\ \ &初始化b{ij}=0 \ &迭代r次:\ &\qquad ci \gets softmax(bi);\ &\qquad sj\gets\sumic{ij}\hat{u{j|i}};\ &\qquad vj\gets squash(sj);\ &\qquad b{ij}\gets b{ij}+ui^Tvj \end{aligned} $$ 就是$sj\gets\sumic{ij}ui$变为了$sj\gets\sumic{ij}\hat{u{j|i}}$,新出来的$\hat{u{j|i}}$由$\hat{u{j|i}}=W{ji}u_i$求得。

    这样的Capsule层相当于普通神经网络中的全连接层。

    进一步的,我们希望上一层的胶囊乘的“观察角度矩阵”$W$能够对上一层所有胶囊共享,就是变成这样:

    这就是权值共享版的Capsule Network,所谓共享版,是指对于上一层传来的胶囊$ui$使用的变换矩阵是公用的,即$W{ji}\equiv Wj$。这样计算上面的$\hat{u{j|i}}$就变成了$\hat{u{j|i}}=W{j}u_i$。

    可以看到,Capsule Network由于需要$W$做仿射变换,而$W$是通过反向传播获得的,因此Capsule Network还是存在反向传播的。

总结

  • Capsule Network将底层神经元由标量表示变为向量表示,这个向量表示就是“胶囊”。基于此,部分人认为“张量网络”更为通俗易懂。

  • Capsule Network与传统神经网络的对比:

    • Capsule Network的输入输出都是向量了,而非传统网络的标量。
    • 上一层传入后,Capsule Network需要做一下放射变换(Affine Transformation),提供不同的观察角度,而传统神经网络并没有这回事。
    • Capsule Network的非线性激活使用的是squash函数,而传统的神经网络是ReLU, tanh, sigmoid等。
  • 原文中的动态路由伪代码:

    对应的简图:

  • 实验上,Dynamic Routing Between Capsules. NIPS 2017 验证了利用capsule作为神经元表示能个获得特征更为丰富的语义,比如能够捕获到笔触粗细,数字旋转等信息。Capsule Network在该文4.1中显示能够去噪,而且表明Capsule Network能给出误判的原因。Capsule Network还能有效地对重叠数字分割。

实现

以广泛传播的CapsNet-Tensorflow,使用MNIST数据集,手写数字识别为例。整个网络分为两个卷积层(包括普通的卷积层Conv1和Capsule版的卷积层PrimaryCaps)和一个全连接层(Capsule版的全连接层DigitCaps)

  • Conv1 layer

    | Input size | kernel size | conv stride | Channel | padding size | activation | pooling | | ---------- | ----------- | ----------- | ------- | ------------ | ---------- | ------- | | 28x28x1 | 9x9 | 1x1 | 256 | [0,0,0,0] | ReLU | No |

    $$[None,28,28,1] -> [None,20,20,256]$$

    参数数量:9x9x256+256=20992

    备注: $$ 输出神经元个数=(输入神经元个数-卷积核大小+2补零个数)/步长+1\ 20=(28-9+20)/1+1 $$

  • PrimaryCaps layer

| Input size | kernel size | conv stride | Channels | non-linearity func | routing | | ---------- | ----------- | ----------- | -------- | ------------------ | ------- | | 20x20x256 | 9x9 | 2x2 | 32 | squash | No |

$$ [None,20,20,256]\rightarrow None,6,6,32\rightarrow \left{\begin{matrix} [None,6,6,1,32]\ [None,6,6,1,32]\ ...\ [None,6,6,1,32] \end{matrix}\right.\rightarrow [None,6,6,8,32] $$

参数数量:9x9x256x32x8+8x32=5,308,672

相当于8个卷积层并行卷积,每次从各个卷积层拿出一个通道拼合出来,这就能得到了8维的向量,也就是标量变胶囊了。[None,6,6,8,32]的axis=0是batch_size;axis=1和axis=2是feature map的大小;axis=3是胶囊的维度,传统的神经网络这一维是没有的,或者可以认为是1;axis=4是通道数。代码实现:

  • DigitCaps layer

    相当于全连接层,上一层6x6x32个capsule进,10个capsule出(表示0~9数字的概率)

    输入:上一层PrimaryCaps输出的6x6x32个capsule,每个capsule维度为[8,1]

    输出:10个capsule,维度为[16,1]。输出既然是向量而非传统的一个个标量,这里使用的是模长表示0~9数字的概率。为什么使用模长,回忆上文中求$sj$(没有使用*squash*激活的$vj$)时,$sj\leftarrow \sumi c{ij}\hat{u{j|i}}$,这里$c{ij}$是softmax得到的概率,表示底层capsule在高层capsule中的重要程度,到了最后一层,就可以理解为哪个capsule是概率最大的输出。所有的capsule都是经过了*spuash*将模规范化到0~1之间的,只有$c{ij}$越大,向量模才能越大。这里就是有人所说的神经元“竞争激活”的概念,$c_{ij}$越大,神经元就被激活。

    $$ [None,1152,8,1]\rightarrow[None,10,16,1] $$ 参数数量:

    1152x10个$W_{ij}$: 1152x10x(8x16)=1,474,560

    1152x10个$c{ij}/b{ij}$: 1152x10x1=11,520

改进的方向

  • squash 函数,存在的必要存疑,以及对该函数本身的改进
  • 动态路由,现在实际上是给了一个框,这个“路由”实际上是一个聚类,现在已经有了用EM做动态路由了,这是可以试试的坑
  • 现有动态路由中有个$b{ij}\gets b{ij}+ui^Tvj$,有个哥们推导了一番,说这东西应该改为$b{ij}\gets ui^Tv_j$,这样使得迭代轮数不是超参数,而是变成迭代轮数越大越好
  • 神经元原先是一个标量,现在用向量表示,可不可以用矩阵,n维张量表示?

Connect

Email: cncmn@sina.cn

GitHub: cnlinxi@github

后记

花了一周多理解Capsule Network,并且动手照着别人的Capsule Network代码理解、默写出来。今天写完算是完成了一件事情。本文大多是拾人牙慧,但是也夹带了自己的一些私货,感谢这些博主和开源代码贡献者。

揭开迷雾,来一顿美味的Capsule盛宴

CapsNet-Tensorflow