最近帮同事调试一个自动摘要的管线,发现他在配置里把“信息抽取”和“文本压缩”这两个模块当成同一个东西来调参,摘要结果一直不对劲:要么抓不住重点,要么句子被砍得支离破碎。这让我想认真聊聊智能摘要里的这对核心概念。它们虽然经常被放在一起说,但在技术实现上完全是两个动作:信息抽取负责搞明白“这篇文本里什么内容重要”,文本压缩负责解决“怎么把内容说得更短更干净”。这篇文章会围绕这两个环节展开,把原理、方法、落地选型和避坑经验都过一遍。如果你正在做文本摘要、信息流内容精炼或者调研报告自动化,这篇应该能直接帮上忙。
1. 先厘清一件事:信息抽取和文本压缩在摘要里到底各干各的活
1.1 摘要不是“复制开头几句”,而是两段式流水线
人写摘要的时候,通常先看完全文,划出重点,然后把重点用自己的话精简表达。机器做智能摘要其实也是流水线:信息抽取先干“划重点”的活,从原文中挑出那些承载核心信息的句子或片段;文本压缩再干“精简表达”的活,把这些候选内容变短、变干净,去掉冗余成分。
很多开发者容易把这两步合并理解,以为“摘要=重要句子拼接”,所以遇到生成结果过长、信息重复、表达啰嗦时,不知道应该在哪一环下手调优。实际排错的时候,我会先问一个问题:结果里的内容是不是都来自原文?如果是,说明问题大概率出在抽取阶段;如果内容来自原文但表达太啰嗦,那就要去查压缩阶段。把两个阶段分开看待,排查效率会高很多。
1.2 抽取、压缩、摘要三者到底差在哪
这三个概念在自然语言处理里经常交替出现,但边界其实很清晰:
| 术语 | 目标 | 输入/输出 | 典型方法 |
|---|---|---|---|
| 信息抽取 | 从原文中定位重要信息 | 输入原文,输出候选片段/句子 | 特征打分、图排序、序列标注 |
| 文本压缩 | 缩短已选内容,保留核心意思 | 输入候选片段,输出更短的表达 | 句法剪枝、删除修饰语、同义替换 |
| 智能摘要 | 最终生成一段凝练且完整的概述 | 输入原文,输出摘要文本 | 抽取式、生成式、两者混合 |
简单说,抽取管“选什么”,压缩管“怎么省”,摘要是二者叠加之后的产物。我见过很多教程把三类混着讲,导致初学者以为TextRank就是摘要的全部,忘记了压缩这一步。实际上,只做抽取不做压缩,摘要往往又长又碎;只做压缩不做抽取,压完的内容又缺少重点。
1.3 混为一谈的代价
把抽取和压缩混为一谈,最直接的代价是调参方向会错。我见过不少项目,做ROUGE评估时只算整篇摘要的分数,却把抽取阶段的错误归因到压缩阶段。比如摘要漏掉关键结论,明明是抽取阶段没选到那句话,却去调压缩参数,结果越调越糟。
反过来也有:抽取阶段选了很长的句子,压缩不足导致摘要超标,也会被误判成抽取失效。正确的做法是先分别评估两个阶段:抽取阶段看候选句的召回率,压缩阶段看压缩率和语义保持率。这样分开看之后,问题在哪个环节基本一目了然。
2. 抽取式摘要是怎么把“重要”从文本里捞出来的
2.1 最朴素的打分公式:词频、位置与线索词组合
抽取式摘要里最经典的方法,就是给每个句子算一个得分,然后按分数取Top N。最简单也最实用的打分公式是三要素组合:
句子得分 = 词频得分 + 位置权重 + 线索词权重
词频得分可以理解成:一句话里如果出现了很多原文里的高频实词,那这句话多半承载了文章主旨。位置权重的意思是,文章首段、每段首句、结尾段往往包含关键信息,这些位置的句子天生就该多加分。线索词则是“综上所述”“重要的是”“实验结果表明”这类提示词,它们往往跟在核心结论前面。
我自己写过一个最简单的Python打分逻辑,大概长这样:
def score_sentence(sentence, word_freq, position_ratio, cue_words): score = 0.0 # 词频得分:句中实词的频率之和 tokens = [w for w in sentence.split() if w not in stop_words] for token in tokens: if token in word_freq: score += word_freq[token] # 位置权重:靠前的句子加分 score += (1 - position_ratio) * 2.0 # 线索词权重 for cue in cue_words: if cue in sentence: score += 1.5 return score这段代码没什么神奇之处,但胜在稳定。词频统计加位置加权,基本上就能把一篇文章的关键句捞个七七八八。线索词的权重可以按业务场景自己调,比如做科技资讯摘要时,“突破”“首次”“最新”这类词的权重可以给高一点。
2.2 TextRank:让句子之间互相投票
词频打分有个天然缺陷:它只看句子和词的关系,没看句子和句子的关系。TextRank就是来解决这个问题的,它的思想来自PageRank:把每个句子当成一个节点,两个句子越相似,它们之间的“投票权重”就越高,最后迭代收敛出每个句子的得分。
句子相似度通常用词重合度或者词向量余弦相似度来计算。词重合度的形式非常简单:
sim = 两句话共现词数 / 两句话平均词数
不用引入什么大模型,跑起来也快。TextRank的好处是,它能区分“重要的句子”和“包含重要词的句子”。举个例子,一篇文章反复出现“收益率”这个词,但真正讲清楚“收益率怎么算”的句子可能只有一句,TextRank通过句间投票,能让这一句在图中获得更高权重。
实际调参时,有两个参数我很在意:一个是迭代次数,一般50到100次就够;另一个是阻尼系数,通常取0.85,和PageRank一致。参数太小,句子得分会偏向局部;参数太大,收敛变慢但结果更平滑,具体得看语料情况。
2.3 MMR去重:别让前三句都在讲同一件事
只按分数从高到低选句子,会经常翻车:分数最高的几个句子,可能是同一段里的“孪生句”,讲的全是同一个例子。那摘要就会变得特别单调。这时候就要引入MMR(最大边际相关)。
MMR的公式理解起来也不难,我直接用大白话解释:它希望选出来的句子,一方面和文档主题相关,另一方面和“已经选中的句子”尽量不重复。两者之间用候选句与已选句的相似度做惩罚,这就是“最大边际相关”里“边际”两个字的由来。
selected = [] for _ in range(top_n): scores = [] for sent in candidates: rel = sentence_doc_similarity(sent, doc_vector) redundancy = max(sentence_similarity(sent, s) for s in selected) if selected else 0 scores.append(rel - lam * redundancy) best = candidates.pop(index_of_max(scores)) selected.append(best)这里的lam是个权衡参数。lam取0的时候不退重,取1的时候重度去重。我在实践中通常选0.3到0.5之间,既能保证内容相关,又能避免摘要变成同一段话的复读。这个技巧对新闻类语料特别有效,因为新闻里的导语、正文、总结经常重复同一个结论。
3. 文本压缩的三种落地手段:删句子、剪短语、换说法
3.1 句子级删除是压缩的第一道闸门
文本压缩最粗粒度的手段,就是直接删除句子。放到摘要场景里,抽取阶段其实已经干了一部分“删句子”的活——从几十个句子里挑出五六个,本质上就是句子级压缩。如果抽取出来之后发现摘要还是太长,可以在候选句内部再做一次次级筛选。
我习惯给句子加一个“信息密度”指标:如果一个句子里的实词数量多、停用词占比低,那它的信息密度就高,更值得保留。反过来,那种满是连接词、感叹词、空话的句子,即使被抽取出来了,也应该在压缩阶段干掉。
3.2 短语级剪枝:站在语法树上动刀
句子级删除之后,压缩粒度再细一层,就是短语级剪枝。它的核心工具是依存句法分析:把句子解析成一棵语法树,主谓宾是树的主干,定语、状语、插入语是枝叶,压缩就是站在树上修剪枝叶。
举一个我试过的例子,原文是“那项在去年年底启动的、由多个研究小组共同参与的技术评估项目,最终如期完成了全部测试环节”。这句话主干只有“项目完成了测试”,但被大量修饰语包裹。通过依存句法剪掉“在去年年底启动的”“由多个研究小组共同参与的”“技术评估”这些定语/修饰成分后,就变成“项目如期完成了全部测试环节”,长度几乎减半,核心意思还在。
剪枝时要注意哪些成分能删,哪些不能删。我整理过一个简单的经验表:
| 可删除成分 | 例子 | 不能轻易删除的成分 | 例子 |
|---|---|---|---|
| 时间状语 | “在2019年”“昨天” | 否定词 | “没有”“不是” |
| 地点状语 | “在上海”“在实验室” | 限定性定语 | “唯一的”“最关键的” |
| 插入语 | “众所周知”“据称” | 核心主语/宾语 | 句子的主干 |
| 冗余修饰 | “非常”“极其” | 数字与单位 | “50%”“3公斤” |
3.3 词汇级改写:同义替换与代词还原
再细一层的压缩,是在词汇级别做动作。同义替换是其中的常见手段:把“迅速降低”替换成“骤降”,把“进行了一个认真的分析”改写成“认真分析”,字数自然就下来了。代词还原则是把“该项目”“这种方案”这类指代,替换成前面出现过的实体名称或类别词,让摘要即使脱离原文也能读懂。
写到这里我必须提醒:词汇级改写的风险是最高的。同义替换容易改变语义强度,“有效缓解”和“解决”之间差着十万八千里;代词还原一旦找错先行词,摘要就出现事实错误。所以我在实际项目里,词汇级改写通常只用在人工后编辑阶段,或者在规则非常明确的情况下才自动化,比如“本公司”统一替换成公司简称。
4. 从抽取到生成:摘要模型为什么绕不开“改写”这一步
4.1 抽取式的天花板:拼出来的摘要缺乏连贯性
既然抽取加压缩已经能产出摘要,为什么业界现在几乎都在搞生成式摘要?原因很简单——抽取式的输出是原文句子的拼接,天然缺乏连贯性。句子之间没有逻辑连接词,读起来像在跳读;指代关系也可能悬空,上一句说“该项目”,下一句已经跳到另一个主题。
我测试过一段用抽取式方法生成的摘要,内容上没错,但可读性很差:“报告指出问题。采用新方法。效果显著。”三个句子之间没有因果关系,读者还得自己脑补。这就是抽取式的天花板。生成式摘要用全新的句子来复述原文,就绕开了这个限制,这也是它成为主流方向的原因。
4.2 Encoder-Decoder结构在摘要里做了什么
生成式摘要的模型基础是Encoder-Decoder结构,它的逻辑可以理解成两个步骤:编码器把原文读成一个语义向量或一组向量表示,解码器再从这个表示中逐个词生成摘要。
训练阶段,模型输入是“原文-参考摘要”这样的配对数据,目标只有一个:让生成出来的摘要和参考摘要尽量接近。这个“接近”通常用交叉熵损失来衡量。推理阶段,模型不再有标准答案,而是一个词一个词地自己预测,直到生成出结束标记。
模型选型上,现在的做法基本都是拿预训练语言模型做微调,而不是从头训练。预训练模型已经掌握了语言的一般规律,微调只需要让它适应“摘要”这个任务。概括类模型在这件事上做得尤其好,因为它们的预训练目标本身就是“把一段话遮住,然后生成”这类接近摘要的任务。
4.3 生成式压缩的本质:理解约束下的改写
生成式摘要看起来和“压缩”没关系,因为它根本不是对原文字面上的删减,而是“重新写”。但如果深看一步,会发现它本质上还是在做压缩:模型在解码的时候,核心动作是放弃不重要的词、合并重复的信息、用更短的表达替换冗长描述。这个过程的本质就是“在理解约束下做改写式压缩”。
很多刚入门的朋友容易陷入一种误区:生成式摘要一定会比抽取式好。实际上模型生成的内容可能出现原文里根本不存在的错误推理,而抽取式至少每一个字都能溯源。我在工程落地时倾向于把两者结合:用抽取式锁定重要句子,再利用生成式模型对句子做压缩改写,这样既有可溯源性,又有连贯性。
5. 评估与落地:ROUGE分数不是唯一答案
5.1 ROUGE是怎么算的,它算不到什么
评估摘要质量,业内最常用的自动指标是ROUGE,核心思想是看“生成摘要”和“参考摘要”之间的词或短语重叠度。ROUGE-1比较的是单元词的重叠,ROUGE-2比较的是相邻词对的重叠,ROUGE-L则基于最长公共子序列,衡量的是整体语序的接近程度。
| 指标 | 比较粒度 | 能反映什么 | 典型短板 |
|---|---|---|---|
| ROUGE-1 | 单个词 | 关键词是否覆盖 | 同义词替换后会虚低 |
| ROUGE-2 | 相邻词对 | 短语级表达是否接近 | 参考摘要少时噪声大 |
| ROUGE-L | 最长公共子序列 | 语序与结构接近度 | 不关心语法正确性 |
我必须强调一个反直觉的点:ROUGE对摘要里“可能的表述方式”非常不敏感。两个摘要语义完全一致,但一个用了“大幅增长”,另一个用了“明显上涨”,ROUGE-1可能直接掉好几个点。所以我从来不把ROUGE当成绝对质量指标,而是把它当成“回归测试指标”——每次改模型或改参数之后,跑一遍ROUGE,如果分数掉了,大概率哪里出了问题。
5.2 一套可复现的两步走落地流程
结合前面说的内容,我落地摘要功能时通常采用“先抽取后压缩”的两段式流水线。核心原因是:抽取能保证摘要内容都源于原文,压缩能保证摘要表达精炼,两者各司其职,出问题也好定位。
具体的流程大概是这样的:
- 文本预处理:分句、分词、去停用词。
- 候选句抽取:用TextRank或MMR选出与文档主题最相关的6到10个句子。
- 句子压缩:对候选句做依存句法剪枝,去掉冗余修饰成分。
- 句子排序:按原文顺序重新排列,保持叙事逻辑。
- 生成式改写(可选):把压缩后的句子交给生成模型,统一改写成流畅的段落。
我实测过,这个流程跑在新闻语料上,ROUGE-L能在0.35到0.40之间浮动,速度和纯生成式相比还快不少。作为一个基础版本的流水线,这个效果已经足够上线试运行。
5.3 长文档场景:分块与层次压缩
长文档的摘要和短文本是两种难度。短文本压缩容易保持全局一致,但长文档有几千上万字,直接整篇丢给抽取模型或生成模型,速度慢而且容易漂移。我的做法是分层处理:先把长文档按语义切块,切块粒度通常控制在三四百字左右,相当于一个自然段或两三段合并;然后对每一块单独做摘要,得到“块级摘要”;最后把块级摘要拼起来,再做一个二次摘要,得到最终的整篇摘要。
分层处理有个额外的好处:中间层的块级摘要天然形成了一套“结果可回溯”的中间产物。用户想知道某条摘要结论出自文档哪里,直接定位到对应块即可。这个特性在调研报告、法律文书这类场景里非常实用。
6. 这几个坑我实测下来最值得注意
6.1 生成式摘要的“复读机”现象
生成式摘要最容易让人崩溃的问题,就是复读机:同一个句子在摘要里连续出现好几遍,或者说来说去都在重复同一条信息。这背后有解码策略的原因——模型在beam search里对结尾标记的概率估计偏低,或者某个片段概率异常高,导致它反复围绕同一段话生成。
我处理复读机问题的经验很简单:先打开解码参数里的重复惩罚项,设置一个合理的重复惩罚系数;如果还有问题,就考虑在候选生成结果里做规则去重,检测连续重复片段并删除。工程上,后者往往比调模型参数更快见效。
6.2 压缩改写带来事实扭曲
这是我认为最隐秘也最危险的问题。文本压缩在简化表达的时候,很容易把限定条件丢掉。举个例子,“该方法在特定条件下将错误率降低了30%”这句话,压缩后如果变成“该方法将错误率降低了30%”,语义就从“特定条件下有效”变成了“普遍有效”,这是事实层级的错误。
应对办法有两层。第一层是解析层面的,压缩规则中要保留否定词、数量词和程度限定词;第二层是检查层面的,做完压缩或生成之后,用实体抽取工具对比原文和摘要的实体及数字,凡是文章里出现的新增实体或数值,都要重点核查。这套“实体一致性检查”我每次上线前都会跑一遍。
6.3 摘要长度的动态控制
摘要长度怎么定,很多人直接设个固定字数上限,比如“最多200字”。但固定上限有个问题:源文本身很短时,摘要会为了凑字数加入废话;源文很长时,200字又装不下核心信息。更合理的做法是让摘要长度随源文长度动态变化,例如取源文字数的四分之一到三分之一,并设置上下限。
我常用的参数组合是:下限100字,上限350字,目标比例0.25。这个组合在新闻和报告语料下效果都不错。要注意,上限的目的是控制成本、保证阅读体验,不是衡量摘要好坏的标尺,所以不用卡得特别死。
6.4 把抽取和生成串成两段式pipeline的经验
最后聊聊我最推荐的做法:两段式pipeline。第一步用抽取式方法把候选句子范围缩小,第二步用生成式模型对候选句做改写压缩。相比纯端到端生成,这种方案有两个明显优势:第一,抽取阶段已经保证了关键信息在候选集内,生成阶段就算随机发挥,也很难把主题彻底带偏;第二,整个流程可以插桩调试,某一步效果不对可以单独替换。
我自己在长文档摘要场景里,会在这个pipeline后面再加一道“标题增强”的步骤:把原文标题当作第一句候选内容喂给压缩模型,因为标题本身就是最强的一句话摘要。实测下来,光是这一步就能让ROUGE-L提高一到两个点,算是性价比极高的技巧。
我在实际使用中还有一个体会:摘要系统的阈值参数,比如抽取句数、MMR的lambda、重复惩罚系数,一定要按语料去调,别指望一组参数通吃所有场景。新闻要突出新鲜信息,论文要保留方法和结论,财经要保留数字趋势。每一次调整都要拿真实badcase来回测,而不是只看ROUGE涨没涨。调了半年摘要之后,我的感受是智能摘要的技术难点从来不在模型选得够不够新,而在于你有没有把“抽取”和“压缩”这两个环节真正分开想清楚。分清楚了,系统就稳了一大半。