zoukankan      html  css  js  c++  java
  • 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_ifrac{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_ifrac{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}^nfrac{e^{x^Tx_i}}{Z}x_i ]

      一开始,我们将(x)初始化为各个(x_i)的均值,然后将(x)带入右侧,左侧得到一个新的(x),然后再将其带入右侧,如此反复,一般迭代有限次即可收敛。

      为了得到得到各个(v_j),一开始先将它们初始化为(u_i)的均值,然后带入softmax迭代求解即可。事实上,输出是输入的聚类结果,而聚类通常都需要迭代算法,这个迭代算法即是“动态路由”。至于这个动态路由的细节,其实是不固定的,取决于聚类的算法。在Dynamic Routing Between Capsules. NIPS 2017 一文中,路由算法为:

      [egin{aligned} &动态路由算法version1\ \ &初始化b_{ij}=0 \ &迭代r次:\ &qquad c_i gets softmax(b_i);\ &qquad s_jgetssum_ic_{ij}u_i;\ &qquad v_jgets 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的伪代码:

      [egin{aligned} 输入:&样本集D={x_1,x_2,...,x_m};\ &聚类簇数k.\ 过程:\ &从D中随机选择k个样本作为初始均值向量{mu_1,mu_2,...,mu_k}\ &repeat\ &quad 令C_i=phi(1leq i leq k)\ &quad for j=1,2,...,m do\ &qquad 计算样本x_j 与各个均值向量mu_i(1leq ileq k)的距离:d_{ij}=||x_j-mu_i||_2;\ &qquad 根据距离最近的均值向量确定x_j的簇标记:lambda_j=argmax_{iin{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_{xin C_i}x;\ &qquad 更新均值向量:mu_igetsmu_{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_ifrac{e^{u_i^T v_j}}{Z_i}u_i) ]

      就要变为:

      [v_j=squash(sum_ifrac{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中的动态路由):

      [egin{aligned} &动态路由算法version2\ \ &初始化b_{ij}=0 \ &迭代r次:\ &qquad c_i gets softmax(b_i);\ &qquad s_jgetssum_ic_{ij}hat{u_{j|i}};\ &qquad v_jgets squash(s_j);\ &qquad b_{ij}gets b_{ij}+u_i^Tv_j end{aligned} ]

      就是(s_jgetssum_ic_{ij}u_i)变为了(s_jgetssum_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

    [[None,20,20,256] ightarrow [None,6,6,32](8个卷积层并行) ightarrow left{egin{matrix} [None,6,6,1,32]\ [None,6,6,1,32]\ ...\ [None,6,6,1,32] end{matrix} ight. ightarrow [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数字的概率。为什么使用模长,回忆上文中求(s_j)(没有使用squash激活的(v_j))时,(s_jleftarrow 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] ightarrow[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代码理解、默写出来。今天写完算是完成了一件事情。本文大多是拾人牙慧,但是也夹带了自己的一些私货,感谢这些博主和开源代码贡献者。

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

  • 相关阅读:
    使用 Log4Net 记录日志
    NuGet安装和使用
    .NET Framework 4 与 .NET Framework 4 Client Profile
    “init terminating in do_boot” Windows10 Rabbit MQ fails to start
    Ubuntu / Win7 安装db2 v10.5
    Win7下的内置FTP组件的设置详解
    c/s模式 (C#)下Ftp的多文件上传及其上传进度
    C#路径/文件/目录/I/O常见操作汇总
    C# 遍历指定目录下的所有文件及文件夹
    Mongodb主从复制 及 副本集+分片集群梳理
  • 原文地址:https://www.cnblogs.com/mengnan/p/9307454.html
Copyright © 2011-2022 走看看