提交 5ec489cc 编写于 作者: W wangxuguang

revised but unfinished

上级 01e08be9
...@@ -6,7 +6,5 @@ logs/ ...@@ -6,7 +6,5 @@ logs/
model_output model_output
dataprovider_copy_1.py dataprovider_copy_1.py
model.list model.list
test.log
train.log
*.pyc *.pyc
.DS_Store .DS_Store
# 情感分析 # 情感分析
## 背景介绍 ## 背景介绍
<br/>&emsp;&emsp;在自然语言处理中,情感分析一般是指判断一段文本所表达的情绪状态。其中,一段文本可以是一个句子,一个段落或一个文档。情绪状态可以是两类,如(正面,负面),(高兴,悲伤),也可以是三类,如(积极,消极,中性)等等。 在自然语言处理中,情感分析一般是指判断一段文本所表达的情绪状态。其中,一段文本可以是一个句子,一个段落或一个文档。情绪状态可以是两类,如(正面,负面),(高兴,悲伤);也可以是三类,如(积极,消极,中性)等等。
<br/>&emsp;&emsp;情感分析的应用场景十分广泛,如把用户在购物网站(亚马逊、天猫、淘宝等)、旅游网站、电影评论网站上发表的评论分成正面评论和负面评论。为了分析用户对于某一产品的整体使用感受,抓取产品的用户评论并进行情感分析等等。
<br/>&emsp;&emsp;对电影评论进行情感分析(正面,负面)的例子如下面的表格1所示。 情感分析的应用场景十分广泛,如把用户在购物网站(亚马逊、天猫、淘宝等)、旅游网站、电影评论网站上发表的评论分成正面评论和负面评论。为了分析用户对于某一产品的整体使用感受,抓取产品的用户评论并进行情感分析等等。表格1展示了对电影评论进行情感分析的例子:
| 电影评论 | 类别 | | 电影评论 | 类别 |
| -------- | ----- | | -------- | ----- |
| 在冯小刚这几年的电影里,算最好的一部的了| 正面 | | 在冯小刚这几年的电影里,算最好的一部的了| 正面 |
| 很不好看,好像一个地方台的电视剧 | 负面 | | 很不好看,好像一个地方台的电视剧 | 负面 |
| 为了讽刺官场刻意丑化农村人的傻片子,圆方镜头全程炫技,色调背景美则美矣,但剧情拖沓,口音不伦不类,一直努力却始终无法入戏。不建议进电影院观看,不然睡着了躺都没地方躺。| 负面| | 圆方镜头全程炫技,色调背景美则美矣,但剧情拖沓,口音不伦不类,一直努力却始终无法入戏| 负面|
|剧情四星。但是圆镜视角加上婺源的风景整个非常有中国写意山水画的感觉,看得实在太舒服了。。难怪作为今年TIFF special presentation的开幕电影。范爷美爆,再往上加一星。|正面| |剧情四星。但是圆镜视角加上婺源的风景整个非常有中国写意山水画的感觉,看得实在太舒服了。。|正面|
<p align="center">表格 1 电影评论情感分析</p> <p align="center">表格 1 电影评论情感分析</p>
<br/>&emsp;&emsp;实际上,在自然语言处理中,情感分析属于典型的**文本分类**问题,即,把需要进行情感分析的文本划分为其所属类别。文本分类问题可以分解为两个子问题:文本表示和分类。在深度学习的方法出现之前,主流的文本表示方法为BOW(bag of words),分类方法有SVM,LR,Boosting等等。BOW忽略了词的顺序信息,而且是高维度的稀疏向量表示,这种表示浮于表面,并未充分表示文本的语义信息。例如,句子`这部电影糟糕透了``一个乏味,空洞,没有内涵的作品`在情感分析中具有很高的语义相似度,但是它们的BOW表示的相似度为0。又如,句子`一个空洞,没有内涵的作品``一个不空洞而且有内涵的作品`的BOW相似度很高,但实际上它们的意思很不一样。本章我们所要介绍的深度学习模型克服了BOW表示的上述缺陷,它在考虑词的顺序的基础上把文本映射到低维度的语义空间,并且以端对端(end to end)的方式进行文本表示及分类,其性能相对于传统方法有显著的提升。
在自然语言处理中,情感分析属于典型的**文本分类**问题,即,把需要进行情感分析的文本划分为其所属类别。文本分类涉及文本表示和分类方法。在深度学习的方法出现之前,主流的文本表示方法为BOW(bag of words),话题模型等等;分类方法有SVM(support vector machine), LR(logistic regression), [Boosting](https://en.wikipedia.org/wiki/Boosting_(machine_learning))等等。BOW忽略了词的顺序信息,而且是高维度的稀疏向量表示,它并不能充分表示文本的语义信息。例如,句子“这部电影糟糕透了”和“一个乏味,空洞,没有内涵的作品”在情感分析中具有很高的语义相似度,但是它们的BOW表示的相似度为0。又如,句子“一个空洞,没有内涵的作品”和“一个不空洞而且有内涵的作品”的BOW相似度很高,但实际上它们的意思很不一样。本章我们所要介绍的深度学习模型克服了BOW表示的上述缺陷,它在考虑词的顺序的基础上把文本映射到低维度的语义空间,并且以端对端(end to end)的方式进行文本表示及分类,其性能相对于传统方法有显著的提升。
## 模型概览 ## 模型概览
<br/>&emsp;&emsp;本章所使用的文本表示模型为卷积神经网络(Convolutional Neural Networks)和循环神经网络(Recurrent Neural Networks)及其扩展。我们首先介绍处理文本的卷积神经网络。 本章所使用的文本表示模型为卷积神经网络(Convolutional Neural Networks)和循环神经网络(Recurrent Neural Networks)及其拓展。我们首先介绍处理文本的卷积神经网络。
### 文本卷积神经网络 ### 文本卷积神经网络(CNN)
<br/>&emsp;&emsp;卷积神经网络经常用来处理具有类似网格拓扑结构(grid-like topology)的数据。例如,图像可以视为2D网格的像素点,自然语言可以视为1D的词序列。卷积神经网络可以提取多种局部特征,并对其进行组合抽象得到更高级的特征表示,且其对于数据的某些变化具有不变性。大量实验表明,卷积神经网络能高效的对图像及文本问题进行建模处理。本小结我们讲解如何使用卷积神经网络处理文本(以句子为例) 卷积神经网络经常用来处理具有类似网格拓扑结构(grid-like topology)的数据。例如,图像可以视为2D网格的像素点,自然语言可以视为1D的词序列。卷积神经网络可以提取多种局部特征,并对其进行组合抽象得到更高级的特征表示,且其对于数据的某些变化具有不变性。大量实验表明,卷积神经网络能高效的对图像及文本问题进行建模处理。本小结我们讲解如何使用卷积神经网络处理文本(以句子为例)\[[1](#参考文献)\]
<p align="center"> <p align="center">
<img src="image/text_cnn.png"><br/> <img src="image/text_cnn.png" width = "90%" height = "90%" align="center"/><br/>
图 1 卷积神经网络文本分类模型 图 1 卷积神经网络文本分类模型
</p> </p>
<br/>&emsp;&emsp;假设一个句子的长度为$n$,其中第$i$个词的word embedding为$x_i\in\mathbb{R}^k$,其维度大小为$k$,我们可以将整个句子表示为$x_{1:n}=x_1\oplus x_2\oplus \ldots \oplus x_n$,其中,$\oplus$表示拼接(concatenation)操作。一般地,我们用$x_{i:i+j}$表示词序列$x_{i},x_{i+1},\ldots,x_{i+j}$的拼接。卷积操作把filter(也称为kernel)$w\in\mathbb{R}^{hk}$应用于包含$h$个词的窗口$x_{i:i+h-1}$,得到特征$c_i$: 假设一个句子的长度为$n$,其中第$i$个词的词向量(word embedding)为$x_i\in\mathbb{R}^k$,维度大小为$k$。我们可以将整个句子表示为$x_{1:n}=x_1\oplus x_2\oplus \ldots \oplus x_n$,其中,$\oplus$表示拼接(concatenation)操作。一般地,我们用$x_{i:i+j}$表示词序列$x_{i},x_{i+1},\ldots,x_{i+j}$的拼接。卷积操作把卷积核(kernel)$w\in\mathbb{R}^{hk}$应用于包含$h$个词的窗口$x_{i:i+h-1}$,得到这$h$个词的特征值$c_i$:
$$c_i=f(w\cdot x_{i:i+h-1}+b)$$ $$c_i=f(w\cdot x_{i:i+h-1}+b)$$
<br/>&emsp;&emsp;其中$b\in\mathbb{R}$为偏置项(bias),$f$为非线性激活函数,如sigmoid。将filter应用于句子中所有的词窗口${x_{1:h},x_{2:h+1},\ldots,x_{n-h+1:n}}$序列,产生一个feature map 其中$b\in\mathbb{R}$为偏置项(bias),$f$为非线性激活函数,如$sigmoid$。将卷积核应用于句子中所有的词窗口${x_{1:h},x_{2:h+1},\ldots,x_{n-h+1:n}}$,产生一个特征图(feature map)
$$c=[c_1,c_2,\ldots,c_{n-h+1}]$$ $$c=[c_1,c_2,\ldots,c_{n-h+1}], c \in \mathbb{R}^{n-h+1}$$
<br/>&emsp;&emsp;其中$c \in \mathbb{R}^{n-h+1}$。接下来我们对feature map采用max pooling over time操作得到此filter对应的整句话的特征 接下来我们对特征图采用时间维度上的最大池化(max pooling over time)操作得到此卷积核对应的整句话的特征$\hat c$,它是特征图中所有元素的最大值
$$\hat c=max(c)$$ $$\hat c=max(c)$$
<br/>&emsp;&emsp;即,$\hat c$是feature map中所有元素的最大值。pooling机制自动处理了句子长度不一的问题。在实际应用中,我们会使用多个filter来处理句子,窗口大小相同的filters堆叠起来形成一个矩阵(上文中的单个filter参数$w$相当于矩阵的某一行),这样可以更高效的完成运算。另外,我们也可使用窗口大小不同的filters来处理句子,最后,将所有filters得到的特征拼接起来即为文本的定长向量表示。对于文本分类问题,将其连接至softmax即构建出完整的模型。 在实际应用中,我们会使用多个卷积核来处理句子,窗口大小相同的卷积核堆叠起来形成一个矩阵(上文中的单个卷积核参数$w$相当于矩阵的某一行),这样可以更高效的完成运算。另外,我们也可使用窗口大小不同的卷积核来处理句子,最后,将所有卷积核得到的特征拼接起来即为文本的定长向量表示。对于文本分类问题,将其连接至softmax即构建出完整的模型。图1是使用卷积神经网络进行文本分类的一个示意图(只画了四个卷积核,黄色的卷积核窗口大小为3,红色的为2)。
<br/>&emsp;&emsp;对于一般的短文本分类问题,上文所述的简单的文本卷积网络即可达到很高的正确率\[[1](#参考文献)\]。若想得到更抽象更高级的文本特征表示,可以参考N. Kalchbrenner, et al.(2014)\[[2](#参考文献)\]或 Yann N. Dauphin, et al.(2016)\[[3](#参考文献)\]的构建深层文本卷积神经网络的方法。
### 循环神经网络 对于一般的短文本分类问题,上文所述的简单的文本卷积网络即可达到很高的正确率\[[1](#参考文献)\]。若想得到更抽象更高级的文本特征表示,可以构建深层文本卷积神经网络\[[2](#参考文献),[3](#参考文献)\]
#### 简单的循环神经网络 ### 循环神经网络(RNN)
<br/>&emsp;&emsp;循环神经网络(rnn)是一种能对序列数据进行精确建模的有力工具。实际上,循环神经网络的理论计算能力是图灵完备的\[[4](#参考文献)\] 循环神经网络是一种能对序列数据进行精确建模的有力工具。实际上,循环神经网络的理论计算能力是图灵完备的\[[4](#参考文献)\]。自然语言是一种典型的序列数据(词序列),近年来,循环神经网络及其变体(如long short term memory\[[5](#参考文献)\]等)在自然语言处理的多个领域取得了丰硕的成果,如在语言模型、句法解析、语义角色标注(或一般的序列标注)、语义表示、图文生成、对话、机器翻译等任务上均表现优异甚至成为目前效果最好的方法。
<br/>&emsp;&emsp;自然语言是一种典型的序列数据(词序列),近年来,循环神经网络及其变体(如long short term memory\[[5](#参考文献)\]等)在自然语言处理的多个领域取得了丰硕的成果,如在语言模型,句法解析,语义角色标注(或一般的序列标注),语义表示,图文生成,对话,机器翻译等任务上均表现优异甚至成为目前效果最好的方法。
<p align="center"> <p align="center">
<img src="image/rnn.png"><br/> <img src="image/rnn.png" width = "70%" height = "70%" align="center"/><br/>
图 2 循环神经网络按时间展开的示意图 图 2 循环神经网络按时间展开的示意图
</p> </p>
<br/>&emsp;&emsp;循环神经网络按时间展开后如图2所示:在第$t$时刻,网络读入第$t$个输入$x_t$(向量表示)及前一时刻隐藏层的输出$h_{t-1}$(向量表示,$h_0$一般初始化为$0$向量),计算得出本时刻隐藏层的值$h_t$,重复这一步骤直至读完所有输入。如果将循环神经网络所表示的函数记为$f$,则其公式可表示为: 循环神经网络按时间展开后如图2所示:在第$t$时刻,网络读入第$t$个输入$x_t$(向量表示)及前一时刻隐层的状态$h_{t-1}$(向量表示,$h_0$一般初始化为$0$向量),计算得出本时刻隐藏层的值$h_t$,重复这一步骤直至读完所有输入。如果将循环神经网络所表示的函数记为$f$,则其公式可表示为:
$$h_t=f(x_t,h_{t-1})=\sigma(W_{xh}x_t+W_{hh}h_{h-1}+b_h)$$ $$h_t=f(x_t,h_{t-1})=\sigma(W_{xh}x_t+W_{hh}h_{h-1}+b_h)$$
<br/>&emsp;&emsp;其中$W_{xh}$是输入到隐层的矩阵参数,$W_{hh}$是隐层到隐层的矩阵参数,$b_h$为隐层的偏置向量(bias)参数,$\sigma$为elementwise的sigmoid函数。在处理自然语言时,一般会先将词(one-hot表示)映射为其embedding表示,然后再作为循环神经网络每一时刻的输入$x_t$。可以根据实际需要的不同在循环神经网络的隐层上连接其它层。如,可以把一个循环神经网络的隐层输出连接至下一个循环神经网络的输入构建深层(deep or stacked)循环神经网络,或者提取最后一个时刻的隐层状态作为句子表示进而使用分类模型等等。 其中$W_{xh}$是输入到隐层的矩阵参数,$W_{hh}$是隐层到隐层的矩阵参数,$b_h$为隐层的偏置向量(bias)参数,$\sigma$为逐元素(elementwise)的$sigmoid$函数。
<br/>&emsp;&emsp;可以看出,隐状态的输入来源于当前输入和前一时刻隐状态的值,这会导致很久以前的输入容易被覆盖掉。实际上,人们发现当序列很长时,循环神经网络就会表现很差(远距离依赖问题),训练过程中会出现梯度消失或爆炸现象\[[6](#参考文献)\]。为了解决这一问题,Hochreiter S, Schmidhuber J. (1997)\[[5](#参考文献)\]提出了lstm(long short term memory)模型。
#### lstm-rnn 在处理自然语言时,一般会先将词(one-hot表示)映射为其词向量(word embedding)表示,然后再作为循环神经网络每一时刻的输入$x_t$。此外,可以根据实际需要的不同在循环神经网络的隐层上连接其它层。如,可以把一个循环神经网络的隐层输出连接至下一个循环神经网络的输入构建深层(deep or stacked)循环神经网络,或者提取最后一个时刻的隐层状态作为句子表示进而使用分类模型等等。
<br/>&emsp;&emsp;相比于简单的循环神经网络,lstm增加了记忆单元$c$,输入门$i$,遗忘门$f$及输出门$o$,这些门及记忆单元组合起来大大提升了循环神经网络处理远距离依赖问题的能力,若将基于lstm的循环神经网络表示的函数记为F,则其公式为:
### 长时短期记忆(LSTM)
循环神经网络隐状态的输入来源于当前输入和前一时刻隐状态的值,这会导致很久以前的输入容易被覆盖掉。实际上,人们发现当序列很长时,循环神经网络就会表现很差(远距离依赖问题),训练过程中会出现梯度消失或爆炸现象\[[6](#参考文献)\]。为了解决这一问题,Hochreiter S, Schmidhuber J. (1997)\[[5](#参考文献)\]提出了lstm(long short term memory)。
相比于简单的循环神经网络,lstm增加了记忆单元$c$、输入门$i$、遗忘门$f$及输出门$o$,这些门及记忆单元组合起来大大提升了循环神经网络处理远距离依赖问题的能力,若将基于lstm的循环神经网络表示的函数记为$F$,则其公式为:
$$ h_t=F(x_t,h_{t-1})$$ $$ h_t=F(x_t,h_{t-1})$$
<br/>&emsp;&emsp;$F$由下列公式组合而成\[[7](#参考文献)\] $F$由下列公式组合而成\[[7](#参考文献)\]
\begin{align} \begin{align}
i_t & = \sigma(W_{xi}x_t+W_{hi}h_{h-1}+W_{ci}c_{t-1}+b_i)\\\\ i_t & = \sigma(W_{xi}x_t+W_{hi}h_{h-1}+W_{ci}c_{t-1}+b_i)\\\\
f_t & = \sigma(W_{xf}x_t+W_{hf}h_{h-1}+W_{cf}c_{t-1}+b_f)\\\\ f_t & = \sigma(W_{xf}x_t+W_{hf}h_{h-1}+W_{cf}c_{t-1}+b_f)\\\\
...@@ -62,30 +66,30 @@ c_t & = f_t\odot c_{t-1}+i_t\odot tanh(W_{xc}x_t+W_{hc}h_{h-1}+b_c)\\\\ ...@@ -62,30 +66,30 @@ c_t & = f_t\odot c_{t-1}+i_t\odot tanh(W_{xc}x_t+W_{hc}h_{h-1}+b_c)\\\\
o_t & = \sigma(W_{xo}x_t+W_{ho}h_{h-1}+W_{co}c_{t}+b_o)\\\\ o_t & = \sigma(W_{xo}x_t+W_{ho}h_{h-1}+W_{co}c_{t}+b_o)\\\\
h_t & = o_t\odot tanh(c_t)\\\\ h_t & = o_t\odot tanh(c_t)\\\\
\end{align} \end{align}
<br/>&emsp;&emsp;其中,$i_t, f_t, c_t, o_t$分别表示输入门,遗忘门,记忆单元(记忆单元一般对外不可见,$h_t$对外部可见)及输出门的向量值,带角标的$W$及$b$为模型参数,$tanh$为elementwise的双曲正切函数,$\odot$表示elementwise的乘法操作。输入门控制着新输入进入记忆单元$c$的强度,遗忘门控制着记忆单元维持上一时刻值的强度,输出门控制着输出记忆单元的强度。三种门的计算方式类似,但有着完全不同的参数,这三种门各自以不同的方式控制着记忆单元$c$,如图3所示: 其中,$i_t, f_t, c_t, o_t$分别表示输入门,遗忘门,记忆单元及输出门的向量值,带角标的$W$及$b$为模型参数,$tanh$为逐元素的双曲正切函数,$\odot$表示逐元素的乘法操作。输入门控制着新输入进入记忆单元$c$的强度,遗忘门控制着记忆单元维持上一时刻值的强度,输出门控制着输出记忆单元的强度。三种门的计算方式类似,但有着完全不同的参数,即各自以不同的方式控制着记忆单元$c$,如图3所示:
<p align="center"> <p align="center">
<img src="image/lstm.png"><br/> <img src="image/lstm.png" width = "70%" height = "70%" align="center"/><br/>
图 3 时刻$t$的lstm\[[7](#参考文献)\] 图 3 时刻$t$的lstm
</p> </p>
<br/>&emsp;&emsp;实际上,lstm的思想正是通过给简单的循环神经网络增加记忆及控制门的方式增强了其处理远距离依赖问题的能力。类似原理的对于简单循环神经网络的改进还有Gated Recurrent Unit (GRU)\[[8](#参考文献)\],其设计更为简洁一些。**这些改进虽然各有不同,但是对他们的宏观描述却与简单的循环神经网络一样,如图2所示,隐状态依据当前输入及前一时刻的隐状态来改变,不断的循环这一过程直至输入处理完毕:** lstm通过给简单的循环神经网络增加记忆及控制门的方式,增强了其处理远距离依赖问题的能力。类似原理的对于简单循环神经网络的改进还有Gated Recurrent Unit (GRU)\[[8](#参考文献)\],其设计更为简洁一些。**这些改进虽然各有不同,但是它们的宏观描述却与简单的循环神经网络一样(如图2所示),即隐状态依据当前输入及前一时刻的隐状态来改变,不断的循环这一过程直至输入处理完毕:**
$$ h_t=Recrurent(x_t,h_{t-1})$$ $$ h_t=Recrurent(x_t,h_{t-1})$$
<br/>&emsp;&emsp;对于正常顺序的循环神经网络而言,$h_t$包含了$t$时刻之前的输入信息,也就是上文信息。同样,为了得到下文信息,我们可以使用反方向(将输入逆序处理)的循环神经网络。结合构建深层循环神经网络的方法,我们可以构建更加强有力的深层双向循环神经网络(deep bi-directional recurrent neural networks)对时序数据进行建模。 对于正常顺序的循环神经网络,$h_t$包含了$t$时刻之前的输入信息,也就是上文信息。同样,为了得到下文信息,我们可以使用反方向(将输入逆序处理)的循环神经网络。结合构建深层循环神经网络的方法,我们可以通过构建更加强有力的栈式双向循环神经网络,来对时序数据进行建模。
#### 使用循环神经网络的组合进行文本分类 #### 栈式双向LSTM(Stacked Bidirectional LSTM)
<br/>&emsp;&emsp;一个简单的做法是分别使用正向lstm-rnn和反向lstm-rnn处理文本,取最后一个时刻的隐层值拼接起来做为文本的定长向量表示,将其连接至softmax得到文本分类模型。但是这样的文本分类模型是一个浅层模型。考虑到深层神经网络往往能得到更抽象和高级的特征表示,我们构建stacked lstm-rnn\[[9](#参考文献)\]。如图4所示(以三层为例),奇数层lstm正向,偶数层lstm反向,高一层的lstm使用低一层lstm及之前所有层的信息作为输入,对最高层lstm序列使用max pooling over time得到文本定长向量表示。**这一表示充分融合了文本的上下文信息,并且对文本进行了深层次抽象。**最后我们将文本表示连接至softmax构建分类模型。 考虑到深层神经网络往往能得到更抽象和高级的特征表示,我们构建基于lstm的栈式双向循环神经网络\[[9](#参考文献)\]。如图4所示(以三层为例),奇数层lstm正向,偶数层lstm反向,高一层的lstm使用低一层lstm及之前所有层的信息作为输入,对最高层lstm序列使用时间维度上的最大池化即可得到文本的定长向量表示。**这一表示充分融合了文本的上下文信息,并且对文本进行了深层次抽象。**最后我们将文本表示连接至softmax构建分类模型。
<p align="center"> <p align="center">
<img src="image/stacked_lstm.jpg"><br/> <img src="image/stacked_lstm.jpg"><br/>
图 4 stacked lstm-rnn for text classification 图 4 栈式双向LSTM用于文本分类
</p> </p>
## 数据准备 ## 数据准备
### 数据介绍与下载 ### 数据介绍与下载
我们以IMDB情感分析数据集为例进行介绍。训练模型之前, 我们需要预处理数椐并构建一个字典。 首先, 你可以使用下面的脚本下载 IMDB 数椐集和[Moses](http://www.statmt.org/moses/)工具, 我们提供了一个数据预处理脚本,它不仅能够处理IMDB数据,还能处理其他用户自定义的数据。 为了使用提前编写的脚本,需要将标记的训练和测试样本移动到另一个路径,这已经在`get_imdb.sh`中完成。 我们以IMDB情感分析数据集为例进行介绍。IMDB数据集的训练集和测试集分别包含25000个已标注过的电影评论。其中,负面评论的得分小于等于4,正面评论的得分大于等于7,满分10分。您可以使用下面的脚本下载 IMDB 数椐集和[Moses](http://www.statmt.org/moses/)工具:
``` ```
./get_imdb.sh ./get_imdb.sh
``` ```
如果数椐获取成功,将在目录```data```中看到下面的文件: 如果数椐获取成功,将在目录```data```中看到下面的文件:
``` ```
aclImdb get_imdb.sh imdb mosesdecoder-master aclImdb get_imdb.sh imdb mosesdecoder-master
...@@ -95,14 +99,8 @@ aclImdb get_imdb.sh imdb mosesdecoder-master ...@@ -95,14 +99,8 @@ aclImdb get_imdb.sh imdb mosesdecoder-master
* imdb: 仅包含训练和测试数椐集。 * imdb: 仅包含训练和测试数椐集。
* mosesdecoder-master: Moses 工具。 * mosesdecoder-master: Moses 工具。
IMDB数据集包含25,000个已标注过的电影评论用于训练,25,000个用于测试。负面的评论的得分小于等于4,正面的评论的得分大于等于7,满分10分。
### 数据预处理 ### 数据预处理
在这个例子中,我们只使用已经标注过的训练集和测试集,且默认在训练集上构建字典。训练集已经做了随机打乱排序。 Moses 工具中的脚本`tokenizer.perl` 用于切分单单词和标点符号。执行下面的命令就可以预处理数椐。 我们只使用已标注的训练集和测试集。将训练集随机打乱排序,默认在训练集上构建字典。Moses 工具中的脚本`tokenizer.perl` 用于切分单词和标点符号。执行下面的命令就可以预处理数椐:
```
./preprocess.sh
```
preprocess.sh:
``` ```
data_dir="./data/imdb" data_dir="./data/imdb"
...@@ -112,31 +110,32 @@ python preprocess.py -i data_dir ...@@ -112,31 +110,32 @@ python preprocess.py -i data_dir
* data_dir: 输入数椐所在目录。 * data_dir: 输入数椐所在目录。
* preprocess.py: 预处理脚本。 * preprocess.py: 预处理脚本。
运行成功后目录`data/pre-imdb` 结构如下: 运行成功后目录`./data/pre-imdb` 结构如下:
``` ```
dict.txt labels.list test.list test_part_000 train.list train_part_000 dict.txt labels.list test.list test_part_000 train.list train_part_000
``` ```
* test\_part\_000 and train\_part\_000: 所有标记的测试集和训练集, 训练集已经随机打乱。 * test\_part\_000 train\_part\_000: 所有标记的测试集和训练集, 训练集已经随机打乱。
* train.list and test.list: 训练集和测试集文件列表。 * train.list test.list: 训练集和测试集文件列表。
* dict.txt: 利用训练集生成的字典。 * dict.txt: 利用训练集生成的字典。
* labels.txt: neg 0, pos 1, 含义:标签0表示负面的评论,标签1表示正面的评论。 * labels.list: 类别标签列表,标签0表示负面评论,标签1表示正面评论。
### 提供数据给PaddlePaddle ### 提供数据给PaddlePaddle
PaddlePaddle可以读取Python写的传输数据脚本,下面dataprovider.py文件给出了完整例子,主要包括两部分: PaddlePaddle可以读取Python写的传输数据脚本,下面`dataprovider.py`文件给出了完整例子,主要包括两部分:
* hook: 定义文本信息、类别Id的数据类型。文本被定义为整数序列`integer_value_sequence`,类别被定义为整数`integer_value` * hook函数: 定义文本信息、类别Id的数据类型。文本被定义为整数序列`integer_value_sequence`,类别被定义为整数`integer_value`
* process: yield文本信息和类别Id,和hook里定义顺序一致。process读取的文件的行为类别和评论文本,以`'\t\t'`分隔。 * process函数: 使用yield关键字返回文本信息和类别Id,和hook里定义顺序一致。process读取的文件的行为类别和评论文本,以`'\t\t'`分隔。
```python ```python
from paddle.trainer.PyDataProvider2 import * from paddle.trainer.PyDataProvider2 import *
def hook(settings, dictionary, **kwargs): def hook(settings, dictionary, **kwargs):
settings.word_dict = dictionary settings.word_dict = dictionary
settings.input_types = [ settings.input_types = {
integer_value_sequence(len(settings.word_dict)), integer_value(2) 'word': integer_value_sequence(len(settings.word_dict)),
] 'label': integer_value(2)
}
settings.logger.info('dict len : %d' % (len(settings.word_dict))) settings.logger.info('dict len : %d' % (len(settings.word_dict)))
...@@ -150,232 +149,157 @@ def process(settings, file_name): ...@@ -150,232 +149,157 @@ def process(settings, file_name):
word_slot = [ word_slot = [
settings.word_dict[w] for w in words if w in settings.word_dict settings.word_dict[w] for w in words if w in settings.word_dict
] ]
yield word_slot, label yield {
'word': word_slot,
'label': label
}
``` ```
## 模型配置说明 ## 模型配置说明
`trainer_config.py` 是一个配置文件的例子。第一行从`sentiment_net.py`中导出预定义的网络。 `trainer_config.py` 是一个配置文件的例子。
### 数据定义
trainer_config.py:
```python ```python
from sentiment_net import * define_py_data_sources2(
train_list,
data_dir = "./data/pre-imdb" test_list,
# whether this config is used for test module="dataprovider",
is_test = get_config_arg('is_test', bool, False) obj="process",
# whether this config is used for prediction args={'dictionary': word_dict})
is_predict = get_config_arg('is_predict', bool, False) ```
dict_dim, class_dim = sentiment_data(data_dir, is_test, is_predict)
################## Algorithm Config ##################### 在模型配置中利用define_py_data_sources2加载数据:
settings( * train.list、test.list: 指定训练、测试数据。
batch_size=128, * module="dataprovider": 数据处理Python文件名。
learning_rate=2e-3, * obj="process": 指定生成数据的函数。
learning_method=AdamOptimizer(), * args={"dictionary": word_dict}: 额外的参数,这里指定词典。
regularization=L2Regularization(8e-4),
gradient_clipping_threshold=25
)
#################### Network Config ######################
stacked_lstm_net(dict_dim, class_dim=class_dim,
stacked_num=3, is_predict=is_predict)
# bidirectional_lstm_net(dict_dim, class_dim=class_dim, is_predict=is_predict)
# convolution_net(dict_dim, class_dim=class_dim, is_predict=is_predict)
```
get\_config\_arg(): 获取通过 `--config_args=xx` 设置的命令行参数。
### 优化算法配置 ### 优化算法配置
* 使用随机梯度下降(sgd)算法。
* 使用 adam 优化。
* 设置batch size大小为128。
* 设置全局学习率。
* 设置L2正则。
* 设置梯度裁剪(clipping)阈值。
### 数据定义
数据定义在方法sentiment_data之中,其实现在文件`sentiment_net.py`中:
```python ```python
def sentiment_data(data_dir=None, settings(
is_test=False, batch_size=128,
is_predict=False, learning_rate=2e-3,
train_list="train.list", learning_method=AdamOptimizer(),
test_list="test.list", regularization=L2Regularization(8e-4),
dict_file="dict.txt"): gradient_clipping_threshold=25)
"""
Predefined data provider for sentiment analysis.
is_test: whether this config is used for test.
is_predict: whether this config is used for prediction.
train_list: text file name, containing a list of training set.
test_list: text file name, containing a list of testing set.
dict_file: text file name, containing dictionary.
"""
dict_dim = len(open(join_path(data_dir, "dict.txt")).readlines())
class_dim = len(open(join_path(data_dir, 'labels.list')).readlines())
if is_predict:
return dict_dim, class_dim
if data_dir is not None:
train_list = join_path(data_dir, train_list)
test_list = join_path(data_dir, test_list)
dict_file = join_path(data_dir, dict_file)
train_list = train_list if not is_test else None
word_dict = dict()
with open(dict_file, 'r') as f:
for i, line in enumerate(open(dict_file, 'r')):
word_dict[line.split('\t')[0]] = i
define_py_data_sources2(
train_list,
test_list,
module="dataprovider",
obj="process",
args={'dictionary': word_dict})
return dict_dim, class_dim
``` ```
在模型配置中利用define_py_data_sources2加载数据: * 设置batch size大小为128。
* 设置全局学习率。
* train.list,test.list: 指定训练、测试数据 * 使用 adam 优化。
* module="dataprovider": 数据处理Python文件名 * 设置L2正则。
* obj="process": 指定生成数据的函数 * 设置梯度截断(clipping)阈值。
* args={"dictionary": word_dict}: 额外的参数,这里指定词典
### 模型结构 ### 模型结构
我们用PaddlePaddle分别基于[文本卷积神经网络](#文本卷积神经网络(CNN))[栈式双向LSTM](#栈式双向LSTM(Stacked Bidirectional LSTM))实现文本分类。
* `convolution_net`: 在`sentiment_net.py`中定义。其论述详见`文本卷积神经网络`小结。 #### 文本卷积神经网络的实现
```python ```python
def convolution_net(input_dim, def convolution_net(input_dim,
class_dim=2, class_dim=2,
emb_dim=128, emb_dim=128,
hid_dim=128, hid_dim=128,
is_predict=False): is_predict=False):
data = data_layer("word", input_dim) # one-hot表示的词序列 # 网络输入:id表示的词序列,词典大小为input_dim
emb = embedding_layer(input=data, size=emb_dim) # 将one-hot表示的词序列映射为embedding序列 data = data_layer("word", input_dim)
conv_3 = sequence_conv_pool(input=emb, context_len=3, hidden_size=hid_dim) #窗口大小为3的convolution及max pooling操作 # 将id表示的词序列映射为embedding序列
conv_4 = sequence_conv_pool(input=emb, context_len=4, hidden_size=hid_dim) #窗口大小为4的convolution及max pooling操作 emb = embedding_layer(input=data, size=emb_dim)
output = fc_layer(input=[conv_3,conv_4], size=class_dim, act=SoftmaxActivation()) #将conv_3和conv_4拼接起来输入给softmax分类 # 卷积及最大化池操作,卷积核窗口大小为3
conv_3 = sequence_conv_pool(input=emb, context_len=3, hidden_size=hid_dim)
if not is_predict: # 卷积及最大化池操作,卷积核窗口大小为4
lbl = data_layer("label", 1) #类别标签 conv_4 = sequence_conv_pool(input=emb, context_len=4, hidden_size=hid_dim)
outputs(classification_cost(input=output, label=lbl)) # 将conv_3和conv_4拼接起来输入给softmax分类,类别数为class_dim
else: output = fc_layer(
outputs(output) input=[conv_3, conv_4], size=class_dim, act=SoftmaxActivation())
```
if not is_predict:
其中,我们仅用一个`sequence_conv_pool`方法就实现了convolution和pooling操作,filter的数量为hidden_size参数。`sequence_conv_pool`的实现详见`Paddle/python/paddle/trainer_config_helpers/networks.py`。 lbl = data_layer("label", 1) #网络输入:类别标签
outputs(classification_cost(input=output, label=lbl))
* `bidirectional_lstm_net`: 在`sentiment_net.py`中定义。其论述详见`使用循环神经网络的组合进行文本分类`小结。 else:
```python outputs(output)
def bidirectional_lstm_net(input_dim, ```
class_dim=2,
emb_dim=128, 其中,我们仅用一个[`sequence_conv_pool`](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/trainer_config_helpers/networks.py)方法就实现了卷积和池化操作,卷积核的数量为hidden_size参数。
lstm_dim=128, #### 栈式双向LSTM的实现
is_predict=False):
data = data_layer("word", input_dim) ```python
emb = embedding_layer(input=data, size=emb_dim) def stacked_lstm_net(input_dim,
bi_lstm = bidirectional_lstm(input=emb, size=lstm_dim) # 双向lstm,其默认返回值为正向lstm-rnn和反向lstm-rnn最后一个时刻的隐层值的拼接。其实现详见`Paddle/python/paddle/trainer_config_helpers/networks.py`
dropout = dropout_layer(input=bi_lstm, dropout_rate=0.5)
output = fc_layer(input=dropout, size=class_dim, act=SoftmaxActivation())
if not is_predict:
lbl = data_layer("label", 1)
outputs(classification_cost(input=output, label=lbl))
else:
outputs(output)
```
* `stacked_lstm_net`: 在`sentiment_net.py`中定义,默认情况下使用此网络。其论述详见`使用循环神经网络的组合进行文本分类`小结。
```python
def stacked_lstm_net(input_dim,
class_dim=2, class_dim=2,
emb_dim=128, emb_dim=128,
hid_dim=512, hid_dim=512,
stacked_num=3, stacked_num=3,
is_predict=False): is_predict=False):
"""
A Wrapper for sentiment classification task.
This network uses bi-directional recurrent network,
consisting three LSTM layers. This configure is referred to
the paper as following url, but use fewer layrs.
http://www.aclweb.org/anthology/P15-1109
input_dim: here is word dictionary dimension.
class_dim: number of categories.
emb_dim: dimension of word embedding.
hid_dim: dimension of hidden layer.
stacked_num: number of stacked lstm-hidden layer.
is_predict: is predicting or not.
Some layers is not needed in network when predicting.
"""
hid_lr = 1e-3
assert stacked_num % 2 == 1
layer_attr = ExtraLayerAttribute(drop_rate=0.5)
fc_para_attr = ParameterAttribute(learning_rate=hid_lr)
lstm_para_attr = ParameterAttribute(initial_std=0., learning_rate=1.)
para_attr = [fc_para_attr, lstm_para_attr]
bias_attr = ParameterAttribute(initial_std=0., l2_rate=0.)
relu = ReluActivation()
linear = LinearActivation()
data = data_layer("word", input_dim)
emb = embedding_layer(input=data, size=emb_dim)
fc1 = fc_layer(input=emb, size=hid_dim, act=linear, bias_attr=bias_attr)
lstm1 = lstmemory(
input=fc1, act=relu, bias_attr=bias_attr, layer_attr=layer_attr) #基于lstm的循环神经网络
inputs = [fc1, lstm1]
for i in range(2, stacked_num + 1): #由fc_layer和lstmemory构建双向stacked_lstm_net
fc = fc_layer(
input=inputs,
size=hid_dim,
act=linear,
param_attr=para_attr,
bias_attr=bias_attr)
lstm = lstmemory(
input=fc,
reverse=(i % 2) == 0,
act=relu,
bias_attr=bias_attr,
layer_attr=layer_attr)
inputs = [fc, lstm]
fc_last = pooling_layer(input=inputs[0], pooling_type=MaxPooling()) #对最后一层fc_layer使用max pooling over time得到定长向量
lstm_last = pooling_layer(input=inputs[1], pooling_type=MaxPooling()) #对最后一层lstmemory使用max pooling over time得到定长向量
output = fc_layer(
input=[fc_last, lstm_last],
size=class_dim,
act=SoftmaxActivation(),
bias_attr=bias_attr,
param_attr=para_attr)
if is_predict:
outputs(output)
else:
outputs(classification_cost(input=output, label=data_layer('label', 1)))
```
hid_lr = 1e-3
assert stacked_num % 2 == 1
# 设置神经网络层的属性
layer_attr = ExtraLayerAttribute(drop_rate=0.5)
# 设置参数的属性
fc_para_attr = ParameterAttribute(learning_rate=hid_lr)
lstm_para_attr = ParameterAttribute(initial_std=0., learning_rate=1.)
para_attr = [fc_para_attr, lstm_para_attr]
bias_attr = ParameterAttribute(initial_std=0., l2_rate=0.)
# 激活函数
relu = ReluActivation()
linear = LinearActivation()
# 网络输入:id表示的词序列,词典大小为input_dim
data = data_layer("word", input_dim)
# 将id表示的词序列映射为embedding序列
emb = embedding_layer(input=data, size=emb_dim)
fc1 = fc_layer(input=emb, size=hid_dim, act=linear, bias_attr=bias_attr)
# 基于lstm的循环神经网络
lstm1 = lstmemory(
input=fc1, act=relu, bias_attr=bias_attr, layer_attr=layer_attr)
# 由fc_layer和lstmemory构建深度为stacked_num的栈式双向LSTM
inputs = [fc1, lstm1]
for i in range(2, stacked_num + 1):
fc = fc_layer(
input=inputs,
size=hid_dim,
act=linear,
param_attr=para_attr,
bias_attr=bias_attr)
lstm = lstmemory(
input=fc,
# 奇数层正向,偶数层反向。
reverse=(i % 2) == 0,
act=relu,
bias_attr=bias_attr,
layer_attr=layer_attr)
inputs = [fc, lstm]
# 对最后一层fc_layer使用时间维度上的最大池化得到定长向量
fc_last = pooling_layer(input=inputs[0], pooling_type=MaxPooling())
# 对最后一层lstmemory使用时间维度上的最大池化得到定长向量
lstm_last = pooling_layer(input=inputs[1], pooling_type=MaxPooling())
# 将fc_last和lstm_last拼接起来输入给softmax分类,类别数为class_dim
output = fc_layer(
input=[fc_last, lstm_last],
size=class_dim,
act=SoftmaxActivation(),
bias_attr=bias_attr,
param_attr=para_attr)
if is_predict:
outputs(output)
else:
outputs(classification_cost(input=output, label=data_layer('label', 1)))
```
## 训练模型 ## 训练模型
首先安装PaddlePaddle。 然后使用下面的脚本 `train.sh` 来开启本地的训练。 使用`train.sh`脚本可以开启本地的训练:
``` ```
./train.sh ./train.sh
``` ```
train.sh: train.sh内容如下:
``` ```bash
config=trainer_config.py paddle train --config=trainer_config.py \
output=./model_output --save_dir=./model_output \
paddle train --config=$config \
--save_dir=$output \
--job=train \ --job=train \
--use_gpu=false \ --use_gpu=false \
--trainer_count=4 \ --trainer_count=4 \
...@@ -387,10 +311,10 @@ paddle train --config=$config \ ...@@ -387,10 +311,10 @@ paddle train --config=$config \
2>&1 | tee 'train.log' 2>&1 | tee 'train.log'
``` ```
* \--config=$config: 设置网络配置。 * \--config=trainer_config.py: 设置模型配置。
* \--save\_dir=$output: 设置输出路径以保存训练完成的模型。 * \--save\_dir=./model_output: 设置输出路径以保存训练完成的模型。
* \--job=train: 设置工作模式为训练。 * \--job=train: 设置工作模式为训练。
* \--use\_gpu=false: 使用CPU训练,如果安装GPU版本的PaddlePaddle,并想使用GPU来训练可将此设置为true。 * \--use\_gpu=false: 使用CPU训练,如果安装GPU版本的PaddlePaddle,并想使用GPU来训练可将此设置为true。
* \--trainer\_count=4:设置线程数(或GPU个数)。 * \--trainer\_count=4:设置线程数(或GPU个数)。
* \--num\_passes=15: 设置pass,PaddlePaddle中的一个pass意味着对数据集中的所有样本进行一次训练。 * \--num\_passes=15: 设置pass,PaddlePaddle中的一个pass意味着对数据集中的所有样本进行一次训练。
* \--log\_period=20: 每20个batch打印一次日志。 * \--log\_period=20: 每20个batch打印一次日志。
...@@ -414,7 +338,7 @@ Test samples=24999 cost=0.39297 Eval: classification_error_evaluator=0.149406 ...@@ -414,7 +338,7 @@ Test samples=24999 cost=0.39297 Eval: classification_error_evaluator=0.149406
* CurrentEval: classification\_error\_evaluator: 最新log_period个batch的分类错误。 * CurrentEval: classification\_error\_evaluator: 最新log_period个batch的分类错误。
* Pass=0: 通过所有训练集一次称为一个Pass。 0表示第一次经过训练集。 * Pass=0: 通过所有训练集一次称为一个Pass。 0表示第一次经过训练集。
默认情况下,我们使用`stacked_lstm_net`网络,如果要使用双向LSTM或卷积网络,注释相应的行即可。 我们的模型配置`trainer_config.py`默认使用`stacked_lstm_net`网络,如果要使用`convolution_net`,注释相应的行即可。
## 应用模型 ## 应用模型
### 测试模型 ### 测试模型
...@@ -424,7 +348,7 @@ Test samples=24999 cost=0.39297 Eval: classification_error_evaluator=0.149406 ...@@ -424,7 +348,7 @@ Test samples=24999 cost=0.39297 Eval: classification_error_evaluator=0.149406
./test.sh ./test.sh
``` ```
test.sh: 测试脚本`test.sh`的内容如下,其中函数`get_best_pass`通过对分类错误率进行排序来获得最佳模型:
```bash ```bash
function get_best_pass() { function get_best_pass() {
...@@ -452,14 +376,14 @@ paddle train --config=$net_conf \ ...@@ -452,14 +376,14 @@ paddle train --config=$net_conf \
2>&1 | tee 'test.log' 2>&1 | tee 'test.log'
``` ```
函数`get_best_pass`依据分类错误率获取最佳模型。 与训练不同,测试时需要指定`--job = test`和模型路径,即`--model_list = $model_list`。如果运行成功,日志将保存在“test.log”中。例如,在我们的测试中,最好的模型是`model_output / pass-00002`,分类误差是0.115645,如下 与训练不同,测试时需要指定`--job = test`和模型路径`--model_list = $model_list`。如果测试成功,日志将保存在`test.log`中。 在我们的测试中,最好的模型是`model_output/pass-00002`,分类错误率是0.115645
``` ```
Pass=0 samples=24999 AvgCost=0.280471 Eval: classification_error_evaluator=0.115645 Pass=0 samples=24999 AvgCost=0.280471 Eval: classification_error_evaluator=0.115645
``` ```
### 预测 ### 预测
`predict.py`脚本提供了一个预测接口。在使用它之前请安装PaddlePaddle的python api。 预测IMDB的未标记评论的一个实例如下: `predict.py`脚本提供了一个预测接口。预测IMDB中未标记评论的示例如下:
``` ```
./predict.sh ./predict.sh
...@@ -467,13 +391,11 @@ Pass=0 samples=24999 AvgCost=0.280471 Eval: classification_error_evaluator=0.115 ...@@ -467,13 +391,11 @@ Pass=0 samples=24999 AvgCost=0.280471 Eval: classification_error_evaluator=0.115
predict.sh: predict.sh:
```bash ```bash
#Note the default model is pass-00002, you shold make sure the model path
#exists or change the mode path.
model=model_output/pass-00002/ model=model_output/pass-00002/
config=trainer_config.py config=trainer_config.py
label=data/pre-imdb/labels.list label=data/pre-imdb/labels.list
cat ./data/aclImdb/test/pos/10007_10.txt | python predict.py \ cat ./data/aclImdb/test/pos/10007_10.txt | python predict.py \
--tconf=$config\ --tconf=$config \
--model=$model \ --model=$model \
--label=$label \ --label=$label \
--dict=./data/pre-imdb/dict.txt \ --dict=./data/pre-imdb/dict.txt \
...@@ -488,7 +410,7 @@ cat ./data/aclImdb/test/pos/10007_10.txt | python predict.py \ ...@@ -488,7 +410,7 @@ cat ./data/aclImdb/test/pos/10007_10.txt | python predict.py \
* `--dict=data/pre-imdb/dict.txt` : 设置文本数据字典文件。 * `--dict=data/pre-imdb/dict.txt` : 设置文本数据字典文件。
* `--batch_size=1` : 预测时的batch size大小。 * `--batch_size=1` : 预测时的batch size大小。
注意应该确保默认模型路径`model_output / pass-00002`存在或更改为其它模型路径。 注意应该确保默认模型路径`model_output/pass-00002`存在或更改为其它模型路径。
本示例的预测结果: 本示例的预测结果:
...@@ -498,7 +420,7 @@ Loading parameters from model_output/pass-00002/ ...@@ -498,7 +420,7 @@ Loading parameters from model_output/pass-00002/
``` ```
## 总结 ## 总结
本章我们以情感分析为例介绍了使用深度学习的方法进行端对端的短文本分类,并且使用PaddlePaddle完成了全部相关实验。我们简要论述了两种文本处理模型:卷积神经网络和循环神经网络。在后续的章节中我们会看到这两种基本的深度学习模型在其它任务上的应用。 本章我们以情感分析为例,介绍了使用深度学习的方法进行端对端的短文本分类,并且使用PaddlePaddle完成了全部相关实验。同时,我们简要介绍了两种文本处理模型:卷积神经网络和循环神经网络。在后续的章节中我们会看到这两种基本的深度学习模型在其它任务上的应用。
## 参考文献 ## 参考文献
1. Kim Y. [Convolutional neural networks for sentence classification](http://arxiv.org/pdf/1408.5882)[J]. arXiv preprint arXiv:1408.5882, 2014. 1. Kim Y. [Convolutional neural networks for sentence classification](http://arxiv.org/pdf/1408.5882)[J]. arXiv preprint arXiv:1408.5882, 2014.
2. Kalchbrenner N, Grefenstette E, Blunsom P. [A convolutional neural network for modelling sentences](http://arxiv.org/pdf/1404.2188.pdf?utm_medium=App.net&utm_source=PourOver)[J]. arXiv preprint arXiv:1404.2188, 2014. 2. Kalchbrenner N, Grefenstette E, Blunsom P. [A convolutional neural network for modelling sentences](http://arxiv.org/pdf/1404.2188.pdf?utm_medium=App.net&utm_source=PourOver)[J]. arXiv preprint arXiv:1404.2188, 2014.
......
...@@ -16,9 +16,10 @@ from paddle.trainer.PyDataProvider2 import * ...@@ -16,9 +16,10 @@ from paddle.trainer.PyDataProvider2 import *
def hook(settings, dictionary, **kwargs): def hook(settings, dictionary, **kwargs):
settings.word_dict = dictionary settings.word_dict = dictionary
settings.input_types = [ settings.input_types = {
integer_value_sequence(len(settings.word_dict)), integer_value(2) 'word': integer_value_sequence(len(settings.word_dict)),
] 'label': integer_value(2)
}
settings.logger.info('dict len : %d' % (len(settings.word_dict))) settings.logger.info('dict len : %d' % (len(settings.word_dict)))
...@@ -32,4 +33,4 @@ def process(settings, file_name): ...@@ -32,4 +33,4 @@ def process(settings, file_name):
word_slot = [ word_slot = [
settings.word_dict[w] for w in words if w in settings.word_dict settings.word_dict[w] for w in words if w in settings.word_dict
] ]
yield word_slot, label yield {'word': word_slot, 'label': label}
...@@ -20,7 +20,7 @@ model=model_output/pass-00002/ ...@@ -20,7 +20,7 @@ model=model_output/pass-00002/
config=trainer_config.py config=trainer_config.py
label=data/pre-imdb/labels.list label=data/pre-imdb/labels.list
cat ./data/aclImdb/test/pos/10007_10.txt | python predict.py \ cat ./data/aclImdb/test/pos/10007_10.txt | python predict.py \
--tconf=$config\ --tconf=$config \
--model=$model \ --model=$model \
--label=$label \ --label=$label \
--dict=./data/pre-imdb/dict.txt \ --dict=./data/pre-imdb/dict.txt \
......
#!/bin/bash
# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -e
echo "Start to preprcess..."
data_dir="./data/imdb"
python preprocess.py -i $data_dir
echo "Done."
# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from os.path import join as join_path
from paddle.trainer_config_helpers import *
def sentiment_data(data_dir=None,
is_test=False,
is_predict=False,
train_list="train.list",
test_list="test.list",
dict_file="dict.txt"):
"""
Predefined data provider for sentiment analysis.
is_test: whether this config is used for test.
is_predict: whether this config is used for prediction.
train_list: text file name, containing a list of training set.
test_list: text file name, containing a list of testing set.
dict_file: text file name, containing dictionary.
"""
dict_dim = len(open(join_path(data_dir, "dict.txt")).readlines())
class_dim = len(open(join_path(data_dir, 'labels.list')).readlines())
if is_predict:
return dict_dim, class_dim
if data_dir is not None:
train_list = join_path(data_dir, train_list)
test_list = join_path(data_dir, test_list)
dict_file = join_path(data_dir, dict_file)
train_list = train_list if not is_test else None
word_dict = dict()
with open(dict_file, 'r') as f:
for i, line in enumerate(open(dict_file, 'r')):
word_dict[line.split('\t')[0]] = i
define_py_data_sources2(
train_list,
test_list,
module="dataprovider",
obj="process",
args={'dictionary': word_dict})
return dict_dim, class_dim
def convolution_net(input_dim,
class_dim=2,
emb_dim=128,
hid_dim=128,
is_predict=False):
data = data_layer("word", input_dim)
emb = embedding_layer(input=data, size=emb_dim)
conv_3 = sequence_conv_pool(input=emb, context_len=3, hidden_size=hid_dim)
conv_4 = sequence_conv_pool(input=emb, context_len=4, hidden_size=hid_dim)
output = fc_layer(
input=[conv_3, conv_4], size=class_dim, act=SoftmaxActivation())
if not is_predict:
lbl = data_layer("label", 1)
outputs(classification_cost(input=output, label=lbl))
else:
outputs(output)
def bidirectional_lstm_net(input_dim,
class_dim=2,
emb_dim=128,
lstm_dim=128,
is_predict=False):
data = data_layer("word", input_dim)
emb = embedding_layer(input=data, size=emb_dim)
bi_lstm = bidirectional_lstm(input=emb, size=lstm_dim)
dropout = dropout_layer(input=bi_lstm, dropout_rate=0.5)
output = fc_layer(input=dropout, size=class_dim, act=SoftmaxActivation())
if not is_predict:
lbl = data_layer("label", 1)
outputs(classification_cost(input=output, label=lbl))
else:
outputs(output)
def stacked_lstm_net(input_dim,
class_dim=2,
emb_dim=128,
hid_dim=512,
stacked_num=3,
is_predict=False):
"""
A Wrapper for sentiment classification task.
This network uses bi-directional recurrent network,
consisting three LSTM layers. This configure is referred to
the paper as following url, but use fewer layrs.
http://www.aclweb.org/anthology/P15-1109
input_dim: here is word dictionary dimension.
class_dim: number of categories.
emb_dim: dimension of word embedding.
hid_dim: dimension of hidden layer.
stacked_num: number of stacked lstm-hidden layer.
is_predict: is predicting or not.
Some layers is not needed in network when predicting.
"""
hid_lr = 1e-3
assert stacked_num % 2 == 1
layer_attr = ExtraLayerAttribute(drop_rate=0.5)
fc_para_attr = ParameterAttribute(learning_rate=hid_lr)
lstm_para_attr = ParameterAttribute(initial_std=0., learning_rate=1.)
para_attr = [fc_para_attr, lstm_para_attr]
bias_attr = ParameterAttribute(initial_std=0., l2_rate=0.)
relu = ReluActivation()
linear = LinearActivation()
data = data_layer("word", input_dim)
emb = embedding_layer(input=data, size=emb_dim)
fc1 = fc_layer(input=emb, size=hid_dim, act=linear, bias_attr=bias_attr)
lstm1 = lstmemory(
input=fc1, act=relu, bias_attr=bias_attr, layer_attr=layer_attr)
inputs = [fc1, lstm1]
for i in range(2, stacked_num + 1):
fc = fc_layer(
input=inputs,
size=hid_dim,
act=linear,
param_attr=para_attr,
bias_attr=bias_attr)
lstm = lstmemory(
input=fc,
reverse=(i % 2) == 0,
act=relu,
bias_attr=bias_attr,
layer_attr=layer_attr)
inputs = [fc, lstm]
fc_last = pooling_layer(input=inputs[0], pooling_type=MaxPooling())
lstm_last = pooling_layer(input=inputs[1], pooling_type=MaxPooling())
output = fc_layer(
input=[fc_last, lstm_last],
size=class_dim,
act=SoftmaxActivation(),
bias_attr=bias_attr,
param_attr=para_attr)
if is_predict:
outputs(output)
else:
outputs(classification_cost(input=output, label=data_layer('label', 1)))
...@@ -14,10 +14,8 @@ ...@@ -14,10 +14,8 @@
# limitations under the License. # limitations under the License.
set -e set -e
config=trainer_config.py paddle train --config=trainer_config.py \
output=./model_output --save_dir=./model_output \
paddle train --config=$config \
--save_dir=$output \
--job=train \ --job=train \
--use_gpu=false \ --use_gpu=false \
--trainer_count=4 \ --trainer_count=4 \
......
...@@ -12,16 +12,37 @@ ...@@ -12,16 +12,37 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from sentiment_net import * from os.path import join as join_path
from paddle.trainer_config_helpers import * from paddle.trainer_config_helpers import *
# whether this config is used for test # whether this config is used for test
is_test = get_config_arg('is_test', bool, False) is_test = get_config_arg('is_test', bool, False)
# whether this config is used for prediction # whether this config is used for prediction
is_predict = get_config_arg('is_predict', bool, False) is_predict = get_config_arg('is_predict', bool, False)
data_dir = "./data/pre-imdb" data_dir = "./data/pre-imdb"
dict_dim, class_dim = sentiment_data(data_dir, is_test, is_predict) train_list = "train.list"
test_list = "test.list"
dict_file = "dict.txt"
dict_dim = len(open(join_path(data_dir, "dict.txt")).readlines())
class_dim = len(open(join_path(data_dir, 'labels.list')).readlines())
if not is_predict:
train_list = join_path(data_dir, train_list)
test_list = join_path(data_dir, test_list)
dict_file = join_path(data_dir, dict_file)
train_list = train_list if not is_test else None
word_dict = dict()
with open(dict_file, 'r') as f:
for i, line in enumerate(open(dict_file, 'r')):
word_dict[line.split('\t')[0]] = i
define_py_data_sources2(
train_list,
test_list,
module="dataprovider",
obj="process",
args={'dictionary': word_dict})
################## Algorithm Config ##################### ################## Algorithm Config #####################
...@@ -34,7 +55,97 @@ settings( ...@@ -34,7 +55,97 @@ settings(
gradient_clipping_threshold=25) gradient_clipping_threshold=25)
#################### Network Config ###################### #################### Network Config ######################
def convolution_net(input_dim,
class_dim=2,
emb_dim=128,
hid_dim=128,
is_predict=False):
data = data_layer("word", input_dim)
emb = embedding_layer(input=data, size=emb_dim)
conv_3 = sequence_conv_pool(input=emb, context_len=3, hidden_size=hid_dim)
conv_4 = sequence_conv_pool(input=emb, context_len=4, hidden_size=hid_dim)
output = fc_layer(
input=[conv_3, conv_4], size=class_dim, act=SoftmaxActivation())
if not is_predict:
lbl = data_layer("label", 1)
outputs(classification_cost(input=output, label=lbl))
else:
outputs(output)
def stacked_lstm_net(input_dim,
class_dim=2,
emb_dim=128,
hid_dim=512,
stacked_num=3,
is_predict=False):
"""
A Wrapper for sentiment classification task.
This network uses bi-directional recurrent network,
consisting three LSTM layers. This configure is referred to
the paper as following url, but use fewer layrs.
http://www.aclweb.org/anthology/P15-1109
input_dim: here is word dictionary dimension.
class_dim: number of categories.
emb_dim: dimension of word embedding.
hid_dim: dimension of hidden layer.
stacked_num: number of stacked lstm-hidden layer.
is_predict: is predicting or not.
Some layers is not needed in network when predicting.
"""
hid_lr = 1e-3
assert stacked_num % 2 == 1
layer_attr = ExtraLayerAttribute(drop_rate=0.5)
fc_para_attr = ParameterAttribute(learning_rate=hid_lr)
lstm_para_attr = ParameterAttribute(initial_std=0., learning_rate=1.)
para_attr = [fc_para_attr, lstm_para_attr]
bias_attr = ParameterAttribute(initial_std=0., l2_rate=0.)
relu = ReluActivation()
linear = LinearActivation()
data = data_layer("word", input_dim)
emb = embedding_layer(input=data, size=emb_dim)
fc1 = fc_layer(input=emb, size=hid_dim, act=linear, bias_attr=bias_attr)
lstm1 = lstmemory(
input=fc1, act=relu, bias_attr=bias_attr, layer_attr=layer_attr)
inputs = [fc1, lstm1]
for i in range(2, stacked_num + 1):
fc = fc_layer(
input=inputs,
size=hid_dim,
act=linear,
param_attr=para_attr,
bias_attr=bias_attr)
lstm = lstmemory(
input=fc,
reverse=(i % 2) == 0,
act=relu,
bias_attr=bias_attr,
layer_attr=layer_attr)
inputs = [fc, lstm]
fc_last = pooling_layer(input=inputs[0], pooling_type=MaxPooling())
lstm_last = pooling_layer(input=inputs[1], pooling_type=MaxPooling())
output = fc_layer(
input=[fc_last, lstm_last],
size=class_dim,
act=SoftmaxActivation(),
bias_attr=bias_attr,
param_attr=para_attr)
if is_predict:
outputs(output)
else:
outputs(classification_cost(input=output, label=data_layer('label', 1)))
stacked_lstm_net( stacked_lstm_net(
dict_dim, class_dim=class_dim, stacked_num=3, is_predict=is_predict) dict_dim, class_dim=class_dim, stacked_num=3, is_predict=is_predict)
# bidirectional_lstm_net(dict_dim, class_dim=class_dim, is_predict=is_predict)
# convolution_net(dict_dim, class_dim=class_dim, is_predict=is_predict) # convolution_net(dict_dim, class_dim=class_dim, is_predict=is_predict)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册