文本改写任务看似简单,实际上隐藏着一个容易被忽略的工程难题:模型生成的句子越流畅,越可能为了通顺而牺牲掉原文中某些关键约束。比如把“仅支持Linux系统的服务器”改写成“支持Linux系统的服务器”,少了一个“仅”字,意思就完全变了。本文将重点讨论如何建立一套自动化的校验机制,在不依赖人工逐条核对的前提下,尽量发现改写后语义漂移的问题。

一、改写失真的常见模式与词面指标的局限
改写模型产生语义偏移的方式通常可以归纳为几类。第一类是核心实体被替换或省略,例如把产品名称、地名、人名写成相近但不一致的词。第二类是否定词和程度词遗漏,比如“不支持”“仅支持”“至少”“最多”这类限定成分一旦丢失,改写就会走向相反或过度泛化。第三类是逻辑关系错乱,原文是因果关系、条件关系或转折关系,改写后变成并列关系甚至倒置因果。第四类是数字、时间、单位被改动,例如“8GB”写成“8G”、“下周一”写成“下周”。
很多团队习惯用BLEU、ROUGE或者编辑距离来评估改写质量,但这类词面指标对语义偏移几乎不敏感。一个删除“不”字的改写,编辑距离成本只有1,BLEU值可能仍然很高,因为它只关心n-gram重叠情况。词面指标也无法区分“把A替换成B”与“把A替换成同义词B’”,前者可能改变指代对象,后者只是换一种说法。因此,要真正保障改写不改变原意,必须引入语义层面的校验手段。
二、核心信息保留:基于依存分析与实体约束的抽取校验
核心信息保留校验的思路很直接:先从原文中抽取出不能丢失的关键成分,再检查改写文本中是否仍然包含这些成分。关键成分通常包括命名实体、数字、时间表达式、否定词以及由依存关系确定的主谓宾结构。借助spaCy或HanLP等自然语言处理库,可以快速完成这些抽取工作。下面这个示例展示如何用spaCy提取实体、否定词和数字。
import spacy
nlp = spacy.load("zh_core_web_md")
def extract_core(text):
doc = nlp(text)
entities = [(ent.text, ent.label_) for ent in doc.ents]
negations = [tok.text for tok in doc if tok.dep_ == "neg"]
numbers = [tok.text for tok in doc if tok.pos_ == "NUM"]
return {"entities": entities, "negations": negations, "numbers": numbers}
original = "仅支持Linux系统的服务器,内存至少需要8GB。"
rewritten = "支持Linux系统的服务器,内存需要8GB。"
print(extract_core(original))
print(extract_core(rewritten))
运行这段代码可以看到,原文中“仅”被标注为否定或限定成分,而改写文本中这一项消失。按照实体约束,如果原文的否定词数量与改写不一致,或者某个实体在改写中完全缺失,就可以判定为高风险改写。实际项目中还可以进一步抽取依存关系三元组,比如(服务器,支持,Linux系统),然后判断改写文本中是否保留了同样的主客体与关系。
核心信息抽取的粒度需要根据业务场景调整。新闻类文本可能更关注人物、地点、时间;合同条款则必须严格保留数字、金额、日期和限定条件。建议为每条核心信息分配权重,例如否定词缺失直接标记为严重问题,同义实体替换只做提示。这样可以把校验结果分成不同等级,便于后续人工复核时优先处理高风险项。
三、语义校验:用句向量与NLI模型检测意图偏移
核心信息保留只能发现显性成分丢失,却无法判断改写后的整体语义是否仍然一致。例如“今天气温三十度,适合户外运动”和“今天三十度,可以出去活动”在实体和否定词层面没有差异,但改写把“适合”换成了“可以”,语气略有变化,总体意图一致。这种情况需要句向量相似度来辅助判断。使用sentence-transformers库可以快速计算两个句子的余弦相似度。
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
emb1 = model.encode("今天气温三十度,适合户外运动。")
emb2 = model.encode("今天三十度,可以出去活动。")
similarity = util.cos_sim(emb1, emb2).item()
print(similarity)
句向量相似度适合捕捉整体语义的偏移,但对细粒度的逻辑关系变化不够敏感。比如“产品A上市后,公司股价上涨”与“公司股价上涨后,产品A上市”这两个句子,相似度可能不低,但因果关系完全颠倒。这时需要借助自然语言推理模型,判断原文是否蕴含改写,或者两者是否存在矛盾。Hugging Face上提供了多语言NLI模型,可以直接调用pipeline接口完成分类。
from transformers import pipeline
classifier = pipeline("text-classification", model="joeddav/xlm-roberta-large-xnli")
result = classifier({"text": original, "text_pair": rewritten})
print(result)
NLI模型会输出entailment、contradiction或neutral标签。在改写校验中,理想情况是原文与改写互为蕴含或至少不存在矛盾。如果模型给出contradiction,说明改写改变了某个关键事实或逻辑,必须拦截。将句向量相似度与NLI结果组合使用,可以显著提高校验的准确率,减少误报和漏报。
四、完整校验管线与工程化落地
把核心信息校验和语义校验串起来,就能形成一个自动化的改写忠实度检查函数。下面这个示例整合了实体比对、否定词检查、相似度计算和NLI分类,最终输出风险等级。
def validate_rewrite(original, rewritten):
report = {}
core_orig = extract_core(original)
core_rew = extract_core(rewritten)
report["missing_entities"] = set(core_orig["entities"]) - set(core_rew["entities"])
report["negation_diff"] = len(core_orig["negations"]) != len(core_rew["negations"])
sim = compute_similarity(original, rewritten)
nli_label = nli_classify(original, rewritten)
report["similarity"] = sim
report["nli_label"] = nli_label
report["risk"] = sim < 0.6 or nli_label == "contradiction" or report["negation_diff"] or len(report["missing_entities"]) > 0
return report
在工程落地时,需要注意阈值设定不能一刀切。不同业务对语义偏移的容忍度不同,客服话术可能允许较大幅度的改写,而法律文书则要求几乎逐字保留核心条款。建议根据历史数据标注一批改写样本,绘制相似度分布曲线,选择一个既能拦截问题改写又不会误杀正常改写的阈值。同时,NLI模型的推理耗时较高,适合放在异步任务队列中批量处理,句向量编码可以预先缓存原文向量,避免重复计算。
校验管线也不能完全替代人工,它更适合作为第一道过滤网。对于高风险改写,自动触发人工复核;对于低风险改写,直接放行。这样可以把人工抽检量降低到原来的十分之一甚至更低,同时保证语义偏差不会被漏掉。随着标注数据的积累,还可以训练专门的改写忠实度判别模型,进一步优化整个流程。