当前位置:   article > 正文

金融NLP需求落地实践总结——使用T5-Pegasus做一句话摘要_pegasus摘要

pegasus摘要

目录

收起

T5基本原理及实现细节

relative position bias

layer normalization的改动

参数初始化以及dense layer中的bias去除

T5.1.1优化了哪些内容

GEGLU替换Relu

T5-PEGASUS基本原理及tf-serving部署

tf-serving部署

keras-model转tf-serving-pb格式

使用tf-serving部署T5-Pegasus的encoder和decoder

高效GRPC调用tf-serving服务生成文本

client代码编写注意点

模型以外的一些优化

小结

最近半年没有发文,原因是最近接的落地需求有点多,光顾着搬砖了。不过,在搬砖的过程中,也积累了一些新的NLP落地经验。之前我介绍过一些NLP在金融场景的落地实践,这些实践都属于NLU(自然语言理解)领域。而在NLP中,自然语言生成(以下简称NLG)是另一个非常重要的研究领域。然而在金融领域,NLG的应用一直难以推广,这其中有很多原因,例如构建高质量、大规模的平行训练语料成本高、难度大;生成的文本质量不稳定,会出现重复、不符合逻辑语法的情况;生成的文本无法使用客观多样可解释的评价方式来评判质量;模型部署存在一定的复杂度。当然,要一次性解决所有问题是不太现实的,我们只能在不断地的研究和实践中一步一步解决上述问题。

今天,我将基于NLG中一个比较经典的任务——生成式文本摘要,来分享我们在指定的金融违约新闻上做一句话摘要的落地小结,希望对同行有一定的启发作用。我们使用的模型是T5-pegasus,来源于追一科技发布的模型技术,具体技术博文可参考:

T5 PEGASUS:开源一个中文生成式预训练模型​spaces.ac.cn/archives/8209正在上传…重新上传取消

开源项目地址可参考:

GitHub - ZhuiyiTechnology/t5-pegasus: 中文生成式预训练模型​github.com/ZhuiyiTechnology/t5-pegasus正在上传…重新上传取消

本文主要从以下几点来介绍:

1、T5模型原理简单介绍以及实现细节的一些注意点分享。

2、T5.1.1相比T5的一些优化点介绍。

3、T5-Pegasus原理简单介绍以及tf-serving部署实践分享。

4、在模型之外,提升文本生成的注意点分享。

对一些基础原理已经比较熟悉的同学可以直接跳至感兴趣的章节阅读。

T5基本原理及实现细节

T5全称是Text-to-Text Transfer Transformer,来源于google的论文:

https://arxiv.org/pdf/1910.10683.pdf​arxiv.org/pdf/1910.10683.pdf

从名字就可以看出来,它由两大基本元素构成:

1、“Text-to-Text Transfer“部分。它的本质实际上就是把不同的NLP任务都通过某种方式转化成一个文本生成的任务。最近比较热门的prompt-based finetuning应用的技术思想与之有一定的相似性,但大家不要将两者混淆。prompt-based方法提供了一种将预训练模型迁移到下游任务的新方法,充分利用了预训练时使用MLM学到的知识,将下游任务转化为MLM任务,使得finetune阶段和预训练保持一致,从而更高效地利用预训练模型学习到的知识。而T5则是认为万物皆可文本生成,只不过不同的任务中输入和输出的平行语料形式不同而已,例如:情感分析任务中,输入是待分析的文本,输出则是代表不同情感的标志词;机器翻译和文本摘要都是文本生成的经典任务,它们也可以放在一起进行训练,只要在输入的时候标识当前的任务名称(或者任务提示)就能进行区分。具体转换规则见论文中的截图:

由于任务提示也是一段自然文本,因此模型在预训练的时候能够理解当前的任务特点。也因为T5在预训练时是以文本生成任务为主,因此它天生可以用来做翻译、摘要、文本复述等NLG任务。另外,在预训练中,T5还借鉴了BERT的MLM思想以及span-bert的span mask思想,引入了corrupt span的思想,论文设计了很多类型的无监督任务,最终通过实验发现这样设计训练任务效果最好:

对于连续的span进行mask,一个span使用一个mask替换,对于该样本,我们直接预测span中的词,同时预测的target文本中,不同span的开头会使用source文本中对应位置的mask来标识,具体如下图中的红框所示:

当然,T5的预训练任务还有很多设置,不过因为我们基本上不会去预训练它,因此我这边就不详细描述了。

