【序列到序列学习】无注意力机制的神经机器翻译

导语

PaddlePaddle提供了丰富的运算单元,帮助大家以模块化的方式构建起千变万化的深度学习模型来解决不同的应用问题。这里,我们针对常见的机器学习任务,提供了不同的神经网络模型供大家学习和使用。本周推文目录如下:

3.12:【命名实体识别】

训练端到端的序列标注模型

3.13:【序列到序列学习】

无注意力机制的神经机器翻译

3.14:【序列到序列学习】

使用Scheduled Sampling改善翻译质量

3.15:【序列到序列学习】

带外部记忆机制的神经机器翻译

3.16:【序列到序列学习】

 生成古诗词

序列到序列学习实现两个甚至是多个不定长模型之间的映射,有着广泛的应用,包括:机器翻译、智能对话与问答、广告创意语料生成、自动编码(如金融画像编码)、判断多个文本串之间的语义相关性等。

在序列到序列学习任务中,我们首先以机器翻译任务为例,提供了多种改进模型供大家学习和使用。包括:不带注意力机制的序列到序列映射模型,这一模型是所有序列到序列学习模型的基础;使用Scheduled Sampling改善RNN模型在生成任务中的错误累积问题;带外部记忆机制的神经机器翻译,通过增强神经网络的记忆能力,来完成复杂的序列到序列学习任务。除机器翻译任务之外,我们也提供了一个基于深层LSTM网络生成古诗词,实现同语言生成的模型。

 【序列到序列学习】 

01

无注意力机制的神经机器翻译

|1. 背景介绍

机器翻译利用计算机将源语言转换成目标语言的同义表达,是自然语言处理中重要的研究方向,有着广泛的应用需求,其实现方式也经历了不断地演化。传统机器翻译方法主要基于规则或统计模型,需要人为地指定翻译规则或设计语言特征,效果依赖于人对源语言与目标语言的理解程度。近些年来,深度学习的提出与迅速发展使得特征的自动学习成为可能。深度学习首先在图像识别和语音识别中取得成功,进而在机器翻译等自然语言处理领域中掀起了研究热潮。机器翻译中的深度学习模型直接学习源语言到目标语言的映射,大为减少了学习过程中人的介入,同时显著地提高了翻译质量。本例介绍在PaddlePaddle中如何利用循环神经网络(Recurrent Neural Network, RNN)构建一个端到端(End-to-End)的神经网络机器翻译(Neural Machine Translation, NMT)模型。

|2. 模型概览

基于 RNN 的神经网络机器翻译模型遵循编码器-解码器结构,其中的编码器和解码器均是一个循环神经网络。将构成编码器和解码器的两个 RNN 沿时间步展开,得到如下的模型结构图:

图 1. 编码器-解码器框架

神经机器翻译模型的输入输出可以是字符,也可以是词或者短语。不失一般性,本例以基于词的模型为例说明编码器/解码器的工作机制:

  • 编码器:将源语言句子编码成一个向量,作为解码器的输入。解码器的原始输入是表示词的 id 序列 w=w1,w2,...,wT,用独热(One-hot)码表示。为了对输入进行降维,同时建立词语之间的语义关联,模型为热独码表示的单词学习一个词嵌入(Word Embedding)表示,也就是常说的词向量,关于词向量的详细介绍请参考 PaddleBook 的词向量一章。最后 RNN 单元逐个词地处理输入,得到完整句子的编码向量。

  • 解码器:接受编码器的输入,逐个词地解码出目标语言序列 u=u1,u2,...,uT′。每个时间步,RNN 单元输出一个隐藏向量,之后经 Softmax 归一化计算出下一个目标词的条件概率,即 P(ui|w,u1,u2,...,ut−1)。因此,给定输入 w,其对应的翻译结果为 u 的概率则为:

以中文到英文的翻译为例,源语言是中文,目标语言是英文。下面是一句源语言分词后的句子

祝愿 祖国 繁荣 昌盛

对应的目标语言英文翻译结果为:

Wish motherland rich and powerful

在预处理阶段,准备源语言与目标语言互译的平行语料数据,并分别构建源语言和目标语言的词典;在训练阶段,用这样成对的平行语料训练模型;在模型测试阶段,输入中文句子,模型自动生成对应的英语翻译,然后将生成结果与标准翻译对比进行评估。在机器翻译领域,BLEU 是最流行的自动评估指标之一。

A.RNN 单元

RNN 的原始结构用一个向量来存储隐状态,然而这种结构的 RNN 在训练时容易发生梯度弥散(gradient vanishing),对于长时间的依赖关系难以建模。因此人们对 RNN 单元进行了改进,提出了 LSTM[1] 和 GRU[2],这两种单元以门来控制应该记住的和遗忘的信息,较好地解决了序列数据的长时依赖问题。以本例所用的 GRU 为例,其基本结构如下:

