课程0基础Agent开发课 / AI伦理与安全 / AI伦理核心原则-公平透明与隐私保护
— 33 min read

AI伦理核心原则-公平透明与隐私保护

在进入技术细节之前,先看几个真实案例。这些不是假设的风险,而是已经在现实中造成了伤害的问题。

AI 伦理核心原则:公平、透明与隐私保护

1.1 真实发生的问题:为什么这些原则重要

在进入技术细节之前,先看几个真实案例。这些不是假设的风险,而是已经在现实中造成了伤害的问题。

亚马逊招聘偏见事件(2018 年):亚马逊内部研发了一套 AI 简历筛选系统,希望自动化初筛过程。测试发现,该系统对包含"女性"相关词汇(如"女子篮球队队长")的简历评分系统性偏低,而对来自男校的申请者给出更高评分。根本原因是训练数据来自亚马逊过去十年的招聘历史——历史上工程师岗位录用男性远多于女性,AI 从这些数据中学到了"工程师倾向于男性"的规律。项目最终被关闭,但这个案例说明:历史数据中的偏见会被 AI 放大,而不是中立化。

COMPAS 司法量刑算法争议(2016 年):美国多个州法院使用 COMPAS 系统预测犯罪嫌疑人的"再犯风险",辅助法官量刑。ProPublica 的调查发现,该系统对黑人被告的再犯风险评估存在系统性偏差——实际未再犯的黑人被告被误判为高风险的比例,是白人被告的 2 倍。这个案例引发了关于 AI 透明度和问责制的广泛讨论:算法的决策依据是什么?当事人有权知道吗?有权质疑吗?

医疗 AI 算法种族偏见(2019 年,Science 期刊):研究发现美国广泛使用的一个医疗资源分配 AI,对黑人患者系统性地低估了疾病严重程度,导致他们获得的医疗资源少于同等健康状况的白人患者。算法使用医疗费用作为"健康需求"的代理指标,但由于历史性的医疗不平等,黑人患者历史医疗支出更低,算法就认为他们"更健康"。

这三个案例有一个共同规律:AI 系统忠实地学习了历史数据中的不平等,然后用算法的"客观性"把这种不平等规模化、自动化、难以被质疑

AI系统正被用于贷款审批、医疗诊断、招聘筛选、司法判决。AI并不天然公正:它会学习历史数据中的偏见,可能在暗箱中做出影响千万人的决定,还可能泄露用户的敏感隐私。

本章介绍AI伦理的三个核心问题:公平性(Fairness)透明度(Transparency)隐私保护(Privacy),并通过代码示例展示如何在实际开发中落地这些原则。


1.2 AI偏见与公平性

1.2.1 AI偏见的产生机制

在理解如何检测和缓解 AI 偏见之前,需要先理解偏见是如何产生的。

根本原因:训练数据是历史的镜像,而历史并不公平。

机器学习模型的核心工作是从数据中学习模式。如果训练数据中反映了历史上的不平等,模型会把这种不平等当作"规律"学习下来。这不是模型的 bug,而是它正确工作的结果——它做到了它被要求做的事:拟合数据中的模式。

以招聘筛选为例:一家公司历史上 90% 的工程师是男性,用这批历史数据训练简历筛选模型,模型会学到"工程师职位倾向于男性候选人"的模式,即使模型从未明确看到"性别"这个特征——它可以通过其他相关特征(毕业院校、工作经历、措辞风格)间接推断性别,产生歧视性结果。

公平性的不同定义及其内在矛盾:

"公平"在数学上有多种定义,而它们之间存在根本冲突——这是 AI 公平性领域的核心难题:

  • 人口统计均等(Demographic Parity):不同群体的正例预测率应相同。例如男性和女性的贷款批准率应相等。
  • 机会均等(Equal Opportunity):不同群体在真正合格的人中,被正确识别的比例应相同(召回率均等)。
  • 预测准确性均等(Predictive Parity):模型给出的预测分数含义对不同群体相同。

数学证明表明,当不同群体的基础率(base rate)不同时,这三种公平性定义无法同时满足。选择哪种公平性定义,是一个价值判断,没有技术上的"唯一正确答案"。