2、“Transformer“部分。T5使用的基本模型还是Transformer,这块应该不用过多介绍。T5一开始设计了三种模型架构,分别是传统的seq2seq架构、只有decoder的类GPT架构以及引入prefix的LM架构(类似微软的UniLM)。最终通过实验,验证了seq2seq架构的效果最好。T5的模型架构是比较简单明了的,但是在具体实现过程中有很多细节需要注意:

relative position bias

在T5的transformer中,替换了原来的绝对位置编码,使用了相对位置编码,对attention中输入的key和query计算两者序列的相对位置的offset,并引入embedding计算,最终将其作为一个attention bias应用在计算attention score之后,softmax之前。相对位置编码相比于绝对位置编码,更能捕捉较长序列中字符之间的位置关联信息。另外,在代码实现的时候,原版的代码引入了bucket的思想,对相对位置过于远的情况进行了特殊处理,将大于max_distance的相对位置offset映射到[0,bucket_num)内,下面是原版代码供大家参考:

  1. def relative_position_bucket(self,relative_position,
  2. bidirectional=True,
  3. num_buckets=32,
  4. max_distance=128):
  5. """Translate relative position to a bucket number for relative attention.
  6. The relative position is defined as memory_position - query_position, i.e.
  7. the distance in tokens from the attending position to the attended-to
  8. position. If bidirectional=False, then positive relative positions are
  9. invalid.
  10. We use smaller buckets for small absolute relative_position and larger buckets
  11. for larger absolute relative_positions. All relative positions >=max_distance
  12. map to the same bucket. All relative positions <=-max_distance map to the
  13. same bucket. This should allow for more graceful generalization to longer
  14. sequences than the model has been trained on.
  15. Args:
  16. relative_position: an int32 Tensor
  17. bidirectional: a boolean - whether the attention is bidirectional
  18. num_buckets: an integer
  19. max_distance: an integer
  20. Returns:
  21. a Tensor with the same shape as relative_position, containing int32
  22. values in the range [0, num_buckets)
  23. """
  24. ret = 0
  25. n = -relative_position
  26. if bidirectional:
  27. num_buckets //= 2
  28. ret += tf.to_int32(tf.less(n, 0)) * num_buckets
  29. n = tf.abs(n)
  30. else:
  31. n = tf.maximum(n, 0)
  32. # now n is in the range [0, inf)
  33. max_exact = num_buckets // 2
  34. is_small = tf.less(n, max_exact)
  35. val_if_large = max_exact + tf.to_int32(
  36. tf.log(tf.to_float(n) / max_exact)
  37. / math.log(max_distance / max_exact) * (num_buckets - max_exact))
  38. val_if_large = tf.minimum(val_if_large, num_buckets - 1)
  39. ret += tf.where(is_small, n, val_if_large)
  40. return ret

layer normalization的改动

相对于原版的layer normalization,T5里面的layernorm在实现上有一定的小改动:

1、去掉了layernorm中的bias以及center

2、将layernorm的计算放在了残差计算的外侧。

从实验上来看,这两者改动对最后的效果是正向的。至于原因,前一个改动在苏神的博文中有一些推测解释,个人认为比较符合实际,即layernorm中的center类似于bias信息,在预训练的时候bias信息会存储一些先验的信息,这些先验信息反而会影响模型的迁移学习能力。具体参考:

浅谈Transformer的初始化、参数化与标准化 - 科学空间|Scientific Spaces

具体的代码实现可以参考如下版本,红框中内容即判断是否考虑减去input的center:

参数初始化以及dense layer中的bias去除

除了layer norm中的bias去除了,其他全连接层的bias都在T5中被去除了,这个原因与上述提到的类似。另外,T5中对于全连接层中的权重参数初始化也要一定的讲究。具体的原因同样可以在苏神的博客中找到其分析:

浅谈Transformer的初始化、参数化与标准化 - 科学空间|Scientific Spaces​spaces.ac.cn/archives/8620#%E7%9B%B4%E6%8E%A5%E6%A0%87%E5%87%86%E5%8C%96

简单来说,就是让权重参数能够尽量以标准差初始化,同时维持二阶矩的稳定,让模型在训练的时候能够更加稳定。T5的做法就是在正态分布初始化的时候,将标准差stddev除以  

 ,d表示当前的hidden_size。这套操作与bert在attention计算时,除以的目的是类似的。而在T5中,attention计算的时候是没有除以做scaling的操作的。 

T5.1.1优化了哪些内容

在T5提出以后,google针对T5的结构做了一些优化改动,提出了T5.1.1,这个版本并没有专门发论文介绍,因此了解的人可能不多,实际上,T5的github中也列出了T5.1.1包含的优化点:

