NLI 交叉编码器的六种实用方法

0 阅读

什么是 NLI?

自然语言推理(NLI)把语言任务简化成一个方向明确的关系判断:给定一个“前提”(premise)和一个“假设”(hypothesis),模型判断两者是蕴含(entailment)、矛盾(contradiction)还是中立(neutral)。

这听起来抽象,但其实很实用。比如这两句话:

  • 前提:“猫在睡觉。”
  • 假设:“猫没在睡觉。”

从语义相似度看,它们用词高度重合,可能被算作“很像”。但在 NLI 框架下,意思完全相反,会被判为“矛盾”。这种单向逻辑关系比双向相似度更能捕捉因果、对立或事实一致性。

正因为这种结构清晰、解释性强,NLI 模型——尤其是交叉编码器(Cross Encoder)——能被灵活“改装”成多种工具,而不仅限于学术任务。

加载一个模型

本文使用 dleemiller/EttinX-nli-s 这个轻量级 NLI 交叉编码器。它基于 Sentence Transformers 库,适合 CPU 推理,速度较快。

from sentence_transformers import CrossEncoder

model = CrossEncoder('dleemiller/EttinX-nli-s')

premise = "The cat is sleeping"
hypothesis = "The cat is not sleeping"
scores = model.predict([(premise, hypothesis)])

labels = ['contradiction', 'entailment', 'neutral']
predicted_label = labels[scores.argmax()]

运行后,predicted_label 会是 'contradiction'。接下来,我们看看这个简单机制能怎么用。

1. 零样本分类与标签打标

你不一定需要百亿参数的大模型才能做文本分类。用 NLI 交叉编码器写个“假设句”,就能实现零样本分类。

比如,想从一堆用户消息里挑出“投诉”:

texts = [
    "I'm extremely disappointed with my recent purchase. The product arrived damaged.",
    "Thank you for the excellent service! The delivery was fast and exceeded expectations.",
    "Can you please provide information about your return policy?",
    "This is absolutely unacceptable! I've been waiting 3 weeks for my order.",
    "I'd like to schedule a demo of your enterprise software solution."
]

hypothesis = "This text expresses a customer complaint."

for i, text in enumerate(texts, 1):
    scores = model.predict([(text, hypothesis)])
    prediction = ['contradiction', 'entailment', 'neutral'][scores.argmax()]
    tag = "COMPLAINT" if prediction == 'entailment' else "NOT COMPLAINT"
    print(f"{i}. {tag}: {text[:50]}...")

结果准确标出了第 1 条和第 4 条为投诉。关键是那句假设:“这段文字表达了客户投诉。”只要换掉这句话,就能立刻切换到其他分类任务,比如识别咨询、表扬、紧急请求等。

这种方法成本低、部署快,特别适合规则明确但数据稀疏的场景。

2. RAG 中的幻觉检测

在检索增强生成(RAG)系统中,大模型的回答可能“编造”事实。NLI 可以用来验证回答是否忠实于检索到的原文。

做法很简单:把检索到的文档片段作为前提,把回答中的每个事实主张作为假设,逐条检查。

source = "The Eiffel Tower was completed in 1889 and stands 324 meters tall."

claims = [
    "The Eiffel Tower was finished in 1889",           # 应该是 entailment
    "The Eiffel Tower is 300 meters high",             # 矛盾(实际是 324 米)
    "The Eiffel Tower was designed by Gustave Eiffel", # 文中未提,应为 neutral
]

for claim in claims:
    scores = model.predict([(source, claim)])
    labels = ['false: contradiction', 'true: entailment', 'no information: neutral']
    result = labels[scores.argmax()]
    print(f"Claim: {claim} ({result})")

输出显示,第一条正确,第二条被识别为错误,第三条标记为“无相关信息”。这种机制可以嵌入到 RAG 流程中,自动过滤或修正不可靠的回答,提升系统可信度。

3. 多选问答

虽然 NLI 模型不能生成答案,但面对一组候选选项时,它能选出最符合上下文的那个。

思路是:对每个选项,构造一个假设句,比如 “答案是 Google”,然后看哪个选项与上下文的“蕴含”得分最高。

contexts_and_questions = [
    ("Sarah works as a software engineer at Google...", 
     "Where does Sarah work?", 
     ["Google", "Microsoft", "Apple", "Facebook"]),
    # 其他例子略
]

for context, question, candidates in contexts_and_questions:
    pairs = [(context, f"The answer is {answer}") for answer in candidates]
    scores = model.predict(pairs)
    entailment_scores = scores[:, 1]  # 第二列是 entailment 概率
    best_answer = candidates[entailment_scores.argmax()]
    print(f"Q: {question}\nA: {best_answer}")

