README.md 11.2 KB
Newer Older
1 2 3 4
运行本目录下的程序示例需要使用PaddlePaddle v0.10.0 版本。如果您的PaddlePaddle安装版本低于此要求,请按照[安装文档](http://www.paddlepaddle.org/docs/develop/documentation/zh/build_and_install/pip_install_cn.html)中的说明更新PaddlePaddle安装版本。

---

W
add doc  
wwhu 已提交
5 6 7
# Scheduled Sampling

## 概述
W
wwhu 已提交
8 9 10

序列生成任务的生成目标是在给定源输入的条件下,最大化目标序列的概率。训练时该模型将目标序列中的真实元素作为解码器每一步的输入,然后最大化下一个元素的概率。生成时上一步解码得到的元素被用作当前的输入,然后生成下一个元素。可见这种情况下训练阶段和生成阶段的解码器输入数据的概率分布并不一致。

C
caoying03 已提交
11
Scheduled Sampling \[[1](#参考文献)\]是一种解决训练和生成时输入数据分布不一致的方法。在训练早期该方法主要使用目标序列中的真实元素作为解码器输入,可以将模型从随机初始化的状态快速引导至一个合理的状态。随着训练的进行,该方法会逐渐更多地使用生成的元素作为解码器输入,以解决数据分布不一致的问题。
W
wwhu 已提交
12 13

标准的序列到序列模型中,如果序列前面生成了错误的元素,后面的输入状态将会收到影响,而该误差会随着生成过程不断向后累积。Scheduled Sampling以一定概率将生成的元素作为解码器输入,这样即使前面生成错误,其训练目标仍然是最大化真实目标序列的概率,模型会朝着正确的方向进行训练。因此这种方式增加了模型的容错能力。
W
add doc  
wwhu 已提交
14 15

## 算法简介
W
wwhu 已提交
16 17 18 19 20 21 22 23 24 25 26
Scheduled Sampling主要应用在序列到序列模型的训练阶段,而生成阶段则不需要使用。

训练阶段解码器在最大化第$t$个元素概率时,标准序列到序列模型使用上一时刻的真实元素$y_{t-1}$作为输入。设上一时刻生成的元素为$g_{t-1}$,Scheduled Sampling算法会以一定概率使用$g_{t-1}$作为解码器输入。

设当前已经训练到了第$i$个mini-batch,Scheduled Sampling定义了一个概率$\epsilon_i$控制解码器的输入。$\epsilon_i$是一个随着$i$增大而衰减的变量,常见的定义方式有:

 - 线性衰减:$\epsilon_i=max(\epsilon,k-c*i)$,其中$\epsilon$限制$\epsilon_i$的最小值,$k$和$c$控制线性衰减的幅度。

 - 指数衰减:$\epsilon_i=k^i$,其中$0<k<1$,$k$控制着指数衰减的幅度。

 - 反向Sigmoid衰减:$\epsilon_i=k/(k+exp(i/k))$,其中$k>1$,$k$同样控制衰减的幅度。
W
add doc  
wwhu 已提交
27

W
wwhu 已提交
28 29 30
图1给出了这三种方式的衰减曲线,

<p align="center">
C
caoying03 已提交
31
<img src="images/decay.jpg" width="50%" align="center"><br>
W
wwhu 已提交
32 33 34 35 36 37
图1. 线性衰减、指数衰减和反向Sigmoid衰减的衰减曲线
</p>

如图2所示,在解码器的$t$时刻Scheduled Sampling以概率$\epsilon_i$使用上一时刻的真实元素$y_{t-1}$作为解码器输入,以概率$1-\epsilon_i$使用上一时刻生成的元素$g_{t-1}$作为解码器输入。从图1可知随着$i$的增大$\epsilon_i$会不断减小,解码器将不断倾向于使用生成的元素作为输入,训练阶段和生成阶段的数据分布将变得越来越一致。

<p align="center">
C
caoying03 已提交
38
<img src="images/Scheduled_Sampling.jpg" width="50%" align="center"><br>
W
wwhu 已提交
39 40
图2. Scheduled Sampling选择不同元素作为解码器输入示意图
</p>
W
add doc  
wwhu 已提交
41 42 43

## 模型实现

44
由于Scheduled Sampling是对序列到序列模型的改进,其整体实现框架与序列到序列模型较为相似。为突出本文重点,这里仅介绍与Scheduled Sampling相关的部分,完整的代码见`network_conf.py`
W
wwhu 已提交
45 46 47

首先导入需要的包,并定义控制衰减概率的类`RandomScheduleGenerator`,如下:

W
add doc  
wwhu 已提交
48 49 50 51 52 53 54 55 56
```python
import numpy as np
import math


class RandomScheduleGenerator:
    """
    The random sampling rate for scheduled sampling algoithm, which uses devcayed
    sampling rate.
W
wwhu 已提交
57

W
add doc  
wwhu 已提交
58
    """
W
wwhu 已提交
59 60
    ...
```
W
add doc  
wwhu 已提交
61

W
wwhu 已提交
62 63 64 65 66
下面将分别定义类`RandomScheduleGenerator``__init__``getScheduleRate``processBatch`三个方法。

`__init__`方法对类进行初始化,其`schedule_type`参数指定了使用哪种衰减方式,可选的方式有`constant``linear``exponential``inverse_sigmoid``constant`指对所有的mini-batch使用固定的$\epsilon_i$,`linear`指线性衰减方式,`exponential`表示指数衰减方式,`inverse_sigmoid`表示反向Sigmoid衰减。`__init__`方法的参数`a``b`表示衰减方法的参数,需要在验证集上调优。`self.schedule_computers`将衰减方式映射为计算$\epsilon_i$的函数。最后一行根据`schedule_type`将选择的衰减函数赋给`self.schedule_computer`变量。

```python
P
peterzhang2029 已提交
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
def __init__(self, schedule_type, a, b):
    """
    schduled_type: is the type of the decay. It supports constant, linear,
    exponential, and inverse_sigmoid right now.
    a: parameter of the decay (MUST BE DOUBLE)
    b: parameter of the decay (MUST BE DOUBLE)
    """
    self.schedule_type = schedule_type
    self.a = a
    self.b = b
    self.data_processed_ = 0
    self.schedule_computers = {
        "constant": lambda a, b, d: a,
        "linear": lambda a, b, d: max(a, 1 - d / b),
        "exponential": lambda a, b, d: pow(a, d / b),
        "inverse_sigmoid": lambda a, b, d: b / (b + math.exp(d * a / b)),
    }
    assert (self.schedule_type in self.schedule_computers)
    self.schedule_computer = self.schedule_computers[self.schedule_type]
W
wwhu 已提交
86
```
W
add doc  
wwhu 已提交
87

W
wwhu 已提交
88 89 90
`getScheduleRate`根据衰减函数和已经处理的数据量计算$\epsilon_i$。

```python
P
peterzhang2029 已提交
91 92 93 94 95
def getScheduleRate(self):
    """
    Get the schedule sampling rate. Usually not needed to be called by the users
    """
    return self.schedule_computer(self.a, self.b, self.data_processed_)
W
add doc  
wwhu 已提交
96

W
wwhu 已提交
97 98 99 100 101
```

`processBatch`方法根据概率值$\epsilon_i$进行采样,得到`indexes``indexes`中每个元素取值为`0`的概率为$\epsilon_i$,取值为`1`的概率为$1-\epsilon_i$。`indexes`决定了解码器的输入是真实元素还是生成的元素,取值为`0`表示使用真实元素,取值为`1`表示使用生成的元素。

```python
P
peterzhang2029 已提交
102 103 104 105 106 107 108 109 110 111 112
def processBatch(self, batch_size):
    """
    Get a batch_size of sampled indexes. These indexes can be passed to a
    MultiplexLayer to select from the grouth truth and generated samples
    from the last time step.
    """
    rate = self.getScheduleRate()
    numbers = np.random.rand(batch_size)
    indexes = (numbers >= rate).astype('int32').tolist()
    self.data_processed_ += batch_size
    return indexes
W
add doc  
wwhu 已提交
113 114
```

W
wwhu 已提交
115
Scheduled Sampling需要在序列到序列模型的基础上增加一个输入`true_token_flag`,以控制解码器输入。
W
add doc  
wwhu 已提交
116

W
wwhu 已提交
117 118 119 120 121 122 123
```python
true_token_flags = paddle.layer.data(
    name='true_token_flag',
    type=paddle.data_type.integer_value_sequence(2))
```

这里还需要对原始reader进行封装,增加`true_token_flag`的数据生成器。下面以线性衰减为例说明如何调用上面定义的`RandomScheduleGenerator`产生`true_token_flag`的输入数据。
W
add doc  
wwhu 已提交
124 125

```python
126 127 128 129
def gen_schedule_data(reader,
                      schedule_type="linear",
                      decay_a=0.75,
                      decay_b=1000000):
W
add doc  
wwhu 已提交
130 131 132 133 134 135 136 137
    """
    Creates a data reader for scheduled sampling.

    Output from the iterator that created by original reader will be
    appended with "true_token_flag" to indicate whether to use true token.

    :param reader: the original reader.
    :type reader: callable
138 139 140 141 142 143
    :param schedule_type: the type of sampling rate decay.
    :type schedule_type: str
    :param decay_a: the decay parameter a.
    :type decay_a: float
    :param decay_b: the decay parameter b.
    :type decay_b: float
W
add doc  
wwhu 已提交
144 145 146 147

    :return: the new reader with the field "true_token_flag".
    :rtype: callable
    """
148
    schedule_generator = RandomScheduleGenerator(schedule_type, decay_a, decay_b)
W
add doc  
wwhu 已提交
149 150 151 152 153 154 155 156 157

    def data_reader():
        for src_ids, trg_ids, trg_ids_next in reader():
            yield src_ids, trg_ids, trg_ids_next, \
                  [0] + schedule_generator.processBatch(len(trg_ids) - 1)

    return data_reader
```

W
wwhu 已提交
158 159 160
这段代码在原始输入数据(即源序列元素`src_ids`、目标序列元素`trg_ids`和目标序列下一个元素`trg_ids_next`)后追加了控制解码器输入的数据。由于解码器第一个元素是序列开始符,因此将追加的数据第一个元素设置为`0`,表示解码器第一步始终使用真实目标序列的第一个元素(即序列开始符)。

训练时`recurrent_group`每一步调用的解码器函数如下:
W
add doc  
wwhu 已提交
161 162

```python
P
peterzhang2029 已提交
163
def gru_decoder_with_attention_train(enc_vec, enc_proj, true_word,
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
                                       true_token_flag):
      """
      The decoder step for training.
      :param enc_vec: the encoder vector for attention
      :type enc_vec: LayerOutput
      :param enc_proj: the encoder projection for attention
      :type enc_proj: LayerOutput
      :param true_word: the ground-truth target word
      :type true_word: LayerOutput
      :param true_token_flag: the flag of using the ground-truth target word
      :type true_token_flag: LayerOutput
      :return: the softmax output layer
      :rtype: LayerOutput
      """

      decoder_mem = paddle.layer.memory(
          name='gru_decoder', size=decoder_size, boot_layer=decoder_boot)

      context = paddle.networks.simple_attention(
          encoded_sequence=enc_vec,
          encoded_proj=enc_proj,
          decoder_state=decoder_mem)

      gru_out_memory = paddle.layer.memory(
          name='gru_out', size=target_dict_dim)

      generated_word = paddle.layer.max_id(input=gru_out_memory)

      generated_word_emb = paddle.layer.embedding(
          input=generated_word,
          size=word_vector_dim,
          param_attr=paddle.attr.ParamAttr(name='_target_language_embedding'))

      current_word = paddle.layer.multiplex(
          input=[true_token_flag, true_word, generated_word_emb])

      decoder_inputs = paddle.layer.fc(
          input=[context, current_word],
          size=decoder_size * 3,
          act=paddle.activation.Linear(),
          bias_attr=False)

      gru_step = paddle.layer.gru_step(
          name='gru_decoder',
          input=decoder_inputs,
          output_mem=decoder_mem,
          size=decoder_size)

      out = paddle.layer.fc(
          name='gru_out',
          input=gru_step,
          size=target_dict_dim,
          act=paddle.activation.Softmax())
      return out
W
add doc  
wwhu 已提交
218 219
```

W
wwhu 已提交
220 221 222
该函数使用`memory``gru_out_memory`记忆上一时刻生成的元素,根据`gru_out_memory`选择概率最大的词语`generated_word`作为生成的词语。`multiplex`层会在真实元素`true_word`和生成的元素`generated_word`之间做出选择,并将选择的结果作为解码器输入。`multiplex`层使用了三个输入,分别为`true_token_flag``true_word``generated_word_emb`。对于这三个输入中每个元素,若`true_token_flag`中的值为`0`,则`multiplex`层输出`true_word`中的相应元素;若`true_token_flag`中的值为`1`,则`multiplex`层输出`generated_word_emb`中的相应元素。

## 参考文献
W
add doc  
wwhu 已提交
223

W
wwhu 已提交
224
[1] Bengio S, Vinyals O, Jaitly N, et al. [Scheduled sampling for sequence prediction with recurrent neural networks](http://papers.nips.cc/paper/5956-scheduled-sampling-for-sequence-prediction-with-recurrent-neural-networks)//Advances in Neural Information Processing Systems. 2015: 1171-1179.