Transformer系列:Greedy Search贪婪搜索解码流程原理解析

本文主要是介绍Transformer系列:Greedy Search贪婪搜索解码流程原理解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

解码器预测流程简述

Encoder-Decoder这类框架需要在解码器中分别拿到前文已经翻译的输入,以及编码器的输出这两个输入,一起预测出下一个翻译的单词。在训练阶段,一个句子通过右移一位的方式转化为从第二个词到最后一个词的逐位预测任务,一个答案句子通过shift right构造出两个句子分别作为输入和预测目标,如图所示

训练shifted right方式

训练阶段虽然输入的是完整的句子,但是配合下三角掩码使得自注意力层每个单词的信息聚合只能利用到该词和该词之前的单词,不许偷看后词,和实际的部署预测场景一致。
在预测场景下,解码器的工作流程变得简单,不再需要掩码,设置初始位置为单词,从开始逐位置开始预测下一个单词,而预测出的单词会和之前已经预测的结果进行拼接,以此继续迭代预测,不断重复这个过程直到预测到截止。

逐位预测方式


解码器自注意力层Q,K,V分配和维护

解码器有两个注意力层,分别是自注意力层和交互注意力层,其中交互注意力层中的采用全局可见的编码器输出,因此K,V固定为完整的编码器输出,而自注意力层不同,为了达到不偷看的目的,Q,K,V需要动态维护的。先上结论

Q:当前位置(上一个预测出的)单词的信息embedding
K:截止到当前位置的所有单词的信息embedding,保证每个词不能偷看到后词信息
V:截止到当前位置的所有单词的信息embedding,保证每个词不能偷看到后词信息

一般Q位置输入为[B,L,D]三维矩阵,其中L代表文本长度seq length,Q的seq length决定了自注意力输出的seq length,在预测阶段的解码器自注意层,Q的seq length长度为1,只需要输出当前词的信息即可,输出也是一个token的embedding表征,这个token天然地代表下一个需要预测的词,原因是shifted right的方式使得上一个单词的Decoder输出embedding成为了下一个单词计算概率分布的依据。
在训练阶段由于Q输入的是带有下三角掩码的完整答案句子,而K,V直接取和Q一致,不需要额外再给到K,V,但是在预测阶段由于每次Q只输入最新的一个token信息,除了位置Q,K,V三者相等,从第二个位置之后再也无法根据Q直接得到K,V,需要在每一步预测的时候记录下历史的K,V,然后将最新的Q信息拼接组合起来,注意K,V只能采用拼接的方式而不能采用模型重新计算的结果,如果重新计算代表K,V已经偷看了后词信息,计算示意图如下

预测阶段对解码器Q,K,V的维护示意图

以一个2层的Decoder为例,上图中||代表拼接, out代表第一层Decoder之后输出的token embedding,output1代表第二层Decoder的输出,output1直接用于翻译单词的概率分布预测。
对于Q:在第一层以源文本的embedding作为Q,在第二层以源文本的embedding经过第一层Decoder的输出embedding作为Q。在位置由于文本总长度也只有1,此时Q,K,V三者一致,从第二个单词开始,Q为当前位置单词。
对于K,V:K,V是截止到当前词之前的所有单词信息,此时需要额外对第一层和第二层的K,V进行维护,具体方式是将Q信息拼接到历史K,V上,对于第一层Decoder实际上是将源文本原始embedding拼接来得到完整的截止到当前的句子信息,而对于第二层Dcoder,这种拼接方式保证了对前词的Sefl Attention结果不做任何修改,例如在输入2的第二层,在 out后面直接拼接上<我> out,完整沿用了 out而没有重新计算,保证了的自注意表征没有偷看到后词。


解码器预测源码分析

在Keras实现的Transformer源码中(github:attention-is-all-you-need-keras),decode_batch_greedy函数以Greedy Search贪婪搜索策略为基础,实现了Transformer的逐位预测过程。

def decode_batch_greedy(src_seq, encode_model, decode_model, start_mark, end_mark, max_len=128):...

函数的输入如下:

  • src_seq:输入的待翻译文本
  • encode_model:编码器模型
  • decode_model:解码器模型
  • start_mark:位置对应的数值id标志
  • end_mark:位置对应的数值id标志
  • max_len:最长预测步长,即翻译的结果文本长度最大值为128

首先计算出待翻译文本在编码器的输出,该输出在整个预测过程的所有步长中全局可用

enc_ret = encode_model(src_seq).numpy()

然后开始准备逐步预测,初始化一个长度永远为1的当前词的数字id矩阵,初次使用以位置开始,矩阵每一行代表该批次下的一条样本。

bs = src_seq.shape[0]
# TODO 该批次下每个样本从<start>开始
target_one = np.zeros((bs, 1), dtype='int32')
target_one[:, 0] = start_mark  # 2

在逐步预测之前定了各层的K,V列表,ended标志列表,解码结果列表

