SPACES:“抽取-生成”式长文本摘要(法研杯总结)
By 苏剑林 | 2021-01-01 | 232129位读者 |“法研杯”算是近年来比较知名的NLP赛事之一,今年是第三届,包含四个赛道,其中有一个“司法摘要”赛道引起了我们的兴趣。经过了解,这是面向法律领域裁判文书的长文本摘要生成,这应该是国内第一个公开的长文本生成任务和数据集。过去一年多以来,我们在文本生成方面都有持续的投入和探索,所以决定选择该赛道作为检验我们研究成果的“试金石”。很幸运,我们最终以微弱的优势获得了该赛道的第一名。在此,我们对我们的比赛模型做一个总结和分享。
在该比赛中,我们跳出了纯粹炼丹的过程,通过新型的Copy机制、Sparse Softmax等颇具通用性的新方法提升了模型的性能。整体而言,我们的模型比较简洁有效,而且可以做到端到端运行。窃以为我们的结果对工程和研究都有一定的参考价值。
赛题分析 #
观察、分析任务数据是NLP的第一步,也是相当重要的一步,它关系到我们后面的模型选择,也关系到后面的提升方向。
统计信息 #
这次比赛官方共提供了9484个标注样本,以“(原文, 摘要)”这样的数据对形式出现,原训练数据还附带了其他的一些辅助标注信息,但为了模型的通用性,我们没有用这些辅助信息,所以我们的模型原则上适用于所有单条样本格式为“(原文, 摘要)”的监督式摘要任务。
下面是训练数据的一些统计信息:
1、总量:9484;
2、输入:平均字数 2568,字数标准差 1122,最大字数 13064,最小数字 866;
3、输出:平均字数 283,字数标准差 36,最大字数 474,最小数字 66;
4、指标:以词为单位的加权Rouge。
因此,简单来说这大概就是一个“输入3000字、输出300字”的文本生成任务,其难度在于两千多的平均长度远远超出了我们平时处理的文本长度。9484是全部发布的数据集,网上目前可以下载到的第一阶段的数据是4047条,其实也可以直接跑出还不错的效果,整个模型对数据量的依赖不是特别严重,读者不用太纠结数据量的问题。
样本预览 #
上图演示的是训练集的某个样本,其中上面是输入(裁判文书原文),下面是输出(人工标注的摘要),其中绿色部分标注的是两者的“最长公共子序列”。可以看到,输出跟输入是高度重合的。
建模思路 #
综合上述数据特性,我们不难想到应该采取“抽取+生成”相结合的方式进行摘要,并配合一些新方法来保证摘要的忠实程度与提升最终的效果。最终的模型笔者我们命名为SPACES:
S:Sparse Softmax(新设计的Softmax替代品);
P:Pretrained Language Model(预训练模型);
A:Abstractive(抽象式,即生成式);
C:Copy Mechanism(新设计的Copy机制);
E:Extractive(抽取式);
S:Special Words(将特殊词添加到预训练模型)。
很显然,这是笔者“煞费苦心”强行拼凑的(捂脸),对应于本博客的域名之一“spaces.ac.cn”。不过,上述缩写确实已经把我们的模型的主要技术点都罗列出来了。下面我们将仔细介绍SPACES为何物。
抽取模型 #
这一节我们将对抽取模型部分做一个简要介绍。抽取模型的思路是先通过规则将原始的生成式语料转化为序列标注式语料,然后用笔者常用的DGCNN模型来建模。
语料转换 #
首先,我们需要记住的是,抽取模型只是过程而不是结果,我们还要把抽取的结果送入到Seq2Seq模型优化。因此,抽取模型的原则是“求全”,即尽量把最终摘要所需要的信息覆盖到。为此,我们按照如下规则将原始训练语料转换为抽取式语料:
1、自行构建分句函数,使得句子的颗粒度更细;
2、人工摘要的每个句子,都在原文中匹配与之相似度最高的那个句子(可以重复匹配);
3、将所有匹配到的原文句子作为抽取句子标签;
4、删掉部分匹配出来的句子,使得与人工摘要的Rouge得分最高。
注意,我们在最终模型中删掉了第4点,而它本来是我们最初版模型的默认选择。事实上,加上第4点有利于提高抽取模型的指标,但是综合生成模型后最终得分反而下降了。这不难理解,生成模型本来有删改功能,而且比抽取模型做得更好;如果抽取模型意外地把本应该抽取的关键句子删掉了的话,那么生成模型就很难把它恢复出来了,从而导致性能下降。也就是说,第4点不满足抽取模型的“求全”原则,我们应该把删改工作教程生成模型来做,不应该放到抽取模型中。
指标问题 #
上述转换流程涉及到一个“相似度”的选择,根据前面的介绍,本次比赛选择“以词为单位的加权Rouge”作为评测指标,因此我们可以直接选择这个加权Rouge作为相似度指标。事实上,我们一开始确实是这样做的,但是后来在调试的时候发现,这样并不是一个好的选择,我们最终选择的是“以字为单位的加权Rouge”。
这两者有什么区别呢?对于官方以词为单位来算评测指标的做法,我们也不难理解其目的,就是为了使得专有名词能够完全匹配上,比如本来是“中国人民共和国未成年人保护法”,你预测成了“中国人民共和国文物保护法”,如果以字为单位的话,最长公共子序列为“中国人民共和国...保护法”,至少还是算对了大部分,但是如果以词为单位的话,两者就是不同的词,因此算全错。因此,以词为单位有利于专有名词匹配得更精准。
然而,以词为单位会带来一个严重的副作用,那就是降低了长词的权重。比如“根据 《 中国人民共和国未成年人保护法 》 的 有关 规定”中,核心词“中国人民共和国未成年人保护法”的权重仅为1,剩下的“根据”、“《”、“》”、“的”等我们认为无关紧要的词权重分别都为1,占了大部分,这样一来,模型宁愿去匹配“根据”、“《”、“》”、“的”等词,也不愿意去拟合核心词“中国人民共和国未成年人保护法”了。说白了,以词为单位的话,得分高的摘要未必是有什么关键信息的摘要。
那怎么调和两者呢?事实上,最好的方案应该还是以词为单位,但是算指标的时候,按照字数跟每个词加权,比如“中国人民共和国未成年人保护法”,匹配不上就给0分,匹配对了就给14分(因为有14个字)而不是1分才好。不过,这需要自己来实现Rouge计算函数,有点麻烦,我们最终是直接选择以字为单位来算加权Rouge,这也勉强够用,因为在转换语料的时候,我们知道摘要和原文都是在描述同一件案子,因此基本不会出现“中国人民共和国未成年人保护法”预测成“中国人民共和国文物保护法”的情况。
模型结构 #
回到模型方面,我们使用的是以句为单位的序列标注模型作为抽取模型,句向量部分用“BERT+平均池化”来生成,并固定不变,标注模型主体方面则用DGCNN模型构建。关于DGCNN模型,请参考《基于CNN的阅读理解式问答模型:DGCNN》、《开源一版DGCNN阅读理解问答模型(Keras版)》、《基于DGCNN和概率图的轻量级信息抽取模型》等。
值得指出的一个细节是,在训练抽取模型的时候,我们是以0.3为阈值做EarlyStop的,但最终以0.2为阈值构建生成模型的数据,依据还是前面说的抽取模型的原则是要“求全”。
输出数据 #
我们需要将原文作为输入,通过抽取模型输出抽取摘要,然后把抽取摘要作为生成模型的输入,来输出最终摘要。但是,这有一个问题,训练的数据我们都是见过的,但我们真正预测的是未见过的数据,如果直接训练一个抽取模型,然后用该模型抽取训练集的摘要,那么很明显由于都被训练过了,抽取出来的摘要分数肯定会偏高,而新样本的效果则会偏低,造成训练预测的不一致性。
这时候的解决方案就是交叉验证了。具体来说,我们将标注数据分为$n$份,其中$n-1$份训练抽取模型,然后用这个抽取模型预测剩下的那份数据的抽取摘要,如此重复$n$遍,就得到全部数据的抽取摘要,并且尽可能地减少了训练和预测阶段的不一致性。
生成模型 #
生成模型是我们投入主要时间的部分,也是我们的主要贡献点。生成模型就是一个Seq2Seq模型,以抽取模型的输出结果作为输入、人工标注的摘要作为输出进行训练,我们可以理解为是对抽取结果做进一步的“润色”。
模型总览 #
如果用一张图概括我们的生成模型,那么大概如下:
接下来我们会介绍模型的各个模块。
基础架构 #
Seq2Seq模型依然选择了经典的UniLM(参考《从语言模型到Seq2Seq:Transformer如戏,全靠Mask》),并且考虑到“输入+输出”的总长度基本上都超过512了,所以选择华为的NEZHA模型作为基础模型架构,因为NEZHA使用了相对位置编码,不限长度。
当然,这是当时的选择,现在的话我们至少还有如下两个选择:
1、参考《层次分解位置编码,让BERT可以处理超长文本》中的直接延拓绝对位置编码的做法,使得BERT有能力直接处理更长序列(理论上可达26万),自然也可以用于“BERT+UniLM”中;
2、使用《那个屠榜的T5模型,现在可以在中文上玩玩了》介绍的多国语言版T5模型(mT5),它用的也是相对位置编码,不限长度,但要注意T5用的tokenizer会将全角逗号转为半角逗号,这会导致评测分数下降。
此外,在使用预训练模型方面,我们首创地将部分词语加入到了NEZHA模型中,改变了中文预训练模型以字为单位的通用选择,这使得模型的效果和速度都有一定的提升。这部分结果已经发布在之前的文章《提速不掉点:基于词颗粒度的中文WoBERT》之中,读者可以移步参考。
BIO Copy #
Copy机制在摘要生成模型中并不新鲜,甚至可以说已经成为了生成式摘要的标配了。常规的Copy机制一般就是《Pointer Networks》的做法,但这种做法有两个不足之处:1、每次只能Copy一个token,不能保证Copy一个连续片段(n-gram)出来;2、实现起来比较复杂,不够即插即用。为此,我们构思了一种新型的Copy机制,暂时称为BIO Copy,它实现起来非常简单,而且具有Copy连续片段的能力。
其实前面的图示已经展示了这种Copy机制,它其实就是在Decoder部分多加一个序列预测任务,即原来Decoder建模的是每个Token的分布$p(y_t|y_{< t}, x)$,现在多预测一个标签分布,变为
\begin{equation}p(y_t, z_t|y_{< t}, x) = p(y_t|y_{< t}, x) p(z_t|y_{< t}, x)\end{equation}
其中$z_t\in\{\text{B},\text{I},\text{O}\}$,含义如下:
B:表示该token复制而来;
I:表示该token复制而来且跟前面Token组成连续片段;
O:表示该token不是复制而来的。
那么,训练时$z$的标签哪里来呢?这里直接采用一种比较简单的方法:算摘要与原文的“最长公共子序列”,只要是出现在最长公共子序列的token,都算是Copy过来的,根据BIO的具体含义设置不同的标签。比如前面图片中的例子,“我 真的 非常 热爱 我 的 祖国”与“我 爱 我 的 祖国”的最长公共子序列“我 我 的 祖国”,其中第一个“我”是单字,标签为B,后面“我 的 祖国”是一个连续片段,标签为“B I I”,其他标签为O,所以总的标签为“B O B I I”。
所以,在训练阶段,其实就是多了一个序列预测任务,并且标签都是已知的,实现起来很容易,也不增加什么计算成本。至于预测阶段,对于每一步,我们先预测标签$z_t$,如果$z_t$是O,那么不用改变,如果$z_t$是B,那么在token的分布中mask掉所有不在原文中的token,如果$z_t$是I,那么在token的分布中mask掉所有不能组成原文中对应的n-gram的token。也就是说,解码的时候还是一步步解码,并不是一次性生成一个片段,但可以通过mask的方式,保证BI部分位置对应的token是原文中的一个片段。
需要指出的是,Copy机制的引入未必能明显提高分数,印象中好像只提升了0.5%左右,但是Copy机制可以保证摘要与原始文本的忠实程度,避免出现专业性错误,这在实际使用中是相当必要的。
稀疏Softmax #
在这次比赛中,我们还发现了一个Softmax及交叉熵代替品,我们称之为Sparse Softmax,我们发现Sparse Softmax可以在相当多的分类问题(包括常规分类问题和文本生成等)中替换掉Softmax,并且效果能得到一定的提升。
Sparse Softmax的思想源于《From Softmax to Sparsemax: A Sparse Model of Attention and Multi-Label Classification》、《Sparse Sequence-to-Sequence Models》等文章,里边作者提出了将Softmax稀疏化的做法来增强其解释性乃至提升效果。但笔者嫌里边的设计太麻烦,于是自己想了一个更简单的版本:
\begin{array}{c|c|c}
\hline
& \text{原版} & \text{稀疏版} \\
\hline
softmax & p_i = \frac{e^{s_i}}{\sum\limits_{j=1}^{n} e^{s_j}} & p_i=\left\{\begin{aligned}&\frac{e^{s_i}}{\sum\limits_{j\in\Omega_k} e^{s_j}},\,i\in\Omega_k\\ &\quad 0,\,i\not\in\Omega_k\end{aligned}\right.\\
\hline
交叉熵 & \log\left(\sum\limits_{i=1}^n e^{s_i}\right) - s_t & \log\left(\sum\limits_{i\in\Omega_k} e^{s_i}\right) - s_t\\
\hline
\end{array}
其中$\Omega_k$是将$s_1, s_2, \dots, s_n$从大到小排列后前$k$个元素的下标集合。说白了,我们提出的Sparse Softmax就是在计算概率的时候,只保留前$k$个,后面的直接置零,$k$是人为选择的超参数,这次比赛中我们选择了$k=10$。在算交叉熵的时候,则将原来的对全体类别$\text{logsumexp}$操作,改为只对最大的$k$个类别进行,其中$t$代表目标类别。
为什么稀疏化之后会有效呢?我们认为这是因为避免了Softmax的过度学习问题。假设已经成功分类,那么我们有$s_{\max}=s_t$(目标类别的分数最大),此时我们可以推导原始交叉熵的一个不等式:
\begin{equation}\begin{aligned}
\log\left(\sum\limits_{i=1}^n e^{s_i}\right)-s_{\max} &= \log\left(1+\sum\limits_{i\neq t} e^{s_i-s_{\max}}\right)\\
&\geq \log\left(1+(n-1) e^{s_{\min}-s_{\max}}\right)
\end{aligned}\end{equation}
假设当前交叉熵值为$\varepsilon$,那么解得
\begin{equation}s_{\max} - s_{\min}\geq \log (n-1) - \log \left(e^{\varepsilon} - 1\right)
\end{equation}
我们以$\varepsilon=\ln 2=0.69...$为例,这时候$\log \left(e^{\varepsilon} - 1\right)=0$,那么$s_{\max} - s_{\min}\geq \log (n-1)$。也就是说,为了要loss降到0.69,那么最大的logit和最小的logit的差就必须大于$\log (n-1)$,当$n$比较大的时候,对于分类问题来说这是一个没有必要的过大的间隔,因为我们只希望目标类的logit比所有非目标类都要大一点就行,但是并不一定需要大$\log (n-1)$那么多,因此常规的交叉熵容易造成过度学习而导致过拟合,而截断之后就不会有这个问题。
在这次比赛中,Sparse Softmax带来的提升可能(没有细测)有2%左右!同时,我们私下还补充做了很多实验,包括NLP和CV的,发现它在大多数任务上都有1%的提升,所以非常欢迎大家尝试!不过,我们也发现,Sparse Softmax只适用于有预训练的场景,因为预训练模型已经训练得很充分了,因此finetune阶段要防止过拟合;但是如果你是从零训练一个模型,那么Sparse Softmax会造成性能下降,因为每次只有$k$个类别被学习到,反而会存在学习不充分的情况(欠拟合)。
其他细节 #
在训练生成模型的时候,我们加入了EMA(权重滑动平均),这能使得训练过程更加稳定,甚至可能提升模型效果。事实上,EMA基本是笔者打比赛的标配,它能让我们省一些调试训练策略的心。
此外,在谈到BIO Copy机制时,我们说到理论上只需要在Decoder处新增一个BIO预测,不过在实际训练的时候,我们同时在Encoder和Decoder处都加了,我们发现这样能提升模型的最终效果。直观来想的话,起作用的原因应该是同时加的话增强了Encoder和Decoder之间的同步性,能够引导Decoder更精准地Attention到Encoder的合理的位置。
至于其他要补充的,还在想,想到了再补充吧。
代码开源 #
SPACES模型的源码已经发布在Github上:
SPACECS:https://github.com/bojone/SPACES
使用说明在Github上也有介绍,这里就不重复了,有问题可以提issue或者留言。开源是技术进步的动力,在非利益相关的情况下,笔者会尽量做到开源,也鼓励大家开源。
可能有读者想看看当前的自动摘要能生成到什么程度了,这里演示一个例子吧(验证集的样本,无人工修改,第一行是原文,第二行是标准摘要,第三行是模型摘要,绿色部分是标准摘要与模型摘要的最长公共子序列):
文章小结 #
本文总结了我们做法研杯司法摘要任务的经验,提出了一个名为SPACES的长文本摘要模型,它通过“先抽取后生成”的方式,结合了我们自研的BIO Copy机制、Sparse Softmax等方法,最终可以得到比较靠谱的摘要结果,欢迎大家交流使用。
转载到请包括本文地址:https://spaces.ac.cn/archives/8046
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Jan. 01, 2021). 《SPACES:“抽取-生成”式长文本摘要(法研杯总结) 》[Blog post]. Retrieved from https://spaces.ac.cn/archives/8046
@online{kexuefm-8046,
title={SPACES:“抽取-生成”式长文本摘要(法研杯总结)},
author={苏剑林},
year={2021},
month={Jan},
url={\url{https://spaces.ac.cn/archives/8046}},
}
January 13th, 2021
苏大神好,请教下训练一般来说训练超长文本的模型选择什么GPU比较好,或是您这边训练超过512字符的文本在训练时用什么GPU。为了减轻显存损耗,您这边一般在训练时是否都会用到显存的重计算或是其他技术吗?是否会舍弃adma优化而选用SGD,或是放弃计算ema等其他trick。不知道能否分享下训练超长文本的一些经验,十分感谢!
我就只有TITAN RTX(24G)和2080TI(11G)可用,没什么选择。减少显存消耗可以考虑用AdaFactor优化器,NLP一般不直接用SGD。我自己也没进行超长文本训练的实验,因此无法提供更多的参考意见了。
January 13th, 2021
你好,我这边在训练的时候,loss一直没有下降好,是因为优化器有问题吗?谢谢
Epoch 7/50
473/473 [==============================] - 170s 360ms/step - loss: 16.3889 - seq2seq_loss: 10.1224 - copy_loss: 3.1333
Epoch 8/50
473/473 [==============================] - 172s 363ms/step - loss: 16.3842 - seq2seq_loss: 10.1297 - copy_loss: 3.1272
Epoch 9/50
473/473 [==============================] - 171s 361ms/step - loss: 16.3847 - seq2seq_loss: 10.1200 - copy_loss: 3.1323
Epoch 10/50
473/473 [==============================] - 168s 355ms/step - loss: 16.3819 - seq2seq_loss: 10.1254 - copy_loss: 3.1282
Epoch 11/50
473/473 [==============================] - 169s 357ms/step - loss: 16.3649 - seq2seq_loss: 10.1126 - copy_loss: 3.1261
Epoch 12/50
473/473 [==============================] - 168s 355ms/step - loss: 16.3880 - seq2seq_loss: 10.1236 - copy_loss: 3.1322
改动了什么吗?确认训练数据是正常的了吗?训练环境有变化吗?我还真没遇到过seq2seq模型训练不收敛的情况...
遇到了同样的问题,loss不收敛,训练环境和数据都没有改动过
我也遇到了这种情况,LOSS一直降不下来。提主解决这个问题了吗?
January 21st, 2021
请教一下抽取模型里面的label标注为0,1代表什么含义?
第一个1是实体首 第二个1是实体尾 0代表不是实体的首或尾
还是有点糊涂,如下面给的例子,sentence是一段话,不是实体,也不在summary里面。
"sentence": "第三人:广州市某联数码科技有限公司原告广州市某数码科技有限公司与被告曾某、第三人广州市某联数码科技有限公司劳动合同纠纷一案,本院于2017年1月23日立案后,依法适用简易程序,公开开庭进行了审理。",
"label": 1
你究竟说的是抽取模型的label,还是训练数据所给的label?训练数据所给的label,是出题方人工标注的、他们自己认为的重要句子,我这个模型中并没有用到。SPACES里边的抽取模型的label,是我们自己用规则构建的,本文已经说得很清楚了。
1代表要抽取出来,0代表不需要抽取。
January 21st, 2021
sparse softmax 测试了一下简单的 自动标题生成,发现效果很差啊,你们有测试吗?在这个任务上?
测试过,效果还不错。
感谢苏神回复,效果是OK的,是我自己的问题,忘记这里的改动了,还是保留了probas
@AutoRegressiveDecoder.wraps(default_rtype='logits')
想了解一下您是怎么改动的?我试着在标题生成中,但是效果不太好
January 22nd, 2021
感谢苏老师的分享,已打赏支持。
January 22nd, 2021
苏神 可以分享一下法研杯2020的数据网 官网挂了 没法下载了
这个自己想办法吧~
现在已经可以下载了
February 15th, 2021
抽取摘要模型结构部分没有体现rouge,请问是在哪用的
在构建抽取式模型的训练语料的过程中用到了加权rouge作为相似度指标。
感谢,我明白了,是因为现在有 文本的人工标注摘要 ,可以通过加权rouge得到作为抽取摘要部分的label。以后有新的语料过来就只能用抽取摘要模型得到 抽取摘要部分的label了。
是的
April 19th, 2021
你好,之前在做的文本风格迁移以及近期的长文本摘要和标题生成都在你的文章里找到很多灵感,非常感谢
去年开始就在考虑长文本生成模型的inference效率问题;以文中提到的unilm模型为例,decoder的每次解码都需要重新做全量的前向运算,但是从模型计算的角度来看,由于mask矩阵的存在 document,t0, ..., tk-1 这部分每一层的hidden_state应该都是一样的(个人认为,想看看您的意见)
参考transformer库的gpt2实现有如下参数,是不是unilm的解码过程也可以改造支持state_cache呢?
past_key_values (:obj:`Tuple[Tuple[torch.Tensor]]`, `optional`, returned when ``use_cache=True`` is passed or when ``config.use_cache=True``):
Tuple of length :obj:`config.n_layers`, containing tuples of tensors of shape :obj:`(batch_size, num_heads,
sequence_length, embed_size_per_head)`).
Contains pre-computed hidden-states (key and values in the attention blocks) that can be used (see
:obj:`past_key_values` input) to speed up sequential decoding.
理论上可以实现,但是我没下定决心去写...(苦笑)
多谢回复!我们在长文本的生成任务发现encoder这部分计算占比太高,线上部署的话估计不得不做了
不过期望利用一些已有的transformer加速方案,可能会基于google官方的代码库来拆分decoder部分(keras实现的bert版本和google原生在op粒度上还是有些出入);后续有结果再继续交流~
有个群友用bert4keras训练好模型,用huggingface写好的gpu版beam_search去推理了,好像还跑通了~
April 21st, 2021
老师您好~我是初学者,请问您这个模型可以迁移到小说章节摘要上吗?如果可以,需要多少语料呢?效果会不会非常差?因为我毕业想做小说摘要,可是在网上搜了很多都没有发现相关的研究,是不是非常不可行所以大家都不做呀?
理论上可以。语料和效果请自己通过实验来确定。
至于没有相关研究的原因,那是因为并不是每个应用场景都一定有人研究的~
April 22nd, 2021
谢谢您的回复~只是我实在搜不到小说的平行语料。想了一下,作者自己也未必会写摘要,想要建立这种语料库太困难了,我还是想想别的课题吧QVQ