diffprivlib 实战(四):训练差分隐私机器学习模型——sklearn 一行换 import 就是 DP 版

摘要 :diffprivlib 提供了与 scikit-learn 完全兼容的差分隐私机器学习模型。本文通过多个模型的实战演示,展示"即插即用"的隐私保护能力,并深入分析隐私预算 ϵ \epsilon ϵ 对模型准确率的影响------隐私-效用 trade-off 到底长什么样?


本文你能学到什么

  • diffprivlib 的 DP 机器学习模型全家福
  • 如何用"一行换 import"的方式将 sklearn 模型升级为 DP 版本
  • ϵ \epsilon ϵ 对不同模型准确率的影响(附完整实验代码和图表)
  • 不同模型的边界参数(bounds / data_norm)如何正确设置
  • DP 模型 vs 非 DP 模型的性能差距到底有多大

一、即插即用:一行换 import 的魔法

1.1 核心理念

diffprivlib 的模型设计哲学极其简洁:把 sklearn 的 import 换成 diffprivlib 的 import,其他代码几乎不变。

说实话,第一次知道 sklearn 换个 import 就能变成差分隐私版的时候,我是真的惊了。这也太方便了吧?后来才知道方便归方便,坑也不少------光是 bounds 和 data_norm 的设置就够我研究好一阵子的。

python 复制代码
# 非私有版本
from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
model.fit(X_train, y_train)

# 私有版本 ------ 只改了这一行!
from diffprivlib.models import GaussianNB
model = GaussianNB(bounds=...)  # 唯一需要额外指定的参数
model.fit(X_train, y_train)

1.2 diffprivlib.models 全家福

diffprivlib 目前提供的差分隐私模型:

模型 类型 来源
GaussianNB 分类 高斯朴素贝叶斯
LogisticRegression 分类 逻辑回归
RandomForestClassifier 分类 随机森林分类器
DecisionTreeClassifier 分类 决策树分类器
LinearRegression 回归 线性回归
KMeans 聚类 K 均值聚类
PCA 降维 主成分分析
StandardScaler 预处理 标准化

这些模型全部继承自对应的 sklearn 类 + DiffprivlibMixin,保留了 sklearn 的所有接口(fit()、predict()、score() 等),唯一区别是内部使用了差分隐私机制。


二、多模型实战演示

2.1 准备数据

我们使用经典的 Iris 鸢尾花数据集:

python 复制代码
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target

# 训练集/测试集划分
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

# 计算 bounds(Iris 数据集特征范围已知)
bounds = [
    (4.3, 7.9),   # 花萼长度
    (2.0, 4.4),   # 花萼宽度
    (1.0, 6.9),   # 花瓣长度
    (0.1, 2.5),   # 花瓣宽度
]

bounds 怎么算? 可以用 np.min(X, axis=0) 和 np.max(X, axis=0) 快速获取,但更好的做法是基于领域知识设定一个合理范围。详见本系列第 2 讲。

2.2 GaussianNB:高斯朴素贝叶斯

python 复制代码
from diffprivlib.models import GaussianNB

# 训练 DP 模型
dp_nb = GaussianNB(epsilon=1.0, bounds=bounds, random_state=42)
dp_nb.fit(X_train, y_train)

# 评估
accuracy = dp_nb.score(X_test, y_test)
print(f"DP GaussianNB 准确率: {accuracy:.4f}")

# 预测
predictions = dp_nb.predict(X_test[:5])
print(f"前5个样本的预测: {predictions}")

2.3 LogisticRegression:逻辑回归

python 复制代码
from diffprivlib.models import LogisticRegression

# LogisticRegression 需要额外指定 data_norm
# data_norm 是数据的 L2 范数上界
import numpy as np
data_norm = np.linalg.norm(X_train, axis=1).max()

dp_lr = LogisticRegression(
    epsilon=1.0,
    data_norm=data_norm,
    random_state=42,
    max_iter=1000
)
dp_lr.fit(X_train, y_train)

accuracy = dp_lr.score(X_test, y_test)
print(f"DP LogisticRegression 准确率: {accuracy:.4f}")

2.4 KMeans:K 均值聚类

python 复制代码
from diffprivlib.models import KMeans

# KMeans 也需要 bounds
dp_kmeans = KMeans(
    n_clusters=3,
    epsilon=1.0,
    bounds=bounds,
    random_state=42
)
dp_kmeans.fit(X_train)

# 聚类结果
labels = dp_kmeans.labels_
print(f"聚类标签分布: {np.bincount(labels)}")

# 聚类中心
print(f"聚类中心:\n{dp_kmeans.cluster_centers_}")

2.5 LinearRegression:线性回归

python 复制代码
from diffprivlib.models import LinearRegression

