您的位置:首页 > 其它

Geoffrey Hinton《Dynamic Routing Between Capsules》理解

2017-11-23 22:10 549 查看
深度学习之父Hinton最近作为共同作者在NIPS2017发表了论文《Dynamic Routing Between Capsules》,一时受到广泛关注。

目前在计算机视觉(Computer Vision)领域,卷积神经网络(Convolutional Neural Networks, CNN)占据了主导地位。然而,论文认为当前的CNN存在较大缺陷:

A good candidate is the difficulty that convolutional nets have in generalizing to novel viewpoints. The ability to deal with translation is built in, but for the other dimensions of an affine transformation we have to chose between replicating feature detectors on a grid that grows exponentially with the number of dimensions, or increasing the size of the labelled training set in a similarly exponential way.

也就是说传统CNN没有归纳能力,要想识别各种变形情况,需要大量的特征空间或者大量训练样本。(比如,我们人类可能只需要看过几个5字的样本,以后再有各种5字的变体,我们基本都可以认出来。)

因此,论文提出了胶囊理论,试图解决这些问题。

CapsNet相比传统的CNN主要改动了两个地方:

Scalar-output feature detectors -> Vector-output capsules

Max-pooling -> Routing-by-agreement

思想理论方面还是看原论文吧,首先我们需要知道作者是咋弄的。

模型结构



第一层(Image -> Conv1)

第一层是传统的卷积操作,卷积核个数为256,卷积核大小(Kernel Size)为[9, 9],步长(Stride)为1,通道(Channel)数为1。

输入是一张图片,即[28, 28]的矩阵,输出得到[20, 20, 256]的张量。

第二层(Conv1 -> PrimaryCaps)

第二层输入是[20, 20, 256]的张量,输出是[6, 6, 8, 32]的张量。

输入可以看作是256个[20, 20]的矩阵,每8个为一组,公用一个卷积核,卷积核大小[9, 9],步长为2,这样可以得到[6, 6, 8]的一个Capsule,每8个一组这样做32次,可以得到32个Capsules,因此输出[6, 6, 8, 32]的张量。

第三层(PrimaryCaps -> DigitCaps)

这是最重要的一层,论文的架构图中显示的是输入[6, 6, 8, 32]的张量,输出是[10, 16]的矩阵,这其中包含了几步操作。

首先将[6, 6, 8, 32]的张量压成[1152, 8]的矩阵,即1152个ui。然后和[8, 16]的矩阵Wij相乘,得到[1152, 16]的矩阵,即1152个u^j|i



重点是u^j|i如何得到vj,这里使用了类似传统的全连接层,不过有三点不同:

(1)节点都是向量。这里u^j|i、sj、vj都是向量;传统网络则是标量。

(2)参数cij由Dynamic Routing算法得到;传统网络是通过反向传播得到。

(3)设计了sj和vj之间新的激活函数;传统网络一般用Sigmoid、ReLU等。

Dynamic Routing算法



bij←0:首先建一个维度跟cij一样的bij,里面元素全部初始化为0。

ci←softmax(bi):bij中每个行向量通过Softmax函数得到概率表示(因为初始所有元素都是0,所以开始时ci中的概率相等)

sj←∑iciju^j|i: 线性运算。

vj←squash(si):通过Squash函数将向量si的长度缩放到(0, 1)之间,这里相当于传统中的激活函数。

bij←bij+u^j|i⋅vj:更新bij。向量点乘u^j|i⋅vj用来度量u^j|i和vj之间的Agreement,点乘越大表示两者越相关。

得到的最终结果是一个[10, 16]的矩阵,即10个vj,分别表示数字0-9,||vj||表示每个数字的概率。

损失函数(Loss Function)

为了能够适应图片存在两个数字重叠的情况,如下图(数字6和7重叠):



论文使用了Margin Loss作为损失函数,对于DigitCaps中10个横向量vk,每个计算Lk:

