README.md 29.5 KB
Newer Older
W
wangxuguang 已提交
1 2
# 情感分析
## 背景介绍
W
wangxuguang 已提交
3 4 5
<br/>&emsp;&emsp;在自然语言处理中,情感分析一般是指判断一段文本所表达的情绪状态。其中,一段文本可以是一个句子,一个段落或一个文档。情绪状态可以是两类,如(正面,负面),(高兴,悲伤),也可以是三类,如(积极,消极,中性)等等。
<br/>&emsp;&emsp;情感分析的应用场景十分广泛,如把用户在购物网站(亚马逊、天猫、淘宝等)、旅游网站、电影评论网站上发表的评论分成正面评论和负面评论。为了分析用户对于某一产品的整体使用感受,抓取产品的用户评论并进行情感分析等等。
<br/>&emsp;&emsp;对电影评论进行情感分析(正面,负面)的例子如下面的表格1所示。
W
wangxuguang 已提交
6 7 8 9 10 11 12 13

| 电影评论       | 类别  |
| --------     | -----  |
| 在冯小刚这几年的电影里,算最好的一部的了| 正面 |
| 很不好看,好像一个地方台的电视剧     | 负面 |
| 为了讽刺官场刻意丑化农村人的傻片子,圆方镜头全程炫技,色调背景美则美矣,但剧情拖沓,口音不伦不类,一直努力却始终无法入戏。不建议进电影院观看,不然睡着了躺都没地方躺。| 负面|
|剧情四星。但是圆镜视角加上婺源的风景整个非常有中国写意山水画的感觉,看得实在太舒服了。。难怪作为今年TIFF special presentation的开幕电影。范爷美爆,再往上加一星。|正面|

14
<p align="center">表格 1 电影评论情感分析</p>
W
wangxuguang 已提交
15
<br/>&emsp;&emsp;实际上,在自然语言处理中,情感分析属于典型的**文本分类**问题,即,把需要进行情感分析的文本划分为其所属类别。文本分类问题可以分解为两个子问题:文本表示和分类。在深度学习的方法出现之前,主流的文本表示方法为BOW(bag of words),分类方法有SVM,LR,Boosting等等。BOW忽略了词的顺序信息,而且是高维度的稀疏向量表示,这种表示浮于表面,并未充分表示文本的语义信息。例如,句子`这部电影糟糕透了``一个乏味,空洞,没有内涵的作品`在情感分析中具有很高的语义相似度,但是它们的BOW表示的相似度为0。又如,句子`一个空洞,没有内涵的作品``一个不空洞而且有内涵的作品`的BOW相似度很高,但实际上它们的意思很不一样。本章我们所要介绍的深度学习模型克服了BOW表示的上述缺陷,它在考虑词的顺序的基础上把文本映射到低维度的语义空间,并且以端对端(end to end)的方式进行文本表示及分类,其性能相对于传统方法有显著的提升。
W
wangxuguang 已提交
16
## 模型概览
W
wangxuguang 已提交
17
<br/>&emsp;&emsp;本章所使用的文本表示模型为卷积神经网络(Convolutional Neural Networks)和循环神经网络(Recurrent Neural Networks)及其扩展。我们首先介绍处理文本的卷积神经网络。
W
wangxuguang 已提交
18
### 文本卷积神经网络
W
wangxuguang 已提交
19
<br/>&emsp;&emsp;卷积神经网络经常用来处理具有类似网格拓扑结构(grid-like topology)的数据。例如,图像可以视为2D网格的像素点,自然语言可以视为1D的词序列。卷积神经网络可以提取多种局部特征,并对其进行组合抽象得到更高级的特征表示,且其对于数据的某些变化具有不变性。大量实验表明,卷积神经网络能高效的对图像及文本问题进行建模处理。本小结我们讲解如何使用卷积神经网络处理文本(以句子为例)。
20 21 22 23
<p align="center">
<img src="image/text_cnn.png"><br/>
图 1 卷积神经网络文本分类模型
</p>
W
wangxuguang 已提交
24
<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$:
W
wangxuguang 已提交
25

W
wangxuguang 已提交
26
$$c_i=f(w\cdot x_{i:i+h-1}+b)$$
W
wangxuguang 已提交
27

W
wangxuguang 已提交
28
<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:
W
wangxuguang 已提交
29

W
wangxuguang 已提交
30
$$c=[c_1,c_2,\ldots,c_{n-h+1}]$$
W
wangxuguang 已提交
31 32 33

