0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

CapsNet入门系列番外:基于TensorFlow实现胶囊网络

zhKF_jqr_AI 来源:未知 作者:李建兵 2018-03-16 11:06 次阅读

编者按:全栈开发者Debarko De简明扼要地介绍了胶囊网络的概念,同时给出了基于numpy和TensorFlow的胶囊网络实现。

什么是胶囊网络?什么是胶囊?胶囊网络比卷积神经网络(CNN)更好吗?本文将讨论这些关于Hinton提出的CapsNet(胶囊网络)的话题

注意,本文讨论的不是制药学中的胶囊,而是神经网络和机器学习中的胶囊。

阅读本文前,你需要对CNN有基本的了解,否则建议你先看下我之前写的Deep Learning for Noobs。下面我将稍稍温习下与本文相关的CNN的知识,这样你能更容易理解下文CNN与CapsNet的对比。闲话就不多说了,让我们开始吧。

基本上,CNN是堆叠一大堆神经元构成的系统。CNN很擅长处理图像分类问题。让神经网络映射一张图像的所有像素,从算力上来讲,太昂贵了。而卷积在保留数据本质的前提下大大简化了计算。基本上,卷积是一大堆矩阵乘法,再将乘积相加。

图像传入网络后,一组核或过滤器扫描图像并进行卷积操作,从而创建特征映射。这些特征接着传给之后的激活层和池化层。取决于网络的层数,这一组合可能反复堆叠。激活网络给网络带来了一些非线性(比如ReLU)。池化(比如最大池化)有助于减少训练时间。池化的想法是为每个子区域创建“概要”。同时池化也提供了一些目标检测的位置和平移不变性。网络的最后是一个分类器,比如softmax分类器,分类器返回类别。训练基于对应标注数据的错误进行反向传播。在这一步骤中,非线性有助于解决梯度衰减问题。

CNN有什么问题?

在分类非常接近数据集的图像时,CNN表现极为出色。但CNN在颠倒、倾斜或其他朝向不同的图像上表现很差。训练时添加同一图像的不同变体可以解决这一问题。在CNN中,每层对图像的理解粒度更粗。举个例子,假设你试图分类船和马。最内层(第一层)理解细小的曲线和边缘。第二层可能理解直线或小形状,例如船的桅杆和整个尾巴的曲线。更高层开始理解更复杂的形状,例如整条尾巴或船体。最后层尝试总览全图(例如整条船或整匹马)。我们在每层之后使用池化,以便在合理的时间内完成计算,但本质上池化同时丢失了位置信息

畸形变换

池化有助于建立位置不变性。否则CNN将只能拟合非常接近训练集的图像或数据。这样的不变性同时导致具备船的部件但顺序错误的图像被误认为船。所以系统会把上图右侧的图像误认为船,而人类则能很清楚地观察到两者的区别。另外,池化也有助于建立比例不变性。

比例不变性

池化本来是用来引入位置、朝向、比例不变性的,然而这一方法非常粗糙。事实上池化加入了各种位置不变性,以致将部件顺序错误的图像也误认为船了。我们需要的不是不变性,而是等价性。不变性使CNN可以容忍视角中的小变动,而等价性使CNN理解朝向和比例变动,并相应地适应图像,从而不损失图像的空间位置信息。CNN会减少自身尺寸以检测较小的船。这导向了最近发展出的胶囊网络。

什么是胶囊网络?

Sara Sabour、Nicholas Frost、Geoffrey Hinton在2017年10月发表了论文Dynamic Routing Between Capsules。当深度学习的祖父之一Geoffrey Hinton发表了一篇论文,这论文注定会是一项重大突破。整个深度学习社区都为此疯狂。这篇论文讨论了胶囊、胶囊网络以及在MNIST上的试验。MNIST是已标注的手写数字图像数据集。相比当前最先进的CNN,胶囊网络在重叠数字上的表现明显提升。论文的作者提出人脑有一个称为“胶囊”的模块,这些胶囊特别擅长处理不同的视觉刺激,以及编码位姿(位置、尺寸、朝向)、变形、速度、反射率、色调、纹理等信息。大脑肯定具备“路由”低层视觉信息至最擅长处理该信息的卷囊的机制。

