diff --git a/understand_sentiment/.gitignore b/understand_sentiment/.gitignore index 30b4423025c225c761bc1d6dfb3d2056e2d16644..667762d327cb160376a4119fa9df9db41b6443b2 100644 --- a/understand_sentiment/.gitignore +++ b/understand_sentiment/.gitignore @@ -2,7 +2,7 @@ data/aclImdb data/imdb data/pre-imdb data/mosesdecoder-master -logs/ +*.log model_output dataprovider_copy_1.py model.list diff --git a/understand_sentiment/README.md b/understand_sentiment/README.md index d8e375e2d48ba52b85ad9189b70085d2b2aa3109..55f34542f1383a369743b55d85c7dccea0894296 100644 --- a/understand_sentiment/README.md +++ b/understand_sentiment/README.md @@ -1,8 +1,6 @@ # 情感分析 ## 背景介绍 -在自然语言处理中,情感分析一般是指判断一段文本所表达的情绪状态。其中,一段文本可以是一个句子,一个段落或一个文档。情绪状态可以是两类,如(正面,负面),(高兴,悲伤);也可以是三类,如(积极,消极,中性)等等。 - -情感分析的应用场景十分广泛,如把用户在购物网站(亚马逊、天猫、淘宝等)、旅游网站、电影评论网站上发表的评论分成正面评论和负面评论。为了分析用户对于某一产品的整体使用感受,抓取产品的用户评论并进行情感分析等等。表格1展示了对电影评论进行情感分析的例子: +在自然语言处理中,情感分析一般是指判断一段文本所表达的情绪状态。其中,一段文本可以是一个句子,一个段落或一个文档。情绪状态可以是两类,如(正面,负面),(高兴,悲伤);也可以是三类,如(积极,消极,中性)等等。情感分析的应用场景十分广泛,如把用户在购物网站(亚马逊、天猫、淘宝等)、旅游网站、电影评论网站上发表的评论分成正面评论和负面评论;或为了分析用户对于某一产品的整体使用感受,抓取产品的用户评论并进行情感分析等等。表格1展示了对电影评论进行情感分析的例子: | 电影评论 | 类别 | | -------- | ----- | @@ -13,20 +11,24 @@

表格 1 电影评论情感分析

