提交 225979c6 编写于 作者: F fengjiayi

fix problems finded in review

上级 304df714
# 文本分类
文本分类是机器学习中的一项常见任务,主要目的是根据一条文本的内容,判断该文本所属的类别。在本例子中,我们利用有标注的IMDB语料库训练二分类DNN和CNN模型,完成对语料的简单文本分类。
DNN与CNN模型之间最大的区别在于,CNN模型中存在卷积结构,而DNN大多使用基本的全连接结构。这使得CNN模型可以对语料信息中相邻单词组成的短语进行分析。例如,"The apple is not bad",其中的"not bad"是决定这个句子情感的关键。对于DNN模型来说,只能感知到句子中有一个"not"和一个"bad",而CNN模型则可能直接感知到"not bad"这个关键词组。因此,在大多数文本分类任务上,CNN模型的表现要好于DNN。
## 实验数据
本例子的实验在IMDB数据集上进行。IMDB数据集包含了来自IMDb(互联网电影数据库)网站的5万条电影影评,并被标注为正面/负面两种评价。数据集被划分为train和test两部分,各2.5万条数据,正负样本的比例基本为1:1。样本直接以英文原文的形式表示。
本例子的实验在IMDB数据集上进行([数据集下载](http://ai.stanford.edu/%7Eamaas/data/sentiment/aclImdb_v1.tar.gz))。IMDB数据集包含了来自IMDb(互联网电影数据库)网站的5万条电影影评,并被标注为正面/负面两种评价。数据集被划分为train和test两部分,各2.5万条数据,正负样本的比例基本为1:1。样本直接以英文原文的形式表示。
本样例在第一次运行的时候会自动下载IMDB数据集并缓存,用户无需手动下载。
## DNN模型
#### DNN的模型结构入下图所示:
***DNN的模型结构入下图所示:***
<p align="center">
<img src="images/dnn_net.png" width = "90%" align="center"/><br/>
图1. DNN文本分类模型
</p>
#### 可以看到,模型主要分为如下几个部分:
***可以看到,模型主要分为如下几个部分:***
- **embedding层**:IMDB的样本由原始的英文单词组成,为了方便模型的训练,必须通过embedding将英文单词转化为固定维度的向量。
- **词向量层**:IMDB的样本由原始的英文单词组成,为了方便模型的训练,必须将英文单词转化为固定维度的向量。
- **max pooling**:max pooling在时间序列上进行,pooling过程消除了不同语料样本在单词数量多少上的差异,并提炼出词向量中每一下标位置上的最大值。经过pooling后,样本被转化为一条固定维度的向量。
- **最大池化层**:最大池化在时间序列上进行,池化过程消除了不同语料样本在单词数量多少上的差异,并提炼出词向量中每一下标位置上的最大值。经过池化后,样本被转化为一条固定维度的向量。例如,假设最大池化前的矩阵为`[[2,3,5],[7,3,6],[1,4,0]]`,该矩阵每一列代表一个词向量,则最大池化的结果为:`[[5],[7],[4]]`
- **全连接隐层**:经过max pooling后的向量被送入一个具有两个隐层的DNN模型,隐层之间为全连接结构。
- **全连接隐层**:经过最大池化后的向量被送入两个连续的隐层,隐层之间为全连接结构。
- **输出层**:输出层的神经元数量和样本的类别数一致,例如在二分类问题中,输出层会有2个神经元。通过Softmax激活函数,我们保证输出层各神经元的输出之和为1,因此第i个神经元的输出就可以认为是样本属于第i类的预测概率。
#### 通过Paddle实现该DNN结构的代码如下:
***通过PaddlePaddle实现该DNN结构的代码如下:***
```python
import paddle.v2 as paddle
......@@ -41,53 +46,55 @@ def fc_net(input_dim, class_dim=2, emb_dim=256):
input=emb, pooling_type=paddle.pooling.Max())
# two hidden layers
hd_layer_size = [128, 32]
hd_layer_init_std = [1.0/math.sqrt(s)/3.0 for s in hd_layer_size]
hd1 = paddle.layer.fc(
input=seq_pool,
size=128,
size=hd_layer_size[0],
act=paddle.activation.Tanh(),
param_attr=paddle.attr.Param(initial_std=0.01))
param_attr=paddle.attr.Param(initial_std=hd_layer_init_std[0]))
hd2 = paddle.layer.fc(
input=hd1,
size=32,
size=hd_layer_size[1],
act=paddle.activation.Tanh(),
param_attr=paddle.attr.Param(initial_std=0.01))
param_attr=paddle.attr.Param(initial_std=hd_layer_init_std[1]))
# output layer
output = paddle.layer.fc(
input=hd2,
size=class_dim,
act=paddle.activation.Softmax(),
param_attr=paddle.attr.Param(initial_std=0.1))
param_attr=paddle.attr.Param(initial_std=1.0/math.sqrt(class_dim)/3.0))
cost = paddle.layer.classification_cost(input=output, label=lbl)
return cost, output
```
该DNN模型默认对输入的语料进行二分类(`class_dim=2`),embedding的词向量维度默认为256(`emd_dim=256`),两个隐层均使用Tanh激活函数(`act=paddle.activation.Tanh()`)。
该DNN模型默认对输入的语料进行二分类(`class_dim=2`),embedding的词向量维度默认为256`emd_dim=256`,两个隐层均使用Tanh激活函数(`act=paddle.activation.Tanh()`)。
需要注意的是,该模型的输入数据为整数序列,而不是原始的英文单词序列。事实上,为了处理方便我们一般会事先将单词根据词频顺序进行id化,即将单词用整数替代。这一步一般在DNN模型之外完成。
## CNN模型
#### CNN的模型结构如下图所示:
***CNN的模型结构如下图所示:***
<p align="center">
<img src="images/cnn_net.png" width = "90%" align="center"/><br/>
图2. CNN文本分类模型
</p>
#### 可以看到,模型主要分为如下几个部分:
***可以看到,模型主要分为如下几个部分:***
- **embedding层**:与DNN中embedding的作用一样,将英文单词转化为固定维度的向量。如图2中所示,将embedding得到的词向量定义为行向量,再将语料中所有的单词产生的行向量拼接在一起组成矩阵。假设embedding_size=5,语料“The cat sat on the read mat”包含7个单词,那么得到的矩阵维度为7*5。
- **词向量层**:与DNN中词向量层的作用一样,将英文单词转化为固定维度的向量。如图2中所示,将得到的词向量定义为行向量,再将语料中所有的单词产生的行向量拼接在一起组成矩阵。假设词向量维度为5,语料“The cat sat on the read mat”包含7个单词,那么得到的矩阵维度为7*5。
- **卷积层**: 文本分类中的卷积在时间序列上进行,即卷积核的宽度和embedding得到的矩阵一致,卷积验证矩阵的高度方向进行。假设卷积核的高度为h,矩阵的高度为N,卷积的step_size为1,则卷积后得到的feature map为一个高度为N+1-h的向量。可以同时使用多个不同高度的卷积核,得到多个feature map
- **卷积层**: 文本分类中的卷积在时间序列上进行,即卷积核的宽度和词向量层产出的矩阵一致,卷积验证矩阵的高度方向进行。卷积后得到的结果被称为“特征图”(feature map)。假设卷积核的高度为h,矩阵的高度为N,卷积的步长为1,则得到的特征图为一个高度为N+1-h的向量。可以同时使用多个不同高度的卷积核,得到多个特征图
- **max pooling**: 对卷积得到的各个feature map分别进行max pooling操作。由于feature map本身已经是向量,因此这里的max pooling实际上就是简单地选出各个向量中的最大元素。各个最大元素又被并置在一起,组成新的向量,显然,该向量的维度等于feature map的数量,也就是卷积核的数量。
- **最大池化层**: 对卷积得到的各个特征图分别进行最大池化操作。由于特征图本身已经是向量,因此这里的最大池化实际上就是简单地选出各个向量中的最大元素。各个最大元素又被并置在一起,组成新的向量,显然,该向量的维度等于特征图的数量,也就是卷积核的数量。
- **全连接与输出层**:将max pooling的结果通过全连接层输出,与DNN模型一样,最后输出层的神经元个数与样本的类别数量一致,且输出之和为1。
- **全连接与输出层**:将最大池化的结果通过全连接层输出,与DNN模型一样,最后输出层的神经元个数与样本的类别数量一致,且输出之和为1。
#### 通过Paddle实现该CNN结构的代码如下:
***通过PaddlePaddle实现该CNN结构的代码如下:***
```python
import paddle.v2 as paddle
......@@ -114,9 +121,10 @@ def convolution_net(input_dim, class_dim=2, emb_dim=128, hid_dim=128):
cost = paddle.layer.classification_cost(input=output, label=lbl)
return cost, output
```
该CNN网络的输入数据类型和前面介绍过的DNN一致。`paddle.networks.sequence_conv_pool`为Paddle中已经封装好的带有pooling的文本序列卷积模块,该模块的`context_len`参数用于指定卷积核在同一时间覆盖的文本长度,也即图2中的卷积核的高度;`hidden_size`用于指定该类型的卷积核的数量。可以看到,上述代码定义的结构中使用了128个大小为3的卷积核和128个大小为4的卷积核,这些卷积的结果经过max pooling和结果并置后产生一个256维的向量,向量经过一个全连接层输出最终预测结果。
该CNN网络的输入数据类型和前面介绍过的DNN一致。`paddle.networks.sequence_conv_pool`为Paddle中已经封装好的带有池化的文本序列卷积模块,该模块的`context_len`参数用于指定卷积核在同一时间覆盖的文本长度,也即图2中的卷积核的高度;`hidden_size`用于指定该类型的卷积核的数量。可以看到,上述代码定义的结构中使用了128个大小为3的卷积核和128个大小为4的卷积核,这些卷积的结果经过最大池化和结果并置后产生一个256维的向量,向量经过一个全连接层输出最终预测结果。
## 运行与输出
......@@ -132,7 +140,7 @@ def convolution_net(input_dim, class_dim=2, emb_dim=128, hid_dim=128):
- **main函数**:主函数
要运行本样例,直接在`text_classification_dnn.py`所在路径下执行`python ./text_classification_dnn.py`即可,样例会自动依次执行数据读取、模型训练和保存、模型读取、新样本预测等步骤。
要运行本样例,直接在`text_classification_dnn.py`所在路径下执行`python ./text_classification_dnn.py`即可,样例会自动依次执行数据集下载、数据读取、模型训练和保存、模型读取、新样本预测等步骤。
预测的输出形式为:
......
text_classification/images/dnn_net.png

288.1 KB | W: | H:

text_classification/images/dnn_net.png

288.7 KB | W: | H:

text_classification/images/dnn_net.png
text_classification/images/dnn_net.png
text_classification/images/dnn_net.png
text_classification/images/dnn_net.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -82,7 +82,8 @@ def train_cnn_model(num_pass):
if isinstance(event, paddle.event.EndPass):
result = trainer.test(reader=test_reader, feeding=feeding)
print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics)
with gzip.open("cnn_params.tar.gz", 'w') as f:
with gzip.open("cnn_params_pass" + str(event.pass_id) + ".tar.gz",
'w') as f:
parameters.to_tar(f)
# begin training network
......@@ -96,7 +97,7 @@ def train_cnn_model(num_pass):
print("Training finished.")
def cnn_infer():
def cnn_infer(file_name):
print("Begin to predict...")
word_dict = paddle.dataset.imdb.word_dict()
......@@ -104,17 +105,13 @@ def cnn_infer():
class_dim = 2
[_, output] = convolution_net(dict_dim, class_dim=class_dim)
parameters = paddle.parameters.Parameters.from_tar(
gzip.open("cnn_params.tar.gz"))
parameters = paddle.parameters.Parameters.from_tar(gzip.open(file_name))
infer_data = []
infer_label_data = []
infer_data_num = 100
infer_data_label = []
for item in paddle.dataset.imdb.test(word_dict):
infer_data.append([item[0]])
infer_label_data.append(item[1])
if len(infer_data) == infer_data_num:
break
infer_data_label.append(item[1])
predictions = paddle.infer(
output_layer=output,
......@@ -122,10 +119,12 @@ def cnn_infer():
input=infer_data,
field=['value'])
for i, prob in enumerate(predictions):
print prob, infer_label_data[i]
print prob, infer_data_label[i]
if __name__ == "__main__":
paddle.init(use_gpu=False, trainer_count=4)
train_cnn_model(num_pass=5)
cnn_infer()
num_pass = 5
train_cnn_model(num_pass=num_pass)
param_file_name = "cnn_params_pass" + str(num_pass - 1) + ".tar.gz"
cnn_infer(file_name=param_file_name)
......@@ -13,6 +13,7 @@
# limitations under the License.
import sys
import math
import paddle.v2 as paddle
import gzip
......@@ -30,23 +31,26 @@ def fc_net(input_dim, class_dim=2, emb_dim=256):
input=emb, pooling_type=paddle.pooling.Max())
# two hidden layers
hd_layer_size = [128, 32]
hd_layer_init_std = [1.0 / math.sqrt(s) / 3.0 for s in hd_layer_size]
hd1 = paddle.layer.fc(
input=seq_pool,
size=128,
size=hd_layer_size[0],
act=paddle.activation.Tanh(),
param_attr=paddle.attr.Param(initial_std=0.01))
param_attr=paddle.attr.Param(initial_std=hd_layer_init_std[0]))
hd2 = paddle.layer.fc(
input=hd1,
size=32,
size=hd_layer_size[1],
act=paddle.activation.Tanh(),
param_attr=paddle.attr.Param(initial_std=0.01))
param_attr=paddle.attr.Param(initial_std=hd_layer_init_std[1]))
# output layer
output = paddle.layer.fc(
input=hd2,
size=class_dim,
act=paddle.activation.Softmax(),
param_attr=paddle.attr.Param(initial_std=0.1))
param_attr=paddle.attr.Param(initial_std=1.0 / math.sqrt(class_dim) /
3.0))
cost = paddle.layer.classification_cost(input=output, label=lbl)
......@@ -94,7 +98,8 @@ def train_dnn_model(num_pass):
if isinstance(event, paddle.event.EndPass):
result = trainer.test(reader=test_reader, feeding=feeding)
print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics)
with gzip.open("dnn_params.tar.gz", 'w') as f:
with gzip.open("dnn_params_pass" + str(event.pass_id) + ".tar.gz",
'w') as f:
parameters.to_tar(f)
# begin training network
......@@ -108,7 +113,7 @@ def train_dnn_model(num_pass):
print("Training finished.")
def dnn_infer():
def dnn_infer(file_name):
print("Begin to predict...")
word_dict = paddle.dataset.imdb.word_dict()
......@@ -116,17 +121,13 @@ def dnn_infer():
class_dim = 2
[_, output] = fc_net(dict_dim, class_dim=class_dim)
parameters = paddle.parameters.Parameters.from_tar(
gzip.open("dnn_params.tar.gz"))
parameters = paddle.parameters.Parameters.from_tar(gzip.open(file_name))
infer_data = []
infer_label_data = []
infer_data_num = 100
infer_data_label = []
for item in paddle.dataset.imdb.test(word_dict):
infer_data.append([item[0]])
infer_label_data.append(item[1])
if len(infer_data) == infer_data_num:
break
infer_data_label.append(item[1])
predictions = paddle.infer(
output_layer=output,
......@@ -134,10 +135,12 @@ def dnn_infer():
input=infer_data,
field=['value'])
for i, prob in enumerate(predictions):
print prob, infer_label_data[i]
print prob, infer_data_label[i]
if __name__ == "__main__":
paddle.init(use_gpu=False, trainer_count=4)
train_dnn_model(num_pass=5)
dnn_infer()
num_pass = 5
train_dnn_model(num_pass=num_pass)
param_file_name = "dnn_params_pass" + str(num_pass - 1) + ".tar.gz"
dnn_infer(file_name=param_file_name)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册