胶囊网络架构

胶囊是一组嵌套的神经网络层。在通常的神经网络中,你不断添加更多层。在胶囊网络中,你会在单个网络层中加入更多的层。换句话说,在一个神经网络层中嵌套另一个。胶囊中的神经元的状态刻画了图像中的一个实体的上述属性。胶囊输出一个表示实体存在性的向量。向量的朝向表示实体的属性。向量发送至神经网络中所有可能的亲本胶囊。胶囊可以为每个可能的亲本计算出一个预测向量,预测向量是通过将自身权重乘以权重矩阵得出的。预测向量乘积标量最大的亲本胶囊的联系将增强,而剩下的亲本胶囊联系将减弱。这一基于合意的路由方法比诸如最大池化之类的现有机制更优越。最大池化路由基于低层网络检测出的最强烈的特征。动态路由之外,胶囊网络给胶囊加上了squash函数。squash属于非线性函数。与CNN给每个网络层添加squash函数不同,胶囊网络给每组嵌套的网络层添加squash函数,从而将squash函数应用到每个胶囊的输出向量。

论文引入了一个全新的squash函数(见上图)。ReLU及类似的非线性函数在单个神经元上表现良好,不过论文发现在胶囊上squash函数表现最好。squash函数压缩胶囊的输出向量的长度:当向量较小时,结果为0;当向量较大时,结果为1。动态路由增加了一些额外的运算开销,但毫无疑问带来了优势。

当然我们也要注意,这篇论文刚发不久,胶囊的概念还没有经过全面的测试。它在MNIST数据集上表现良好,但在其他更多种类、更大的数据集上的表现还有待证明。在论文发布的几天之内,就有人提出一些意见。

当前的胶囊网络实现还有改进的空间。不过别忘了Hinton的论文一开始就提到了:

这篇论文的目标不是探索整个空间,而是简单地展示一个相当直接的实现表现良好,同时动态路由有所裨益。

好了,我们已经谈了够多理论了。让我们找点乐子,构建一个胶囊网络。我将引领你阅读一些为MNIST数据配置一个胶囊网络的代码。我会在代码里加上注释,这样你可以逐行理解这些代码是如何工作的。本文将包括两个重要的代码片段。其余代码见GitHub仓库:

# 只依赖numpy和tensorflow

import numpy as np

import tensorflow as tf

from config import cfg

# 定义卷积胶囊类,该类由多个神经网络层组成

#

classCapsConv(object):

''' 胶囊层

参数

input:一个4维张量。

num_units:整数,胶囊的输出向量的长度。

with_routing:布尔值,该胶囊路由经过低层胶囊。

num_outputs:该层中的胶囊数目。

返回:

一个4维张量。

'''

def __init__(self, num_units, with_routing=True):

self.num_units = num_units

self.with_routing = with_routing

def __call__(self, input, num_outputs, kernel_size=None, stride=None):

self.num_outputs = num_outputs

self.kernel_size = kernel_size

self.stride = stride

ifnot self.with_routing:

# 主胶囊(PrimaryCaps)层

# 输入: [batch_size, 20, 20, 256]

assert input.get_shape() == [cfg.batch_size, 20, 20, 256]

capsules = []

for i in range(self.num_units):

# 每个胶囊i: [batch_size, 6, 6, 32]

with tf.variable_scope('ConvUnit_' + str(i)):

caps_i = tf.contrib.layers.conv2d(input,

self.num_outputs,

self.kernel_size,

self.stride,

padding="VALID")

caps_i = tf.reshape(caps_i, shape=(cfg.batch_size, -1, 1, 1))

capsules.append(caps_i)

assert capsules[0].get_shape() == [cfg.batch_size, 1152, 1, 1]

# [batch_size, 1152, 8, 1]

capsules = tf.concat(capsules, axis=2)

capsules = squash(capsules)

assert capsules.get_shape() == [cfg.batch_size, 1152, 8, 1]

else:

# 数字胶囊(DigitCaps)层

# reshape输入至:[batch_size, 1152, 8, 1]

self.input = tf.reshape(input, shape=(cfg.batch_size, 1152, 8, 1))

# b_IJ: [1, num_caps_l, num_caps_l_plus_1, 1]

b_IJ = tf.zeros(shape=[1, 1152, 10, 1], dtype=np.float32)

capsules = []

for j in range(self.num_outputs):

with tf.variable_scope('caps_' + str(j)):

caps_j, b_IJ = capsule(input, b_IJ, j)

capsules.append(caps_j)

# 返回一个张量:[atch_size, 10, 16, 1]

capsules = tf.concat(capsules, axis=1)

assert capsules.get_shape() == [cfg.batch_size, 10, 16, 1]

return(capsules)

def capsule(input, b_IJ, idx_j):

''' 层l+1中的单个胶囊的路由算法

参数:

input: 张量 [batch_size, num_caps_l=1152, length(u_i)=8, 1]

num_caps_l为l层的胶囊数

返回:

张量 [batch_size, 1, length(v_j)=16, 1] 表示

l+1层的胶囊j的输出向量`v_j`

注意:

u_i表示l层胶囊i的输出向量,

v_j则表示l+1层胶囊j的输出向量

'''

with tf.variable_scope('routing'):

w_initializer = np.random.normal(size=[1, 1152, 8, 16], scale=0.01)

W_Ij = tf.Variable(w_initializer, dtype=tf.float32)

# 重复batch_size次W_Ij:[batch_size, 1152, 8, 16]

W_Ij = tf.tile(W_Ij, [cfg.batch_size, 1, 1, 1])

# 计算 u_hat

# [8, 16].T x [8, 1] => [16, 1] => [batch_size, 1152, 16, 1]

u_hat = tf.matmul(W_Ij, input, transpose_a=True)

assert u_hat.get_shape() == [cfg.batch_size, 1152, 16, 1]

shape = b_IJ.get_shape().as_list()

size_splits = [idx_j, 1, shape[2] - idx_j - 1]

for r_iter in range(cfg.iter_routing):

# 第4行:

# [1, 1152, 10, 1]

c_IJ = tf.nn.softmax(b_IJ, dim=2)

assert c_IJ.get_shape() == [1, 1152, 10, 1]

# 第5行:

# 在第三维使用c_I加权u_hat

# 接着在第二维累加,得到[batch_size, 1, 16, 1]

b_Il, b_Ij, b_Ir = tf.split(b_IJ, size_splits, axis=2)

c_Il, c_Ij, b_Ir = tf.split(c_IJ, size_splits, axis=2)

assert c_Ij.get_shape() == [1, 1152, 1, 1]

s_j = tf.multiply(c_Ij, u_hat)

s_j = tf.reduce_sum(tf.multiply(c_Ij, u_hat),

axis=1, keep_dims=True)

assert s_j.get_shape() == [cfg.batch_size, 1, 16, 1]

# 第六行:

# 使用上文提及的squash函数,得到:[batch_size, 1, 16, 1]

v_j = squash(s_j)

assert s_j.get_shape() == [cfg.batch_size, 1, 16, 1]

# 第7行:

# 平铺v_j,由[batch_size ,1, 16, 1] 至[batch_size, 1152, 16, 1]

# [16, 1].T x [16, 1] => [1, 1]

# 接着在batch_size维度递归运算均值,得到 [1, 1152, 1, 1]

v_j_tiled = tf.tile(v_j, [1, 1152, 1, 1])

u_produce_v = tf.matmul(u_hat, v_j_tiled, transpose_a=True)

assert u_produce_v.get_shape() == [cfg.batch_size, 1152, 1, 1]

b_Ij += tf.reduce_sum(u_produce_v, axis=0, keep_dims=True)

b_IJ = tf.concat([b_Il, b_Ij, b_Ir], axis=2)

return(v_j, b_IJ)

def squash(vector):

'''压缩函数

参数:

vector:一个4维张量 [batch_size, num_caps, vec_len, 1],

返回:

一个和vector形状相同的4维张量,

但第3维和第4维经过压缩

'''