图 2. GRU 单元

可以看到除了隐含状态以外,GRU 内部还包含了两个门:更新门(Update Gate)、重置门(Reset Gate)。在每一个时间步,门限和隐状态的更新由图 2 右侧的公式决定。这两个门限决定了状态以何种方式更新。

B.    双向编码器

在上述的基本模型中,编码器在顺序处理输入句子序列时,当前时刻的状态只包含了历史输入信息,而没有未来时刻的序列信息。而对于序列建模,未来时刻的上下文同样包含了重要的信息。可以使用如图 3 所示的这种双向编码器来同时获取当前时刻输入的上下文:

图 3. 双向编码器结构示意图

图 3 所示的双向编码器[3]由两个独立的 RNN 构成,分别从前向和后向对输入序列进行编码,然后将两个 RNN 的输出合并在一起,作为最终的编码输出。 在 PaddlePaddle 中,双向编码器可以很方便地调用相关 APIs 实现:

src_word_id = paddle.layer.data(

    name='source_language_word',

    type=paddle.data_type.integer_value_sequence(source_dict_dim))

# source embedding

src_embedding = paddle.layer.embedding(

    input=src_word_id, size=word_vector_dim)

# bidirectional GRU as encoder

encoded_vector = paddle.networks.bidirectional_gru(

   input=src_embedding,

    size=encoder_size,

    fwd_act=paddle.activation.Tanh(),

    fwd_gate_act=paddle.activation.Sigmoid(),

    bwd_act=paddle.activation.Tanh(),

    bwd_gate_act=paddle.activation.Sigmoid(),

    return_seq=True)

C.柱搜索(Beam Search) 算法

训练完成后的生成阶段,模型根据源语言输入,解码生成对应的目标语言翻译结果。解码时,一个直接的方式是取每一步条件概率最大的词,作为当前时刻的输出。但局部最优并不一定能得到全局最优,即这种做法并不能保证最后得到的完整句子出现的概率最大。如果对解的全空间进行搜索,其代价又过大。为了解决这个问题,通常采用柱搜索(Beam Search)算法。柱搜索是一种启发式的图搜索算法,用一个参数 k 控制搜索宽度,其要点如下:

  • 在解码的过程中,始终维护 k 个已解码出的子序列;

  • 在中间时刻 t, 对于 k 个子序列中的每个序列,计算下一个词出现的概率并取概率最大的前 k 个词,组合得到 k2 个新子序列;

  • 取 2 中这些组合序列中概率最大的前 k 个以更新原来的子序列;

  • 不断迭代下去,直至得到 k 个完整的句子,作为翻译结果的候选。

关于柱搜索的更多介绍,可以参考 PaddleBook 中机器翻译一章中柱搜索一节。

D.无注意力机制的解码器

PaddleBook中机器翻译的相关章节中,已介绍了带注意力机制(Attention Mechanism)的 Encoder-Decoder 结构,本例介绍的则是不带注意力机制的 Encoder-Decoder 结构。关于注意力机制,读者可进一步参考 PaddleBook 和参考文献[3]。

对于流行的RNN单元,PaddlePaddle 已有很好的实现均可直接调用。如果希望在 RNN 每一个时间步实现某些自定义操作,可使用 PaddlePaddle 中的recurrent_layer_group。首先,自定义单步逻辑函数,再利用函数 recurrent_group() 循环调用单步逻辑函数处理整个序列。本例中的无注意力机制的解码器便是使用recurrent_layer_group来实现,其中,单步逻辑函数gru_decoder_without_attention()相关代码如下:

# the initialization state for decoder GRU

encoder_last = paddle.layer.last_seq(input=encoded_vector)

encoder_last_projected = paddle.layer.fc(

    size=decoder_size, act=paddle.activation.Tanh(), input=encoder_last)

# the step function for decoder GRU

def gru_decoder_without_attention(enc_vec, current_word):

    '''

    Step function for gru decoder

    :param enc_vec: encoded vector of source language

    :type enc_vec: layer object

    :param current_word: current input of decoder

    :type current_word: layer object

    '''

    decoder_mem = paddle.layer.memory(

            name="gru_decoder",

            size=decoder_size,

            boot_layer=encoder_last_projected)

    context = paddle.layer.last_seq(input=enc_vec)

    decoder_inputs = paddle.layer.fc(

        size=decoder_size * 3, input=[context, current_word])

    gru_step = paddle.layer.gru_step(

        name="gru_decoder",

        act=paddle.activation.Tanh(),

        gate_act=paddle.activation.Sigmoid(),

        input=decoder_inputs,

        output_mem=decoder_mem,

        size=decoder_size)

     out = paddle.layer.fc(

        size=target_dict_dim,

        bias_attr=True,

        act=paddle.activation.Softmax(),

        input=gru_step)

    return out  