# 线性回归需要 bounds 和 data_norm
dp_linreg = LinearRegression(
    epsilon=1.0,
    bounds=bounds,
    data_norm=data_norm,
    random_state=42
)
dp_linreg.fit(X_train, y_train)

# 回归评估
score = dp_linreg.score(X_test, y_test)
print(f"DP LinearRegression R²: {score:.4f}")

三、核心实验: ϵ \epsilon ϵ 对模型准确率的影响

3.1 实验设计

我们对 GaussianNB 和 LogisticRegression 分别测试 ϵ ∈ { 0.01 , 0.1 , 0.5 , 1.0 , 5.0 , 10.0 } \epsilon \in \{0.01, 0.1, 0.5, 1.0, 5.0, 10.0\} ϵ∈{0.01,0.1,0.5,1.0,5.0,10.0} 下的准确率,每个 ϵ \epsilon ϵ 重复 10 次取平均(因为 DP 模型有随机性)。

python 复制代码
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from diffprivlib.models import GaussianNB, LogisticRegression
import matplotlib.pyplot as plt

# 准备数据
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

bounds = [
    (4.3, 7.9), (2.0, 4.4), (1.0, 6.9), (0.1, 2.5)
]
data_norm = np.linalg.norm(X_train, axis=1).max()

# 测试的 epsilon 值
epsilons = [0.01, 0.1, 0.5, 1.0, 5.0, 10.0]
n_trials = 10

# 存储结果
results = {
    'GaussianNB': [],
    'LogisticRegression': [],
}

for eps in epsilons:
    nb_scores = []
    lr_scores = []

    for trial in range(n_trials):
        # GaussianNB
        nb = GaussianNB(epsilon=eps, bounds=bounds, random_state=trial)
        nb.fit(X_train, y_train)
        nb_scores.append(nb.score(X_test, y_test))

        # LogisticRegression
        lr = LogisticRegression(
            epsilon=eps, data_norm=data_norm,
            random_state=trial, max_iter=1000
        )
        lr.fit(X_train, y_train)
        lr_scores.append(lr.score(X_test, y_test))

    results['GaussianNB'].append(np.mean(nb_scores))
    results['LogisticRegression'].append(np.mean(lr_scores))
    print(f"ε={eps:.2f} | GaussianNB: {results['GaussianNB'][-1]:.4f} "
          f"| LogisticRegression: {results['LogisticRegression'][-1]:.4f}")

3.2 可视化: ϵ \epsilon ϵ vs 准确率曲线

python 复制代码
plt.figure(figsize=(10, 6))

plt.plot(epsilons, results['GaussianNB'], 'o-', 
         label='DP GaussianNB', linewidth=2, markersize=8)
plt.plot(epsilons, results['LogisticRegression'], 's-', 
         label='DP LogisticRegression', linewidth=2, markersize=8)

plt.xlabel('Privacy Budget ε', fontsize=14)
plt.ylabel('Accuracy', fontsize=14)
plt.title('Privacy-Utility Trade-off: ε vs Accuracy (Iris Dataset)', fontsize=16)
plt.xscale('log')
plt.legend(fontsize=12)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('epsilon_vs_accuracy.png', dpi=150)
plt.show()

3.3 结果分析

运行上述代码后,你会看到类似这样的结果:

做这个实验的时候我调了半天 epsilon,从 0.01 试到 100,最后画出来的曲线才像样。过程虽然枯燥,但结果很直观------隐私保护越强,模型越"瞎"。ε=0.01 的时候准确率直接跌到 33%,跟瞎猜差不多了,确实触目惊心。

复制代码
ε=0.01 | GaussianNB: 0.3333 | LogisticRegression: 0.2889
ε=0.10 | GaussianNB: 0.7111 | LogisticRegression: 0.5333
ε=0.50 | GaussianNB: 0.9111 | LogisticRegression: 0.8444
ε=1.00 | GaussianNB: 0.9333 | LogisticRegression: 0.9556
ε=5.00 | GaussianNB: 0.9556 | LogisticRegression: 0.9778
ε=10.0 | GaussianNB: 0.9556 | LogisticRegression: 0.9778

关键观察:

  1. ϵ \epsilon ϵ 越大,准确率越高 :这是差分隐私的核心 trade-off------隐私保护越弱( ϵ \epsilon ϵ 越大),模型效用越好
  2. ϵ < 0.1 \epsilon < 0.1 ϵ<0.1 时性能急剧下降:强隐私保护下,模型几乎"学不到东西"
  3. ϵ > 1.0 \epsilon > 1.0 ϵ>1.0 后趋于稳定:此时噪声已经很小,模型接近非私有版本
  4. 不同模型对噪声的敏感度不同 :LogisticRegression 在低 ϵ \epsilon ϵ 下下降更明显

四、边界参数详解

4.1 为什么需要边界参数?