vec_abs = tf.sqrt(tf.reduce_sum(tf.square(vector))) # 一个标量

scalar_factor = tf.square(vec_abs) / (1 + tf.square(vec_abs))

vec_squashed = scalar_factor * tf.divide(vector, vec_abs) # 对应元素相乘

return(vec_squashed)

上面是一整个胶囊层。堆叠胶囊层以构成胶囊网络。

import tensorflow as tf

from config import cfg

from utils import get_batch_data

from capsLayer importCapsConv

classCapsNet(object):

def __init__(self, is_training=True):

self.graph = tf.Graph()

with self.graph.as_default():

if is_training:

self.X, self.Y = get_batch_data()

self.build_arch()

self.loss()

# t_vars = tf.trainable_variables()

self.optimizer = tf.train.AdamOptimizer()

self.global_step = tf.Variable(0, name='global_step', trainable=False)

self.train_op = self.optimizer.minimize(self.total_loss, global_step=self.global_step) # var_list=t_vars)

else:

self.X = tf.placeholder(tf.float32,

shape=(cfg.batch_size, 28, 28, 1))

self.build_arch()

tf.logging.info('Seting up the main structure')

def build_arch(self):

with tf.variable_scope('Conv1_layer'):

# Conv1(第一卷积层), [batch_size, 20, 20, 256]

conv1 = tf.contrib.layers.conv2d(self.X, num_outputs=256,

kernel_size=9, stride=1,

padding='VALID')

assert conv1.get_shape() == [cfg.batch_size, 20, 20, 256]

# TODO: 将'CapsConv'类重写为函数,

# capsLay函数应该封装为两个函数,

# 一个类似conv2d,另一个为TensorFlow的fully_connected(全连接)。

# 主胶囊,[batch_size, 1152, 8, 1]

with tf.variable_scope('PrimaryCaps_layer'):

primaryCaps = CapsConv(num_units=8, with_routing=False)

caps1 = primaryCaps(conv1, num_outputs=32, kernel_size=9, stride=2)

assert caps1.get_shape() == [cfg.batch_size, 1152, 8, 1]

# 数字胶囊层,[batch_size, 10, 16, 1]

with tf.variable_scope('DigitCaps_layer'):

digitCaps = CapsConv(num_units=16, with_routing=True)

self.caps2 = digitCaps(caps1, num_outputs=10)

# 前文示意图中的编码器结构

# 1. 掩码:

with tf.variable_scope('Masking'):

# a). 计算 ||v_c||,接着计算softmax(||v_c||)

# [batch_size, 10, 16, 1] => [batch_size, 10, 1, 1]

self.v_length = tf.sqrt(tf.reduce_sum(tf.square(self.caps2),

axis=2, keep_dims=True))

self.softmax_v = tf.nn.softmax(self.v_length, dim=1)

assert self.softmax_v.get_shape() == [cfg.batch_size, 10, 1, 1]

# b). 选取10个胶囊的最大softmax值的索引

# [batch_size, 10, 1, 1] => [batch_size] (index)

argmax_idx = tf.argmax(self.softmax_v, axis=1, output_type=tf.int32)

assert argmax_idx.get_shape() == [cfg.batch_size, 1, 1]

# c). 索引

# 由于我们是三维生物,

# 理解argmax_idx的索引过程并不容易

masked_v = []

argmax_idx = tf.reshape(argmax_idx, shape=(cfg.batch_size, ))

for batch_size in range(cfg.batch_size):

v = self.caps2[batch_size][argmax_idx[batch_size], :]

masked_v.append(tf.reshape(v, shape=(1, 1, 16, 1)))

self.masked_v = tf.concat(masked_v, axis=0)

assert self.masked_v.get_shape() == [cfg.batch_size, 1, 16, 1]

# 2. 使用3个全连接层重建MNIST图像

# [batch_size, 1, 16, 1] => [batch_size, 16] => [batch_size, 512]

with tf.variable_scope('Decoder'):

vector_j = tf.reshape(self.masked_v, shape=(cfg.batch_size, -1))

