胶囊网络(Capsule)实战——keras算法练习(2)

Capsule是深度学习之父hinton在2017年提出来的一个较为轰动的网络结构。capsule这个结构主要的特点是:Vector in Vector out——向量进,向量出,而普通的神经元(Neuron)是Vector in Scalar out——向量进,标量出。capsule输出的向量比Neuron输出的标量表达出更丰富的特征。
下图台湾大学的李宏毅老师对capsule解读的slide。

  • Neuron的输出标量只能表示到是否存在鸟嘴。
  • capsule的输出的向量不仅能表示鸟嘴是否存在,而且还能表示出鸟嘴的方向(如图中向量第一维),鸟嘴的颜色等,鸟嘴的其他特征。

现在是不是开始感受到vector out 的威力了。


李宏毅老师capsule讲解的slide

Capsule算法简介

了解到capsule的强大之后,接下来笔者对Capsule算法实现做一个简单的介绍,感受一下为什么Capsule这么强大。Capsule结构有两个比较重要的创新,如下图所示:

  • Squash压缩激活函数
  • Dynamic Routing(动态路由)
    李宏毅老师capsule讲解的slide

Squash函数:

Squash(S) = \frac{S}{||S||}\frac{||S||^2} {1+||S||^2}
Squash压缩激活函数其实就是对向量进行一下压缩,但保留向量的模长信息,函数的表达式可以看作两部分:

  • \frac{S}{||S||}这部分是用向量除以向量本身的模,其含义是将就是将模长压缩为1。
  • \frac{||S||^2} {1+||S||^2}而这部分就可以看出S向量的模长||S||越大,则这部分值越大。其含义是如果一个向量的模长越长,其向量所代表的那个特征就越强,类比于神经网络中Neuron的输出值越大。

Dynamic Routing(动态路由):

这一部分作用的有如下2种理解方式:

  • 对输入的特征向量进行聚类。
  • 相似特征越多,这类特征越强的,从而弱化离群特征,类似于一个特征选择的过程,本质上也是个聚类过程。

如下图所示:Cupsule的输出向量a 和 输入向量u_1,u_2,u_3之间的内积相识度c,决定了a 最终包各个输入向量的信息程度。
a = Squash(c_1u_1+c_2u_2+c_3u_3)
u_1,u_2,u_3三个特征向量进来,通过动态路由循环,最后下图中c_1c_2 会比较大(u_1u_2比较相似),c_3会比较小。输出的a 就更多的保留了u_1u_2的信息。换个角度理解,如果有些特征向量很相似,他们的信息就会很大程度被保留下来。熟悉textrank算法的同学有没有感觉到,其过程很像通过文本相识度进行重要度排序的过程。

Dynamic Routing

下图是Dynamic Routing(动态路由)的详细计算过程。有点类似于RNN的计算过程,或者直接理解成聚类的迭代过程。相识度系数
c
在迭代过程中被计算出来,模型test时
c
也是动态决定的。
Dynamic Routing

综上,特征向量输入到Capsule之后,比普通神经网络中的Neuron有如下三点优势:

  • 做特征间的聚类,强化相似特征
  • 自动做特征选择,输出更有表达力的特征向量,
  • 输出特征向量的每个维度代表是 特征的特征

上述李老师的例子对3个特征向量只聚了一类。真实的情况一般是你输入一堆特征向量(matrix),返回一堆capsule处理后的特征向量(matrix),如下图所示:


Capsule实战部分——文本分类

数据载入

笔者这里使用的是评论情感分析数据集,之前的情感分析文章中介绍了这个数据集的数据格式,读者可以去这篇文章查看数据详情。

def read_data(data_path):
    senlist = []
    labellist = []  
    with open(data_path, "r",encoding='gb2312',errors='ignore') as f:
         for data in  f.readlines():
                data = data.strip()
                sen = data.split("\t")[2] 
                label = data.split("\t")[3]
                if sen != "" and (label =="0" or label=="1" or label=="2" ) :
                    senlist.append(sen)
                    labellist.append(label) 
                else:
                    pass                    
    assert(len(senlist) == len(labellist))            
    return senlist ,labellist 

sentences,labels = read_data("data_train.csv")
char_set = set(word for sen in sentences for word in sen)
char_dic = {j:i+1 for i,j in enumerate(char_set)}
char_dic["unk"] = 0

数据预处理

这部分就是将句子进行向量化,同时做padding。

def process_data(data,labels,dic,maxlen):
    sen2id = [[dic.get(char,0) for char in sen ] for sen in data]
    labels = np_utils.to_categorical(labels)
    return pad_sequences(sen2id,maxlen=maxlen),labels

train_data,train_labels = process_data(sentences,labels,char_dic,100)

模型定义

这里的代码笔者是借用的苏剑林大神基于pure Keras实现的Capsule。

import keras.backend as K
import numpy as np
#squash压缩函数和原文不一样,可自己定义
def squash(x, axis=-1):
    s_squared_norm = K.sum(K.square(x), axis, keepdims=True)
    scale = K.sqrt(s_squared_norm + K.epsilon())
    return x / scale

