用于文本摘要 Pegasus 预训练模型

用于文本摘要 Pegasus 预训练模型

读长文最直接的需求就是摘要。新闻每天成百上千篇,论文和报告更长,人不可能逐字读完,自动摘要就是从一篇长文本里提炼出几句话,把最要紧的信息讲清楚。这件事看着简单,做起来并不容易,它分成两条路线。一条叫抽取式,直接从原文里挑出几句原封不动搬过来拼在一起。另一条叫生成式,让模型像人读完文章后自己动笔,用新的句子把意思概括出来。抽取式简单可靠,但挑出来的句子常常生硬重复。生成式更接近人做摘要的样子,难点也更高,它得真的读懂全文,抓住主旨,还得把话说通顺。

PEGASUS 就是冲着生成式摘要来的预训练模型,Google Research 在 2019 年底提出,论文后来发表在 ICML 2020。它的核心贡献不是网络结构,而是一个专门为摘要设计的预训练任务,叫空位句子生成。接下来,我们把这个任务怎么运作、挑哪些句子来遮、各种挑法效果差多少讲透。读这篇之前,你只需要知道 Transformer 的编码器解码器结构大概怎么工作,明白预训练加微调这套流程是什么意思。

1. 摘要难点

我们先把两条路线的差别说清楚,后面你才明白 PEGASUS 的预训练任务为什么要那样设计。抽取式摘要像划重点,从原文里选几句原样搬进摘要,好处是不会胡说,坏处是选中的句子凑在一起常常前言不搭后语,信息也散。生成式摘要要求模型自己遣词造句,把原文的意思压缩成几句通顺的话,这才是大家想象中机器写摘要的样子。

那生成式为什么难呢?它不光要把话说顺,还得忠于原文,不能自己编造,同时要敢删掉次要信息。传统上做生成式摘要的神经网络,都是在大量人工写好的文档加摘要对上做有监督训练,结构上用编码器解码器的序列到序列模型。可人工摘要数据很贵,能凑出来的规模有限。既然 BERT 那类模型已经证明,在海量无标注语料上先预训练再微调效果很好,自然会有人想,摘要能不能也这么干。

2. 下游对齐

知道了生成式摘要难在哪,再看 PEGASUS 想解决的问题就清楚了。预训练不是把 Transformer 随便丢进语料里跑就行,得先设计一个预训练任务,模型在练这个任务的过程中学到表示。任务设计得越贴近下游要用的活,微调时迁移得越好。猜词这类预训练任务离写摘要还差得远,PEGASUS 的思路是干脆把预训练本身做成一个假的摘要任务,让模型在没有人工摘要的语料上,就把读全文写概括这套动作练起来。

3. 伪摘要构造

没有人工摘要的海量文档,怎么凭空造出一对对文档和摘要呢?PEGASUS 的做法很直接,从一篇文档里挖掉几句重要的话,把剩下的内容喂给编码器,让解码器把挖掉的句子重新写出来。被挖掉的句子叫空位句,它们按原文出现的顺序拼在一起,就成了一份伪摘要。空位句原来在文档里的位置,换成一个掩码标记,告诉模型这里被拿走了。编码器读剩下的句子,解码器照着伪摘要逐词生成,这个输入输出的形态,已经和真正的摘要任务一模一样。

我们拿一段五句的短新闻走一遍。假设文档是下面五句,今天凌晨股市大幅低开,金融板块领跌两市,分析师称外围扰动是主因,成交量比前一交易日萎缩两成,机构建议短期保持观望。按四成比例挖走两句,比如挖走第一句和第三句,编码器看到的就是剩下三句,挖走的两处各放一个掩码标记。解码器要生成的伪摘要,就是第一句加第三句拼起来的那两行。模型练的就是看着三句残篇,把被删掉的重点重新写出来。

空位句占文档总句子数的比例,叫空位句比例,缩写 GSR。三成的意思就是每十句里挖走三句。下面这段无依赖的小脚本,把挑句、拼伪摘要、分别喂给编码器和解码器的过程完整跑一遍。真实论文里挑重要句子用的是 ROUGE 指标,这里用词与词的重合做了个近似,只为把流程演示清楚。

doc = [
    "今天凌晨股市大幅低开",
    "金融板块领跌两市",
    "分析师称外围扰动是主因",
    "成交量比前一交易日萎缩两成",
    "机构建议短期保持观望",
]

GSR = 0.4                       # 空位句比例,挖走多少成句子
m = max(1, round(len(doc) * GSR))

def importance(i):
    # 论文里用 ROUGE-1 F1 衡量这句和全文其余部分的代表性
    # 这里用字符重合数做一个等价的粗略打分,只为演示流程
    rest = set("".join(doc[j] for j in range(len(doc)) if j != i))
    return sum(ch in rest for ch in doc[i])

ranked = sorted(range(len(doc)), key=importance, reverse=True)
gap = sorted(ranked[:m])        # 保持原文顺序拼接伪摘要