差分隐私的核心操作是添加噪声。噪声的大小取决于数据的范围(即 sensitivity),而 sensitivity 需要知道数据的边界。

4.2 各模型的参数要求

模型 必需参数 说明
GaussianNB bounds 每个特征的 (min, max) 元组列表
LogisticRegression bounds + data_norm bounds 同上,data_norm 为 L2 范数上界
LinearRegression bounds + data_norm 同 LogisticRegression
KMeans bounds 每个特征的 (min, max) 元组列表
PCA bounds 每个特征的 (min, max) 元组列表
StandardScaler bounds 每个特征的 (min, max) 元组列表
RandomForestClassifier bounds 每个特征的 (min, max) 元组列表
DecisionTreeClassifier bounds 每个特征的 (min, max) 元组列表

4.3 data_norm 是什么?

data_norm 是数据矩阵的 L2 范数上界 (即 ∥ X ∥ 2 \|X\|_2 ∥X∥2 的最大可能值)。它用于梯度裁剪,防止单个样本对模型更新产生过大影响。

python 复制代码
import numpy as np

# 方法1:从训练集估算
data_norm = np.linalg.norm(X_train, axis=1).max()

# 方法2:更保守的估计(基于 bounds)
bounds_array = np.array(bounds)
data_norm_conservative = np.sqrt(np.sum(bounds_array[:, 1] ** 2))

print(f"训练集估算: {data_norm:.4f}")
print(f"保守估计: {data_norm_conservative:.4f}")

⚠️ 注意 :data_norm 不能从数据中计算!这会破坏隐私保证。应该基于领域知识或 bounds 来设定一个合理的上界。


五、DP 模型 vs 非 DP 模型:性能差多少?

5.1 完整对比实验

python 复制代码
from sklearn.naive_bayes import GaussianNB as SkGaussianNB
from sklearn.linear_model import LogisticRegression as SkLogisticRegression
from sklearn.metrics import accuracy_score

# 非私有模型
sk_nb = SkGaussianNB()
sk_nb.fit(X_train, y_train)
sk_nb_acc = sk_nb.score(X_test, y_test)

sk_lr = SkLogisticRegression(max_iter=1000, random_state=42)
sk_lr.fit(X_train, y_train)
sk_lr_acc = sk_lr.score(X_test, y_test)

# DP 模型(不同 epsilon)
for eps in [0.1, 1.0, 10.0]:
    dp_nb = GaussianNB(epsilon=eps, bounds=bounds, random_state=42)
    dp_nb.fit(X_train, y_train)
    dp_nb_acc = dp_nb.score(X_test, y_test)

    dp_lr = LogisticRegression(
        epsilon=eps, data_norm=data_norm,
        random_state=42, max_iter=1000
    )
    dp_lr.fit(X_train, y_train)
    dp_lr_acc = dp_lr.score(X_test, y_test)

    print(f"ε={eps}")
    print(f"  GaussianNB:       非私有={sk_nb_acc:.4f}  DP={dp_nb_acc:.4f}  "
          f"差距={sk_nb_acc - dp_nb_acc:.4f}")
    print(f"  LogisticRegression: 非私有={sk_lr_acc:.4f}  DP={dp_lr_acc:.4f}  "
          f"差距={sk_lr_acc - dp_lr_acc:.4f}")

5.2 对比结果分析

典型结果:

模型 非私有准确率 DP (ε=0.1) DP (ε=1.0) DP (ε=10.0)
GaussianNB 97.78% 71.11% 93.33% 95.56%
LogisticRegression 100.00% 53.33% 95.56% 97.78%

结论:

差距最大的时候能差 20 多个百分点,一开始看到这个数字确实让人有些沮丧。但换个角度想:差分隐私本来就不是为了提升准确率的,而是为了保护人。当你的模型需要处理医疗数据、财务数据的时候,这点准确率下降换来的是用户隐私的真正保护,是值得的。

  • 在 ϵ ≥ 1.0 \epsilon \geq 1.0 ϵ≥1.0 时,DP 模型与非私有模型的差距通常在 2-5% 以内
  • 在 ϵ = 10.0 \epsilon = 10.0 ϵ=10.0 时,差距几乎可以忽略
  • 只有在强隐私保护( ϵ < 0.1 \epsilon < 0.1 ϵ<0.1)下,性能损失才比较显著

六、数据集选择建议

6.1 Iris 鸢尾花数据集(小数据集)

python 复制代码
from sklearn.datasets import load_iris
iris = load_iris()  # 150 样本, 4 特征

适用场景:快速验证、教学演示、调参实验

优点:

  • 数据量小,训练快
  • 特征范围明确,bounds 好设
  • 结果容易解释

6.2 Adult 人口普查数据集(真实场景)

python 复制代码
from sklearn.datasets import fetch_openml

# 加载 Adult 数据集
adult = fetch_openml(name='adult', version=1, as_frame=True)
X, y = adult.data, adult.target

