TensorFlow技术帖keras深度学习模型Machine Learning & Recommendation & NLP & DL

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

2019-01-24  本文已影响6人  王同学死磕技术

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

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


李宏毅老师capsule讲解的slide

Capsule算法简介

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

Squash函数:

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

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的计算过程,或者直接理解成聚类的迭代过程。相识度系数 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

上一篇下一篇

猜你喜欢

热点阅读