在模型训练和测试阶段,解码器的行为有很大的不同:

  • 训练阶段:目标翻译结果的词向量trg_embedding作为参数传递给单步逻辑gru_decoder_without_attention(),函数recurrent_group()循环调用单步逻辑执行,最后计算目标翻译与实际解码的差异cost并返回;

  • 测试阶段:解码器根据最后一个生成的词预测下一个词,GeneratedInput()自动取出模型预测出的概率最高的k个词的词向量传递给单步逻辑,beam_search()函数调用单步逻辑函数gru_decoder_without_attention()完成柱搜索并作为结果返回。

训练和生成的逻辑分别实现在如下的if-else条件分支中:

group_input1 = paddle.layer.StaticInput(input=encoded_vector)

group_inputs = [group_input1]

decoder_group_name = "decoder_group"

if is_generating:

    trg_embedding = paddle.layer.GeneratedInput(

        size=target_dict_dim,

 embedding_name="_target_language_embedding"

        embedding_size=word_vector_dim)

    group_inputs.append(trg_embedding)

    beam_gen = paddle.layer.beam_search(

        name=decoder_group_name,

        step=gru_decoder_without_attention,

        input=group_inputs,

        bos_id=0,

        eos_id=1,

        beam_size=beam_size,

        max_length=max_length)

    return beam_gen

else:

    trg_embedding = paddle.layer.embedding(

        input=paddle.layer.data(

            name="target_language_word",

 type=paddle.data_type.integer_value_sequence(target_dict_dim)),

        size=word_vector_dim,

param_attr=paddle.attr.ParamAttr(name="_target_language_embedding"))

    group_inputs.append(trg_embedding)

    decoder = paddle.layer.recurrent_group(

        name=decoder_group_name,

        step=gru_decoder_without_attention,

        input=group_inputs)

    lbl = paddle.layer.data(

        name="target_language_next_word",

 type=paddle.data_type.integer_value_sequence(target_dict_dim))

    cost = paddle.layer.classification_cost(input=decoder, label=lbl)

    return cost

|3. 数据准备

本例所用到的数据来自WMT14,该数据集是法文到英文互译的平行语料。用bitexts作为训练数据,dev+test data作为验证与测试数据。在PaddlePaddle中已经封装好了该数据集的读取接口,在首次运行的时候,程序会自动完成下载,用户无需手动完成相关的数据准备。

|4. 模型的训练与测试

A.模型训练

启动模型训练的十分简单,只需在命令行窗口中执行python train.py。模型训练阶段 train.py 脚本中的 train() 函数依次完成了如下的逻辑:

a) 由网络定义,解析网络结构,初始化模型参数

# define the network topolgy.

cost = seq2seq_net(source_dict_dim, target_dict_dim)parameters = paddle.parameters.create(cost)

b) 设定训练过程中的优化策略、定义训练数据读取 reader

# define optimization method

optimizer = paddle.optimizer.RMSProp(
   learning_rate=1e-3,
   gradient_clipping_threshold=10.0,
   regularization=paddle.optimizer.L2Regularization(rate=8e-4))

# define the trainer instance

trainer = paddle.trainer.SGD(
   cost=cost, parameters=parameters, update_equation=optimizer)

# define data reader

wmt14_reader = paddle.batch(
   paddle.reader.shuffle(
       paddle.dataset.wmt14.train(source_dict_dim), buf_size=8192),
   batch_size=55)

c) 定义事件句柄,打印训练中间结果、保存模型快照

# define the event_handler callback

def event_handler(event):

    if isinstance(event, paddle.event.EndIteration):

        if not event.batch_id % 100 and event.batch_id:

            with gzip.open(

                    os.path.join(save_path,             "nmt_without_att_%05d_batch_%05d.tar.gz" %

                                 event.pass_id, event.batch_id), "w") as f:

                parameters.to_tar(f)

        if event.batch_id and not event.batch_id % 10:

            logger.info("Pass %d, Batch %d, Cost %f, %s" % (

                event.pass_id, event.batch_id, event.cost, event.metrics))

d) 开始训练

# start training

trainer.train(
   reader=wmt14_reader, event_handler=event_handler, num_passes=2)

输出样例为

Pass 0, Batch 0, Cost 267.674663, {'classification_error_evaluator': 1.0}
.........
Pass 0, Batch 10, Cost 172.892294, {'classification_error_evaluator': 0.953895092010498}
.........
Pass 0, Batch 20, Cost 177.989329, {'classification_error_evaluator': 0.9052488207817078}
.........
Pass 0, Batch 30, Cost 153.633665, {'classification_error_evaluator': 0.8643803596496582}
.........
Pass 0, Batch 40, Cost 168.170543, {'classification_error_evaluator': 0.8348183631896973}