适用场景:真实业务场景验证、论文实验

优点:

  • 数据量大(约 48,000 样本)
  • 特征多(14 个特征)
  • 二分类任务(收入 >50K 与否)
  • 更接近真实世界的隐私保护需求

建议:先用 Iris 跑通代码,再用 Adult 验证真实场景下的表现。


七、进阶技巧

7.1 random_state 控制可复现性

python 复制代码
# 不设 random_state:每次运行结果不同
model = GaussianNB(epsilon=1.0, bounds=bounds)

# 设 random_state:结果可复现
model = GaussianNB(epsilon=1.0, bounds=bounds, random_state=42)

7.2 使用 BudgetAccountant 管理隐私预算

python 复制代码
from diffprivlib.accountant import BudgetAccountant

accountant = BudgetAccountant()

# 每次训练都会自动记录隐私消耗
model = GaussianNB(epsilon=1.0, bounds=bounds)
model.fit(X_train, y_train)

# 查看剩余预算
print(f"已消耗预算: {accountant.total}")
print(f"剩余预算: {accountant.remaining(1.0)}")

7.3 多次查询的隐私累积

python 复制代码
accountant = BudgetAccountant()

# 多次训练(模拟多次查询)
for i in range(5):
    model = GaussianNB(epsilon=0.5, bounds=bounds)
    model.fit(X_train, y_train)
    print(f"第 {i+1} 次训练后,总消耗: {accountant.total}")

# 如果总消耗超过阈值,会抛出 BudgetError

八、小结

要点 说明
即插即用 把 sklearn 的 import 换成 diffprivlib 的 import,代码几乎不变
必需参数 bounds(所有模型)、data_norm(线性模型)
ϵ \epsilon ϵ 影响 越大越准,越小越私; ϵ ≥ 1.0 \epsilon \geq 1.0 ϵ≥1.0 时性能损失通常 <5%
可复现性 使用 random_state 参数
隐私预算 使用 BudgetAccountant 追踪累积消耗

最后给读者一个建议:这篇代码量比较大,建议 clone 下来自己跑一遍,光看不练是学不会的。第一次看的时候可能觉得"懂了懂了",结果自己一跑全是坑。动手实践才是硬道理。

核心公式:差分隐私的定义要求满足:

Pr ⁡ M ( D ) ∈ S ≤ e ϵ ⋅ Pr ⁡ M ( D ′ ) ∈ S \PrM(D) \\in S \leq e^{\epsilon} \cdot \PrM(D') \\in S PrM(D)∈S≤eϵ⋅PrM(D′)∈S

其中 D D D 和 D ′ D' D′ 是仅差一条记录的相邻数据集, M M M 是随机化算法(即我们的 DP 模型)。


下一篇预告

第 5 讲:构建差分隐私 Pipeline------预处理 + 模型的隐私保护组合拳

在实际项目中,我们很少直接把原始数据丢给模型。通常需要先做标准化、降维等预处理步骤。那么问题来了:

  • 预处理步骤也需要隐私保护吗? 答案是:需要!
  • 如何让整个 Pipeline 都满足差分隐私?
  • 隐私预算如何在多个步骤之间分配?

下一篇我们将用 diffprivlib.models.StandardScaler + PCA + LogisticRegression 搭建一个完整的 DP Pipeline,手把手教你如何在保持隐私的同时完成端到端的机器学习流程。


系列导航


本文为 diffprivlib 中文教程系列第 4 讲,代码基于 diffprivlib 0.6.x 版本。如遇问题欢迎在评论区讨论。

相关推荐
郝学胜-神的一滴1 小时前
AI 编程智能体 02:AI智能体到底是什么
开发语言·人工智能·python·程序人生·pycharm
天一生水water1 小时前
超参分析入门教程
人工智能
LaughingZhu1 小时前
Product Hunt 每日热榜 | 2026-10-01
数据库·人工智能·深度学习·神经网络·搜索引擎
橘和柠1 小时前
CUDA 与 N 卡驱动安装
人工智能
悟天特斯1 小时前
边缘计算:让智慧园区的治理能力“下沉“到最后一公里
人工智能·边缘计算
宋哥转AI1 小时前
AgentScope Java 实战 05:Spring Boot 整合——11 个官方 starter 的自动装配路线
人工智能·ai·ai编程
I Am a robert girl1 小时前
从一张图到碎裂瞬间:FracGen 如何用物理信号“导演“物体撕裂
人工智能·深度学习·计算机视觉·生成模型·视频生成·物理仿真·断裂模拟
能源革命1 小时前
AI 日报 · 2026-10-01
人工智能
量子-Alex1 小时前
【大模型强化学习后训练】强化学习后训练算力应投向何处?模型规模、搜索、学习与反馈
人工智能·学习