本文亮点:

将用于自然语言处理的CNN架构,从keras0.3.3搬运到了keras2.x,强行练习了Sequential+Model的混合使用,具体来说,是Model里嵌套了Sequential。

本文背景:

暑假在做一个推荐系统的小项目,老师让我们搜集推荐系统领域Top5的算法和模型,要求结合深度学习。

我和小伙伴选择了其中的两篇文献深入研究,我负责跑通文献Convolutional Matrix Factorization for Document Context-Aware Recommendation里的模型,这是Recsys16下载量Top2的文章,仅次于youtube那篇。论文链接:http://dm.postech.ac.kr/~cartopy/ConvMF/ ,文章最下有数据集和源代码。

源代码的配置环境比较旧,基于python2.7和keras0.3.3,高级的地方是使用了GPU加速训练。其中用到的处理item document的CNN架构完全用keras0.3.3写的(这里的CNN用于NLP,与用于图像识别的CNN架构上有一定区别),其余的.py文件都是用py2写的

一、codes搬运:从python2到python3

现在py3用的比较多,也是趋势,所以我首先确定把py2的代码改写成py3能用的

python2和python3在code时有一定区别,在改写成py3时,有些简单的根据报错信息,百度google下很容易debug出来,比如:

print的内容在py3里要加括号

xrange-->range

zip()[]-->list(zip())[]

以上这些涉及到的代码就相当多了

真正麻烦耗时的是,二进制文件的读取,涉及编码类型。文献用的movielens数据集不是csv文件,下载下来查看了下有的编码是"utf-8",有的是"ANSI"。

我在下面这些地方研究了很久,简要记录下:

cpickle-->pikle,pickle.load,pickle.dump,"rb","wb","r"...

codecs.open(,"utf-8-sig")

二、CNN架构搬运:从keras0.3.3到keras2.x

两个版本的区别:keras0.3.3和keras2.x相差太多(keras0.x和keras1.x就相差很多了),主要是后者移除了Graph,新增了函数式模型--Model。

两个模型的区别:简单来说,Sequential是单输入单输出,Model(Graph)是多输入多输出。Graph图模型,建模时依然是向其中add_input, add_node, add_output,而2.x版的Model是一种函数式模型,建模时先定义Input,传递给Layer1,做了一次矩阵计算,再将当前层的输出,传递给Layer2,又做了一次矩阵计算···将最后的输出定义为整个模型的输出,最开始的输入定义为整个模型的输入,相当于给一个输入,得到一个输出,中间经过的黑箱就是我们的模型,这种行为就是函数,所以叫函数式模型。在2.x里,更强调Sequential是Model的特殊情况(这是文档里的原话,那不就意味着,必要时,Sequential也能当函数式模型用)。

改写难度:就该文献而言,改写难度在于源代码混合使用了Sequential+Graph,具体来说,是Graph里嵌套了Sequential。从0.x到2.x可以简单认为Graph对应着Model,这意味我可能要同时使用Sequential+Model,这是思路1。此时最希望的就是有现成的例子可以参考啊,而博客、文档中能找到的例子都是单独使用Sequential和Graph(Model)的,难道改写时,需要换成全用Sequential或全用Model,这里分别产生了思路2和思路3。

我很快决定采用思路1的主要原因有两点:

1)直觉:源代码混合使用了Sequential+Graph,从0.x到2.x可以简单认为Graph对应着Model,这暗示Sequential+Model可行(debug到实在不行我再全部改用Sequential);

2)就像前面文档里提到的,“在2.x里,更强调Sequential是Model的特殊情况”,那不就意味着,必要时,Sequential也能当函数式模型用(在写这篇博客时,我发现这里和思路3本质相同);

其实对于keras小白来说,快速选择思路1,主要还是靠直觉,后面也是理解了CNN架构、源代码逻辑,仔细查看两个版本的文档时,越来越确认思路1可行。所以,这也是下面要介绍的。

改写的正确姿势:看懂CNN的架构(原理图、流程图)-->看懂keras0.3.3写的架构-->对应着改写到keras2.x

1、CNN的架构

输入层

