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胶囊网络”为“张量网络”
-
层层抽象,层层分类
上图展示了特征\(u_1\)的连接,目前从上一层传来的特征\(u_1\)假设表示羽毛,下一层抽取得到的\(v_1,v_2,v_3,v_4\)分别表示猫、狗、兔、鸟4个种类。可以很容易想到softmax:
\[(p_{1|1},p_{2|1},p_{3|1},p_{4|1})=\frac{1}{Z_1}(e^{u_1^T v_1},e^{u_1^T v_2},e^{u_1^T v_3},e^{u_1^T v_4}),\quad Z_1=\sum_{i=0}^4e^{u_1^T v_i} \]我们一般选择最大的那个概率就行了,但是单靠这一个特征是不够的,我们需要综合各个特征,可以把上面的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})\)。这时就需要融合这些特征,很容易想到“加权和”
对于特征\(u_i\),获得的特征分布为\((p_{1|i},p_{2|i},p_{3|i},p_{4|i})\),加权和就是:\(\sum_i u_ip_{j|i}(j=1,2,3,4)\)。对于希望获得向上一层的特征\(v_j\),这里paper中使用了一种squash函数处理后,就变成了向上一层的特征\(v_j\),也即:
\[v_j=squash(\sum_i p_{j|i}u_i)=squash(\sum_i\frac{e^{u_i^T v_j}}{Z_i}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哪来的?可不可以尝试其他常数实验效果,这会是一个超参数吗
动态路由
注意到:
\[v_j=squash(\sum_i p_{j|i}u_i)=squash(\sum_i\frac{e^{u_i^T v_j}}{Z_i}u_i) \]为了求\(v_j\)需要求softmax,而为了求softmax又需要知道\(v_j\),似乎陷入了鸡生蛋,蛋生鸡的问题,这就是动态路由(Dynamic Routing)要解决的问题,它能够“自主更新”参数,从而达到Hinton放弃梯度下降的目标。
看一个NLP的例子,考虑一个向量\((x_1,x_2,...,x_n)\),现在希望将n个向量整合成一个向量\(x\)(encoder)。简化下情况,仅仅使用原来向量的线性组合,也就是:
\[x=\sum_{i=1}^n \lambda_ix_i \]这里的\(\lambda_i\)相当于衡量了\(x\)与\(x_i\)的相似度,衡量了\(x_i\)在最终的\(x\)中的重要程度,这也是一个鸡生蛋,蛋生鸡的问题,解决方法就是迭代。定义一个基于softmax的相似度指标,然后“加权和”:
\[x=\sum_{i=1}^n\frac{e^{x^Tx_i}}{Z}x_i \]一开始,我们将\(x\)初始化为各个\(x_i\)的均值,然后将\(x\)带入右侧,左侧得到一个新的\(x\),然后再将其带入右侧,如此反复,一般迭代有限次即可收敛。
为了得到得到各个\(v_j\),一开始先将它们初始化为\(u_i\)的均值,然后带入softmax迭代求解即可。事实上,输出是输入的聚类结果,而聚类通常都需要迭代算法,这个迭代算法即是“动态路由”。至于这个动态路由的细节,其实是不固定的,取决于聚类的算法。在Dynamic Routing Between Capsules. NIPS 2017 一文中,路由算法为:
\[\begin{aligned} &动态路由算法version1\\ \\ &初始化b_{ij}=0 \\ &迭代r次:\\ &\qquad c_i \gets softmax(b_i);\\ &\qquad s_j\gets\sum_ic_{ij}u_i;\\ &\qquad v_j\gets squash(s_j);\\ &\qquad b_{ij}\gets b_{ij}+u_i^Tv_j \end{aligned} \]这里的\(c_{ij}\)即是前文中的\(p_{j|i}\)。
这里有人认为原文中的\(b_{ij}\gets b_{ij}+u_i^Tv_j\)有误,应为\(b_{ij}\gets u_i^Tv_j\)。没有看懂什么意思,不作评价。
补充下聚类算法K-Means的伪代码:
\[\begin{aligned} 输入:&样本集D=\{x_1,x_2,...,x_m\};\\ &聚类簇数k.\\ 过程:\\ &从D中随机选择k个样本作为初始均值向量\{\mu_1,\mu_2,...,\mu_k\}\\ &repeat\\ &\quad 令C_i=\phi(1\leq i \leq k)\\ &\quad for\ j=1,2,...,m\ do\\ &\qquad 计算样本x_j 与各个均值向量\mu_i(1\leq i\leq k)的距离:d_{ij}=||x_j-\mu_i||_2;\\ &\qquad 根据距离最近的均值向量确定x_j的簇标记:\lambda_j=argmax_{i\in\{1,2,...,k\}}d_{ij};\\ &\qquad 将样本x_j划入相应的簇:C_{\lambda_j}=C_{\lambda_j}\cup\{x_j\};\\ &\quad end\ for\\ &\quad for\ j=1,2,...,k\ do\\ &\qquad 计算新的均值向量:\mu_{i}'=\frac{1}{|C_i|}\sum_{x\in C_i}x;\\ &\qquad 更新均值向量:\mu_i\gets\mu_{i}'\\ & until\ 当前所有均值向量均不再更新 \end{aligned} \]类比下,这里的\(x_1,x_2,...,x_m\)可以看作是上一层传来的特征\(u_i\);而获得的聚类中心,即均值向量\(\mu_i\)可以看作是这层抽取得到的特征\(v_j\)。
按照这个算法,\(v_j\)能够迭代的算出来,那就真的抛弃反向传播了,但事实上\(v_j\)是作为输入\(u_i\)的某种聚类中心出现的,如果如此,各个\(v_j\)就都一样了。上面类比的K-Means算法获得聚类中心是需要一个参数“聚类簇数k”,这在动态路由中是没有的,类似的,神经元需要从多个角度看输入,从而得到不同的聚类中心。可以想象一个立方体,从正面获得一个中心点,从侧面又获得一个,从上面的面又可以获得一个中心点。为了实现“多角度看特征”,可以在上一层胶囊传入下一层之前,乘上一个矩阵做变换,这种变换就是所谓的仿射变换,它给神经元看特征提供了“观察角度”,这个矩阵现在还是需要反向传播训练得到的。那么,
\[v_j=squash(\sum_i p_{j|i}u_i)=squash(\sum_i\frac{e^{u_i^T v_j}}{Z_i}u_i) \]就要变为:
\[v_j=squash(\sum_i\frac{e^{\hat{u_{j|i}}^Tv_j}}{Z_i}\hat{u_{j|i}}),\ 其中\hat{u_{j|i}}=W_{ji}u_i \]这里的\(W_{ji}\)是待训练的矩阵,这里的乘法是矩阵乘法,矩阵乘向量,因此,Capsule Network变成了:
完整的动态路由算法(Dynamic Routing Between Capsules. NIPS 2017中的动态路由):
\[\begin{aligned} &动态路由算法version2\\ \\ &初始化b_{ij}=0 \\ &迭代r次:\\ &\qquad c_i \gets softmax(b_i);\\ &\qquad s_j\gets\sum_ic_{ij}\hat{u_{j|i}};\\ &\qquad v_j\gets squash(s_j);\\ &\qquad b_{ij}\gets b_{ij}+u_i^Tv_j \end{aligned} \]就是\(s_j\gets\sum_ic_{ij}u_i\)变为了\(s_j\gets\sum_ic_{ij}\hat{u_{j|i}}\),新出来的\(\hat{u_{j|i}}\)由\(\hat{u_{j|i}}=W_{ji}u_i\)求得。
这样的Capsule层相当于普通神经网络中的全连接层。
进一步的,我们希望上一层的胶囊乘的“观察角度矩阵”\(W\)能够对上一层所有胶囊共享,就是变成这样:
这就是权值共享版的Capsule Network,所谓共享版,是指对于上一层传来的胶囊\(u_i\)使用的变换矩阵是公用的,即\(W_{ji}\equiv W_j\)。这样计算上面的\(\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+2*0)/1+1 \] -
PrimaryCaps layer
Input size | kernel size | conv stride | Channels | non-linearity func | routing |
---|---|---|---|---|---|
20x20x256 | 9x9 | 2x2 | 32 | squash | No |
参数数量: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数字的概率。为什么使用模长,回忆上文中求\(s_j\)(没有使用squash激活的\(v_j\))时,\(s_j\leftarrow \sum_i 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}+u_i^Tv_j\),有个哥们推导了一番,说这东西应该改为\(b_{ij}\gets u_i^Tv_j\),这样使得迭代轮数不是超参数,而是变成迭代轮数越大越好
- 神经元原先是一个标量,现在用向量表示,可不可以用矩阵,n维张量表示?
Connect
Email: cncmn@sina.cn
GitHub: cnlinxi@github
后记
花了一周多理解Capsule Network,并且动手照着别人的Capsule Network代码理解、默写出来。今天写完算是完成了一件事情。本文大多是拾人牙慧,但是也夹带了自己的一些私货,感谢这些博主和开源代码贡献者。