这正是 AI 伦理不能单纯依赖技术解决的原因:公平性的定义本身是社会和道德问题,需要人类在充分理解权衡之后做出明确的价值选择。

1.2.2 什么是AI偏见?

AI偏见(AI Bias)是指模型在不同群体之间产生系统性的不公平结果。常见来源:

偏见类型 说明 典型案例
历史偏见 训练数据反映了历史上的歧视 简历筛选模型歧视女性
表示偏见 某些群体在训练数据中严重欠代表 人脸识别对深肤色识别率低
测量偏见 标签本身含有偏见 "犯罪风险"标签基于有偏执法记录
聚合偏见 对所有人用同一模型,忽视群体差异 医疗模型忽视种族间生理差异

1.2.3 公平性检测

公平性检测的核心是比较模型在不同受保护属性(如性别、种族、年龄)群体上的表现差异。下面的代码验证公平性审计的基本流程:分群体计算指标,然后计算群体间的差距。

python
import numpy as np
import pandas as pd
from sklearn.metrics import (
    accuracy_score, precision_score, recall_score, f1_score
)
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

def fairness_audit(model, X_test, y_test, sensitive_feature, feature_name="sensitive"):
    """
    对模型进行公平性审计
    
    参数:
        model: 已训练的分类模型
        X_test: 测试集特征(DataFrame)
        y_test: 测试集真实标签
        sensitive_feature: 敏感属性列(Series)
        feature_name: 敏感属性名称(用于报告)
    
    返回:
        公平性指标字典
    """
    y_pred = model.predict(X_test)
    groups = sensitive_feature.unique()
    
    results = {}
    print(f"\n{'='*60}")
    print(f"公平性审计报告 - 属性: {feature_name}")
    print(f"{'='*60}")
    
    for group in sorted(groups):
        mask = (sensitive_feature == group)
        group_y_true = y_test[mask]
        group_y_pred = y_pred[mask]
        
        if len(group_y_true) == 0:
            continue
            
        metrics = {
            "样本数量": int(mask.sum()),
            "准确率": accuracy_score(group_y_true, group_y_pred),
            "精确率": precision_score(group_y_true, group_y_pred, zero_division=0),
            "召回率": recall_score(group_y_true, group_y_pred, zero_division=0),
            "F1分数": f1_score(group_y_true, group_y_pred, zero_division=0),
            # 正例预测率(Positive Predictive Parity)
            "正例预测率": group_y_pred.mean(),
        }
        results[group] = metrics
        
        print(f"\n群体: {group} (样本={metrics['样本数量']})")
        for k, v in metrics.items():
            if k != "样本数量":
                print(f"  {k}: {v:.4f}")
    
    # 计算公平性差距
    if len(results) >= 2:
        group_list = list(results.keys())
        print(f"\n{'='*60}")
        print("公平性差距分析")
        print(f"{'='*60}")
        
        for metric in ["准确率", "正例预测率", "召回率"]:
            values = [results[g][metric] for g in group_list]
            max_val = max(values)
            min_val = min(values)
            gap = max_val - min_val
            
            # 差异阈值:通常 > 0.1 需要关注,> 0.2 需要干预
            flag = "⚠️  需要关注" if gap > 0.1 else "✓ 在可接受范围"
            print(f"  {metric} 最大差距: {gap:.4f} {flag}")
    
    return results


# 示例:信用贷款审批模型的公平性审计
def demo_fairness_audit():
    np.random.seed(42)
    n = 1000
    
    # 模拟数据集(包含历史偏见)
    gender = np.random.choice([0, 1], n)  # 0=女性, 1=男性
    income = np.random.normal(50000, 15000, n) + gender * 8000  # 男性收入偏高
    credit_score = np.random.normal(650, 80, n)
    
    # 带偏见的历史标签(男性审批率人为更高)
    approval_prob = (
        0.3 + 
        0.4 * (credit_score > 650) + 
        0.2 * (income > 55000) + 
        0.15 * gender  # 历史偏见:男性额外+15%
    )
    y = (np.random.random(n) < approval_prob).astype(int)
    
    df = pd.DataFrame({
        "收入": income,
        "信用分": credit_score,
        "性别": gender,
        "审批结果": y
    })
    
    X = df[["收入", "信用分"]]  # 故意不包含性别
    y_labels = df["审批结果"]
    gender_series = pd.Series(gender, name="性别").map({0: "女性", 1: "男性"})
    
    X_train, X_test, y_train, y_test, gender_train, gender_test = train_test_split(
        X, y_labels, gender_series, test_size=0.3, random_state=42
    )
    
    model = LogisticRegression(random_state=42)
    model.fit(X_train, y_train)
    
    results = fairness_audit(model, X_test, y_test, gender_test, "性别")
    return results