输入对象为文档,可看成长度为l的词序列,每篇文档长度不等,统一设置max_len = 300(后面在keras序列数据预处理时用pad_sequence填充)

keras0.x:add_input

model = Graph()

'''Input'''

model.add_input(name='input', input_shape=(max_len,), dtype=int)

keras2.x:Input

'''Input'''

doc_input = Input(shape=(max_len,), dtype='int32', name='doc_input')

print("Builded input...")

嵌入层

将one-hot编码的词向量(每个词向量维度=词典单词总数,这里vocab_size = 8000)嵌入成p维,这里emb_dim = 200,此时词序列的shape为p*l

Reshape层

进入Conv2D之前,reshape成channels last的形式

卷积层:3种不同window size的filter,每种filter提取出的特征图深度均为100,nb_filters=100,

池化层:3种不同window size的filter

\(W_c^j\)提取\(c_i^j\)(第\(i\)个单词的\(j\)号上下文特征),每篇文档词序列长度为\(l\),共有\(l-ws+1\)个单词会被提取上下文特征,每个单词由\(n_c\)种不同的共享权重\(W_c^1,...,W_c^j,...,W_c^{n_c}\)提取\(n_c\)种特征,所以一篇文档,经卷积层提取出的上下文特征的shape是\(n_c*(l-ws+1)\),\(n_c\)相当于用于图像识别的CNN中特征图深度这一概念,这里特征图深度\(n_c=nbfilters=100\)

\[c^1=[c_1^1,c_2^1,...c_i^1,...c_{l-ws+1}^1]
\]

\[c^2=[c_1^2,c_2^2,...c_i^2,...c_{l-ws+1}^2]
\]

\[...
\]

\[c^j=[c_1^j,c_2^j,...c_i^j,...c_{l-ws+1}^j]
\]

\[...
\]

\[c^{n_c}=[c_1^{n_c},c_2^{n_c},...c_i^{n_c},...c_{l-ws+1}^{n_c}]
\]

这里文档长度\(l\)和卷积核\(ws\)不同,提取出的特征矩阵shape不同,但只要经过maxpooling层,每个\(c^j\)取最大,最终得到\(n_c=100\)维的特征向量

\[d_f=[max(c^1),max(c^2),...,max(c^j),...,max(c^{n_c}))]
\]

flatten层

全连接层(200)&projection层(50)

2、源代码逻辑

这里的CNN架构本质上是单输入单输出,输入是每一个item的document,输出是文档隐向量。中间用了3个不同window size的filter对reshape的输出做卷积&池化,flatten之后(斜体部分用循环生成了3个Sequential,注意这里不是共享层,共享层要求结构相同,这里3个不同的filter做卷积&池化,layer的结构明显不同),有3个outputs(都是100维的向量),再作为inputs输入到后面的layers(依次:全连接层200维,projection层50维)时,实际将3个inputs拼接起来了。

我一开始没注意到这里的拼接,文献和源代码也没有明显指出3个inputs的拼接,只是如果不拼接,每个input单独输入后面的共享层,从100维到200维再到50维,很奇怪!!一般都是高维的特征进入全连接层进行特征压缩,应该降维才对,这里居然升维,而如果3个100维的inputs拼接起来变成300维,再降到200维,最后降到50维,这样才合理,这是我的推断。后来我在keras0.3.3写的源代码找到了一个拼接的证据,这步里用到了add_node:

model.add_node(Dense(vanila_dimension, activation='tanh'), name='fully_connect', inputs=['unit_' + str(i) for i in filter_lengths])

这里的3个inputs实际是concat了在一起,变成3*100维,下面经过fc变成200维,再经过pj变成50维

keras0.3.3文档中关于add_node,默认merge_mode=‘cancat’

文档是这样定义的:https://keras.io/layers/containers/

add_node(layer, name, input=None, inputs=[], merge_mode='concat', concat_axis=-1, dot_axes=-1, create_output=False)

解释:Add a node in the graph. It can be connected to multiple inputs, which will first be merged into one tensor according to the mode specified.

另外,我也找到Graph对于多输入做merge的例子:https://github.com/cartopy/keras-0.3.3/blob/master/docs/templates/models.md

