推理模型在完成复杂任务的同时,也可能把训练语料中潜藏的社会偏见一并学到手。比如问模型“护士通常是男性还是女性”,答案倾向于女性;问“某个族群的犯罪率如何”,输出可能带有明显刻板印象。这类问题一旦出现在面向公众的产品里,轻则用户体验受损,重则触碰监管红线。要解决推理模型输出偏见的问题,需要从数据集层面和训练约束层面双管齐下,形成一条完整的去偏流水线。

一、偏见从哪里来:定位推理模型偏见的根源
推理模型的偏见大致有三个来源。第一是语料偏见,互联网文本本身反映了现实社会的不平衡分布,比如提到CEO的句子中男性名字出现频率远高于女性,模型预训练阶段就会把这些统计规律内化为关联。第二是标注偏见,人工标注数据时,标注者自身的文化背景和价值判断会渗入标签,尤其是涉及情感分析、毒性判断这类主观任务。第三是推理放大效应,思维链式的推理过程会让模型在中间步骤里生成带有偏见的假设,然后基于这个假设继续推导,最终输出比直接回答时偏见更明显。
定位偏见的第一步是做系统化评测。学术界常用的基准包括StereoSet、CrowS-Pairs、BBQ(Bias Benchmark for QA)以及针对中文的CValues等。以BBQ为例,它通过在模糊语境下测试模型是否倾向于选择带有刻板印象的答案来量化偏见程度。检测的核心逻辑很简单:构建一组只在敏感属性(性别、种族、地域等)上有差异的样本对,如果模型对两组样本的输出分布差异显著,就说明存在偏见。下面是一段用于检测偏见倾向的简化代码:
import json
# 构造只改变敏感属性的样本对
sample_pairs = [
{"a": "张女士申请了工程师岗位,她毕业于名校", "b": "李先生申请了工程师岗位,他毕业于名校"},
{"a": "这位护士说他很有耐心", "b": "这位护士说她很有耐心"},
]
def check_bias(model, pairs):
results = []
for pair in pairs:
score_a = model.likelihood(pair["a"])
score_b = model.likelihood(pair["b"])
# 似然差超过阈值说明模型对某个属性有系统性偏好
results.append(abs(score_a - score_b))
return sum(results) / len(results)
bias_score = check_bias(my_model, sample_pairs)
print(f"平均偏见分数: {bias_score:.4f}")
检测出偏见来源和程度之后,才能有针对性地选择去偏策略。一般来说,语料层面的偏见靠数据清洗与重建,推理阶段的偏见靠约束与过滤,两者不可偏废。
二、数据层面的解法:构建高质量去偏数据集
去偏数据集的构建是整个方案的地基。最直接的思路是数据清洗,即使用词表和分类器扫描语料,识别含有明显歧视性表述、刻板印象关联的句子并予以删除或降权。但要注意,简单删除可能带来副作用——某些群体在语料中的提及频率下降,反而导致模型对这些群体的表征能力退化。因此更稳妥的做法是改写而非删除,比如把带有刻板印象的句子改写成中性的、事实性的表述。
第二种重要技术是反事实数据增强(Counterfactual Data Augmentation,CDA)。其核心思想是:对每一条训练样本,把其中的敏感属性替换成其他取值,生成一个“平行世界”的副本,两个副本一起进入训练。例如把“男程序员调试代码”同时生成“女程序员调试代码”,让模型在学习时无法依赖性别属性做推断。反事实增强对消除性别类偏见尤其有效,因为性别词汇有比较清晰的映射表(他/她、先生/女士等)。代码示例如下:
# 简化的反事实数据增强实现
gender_map = {"他": "她", "她": "他", "先生": "女士", "女士": "先生",
"父亲": "母亲", "母亲": "父亲", "男孩": "女孩", "女孩": "男孩"}
def counterfactual_augment(text):
variants = [text]
swapped = ""
for char in text:
swapped += gender_map.get(char, char)
if swapped != text:
variants.append(swapped)
return variants
corpus = ["他是一位优秀的飞行员,技术过硬。"]
augmented = []
for doc in corpus:
augmented.extend(counterfactual_augment(doc))
print(augmented)
# 输出: ['他是一位优秀的飞行员,技术过硬。', '她是一位优秀的飞行员,技术过硬。']
第三种手段是属性平衡采样。在构建训练批次时,统计每个敏感属性组合下的样本数量,通过过采样少数群体、欠采样多数群体的方式,让模型在各属性维度上见到均衡的数据分布。同时可以引入正负样本配对,即在每个职业、每个成就场景下,确保不同群体的人物出现比例接近。实践中通常将清洗、反事实增强和平衡采样组合使用:先清洗掉极端毒性内容,再用CDA扩充中性样本,最后按属性配额组织训练数据,三步下来数据集的公平性会有明显改善。
三、训练与推理层面:公平性约束的落地方法
数据层解决的是“看到什么”,训练层解决的是“学到什么”。公平性约束的主流做法是在损失函数中加入正则项,惩罚模型内部表征中与敏感属性的关联度。具体而言,可以在训练过程中为隐藏层表示接一个敏感属性判别器,训练主任务的同时让表示尽可能“骗过”判别器,也就是所谓的对抗去偏。当模型无法从中间表征中判断出性别或种族信息时,其输出对这些属性的依赖自然下降。损失函数的形式通常是:
import torch
import torch.nn as nn
class FairTrainLoss(nn.Module):
def __init__(self, alpha=0.5):
super().__init__()
self.task_loss = nn.CrossEntropyLoss()
self.disc_loss = nn.BCELoss()
self.alpha = alpha # 公平性约束权重
def forward(self, logits, labels, attr_pred, attr_true):
# 主任务损失:保证模型能力不下降
l_task = self.task_loss(logits, labels)
# 属性判别损失:希望判别器难以识别敏感属性
l_fair = self.disc_loss(attr_pred, attr_true)
# 总损失 = 主任务 - alpha * 公平项,对抗式地削弱属性泄露
return l_task - self.alpha * l_fair, l_task.item(), l_fair.item()
除了对抗方法,还有对比学习路线。把反事实样本对作为正样本对,让模型在表示空间中拉近它们的距离,同时推远不同语义的句子,这本质上是在向量空间中直接抹平敏感属性造成的偏移。这类方法实现相对简单,与指令微调流程兼容性好,适合在算力有限的团队中推广。需要注意的是,约束权重alpha的调节非常关键:权重过大模型会牺牲主任务性能,权重过小去偏效果不彰,一般通过在小规模验证集上扫描0.1到1.0之间的取值来确定。
推理阶段同样可以加一道保险。一种做法是对输出做敏感词与偏见分类器过滤,检测到高风险表述时触发重采样或改写;另一种做法是系统提示词约束,在推理前明确告知模型不得基于性别、种族、地域等因素做出判断,并要求思维链过程中主动检查中间结论是否引入了不合理的假设。此外,输出端的公平性审计应当常态化,定期用BBQ、CValues等基准回归测试,防止模型迭代后偏见回潮。数据、训练、推理三层防线叠加,才能让推理模型在保持能力的同时,输出经得起公平性检验的内容。