fc1 = tf.contrib.layers.fully_connected(vector_j, num_outputs=512)

assert fc1.get_shape() == [cfg.batch_size, 512]

fc2 = tf.contrib.layers.fully_connected(fc1, num_outputs=1024)

assert fc2.get_shape() == [cfg.batch_size, 1024]

self.decoded = tf.contrib.layers.fully_connected(fc2, num_outputs=784, activation_fn=tf.sigmoid)

def loss(self):

# 1. 边际损失

# [batch_size, 10, 1, 1]

# max_l = max(0, m_plus-||v_c||)^2

max_l = tf.square(tf.maximum(0., cfg.m_plus - self.v_length))

# max_r = max(0, ||v_c||-m_minus)^2

max_r = tf.square(tf.maximum(0., self.v_length - cfg.m_minus))

assert max_l.get_shape() == [cfg.batch_size, 10, 1, 1]

# reshape: [batch_size, 10, 1, 1] => [batch_size, 10]

max_l = tf.reshape(max_l, shape=(cfg.batch_size, -1))

max_r = tf.reshape(max_r, shape=(cfg.batch_size, -1))

# 计算 T_c: [batch_size, 10]

# T_c = Y,我的理解没错吧?试试看。

T_c = self.Y

# [batch_size, 10],对应元素相乘

L_c = T_c * max_l + cfg.lambda_val * (1 - T_c) * max_r

self.margin_loss = tf.reduce_mean(tf.reduce_sum(L_c, axis=1))

# 2. 重建损失

orgin = tf.reshape(self.X, shape=(cfg.batch_size, -1))

squared = tf.square(self.decoded - orgin)

self.reconstruction_err = tf.reduce_mean(squared)

# 3. 总损失

self.total_loss = self.margin_loss + 0.0005 * self.reconstruction_err

# 总结

tf.summary.scalar('margin_loss', self.margin_loss)

tf.summary.scalar('reconstruction_loss', self.reconstruction_err)

tf.summary.scalar('total_loss', self.total_loss)

recon_img = tf.reshape(self.decoded, shape=(cfg.batch_size, 28, 28, 1))

tf.summary.image('reconstruction_img', recon_img)

self.merged_sum = tf.summary.merge_all()