dec_outputs = [np.zeros((bs, 1, d_model)) for _ in range(n_dlayers)]
ended = [0 for x in range(bs)]
decoded_indexes = [[] for x in range(bs)]
  • dec_outputs:K,V维护列表,用于记录历史K,V,已经每步拼接当前词的信息形成新的K,V
  • ended:该批次下每条样本的结束标志,如果已经结束该列表对应索引位置下改为1
  • decoded_indexes:翻译预测结果,list to list格式,记录了每条样本的翻译结果,每条翻译结果为字符的数字id列表

逐步预测代码在一个循环体中,代码注解如下

    for i in range(max_len - 1):print("max_len-1 {}".format(i))# TODO src_seq的作用是在交互注意力层添加encoder信息的mask# TODO target_one是上一个预测出的单词# TODO target_one和dec_outputs,是随着步长变化的,编码器的信息不变# TODO 在解码器内部 target_one 和 dec_outputs 会进行相加outputs = [x.numpy() for x in decode_model([target_one, src_seq, enc_ret] + dec_outputs)]# TODO 这个output [1, 1, softmax]是decoder最终输出+Linear映射的得分# TODO new_dec_outputs 上一个预测词的原始embedding输入,和经过一层decoder之后的embedding输入new_dec_outputs, output = outputs[:-1], outputs[-1]for dec_output, new_out in zip(dec_outputs, new_dec_outputs):# TODO new_out 永远等于[batch_size, 1, 256]# TODO 更新前词(不包含当前词)的向量信息dec_output[:, -1, :] = new_out[:, 0, :]# TODO [[batch_size, n+1, embedding], [batch_size, n+1, embedding]]# TODO 这个地方预留0是为了在Decoder里面和当前词相加,获得完整的k,vdec_outputs = [np.concatenate([x, np.zeros_like(new_out)], axis=1) for x in dec_outputs]# TODO output[:, 0, :] => [1, softmax]# TODO 每次取当前位置的最大预测结果 贪婪 不一定是全局最优sampled_indexes = np.argmax(output[:, 0, :], axis=-1)for ii, sampled_index in enumerate(sampled_indexes):if sampled_index == end_mark:# TODO 该批次下某条样本已经翻译结束ended[ii] = 1if not ended[ii]:# TODO 如果没有结束,收集每条样本的翻译token# TODO 之前已经结束的,也不会再增加新的翻译结果了decoded_indexes[ii].append(sampled_index)# TODO 该批次下所有样本翻译结束if sum(ended) == bs:break# TODO target_one 永远只记录上一个预测出来的最新的单词target_one[:, 0] = sampled_indexes

在循环体中不断更新target_one,dec_outputs,其中target_one只表征当前一个单词,dec_outputs表征截止当前词之前在两层Decoder的K,V信息,每步计算出最终的Decoder输出output,使用贪婪搜索策略argmax来拿到每步得分最大的单词作为预测结果。

sampled_indexes = np.argmax(output[:, 0, :], axis=-1)

如果某条样本没有预测到结束则添加到最新翻译结果,否则不再添加新的预测单词

if sampled_index == end_mark:ended[ii] = 1
if not ended[ii]:decoded_indexes[ii].append(sampled_index)

最终等到该批次样本哦每一条都预测到end,跳出循环体任务结束

if sum(ended) == bs:break

Greedy Search贪婪搜索简述

Greedy Search贪婪搜索比较简单,每一步都选择概率最大的单词输出,最后组成整个句子输出。这种方法给出的结果一般情况结果比较差,因为只考虑了每一步的最优解,并不一定是全局最优,因为贪婪搜索会错过隐藏在低概率单词后面的高概率单词,另外当如果每个位置选错了,后续位置生成的内容很可能也是错误的,具有错误的累加效果。
贪婪搜索由于每步只需要关注最大得分,考虑的因素少,因此实现容易,执行速度快,因此在源码中作者将Greedy Search封装在decode_sequence_fast方法下,代表对序列的快速解码。

贪婪搜索示意图


解码器和贪婪搜索总结
  • 1.解码器在预测阶段从初始的占位符开始,利用历史前词和全局编码器结果,逐位预测下一个单词,知道预测到end停止
  • 2.解码器的输入作为Query,Query只输入当前最新单词信息,自注意力层的Key,Value需要手动维护历史并且拼接当前最新信息
  • 3.贪婪搜索的优点是实现简单,可作为解码的快速实现,但是它只考虑了每一步的局部最优并不一定是全局最优,往往效果较差

最后的最后

感谢你们的阅读和喜欢,我收藏了很多技术干货,可以共享给喜欢我文章的朋友们,如果你肯花时间沉下心去学习,它们一定能帮到你。

因为这个行业不同于其他行业,知识体系实在是过于庞大,知识更新也非常快。作为一个普通人,无法全部学完,所以我们在提升技术的时候,首先需要明确一个目标,然后制定好完整的计划,同时找到好的学习方法,这样才能更快的提升自己。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

五、面试资料

我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下。
在这里插入图片描述

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

这篇关于Transformer系列:Greedy Search贪婪搜索解码流程原理解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1029895

相关文章

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、