信息压缩是自然语言处理中的经典难题,LLM加持下的摘要与关键词提取虽然流畅,但细节丢失问题反而更隐蔽。一个合同条款摘要可能漏掉金额单位,一条新闻的关键词可能丢失时间限定,这些错误在人工评估时容易被忽略,却会让自动化流程产生连锁反应。要解决这个问题,需要先区分摘要和关键词两种任务的丢失模式。

压缩丢失细节的四种典型表现与根因
摘要任务中最常见的丢失对象是数值、单位、否定词、条件从句和实体关系。比如原文写的是「若甲方未按期支付,则不承担后续利息」,模型可能简化成「甲方不承担利息」,把条件句和否定范围完全扭曲。再比如金融新闻里出现「约3.5亿元」,摘要写成「大额资金」,虽然语义方向没错,但金额量级和单位已经消失,后续做量化分析时就会出错。
关键词提取同样存在隐性丢失。基于词频或共现的算法天然偏向高频实体,像「合同」「甲方」「乙方」这类词很容易被选中,而低频但关键的限定词如「不」「仅」「除外」往往被丢弃。生成式关键词提取则可能产生原文中根本不存在的词,比如把「新能源补贴退坡」提取成「新能源政策」,丢失了退坡这一核心变化。
从根因上看,注意力窗口截断只是表层问题。更深层的原因是自回归解码的目标函数倾向于生成流畅、通用的表达,而不是逐字忠实于原文。模型没有显式的对齐机制,不知道哪些细节对下游任务至关重要,只能按语言概率猜测。因此,解决细节丢失不能只靠调大窗口或换大模型,而需要从任务设计上引入约束和对齐信号。
分层摘要与约束注入:让模型知道什么不能丢
分层摘要的思路是把长文本先切成可处理的块,每块独立生成局部摘要,再把这些局部摘要拼接成第二层输入,生成最终摘要。这样每个块都能获得足够的注意力,数值和实体在局部阶段更不容易被吞掉。实现上可以使用递归方式,控制每块长度在模型可稳定处理的范围内,并在相邻块之间保留一定重叠,避免边界信息断裂。
下面的Python示例演示了如何用OpenAI接口对单个文本块生成带约束的局部摘要。约束列表里显式列出了必须保留的槽位,比如日期、金额、主体和否定词。
import openai
def summarize_chunk(chunk: str, constraints: list) -> str:
constraint_text = "、".join(constraints)
prompt = (
"请对以下文本生成局部摘要,必须保留这些约束信息:"
f"{constraint_text}\n\n原文:\n{chunk}"
)
resp = openai.ChatCompletion.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
temperature=0.1
)
return resp["choices"][0]["message"]["content"]
约束注入的另一种做法是利用结构化输出。例如要求模型返回JSON,字段固定为摘要、关键日期、金额、主体、否定词列表。这样即使摘要正文简化了细节,结构化字段仍然保留了关键事实,下游系统可以优先消费字段。实践表明,显式列出不可丢失的槽位比单纯说「请保持忠实」有效得多,因为模型会把这些槽位当作显式目标去优化。
分层摘要的代价是错误可能在前层被固化,后面无法修正。因此可以在第二层汇总时再次应用约束注入,并让模型列出它认为最不确定的细节,由人工或规则快速复核。
关键词提取的对齐策略与打分修正
抽取式关键词直接从原文中选取,好处是不会产生新词,但容易漏掉语义上重要却低频的词;生成式关键词灵活,却可能脱离原文。一个折中方案是先用生成式模型提出候选关键词,再用向量相似度将这些候选词与原文片段对齐,过滤掉无法在原文中找到语义支撑的词。
下面的代码使用sentence-transformers计算候选关键词与原文的余弦相似度,只保留相似度超过阈值的词。阈值通常设置在0.4到0.6之间,过低会放过噪声,过高会误杀同义表达。
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
def filter_keywords(keywords, source_text, threshold=0.5):
src_emb = model.encode(source_text)
kept = []
for kw in keywords:
kw_emb = model.encode(kw)
sim = util.cos_sim(kw_emb, src_emb).item()
if sim > threshold:
kept.append(kw)
return kept
对齐之后还需要修正打分。传统TextRank或TF-IDF偏向高频词,而LLM生成的关键词更看重语义重要性。可以把两者分数加权融合,并额外增加对否定词和程度词的加权,例如检测到「不承担」时,把「不承担」整体作为一个关键词,而不是拆成「不」和「承担」。这样能避免关键词列表因拆词而丢失否定语义。
另一种做法是让LLM输出关键词的同时给出每个关键词对应的原文支持片段,如果模型无法给出片段,则说明该关键词可能是臆造的。这个信号可以用于自动降权,也可以在人工审核界面中高亮提示。
工程实践:滑动窗口、自洽性校验与评估指标
对于超长文档,滑动窗口是保证细节不丢的基础手段。窗口长度可以按照模型最优处理长度设置,相邻窗口之间保留10%到20%的重叠。每个窗口独立生成摘要或提取关键词后,在合并阶段再次去重并保留所有出现过的数值和实体。这样即使某个窗口边界切断了实体关系,重叠部分也能提供恢复机会。
自洽性校验是另一个有效防线。先让模型生成摘要,再让它根据摘要回答从原文中预先抽取的关键问题,例如「合同金额是多少」「违约责任由谁承担」「事件发生在什么时间」。如果答案与原文不一致,就说明摘要丢失了对应细节。下面的代码演示了一个简单的自洽性校验流程。
import openai
def verify_summary(original_text: str, summary: str, question: str) -> bool:
prompt = (
f"根据以下摘要回答问题,如果摘要中没有足够信息则回答'无法确定'。\n"
f"摘要:{summary}\n问题:{question}"
)
resp = openai.ChatCompletion.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
answer = resp["choices"][0]["message"]["content"].strip()
return answer not in ("无法确定", "未提及")
评估环节不能只看ROUGE分数,因为ROUGE对同义替换不敏感,但对数值和否定词丢失非常敏感。建议额外使用BERTScore和FactCC这类指标,前者衡量语义相似度,后者专门检测事实一致性。更重要的是构建一个小型黄金标注集,里面包含大量带数值、否定词和实体关系的样本,每次模型或流程变更后都跑一遍回归测试。
人工抽检时也应把注意力集中在四类细节上:数值、时间、否定词、实体关系。只要这四类细节的保留率持续提升,摘要和关键词在下游任务中的可用性就会明显改善。压缩细节丢失不是一个可以一次性解决的问题,而是需要分层约束、对齐过滤和自洽性校验共同作用的持续优化过程。