Lk=Tkmax(0,m+−||vk||)2+λ(1−Tk)max(0,||vk||−m−)2

其中:

Tk:当图片中存在这个数字时Tk等于1,否则等于0;

λ:超参,默认0.5;

m+:超参,默认0.9;

m−:超参,默认0.1;

所以:

当图片中存在这个数字时,Lk=max(0,0.9−||vk||)2,此时||vk||越大,损失越小。

不存在这个数字时,Lk=0.5×max(0,||vk||−0.1)2,此时||vk||越大,损失越大。

*记得||vk||的取值范围是(0, 1)。

附:

(1)Squash函数理解

vj=||sj||21+||sj||2sj||sj||

sj||sj||,即单位向量。

||sj||21+||sj||2,将||sj||看作变量x,查看函数y=x21+x2的图像:



所以,越长的向量映射后长度越接近1,越短的向量映射后长度越接近0。

PS:这里我有个疑问:向量长度会不会出现等于0的情况?

Keras实现

数据准备

首先我们加载数据:

from keras.datasets import mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()


图片是灰度图,所以是单通道;如果是RGB图,那就是3通道;同理如果是RGBA图,那就是4通道了。

默认,共有60000个训练样本,10000个测试样本

x_train的shape是(60000, 28, 28),dtype是uint8,取值范围[0, 255]

y_train的shape是(60000, ),dtype是uint8,取值范围[0, 9]

在数据送入模型之前我们需要对数据进行以下预处理:

1. 整型 -> 浮点型;

2. 每一张图片维度改为
[28, 28, 1]
,表示宽度
28
、高度
28
、通道数为
1
的图片,然后除以255将取值范围归一化为
[0, 1]


3. 每一个输出由标量改为向量,即
5.0
->
[0.0, 0.0, 0.0, 0.0, 0.0,1.0, 0.0, 0.0, 0.0, 0.0]


代码:

from keras.utils import to_categorical

x_train = x_train.astype('float32').reshape(-1, 28, 28, 1) / 255.
x_test = x_test.astype('float32').reshape(-1, 28, 28, 1) / 255.
y_train = to_categorical(y_train.astype('float32'))
y_test = to_categorical(y_test.astype('float32'))


最终输入维度为[None, 28, 28, 1],输出维度为[None, 10, 1]。

模型构建

第一层(Image -> Conv1)

这一层传统卷积层Keras里已经有了,直接调用即可。

from keras import layers

layers.Conv2D(filters=256, kernel_size=9, strides=1, padding='valid', activation='relu', name='Conv1')


这里
padding='valid'
表示不填充,若
padding='same'
则表示填充额外的0。可参考这里

第二层(Conv1 -> PrimaryCaps)

这里的输入可以看作是长宽为[20, 20],256个通道的图片。

首先卷积操作:

layers.Conv2D(filters=256, kernel_size=9, strides=9, padding='valid', name='PrimaryCaps_conv2d')


输出是[None, 6, 6, 256]

然后Reshape操作:

layers.Reshape(target_shape=[1152, 8], name='PrimaryCaps_reshape')


输出是[None, 1152, 8]

最后再来一层类似激活函数的Squash函数,即对每个8维的Primary Capsule向量进行归一化:

layers.Lambda(squash, name='PrimaryCaps_squash')


输出还是[None, 1152, 8]

封装起来就是:

def PrimaryCaps(inputs, dim_capsule, n_channels, kernel_size, strides, padding):

primarycap_conv2d = layers.Conv2D(filters=dim_capsule*n_channels, kernel_size=kernel_size, strides=strides, padding=padding, name='PrimaryCaps_conv2d')(inputs)
primarycap_reshape = layers.Reshape(target_shape=[-1, dim_capsule], name='PrimaryCaps_reshape')(primarycap_conv2d)
outputs = layers.Lambda(squash, name='PrimaryCaps_squash')(primarycap_reshape)

return outputs


第三层(PrimaryCaps -> DigitCaps)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息