class Capsule(Layer):
    def __init__(self, num_capsule, dim_capsule, routings=3, kernel_size=(9, 1), share_weights=True,
                 activation='default', **kwargs):
        super(Capsule, self).__init__(**kwargs)
        self.num_capsule = num_capsule
        self.dim_capsule = dim_capsule
        self.routings = routings
        self.kernel_size = kernel_size
        self.share_weights = share_weights
        if activation == 'default':
            self.activation = squash
        else:
            self.activation = Activation(activation)

    def build(self, input_shape):
        super(Capsule, self).build(input_shape)
        input_dim_capsule = input_shape[-1]
        if self.share_weights:
            self.W = self.add_weight(name='capsule_kernel',
                                     shape=(1, input_dim_capsule,
                                            self.num_capsule * self.dim_capsule),
                                     # shape=self.kernel_size,
                                     initializer='glorot_uniform',
                                     trainable=True)
        else:
            input_num_capsule = input_shape[-2]
            self.W = self.add_weight(name='capsule_kernel',
                                     shape=(input_num_capsule,
                                            input_dim_capsule,
                                            self.num_capsule * self.dim_capsule),
                                     initializer='glorot_uniform',
                                     trainable=True)

    def call(self, u_vecs):
        if self.share_weights:
            u_hat_vecs = K.conv1d(u_vecs, self.W)
        else:
            u_hat_vecs = K.local_conv1d(u_vecs, self.W, [1], [1])

        batch_size = K.shape(u_vecs)[0]
        input_num_capsule = K.shape(u_vecs)[1]
        u_hat_vecs = K.reshape(u_hat_vecs, (batch_size, input_num_capsule,
                                            self.num_capsule, self.dim_capsule))
        u_hat_vecs = K.permute_dimensions(u_hat_vecs, (0, 2, 1, 3))

        b = K.zeros_like(u_hat_vecs[:, :, :, 0])  # shape = [None, num_capsule, input_num_capsule]
        #动态路由部分
        for i in range(self.routings):
            b = K.permute_dimensions(b, (0, 2, 1))  # shape = [None, input_num_capsule, num_capsule]
            c = K.softmax(b)
            c = K.permute_dimensions(c, (0, 2, 1))
            b = K.permute_dimensions(b, (0, 2, 1))
            outputs = self.activation(K.batch_dot(c, u_hat_vecs, [2, 2]))
            if i < self.routings - 1:
                b = K.batch_dot(outputs, u_hat_vecs, [2, 3])

        return outputs

    def compute_output_shape(self, input_shape):
        return (None, self.num_capsule, self.dim_capsule)

这里定义了一个文本分类模型构建,采用双层LSTM加Capsule的结构,同时你需要定义Capsule出来的向量个数n_cap,以及向量维度cap_dim,和动态路由的轮数routings。

def build_model(vocab,emb_dim,maxlen,n_cap,cap_dim,n_class):
    word_input = Input(shape=(None,), dtype="int32")
    embed = Embedding(input_dim=len(vocab),
              output_dim=100,
              input_length=maxlen
              )(word_input)
    x = Bidirectional(LSTM(100,return_sequences=True))(embed)
    x = Capsule(
        num_capsule=n_cap,dim_capsule=cap_dim,
        routings=3, share_weights=True)(x)
    x = Flatten()(x)
    x = Dropout(0.5)(x)
    outputs = Dense(n_class, activation='softmax')(x)
    model = Model(inputs=word_input, outputs=outputs)
    model.compile(loss='categorical_crossentropy', optimizer='nadam',metrics=['accuracy'])
    model.summary()
    return model

运行下方代码模型就构建成功了,同时从下图中keras的模型可视化输出可以看到,capsule的如果你的向量个数n_cap,以及向量维度cap_dim设置过大,参数还是挺多的。

model = build_model(char_dic,100,200,100,100,3)
model

模型训练

将数据喂给模型,指定好模型一些必要的参数,就可以训练起来了。

model.fit(train_data,train_labels,batch_size=16,epochs=3,validation_split=0.2)

结语

笔者这里没有去对比capsule结构和其他网络之间的性能,但是从一些capsule的实验中可以大致了解到capsule的 泛化能力较强,用向量代替标量表示特征,可以应付一下图片中pattern方向不同,大小不同,颜色不同等困难场景。所以这个网络还是很值得研究一番,笔者这里只是一个简介,大家可以看看我放在参考中的苏剑林大神的讲解和李宏毅教授的视频,甚至可以结合原文去仔细揣摩一番,可能收获更多。

参考:
https://kexue.fm/archives/4819

http://www.bilibili.com/video/av9770302?p=12&share_medium=android&share_source=qq&bbid=AFC24BAA-6165-47A4-8519-F10252D4DED038909infoc&ts=1548308610076

https://arxiv.org/abs/1710.09829

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容