完整代码(含训练和验证模型)见此(https://github.com/debarko/CapsNet-Tensorflow)。代码以Apache 2.0许可发布。我参考了naturomics的代码(https://github.com/naturomics)。

总结

我们介绍了胶囊网络的概念以及如何实现胶囊网络。我们尝试理解胶囊是高层的嵌套神经网络层。我们也查看了胶囊网络是如何交付朝向和其他不变性的——对图像中的每个实体而言,保持空间配置的等价性。我确信存在一些本文没有回答的问题,其中最主要的大概是胶囊及其最佳实现。不过本文是解释这一主题的初步尝试。如果你有任何疑问,请评论。我会尽我所能回答。

Siraj Raval及其演讲对本文影响很大。请在Twitter上分享本文。在twitter关注我以便在未来获取更新信息。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 算法
    +关注

    关注

    23

    文章

    4456

    浏览量

    90757
  • 人工智能
    +关注

    关注

    1776

    文章

    43860

    浏览量

    230607

原文标题:CapsNet入门系列番外:基于TensorFlow实现胶囊网络

文章出处:【微信号:jqr_AI,微信公众号:论智】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    树莓派时光胶囊

    `一次偶然的尝试,我发现一个玻璃瓶子刚好能装进树莓派。透过透明的玻璃瓶子,看上去有种独特的怀旧美感,马上我就想到做一个时光胶囊是一个不错的主意。用树莓派保存数码内容,把它作为一个稳定的平台从手机
    发表于 01-21 11:52

    干货 | TensorFlow的55个经典案例

    TensorFlow 提供了简化的接口的库。里面有很多示例和预构建的运算和层。使用教程:TFLearn 快速入门。通过一个具体的机器学习任务学习 TFLearn 基础。开发和训练一个深度神经网络分类器
    发表于 10-09 11:28

    TensorFlow如何入门

    TensorFlow 入门(四)
    发表于 05-27 17:40

    tensorflow怎么入门

    tensorflow入门(五)
    发表于 05-28 13:05

    TensorFlow是什么

    更长。TensorFlow 使这一切变得更加简单快捷,从而缩短了想法到部署之间的实现时间。在本教程中,你将学习如何利用 TensorFlow 的功能来实现深度神经
    发表于 07-22 10:14

    TensorFlow实现多元线性回归(超详细)

    TensorFlow 实现简单线性回归的基础上,可通过在权重和占位符的声明中稍作修改来对相同的数据进行多元线性回归。在多元线性回归的情况下,由于每个特征具有不同的值范围,归一化变得至关重要
    发表于 08-11 19:35

    基于单片机胶囊咖啡机控制系统的设计

    【资源下载】下载地址如下:仿真商用胶囊咖啡机控制系统的设计及实现(仿真电路图+源程序+演示视频+任务书+论文)1.商用胶囊咖啡机控制系统主要具备的功能(1)设计的商用胶囊咖啡机拥有两个
    发表于 07-19 06:25

    用tflite接口调用tensorflow模型进行推理

    摘要本文为系列博客tensorflow模型部署系列的一部分,用于实现通用模型的部署。本文主要实现用tflite接口调用
    发表于 12-22 06:51

    TensorFlow将神经网络量化为8位

    随着TensorFlow Lite的推出,TensorFlow已经更新了量化技术和工具,您可以使用这些技术和工具来提高网络性能。 本指南向您展示如何量化网络,使其在训练过程中使用8位数
    发表于 08-10 06:01

    一种新型神经网络结构:胶囊网络

    胶囊网络是 Geoffrey Hinton 提出的一种新型神经网络结构,为了解决卷积神经网络(ConvNets)的一些缺点,提出了胶囊
    的头像 发表于 02-02 09:25 5457次阅读

    ICLR 2019论文解读:胶囊图神经网络的PyTorch实现

    上图所示为CapsGNN的简化版本。它由三个关键模块组成:1)基本节点胶囊提取模块:GNN用于提取具有不同感受野的局部顶点特征,然后在该模块中构建主节点胶囊。 2)高级图胶囊提取模块:融合了注意力模块和动态路由,以生成多个图
    的头像 发表于 03-29 10:11 7822次阅读
    ICLR 2019论文解读:<b class='flag-5'>胶囊</b>图神经<b class='flag-5'>网络</b>的PyTorch<b class='flag-5'>实现</b>

    CapsNet再升级!堆栈式胶囊自编码器面世

    这也是胶囊网络第一次在无监督领域取得新的突破。
    的头像 发表于 06-24 15:43 4117次阅读
    <b class='flag-5'>CapsNet</b>再升级!堆栈式<b class='flag-5'>胶囊</b>自编码器面世

    胶囊网络在短文本多种意图识别的应用及研究

    意图识别是人机对话系统中口语理解的关键子任务。考虑到当前用户表达存在多个意图的问题,主要采用胶囊网络构造基于单意图标记的多意图分类器对用户表达的多种意图进行识别。为了保证意图文本的特征质量,通过
    发表于 04-07 14:14 10次下载
    <b class='flag-5'>胶囊</b><b class='flag-5'>网络</b>在短文本多种意图识别的应用及研究

    基于深度图卷积胶囊网络融合的图分类模型

    针对提取图表征用于图分类过程中的结构信息提取过程的问题,提出了一种图卷积神经网络胶囊网络融合的图分类模型。首先,利用图卷积神经网络处理图中的节点信息,迭代以后得到节点表征,表征中蕴含
    发表于 05-07 15:17 9次下载

    轻松入门,高效成长: "TensorFlow 机器学习技能解锁季"

    体系,体验 TensorFlow 的最佳实践模式? 《 TensorFlow 入门实操课程》 带你零基础快速上手 想高效实践机器学习模型部署,借助 TensorFlow 在多端灵活运行
    的头像 发表于 11-10 11:35 589次阅读