[软件工程应用与实践]lingvo代码阅读
2021SC@SDUSC
阅读上一篇笔记时,发现代码截图略显冗余。因此这节只放部分代码截图,重点放在自己对代码的理解以及从代码中学习到的知识。
lingvo.core.ops包
beam_search_step_op_kernels.cc
lingvo.core.ops.beam_search_step(scores, atten_probs, best_scores, cumulative_scores, in_scores, in_hyps, in_prev_hyps, in_done_hyps, in_atten_probs, in_beam_done, is_last_chunk, cur_step, eos_id, beam_size, num_hyps_per_beam, eoc_id=- 1, valid_eos_max_logit_delta=5, local_eos_threshold=- 100, merge_paths=False, allow_empty_terminated_hyp=True, ensure_full_beam=False, force_eos_in_last_step=False, force_eos_in_top_k=False, force_last_chunk_eoc_in_top_k=False, merged_topk_buffer_size_factor=2, beam_independence=False, atten_vecs_in_hypothesis_protos=True, name=None)
波束搜索向前推进一步
设“b”为波束数, “k”为每个波束的hyps数, “t”为最大解码步长.
在第一个解码步骤之前分配了以下数据结构,并从当前步骤传递到下一个步骤:
in_scores
形状为[t, k * b]的张量. In_scores [i, j]为第i步解码时第j个hyp的局部分数.
in_hyps
形状为[t, k * b]的张量。In_hyps [i, j]为第i步解码时第j个hyp的令牌id。
in_prev_hyps
形状为[t, k * b]的张量。In_prev_hyps [i, j]存储时间步长i的第j个hyp的指针到上一个时间步长(i - 1)的hyp。0 <= In_prev_hyps [i, j] < k * b。
in_done_hyps
形状为[t, k * b]的张量。in_done_hyps[i, j]可以是一个空字符串,也可以是一个序列化的Hypothesis原型。终止的hyps将从波束中移除,并移动到相应的in_done_hyps槽中。
in_atten_probs
形状为[t, k * b, s_len]的张量。In_atten_probs [i, j,…]是第j个hyp在第i个时间步长的源单词上的注意probs。
in_beam_done
bool类型的形状[b]张量,是否每一束都做过。
这些张量在这个op调用中被修改(包含cur_step timestep的内容),并被传递给相应的输出张量。
参数
- scores :
float32
型张量。形状为[k * b, vocab_size]的矩阵,其中b为有源波束的数量,k为每个波束中hyps的数量。当前时间步长的本地分数。 - atten_probs :
float32
型张量。形状为[k * b, source_len]的矩阵。注意力机制时间步长的概率。 - best_scores :
float32
型张量。一个大小为[b]的矢量,是迄今为止每个光束中终止的hyps的最好分数。 - cumulative_scores :
float32
型张量。大小为[k * b]的向量当前步骤之前每个活跃的hyp的累计得分。 - in_scores :
float32
型张量。 - in_hyps :
float32
型张量。 - in_prev_hyps :
int32
型张量。 - in_done_hyps :
string
型张量。 - in_atten_probs :
float32
型张量。 - in_beam_done :
bool
型张量。向量[b],是否每个波束都是以前处理过的。 - is_last_chunk :
bool
型张量。形状[k * b]的张量。神经传感器用来判断当前假设是否到达了最后一个块,是否应该将下一个块结束符号作为句子结束符。 - cur_step :
int32
型张量。当前步骤id。 - eos_id :
int
。特殊序列结束令牌的令牌id。 - beam_size :
float
。如果波束中主动hyps的分数与最佳分数之间的差值超过这个阈值,搜索将终止。 - num_hyps_per_beam :
int
。hyps的数量。 - eoc_id : 块标记的特殊结束的标记id。
- valid_eos_max_logit_delta : 我们只允许在其logit与最佳候选logit的距离不超过valid_eos_max_logit_delta的情况下终止hyp。
- local_eos_threshold : 如果的局部评分大于local_eos_threshold,则允许终止hyp。
- merge_paths : 如果为真,当删除时相同的hyps将被合并为一个单独的hyp。合并hyp的概率将是各组成hyps的概率之和。这只适用于发射模型(RNN-T和NT)。
- allow_empty_terminated_hyp : 是否可以考虑一个只包含的hypp终止。默认情况下这是正确的,因为一个话语可能包含沉默。当EMBR训练epsilon-emitting模型(例如RNN-T)时,它应该被设为false,因为即使在没有沉默的情况下,这些模型也容易发出全epsilon hyps。注意,在EOS中终止的hyp不会被认为是空的,所以这个标志对非发射模型没有影响。
- ensure_full_beam : 如果为True,我们不会将all_done输出设置为True,直到我们找到’ num_hyps_per_beam ‘终止的hyps,并且没有活动的hyps在’ beam_size '范围内的最佳终止的hyps。如果为False,只有第二个条件必须满足。一般来说,这应该是假的,除非波束搜索是作为最低字错误率训练的一部分运行。
- force_eos_in_last_step : 如果为真,则如果解码即使在(max - 1)步后也没有终止,则eos符号被注入结果,并返回部分假设(最后有一个有效的eos符号)。对于这些偏导,All_done设置为true。如果为false(默认行为),则返回空假设,并在终止时将all_done设置为false。
- force_eos_in_top_k : 是否始终认为eos代币在每一步都在k代币之列。当为False时,hyps只能在eos令牌是k的一部分的情况下终止。valid_eos_max_logit_delta和local_eos_threshold始终适用,无论这个值是多少。
- force_last_chunk_eoc_in_top_k : 是否始终认为最后一块eoc令牌在前k个令牌中。这只有在解码到达输入的最后一帧时才有效。当为True时,hyps可以在eoc的最后一帧终止,即使eoc得分还没有高到足以进入最高k。请注意,无论如何,p.valid_eos_max_logit_delta和p.local_eos_threshold总是适用的。
- merged_topk_buffer_size_factor : 当剪枝每个hyp顶部k扩展以形成每个梁顶部k扩展时的缓冲区大小因子。如果当eoc_id >= 0时,该因子设置为大于或等于num_hyps_per_beam + 2,则在执行所有可能的路径合并之前不会进行修剪(如果merge_paths=True)。为了提高内存效率(即在剪枝期间保持更少的hyps),合理的值是2。
- beam_independence : 当启用时,当且仅当in_beam_done[beam_id] == True时,此步骤将成为beam_id的无操作。
- atten_vecs_in_hypothesis_protos : 是否在返回的假设原中填充atten_vecs字段。
- name : 操作的名称。
返回值
一个张量对象的元组(out_best_scores, out_cumulative_scores, out_scores, out_hyps, out_prev_hyps, out_done_hyps, out_atten_probs, out_beam_done, all_done)。
- out_best_scores : 更新了每一束的最佳分数。
- out_cumulative_scores : 大小为[k * b]的向量在当前解码步骤之后,新hyps的累积分数。
- out_scores
- out_hyps
- out_prev_hyps
- out_done_hyps
- out_atten_probs
- out_beam_done : 大小为[b]的向量,每个波束是否在此步骤之后完成。下面的all_done是out_beam_done在所有波束上的逻辑与。
- all_done : 标量,是否对所有波束终止解码。
实践启示——注意力机制
注意力模型(AM)最初被用于机器翻译,现在已成为神经网络领域的一个重要概念。lingvo作为自然语言处理框架,内置机器翻译、语音识别等模块,自然需要使用AM相关算法。
个人对注意力机制的理解
Attention本质上是一个寻址的过程。
当我们翻译一句话时,如csdn个人中心这句
“您还不是会员身份”,我们在不同阶段会把注意力放在不同的汉字/单词上。
通过计算出q与k的相关度,得到现在最应该“注意”的单词
step1 信息输入
用X = [x1, · · · , xN ]表示N 个输入信息;
step2 注意力分布计算
令Key=Value=X,则可以给出注意力分布alpha
step3 信息加权平均
注意力分布 [公式] 可以解释为在上下文查询q时,第i个信息受关注的程度,采用一种“软性”的信息选择机制对输入信息X进行编码
- Self-Attention
self-attention只关注输入序列间的关系,在序列内部进行attention计算,捕捉文本的内在联系。
- Multi-Head Attention
在Self-Attention的基础上,用多种变换生成的Q、K、V进行运算,再将它们对相关性的结论综合起来,增强Self-Attention的学习效果。
Beam Search
beam search是对greedy search的一个改进算法。相对greedy search扩大了搜索空间,但远远不及穷举搜索指数级的搜索空间,是二者的一个折中方案。
穷举搜索需遍历所有可能性,求出概率最大序列,最有可能得到全局最优解,但费时费力;贪心搜索每一步只搜索一次,省时省力但准确度低。beam search折中二者,设定贪心搜索次数,保留竖宽范围内的所有序列,较穷举相比省力,较贪心相比准确。
beam search有一个超参数beam size(束宽),设为 k 。第一个时间步长,选取当前条件概率最大的 k 个词,当做候选输出序列的第一个词。之后的每个时间步长,基于上个步长的输出序列,挑选出所有组合中条件概率最大的 k 个,作为该时间步长下的候选输出序列。始终保持 k 个候选。最后从 k 个候选中挑出最优的。
beam search代码实现
def beam_search(decoder, num_beams, max_len, *input):
"""
a beam search implementation about seq2seq with attention
:param decoder:
:param num_beams: number of beam, int
:param max_len: max length of result
:param input: input of decoder
:return: list of index
"""
# init
state = input[0] # state of decoder
outputs = input[1] # outputs of encoder
src_len = input[2] # length of encode sequence
beams = [[[1], 1, state]]
cur_pro = 0
cur_seq = None
for i in range(max_len):
results = []
for beam in beams:
tgt = torch.LongTensor(beam[0][-1:]).unsqueeze(0).cuda()
input = [tgt, beam[2], outputs, src_len, 1]
output, state = decoder(*input)
v, i = torch.topk(output.view(-1).data, k=num_beams)
for m, n in zip(v, i):
gen_seq = beam[0] + [n.item()]
pro = beam[1] * m.item()
results.append([gen_seq, pro, state])
if n.item() == 2 and pro > cur_pro: # eos_token = 2
cur_pro = pro
cur_seq = gen_seq
# filter beams
beams = []
for gen_seq, pro, state in results:
if pro > cur_pro:
beams.append([gen_seq, pro, state])
# cut
if len(beams) > num_beams:
results = []
pros = []
for beam in beams:
pros.append(beam[1])
pros_idx = np.array(pros).argsort()[-1*num_beams:]
for pro_idx in pros_idx:
results.append(beams[pro_idx])
beams = results
if len(beams) == 0:
return cur_seq
if cur_seq is not None:
return cur_seq
else:
max_pro = 0
max_seq = None
for beam in beams:
if beam[1] > max_pro:
max_pro = beam[1]
max_seq = beam[0]
return max_seq