思考卷积&池化,选择3种window size的filters意义何在:不同ws的filter对每个单词提取的上下文范围不同,对应了不同的空间尺度空间,最后再组合起来。有点类似图像处理里,将二维图像分解到不同空间尺度上。这里将每个单词的上下文特征分解到3个尺度上,最后再拼接回来。当然也可以分解到更多空间尺度上。

========================================================================================================

分割线内容写于这篇blog初步发表的一天后:选择3个不同window size的filter处理嵌入后的文本:卷积&池化(&flatten)之后再拼接,这是用CNN做NLP的基本操作,改写codes的时候,我只学过CNN做图像识别,后来学了点CNN做文本分类,恍然大悟,原来就是需要拼接的

========================================================================================================

3、两个版本的代码对比如下

首先是keras0.3.3:


  1. # coding: utf-8
  2. '''
  3. Created on Dec 8, 2015
  4. @author: donghyun
  5. '''
  6. import numpy as np
  7. np.random.seed(1337)
  8. from keras.callbacks import EarlyStopping
  9. from keras.layers.containers import Sequential
  10. from keras.layers.convolutional import Convolution2D, MaxPooling2D
  11. from keras.layers.core import Reshape, Flatten, Dropout, Dense
  12. from keras.layers.embeddings import Embedding
  13. from keras.models import Graph
  14. from keras.preprocessing import sequence
  15. output_dimesion = 50
  16. vocab_size = 8000
  17. dropout_rate = 0.2
  18. emb_dim = 200
  19. max_len = 300
  20. nb_filters = 100
  21. init_W = None
  22. max_features = vocab_size
  23. vanila_dimension = 200
  24. projection_dimension = output_dimesion
  25. filter_lengths = [3, 4, 5]
  26. model = Graph()
  27. print("Building Embedding Layer...")
  28. '''Embedding Layer'''
  29. model.add_input(name='input', input_shape=(max_len,), dtype=int)
  30. if init_W is None:
  31. model.add_node(Embedding(max_features, emb_dim, input_length=max_len), name='sentence_embeddings', input='input')
  32. else:
  33. self.model.add_node(Embedding(max_features, emb_dim, input_length=max_len, weights=[init_W / 20]), name='sentence_embeddings', input='input')
  34. print("Building Convolution Layer & Max Pooling Layer...")
  35. '''Convolution Layer & Max Pooling Layer'''
  36. for i in filter_lengths:
  37. model_internal = Sequential()
  38. model_internal.add(Reshape(dims=(1, self.max_len, emb_dim), input_shape=(self.max_len, emb_dim)))
  39. model_internal.add(Convolution2D(nb_filters, i, emb_dim, activation="relu"))
  40. model_internal.add(MaxPooling2D(pool_size=(self.max_len - i + 1, 1)))
  41. model_internal.add(Flatten())
  42. model.add_node(model_internal, name='unit_' + str(i), input='sentence_embeddings')
  43. # 以上for循环体内的代码要缩进
  44. print("Building Fully Connect Layer & Dropout Layer...")
  45. '''Fully Connect Layer'''
  46. model.add_node(Dense(vanila_dimension, activation='tanh'), name='fully_connect', inputs=['unit_' + str(i) for i in filter_lengths])
  47. # 这里的3个inputs实际是concat了在一起,变成3*100维,下面经过fc变成200维,再经过pj变成50维
  48. # keras0.3.3文档中关于add_node,默认merge_mode=‘cancat’,https://keras.io/layers/containers/
  49. '''Dropout Layer'''
  50. model.add_node(Dropout(dropout_rate), name='dropout', input='fully_connect')
  51. print("Building Projection Layer & Output Layer...")
  52. '''Projection Layer & Output Layer'''
  53. model.add_node(Dense(projection_dimension, activation='tanh'), name='projection', input='dropout')
  54. # Output Layer
  55. model.add_output(name='output', input='projection')
  56. model.compile('rmsprop', {'output': 'mse'})

