|
| 1 | +# 神经机器翻译模型 |
| 2 | +## 背景介绍 |
| 3 | + |
| 4 | +机器翻译利用计算机将源语言的表达转换成目标语言的同义表达,是自然语言处理中非常重要的研究方向。机器翻译有着广泛的应用需求,其实现方式也经历了不断的演化。传统的机器翻译方法主要基于规则或统计模型,需要人为地指定翻译规则或设计语言特征,效果依赖于人对源语言与目标语言的理解程度。近些年来,深度学习的提出与迅速发展使得特征的自动学习成为了可能。深度学习首先在图像识别和语音识别中取得成功,进而在机器翻译等自然语言处理领域中掀起了研究热潮。机器翻译中的深度学习模型直接学习源语言到目标语言的映射,大为减少了学习过程中人的介入,同时显著地提高了翻译质量。本教程主要介绍的就是在PaddlePaddle中如何利用循环神经网络(RNN),构建一个端到端(End-to-End)的神经机器翻译(Neural Machine Translation)模型。 |
| 5 | + |
| 6 | +## 模型概览 |
| 7 | + |
| 8 | + |
| 9 | +基于RNN的机器翻译模型常见的是一个编码器-解码器(Encoder-Decoder)结构,其中的编码器和解码器均是一个循环神经网络。如果将构成编码器和解码器的两个 RNN 在时间上展开,可以得到如下的模型结构图 |
| 10 | + |
| 11 | +<p align="center"><img src="images/Encoder-Decoder.png" width = "90%" align="center"/><br/>图 1. 编码器-解码器框架 </p> |
| 12 | + |
| 13 | +该翻译模型输入输出的基本单位可以是字符,也可以是词或者短语。不失一般性,下面以基于词的模型为例说明编码器/解码器的工作机制: |
| 14 | + |
| 15 | +- **编码器**:将源语言句子编码成一个向量,作为解码器的输入。解码器的原始输入是表示词的 id 序列 $w = {w_1, w_2, ..., w_T}$,用独热码(One-hot encoding)表示。为了对输入进行降维,同时建立词语之间的语义关联,模型为热独码表示的单词学习一个词嵌入(Word Embedding)表示,也就是常说的词向量,关于词向量的详细介绍请参考PaddleBook的[词向量](https://github.com/lcy-seso/book/tree/develop/04.word2vec)一章。最后 RNN 单元逐个词地处理输入,得到完整句子的编码向量。 |
| 16 | + |
| 17 | +- **解码器**:接受编码器的输入,逐个词地解码出目标语言序列 $u = {u_1, u_2, ..., u_{T'}}$。每个时间步, RNN 单元输出一个隐藏向量,之后经 `Softmax` 归一化计算出下一个目标词的条件概率,即 $P(u_i | w, u_1, u_2, ..., u_{t-1})$。因此,给定输入 $w$,其对应的翻译结果为 $u$ 的概率则为 |
| 18 | + |
| 19 | +$$ P(u_1,u_2,...,u_{T'} | w) = \prod_{t=1}^{t={T'}}p(u_t|w, u_1, u_2, u_{t-1})$$ |
| 20 | + |
| 21 | +以中文到英文的翻译为例,对于如下分词后的句子 |
| 22 | + |
| 23 | +``` |
| 24 | +祝愿 祖国 繁荣 昌盛 |
| 25 | +``` |
| 26 | + |
| 27 | +其对应的英文翻译正确结果应该是 |
| 28 | + |
| 29 | +``` |
| 30 | +Wish motherland rich and powerful |
| 31 | +``` |
| 32 | + |
| 33 | +在预处理阶段,准备源语言与目标语言之间互译的平行语料数据,并分别构建源语言和目标语言的词典;在训练阶段,用这样成对的语料训练模型;而在模型测试阶段,如上的英文句子则是期望看到的翻译结果。 |
| 34 | + |
| 35 | +### RNN 单元 |
| 36 | +构成RNN的单元可以为基本单元,即用一个向量存储RNN的隐状态,然而基于这种结构的RNN在训练时容易发生梯度弥散(gradient vanishing),对于长时间的依赖关系难以建模。因此通常所用的RNN单元是GRU或者LSTM,这两种单元以门限来控制应该记住的和遗忘的信息,解决了序列数据的的长时依赖问题。以本教程所用的GRU为例,其基本结构如下 |
| 37 | + |
| 38 | +<p align="center"> |
| 39 | +<img src="images/gru.png" width = "90%" align="center"/><br/> |
| 40 | +图 2. GRU 单元 |
| 41 | + </p> |
| 42 | + |
| 43 | +可以看到除了隐含状态以外,GRU内部还包含了两个门限:更新门(Update Gate)、重置门(Reset Gate)。在每一个时间步,门限和隐状态的更新由图2右侧的公式决定。这两个门限决定了状态以何种方式更新。 |
| 44 | + |
| 45 | +### 双向编码器 |
| 46 | +在上述的基本模型中,编码器在顺序处理输入句子序列时,当前时刻的状态只包含了历史输入信息,而没有未来时刻的序列信息。而对于序列建模,未来时刻的上下文同样包含了重要的信息。因此我们可以采用一个双向的编码器来同时获取当前时刻输入的上下文,其结构如下图所示 |
| 47 | +<p align="center"> |
| 48 | +<img src="images/bidirectional-encoder.png" width = "90%" align="center"/><br/> |
| 49 | +图 3. 双向编码器 |
| 50 | + </p> |
| 51 | + |
| 52 | +该双向编码器\[[1](#参考文献)\]由两个独立的编码器构成,分别从前向和后向对输入序列进行编码,然后将两个编码器的输出合并在一起,作为最终的编码输出。 |
| 53 | +在PaddlePaddle中,双向编码器可以很方便地调用相关 APIs 实现: |
| 54 | + |
| 55 | +```python |
| 56 | +#### Encoder |
| 57 | +src_word_id = paddle.layer.data( |
| 58 | + name='source_language_word', |
| 59 | + type=paddle.data_type.integer_value_sequence(source_dict_dim)) |
| 60 | +# source embedding |
| 61 | +src_embedding = paddle.layer.embedding( |
| 62 | + input=src_word_id, size=word_vector_dim) |
| 63 | +# use bidirectional_gru |
| 64 | +encoded_vector = paddle.networks.bidirectional_gru( |
| 65 | + input=src_embedding, |
| 66 | + size=encoder_size, |
| 67 | + fwd_act=paddle.activation.Tanh(), |
| 68 | + fwd_gate_act=paddle.activation.Sigmoid(), |
| 69 | + bwd_act=paddle.activation.Tanh(), |
| 70 | + bwd_gate_act=paddle.activation.Sigmoid(), |
| 71 | + return_seq=True) |
| 72 | +``` |
| 73 | + |
| 74 | +### 柱搜索(Beam Search) 算法 |
| 75 | +在完成训练后的测试阶段,模型根据输入的不同,输出对应的翻译结果。解码时,一个直接的方式是取每一步条件概率最大的词,作为当前时刻的输出。但局部最优并不一定能得到全局最优,即这种做法并不能保证最后得到的完整句子出现的概率最大。如果对全空间进行搜索,其代价又过大。为了解决这个问题,通常采用柱搜索(Beam Search)算法。柱搜索是一种启发式的图搜索算法,用一个参数`k`控制搜索宽度,其要点如下: |
| 76 | + |
| 77 | +**1.** 在解码的过程中,始终维护`k`个已解码出的子序列; |
| 78 | + |
| 79 | +**2.** 在中间时刻`t`, 这`k`个子序列与所有词组合,取这些组合序列中概率最大的`k`个以更新原来的子序列; |
| 80 | + |
| 81 | +**3.** 不断迭代下去,直至得到`k`个完整的句子,作为翻译结果的候选。 |
| 82 | + |
| 83 | +关于柱搜索的更多解释,可以参考 PaddleBook 中[机器翻译](https://github.com/lcy-seso/book/tree/develop/08.machine_translation)一章中[柱搜索](https://github.com/lcy-seso/book/tree/develop/08.machine_translation#柱搜索算法)的一节。 |
| 84 | + |
| 85 | + |
| 86 | +### 无注意力机制的解码器 |
| 87 | +PaddleBook中机器翻译的一章中,已介绍了带注意力机制(Attention Mechanism)的encoder-decoder结构,这里介绍不带注意力机制的encoder-decoder结构。在PaddlePaddle中,无Attention的解码器单步逻辑`gru_decoder_without_attention`可以按如下的方式实现: |
| 88 | + |
| 89 | +```python |
| 90 | +#### Decoder |
| 91 | +encoder_last = paddle.layer.last_seq(input=encoded_vector) |
| 92 | +with paddle.layer.mixed( |
| 93 | + size=decoder_size, |
| 94 | + act=paddle.activation.Tanh()) as encoder_last_projected: |
| 95 | + encoder_last_projected += paddle.layer.full_matrix_projection( |
| 96 | + input=encoder_last) |
| 97 | +# gru step |
| 98 | +def gru_decoder_without_attention(enc_vec, current_word): |
| 99 | + |
| 100 | + decoder_mem = paddle.layer.memory( |
| 101 | + name='gru_decoder', |
| 102 | + size=decoder_size, |
| 103 | + boot_layer=encoder_last_projected) |
| 104 | + |
| 105 | + context = paddle.layer.last_seq(input=enc_vec) |
| 106 | + |
| 107 | + with paddle.layer.mixed(size=decoder_size * 3) as decoder_inputs: |
| 108 | + decoder_inputs +=paddle.layer.full_matrix_projection(input=context) |
| 109 | + decoder_inputs += paddle.layer.full_matrix_projection( |
| 110 | + input=current_word) |
| 111 | + |
| 112 | + gru_step = paddle.layer.gru_step( |
| 113 | + name='gru_decoder', |
| 114 | + act=paddle.activation.Tanh(), |
| 115 | + gate_act=paddle.activation.Sigmoid(), |
| 116 | + input=decoder_inputs, |
| 117 | + output_mem=decoder_mem, |
| 118 | + size=decoder_size) |
| 119 | + |
| 120 | + with paddle.layer.mixed( |
| 121 | + size=target_dict_dim, |
| 122 | + bias_attr=True, |
| 123 | + act=paddle.activation.Softmax()) as out: |
| 124 | + out += paddle.layer.full_matrix_projection(input=gru_step) |
| 125 | + return out |
| 126 | +``` |
| 127 | + |
| 128 | +在模型训练和测试阶段,解码器的行为有很大的不同: |
| 129 | + |
| 130 | +- **训练阶段:**目标翻译结果的词嵌入`trg_embedding`作为参数传递给单步逻辑`gru_decoder_without_attention()`,函数`recurrent_group()`循环调用单步逻辑执行,最后计算目标结果与实际结果的差异cost并返回; |
| 131 | +- **测试阶段:**解码器根据最后一个生成的词预测下一个词,`GeneratedInputV2()`自动生成最后一个词的词嵌入并传递给单步逻辑,`beam_search()`完成集束搜索作为结果返回。 |
| 132 | + |
| 133 | +这两部分的逻辑分别实现在如下的`if-else`条件分支中: |
| 134 | + |
| 135 | +```python |
| 136 | +decoder_group_name = "decoder_group" |
| 137 | +group_input1 = paddle.layer.StaticInputV2(input=encoded_vector, is_seq=True) |
| 138 | +group_inputs = [group_input1] |
| 139 | +if not generating: |
| 140 | + trg_embedding = paddle.layer.embedding( |
| 141 | + input=paddle.layer.data( |
| 142 | + name='target_language_word', |
| 143 | + type=paddle.data_type.integer_value_sequence(target_dict_dim)), |
| 144 | + size=word_vector_dim, |
| 145 | + param_attr=paddle.attr.ParamAttr(name='_target_language_embedding')) |
| 146 | + group_inputs.append(trg_embedding) |
| 147 | + |
| 148 | + decoder = paddle.layer.recurrent_group( |
| 149 | + name=decoder_group_name, |
| 150 | + step=gru_decoder_without_attention, |
| 151 | + input=group_inputs) |
| 152 | + |
| 153 | + lbl = paddle.layer.data( |
| 154 | + name='target_language_next_word', |
| 155 | + type=paddle.data_type.integer_value_sequence(target_dict_dim)) |
| 156 | + cost = paddle.layer.classification_cost(input=decoder, label=lbl) |
| 157 | + |
| 158 | + return cost |
| 159 | +else: |
| 160 | + |
| 161 | + trg_embedding = paddle.layer.GeneratedInputV2( |
| 162 | + size=target_dict_dim, |
| 163 | + embedding_name='_target_language_embedding', |
| 164 | + embedding_size=word_vector_dim) |
| 165 | + group_inputs.append(trg_embedding) |
| 166 | + |
| 167 | + beam_gen = paddle.layer.beam_search( |
| 168 | + name=decoder_group_name, |
| 169 | + step=gru_decoder_without_attention, |
| 170 | + input=group_inputs, |
| 171 | + bos_id=0, |
| 172 | + eos_id=1, |
| 173 | + beam_size=beam_size, |
| 174 | + max_length=max_length) |
| 175 | + |
| 176 | + return beam_gen |
| 177 | +``` |
| 178 | + |
| 179 | +## 数据准备 |
| 180 | +本教程所用到的数据来自[WMT14](http://www-lium.univ-lemans.fr/~schwenk/cslm_joint_paper/),该数据集是法文到英文互译的平行语料数据。用[bitexts](http://www-lium.univ-lemans.fr/~schwenk/cslm_joint_paper/data/bitexts.tgz)作为训练数据,[dev+test data](http://www-lium.univ-lemans.fr/~schwenk/cslm_joint_paper/data/dev+test.tgz)作为验证与测试数据。在PaddlePaddle中已经封装好了该数据集的读取接口,在首次运行的时候,程序会自动完成下载,用户无需手动完成相关的数据准备。 |
| 181 | + |
| 182 | +## 模型的训练与测试 |
| 183 | + |
| 184 | +在定义好网络结构后,我们就可以进行模型训练与测试了。模型的训练与测试分别由`main()`函数调用`train()`和`generate()`完成: |
| 185 | + |
| 186 | +- **train():** 依次完成网络的构建、data reader的定义、trainer的建议和事件句柄的定义,最后训练网络; |
| 187 | + |
| 188 | +- **generate():**: 根据指定的模型路径初始化网络,导入测试数据并由`beam_search`完成翻译过程。 |
| 189 | + |
| 190 | +### 模型训练 |
| 191 | + |
| 192 | +进行模型训练十分简单,只需在命令行窗口中执行 |
| 193 | + |
| 194 | +``` |
| 195 | +python nmt_without_attention_v2.py --train |
| 196 | +``` |
| 197 | + |
| 198 | +输出样例为 |
| 199 | + |
| 200 | +``` |
| 201 | +Pass 0, Batch 0, Cost 267.674663, {'classification_error_evaluator': 1.0} |
| 202 | +......... |
| 203 | +Pass 0, Batch 10, Cost 172.892294, {'classification_error_evaluator': 0.953895092010498} |
| 204 | +......... |
| 205 | +Pass 0, Batch 20, Cost 177.989329, {'classification_error_evaluator': 0.9052488207817078} |
| 206 | +......... |
| 207 | +Pass 0, Batch 30, Cost 153.633665, {'classification_error_evaluator': 0.8643803596496582} |
| 208 | +......... |
| 209 | +Pass 0, Batch 40, Cost 168.170543, {'classification_error_evaluator': 0.8348183631896973} |
| 210 | +``` |
| 211 | + |
| 212 | + |
| 213 | +### 模型测试 |
| 214 | +模型测试的执行与模型训练类似,只需执行 |
| 215 | + |
| 216 | +``` |
| 217 | +python nmt_without_attention_v2.py --generate |
| 218 | +``` |
| 219 | +则自动为测试数据生成了对应的翻译结果。 |
| 220 | +如果设置beam search的大小为3,输入法文句子 |
| 221 | + |
| 222 | +``` |
| 223 | +src: <s> Elles connaissent leur entreprise mieux que personne . <e> |
| 224 | +``` |
| 225 | + |
| 226 | +对应的英文翻译结果为, |
| 227 | + |
| 228 | +``` |
| 229 | +prob = 0.000000: They know their business better than anyone . <e> |
| 230 | +prob = 0.000014: They know their businesses better than anyone . <e> |
| 231 | +prob = 0.000000: They know their business better than anybody . <e> |
| 232 | +``` |
| 233 | + |
| 234 | +* `prob`表示生成句子的得分,随之其后则是翻译生成的句子; |
| 235 | +* `<s>` 表示句子的开始,`<e>`表示一个句子的结束,如果出现了在词典中未包含的词,则用`<unk>`替代。 |
| 236 | + |
| 237 | +至此,我们在PaddlePaddle上实现了一个初步的机器翻译模型。我们可以看到,PaddlePaddle提供了灵活丰富的API供选择和使用,使得我们能够很方便完成各种复杂网络的配置。机器翻译本身也是个快速发展的领域,各种新方法新思想在不断涌现。在学习完本教程后,读者若有兴趣和余力,可基于PaddlePaddle平台实现更为复杂、性能更优的机器翻译模型。 |
| 238 | + |
| 239 | + |
| 240 | +## 参考文献 |
| 241 | +[1]Bahdanau D, Cho K, Bengio Y. [Neural machine translation by jointly learning to align and translate](https://arxiv.org/abs/1409.0473)[C]. Proceedings of ICLR 2015, 2015 |
0 commit comments