README.md 10.9 KB
Newer Older
P
peterzhang2029 已提交
1
# 基于双层序列的文本分类
P
peterzhang2029 已提交
2
## 简介
P
peterzhang2029 已提交
3
序列是自然语言处理任务面对的一种主要输入数据类型:句子由词语构成,而多个句子进一步构成了段落。因此,段落可以看作是一个嵌套的序列(或者叫作:双层序列),这个序列的每个元素又是一个序列。
P
peterzhang2029 已提交
4

P
peterzhang2029 已提交
5
双层序列是 PaddlePaddle 支持的一种非常灵活的数据组织方式,能够帮助我们更好地描述段落、多轮对话等更为复杂的语言数据。以双层序列作为输入,我们可以设计一个层次化的网络,分别从词语和句子级别编码输入数据,从而更好地完成一些复杂的语言理解任务。
P
peterzhang2029 已提交
6

P
peterzhang2029 已提交
7
本例将演示如何在 PaddlePaddle 中将长文本输入(通常能达到段落或者篇章基本)组织为双层序列,完成对长文本的分类任务。
P
peterzhang2029 已提交
8 9

## 模型介绍
P
peterzhang2029 已提交
10 11 12
我们将一段文本看成句子的序列,而每个句子又是词语的序列。

我们首先用卷积神经网络编码段落中的每一句话;然后,将每句话的表示向量经过池化层得到段落的编码向量;最后将段落的编码向量作为分类器(以softmax层的全连接层)输入,得到最终的分类结果。
P
peterzhang2029 已提交
13 14 15 16

**模型结构如下图所示**
<p align="center">
<img src="images/model.jpg" width = "60%" align="center"/><br/>
P
peterzhang2029 已提交
17
图1. 基于双层序列的文本分类模型
P
peterzhang2029 已提交
18 19 20 21
</p>

PaddlePaddle 实现该网络结构的代码见 `network_conf.py`

P
peterzhang2029 已提交
22 23 24 25
对双层时间序列的处理,需要先将双层时间序列数据变换成单层时间序列数据,再对每一个单层时间序列进行处理。 在 PaddlePaddle 中 ,`recurrent_group` 是帮助我们构建处理双层序列的层次化模型的主要工具。这里,我们使用两个嵌套的 `recurrent_group` 。外层的 `recurrent_group` 将段落拆解为句子,`step` 函数中拿到的输入是句子序列;内层的 `recurrent_group` 将句子拆解为词语,`step` 函数中拿到的输入是非序列的词语。

在词语级别,我们通过 CNN 网络以词向量为输入输出学习到的句子表示;在段落级别,将每个句子的表示通过池化作用得到段落表示。

P
peterzhang2029 已提交
26 27 28 29 30
``` python
nest_group = paddle.layer.recurrent_group(input=[paddle.layer.SubsequenceInput(emb),
                                                 hidden_size],
                                         step=cnn_cov_group)
```
P
peterzhang2029 已提交
31

P
peterzhang2029 已提交
32 33 34 35 36 37 38 39 40 41

拆解后的单层序列数据经过一个CNN网络学习对应的向量表示,CNN的网络结构包含以下部分:

- **卷积层**: 文本分类中的卷积在时间序列上进行,卷积核的宽度和词向量层产出的矩阵一致,卷积后得到的结果为“特征图”, 使用多个不同高度的卷积核,可以得到多个特征图。本例代码默认使用了大小为 3(图1红色框)和 4(图1蓝色框)的卷积核。
- **最大池化层**: 对卷积得到的各个特征图分别进行最大池化操作。由于特征图本身已经是向量,因此最大池化实际上就是选出各个向量中的最大元素。将所有最大元素又被拼接在一起,组成新的向量。
- **线性投影层**: 将不同卷积得到的结果经过最大池化层之后拼接为一个长向量, 然后经过一个线性投影得到对应单层序列的表示向量。

