index.html 11.5 KB
Newer Older
C
caoying03 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 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

<html>
<head>
  <script type="text/x-mathjax-config">
  MathJax.Hub.Config({
    extensions: ["tex2jax.js", "TeX/AMSsymbols.js", "TeX/AMSmath.js"],
    jax: ["input/TeX", "output/HTML-CSS"],
    tex2jax: {
      inlineMath: [ ['$','$'] ],
      displayMath: [ ['$$','$$'] ],
      processEscapes: true
    },
    "HTML-CSS": { availableFonts: ["TeX"] }
  });
  </script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js" async></script>
  <script type="text/javascript" src="../.tools/theme/marked.js">
  </script>
  <link href="http://cdn.bootcss.com/highlight.js/9.9.0/styles/darcula.min.css" rel="stylesheet">
  <script src="http://cdn.bootcss.com/highlight.js/9.9.0/highlight.min.js"></script>
  <link href="http://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/perfect-scrollbar/0.6.14/css/perfect-scrollbar.min.css" rel="stylesheet">
  <link href="../.tools/theme/github-markdown.css" rel='stylesheet'>
</head>
<style type="text/css" >
.markdown-body {
    box-sizing: border-box;
    min-width: 200px;
    max-width: 980px;
    margin: 0 auto;
    padding: 45px;
}
</style>


<body>

<div id="context" class="container-fluid markdown-body">
</div>

<!-- This block will be replaced by each markdown file content. Please do not change lines below.-->
<div id="markdown" style='display:none'>
# 使用循环神经网语言模型生成文本

语言模型(Language Model)是一个概率分布模型,简单来说,就是用来计算一个句子的概率的模型。利用它可以确定哪个词序列的可能性更大,或者给定若干个词,可以预测下一个最可能出现的词。语言模型是自然语言处理领域里一个重要的基础模型。

## 应用场景
**语言模型被应用在很多领域**,如:

* **自动写作**:语言模型可以根据上文生成下一个词,递归下去可以生成整个句子、段落、篇章。
* **QA**:语言模型可以根据Question生成Answer。
* **机器翻译**:当前主流的机器翻译模型大多基于Encoder-Decoder模式,其中Decoder就是一个待条件的语言模型,用来生成目标语言。
* **拼写检查**:语言模型可以计算出词序列的概率,一般在拼写错误处序列的概率会骤减,可以用来识别拼写错误并提供改正候选集。
* **词性标注、句法分析、语音识别......**

## 关于本例
本例实现基于RNN的语言模型,以及利用语言模型生成文本,本例的目录结构如下:

```text
.
├── data
│   └── train_data_examples.txt        # 示例数据,可参考示例数据的格式,提供自己的数据
├── config.py    # 配置文件,包括data、train、infer相关配置
├── generate.py  # 预测任务脚本,即生成文本
├── beam_search.py    # beam search 算法实现
├── network_conf.py   # 本例中涉及的各种网络结构均定义在此文件中,希望进一步修改模型结构,请修改此文件
├── reader.py    # 读取数据接口
├── README.md
├── train.py    # 训练任务脚本
└── utils.py    # 定义通用的函数,例如:构建字典、加载字典等
```

## RNN 语言模型
### 简介

RNN是一个序列模型,基本思路是:在时刻$t$,将前一时刻$t-1$的隐藏层输出和$t$时刻的词向量一起输入到隐藏层从而得到时刻$t$的特征表示,然后用这个特征表示得到t时刻的预测输出,如此在时间维上递归下去。可以看出RNN善于使用上文信息、历史知识,具有“记忆”功能。理论上RNN能实现“长依赖”(即利用很久之前的知识),但在实际应用中发现效果并不理想,于是出现了很多RNN的变种,如常用的LSTM和GRU,它们对传统RNN的cell进行了改进,弥补了传统RNN的不足,本例中即使用了LSTM、GRU。下图是RNN(广义上包含了LSTM、GRU等)语言模型“循环”思想的示意图:

