专栏首页PaddlePaddle【序列到序列学习】使用Scheduled Sampling改善翻译质量

【序列到序列学习】使用Scheduled Sampling改善翻译质量

生成古诗词

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

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

【序列到序列学习】

02

使用Scheduled Sampling

改善翻译质量

|1. 概述

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

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

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

|2. 算法简介

Scheduled Sampling主要应用在序列到序列模型的训练阶段,而生成阶段则不需要使用。

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

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

  • 线性衰减:ϵi=max(ϵ,k−c∗i),其中ϵ限制ϵi的最小值,k和c控制线性衰减的幅度。
  • 指数衰减:ϵi=ki,其中0<k<1,k控制着指数衰减的幅度。
  • 反向Sigmoid衰减:ϵi=k/(k+exp(i/k)),其中k>1,k同样控制衰减的幅度。

图1给出了这三种方式的衰减曲线,

图1. 线性衰减、指数衰减和

反向Sigmoid衰减的衰减曲线

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

图2. Scheduled Sampling选择不同元素作为解码器输入示意图

|3. 模型实现

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

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

import numpy as np

import math

class RandomScheduleGenerator:

"""

The random sampling rate for scheduled sampling algoithm, which uses devcayed

sampling rate.

"""

...

下面将分别定义类RandomScheduleGenerator的__init__、getScheduleRate和processBatch三个方法。

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

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]

getScheduleRate根据衰减函数和已经处理的数据量计算ϵi。

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_)

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

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

Scheduled Sampling需要在序列到序列模型的基础上增加一个输入true_token_flag,以控制解码器输入。

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的输入数据。

def gen_schedule_data(reader,

schedule_type="linear",

decay_a=0.75,

decay_b=1000000):

"""

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

: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

:return: the new reader with the field "true_token_flag".

:rtype: callable

"""

schedule_generator = RandomScheduleGenerator(schedule_type, decay_a, decay_b)

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

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

训练时recurrent_group每一步调用的解码器函数如下:

def gru_decoder_with_attention_train(enc_vec, enc_proj, true_word,

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

该函数使用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中的相应元素。

【参考文献】

  1. Bengio S, Vinyals O, Jaitly N, et al. Scheduled sampling for sequence prediction with recurrent neural networks//Advances in Neural Information Processing Systems. 2015: 1171-1179.

本文分享自微信公众号 - PaddlePaddle(PaddleOpenSource)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-03-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【PaddlePaddle视频新课】之识别数字

    PaddlePaddle之识别数字 课程介绍:本节课从背景介绍引出手写识别问题;由浅入深介绍Softmax回归模型、多层感知器模型、卷积神经网络模型3个模型,...

    用户1386409
  • 【AI核心技术】课程十五:序列场景分析

    UAI与PaddlePaddle联合推出的【AI核心技术掌握】系列课程持续更新中!

    用户1386409
  • 【AI核心技术】课程二:Modern AI课程体系

    从今天开始,UAI与PaddlePaddle联合推出的【AI核心技术掌握】系列课程继续更新!

    用户1386409
  • ElasticSearch(7.2.2)-es集群索引分⽚管理

    cwl_java
  • A parameter verify tools for Egg

    最近接触EGG框架,刚接触,其中涉及到验证参数的一些运用,网上找的egg-validate 都不是很好用,最后找到了parameter插件,挺好用,推荐给大家...

    张炳
  • Git 项目推荐 | 基于 J2Cache 的多级缓存框架

    基于j2cache的理念,重新设计开发的一套分布式缓存。支持2级并不限于2级的多级缓存系统。 github地址:atoms 配置文件: <?xml versio...

    码云Gitee
  • C++服务器开发之笔记三

    为什么需要原子性操作? 我们考虑一个例子: (1)x++这个常见的运算符在内存中是怎样操作的? 从内存中读x的值到寄存器中,对寄存器加1,再把新值写回x所处的内...

    用户1198337
  • JAVA实例化泛型

    DH镔
  • [PYTHON] 核心编程笔记之四

    身份: 每一个对象都有一个唯一的身份标识自己,任何对象的身份可以使用内建函数id()来得到,这个值可以认为是该对象的内存地址(只读)

    py3study
  • 控制、生成、扩充:一个可伸缩的多属性文本生成框架(CS.CL)

    本文提出了一种基于多属性控制的文本生成方法。我们介绍了CGA,一个可变的自动编码器的架构,以控制,产生,并扩大文本。通过将对抗性学习与上下文感知损失相结合,CG...

    用户7236395

扫码关注云+社区

领取腾讯云代金券