CNN网络具体代码实现如下:
```python
def cnn_cov_group(group_input, hidden_size):
P
peterzhang2029 已提交
42 43 44 45 46 47 48
    """
    Covolution group definition
    :param group_input: The input of this layer.
    :type group_input: LayerOutput
    :params hidden_size: Size of FC layer.
    :type hidden_size: int
    """
P
peterzhang2029 已提交
49 50 51 52
    conv3 = paddle.networks.sequence_conv_pool(
        input=group_input, context_len=3, hidden_size=hidden_size)
    conv4 = paddle.networks.sequence_conv_pool(
        input=group_input, context_len=4, hidden_size=hidden_size)
P
peterzhang2029 已提交
53 54

    linear_proj = paddle.layer.fc(input=[conv3, conv4],
P
peterzhang2029 已提交
55 56 57 58
                                  size=hidden_size,
                                  param_attr=paddle.attr.ParamAttr(name='_cov_value_weight'),
                                  bias_attr=paddle.attr.ParamAttr(name='_cov_value_bias'),
                                  act=paddle.activation.Linear())
P
peterzhang2029 已提交
59 60

    return linear_proj
P
peterzhang2029 已提交
61 62 63 64 65 66 67 68 69 70 71
```
PaddlePaddle 中已经封装好的带有池化的文本序列卷积模块:`paddle.networks.sequence_conv_pool`,可直接调用。

在得到每个句子的表示向量之后, 将所有句子表示向量经过一个平均池化层, 得到一个样本的向量表示, 向量经过一个全连接层输出最终的预测结果。 代码如下:
```python
avg_pool = paddle.layer.pooling(input=nest_group, pooling_type=paddle.pooling.Avg(),
                                    agg_level=paddle.layer.AggregateLevel.TO_NO_SEQUENCE)  
prob = paddle.layer.mixed(size=class_num,
                                     input=[paddle.layer.full_matrix_projection(input=avg_pool)],
                                       act=paddle.activation.Softmax())
```
P
peterzhang2029 已提交
72 73 74 75
## 安装依赖包
```bash
pip install -r requirements.txt
```
P
peterzhang2029 已提交
76

P
peterzhang2029 已提交
77 78
## 指定训练配置参数

P
peterzhang2029 已提交
79 80
通过 `config.py` 脚本修改训练和模型配置参数,脚本中有对可配置参数的详细解释,示例如下:
```python
P
peterzhang2029 已提交
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
class TrainerConfig(object):

    # whether to use GPU for training
    use_gpu = False
    # the number of threads used in one machine
    trainer_count = 1

    # train batch size
    batch_size = 32

    ...


class ModelConfig(object):

    # embedding vector dimension
    emb_size = 28

    ...
```
P
peterzhang2029 已提交
101
修改 `config.py` 对参数进行调整。例如,通过修改 `use_gpu` 参数来指定是否使用 GPU 进行训练。
P
peterzhang2029 已提交
102

P
peterzhang2029 已提交
103 104 105 106 107 108 109 110 111 112 113
## 使用 PaddlePaddle 内置数据运行

### 训练
在终端执行:
```bash
python train.py
```
将以 PaddlePaddle 内置的情感分类数据集: `imdb` 运行本例。
### 预测
训练结束后模型将存储在指定目录当中(默认models目录),在终端执行:
```bash
P
peterzhang2029 已提交
114
python infer.py --model_path 'models/params_pass_00000.tar.gz'
P
peterzhang2029 已提交
115 116 117 118 119 120 121 122
```
默认情况下,预测脚本将加载训练一个pass的模型对 `imdb的测试集` 进行测试。

## 使用自定义数据训练和预测

### 训练
1.数据组织

P
peterzhang2029 已提交
123
输入数据格式如下:每一行为一条样本,以 `\t` 分隔,第一列是类别标签,第二列是输入文本的内容。以下是两条示例数据:
P
peterzhang2029 已提交
124 125

    ```
P
peterzhang2029 已提交
126 127
    positive        This movie is very good. The actor is so handsome.
    negative        What a terrible movie. I waste so much time.
P
peterzhang2029 已提交
128 129 130 131 132 133
    ```

2.编写数据读取接口

