上周 Geoffrey Hinton 等人公开了那篇
备受关注的 NIPS 论文,然后不少研究者与开发者都阅读了该论文并做出了必定的代码实现。机器之心在本文中将详细解释该论文提出的结构与过程,并借助 GitHub 上热烈讨论的项目完成了 CapsNet 的 TensorFlow 实现,并提供了主体架构的代码注释。
本文是机器之心的第三个 GitHub 项目,旨在解释 CapsNet 的网络架构与实现。为了解释 CapsNet,咱们将从卷积层与卷积机制开始,从工程实践的角度解释卷积操做的过程与输出,这对进一步理解 Capsule 层的处理十分有利,后面咱们将基于对 Capsule 层的理解解释 Geoffrey Hinton 等人最近提出来的 CapsNet 架构。最后咱们会根据 naturomics 的实现进行测试与解释。
机器之心 GitHub 项目地址:https://github.com/jiqizhixin/ML-Tutorial-Experiment
这一部分主要是为不太了解卷积机制具体过程的读者准备,由于 CapsNet 的前面两层本质上仍是传统的卷积操做。若读者已经了解基本的卷积操做,那么能够跳过这一章节直接阅读 Capsule 层的结构与过程。
若要解释卷积神经网络,咱们先要知道为何卷积在图像上能比全链接网络有更好的性能,如下分别展现了全链接网络和卷积网络通常的架构:
咱们知道全链接网络前一层的每一个神经元(或单元)都会与后一层中每一个神经元相连,链接的强弱能够经过相对应的权重控制。而全部链接权重就是该全链接神经网络但愿学到的。上图可知卷积神经网络也是由一层一层的神经元组织起来的,只不过全链接网络相邻两层的神经元都有链接,因此能够将相同层的神经元排列为一列,这样能够方便显示链接结构。而卷积网络相连两层之间只有部分神经元相连,为了展现每一层神经元的维度,咱们通常会将每个卷积层的结点组织为一个三维张量。
全链接网络处理图像最大的问题是每层之间的参数或权重太多了,主要是由于两层间的神经元都有链接。若使用一个隐藏层为 500 个单元的全链接网络(784×500×10)识别 MNIST 手写数字,那么参数的数量为 28×28×500+5000+510=397510 个参数,这大大限制了网络层级的加深。
而对于卷积网络来讲,每个单元都只会和上一层部分单元相链接。通常每一个卷积层的单元均可以组织成一个三维张量,即矩阵沿第三个方向增长一维数据。例如 Cifar-10 数据集的输入层就能够组织成 32×32×3 的三维张量,其中 32×32 表明图片的尺寸或像素数量,而 3 表明 RGB 三色通道。
卷积层试图将神经网络中的每一小块进行更加深刻的分析,从而得出抽象程度更高的特征。通常来讲经过卷积层处理的神经元结点矩阵会变得更深,即神经元的组织在第三个维度上会增长。
下图展现了卷积核或滤波器(filter)将当前层级上的一个子结点张量转化为下一层神经网络上的一个长和宽都为 1,深度不限的结点矩阵。下图输入是一个 32×32×3 的张量,中间的小长方体为卷积核,通常能够为 3×3 或 5×5 等,且由于要计算乘积,那么卷积核的第三个维度必须和其处理的图像深度(即输入张量第三个维度 3)相等。最右边的矩形体的深度为 5,即前面使用了五个卷积核执行卷积操做。这五个卷积核有不一样的权重,但每个卷积层使用一个卷积核的权重是同样的,因此下图五层特征中每一层特征都是经过一个卷积核得出来的,也就是该层共享了权重。
可能入门读者对卷积的具体过程仍是不够了解,下面咱们能够讨论卷积操做的具体过程。以下所示,该图展现了卷积的具体操做过程。首先咱们的输入为 5×5×3 的张量,即 x[:, :, 0 : 3]。其次咱们有两个 3×3 的卷积核,即 W0 和 W1,第三个维度必须和输入张量的第三个维度相等,因此通常只用两个维度描述一个卷积核。最后卷积操做输出 3×3×2 的张量,其中 o[:, :, 0] 为第一个卷积核 W0 的卷积输出,o[:, :, 1] 为第二个卷积核的输出。由于输入张量使用了 Padding,即每个通道的输入图像周围加 0,且卷积核移动的步幅为 2,则每一个卷积核输出的维度为 3×3(即 (7-3)/2)。
在上图中,卷积核会与输入张量对应相乘相加,而后再加上偏置项就等于输出张量中对应位置的值。例如使用卷积和 W0 对输入张量(深度为 3 可看做图像拥有的 RGB 三个通道)作卷积,卷积和三个层级将对应输入张量的三个层级作乘积累计。w0[:, :, 0] 乘以 x[:, :, 0] 左上角的九个元素为 1*0+1*0-1*0-1*0+0*0+1*1-1*0-1*0+0*1=1,同理 w0[:, :, 1] 乘以 x[:, :, 1] 左上角九个元素为-一、w0[:, :, 2] 乘以 x[:, :, 2] 左上角九个元素为 0,这三个值相加再加上偏置项 b0 就等于最右边输出张量 o[:, :, 0] 的左上角第一个元素,即 1-1+0+1=1。
随着卷积核移动一个步长,咱们能够计算出输出矩阵移动一个元素的值。注意但卷积核在输入张量上移动的时候,卷积核权重是相同的,也就是说这一层共享了相同的权重,即 o[:, :, 0] 和 o[:, :, 1] 分别共享了一组权重。这里之因此强调权重的共享,不只由于它是卷积层核心的属性,同时还有利于咱们在后面理解 CapsNet 的 PrimaryCaps 层。
卷积还有不少性质没有解释,例如最大池化选取一个滤波器内数值最大的值表明该区域的特征以减小输出张量的尺寸,Inception 模块将多组卷积核并联地对输入张量进行处理,而后再将并联处理获得的多个输出张量按序串联地组成一个很深的输出张量做为 Inception 模块的输出等。读者也能够继续阅读机器之心关于卷积的文章进一步了解。最后,咱们提供了一个简单的实现展现卷积操做的计算过程:
这一段代码执行了卷积操做和平均池化,它的输出以下:
这一部分主要是解释 Capsule 层与动态路由(DynamicRouting)机制的大概原理,这一部分基于咱们对 Hinton 原论文的理解完成,并采用了知乎 SIY.Z、Debarko De 等人的观点。文末将给出更多的参考资料,读者可进一步阅读以了解更多。
前面咱们已经知道卷积经过权重共享和局部链接能够减小不少参数,此外共享卷积核权重可使图像上的内容不受位置的影响。例如 Cifar-10 中的图像为 32×32×3,而由 16 个尺寸为 5×5 的卷积核(或表述深度为 16)所构成的卷积层,其参数共有 5*5*3*16+16=1216 个。但这这样的卷积层单元仍是太简单了,它们也不能表征复杂的概念。
例如当图像进行一些旋转、变形或朝向不一样的方向,那么 CNN 自己是没法处理这些图片的。固然这个问题能够在训练中添加相同图像的不一样变形而获得解决。在 CNN 中每一层都以很是细微的方式理解图像,由于咱们卷积核的感觉野通常使用 3×3 或 5×5 等像素级的操做来理解图像,因此卷积层老是尝试理解局部的特征与信息。而当咱们由前面低级特征组合成后面复杂与抽象的特征时,咱们极可能须要使用池化操做来减小输出张量或特征图的尺寸,而这种操做实际上会丢失一些信息,好比说位置信息。
而等变映射(Equivariance)能够帮助 CNN 理解旋转或比例等属性变换,并相应地调整本身,这样图像空间中的位置等属性信息就不会丢失。而 Geoffrey Hinton 等人提出的 CapsNet 使用向量代替标量点,所以能获取更多的信息。此外,咱们感受 Capsule 使用向量做为输入与输出是这篇论文的亮点。
在论文中,Geoffrey Hinton 介绍 Capsule 为:「Capsule 是一组神经元,其输入输出向量表示特定实体类型的实例化参数(即特定物体、概念实体等出现的几率与某些属性)。咱们使用输入输出向量的长度表征实体存在的几率,向量的方向表示实例化参数(即实体的某些图形属性)。同一层级的 capsule 经过变换矩阵对更高级别的 capsule 的实例化参数进行预测。当多个预测一致时(本论文使用动态路由使预测一致),更高级别的 capsule 将变得活跃。」
Capsule 中的神经元的激活状况表示了图像中存在的特定实体的各类性质。这些性质能够包含不少种不一样的参数,例如姿式(位置,大小,方向)、变形、速度、反射率,色彩、纹理等等。而输入输出向量的长度表示了某个实体出现的几率,因此它的值必须在 0 到 1 之间。
为了实现这种压缩,并完成 Capsule 层级的激活功能,Hinton 等人使用了一个被称为「squashing」的非线性函数。该非线性函数确保短向量的长度可以缩短到几乎等于零,而长向量的长度压缩到接近但不超过 1 的状况。如下是该非线性函数的表达式:
其中 v_j 为 Capsule j 的输出向量,s_j 为上一层全部 Capsule 输出到当前层 Capsule j 的向量加权和,简单说 s_j 就为 Capsule j 的输入向量。该非线性函数能够分为两部分,即
,前一部分是输入向量 s_j 的缩放尺度,第二部分是输入向量 s_j 的单位向量,该非线性函数既保留了输入向量的方向,又将输入向量的长度压缩到区间 [0,1) 内。s_j 向量为零向量时 v_j 能取到 0,而 s_j 无穷大时 v_j 无限逼近 1。该非线性函数能够看做是对向量长度的一种压缩和重分配,所以也能够看做是一种输入向量后「激活」输出向量的方式。
那么如上所述,Capsule 的输入向量就至关于经典神经网络神经元的标量输入,而该向量的计算就至关于两层 Capsule 间的传播与链接方式。输入向量的计算分为两个阶段,即线性组合和 Routing,这一过程能够用如下公式表示:
其中 u_j|i hat 为 u_i 的线性组合,这一点能够看做是通常全链接网络前一层神经元以不一样强弱的链接输出到后一层某个神经元。只不过 Capsule 相对于通常神经网络每一个结点都有一组神经元(以生成向量),即 u_j|i hat 表示上一层第 i 个 Capsule 的输出向量和对应的权重向量相乘(W_ij 表示向量而不是元素)而得出的预测向量。u_j|i hat 也能够理解为在前一层为第 i 个 Capsule 的状况下链接到后一层第 j 个 Capsule 的强度。
在肯定 u_j|i hat 后,咱们须要使用 Routing 进行第二个阶段的分配以计算输出结点 s_j,这一过程就涉及到使用动态路由(dynamic routing)迭代地更新 c_ij。经过 Routing 就能获取下一层 Capsule 的输入 s_j,而后将 s_j 投入「Squashing」非线性函数后就能得出下一层 Capsule 的输出。后面咱们会重点解释 Routing 算法,但整个 Capsule 层及它们间传播的过程已经完成了。
因此整个层级间的传播与分配能够分为两个部分,第一部分是下图 u_i 与 u_j|i hat 间的线性组合,第二部分是 u_j|i hat 与 s_j 之间的 Routing 过程。若读者对传播过程仍然不是太明晰,那么能够看如下两层 Capsule 单元间的传播过程,该图是根据咱们对传播过程的理解而绘制的:
如上所示,该图展现了 Capsule 的层级结构与动态 Routing 的过程。最下面的层级 u_i 共有两个 Capsule 单元,该层级传递到下一层级 v_j 共有四个 Capsule。u_1 和 u_2 是一个向量,即含有一组神经元的 Capsule 单元,它们分别与不一样的权重 W_ij(一样是向量)相乘得出 u_j|i hat。例如 u_1 与 W_12 相乘得出预测向量 u_2|1 hat。随后该预测向量和对应的「耦合系数」c_ij 相乘并传入特定的后一层 Capsule 单元。不一样 Capsule 单元的输入 s_j 是全部可能传入该单元的加权和,即全部可能传入的预测向量与耦合系数的乘积和。随后咱们就获得了不一样的输入向量 s_j,将该输入向量投入到「squashing」非线性函数就能得出后一层 Capsule 单元的输出向量 v_j。而后咱们能够利用该输出向量 v_j 和对应预测向量 u_j|i hat 的乘积更新耦合系数 c_ij,这样的迭代更新不须要应用反向传播。
由于按照 Hinton 的思想,找到最好的处理路径就等价于正确处理了图像,因此在 Capsule 中加入 Routing 机制能够找到一组系数 c_ij,它们能令预测向量 u_j|i hat 最符合输出向量 v_j,即最符合输出的输入向量,这样咱们就找到了最好的路径。
按照原论文所述,c_ij 为耦合系数(coupling coefficients),该系数由动态 Routing 过程迭代地更新与肯定。Capsule i 和后一层级全部 Capsule 间的耦合系数和为 1,即图四 c_11+c_12+c_13+c_14=1。此外,该耦合系数由「routing softmax」决定,且 softmax 函数中的 logits b_ij 初始化为 0,耦合系数 c_ij 的 softmax 计算方式为:
b_ij 依赖于两个 Capsule 的位置与类型,但不依赖于当前的输入图像。咱们能够经过测量后面层级中每个 Capsule j 的当前输出 v_j 和 前面层级 Capsule i 的预测向量间的一致性,而后借助该测量的一致性迭代地更新耦合系数。本论文简单地经过内积度量这种一致性,即
,这一部分也就涉及到使用 Routing 更新耦合系数。
Routing 过程就是上图 4 右边表述的更新过程,咱们会计算 v_j 与 u_j|i hat 的乘积并将它与原来的 b_ij 相加而更新 b_ij,而后利用 softmax(b_ij) 更新 c_ij 而进一步修正了后一层的 Capsule 输入 s_j。当输出新的 v_j 后又能够迭代地更新 c_ij,这样咱们不须要反向传播而直接经过计算输入与输出的一致性更新参数。
该 Routing 算法更具体的更新过程能够查看如下伪代码:
对于全部在 l 层的 Capsule i 和在 l+1 层的 Capsule j,先初始化 b_ij 等于零。而后迭代 r 次,每次先根据 b_i 计算 c_i,而后在利用 c_ij 与 u_j|i hat 计算 s_j 与 v_j。利用计算出来的 v_j 更新 b_ij 以进入下一个迭代循环更新 c_ij。该 Routing 算法十分容易收敛,基本上经过 3 次迭代就能有不错的效果。
Hinton 等人实现了一个简单的 CapsNet 架构,该架构由两个卷积层和一个全链接层组成,其中第一个为通常的卷积层,第二个卷积至关于为 Capsule 层作准备,而且该层的输出为向量,因此它的维度要比通常的卷积层再高一个维度。最后就是经过向量的输入与 Routing 过程等构建出 10 个 v_j 向量,每个向量的长度都直接表示某个类别的几率。
第一个卷积层使用了 256 个 9×9 卷积核,步幅为 1,且使用了 ReLU 激活函数。该卷积操做应该没有使用 Padding,输出的张量才能是 20×20×256。此外,CapsNet 的卷积核感觉野使用的是 9×9,相比于其它 3×3 或 5×5 的要大一些,这个能是由于较大的感觉野在 CNN 层次较少的状况下能感觉的信息越多。这两层间的权值数量应该为 9×9×256+256=20992。
随后,第二个卷积层开始做为 Capsule 层的输入而构建相应的张量结构。咱们能够从上图看出第二层卷积操做后生成的张量维度为 6×6×8×32,那么咱们该如何理解这个张量呢?云梦居客在知乎上给出了一个十分形象且有意思的解释,如前面章节所述,若是咱们先考虑 32 个(32 channel)9×9 的卷积核在步幅为 2 的状况下作卷积,那么实际上获得的是传统的 6×6×32 的张量,即等价于 6×6×1×32。
由于传统卷积操做每次计算的输出都是一个标量,而 PrimaryCaps 的输出须要是一个长度为 8 的向量,所以传统卷积下的三维输出张量 6×6×1×32 就须要变化为四维输出张量 6×6×8×32。以下所示,其实咱们能够将第二个卷积层看做对维度为 20×20×256 的输入张量执行 8 次不一样权重的 Conv2d 操做,每次 Conv2d 都执行带 32 个 9×9 卷积核、步幅为 2 的卷积操做。
因为每次卷积操做都会产生一个 6×6×1×32 的张量,一共会产生 8 个相似的张量,那么将这 8 个张量(即 Capsule 输入向量的 8 个份量)在第三个维度上合并在一块儿就成了 6×6×8×32。从上可知 PrimaryCaps 就至关于一个深度为 32 的普通卷积层,只不过每一层由之前的标量值变成了长度为 8 的向量。
此外,结合 Hinton 等人给出的 Capsule 定义,它就至关于一组常见的神经元,这些神经元封装在一块儿造成了新的单元。在本论文讨论的 CapsNet 架构中,咱们将 8 个卷积单元封装在一块儿成为了一个新的 Caosule 单元。PrimaryCaps 层的卷积计算都没有使用 ReLU 等激活函数,它们以向量的方式预备输入到下一层 Capsule 单元中。
PrimaryCaps 每个向量的份量层级是共享卷积权重的,即获取 6×6 张量的卷积核权重为相同的 9×9 个。这样该卷积层的参数数量为 9×9×256×8×32+8×32=5308672,其中第二部分 8×32 为偏置项参数数量。
第三层 DigitCaps 在第二层输出的向量基础上进行传播与 Routing 更新。第二层共输出 6×6×32=1152 个向量,每个向量的维度为 8,即第 i 层共有 1152 个 Capsule 单元。而第三层 j 有 10 个标准的 Capsule 单元,每一个 Capsule 的输出向量有 16 个元素。前一层的 Capsule 单元数是 1152 个,那么 w_ij 将有 1152×10 个,且每个 w_ij 的维度为 8×16。当 u_i 与对应的 w_ij 相乘获得预测向量后,咱们会有 1152×10 个耦合系数 c_ij,对应加权求和后会获得 10 个 16×1 的输入向量。将该输入向量输入到「squashing」非线性函数中求得最终的输出向量 v_j,其中 v_j 的长度就表示识别为某个类别的几率。
DigitCaps 层与 PrimaryCaps 层之间的参数包含两类,即 W_ij 和 c_ij。全部 W_ij 的参数数量应该是 6×6×32×10×8×16=1474560,c_ij 的参数数量为 6×6×32×10×16=184320,此外还应该有 2×1152×10=23040 个偏置项参数,不过原论文并无明确指出这些偏置项。最后小编计算出该三层 CapsNet 一共有 5537024 个参数,这并不包括后面的全链接重构网络参数。(算错了不要怪小编呦~)
前面咱们已经了解 DigitCaps 层输出向量的长度即某个类别的几率,那么咱们该如何构建损失函数,并根据该损失函数迭代地更新整个网络?前面咱们耦合系数 c_ij 是经过一致性 Routing 进行更新的,他并不须要根据损失函数更新,但整个网络其它的卷积参数和 Capsule 内的 W_ij 都须要根据损失函数进行更新。通常咱们就能够对损失函数直接使用标准的反向传播更新这些参数,而在原论文中,做者采用了 SVM 中经常使用的 Margin loss,该损失函数的表达式为:
其中 c 是分类类别,T_c 为分类的指示函数(c 存在为 1,c 不存在为 0),m+ 为上边界,m- 为下边界。此外,v_c 的模即向量的 L2 距离。
由于实例化向量的长度来表示 Capsule 要表征的实体是否存在,因此当且仅当图片里出现属于类别 k 的手写数字时,咱们但愿类别 k 的最顶层 Capsule 的输出向量长度很大(在本论文 CapsNet 中为 DigitCaps 层的输出)。为了容许一张图里有多个数字,咱们对每个表征数字 k 的 Capsule 分别给出单独的 Margin loss。
重构即咱们但愿利用预测的类别从新构建出该类别表明的实际图像,例如咱们前面部分的模型预测出该图片属于一个类别,而后后面重构网络会将该预测的类别信息从新构建成一张图片。
前面咱们假设过 Capsule 的向量能够表征一个实例,那么若是咱们将一个向量投入到后面的重构网络中,它应该能重构出一个完整的图像。所以,Hinton 等人使用额外的重构损失(reconstruction loss)来促进 DigitCaps 层对输入数字图片进行编码。下图展现了整个重构网络的的架构:
咱们在训练期间,除了特定的 Capsule 输出向量,咱们须要蒙住其它全部的输出向量。而后,使用该输出向量重构手写数字图像。DigitCaps 层的输出向量被馈送至包含 3 个全链接层的解码器中,并以上图所示的方式构建。这一过程的损失函数经过计算 FC Sigmoid 层的输出像素点与原始图像像素点间的欧几里德距离而构建。Hinton 等人还按 0.0005 的比例缩小重构损失,以使它不会主导训练过程当中的 Margin loss。
Capsule 输出向量的重构与表征除了能提高模型的准确度之外,还能提高模型的可解释性,由于咱们能修正须要重构向量中的某个或某些份量而观察重构后的图像变化状况,这有助于咱们理解 Capsule 层的输出结果。
以上就是本论文构建的 CapsNet 架构,固然 Hinton 还描述了不少试验结果与发现,感兴趣的读者能够查阅论文的后一部分。
如下定义构建 CapsNet 后面两层的方法。在 CapsNet 架构中,咱们能访问该类中的对象和方法构建 PrimaryCaps 层和 DigitCaps 层。
下面是整个 CapsNet 的架构与推断过程代码,咱们须要从 MNIST 抽出图像并投入到如下定义的方法中,该批量的图像将先经过三层 CapsNet 网络输出 10 个类别向量,每一个向量有 16 个元素,且每一个类别向量的长度为输出图像是该类别的几率。随后,咱们会将一个向量投入到重构网络中构建出该向量所表明的图像。
以上是该网络的主体代码,更多代码请查看 naturomics 的 GitHub 地址,或机器之心的 GitHub 地址,咱们上传的是带注释的代码,但愿能帮助初学者更加理解 CapsNet 的过程与架构。如下是上面咱们定义 CapsNet 的主体计算图,即 TensorFlow 中的静态计算图:
咱们也迭代训练了大概 3 万多步,不过由于使用的是 CPU,因此咱们将批量大小调整为 8 以减小单次迭代的计算压力,如下是咱们训练时的损失状况,最上面是 Margin loss,下面还有重构损失和总损失:
最后放上两张由 DigitCaps 层输出向量重构出的对应图像:
咱们只是初步地探索了 CapsNet,它还存在不少的可能性,例如它以向量的形式应该能获取很是多的图像信息,这种优点是否能在其它大型数据集或平面 3D 图像数据集中进一步展示出非凡的表征力?并且第二层 PrimaryCaps 的参数很是多,就像一组横向并联的卷积结构以产生向量(相似 Inception 模块,但要宽地多),咱们是否能经过某种方式的共享进一步减小该层级的参数?还有当前 Routing 过程的效果至少在 MNIST 数据集中并很差,它仅仅只能展现存在这个概念,那么咱们可否找到更加高效的 Routing 算法?此外,Capsule 是否能扩展到其余神经网络结构如循环或门控单元?这些可能都是咱们存在的疑惑,但向前走,时间总会给咱们答案的。
欢迎你们留言讨论,本文在机器之心网站上将持续更新与修正。
-
原论文:Dynamic Routing Between Capsules(https://arxiv.org/abs/1710.09829)
-
知乎讨论地址:https://www.zhihu.com/question/67287444/answer/251241736
-
naturomics 实现地址(TensorFlow):https://github.com/naturomics/CapsNet-Tensorflow
-
XifengGuo 实现地址(Keras):https://github.com/XifengGuo/CapsNet-Keras
-
leftthomas 实现地址(Pytorch):https://github.com/leftthomas/CapsNet