红框是针对模型架构优化的一些点,其中T5.1.1取消了input embedding和最终预测词的classifier的embedding参数共享,在分类层的时候,随机初始化了新的embedding权重单独学习。另外,在预训练的过程中,将dropout都关闭了,原因可能是通过T5的实验,发现T5的学习能力还未达到饱和,因此不需要使用dropout来避免过拟合。最后,T5.1.1在ff层中使用了GEGLU代理了Relu,这块要重点提一下。

GEGLU替换Relu

GEGLU 其实可以称为Gated Gelu,来自于论文

https://arxiv.org/pdf/2002.05202.pdf​arxiv.org/pdf/2002.05202.pdf

具体计算公式如下:

其中,W和V为kernel的参数,b和c为bias。当然,在T5中bias是都去掉的,因此最终的GEGLU应当是这样的:

这篇文章超级短,感觉都不像一篇论文,更像一个实验报告。简单来说,就是针对一些激活函数,引入了gate机制,并针对不同的激活函数,做了实验对比。最终在T5中,发现gated Gelu的效果最好,至于原因,论文并没有给出。文章最后的conclusion也比较搞笑,贴出来给大家看看。

(divine benevolence 我查了词典是神的仁义,各位可以体会一下。。。)

T5-PEGASUS基本原理及tf-serving部署

T5-PEGASUS来自于追一科技以及苏神的研究工作,主体模型还是基于T5.1.1。但是在预训练时,设计了专门针对于中文文本摘要的训练任务,具体的原理我就不赘述了,可以参考文章开头的引用,我这里简单罗列一下核心要素点:

1、tokenizer的优化。模型主要基于的还是Bert的tokenizer,但是在词表中加入了jieba分词之后的前20万个词(在语料中事先统计得到)。在具体分词时,如果碰到词表中的jieba词,则直接将该词分出来,若没有匹配到,则按照原来的中文tokenizer方式来做分词。这样做的好处是在能够充分利用词级别的信息,尤其是在做NLG任务时,词级别的生成质量会比字符级别要好一点。

2、借鉴了PEGASUS的思想,PEGASUS来源于另外一篇针对文本摘要做预训练的论文:

https://arxiv.org/abs/1912.08777​arxiv.org/abs/1912.08777

其思想为将mask的级别拓展到句子,即对于一篇文章,通过一些策略mask掉一些句子,然后用剩余的句子来预测被mask的句子的内容。T5-PEGASUS借鉴了类似的思想,区别在于它设计了一个新的预训练样本构建方式,针对一篇文档,通过一种搜索策略,选择了一定数量的target句子,并用剩余的句子作为source句子,做seq2seq任务。

T5-PEGASUS的效果还是令人欣喜的,尤其是在小样本的情况。我们文本摘要任务的实际标注数据大概在500左右,我们将该模型与GPT-2的中文版进行实验对比。以肉眼看,两者生成的文本在通顺度和内容相关度上都差不多,但是T5-PEGASUS在rouge-1、rouge-2、rouge-l以及BLEU-4上都比GPT-2更好,所有指标基本高1-2个百分点。

tf-serving部署

既然T5-PEGASUS的效果不错,那么接下来就要把模型应用在实际的工程中。最简单的应用方式就是将keras和bert4keras的所有依赖环境都打包到镜像中,在启动服务的时候就将模型加载到内存中,以后每次调用模型进行inference都是从内存中加载模型。这样做除了第一次启动服务加载模型比较耗时外,其他时间调用模型进行生成都不会花费太长时间。

当然,这个生成时间不会太长指的是在有GPU的环境下,但实际上我们线上服务很难有GPU资源供使用,因此必须在纯CPU的环境下部署服务。这个时候,直接用keras调用模型进行生成的时间成本就比较高了。我实际进行了验证,输入一篇256个字符的文本,目标是生成一段最大长度为40的摘要文本,在CPU环境下需要花费30-40秒的时间,这个显然难以接受,因此需要使用更高效的模型部署方式来应用模型。之前,我们有对文本分类任务的模型应用过tf-serving进行部署,最终的性能提升也是比较可观的。最终,我决定还是使用tf-serving来部署T5-PEGASUS。

要成功使用tf-serving来部署T5-PEGASUS,需要解决几个问题:

1、模型训练代码基于keras,并非直接使用tensorFlow,因此需要将keras训练保存的模型转化成适配tf-serving的格式。