keras2.x:


  1. import numpy as np
  2. np.random.seed(1337)
  3. from keras.callbacks import EarlyStopping
  4. from keras.layers.convolutional import Conv2D, MaxPooling2D
  5. from keras.layers.core import Reshape, Flatten, Dropout
  6. from keras.layers import Input, Embedding, Dense
  7. from keras.models import Model, Sequential
  8. from keras.preprocessing import sequence
  9. output_dimesion = 50
  10. vocab_size = 8000
  11. dropout_rate = 0.2
  12. emb_dim = 200
  13. max_len = 300
  14. nb_filters = 100
  15. init_W = None
  16. max_features = vocab_size
  17. vanila_dimension = 200
  18. projection_dimension = output_dimesion
  19. filter_lengths = [3, 4, 5]
  20. '''Input'''
  21. doc_input = Input(shape=(max_len,), dtype='int32', name='doc_input')
  22. print("Builded input...")
  23. '''Embedding Layer'''
  24. if init_W is None:
  25. sentence_embeddings = Embedding(output_dim=emb_dim, input_dim=max_features, input_length=max_len, name='sentence_embeddings')(doc_input)
  26. else:
  27. sentence_embeddings = Embedding(output_dim=emb_dim, input_dim=max_features, input_length=max_len, weights=[init_W / 20], name='sentence_embeddings')(doc_input)
  28. print("Builded Embedding Layer...")
  29. '''Reshape Layer'''
  30. reshape = Reshape(target_shape=(max_len, emb_dim, 1), name='reshape')(sentence_embeddings) # chanels last
  31. print("Builded Reshape Layer...")
  32. flatten_ = []
  33. for i in filter_lengths:
  34. model_internal = Sequential()
  35. model_internal.add(Conv2D(nb_filters, (i, emb_dim), activation="relu", name='conv2d_' + str(i), input_shape=(self.max_len, emb_dim, 1)))
  36. # chanels last,默认了strides=(1,1), padding='valid'
  37. model_internal.add(MaxPooling2D(pool_size=(self.max_len - i + 1, 1), name='maxpool2d_' + str(i)))
  38. model_internal.add(Flatten())
  39. flatten = model_internal(reshape)
  40. flatten_.append(flatten)
  41. # 以上for循环体内的代码要缩进
  42. '''Fully Connect Layer & Dropout Layer'''
  43. fully_connect = Dense(vanila_dimension, activation='tanh', name='fully_connect')(concatenate(flatten_, axis=-1))
  44. dropout = Dropout(dropout_rate, name='dropout')(fully_connect)
  45. print("Builded Fully Connect Layer & Dropout Layer...")
  46. '''Projection Layer & Output Layer'''
  47. pj = Dense(projection_dimension, activation='tanh', name='output') # output layer
  48. projection = pj(dropout)
  49. print("Builded Projection Layer & Output Layer...")
  50. # Compile Model: set inputs & outputs ...
  51. model = Model(inputs=doc_input, outputs=projection)
  52. model.compile(optimizer='rmsprop', loss='mse')

参考资料

1、keras0.3.3文档关于model的部分:https://github.com/cartopy/keras-0.3.3/blob/master/docs/templates/models.md

(keras0.3.3的文档很难找,一开始找到的总是2.x的文档,后来baidu+google+bing找到了github上的这篇,目前还不会用github,据说很好找东西,用git??,值得研究)

介绍了keras0.x中用的两种模型,

2、keras2.x文档很好找,重点关注Models和各种Layers