print("编码器看到的文档:")
for i, s in enumerate(doc):
    print("[MASK1]" if i in gap else s)

print("解码器要生成的伪摘要:")
print("。".join(doc[i] for i in gap))

# 输出大致为:
# 编码器看到的文档:
# [MASK1]
# 金融板块领跌两市
# [MASK1]
# 成交量比前一交易日萎缩两成
# 机构建议短期保持观望
# 解码器要生成的伪摘要:
# 今天凌晨股市大幅低开。分析师称外围扰动是主因

跑一遍就看清了。左边是编码器吃进去的残篇,右边是解码器要背出来的伪摘要。真实训练时,损失拿解码器逐词生成的结果和这几句原文算交叉熵,再反向传播更新全部参数。模型练得多了,它没法靠记住某句话的位置偷懒,必须学会从上下文里推断哪几句最重要,再把它们重新组织成通顺的话,这正是生成式摘要要的本事。

4. 句子选择

伪摘要的好坏,直接取决于挖走的是哪几句。随机乱挖,模型练的就是把边角料拼回去,对写摘要帮助有限。论文系统比了三种挑法。第一种 Random,从 n 句里随机拿 m 句,纯粹当对照基线。第二种 Lead,拿最前面 m 句,新闻写作里第一句往往就是要点,这个偏置让它在新闻类数据上不算差。第三种 Principal,按重要性挑,拿每句话和文档其余部分算 ROUGE-1 F1,ROUGE 是看 n 元词组重合多少的指标,分数高说明这句最能代表全文,挑出来的句子就最像人工摘要里会保留的重点。

Principal 这一种又拆出四个变体,差别在两个选择上。一个是怎么挑,Ind 是把每句话单独打分,直接取分数最高的 m 句。Seq 是贪心逐句挑,挑进一句之后,把它算进已选集合再重新打分,目标是让凑出来这一组整体最能代表全文,而不是单句各自最优。另一个是怎么算 ROUGE,Orig 按原始方式数 n 元组,重复出现的算多次。Uniq 先把 n 元组去重再数,重复啰嗦的句子不会因为反复出现而虚高。两个维度两两组合,就是 Ind-Orig、Ind-Uniq、Seq-Orig、Seq-Uniq 四种。加上 Random 和 Lead,一共六种挑法对照。

挑法怎么选特点
Random随机取 m 句最弱的对照基线
Lead取最前 m 句吃新闻首句偏置,换文体就掉队
Ind-Orig每句独立算 ROUGE-1 F1 取前 m,重复 n 元组照数论文最终选用
Ind-Uniq单句独立打分,n 元组先去重
Seq-Orig贪心逐句把代表整体的句子加进来
Seq-Uniq贪心逐句加选,n 元组去重

5. 策略与比例

六种挑法到底谁强呢?论文用一个 2.23 亿参数的缩小版模型,在 XSum、CNN/DailyMail、WikiHow、Reddit TIFU 四个风格差异很大的数据集上做了消融实验。结论很明确,Ind-Orig 最好,Seq-Uniq 第二,这两个按重要性挑句子的,稳定压过 Random 和 Lead。Lead 在两个新闻数据集上表现还行,到了两个非新闻数据集明显掉队,正好说明首句最重要这件事只是新闻文体的偏置,不能到处套用。正式的 5.68 亿参数大模型,最终就定了 Ind-Orig。

挑法定了,空位句比例 GSR 就是另一个关键旋钮。GSR 太低,挖走的句子少,任务太简单,模型练不出概括能力。GSR 太高,挖走的句子太多,剩下的上下文撑不起生成。论文从 15% 一路扫到 75%,发现最优值都不超过一半,而且不同文体偏好不一样。

下游数据集较优的空位句比例
CNN/DailyMail15%
XSum、Reddit TIFU30%
WikiHow45%

正式大模型没有逐套数据集各训一个,而是统一按三成的有效空位比例预训练。最后说一个容易被网上示意图误导的地方。BERT 那种遮住词让模型猜词的 MLM 目标,作者试过单独用,也试过和空位句生成一起用。结果单用 MLM 差一大截,MLM 加空位句生成也没比 Random 强多少,等于白加。所以正式大模型只留了空位句生成这一个目标。那张画着同时遮词又遮句的图,只是说明两个目标技术上能怎么叠加,不是最终上线的方案。

我们回到开头。PEGASUS 没有发明新结构,它赢在预训练任务和摘要任务长得一样。从文档里抽走最重要的几句拼成伪摘要,让编码器读剩下的、解码器重写挖走的,模型在无标注语料上就把读全文抓重点这套功夫练熟了。挑句子用 Ind-Orig,空位比例定三成,MLM 帮不上忙就果断扔掉。理解到这一步,它的思路你就心里有数了。原论文见 https://arxiv.org/pdf/1912.08777.pdf