if __name__ == "__main__":
    demo_fairness_audit()

公平性审计分组对比
不同群体的模型性能对比(公平性审计)

1.2.4 公平性缺陷的缓解方法

python
from sklearn.utils import resample
from sklearn.linear_model import LogisticRegression
import numpy as np
import pandas as pd

# ---- 方法一:重采样(对欠代表群体过采样)----

def mitigate_with_resampling(X_train, y_train, sensitive_train):
    """通过重采样平衡各群体在正样本中的比例"""
    df = X_train.copy()
    df["label"] = y_train.values
    df["sensitive"] = sensitive_train.values
    
    # 找到最大组的正例数量
    group_pos_counts = df.groupby("sensitive")["label"].sum()
    max_count = int(group_pos_counts.max())
    
    resampled_parts = []
    for group in df["sensitive"].unique():
        group_df = df[df["sensitive"] == group]
        pos_df = group_df[group_df["label"] == 1]
        neg_df = group_df[group_df["label"] == 0]
        
        # 对正例过采样到最大数量
        if len(pos_df) < max_count:
            pos_df_upsampled = resample(pos_df, replace=True, 
                                        n_samples=max_count, random_state=42)
        else:
            pos_df_upsampled = pos_df
        
        resampled_parts.extend([pos_df_upsampled, neg_df])
    
    resampled_df = pd.concat(resampled_parts).sample(frac=1, random_state=42)
    X_resampled = resampled_df.drop(["label", "sensitive"], axis=1)
    y_resampled = resampled_df["label"]
    return X_resampled, y_resampled


# ---- 方法二:类别权重(让模型更关注欠代表群体)----

def mitigate_with_class_weights(X_train, y_train, sensitive_train):
    """为不同群体的样本分配不同权重"""
    weights = np.ones(len(y_train))
    
    # 统计每个群体的正例比例
    for group in sensitive_train.unique():
        mask = (sensitive_train == group)
        group_pos_rate = y_train[mask].mean()
        overall_pos_rate = y_train.mean()
        
        # 正例比例低于整体的群体,增加其权重
        if group_pos_rate < overall_pos_rate:
            adjustment = overall_pos_rate / (group_pos_rate + 1e-8)
            weights[mask & (y_train == 1)] *= min(adjustment, 3.0)  # 限制最大权重
    
    model = LogisticRegression(random_state=42, max_iter=500)
    model.fit(X_train, y_train, sample_weight=weights)
    return model


# ---- 方法三:后处理校准(调整决策阈值)----

def calibrate_thresholds(model, X_val, y_val, sensitive_val):
    """为不同群体设置不同的分类阈值,使召回率平衡"""
    probas = model.predict_proba(X_val)[:, 1]
    thresholds = {}
    
    target_recall = 0.7  # 目标召回率
    
    for group in sensitive_val.unique():
        mask = (sensitive_val == group)
        group_probas = probas[mask]
        group_labels = y_val[mask]
        
        best_threshold = 0.5
        best_diff = float("inf")
        
        for threshold in np.arange(0.1, 0.9, 0.01):
            preds = (group_probas >= threshold).astype(int)
            if group_labels.sum() == 0:
                continue
            recall = (preds & group_labels.values).sum() / group_labels.sum()
            diff = abs(recall - target_recall)
            if diff < best_diff:
                best_diff = diff
                best_threshold = threshold
        
        thresholds[group] = best_threshold
        print(f"群体 {group}: 最优阈值 = {best_threshold:.2f}")
    
    return thresholds

1.3 透明度与可解释性

1.3.1 透明度和可解释性的区别

"透明度"和"可解释性"经常被混用,但它们有重要区别:

透明度(Transparency) 是指能够知道系统是如何工作的——它的训练数据、架构、训练过程、评估方法。透明度是系统层面的概念,对应"这个系统整体上是怎么运作的"。

可解释性(Explainability) 是指能够理解为什么系统对某个具体输入给出了某个具体输出——"这次贷款被拒绝,是因为哪些因素"。可解释性是决策层面的概念,对应"这次预测是怎么得出的"。

两者都重要,但解决的问题不同。一个系统可以是透明的(完全开源,所有人都能看到代码)但不可解释(深度神经网络的单次预测仍然难以解释);也可以是可解释的(能给出具体理由)但不透明(商业模型的架构是保密的)。

监管合规通常同时需要两者:GDPR 要求"有意义的解释"(可解释性),也要求数据处理透明(透明度)。

1.3.2 为什么可解释性很重要?

监管层面:欧盟 GDPR 第22条要求自动化决策提供"有意义的解释",中国《个人信息保护法》第24条要求自动化决策保证透明度,金融行业拒绝贷款必须给出具体原因。

业务层面:医生需要理解AI诊断依据才会信任和使用;工程师需要可解释性来调试和改进模型。

1.3.3 使用SHAP解释模型决策

SHAP(SHapley Additive exPlanations,基于博弈论中沙普利值的模型解释工具,通俗理解:计算"每个特征对最终预测贡献了多少分")是目前最主流的模型解释工具,基于博弈论中的沙普利值,衡量每个特征对预测结果的贡献。

SHAP 的核心思想来自博弈论中的"沙普利值":把预测问题类比为多人合作游戏,每个特征是一个"玩家",最终预测值是"总收益"。沙普利值回答的问题是:在所有可能的特征组合顺序中,这个特征平均贡献了多少?

这个方法的优点是理论上有保证(满足效率、对称性、虚拟性、可加性四个公理),并且能给出每个特征在单次预测中的具体贡献数值,支持全局重要性分析和单样本解释。下面验证 SHAP 的基本用法:

python
# 安装:pip install shap

import shap
import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split

def explain_model_with_shap(model, X_train, X_test, feature_names):
    """
    使用SHAP解释模型预测
    
    参数:
        model: 已训练的模型(树模型效果最好)
        X_train: 训练集(用于建立背景分布)
        X_test: 要解释的测试样本
        feature_names: 特征名称列表
    """
    # 创建SHAP解释器(TreeExplainer适用于树模型)
    explainer = shap.TreeExplainer(model)
    shap_values = explainer.shap_values(X_test)
    
    # 如果是二分类,取正类的SHAP值
    if isinstance(shap_values, list):
        shap_values = shap_values[1]
    
    # 输出全局特征重要性
    print("\n全局特征重要性(基于SHAP平均绝对值):")
    mean_abs_shap = np.abs(shap_values).mean(axis=0)
    importance_df = pd.DataFrame({
        "特征": feature_names,
        "平均|SHAP值|": mean_abs_shap
    }).sort_values("平均|SHAP值|", ascending=False)
    print(importance_df.to_string(index=False))
    
    return shap_values, explainer


def explain_single_prediction(shap_values, explainer, sample, feature_names, idx=0):
    """解释单个样本的预测"""
    sample_shap = shap_values[idx]
    base_value = explainer.expected_value
    if isinstance(base_value, list):
        base_value = base_value[1]
    
    prediction = base_value + sample_shap.sum()
    
    print(f"\n样本 #{idx} 的预测解释:")
    print(f"  基准值(平均预测): {base_value:.4f}")
    print(f"  最终预测概率: {prediction:.4f}")
    print(f"  预测结论: {'批准' if prediction > 0.5 else '拒绝'}")
    print("\n  各特征贡献:")
    
    contributions = sorted(
        zip(feature_names, sample[idx] if hasattr(sample, '__getitem__') else sample.iloc[idx],
            sample_shap),
        key=lambda x: abs(x[2]),
        reverse=True
    )
    
    for feat, val, shap_val in contributions:
        direction = "↑正向" if shap_val > 0 else "↓负向"
        print(f"  {feat}: 值={val:.2f}, SHAP={shap_val:+.4f} {direction}")