-在自然语言处理中,情感分析属于典型的**文本分类**问题,即,把需要进行情感分析的文本划分为其所属类别。文本分类涉及文本表示和分类方法。在深度学习的方法出现之前,主流的文本表示方法为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)的方式进行文本表示及分类,其性能相对于传统方法有显著的提升。 +在自然语言处理中,情感分析属于典型的**文本分类**问题,即,把需要进行情感分析的文本划分为其所属类别。文本分类涉及文本表示和分类方法两个问题。在深度学习的方法出现之前,主流的文本表示方法为词袋模型BOW(bag of words),话题模型等等;分类方法有SVM(support vector machine), LR(logistic regression)等等。 + +BOW假定对于一段文本,忽略其词顺序和语法、句法,将其仅仅看做是一个词集合,它并不能充分表示文本的语义信息。例如,句子“这部电影糟糕透了”和“一个乏味,空洞,没有内涵的作品”在情感分析中具有很高的语义相似度,但是它们的BOW表示的相似度为0。又如,句子“一个空洞,没有内涵的作品”和“一个不空洞而且有内涵的作品”的BOW相似度很高,但实际上它们的意思很不一样。 + +本章我们所要介绍的深度学习模型克服了BOW表示的上述缺陷,它在考虑词顺序的基础上把文本映射到低维度的语义空间,并且以端对端(end to end)的方式进行文本表示及分类,其性能相对于传统方法有显著的提升\[[1](#参考文献)\]。 ## 模型概览 -本章所使用的文本表示模型为卷积神经网络(Convolutional Neural Networks)和循环神经网络(Recurrent Neural Networks)及其扩展。我们首先介绍处理文本的卷积神经网络。 +本章所使用的文本表示模型为卷积神经网络(Convolutional Neural Networks)和循环神经网络(Recurrent Neural Networks)及其扩展。下面依次介绍这几个模型。 ### 文本卷积神经网络(CNN) -卷积神经网络经常用来处理具有类似网格拓扑结构(grid-like topology)的数据。例如,图像可以视为2D网格的像素点,自然语言可以视为1D的词序列。卷积神经网络可以提取多种局部特征,并对其进行组合抽象得到更高级的特征表示,且其对于数据的某些变化具有不变性。大量实验表明,卷积神经网络能高效的对图像及文本问题进行建模处理。本小结我们讲解如何使用卷积神经网络处理文本(以句子为例)\[[1](#参考文献)\]。 +卷积神经网络经常用来处理具有类似网格拓扑结构(grid-like topology)的数据。例如,图像可以视为二维网格的像素点,自然语言可以视为一维的词序列。卷积神经网络可以提取多种局部特征,并对其进行组合抽象得到更高级的特征表示。实验表明,卷积神经网络能高效地对图像及文本问题进行建模处理。本小结我们讲解如何使用卷积神经网络处理文本(以句子为例)\[[1](#参考文献)\]。


-图 1 卷积神经网络文本分类模型 +图1. 卷积神经网络文本分类模型

-假设一个句子的长度为$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}$,得到特征$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}$,得到特征$c_i$: $$c_i=f(w\cdot x_{i:i+h-1}+b)$$ -其中$b\in\mathbb{R}$为偏置项(bias),$f$为非线性激活函数,如$sigmoid$。将卷积核应用于句子中所有的词窗口${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 \in \mathbb{R}^{n-h+1}$$ @@ -38,23 +40,23 @@ $$\hat c=max(c)$$ 对于一般的短文本分类问题,上文所述的简单的文本卷积网络即可达到很高的正确率\[[1](#参考文献)\]。若想得到更抽象更高级的文本特征表示,可以构建深层文本卷积神经网络\[[2](#参考文献),[3](#参考文献)\]。 ### 循环神经网络(RNN) -循环神经网络是一种能对序列数据进行精确建模的有力工具。实际上,循环神经网络的理论计算能力是图灵完备的\[[4](#参考文献)\]。自然语言是一种典型的序列数据(词序列),近年来,循环神经网络及其变体(如long short term memory\[[5](#参考文献)\]等)在自然语言处理的多个领域取得了丰硕的成果,如在语言模型、句法解析、语义角色标注(或一般的序列标注)、语义表示、图文生成、对话、机器翻译等任务上均表现优异甚至成为目前效果最好的方法。 +循环神经网络是一种能对序列数据进行精确建模的有力工具。实际上,循环神经网络的理论计算能力是图灵完备的\[[4](#参考文献)\]。自然语言是一种典型的序列数据(词序列),近年来,循环神经网络及其变体(如long short term memory\[[5](#参考文献)\]等)在自然语言处理的多个领域,如语言模型、句法解析、语义角色标注(或一般的序列标注)、语义表示、图文生成、对话、机器翻译等任务上均表现优异甚至成为目前效果最好的方法。


-图 2 循环神经网络按时间展开的示意图 +图2. 循环神经网络按时间展开的示意图

循环神经网络按时间展开后如图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)$$ -其中$W_{xh}$是输入到隐层的矩阵参数,$W_{hh}$是隐层到隐层的矩阵参数,$b_h$为隐层的偏置向量(bias)参数,$\sigma$为逐元素(elementwise)的$sigmoid$函数。 +其中$W_{xh}$是输入到隐层的矩阵参数,$W_{hh}$是隐层到隐层的矩阵参数,$b_h$为隐层的偏置向量(bias)参数,$\sigma$为$sigmoid$函数。 在处理自然语言时,一般会先将词(one-hot表示)映射为其词向量(word embedding)表示,然后再作为循环神经网络每一时刻的输入$x_t$。此外,可以根据实际需要的不同在循环神经网络的隐层上连接其它层。如,可以把一个循环神经网络的隐层输出连接至下一个循环神经网络的输入构建深层(deep or stacked)循环神经网络,或者提取最后一个时刻的隐层状态作为句子表示进而使用分类模型等等。 -### 长时短期记忆(LSTM) -循环神经网络隐状态的输入来源于当前输入和前一时刻隐状态的值,这会导致很久以前的输入容易被覆盖掉。实际上,人们发现当序列很长时,循环神经网络就会表现很差(远距离依赖问题),训练过程中会出现梯度消失或爆炸现象\[[6](#参考文献)\]。为了解决这一问题,Hochreiter S, Schmidhuber J. (1997)提出了lstm(long short term memory\[[5](#参考文献)\])。 +### 长短期记忆(LSTM) +对于较长的序列数据,循环神经网络的训练过程中容易出现梯度消失或爆炸现象\[[6](#参考文献)\]。为了解决这一问题,Hochreiter S, Schmidhuber J. (1997)提出了LSTM(long short term memory\[[5](#参考文献)\])。 -相比于简单的循环神经网络,lstm增加了记忆单元$c$、输入门$i$、遗忘门$f$及输出门$o$,这些门及记忆单元组合起来大大提升了循环神经网络处理远距离依赖问题的能力,若将基于lstm的循环神经网络表示的函数记为$F$,则其公式为: +相比于简单的循环神经网络,LSTM增加了记忆单元$c$、输入门$i$、遗忘门$f$及输出门$o$。这些门及记忆单元组合起来大大提升了循环神经网络处理长序列数据的能力。若将基于LSTM的循环神经网络表示的函数记为$F$,则其公式为: $$ h_t=F(x_t,h_{t-1})$$ @@ -66,28 +68,28 @@ 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)\\\\ h_t & = o_t\odot tanh(c_t)\\\\ \end{align} -其中,$i_t, f_t, c_t, o_t$分别表示输入门,遗忘门,记忆单元及输出门的向量值,带角标的$W$及$b$为模型参数,$tanh$为逐元素的双曲正切函数,$\odot$表示逐元素的乘法操作。输入门控制着新输入进入记忆单元$c$的强度,遗忘门控制着记忆单元维持上一时刻值的强度,输出门控制着输出记忆单元的强度。三种门的计算方式类似,但有着完全不同的参数,它们各自以不同的方式控制着记忆单元$c$,如图3所示: +其中,$i_t, f_t, c_t, o_t$分别表示输入门,遗忘门,记忆单元及输出门的向量值,带角标的$W$及$b$为模型参数,$tanh$为双曲正切函数,$\odot$表示逐元素(elementwise)的乘法操作。输入门控制着新输入进入记忆单元$c$的强度,遗忘门控制着记忆单元维持上一时刻值的强度,输出门控制着输出记忆单元的强度。三种门的计算方式类似,但有着完全不同的参数,它们各自以不同的方式控制着记忆单元$c$,如图3所示:

-
-图 3 时刻$t$的lstm +
+图3. 时刻$t$的LSTM

-lstm通过给简单的循环神经网络增加记忆及控制门的方式,增强了其处理远距离依赖问题的能力。类似原理的对于简单循环神经网络的改进还有Gated Recurrent Unit (GRU)\[[8](#参考文献)\],其设计更为简洁一些。**这些改进虽然各有不同,但是它们的宏观描述却与简单的循环神经网络一样(如图2所示),即隐状态依据当前输入及前一时刻的隐状态来改变,不断的循环这一过程直至输入处理完毕:** +LSTM通过给简单的循环神经网络增加记忆及控制门的方式,增强了其处理远距离依赖问题的能力。类似原理的改进还有Gated Recurrent Unit (GRU)\[[8](#参考文献)\],其设计更为简洁一些。**这些改进虽然各有不同,但是它们的宏观描述却与简单的循环神经网络一样(如图2所示),即隐状态依据当前输入及前一时刻的隐状态来改变,不断地循环这一过程直至输入处理完毕:** $$ h_t=Recrurent(x_t,h_{t-1})$$ 对于正常顺序的循环神经网络,$h_t$包含了$t$时刻之前的输入信息,也就是上文信息。同样,为了得到下文信息,我们可以使用反方向(将输入逆序处理)的循环神经网络。结合构建深层循环神经网络的方法,我们可以通过构建更加强有力的栈式双向循环神经网络,来对时序数据进行建模。 -#### 栈式双向LSTM(Stacked Bidirectional LSTM) -考虑到深层神经网络往往能得到更抽象和高级的特征表示,我们构建基于lstm的栈式双向循环神经网络\[[9](#参考文献)\]。如图4所示(以三层为例),奇数层lstm正向,偶数层lstm反向,高一层的lstm使用低一层lstm及之前所有层的信息作为输入,对最高层lstm序列使用时间维度上的最大池化即可得到文本的定长向量表示。**这一表示充分融合了文本的上下文信息,并且对文本进行了深层次抽象。**最后我们将文本表示连接至softmax构建分类模型。 +### 栈式双向LSTM(Stacked Bidirectional LSTM) +考虑到深层神经网络往往能得到更抽象和高级的特征表示,我们构建基于LSTM的栈式双向循环神经网络\[[9](#参考文献)\]。如图4所示(以三层为例),奇数层LSTM正向,偶数层LSTM反向,高一层的LSTM使用低一层LSTM及之前所有层的信息作为输入,对最高层LSTM序列使用时间维度上的最大池化即可得到文本的定长向量表示。**这一表示充分融合了文本的上下文信息,并且对文本进行了深层次抽象。**最后我们将文本表示连接至softmax构建分类模型。


-图 4 栈式双向LSTM用于文本分类 +图4. 栈式双向LSTM用于文本分类

## 数据准备 ### 数据介绍与下载 -我们以IMDB情感分析数据集为例进行介绍。IMDB数据集的训练集和测试集分别包含25000个已标注过的电影评论。其中,负面评论的得分小于等于4,正面评论的得分大于等于7,满分10分。您可以使用下面的脚本下载 IMDB 数椐集和[Moses](http://www.statmt.org/moses/)工具: +我们以[IMDB情感分析数据集](http://ai.stanford.edu/%7Eamaas/data/sentiment/)为例进行介绍。IMDB数据集的训练集和测试集分别包含25000个已标注过的电影评论。其中,负面评论的得分小于等于4,正面评论的得分大于等于7,满分10分。您可以使用下面的脚本下载 IMDB 数椐集和[Moses](http://www.statmt.org/moses/)工具: ```bash -data/get_imdb.sh +./data/get_imdb.sh ``` 如果数椐获取成功,您将在目录```data```中看到下面的文件: @@ -100,23 +102,20 @@ aclImdb get_imdb.sh imdb mosesdecoder-master * mosesdecoder-master: Moses 工具。 ### 数据预处理 -我们只使用已标注的训练集和测试集。将训练集随机打乱排序,默认在训练集上构建字典。Moses 工具中的脚本`tokenizer.perl` 用于切分单词和标点符号。执行下面的命令就可以预处理数椐: +我们使用的预处理脚本为`preprocess.py`。该脚本会调用Moses工具中的`tokenizer.perl`脚本来切分单词和标点符号,并会将训练集随机打乱排序再构建字典。注意:我们只使用已标注的训练集和测试集。执行下面的命令就可以预处理数椐: ``` data_dir="./data/imdb" python preprocess.py -i $data_dir ``` -* data_dir: 输入数椐所在目录。 -* preprocess.py: 预处理脚本。 - 运行成功后目录`./data/pre-imdb` 结构如下: ``` dict.txt labels.list test.list test_part_000 train.list train_part_000 ``` -* test\_part\_000 和 train\_part\_000: 所有标记的测试集和训练集, 训练集已经随机打乱。 +* test\_part\_000 和 train\_part\_000: 所有标记的测试集和训练集,训练集已经随机打乱。 * train.list 和 test.list: 训练集和测试集文件列表。 * dict.txt: 利用训练集生成的字典。 * labels.list: 类别标签列表,标签0表示负面评论,标签1表示正面评论。 @@ -124,8 +123,8 @@ dict.txt labels.list test.list test_part_000 train.list train_part_000 ### 提供数据给PaddlePaddle PaddlePaddle可以读取Python写的传输数据脚本,下面`dataprovider.py`文件给出了完整例子,主要包括两部分: -* hook函数: 定义文本信息、类别Id的数据类型。文本被定义为整数序列`integer_value_sequence`,类别被定义为整数`integer_value`。 -* process函数: 使用yield关键字返回文本信息和类别Id,和hook里定义顺序一致。process读取的文件的行为类别和评论文本,以`'\t\t'`分隔。 +* hook: 定义文本信息、类别Id的数据类型。文本被定义为整数序列`integer_value_sequence`,类别被定义为整数`integer_value`。 +* process: 按行读取以`'\t\t'`分隔的类别ID和文本信息,并用yield关键字返回。 ```python from paddle.trainer.PyDataProvider2 import * @@ -159,22 +158,45 @@ def process(settings, file_name): `trainer_config.py` 是一个配置文件的例子。 ### 数据定义 ```python -define_py_data_sources2( - train_list, - test_list, - module="dataprovider", - obj="process", - args={'dictionary': word_dict}) +from os.path import join as join_path +from paddle.trainer_config_helpers import * +# 是否是测试模式 +is_test = get_config_arg('is_test', bool, False) +# 是否是预测模式 +is_predict = get_config_arg('is_predict', bool, False) + +# 数据路径 +data_dir = "./data/pre-imdb" +# 文件名 +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函数从dataprovider.py中读取数据 + define_py_data_sources2( + train_list, + test_list, + module="dataprovider", + obj="process", # 指定生成数据的函数。 + args={'dictionary': word_dict}) # 额外的参数,这里指定词典。 ``` -在模型配置中利用define_py_data_sources2加载数据: - -* train.list、test.list: 指定训练、测试数据。 -* module="dataprovider": 数据处理Python文件名。 -* obj="process": 指定生成数据的函数。 -* args={"dictionary": word_dict}: 额外的参数,这里指定词典。 - -### 优化算法配置 +### 算法配置 ```python settings( @@ -187,12 +209,12 @@ settings( * 设置batch size大小为128。 * 设置全局学习率。 -* 使用 adam 优化。 +* 使用adam优化。 * 设置L2正则。 * 设置梯度截断(clipping)阈值。 ### 模型结构 -我们用PaddlePaddle分别基于[文本卷积神经网络](#文本卷积神经网络(CNN))和[栈式双向LSTM](#栈式双向LSTM(Stacked Bidirectional LSTM))实现文本分类。 +我们用PaddlePaddle实现了两种文本分类算法,分别基于上文所述的[文本卷积神经网络](#文本卷积神经网络(CNN))和[栈式双向LSTM](#栈式双向LSTM(Stacked Bidirectional LSTM))。 #### 文本卷积神经网络的实现 ```python def convolution_net(input_dim, @@ -242,13 +264,15 @@ def stacked_lstm_net(input_dim, # 激活函数 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的循环神经网络 + # 基于LSTM的循环神经网络 lstm1 = lstmemory( input=fc1, act=relu, bias_attr=bias_attr, layer_attr=layer_attr) @@ -288,6 +312,13 @@ def stacked_lstm_net(input_dim, outputs(classification_cost(input=output, label=data_layer('label', 1))) ``` +我们的模型配置`trainer_config.py`默认使用`stacked_lstm_net`网络,如果要使用`convolution_net`,注释相应的行即可。 +```python +stacked_lstm_net( + dict_dim, class_dim=class_dim, stacked_num=3, is_predict=is_predict) +# convolution_net(dict_dim, class_dim=class_dim, is_predict=is_predict) +``` + ## 训练模型 使用`train.sh`脚本可以开启本地的训练: @@ -338,11 +369,11 @@ Test samples=24999 cost=0.39297 Eval: classification_error_evaluator=0.149406 * CurrentEval: classification\_error\_evaluator: 最新log_period个batch的分类错误。 * Pass=0: 通过所有训练集一次称为一个Pass。 0表示第一次经过训练集。 -我们的模型配置`trainer_config.py`默认使用`stacked_lstm_net`网络,如果要使用`convolution_net`,注释相应的行即可。 + ## 应用模型 -### 测试模型 +### 测试 -测试模型是指使用训练出的模型评估已标记的数据集。 +测试是指使用训练出的模型评估已标记的数据集。 ``` ./test.sh @@ -419,6 +450,7 @@ Loading parameters from model_output/pass-00002/ predicting label is pos ``` +`10007_10.txt`在路径`./data/aclImdb/test/pos`下面,而这里预测的标签也是pos,说明预测正确。 ## 总结 本章我们以情感分析为例,介绍了使用深度学习的方法进行端对端的短文本分类,并且使用PaddlePaddle完成了全部相关实验。同时,我们简要介绍了两种文本处理模型:卷积神经网络和循环神经网络。在后续的章节中我们会看到这两种基本的深度学习模型在其它任务上的应用。 ## 参考文献