自定义数据读取接口只需编写一个 Python 生成器实现**从原始输入文本中解析一条训练样本**的逻辑。以下代码片段实现了读取原始数据返回类型为: `paddle.data_type.integer_value_sub_sequence``paddle.data_type.integer_value`
```python
P
peterzhang2029 已提交
134
def train_reader(data_dir, word_dict, label_dict):
P
peterzhang2029 已提交
135 136 137 138 139 140 141 142
    """
    Reader interface for training data

    :param data_dir: data directory
    :type data_dir: str
    :param word_dict: path of word dictionary,
        the dictionary must has a "UNK" in it.
    :type word_dict: Python dict
P
peterzhang2029 已提交
143 144
    :param label_dict: path of label dictionary.
    :type label_dict: Python dict
P
peterzhang2029 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
    """

    def reader():
        UNK_ID = word_dict['<unk>']
        word_col = 1
        lbl_col = 0

        for file_name in os.listdir(data_dir):
            file_path = os.path.join(data_dir, file_name)
            if not os.path.isfile(file_path):
                continue
            with open(file_path, "r") as f:
                for line in f:
                    line_split = line.strip().split("\t")
                    doc = line_split[word_col]
                    doc_ids = []
                    for sent in doc.strip().split("."):
                        sent_ids = [
                            word_dict.get(w, UNK_ID)
                            for w in sent.split()]
                        if sent_ids:
                            doc_ids.append(sent_ids)

P
peterzhang2029 已提交
168
                    yield doc_ids, label_dict[line_split[lbl_col]]
P
peterzhang2029 已提交
169 170 171 172 173

    return reader
```
需要注意的是, 本例中以英文句号`'.'`作为分隔符, 将一段文本分隔为一定数量的句子, 且每个句子表示为对应词表的索引数组(`sent_ids`)。 由于当前样本的表示(`doc_ids`)中包含了该段文本的所有句子, 因此,它的类型为:`paddle.data_type.integer_value_sub_sequence`

P
peterzhang2029 已提交
174

P
peterzhang2029 已提交
175 176 177 178
3.指定命令行参数进行训练

`train.py`训练脚本中包含以下参数:
```
179
Options:
P
peterzhang2029 已提交
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
  --train_data_dir TEXT   The path of training dataset (default: None). If
                          this parameter is not set, imdb dataset will be
                          used.
  --test_data_dir TEXT    The path of testing dataset (default: None). If this
                          parameter is not set, imdb dataset will be used.
  --word_dict_path TEXT   The path of word dictionary (default: None). If this
                          parameter is not set, imdb dataset will be used. If
                          this parameter is set, but the file does not exist,
                          word dictionay will be built from the training data
                          automatically.
  --label_dict_path TEXT  The path of label dictionary (default: None).If this
                          parameter is not set, imdb dataset will be used. If
                          this parameter is set, but the file does not exist,
                          label dictionay will be built from the training data
                          automatically.
  --model_save_dir TEXT   The path to save the trained models (default:
                          'models').
  --help                  Show this message and exit.
P
peterzhang2029 已提交
198 199 200 201
```

修改`train.py`脚本中的启动参数,可以直接运行本例。 以`data`目录下的示例数据为例,在终端执行:
```bash
P
peterzhang2029 已提交
202 203 204 205 206
python train.py \
  --train_data_dir 'data/train_data'  \
  --test_data_dir 'data/test_data' \
  --word_dict_path 'word_dict.txt' \
  --label_dict_path 'label_dict.txt'
P
peterzhang2029 已提交
207 208 209 210 211
```
即可对样例数据进行训练。

### 预测

212
1.指定命令行参数
P
peterzhang2029 已提交
213

214 215 216 217
`infer.py`训练脚本中包含以下参数:

```
Options:
P
peterzhang2029 已提交
218 219 220 221 222 223 224 225 226 227
  --data_path TEXT        The path of data for inference (default: None). If
                          this parameter is not set, imdb test dataset will be
                          used.
  --model_path TEXT       The path of saved model.  [required]
  --word_dict_path TEXT   The path of word dictionary (default: None). If this
                          parameter is not set, imdb dataset will be used.
  --label_dict_path TEXT  The path of label dictionary (default: None).If this
                          parameter is not set, imdb dataset will be used.
  --batch_size INTEGER    The number of examples in one batch (default: 32).
  --help                  Show this message and exit.
228 229 230 231
```

2.`data`目录下的示例数据为例,在终端执行:
```bash
P
peterzhang2029 已提交
232 233 234 235 236
python infer.py \
  --data_path 'data/infer.txt' \
  --word_dict_path 'word_dict.txt' \
  --label_dict_path 'label_dict.txt' \
  --model_path 'models/params_pass_00000.tar.gz'
P
peterzhang2029 已提交
237 238
```

239
即可对样例数据进行预测。