导语
PaddlePaddle提供了丰富的运算单元,帮助大家以模块化的方式构建起千变万化的深度学习模型来解决不同的应用问题。这里,我们针对常见的机器学习任务,提供了不同的神经网络模型供大家学习和使用。本周推文目录如下:
3.12:【命名实体识别】
训练端到端的序列标注模型
3.13:【序列到序列学习】
无注意力机制的神经机器翻译
3.14:【序列到序列学习】
使用Scheduled Sampling改善翻译质量
3.15:【序列到序列学习】
带外部记忆机制的神经机器翻译
3.16:【序列到序列学习】
生成古诗词
序列到序列学习实现两个甚至是多个不定长模型之间的映射,有着广泛的应用,包括:机器翻译、智能对话与问答、广告创意语料生成、自动编码(如金融画像编码)、判断多个文本串之间的语义相关性等。
在序列到序列学习任务中,我们首先以机器翻译任务为例,提供了多种改进模型供大家学习和使用。包括:不带注意力机制的序列到序列映射模型,这一模型是所有序列到序列学习模型的基础;使用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
反向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中的相应元素。
【参考文献】
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.
今 日 AI 资 讯
(如欲了解详情,在后台回复当日日期数字,例如“314”!)
1.普华永道发布人工智能报告,对人工智能在2018年的发展做出了8项预测。(AI视点)
2.上交大发布知识图谱AceKG,超1亿实体,近100G数据。(新智元)
3.第四范式业界首推免费智能客服服务。(机器之心)
end
*原创贴,版权所有,未经许可,禁止转载
*值班小Paddle:wangp
*欢迎在留言区分享您的观点
*为了方便大家问题的跟进解决,我们采用Github Issue来采集信息和追踪进度。大家遇到问题请搜索Github Issue,问题未解决请优先在Github Issue上提问,有助于问题的积累和沉淀
点击“阅读原文”,访问Github Issue。