在测试案例中,模型正确选出了 “Google”、“7:30 PM” 和 “1991”。这种方法适用于知识库问答、考试系统或任何有固定选项的场景,且比调用大模型更省资源。

4. 响应质量评估

我们可以用 NLI 模型充当“裁判”,评估 AI 回答的质量。这里需要更精细的打分,而不仅是三类标签。

例如,定义一个评分维度表(rubric):

  • 清晰度:“回答写得清楚。”
  • 相关性:“回答直接回应了问题。”
  • 安全性:“回答不含毒性语言。”

然后把用户问题 + AI 回答拼成一段文本,作为前提,分别与每个维度的假设句配对:

user_request = "Q: How can I speed up pandas groupby on tens of millions of rows?"
passage = "A: Thanks for the thoughtful question! Performance work can be nuanced..."

RUBRIC = [
    ("clarity", "The answer is written clearly."),
    ("relevance", "The answer specifically addresses the question."),
    ("safety", "The answer does not contain toxic language."),
]

pairs = [(user_request + " " + passage, hyp) for _, hyp in RUBRIC]
logits = model.predict(pairs, convert_to_tensor=True)
probs = torch.softmax(logits, dim=-1)

# 自定义打分函数:entailment 得分高,contradiction 扣分
def hybrid_score(p):
    raw = 0.5 * p[2] + p[1] - p[0]  # neutral*0.5 + entailment - contradiction
    return float(max(0.0, min(1.0, raw)))

per_dim = {name: hybrid_score(p) for (name, _), p in zip(RUBRIC, probs)}

结果发现,相关性得分为 0——因为回答全是套话,没提任何 pandas 优化技巧。这种评估可用于自动筛选最佳回复(best-of-N)、优化提示词,或作为 DSPy 等框架中的奖励信号。

5. AI 安全护栏

同样的思路稍作调整,就能构建内容安全护栏。关键在于把“安全策略”写成正面假设句。

比如要检测一段文本是否包含:

  • 个人身份信息(PII)
  • 非法或有害请求
  • 不当内容

可以这样定义策略:

GUARDRAILS = [
    ("no_pii", "This text does not contain personal identifiable information..."),
    ("no_harmful_intent", "This text does not request illegal, harmful... activities."),
    ("appropriate_content", "This text contains appropriate and safe content."),
]

注意:这些句子描述的是“理想状态”。如果模型判断为“蕴含”,说明内容合规;如果是“矛盾”,则违规。

对一段包含姓名、邮箱、SSN 并请求“黑进前女友账号”的文本进行检测,三个维度得分都接近 0,系统会判定为不安全并阻止处理。

这种方法比关键词过滤更智能,能理解语义意图,适合用于聊天机器人、用户输入过滤或生成内容审核。

6. 教育场景中的自动评分

在教学中,学生常需提交简答题。用 NLI 模型对比学生答案和标准答案,可以快速给出初步评分。

  • 蕴含 → 答案正确
  • 矛盾 → 答案错误
  • 中立 → 部分相关,可给部分分
reference = "Photosynthesis is the process where plants convert sunlight, CO2, and water into glucose and oxygen."

student_answers = [
    "Plants use sunlight to make glucose and oxygen from carbon dioxide and water.",
    "Plants absorb sunlight to create carbon dioxide and release oxygen.",
    "Chlorophyll helps plants absorb light energy for chemical reactions.",
    # ...
]

for i, answer in enumerate(student_answers, 1):
    scores = model.predict([(reference, answer)])
    prediction = ['contradiction', 'entailment', 'neutral'][scores.argmax()]
    grade = "CORRECT" if prediction == 'entailment' else "INCORRECT" if prediction == 'contradiction' else "PARTIAL"
    print(f"Student {i} - {grade}: {answer[:45]}...")

结果准确识别出:第 1 和第 5 条正确(虽措辞不同但意思一致),第 2 和第 4 条错误(颠倒了气体交换方向),第 3 条只提到叶绿素,未涉及核心过程,判为部分正确。

这种方式不能替代教师,但可作为初筛工具,减轻批改负担,尤其适合大规模在线课程。

小结

NLI 交叉编码器不是万能的,但它提供了一种轻量、可靠、可解释的推理方式。通过巧妙设计“假设句”,我们能把同一个模型用于分类、验证、评估、防护等多种任务。在资源有限或需要高确定性的场景下,它往往是比大语言模型更务实的选择。