2、T5-PEGASUS本质上是一个seq2seq模型,包含encoder和decoder,两个模块都是一个独立的keras的model,如何将其应用到tf-serving上也是需要考虑的问题。

下面针对上述两个问题,简单介绍一下我们如何使用tf-serving来部署T5-PEGASUS。

keras-model转tf-serving-pb格式

这部分主要是讲keras保存的模型文件转化为tf-serving支持的pb格式,一个标准的tf-serving-pb格式应当如下图所示:

除了已经freeze的pb模型文件之外,还要将meta graph信息以及variable权重参数的数据放在对应的variables目录中。还需要指定模型的版本作为模型目录(上图中的1581428796)。

为了得到上述格式的模型文件,我们最终需要做如下的转换操作。

首先,我们要加载训练好的keras的model,并利用tensorFlow中的api将模型计算图中的所有变量参数都freeze,并保存为静止的pb文件。此时这个文件还不符合tf-serving的要求,还需要将静止的pb文件转化为tf-serving支持的格式,之后就可以调用tensorFlow中tf.saved_model.builder.SavedModelBuilder对象的add_meta_graph_and_variables方法,将tf-serving需要的meta graph信息和variables信息进行保存。具体的代码可以参考这个博客:

如何将keras训练好的模型转换成tensorflow的.pb的文件并在TensorFlow serving环境调用​blog.csdn.net/mouxiaoqiu/article/details/81220222

有的同学可能会说keras以及tf2.0不是可以直接使用api将h5模型转化为pb模型吗?正如这个GitHub的issue所述:

https://github.com/ZhuiyiTechnology/pretrained-models/issues/5​github.com/ZhuiyiTechnology/pretrained-models/issues/5

里面贴上了如何将simBert转化为tfserving-pb的api。不过使用相同做法转化T5-pegasus会报如下类似错误

keras can’t pickle _thread.rlock

经过一段时间的研究和debug,我发现是由于T5-PEGASUS的decoder中使用了lambda layer,上述api无法序列化一个lambda layer,具体代码如下图所示:

该代码位于final_layer之中,应用在classifier layer之前。由于T5里面,会将分类层中定义的权重output embedding与input embedding中的权重参数相关联(并非share参数,而是统一用一个词表),因此在分类层之前需要对decoder的output做rescaling。

这块rescaling的原因我感觉还是为了保持模型在训练中的稳定,不过我在finetune阶段把这块计算去掉了,最后训练也收敛了,且生成的文本效果并未变差。因此rescaling应该是对预训练过程比较重要的计算。如果其他同学对这块有自己的见解,欢迎评论。

另外,我们尝试单独转化encoder是成功的,这也从侧面证明了是由于decoder的存在导致不能使用上述方法来转换。

使用tf-serving部署T5-Pegasus的encoder和decoder

我们之前对文本分类使用tf-serving进行部署的时候,通常只会封装一个模型、一个接口,因为文本分类模型通常只会有一个输入和一个输出。但是对于seq2seq模型来说,包含encoder和decoder两个子模块,两个子模块的输入和输出都不相同,且decoder还会依赖于encoder的输出,因此有必要将两个子模块分离出来,各自单独封装成一个独立接口。

对于encoder来说,其输入是source文本,输出则是编码了source文本语义信息的模型输出。其签名定义可以用如下代码表示:

其中,需要根据graph中相应tensor的name找到input和output对应的tensor

对于decoder来说,其输入包含了encoder输出的source文本语义信息输出以及前t-1个timestep已经生成的文本(若是起始位置,则会输入一个起始标志符表示开始生成文本)。其签名定义可以用如下代码表示:

高效GRPC调用tf-serving服务生成文本

通过上面的流程,我们已经生成了适配tf-serving的pb模型,可以使用docker来启动模型服务了,这里需要注意一点就是tf-serving的镜像版本最好使用nightly版本,使用其他版本或多或少都会遇到一些版本问题。

最后一步,我们需要编写代码调用tf-serving的模型服务。tf-serving支持http协议的restful api调用以及GRPC的client-server调用。http协议比较容易实现,但是在数据传输的效率上面还是不如GRPC协议的,因此我一般比较推荐使用GRPC协议,能够获得更好的性能。要使用GRPC协议就需要编写对应的client代码,这块可以参考很多博客的介绍,我推荐这个博文:

Improve Tensorflow Performance by 70% | Mux blog​mux.com/blog/tuning-performance-of-tensorflow-serving-pipeline/正在上传…重新上传取消