# 演示
def demo_shap_explanation():
    np.random.seed(42)
    n = 500
    
    X = pd.DataFrame({
        "信用分": np.random.normal(650, 80, n),
        "年收入(万)": np.random.normal(15, 5, n),
        "负债率": np.random.uniform(0.1, 0.8, n),
        "工作年限": np.random.uniform(0, 20, n),
        "贷款金额(万)": np.random.normal(20, 8, n)
    })
    
    y = (
        (X["信用分"] > 650).astype(int) +
        (X["年收入(万)"] > 15).astype(int) +
        (X["负债率"] < 0.4).astype(int)
    ) >= 2
    
    X_train, X_test = train_test_split(X, test_size=0.2, random_state=42)
    y_train, y_test = train_test_split(y, test_size=0.2, random_state=42)
    
    model = GradientBoostingClassifier(n_estimators=50, random_state=42)
    model.fit(X_train, y_train)
    
    shap_values, explainer = explain_model_with_shap(
        model, X_train, X_test, X.columns.tolist()
    )
    explain_single_prediction(
        shap_values, explainer, X_test, X.columns.tolist(), idx=0
    )


if __name__ == "__main__":
    demo_shap_explanation()

SHAP特征重要性
SHAP值揭示各特征对预测的影响方向和幅度


1.4 隐私保护

1.4.1 数据最小化原则

只收集完成任务所必需的最少数据,是隐私保护的第一道防线。

python
from dataclasses import dataclass, field
from typing import List, Optional, Set
import hashlib
import re

@dataclass
class DataSchema:
    """数据模式定义,强制声明每个字段的必要性"""
    name: str
    purpose: str
    required_fields: List[str]
    optional_fields: List[str] = field(default_factory=list)
    retention_days: int = 90
    
    def validate_collection(self, collected_fields: Set[str]) -> List[str]:
        """检查是否收集了超出需要的数据"""
        allowed = set(self.required_fields + self.optional_fields)
        excess = set(collected_fields) - allowed
        missing = set(self.required_fields) - set(collected_fields)
        
        warnings = []
        if excess:
            warnings.append(f"违规:收集了不必要的字段: {excess}")
        if missing:
            warnings.append(f"缺失:必要字段未收集: {missing}")
        return warnings


# 示例:贷款申请数据最小化
loan_schema = DataSchema(
    name="贷款申请",
    purpose="信用风险评估",
    required_fields=["年龄", "年收入", "信用历史", "贷款金额", "贷款用途"],
    optional_fields=["工作单位类型", "居住年限"],
    retention_days=1825  # 5年(合规要求)
)

# 检查数据收集合规性
collected = {"年龄", "年收入", "信用历史", "贷款金额", "贷款用途", "宗教信仰", "种族"}
warnings = loan_schema.validate_collection(collected)
for w in warnings:
    print(f"⚠️  {w}")

1.4.2 数据匿名化

python
import hashlib
import re
from typing import Dict, Any