<br/>&emsp;&emsp;其中$c \in \mathbb{R}^{n-h+1}$。接下来我们对feature map采用max pooling over time操作得到此filter对应的整句话的特征:

W
wangxuguang 已提交
34
$$\hat c=max(c)$$
W
wangxuguang 已提交
35

W
wangxuguang 已提交
36 37
<br/>&emsp;&emsp;即,$\hat c$是feature map中所有元素的最大值。pooling机制自动处理了句子长度不一的问题。在实际应用中,我们会使用多个filter来处理句子,窗口大小相同的filters堆叠起来形成一个矩阵(上文中的单个filter参数$w$相当于矩阵的某一行),这样可以更高效的完成运算。另外,我们也可使用窗口大小不同的filters来处理句子,最后,将所有filters得到的特征拼接起来即为文本的定长向量表示。对于文本分类问题,将其连接至softmax即构建出完整的模型。
<br/>&emsp;&emsp;对于一般的短文本分类问题,上文所述的简单的文本卷积网络即可达到很高的正确率\[[1](#参考文献)\]。若想得到更抽象更高级的文本特征表示,可以参考N. Kalchbrenner, et al.(2014)\[[2](#参考文献)\]或 Yann N. Dauphin, et al.(2016)\[[3](#参考文献)\]的构建深层文本卷积神经网络的方法。
W
wangxuguang 已提交
38 39
### 循环神经网络
#### 简单的循环神经网络
W
wangxuguang 已提交
40 41
<br/>&emsp;&emsp;循环神经网络(rnn)是一种能对序列数据进行精确建模的有力工具。实际上,循环神经网络的理论计算能力是图灵完备的\[[4](#参考文献)\]
<br/>&emsp;&emsp;自然语言是一种典型的序列数据(词序列),近年来,循环神经网络及其变体(如long short term memory\[[5](#参考文献)\]等)在自然语言处理的多个领域取得了丰硕的成果,如在语言模型,句法解析,语义角色标注(或一般的序列标注),语义表示,图文生成,对话,机器翻译等任务上均表现优异甚至成为目前效果最好的方法。
42 43 44 45 46
<p align="center">
<img src="image/rnn.png"><br/>
图 2 循环神经网络按时间展开的示意图
</p>
<br/>&emsp;&emsp;循环神经网络按时间展开后如图2所示:在第$t$时刻,网络读入第$t$个输入$x_t$(向量表示)及前一时刻隐藏层的输出$h_{t-1}$(向量表示,$h_0$一般初始化为$0$向量),计算得出本时刻隐藏层的值$h_t$,重复这一步骤直至读完所有输入。如果将循环神经网络所表示的函数记为$f$,则其公式可表示为:
W
wangxuguang 已提交
47

W
wangxuguang 已提交
48
$$h_t=f(x_t,h_{t-1})=\sigma(W_{xh}x_t+W_{hh}h_{h-1}+b_h)$$
W
wangxuguang 已提交
49

W
wangxuguang 已提交
50
<br/>&emsp;&emsp;其中$W_{xh}$是输入到隐层的矩阵参数,$W_{hh}$是隐层到隐层的矩阵参数,$b_h$为隐层的偏置向量(bias)参数,$\sigma$为elementwise的sigmoid函数。在处理自然语言时,一般会先将词(one-hot表示)映射为其embedding表示,然后再作为循环神经网络每一时刻的输入$x_t$。可以根据实际需要的不同在循环神经网络的隐层上连接其它层。如,可以把一个循环神经网络的隐层输出连接至下一个循环神经网络的输入构建深层(deep or stacked)循环神经网络,或者提取最后一个时刻的隐层状态作为句子表示进而使用分类模型等等。
W
wangxuguang 已提交
51 52 53 54
<br/>&emsp;&emsp;可以看出,隐状态的输入来源于当前输入和前一时刻隐状态的值,这会导致很久以前的输入容易被覆盖掉。实际上,人们发现当序列很长时,循环神经网络就会表现很差(远距离依赖问题),训练过程中会出现梯度消失或爆炸现象\[[6](#参考文献)\]。为了解决这一问题,Hochreiter S, Schmidhuber J. (1997)\[[5](#参考文献)\]提出了lstm(long short term memory)模型。
#### lstm-rnn
<br/>&emsp;&emsp;相比于简单的循环神经网络,lstm增加了记忆单元$c$,输入门$i$,遗忘门$f$及输出门$o$,这些门及记忆单元组合起来大大提升了循环神经网络处理远距离依赖问题的能力,若将基于lstm的循环神经网络表示的函数记为F,则其公式为:

W
wangxuguang 已提交
55
$$ h_t=F(x_t,h_{t-1})$$
W
wangxuguang 已提交
56 57

<br/>&emsp;&emsp;$F$由下列公式组合而成\[[7](#参考文献)\]
W
wangxuguang 已提交
58
\begin{align}
W
wangxuguang 已提交
59 60
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)\\\\
W
wangxuguang 已提交
61
c_t & = f_t\odot c_{t-1}+i_t\odot tanh(W_{xc}x_t+W_{hc}h_{h-1}+b_c)\\\\
W
wangxuguang 已提交
62
o_t & = \sigma(W_{xo}x_t+W_{ho}h_{h-1}+W_{co}c_{t}+b_o)\\\\
W
wangxuguang 已提交
63 64
h_t & = o_t\odot tanh(c_t)\\\\
\end{align}
W
wangxuguang 已提交
65 66 67 68 69 70 71
<br/>&emsp;&emsp;其中,$i_t, f_t, c_t, o_t$分别表示输入门,遗忘门,记忆单元(记忆单元一般对外不可见,$h_t$对外部可见)及输出门的向量值,带角标的$W$及$b$为模型参数,$tanh$为elementwise的双曲正切函数,$\odot$表示elementwise的乘法操作。输入门控制着新输入进入记忆单元$c$的强度,遗忘门控制着记忆单元维持上一时刻值的强度,输出门控制着输出记忆单元的强度。三种门的计算方式类似,但有着完全不同的参数,这三种门各自以不同的方式控制着记忆单元$c$,如图3所示:
<p align="center">
<img src="image/lstm.png"><br/>
图 3 时刻$t$的lstm\[[7](#参考文献)\]
</p>
<br/>&emsp;&emsp;实际上,lstm的思想正是通过给简单的循环神经网络增加记忆及控制门的方式增强了其处理远距离依赖问题的能力。类似原理的对于简单循环神经网络的改进还有Gated Recurrent Unit (GRU)\[[8](#参考文献)\],其设计更为简洁一些。**这些改进虽然各有不同,但是对他们的宏观描述却与简单的循环神经网络一样,如图2所示,隐状态依据当前输入及前一时刻的隐状态来改变,不断的循环这一过程直至输入处理完毕:**

W
wangxuguang 已提交
72
$$ h_t=Recrurent(x_t,h_{t-1})$$
W
wangxuguang 已提交
73

W
wangxuguang 已提交
74
<br/>&emsp;&emsp;对于正常顺序的循环神经网络而言,$h_t$包含了$t$时刻之前的输入信息,也就是上文信息。同样,为了得到下文信息,我们可以使用反方向(将输入逆序处理)的循环神经网络。结合构建深层循环神经网络的方法,我们可以构建更加强有力的深层双向循环神经网络(deep bi-directional recurrent neural networks)对时序数据进行建模。
W
wangxuguang 已提交
75
#### 使用循环神经网络的组合进行文本分类
W
wangxuguang 已提交
76
<br/>&emsp;&emsp;一个简单的做法是分别使用正向lstm-rnn和反向lstm-rnn处理文本,取最后一个时刻的隐层值拼接起来做为文本的定长向量表示,将其连接至softmax得到文本分类模型。但是这样的文本分类模型是一个浅层模型。考虑到深层神经网络往往能得到更抽象和高级的特征表示,我们构建stacked lstm-rnn\[[9](#参考文献)\]。如图4所示(以三层为例),奇数层lstm正向,偶数层lstm反向,高一层的lstm使用低一层lstm及之前所有层的信息作为输入,对最高层lstm序列使用max pooling over time得到文本定长向量表示。**这一表示充分融合了文本的上下文信息,并且对文本进行了深层次抽象。**最后我们将文本表示连接至softmax构建分类模型。
77 78
<p align="center">
<img src="image/stacked_lstm.jpg"><br/>
W
wangxuguang 已提交
79
图 4 stacked lstm-rnn for text classification
80 81 82 83
</p>
## 数据准备
### 数据介绍与下载
我们以IMDB情感分析数据集为例进行介绍。训练模型之前, 我们需要预处理数椐并构建一个字典。 首先, 你可以使用下面的脚本下载 IMDB 数椐集和[Moses](http://www.statmt.org/moses/)工具, 我们提供了一个数据预处理脚本,它不仅能够处理IMDB数据,还能处理其他用户自定义的数据。 为了使用提前编写的脚本,需要将标记的训练和测试样本移动到另一个路径,这已经在`get_imdb.sh`中完成。
W
wangxuguang 已提交
84

85 86 87 88
```
./get_imdb.sh
```
如果数椐获取成功,你将在目录```data```中看到下面的文件:
W
wangxuguang 已提交
89

90 91 92
```
aclImdb  get_imdb.sh  imdb  mosesdecoder-master
```
W
wangxuguang 已提交
93

94 95 96
* aclImdb: 从外部网站上下载的原始数椐集。
* imdb: 仅包含训练和测试数椐集。
* mosesdecoder-master: Moses 工具。
W
wangxuguang 已提交
97

98 99 100
IMDB数据集包含25,000个已标注过的电影评论用于训练,25,000个用于测试。负面的评论的得分小于等于4,正面的评论的得分大于等于7,满分10分。 
### 数据预处理
在这个例子中,我们只使用已经标注过的训练集和测试集,且默认在训练集上构建字典。训练集已经做了随机打乱排序。 Moses 工具中的脚本`tokenizer.perl` 用于切分单单词和标点符号。执行下面的命令就可以预处理数椐。
W
wangxuguang 已提交
101

102 103 104 105
```
./preprocess.sh
```
preprocess.sh:
W
wangxuguang 已提交
106

107 108 109 110
```
data_dir="./data/imdb"
python preprocess.py -i data_dir
```
W
wangxuguang 已提交
111

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
* data_dir: 输入数椐所在目录。
* preprocess.py: 预处理脚本。

运行成功后目录`data/pre-imdb` 结构如下:

```
dict.txt  labels.list  test.list  test_part_000  train.list  train_part_000
```

* test\_part\_000 and train\_part\_000: 所有标记的测试集和训练集, 训练集已经随机打乱。
* train.list and test.list: 训练集和测试集文件列表。
* dict.txt: 利用训练集生成的字典。
* labels.txt: neg  0, pos 1, 含义:标签0表示负面的评论,标签1表示正面的评论。

### 提供数据给PaddlePaddle
PaddlePaddle可以读取Python写的传输数据脚本,下面dataprovider.py文件给出了完整例子,主要包括两部分:

* hook: 定义文本信息、类别Id的数据类型。文本被定义为整数序列`integer_value_sequence`,类别被定义为整数`integer_value`
* process: yield文本信息和类别Id,和hook里定义顺序一致。process读取的文件的行为类别和评论文本,以`'\t\t'`分隔。

```python
from paddle.trainer.PyDataProvider2 import *

def hook(settings, dictionary, **kwargs):
    settings.word_dict = dictionary
    settings.input_types = [
        integer_value_sequence(len(settings.word_dict)), integer_value(2)
    ]
    settings.logger.info('dict len : %d' % (len(settings.word_dict)))


@provider(init_hook=hook)
def process(settings, file_name):
    with open(file_name, 'r') as fdata:
        for line_count, line in enumerate(fdata):
            label, comment = line.strip().split('\t\t')
            label = int(label)
            words = comment.split()
            word_slot = [
                settings.word_dict[w] for w in words if w in settings.word_dict
            ]
            yield word_slot, label
```

## 模型配置说明
`trainer_config.py` 是一个配置文件的例子。第一行从`sentiment_net.py`中导出预定义的网络。

trainer_config.py:

```python
from sentiment_net import *

data_dir  = "./data/pre-imdb"
# whether this config is used for test
is_test = get_config_arg('is_test', bool, False)
# whether this config is used for prediction
is_predict = get_config_arg('is_predict', bool, False)
dict_dim, class_dim = sentiment_data(data_dir, is_test, is_predict)

################## Algorithm Config #####################

settings(
  batch_size=128,
  learning_rate=2e-3,
  learning_method=AdamOptimizer(),
  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
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
```

在模型配置中利用define_py_data_sources2加载数据:

* train.list,test.list: 指定训练、测试数据
* module="dataprovider": 数据处理Python文件名
* obj="process": 指定生成数据的函数
* args={"dictionary": word_dict}: 额外的参数,这里指定词典

### 模型结构

   * `convolution_net`: 在`sentiment_net.py`中定义。其论述详见`文本卷积神经网络`小结。
   ```python
    def convolution_net(input_dim,
                    class_dim=2,
                    emb_dim=128,
                    hid_dim=128,
                    is_predict=False):
		data = data_layer("word", input_dim)              # one-hot表示的词序列
    	emb = embedding_layer(input=data, size=emb_dim)   # 将one-hot表示的词序列映射为embedding序列
    	conv_3 = sequence_conv_pool(input=emb, context_len=3, hidden_size=hid_dim)   #窗口大小为3的convolution及max pooling操作
    	conv_4 = sequence_conv_pool(input=emb, context_len=4, hidden_size=hid_dim)   #窗口大小为4的convolution及max pooling操作
    	output = fc_layer(input=[conv_3,conv_4], size=class_dim, act=SoftmaxActivation())  #将conv_3和conv_4拼接起来输入给softmax分类

	    if not is_predict:
	        lbl = data_layer("label", 1)  #类别标签
	        outputs(classification_cost(input=output, label=lbl))
	    else:
	        outputs(output)
   ```

       其中,我们仅用一个`sequence_conv_pool`方法就实现了convolution和pooling操作,filter的数量为hidden_size参数。`sequence_conv_pool`的实现详见`Paddle/python/paddle/trainer_config_helpers/networks.py`。

   * `bidirectional_lstm_net`: 在`sentiment_net.py`中定义。其论述详见`使用循环神经网络的组合进行文本分类`小结。
   ```python   
    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) # 双向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,
                     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)  #基于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)))
   ```


## 训练模型
首先安装PaddlePaddle。 然后使用下面的脚本 `train.sh` 来开启本地的训练。

```
./train.sh
```

train.sh:

```
config=trainer_config.py
output=./model_output
paddle train --config=$config \
             --save_dir=$output \
             --job=train \
             --use_gpu=false \
             --trainer_count=4 \
             --num_passes=10 \
             --log_period=20 \
             --dot_period=20 \
             --show_parameter_stats_period=100 \
             --test_all_data_in_one_period=1 \
             2>&1 | tee 'train.log'
```

* \--config=$config: 设置网络配置。
* \--save\_dir=$output: 设置输出路径以保存训练完成的模型。
* \--job=train: 设置工作模式为训练。
* \--use\_gpu=false: 使用CPU训练,如果你安装GPU版本的PaddlePaddle,并想使用GPU来训练可将此设置为true。
* \--trainer\_count=4:设置线程数(或GPU个数)。
* \--num\_passes=15: 设置pass,PaddlePaddle中的一个pass意味着对数据集中的所有样本进行一次训练。
* \--log\_period=20: 每20个batch打印一次日志。
* \--show\_parameter\_stats\_period=100: 每100个batch打印一次统计信息。
* \--test\_all_data\_in\_one\_period=1: 每次测试都测试所有数据。

如果运行成功,输出日志保存在 `train.log`中,模型保存在目录`model_output/`中。  输出日志说明如下:

```
Batch=20 samples=2560 AvgCost=0.681644 CurrentCost=0.681644 Eval: classification_error_evaluator=0.36875  CurrentEval: classification_error_evaluator=0.36875
...
Pass=0 Batch=196 samples=25000 AvgCost=0.418964 Eval: classification_error_evaluator=0.1922
Test samples=24999 cost=0.39297 Eval: classification_error_evaluator=0.149406
```

* Batch=xx: 表示训练了xx个Batch。
* samples=xx: 表示训练了xx个样本。
* AvgCost=xx: 从第0个batch到当前batch的平均损失。
* CurrentCost=xx: 最新log_period个batch的损失。
* Eval: classification\_error\_evaluator=xx: 表示第0个batch到当前batch的分类错误。
* CurrentEval: classification\_error\_evaluator: 最新log_period个batch的分类错误。
* Pass=0: 通过所有训练集一次称为一个Pass。 0表示第一次经过训练集。

默认情况下,我们使用`stacked_lstm_net`网络,如果要使用双向LSTM或卷积网络,注释相应的行即可。
## 应用模型
### 测试模型

测试模型是指使用训练出的模型评估已标记的数据集。

```
./test.sh
```

test.sh:

```bash
function get_best_pass() {
  cat $1  | grep -Pzo 'Test .*\n.*pass-.*' | \
  sed  -r 'N;s/Test.* error=([0-9]+\.[0-9]+).*\n.*pass-([0-9]+)/\1 \2/g' | \
  sort | head -n 1
}

log=train.log
LOG=`get_best_pass $log`
LOG=(${LOG})
evaluate_pass="model_output/pass-${LOG[1]}"

echo 'evaluating from pass '$evaluate_pass

model_list=./model.list
touch $model_list | echo $evaluate_pass > $model_list
net_conf=trainer_config.py
paddle train --config=$net_conf \
             --model_list=$model_list \
             --job=test \
             --use_gpu=false \
             --trainer_count=4 \
             --config_args=is_test=1 \
             2>&1 | tee 'test.log'
```

函数`get_best_pass`依据分类错误率获取最佳模型。 与训练不同,测试时需要指定`--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
```

### 预测
`predict.py`脚本提供了一个预测接口。在使用它之前请安装PaddlePaddle的python api。 预测IMDB的未标记评论的一个实例如下:

```
./predict.sh
```
predict.sh:

```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/
config=trainer_config.py
label=data/pre-imdb/labels.list
cat ./data/aclImdb/test/pos/10007_10.txt | python predict.py \
     --tconf=$config\
     --model=$model \
     --label=$label \
     --dict=./data/pre-imdb/dict.txt \
     --batch_size=1
```

* `cat ./data/aclImdb/test/pos/10007_10.txt` : 输入预测样本。
* `predict.py` : 预测接口脚本。
* `--tconf=$config` : 设置网络配置。
* `--model=$model` : 设置模型路径。
* `--label=$label` : 设置标签类别字典,这个字典是整数标签和字符串标签的一个对应。
* `--dict=data/pre-imdb/dict.txt` : 设置文本数据字典文件。
W
wangxuguang 已提交
489
* `--batch_size=1` : 预测时的batch size大小。
490 491 492 493 494 495 496 497 498 499 500

注意应该确保默认模型路径`model_output / pass-00002`存在或更改为其它模型路径。

本示例的预测结果:

```
Loading parameters from model_output/pass-00002/
./data/aclImdb/test/pos/10014_7.txt: predicting label is pos
```

## 总结
W
wangxuguang 已提交
501
本章我们以情感分析为例介绍了使用深度学习的方法进行端对端的短文本分类,并且使用PaddlePaddle完成了全部相关实验。我们简要论述了两种文本处理模型:卷积神经网络和循环神经网络。在后续的章节中我们会看到这两种基本的深度学习模型在其它任务上的应用。
W
wangxuguang 已提交
502
## 参考文献
W
wangxuguang 已提交
503 504 505 506 507 508 509 510 511
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.
3. Yann N. Dauphin, et al. [Language Modeling with Gated Convolutional Networks](https://arxiv.org/pdf/1612.08083v1.pdf)[J] arXiv preprint arXiv:1612.08083, 2016.
4. Siegelmann H T, Sontag E D. [On the computational power of neural nets](http://research.cs.queensu.ca/home/akl/cisc879/papers/SELECTED_PAPERS_FROM_VARIOUS_SOURCES/05070215382317071.pdf)[C]//Proceedings of the fifth annual workshop on Computational learning theory. ACM, 1992: 440-449.
5. Hochreiter S, Schmidhuber J. [Long short-term memory](http://web.eecs.utk.edu/~itamar/courses/ECE-692/Bobby_paper1.pdf)[J]. Neural computation, 1997, 9(8): 1735-1780.
6. Bengio Y, Simard P, Frasconi P. [Learning long-term dependencies with gradient descent is difficult](http://www-dsi.ing.unifi.it/~paolo/ps/tnn-94-gradient.pdf)[J]. IEEE transactions on neural networks, 1994, 5(2): 157-166.
7. Graves A. [Generating sequences with recurrent neural networks](http://arxiv.org/pdf/1308.0850)[J]. arXiv preprint arXiv:1308.0850, 2013.
8. Cho K, Van Merriënboer B, Gulcehre C, et al. [Learning phrase representations using RNN encoder-decoder for statistical machine translation](http://arxiv.org/pdf/1406.1078)[J]. arXiv preprint arXiv:1406.1078, 2014.
9. Zhou J, Xu W. [End-to-end learning of semantic role labeling using recurrent neural networks](http://www.aclweb.org/anthology/P/P15/P15-1109.pdf)[C]//Proceedings of the Annual Meeting of the Association for Computational Linguistics. 2015.