可能需要用科学上网才能访问。它在博文里面介绍了很多能够提升tf-serving服务调用性能的优化方法,包括对tf-serving进行适配物理硬件的编译、设置一些tf-serving的启动参数等等。它还介绍了如何从client端来进一步提升inference性能的方法,简单来说就是client端使用的tensorFlow prediction api基本上都视作一个protobuf 程序段,如下所示:

  1. tensorflow/serving/
  2. tensorflow_serving/apis/model.proto
  3. tensorflow_serving/apis/predict.proto
  4. tensorflow_serving/apis/prediction_service.proto
  5. tensorflow/tensorflow/
  6. tensorflow/core/framework/resource_handle.proto
  7. tensorflow/core/framework/tensor_shape.proto
  8. tensorflow/core/framework/tensor.proto
  9. tensorflow/core/framework/types.proto

正常情况下client端会加载所有tensorFlow和tensorflow_serving库,这个过程会带来很大的时延,因此我们可以预先将prediction所需要的依赖库抽出来,然后在代码中只加载这些库,从而大大提升client端的效率。具体做法就是将上述的proto文件使用grpcio.tools.protoc接口将proto转化为python实现,接下来我们就可以直接import这些转化后的库。

client代码编写注意点

最后一步就是编写client代码,调用tf-serving来生成文本了。T5-PEGASUS基于seq2seq架构,最后的文本生成采用beam-search方法,它的生成过程是step-by-step的。每个step都会依赖前面历史step的生成结果。大家可以参考这个issue里面的方法,它采用的是http协议访问tf-serving,我们只需要将remote_call中调用tf-serving的方法转化为client风格的调用就可以了:

让bert4keras使用Tensorflow serving调用模型 · Issue #194 · bojone/bert4keras​github.com/bojone/bert4keras/issues/194正在上传…重新上传取消

详细的代码我就不贴出来了,这里主要提两点编写client代码需要注意的问题:

1、创建grpc通道的时候(grpc.insecure_channel),需要指定grpc的max_send_message_length以及max_receive_message_length。因为T5-PEGASUS的encoder-output的张量还是挺大的,默认的grpc最大传输消息长度似乎只有4 MB,如果使用默认的长度限制,会报错,显示传输消息长度大于最大限制。因此,需要设置上面两个参数,不能设置太大,也不能设置太小。

2、调用decoder的时候,需要指定decoder_input的shape。这里要注意每个step的decoder_input的序列长度都会不同,因此需要动态获取其shape,之前没注意,用了一个常数值填充了代表序列长度的那个维度,导致最后生成的文本几乎都是相同的字符。

综上,通过tf-serving的部署以及一系列性能优化,我们在实际的模型部署应用时,将文本生成的时间从一开始的30多秒缩减到了5秒左右,对于当前场景基本可用。

模型以外的一些优化

之前已经提到,T5-Pegasus的文本生成效果还是比较可观的,不过经过业务老师的一些质检,也发现了很多小问题,这些问题有的能够通过增加相应的训练样本来解决,有些则需要进行一些后处理。

例如下面这段文本:

19凤凰机场cp001”寻求展期,318日召开持有人会议

业务老师要求债券的名字尽可能还原原始的描述,包括英文字符的大小写。这个债券名字中的cp应该是大写的。但是T5-Pegasus中,对于大小写是不敏感的。我尝试过用大小写敏感来finetune,但是效果会变坏,原因应该是在预训练的时候模型是以大小写不敏感的规则来学习的,因此在finetune时需要与其保持一致,否则就需要自己做预训练。在没有条件预训练的情况下,我们可以针对性得做一些后处理。

具体来说可以通过引入债券名字识别的服务,将债券名字从原文中提取出来,并与摘要中的债券名字做比对,若两个字符串只有英文字符不同,且是大小写不同,则直接用原文抽取出来的债券名字替换摘要中的名字。

从上述问题也可以看出模型并不能handle一切,此时就需要算法工程师对相应的bad case进行分析,并引入其他的模块来解决问题,另外也需要跟质检人员和业务老师保持紧密沟通,随时跟进需求。

小结

本文主要分享了我们在如何将T5-Pegasus应用在生成式摘要的任务当中。本文除了介绍T5-Pegasus的基本原理之外,也详细介绍了如何将seq2seq模型部署并调用进行文本生成的过程。从这次实践中,我们更加体会到了算法工程师除了研究算法模型外,还需要熟悉模型的实际应用以及相关的技术流程,只有这样才能更好得发挥算法模型的能力。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/359001
推荐阅读
相关标签
  

闽ICP备14008679号