用于NLP的CNN架构搬运:from keras0.x to keras2.x的更多相关文章

  1. 【深度学习篇】--神经网络中的池化层和CNN架构模型

    一.前述 本文讲述池化层和经典神经网络中的架构模型. 二.池化Pooling 1.目标 降采样subsample,shrink(浓缩),减少计算负荷,减少内存使用,参数数量减少(也可防止过拟合)减少输 ...

  2. JWPL工具处理维基百科wikipedia数据用于NLP

    JWPL处理维基百科数据用于NLP 处理zhwiki JWPL是一个Wikipedia处理工具,主要功能是将Wikipedia dump的文件经过处理.优化导入mysql数据库,用于NLP过程.以下以 ...

  3. CNN结构:用于检测的CNN结构进化-分离式方法

    前言: 原文链接:基于CNN的目标检测发展过程       文章有大量修改,如有不适,请移步原文. 参考文章:图像的全局特征--用于目标检测 目标的检测和定位中一个很困难的问题是,如何从数以万计的候选 ...

  4. 深度学习与CV教程(10) | 轻量化CNN架构 (SqueezeNet,ShuffleNet,MobileNet等)

    作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/37 本文地址:http://www.showmeai.tech/article-det ...

  5. CNN结构:用于检测的CNN结构进化-结合式方法

    原文链接:何恺明团队提出 Focal Loss,目标检测精度高达39.1AP,打破现有记录     呀 加入Facebook的何凯明继续优化检测CNN网络,arXiv 上发现了何恺明所在 FAIR 团 ...

  6. NLP用CNN分类Mnist,提取出来的特征训练SVM及Keras的使用(demo)

    用CNN分类Mnist http://www.bubuko.com/infodetail-777299.html /DeepLearning Tutorials/keras_usage 提取出来的特征 ...

  7. 第九节课-CNN架构

    2017-08-21 这次的课程比较偏向实际的运用,介绍了当前几种比较主流的CNN网络: 主要是AlexNet,GoogleNet, VGG, ResNet 需要把课后习题做了才能更好的理解.

  8. CNN结构:用于检测的CNN结构进化-一站式方法

    有兴趣查看原文:YOLO详解 人眼能够快速的检测和识别视野内的物体,基于Maar的视觉理论,视觉先识别出局部显著性的区块比如边缘和角点,然后综合这些信息完成整体描述,人眼逆向工程最相像的是DPM模型. ...

  9. 转载 - CNN感受野(receptive-fields)RF

    本文翻译自A guide to receptive field arithmetic for Convolutional Neural Networks(可能需要FQ才能访问),方便自己学习和参考.若 ...

随机推荐

  1. HTML基础了解

     对HTML最基本的认识和编写:"我的第一个网页" HTML是什么: 它的全称是Hyper Text Markup Language超文本标记语言,页面中包括有视频.图片.链接等其 ...

  2. [leetcode-565-Array Nesting]

    A zero-indexed array A consisting of N different integers is given. The array contains all integers ...

  3. 遇到android.os等系统sdk包没有自动导入的情况

    采取手动导入,build path,然后add external jar,找到sdk的安装目录,导入android 的jar包即可

  4. tomcat7以上,ajax post参数后台获取不到的问题

    AJAX post传参后台获取不到查询参数. 网上找了各种方法,包括设置content-type,又是把json转成json格式字符串,问题依然存在,但是把post改成get又可以获取到,百思不得其解 ...

  5. Linux内核的基本概念

    Linux内核学习,推荐的书籍: <linux设备驱动开发详解第二版>.<Linux内核设计与实现第三版>.<嵌入式Linux应用开发完全手册> 第一篇:讲解Lin ...

  6. [图形学] Chp9 三维几何变换--栈处理函数与矩阵管理函数的区别

    矩阵管理函数:glLoadIdentity()是把当前活动矩阵设置为单位矩阵. 栈处理函数:glPushMatrix()是将当前活动的变换矩阵复制一份,压入栈顶:glPopMatrix()是破坏当前活 ...

  7. window.onload的使用心得

    如果我问你window.onload是什么意思,恐怕你会回答我:"这不是页面加载完就执行吗".  但是答案是不一定,得看你怎么用.看一下例子吧 例1:  代码如下:   <! ...

  8. jquery.validata.js 插件2

    结合上面的,今天写一下validate的使用方法. validate()验证所选的form. validate 方法返回一个 Validator 对象.Validator 对象有很多方法可以用来引发校 ...

  9. usaco training 4.1.1 麦香牛块 题解

    Beef McNuggets题解 Hubert Chen Farmer Brown's cows are up in arms, having heard that McDonalds is cons ...

  10. Sublime Text 关闭自动更新的办法

    解决关于Submit Text每次打开 都会跳出更新选项的问题 Windows 下: 打开 Preferences —— Settings—User { "color_scheme" ...