<p align=center><img src='images/rnn.png' width='500px'/></p>

### 模型实现

本例中RNN语言模型的实现简介如下:

- **定义模型参数**:`config.py`中定义了模型的参数变量。
- **定义模型结构**:`network_conf.py`中的`rnn_lm`**函数**中定义了模型的**结构**,如下:
    - 输入层:将输入的词(或字)序列映射成向量,即词向量层: `embedding`。
    - 中间层:根据配置实现RNN层,将上一步得到的`embedding`向量序列作为输入。
    - 输出层:使用`softmax`归一化计算单词的概率。
    - loss:定义多类交叉熵作为模型的损失函数。
- **训练模型**:`train.py`中的`main`方法实现了模型的训练,实现流程如下:
    - 准备输入数据:建立并保存词典、构建train和test数据的reader。
    - 初始化模型:包括模型的结构、参数。
    - 构建训练器:demo中使用的是Adam优化算法。
    - 定义回调函数:构建`event_handler`来跟踪训练过程中loss的变化,并在每轮训练结束时保存模型的参数。
    - 训练:使用trainer训练模型。

- **生成文本**:`generate.py` 实现了文本的生成,实现流程如下:
    - 加载训练好的模型和词典文件。
    - 读取`gen_file`文件,每行是一个句子的前缀,用[柱搜索算法(Beam Search)](https://github.com/PaddlePaddle/book/blob/develop/08.machine_translation/README.cn.md#柱搜索算法)根据前缀生成文本。
    - 将生成的文本及其前缀保存到文件`gen_result`。

## 使用说明

运行本例的方法如下:

* 1,运行`python train.py`命令,开始train模型(默认使用RNN),待训练结束。
* 2,运行`python generate.py`运行文本生成。(输入的文本默认为`data/train_data_examples.txt`,生成的文本默认保存到`data/gen_result.txt`中。)


**如果需要使用自己的语料、定制模型,需要修改的地方主要是`语料`和`config.py`中的配置,需要注意的细节和适配工作详情如下:**


### 语料适配

* 清洗语料:去除原文中空格、tab、乱码,按需去除数字、标点符号、特殊符号等。
* 内容格式:每个句子占一行;每行中的各词之间使用一个空格符分开。
* 按需要配置`config.py`中的如下参数:

    ```python  
     train_file = "data/train_data_examples.txt"
     test_file = ""

     vocab_file = "data/word_vocab.txt"
     model_save_dir = "models"
    ```
    1. `train_file`:指定训练数据的路径,**需要预先分词**。
    2. `test_file`:指定测试数据的路径,如果训练数据不为空,将在每个 `pass` 训练结束对指定的测试数据进行测试。
    3. `vocab_file`:指定字典的路径,如果字典文件不存在,将会对训练语料进行词频统计,构建字典。
    4. `model_save_dir`:指定模型保存的路径,如果指定的文件夹不存在,将会自动创建。

### 构建字典的策略
- 当指定的字典文件不存在时,将对训练数据进行词频统计,自动构建字典`config.py` 中有如下两个参数与构建字典有关:

    ```python
    max_word_num = 51200 - 2
    cutoff_word_fre = 0
    ```
    1. `max_word_num`:指定字典中含有多少个词。
    2. `cutoff_word_fre`:字典中词语在训练语料中出现的最低频率。
- 加入指定了 `max_word_num = 5000`,并且 `cutoff_word_fre = 10`,词频统计发现训练语料中出现频率高于10次的词语仅有3000个,那么最终会取3000个词构成词典。
- 构建词典时,会自动加入两个特殊符号:
    1. `<unk>`:不出现在字典中的词
    2. `<e>`:句子的结束符

    *注:需要注意的是,词典越大生成的内容越丰富,但训练耗时越久。一般中文分词之后,语料中不同的词能有几万乃至几十万,如果`max_word_num`取值过小则导致`<unk>`占比过高,如果`max_word_num`取值较大,则严重影响训练速度(对精度也有影响)。所以,也有“按字”训练模型的方式,即:把每个汉字当做一个词,常用汉字也就几千个,使得字典的大小不会太大、不会丢失太多信息,但汉语中同一个字在不同词中语义相差很大,有时导致模型效果不理想。建议多试试、根据实际情况选择是“按词训练”还是“按字训练”。*

### 模型适配、训练

* 按需调整`config.py`中如下配置,来修改 rnn 语言模型的网络结果:

    ```python
    rnn_type = "lstm"  # "gru" or "lstm"
    emb_dim = 256
    hidden_size = 256
    stacked_rnn_num = 2
    ```
    1. `rnn_type`:支持 ”gru“ 或者 ”lstm“ 两种参数,选择使用何种 RNN 单元。
    2. `emb_dim`:设置词向量的维度。
    3. `hidden_size`:设置 RNN 单元隐层大小。
    4. `stacked_rnn_num`:设置堆叠 RNN 单元的个数,构成一个更深的模型。

* 运行`python train.py`命令训练模型,模型将被保存到`model_save_dir`指定的目录。

### 按需生成文本

* 按需调整`config.py`中以下变量,详解如下:

    ```python
    gen_file = "data/train_data_examples.txt"
    gen_result = "data/gen_result.txt"
    max_gen_len = 25  # the max number of words to generate
    beam_size = 5
    model_path = "models/rnn_lm_pass_00000.tar.gz"
    ```
    1. `gen_file`:指定输入数据文件,每行是一个句子的前缀,**需要预先分词**。
    2. `gen_result`:指定输出文件路径,生成结果将写入此文件。
    3. `max_gen_len`:指定每一句生成的话最长长度,如果模型无法生成出`<e>`,当生成 `max_gen_len` 个词语后,生成过程会自动终止。
    4. `beam_size`:Beam Search 算法每一步的展开宽度
    5. `model_path`:指定训练好的模型的路径。

    其中,`gen_file` 中保存的是待生成的文本前缀,每个前缀占一行,形如:

    ```text
    若隐若现 地像 幽灵 , 像 死神
    ```
    将需要生成的文本前缀按此格式存入文件即可;

* 运行`python generate.py`命令运行beam search 算法为输入前缀生成文本,下面是模型生成的结果:

    ```text
    81    若隐若现 地像 幽灵 , 像 死神
    -12.2542    一样 。 他 是 个 怪物 <e>
    -12.6889    一样 。 他 是 个 英雄 <e>
    -13.9877    一样 。 他 是 我 的 敌人 <e>
    -14.2741    一样 。 他 是 我 的 <e>
    -14.6250    一样 。 他 是 我 的 朋友 <e>
    ```
    其中:
    1. 第一行 `81    若隐若现 地像 幽灵 , 像 死神`以`\t`为分隔,共有两列:
        - 第一列是输入前缀在训练样本集中的序号
        - 第二列是输入的前缀。
    2. 第二 ~ `beam_size + 1` 行是生成结果,同样以 `\t` 分隔为两列:
        - 第一列是该生成序列的对数概率(log probability)
        - 第二列是生成的文本序列,正常的生成结果会以符号`<e>`结尾,如果没有以`<e>`结尾,意味着超过了最大序列长度,生成强制终止

</div>
<!-- You can change the lines below now. -->

<script type="text/javascript">
marked.setOptions({
  renderer: new marked.Renderer(),
  gfm: true,
  breaks: false,
  smartypants: true,
  highlight: function(code, lang) {
    code = code.replace(/&amp;/g, "&")
    code = code.replace(/&gt;/g, ">")
    code = code.replace(/&lt;/g, "<")
    code = code.replace(/&nbsp;/g, " ")
    return hljs.highlightAuto(code, [lang]).value;
  }
});
document.getElementById("context").innerHTML = marked(
        document.getElementById("markdown").innerHTML)
</script>
</body>