class DataAnonymizer:
    """数据匿名化工具,支持多种匿名化技术"""
    
    def __init__(self, salt: str = "default_salt_change_in_production"):
        self.salt = salt
        # 中文手机号、身份证、邮箱的正则
        self._phone_re = re.compile(r'1[3-9]\d{9}')
        self._id_card_re = re.compile(r'\d{17}[\dX]')
        self._email_re = re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}')
    
    def pseudonymize_id(self, user_id: str) -> str:
        """假名化:用不可逆哈希替换真实ID(可用于关联同一用户,但无法还原)"""
        hash_input = f"{self.salt}:{user_id}".encode("utf-8")
        return "user_" + hashlib.sha256(hash_input).hexdigest()[:16]
    
    def anonymize_name(self, name: str) -> str:
        """姓名脱敏:保留姓氏,其余用*替换"""
        if not name:
            return "***"
        if len(name) <= 1:
            return "*"
        return name[0] + "*" * (len(name) - 1)
    
    def mask_phone(self, phone: str) -> str:
        """手机号脱敏:保留前3位和后4位"""
        phone = re.sub(r'\s|-', '', phone)
        if len(phone) == 11:
            return phone[:3] + "****" + phone[7:]
        return "***"
    
    def mask_id_card(self, id_card: str) -> str:
        """身份证脱敏:只保留前4位和后4位"""
        id_card = id_card.strip()
        if len(id_card) == 18:
            return id_card[:4] + "**********" + id_card[14:]
        return "***"
    
    def generalize_age(self, age: int, bucket_size: int = 5) -> str:
        """年龄泛化:将精确年龄替换为区间,降低识别风险"""
        lower = (age // bucket_size) * bucket_size
        upper = lower + bucket_size - 1
        return f"{lower}-{upper}岁"
    
    def generalize_location(self, location: str) -> str:
        """位置泛化:只保留城市级别,去除街道信息"""
        # 简单示例:去除区/街道信息
        parts = location.replace("省", "省|").replace("市", "市|").replace("区", "区|")
        parts = [p for p in parts.split("|") if p]
        return "".join(parts[:2]) if len(parts) >= 2 else location
    
    def scrub_text(self, text: str) -> str:
        """从自由文本中自动删除敏感信息"""
        # 删除手机号
        text = self._phone_re.sub("[手机号已脱敏]", text)
        # 删除身份证
        text = self._id_card_re.sub("[证件号已脱敏]", text)
        # 删除邮箱
        text = self._email_re.sub("[邮箱已脱敏]", text)
        return text
    
    def anonymize_record(self, record: Dict[str, Any]) -> Dict[str, Any]:
        """对整条数据记录进行匿名化处理"""
        result = record.copy()
        
        field_handlers = {
            "user_id": self.pseudonymize_id,
            "姓名": self.anonymize_name,
            "手机号": self.mask_phone,
            "身份证": self.mask_id_card,
            "地址": self.generalize_location,
        }
        
        if "年龄" in result:
            result["年龄"] = self.generalize_age(result["年龄"])
        
        for field, handler in field_handlers.items():
            if field in result:
                result[field] = handler(str(result[field]))
        
        return result


# 使用示例
anonymizer = DataAnonymizer(salt="your_secret_salt_2024")

raw_record = {
    "user_id": "U123456",
    "姓名": "张伟",
    "手机号": "13812345678",
    "身份证": "110101199001011234",
    "年龄": 34,
    "地址": "北京市朝阳区望京街道某某小区101室",
    "信用分": 720,
    "备注": "用户来电咨询,电话13812345678,邮箱zhang@example.com"
}

anonymized = anonymizer.anonymize_record(raw_record)
anonymized["备注"] = anonymizer.scrub_text(raw_record["备注"])

print("原始数据:", raw_record)
print("\n匿名化后:", anonymized)

1.4.3 防止LLM泄露个人信息

在将用户数据发送给LLM时,需要防止PII(Personally Identifiable Information,个人身份信息,如姓名、手机号、身份证号等)泄露。

code

![PII数据脱敏对比](/uploads/AI伦理与安全__pii-anonymization-example.png)
*数据脱敏前后对比:保护个人隐私信息*
python
import re
from typing import Tuple

class PIIGuard:
    """防止PII进入LLM或从LLM输出中泄露的守护工具"""
    
    PII_PATTERNS = {
        "手机号": r'1[3-9]\d{9}',
        "身份证": r'\d{17}[\dX]',
        "邮箱": r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
        "银行卡": r'\d{16,19}',
        "IPv4地址": r'\b(?:\d{1,3}\.){3}\d{1,3}\b',
    }
    
    def __init__(self):
        self._compiled = {
            name: re.compile(pattern) 
            for name, pattern in self.PII_PATTERNS.items()
        }
        self._placeholder_map = {}  # 用于可逆替换(如需要还原)
    
    def sanitize_input(self, text: str, reversible: bool = False) -> Tuple[str, Dict]:
        """
        清理输入文本中的PII
        
        参数:
            text: 待清理的文本
            reversible: 是否保存替换映射(用于后续还原)
        
        返回:
            (清理后的文本, 替换映射)
        """
        placeholder_map = {}
        clean_text = text
        
        for pii_type, pattern in self._compiled.items():
            matches = pattern.findall(clean_text)
            for i, match in enumerate(matches):
                placeholder = f"[{pii_type}_{i+1}]"
                if reversible:
                    placeholder_map[placeholder] = match
                clean_text = clean_text.replace(match, placeholder, 1)
        
        return clean_text, placeholder_map
    
    def check_output(self, llm_output: str) -> Tuple[bool, List[str]]:
        """
        检查LLM输出是否包含PII(用于输出过滤)
        
        返回:
            (是否安全, 发现的PII类型列表)
        """
        found_pii = []
        for pii_type, pattern in self._compiled.items():
            if pattern.search(llm_output):
                found_pii.append(pii_type)
        
        return len(found_pii) == 0, found_pii
    
    def safe_llm_call(self, user_input: str, llm_func) -> str:
        """
        安全的LLM调用包装器,自动处理PII进出过滤
        
        参数:
            user_input: 用户输入
            llm_func: 实际调用LLM的函数(接受str,返回str)
        """
        # 1. 清理输入
        clean_input, placeholder_map = self.sanitize_input(user_input, reversible=True)
        
        # 2. 调用LLM
        llm_output = llm_func(clean_input)
        
        # 3. 检查输出中是否有PII泄露
        is_safe, found_pii = self.check_output(llm_output)
        if not is_safe:
            print(f"⚠️  LLM输出中发现PII: {found_pii},已自动脱敏")
            for pii_type, pattern in self._compiled.items():
                llm_output = pattern.sub(f"[{pii_type}已隐藏]", llm_output)
        
        return llm_output


# 使用示例
guard = PIIGuard()

user_message = "我的手机号是13812345678,身份证号110101199001011234,帮我查一下贷款记录"
clean_message, mapping = guard.sanitize_input(user_message, reversible=True)

print(f"原始输入: {user_message}")
print(f"清理后发给LLM: {clean_message}")
print(f"替换映射(仅系统保存): {mapping}")

1.5 总结

原则 核心问题 主要工具/方法 关键指标
公平性 模型是否对不同群体一视同仁? 公平性审计、重采样、阈值校准 差异化影响率 < 10%
透明度 决策依据可以解释吗? SHAP、LIME(Local Interpretable Model-agnostic Explanations,通过局部近似来解释任意模型单次预测的工具)、模型卡片 可解释性报告完整性
隐私保护 用户数据是否得到保护? 数据最小化、匿名化、PII过滤 数据泄露事件为零

1.5.1 开发检查清单

  • 是否对训练数据中的敏感属性进行了公平性分析?
  • 模型对不同群体的准确率差距是否在可接受范围内(< 10%)?
  • 高风险决策(贷款、医疗、就业)是否提供了可解释的决策依据?
  • 数据收集是否遵循最小化原则,只收集必要信息?
  • 存储的用户数据是否经过匿名化处理?
  • 发送给LLM的内容是否经过PII过滤?
  • 是否制定了数据保留期限和删除策略?

记住:AI伦理不是可选项,而是可信AI系统的基础。从项目初期就把这些原则融入开发流程,比事后补救容易得多。

1.6 实践优先级:从哪里开始

如果你正在开发 AI 系统,不知道从哪里开始落实这些原则,这里是一个优先级建议:

第一步:隐私保护(最紧急,成本最低)

在发送给 LLM 之前,过滤用户输入中的手机号、身份证号等个人信息。这一步只需要几十行代码(用本文的 PIIGuard 类),但能立刻降低隐私风险。

第二步:用户告知(合规要求)

确保用户知道他们在和 AI 交互,明确告知 AI 可能产生不准确的内容,并在 AI 生成的内容中添加明确标识。这不只是道德要求,也是中国《生成式 AI 管理办法》的法规要求。

第三步:公平性审计(如果涉及高风险决策)

如果你的系统参与招聘、贷款、医疗等高风险决策,对不同用户群体进行性能指标对比(用本文的 fairness_audit 函数),确保差距在可接受范围内。这一步工作量较大,但对于高风险场景是必须的。

第四步:可解释性(增加用户信任)

对于拒绝贷款、标记风险等高影响力的单次决策,提供可解释的依据(用 SHAP 等工具)。这不只是技术问题,也是监管要求——中国《个人信息保护法》第 24 条要求自动化决策保证透明度。

一个原则:越早做越好,越便宜。在代码设计阶段考虑隐私保护,比在已经上线的系统里追加防护容易十倍。

本页目录