B.生成翻译结果

利用训练好的模型生成翻译文本也十分简单。

首先请修改generate.py脚本中main中传递给generate函数的参数,以选择使用哪一个保存的模型来生成。默认参数如下所示:

generate(
   source_dict_dim=30000,
   target_dict_dim=30000,
   batch_size=20,
   beam_size=3,
   model_path="models/nmt_without_att_params_batch_00100.tar.gz")

在终端执行命令 python generate.py,脚本中的generate()执行了依次如下逻辑:

a) 加载测试样本

# load data  samples for generationgen_creator = paddle.dataset.wmt14.gen(source_dict_dim)gen_data = []for item in gen_creator():
   gen_data.append((item[0], ))

b) 初始化模型,执行infer()为每个输入样本生成beam search的翻译结果

beam_gen = seq2seq_net(source_dict_dim, target_dict_dim, True)with gzip.open(init_models_path) as f:
   parameters = paddle.parameters.Parameters.from_tar(f)# prob is the prediction probabilities, and id is the prediction word.beam_result = paddle.infer(
   output_layer=beam_gen,
   parameters=parameters,
   input=gen_data,
   field=['prob', 'id'])

c) 加载源语言和目标语言词典,将id序列表示的句子转化成原语言并输出结果

beam_result = inferer.infer(input=test_batch, field=["prob", "id"])gen_sen_idx = np.where(beam_result[1] == -1)[0]assert len(gen_sen_idx) == len(test_batch) * beam_sizestart_pos, end_pos = 1, 0for i, sample in enumerate(test_batch):
   print(" ".join([
       src_dict[w] for w in sample[0][1:-1]
   ]))  # skip the start and ending mark when print the source sentence
   for j in xrange(beam_size):
       end_pos = gen_sen_idx[i * beam_size + j]
       print("%.4f\t%s" % (beam_result[0][i][j], " ".join(
           trg_dict[w] for w in beam_result[1][start_pos:end_pos])))
       start_pos = end_pos + 2
   print("\n")

设置beam search的宽度为3,输入为一个法文句子,则自动为测试数据生成对应的翻译结果,输出格式如下:

Elles connaissent leur entreprise mieux que personne .
-3.754819        They know their business better than anyone . <e>
-4.445528        They know their businesses better than anyone . <e>
-5.026885        They know their business better than anybody . <e>

  • 第一行为输入的源语言句子。

  • 第二 ~ beam_size + 1 行是柱搜索生成的 beam_size 条翻译结果 - 相同行的输出以“t”分隔为两列,第一列是句子的log 概率,第二列是翻译结果的文本。

  • 符号<s> 表示句子的开始,符号<e>表示一个句子的结束,如果出现了在词典中未包含的词,则用符号<unk>替代。

至此,我们在 PaddlePaddle 上实现了一个初步的机器翻译模型。我们可以看到,PaddlePaddle 提供了灵活丰富的API供大家选择和使用,使得我们能够很方便完成各种复杂网络的配置。机器翻译本身也是个快速发展的领域,各种新方法新思想在不断涌现。在学习完本例后,读者若有兴趣和余力,可基于 PaddlePaddle 平台实现更为复杂、性能更优的机器翻译模型。

【参考文献】

  1.  Sutskever I, Vinyals O, Le Q V. Sequence to Sequence Learning with Neural Networks[J]. 2014, 4:3104-3112.

  2. Cho K, Van Merriënboer B, Gulcehre C, et al. Learning phrase representations using RNN encoder-decoder for statistical machine translation[C]. Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing (EMNLP), 2014: 1724-1734.

  3. Bahdanau D, Cho K, Bengio Y. Neural machine translation by jointly learning to align and translate[C]. Proceedings of ICLR 2015, 2015

今 日 AI 资 讯 

(如欲了解详情,在后台回复“313”即可!)

1.拉里佩奇的飞行出租车在新西兰试飞;微软三位AI大牛出走。(AI前线)

2.陈天奇团队TVM重磅更新:直接在浏览器使用GPU。(新智元)

3.新研究提出深度残差等价映射:有正脸加强侧脸识别效果。(机器之心)

 end

*原创贴,版权所有,未经许可,禁止转载

*值班小Paddle:wangp

*欢迎在留言区分享您的观点

*为了方便大家问题的跟进解决,我们采用Github Issue来采集信息和追踪进度。大家遇到问题请搜索Github Issue,问题未解决请优先在Github Issue上提问,有助于问题的积累和沉淀

点击“阅读原文”,访问Github Issue。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值