diffprivlib 实战(四):训练差分隐私机器学习模型——sklearn 一行换 import 就是 DP 版
发布时间:2026/10/2 8:17:16来源:尧图网络
摘要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 的设置就够我研究好一阵子的。# 非私有版本fromsklearn.naive_bayesimportGaussianNB modelGaussianNB()model.fit(X_train,y_train)# 私有版本 —— 只改了这一行fromdiffprivlib.modelsimportGaussianNB modelGaussianNB(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 鸢尾花数据集fromsklearn.datasetsimportload_irisfromsklearn.model_selectionimporttrain_test_split# 加载数据irisload_iris()X,yiris.data,iris.target# 训练集/测试集划分X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.3,random_state42)# 计算 boundsIris 数据集特征范围已知bounds[(4.3,7.9),# 花萼长度(2.0,4.4),# 花萼宽度(1.0,6.9),# 花瓣长度(0.1,2.5),# 花瓣宽度]bounds 怎么算可以用np.min(X, axis0)和np.max(X, axis0)快速获取但更好的做法是基于领域知识设定一个合理范围。详见本系列第 2 讲。2.2 GaussianNB高斯朴素贝叶斯fromdiffprivlib.modelsimportGaussianNB# 训练 DP 模型dp_nbGaussianNB(epsilon1.0,boundsbounds,random_state42)dp_nb.fit(X_train,y_train)# 评估accuracydp_nb.score(X_test,y_test)print(fDP GaussianNB 准确率:{accuracy:.4f})# 预测predictionsdp_nb.predict(X_test[:5])print(f前5个样本的预测:{predictions})2.3 LogisticRegression逻辑回归fromdiffprivlib.modelsimportLogisticRegression# LogisticRegression 需要额外指定 data_norm# data_norm 是数据的 L2 范数上界importnumpyasnp data_normnp.linalg.norm(X_train,axis1).max()dp_lrLogisticRegression(epsilon1.0,data_normdata_norm,random_state42,max_iter1000)dp_lr.fit(X_train,y_train)accuracydp_lr.score(X_test,y_test)print(fDP LogisticRegression 准确率:{accuracy:.4f})2.4 KMeansK 均值聚类fromdiffprivlib.modelsimportKMeans# KMeans 也需要 boundsdp_kmeansKMeans(n_clusters3,epsilon1.0,boundsbounds,random_state42)dp_kmeans.fit(X_train)# 聚类结果labelsdp_kmeans.labels_print(f聚类标签分布:{np.bincount(labels)})# 聚类中心print(f聚类中心:\n{dp_kmeans.cluster_centers_})2.5 LinearRegression线性回归fromdiffprivlib.modelsimportLinearRegression# 线性回归需要 bounds 和 data_normdp_linregLinearRegression(epsilon1.0,boundsbounds,data_normdata_norm,random_state42)dp_linreg.fit(X_train,y_train)# 回归评估scoredp_linreg.score(X_test,y_test)print(fDP 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 模型有随机性。importnumpyasnpfromsklearn.datasetsimportload_irisfromsklearn.model_selectionimporttrain_test_splitfromsklearn.metricsimportaccuracy_scorefromdiffprivlib.modelsimportGaussianNB,LogisticRegressionimportmatplotlib.pyplotasplt# 准备数据irisload_iris()X,yiris.data,iris.target X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.3,random_state42)bounds[(4.3,7.9),(2.0,4.4),(1.0,6.9),(0.1,2.5)]data_normnp.linalg.norm(X_train,axis1).max()# 测试的 epsilon 值epsilons[0.01,0.1,0.5,1.0,5.0,10.0]n_trials10# 存储结果results{GaussianNB:[],LogisticRegression:[],}forepsinepsilons:nb_scores[]lr_scores[]fortrialinrange(n_trials):# GaussianNBnbGaussianNB(epsiloneps,boundsbounds,random_statetrial)nb.fit(X_train,y_train)nb_scores.append(nb.score(X_test,y_test))# LogisticRegressionlrLogisticRegression(epsiloneps,data_normdata_norm,random_statetrial,max_iter1000)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 准确率曲线plt.figure(figsize(10,6))plt.plot(epsilons,results[GaussianNB],o-,labelDP GaussianNB,linewidth2,markersize8)plt.plot(epsilons,results[LogisticRegression],s-,labelDP LogisticRegression,linewidth2,markersize8)plt.xlabel(Privacy Budget ε,fontsize14)plt.ylabel(Accuracy,fontsize14)plt.title(Privacy-Utility Trade-off: ε vs Accuracy (Iris Dataset),fontsize16)plt.xscale(log)plt.legend(fontsize12)plt.grid(True,alpha0.3)plt.tight_layout()plt.savefig(epsilon_vs_accuracy.png,dpi150)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关键观察ϵ \epsilonϵ越大准确率越高这是差分隐私的核心 trade-off——隐私保护越弱ϵ \epsilonϵ越大模型效用越好ϵ 0.1 \epsilon 0.1ϵ0.1时性能急剧下降强隐私保护下模型几乎学不到东西ϵ 1.0 \epsilon 1.0ϵ1.0后趋于稳定此时噪声已经很小模型接近非私有版本不同模型对噪声的敏感度不同LogisticRegression 在低ϵ \epsilonϵ下下降更明显四、边界参数详解4.1 为什么需要边界参数差分隐私的核心操作是添加噪声。噪声的大小取决于数据的范围即 sensitivity而 sensitivity 需要知道数据的边界。4.2 各模型的参数要求模型必需参数说明GaussianNBbounds每个特征的 (min, max) 元组列表LogisticRegressionboundsdata_normbounds同上data_norm为 L2 范数上界LinearRegressionboundsdata_norm同 LogisticRegressionKMeansbounds每个特征的 (min, max) 元组列表PCAbounds每个特征的 (min, max) 元组列表StandardScalerbounds每个特征的 (min, max) 元组列表RandomForestClassifierbounds每个特征的 (min, max) 元组列表DecisionTreeClassifierbounds每个特征的 (min, max) 元组列表4.3data_norm是什么data_norm是数据矩阵的L2 范数上界即∥ X ∥ 2 \|X\|_2∥X∥2的最大可能值。它用于梯度裁剪防止单个样本对模型更新产生过大影响。importnumpyasnp# 方法1从训练集估算data_normnp.linalg.norm(X_train,axis1).max()# 方法2更保守的估计基于 boundsbounds_arraynp.array(bounds)data_norm_conservativenp.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 完整对比实验fromsklearn.naive_bayesimportGaussianNBasSkGaussianNBfromsklearn.linear_modelimportLogisticRegressionasSkLogisticRegressionfromsklearn.metricsimportaccuracy_score# 非私有模型sk_nbSkGaussianNB()sk_nb.fit(X_train,y_train)sk_nb_accsk_nb.score(X_test,y_test)sk_lrSkLogisticRegression(max_iter1000,random_state42)sk_lr.fit(X_train,y_train)sk_lr_accsk_lr.score(X_test,y_test)# DP 模型不同 epsilonforepsin[0.1,1.0,10.0]:dp_nbGaussianNB(epsiloneps,boundsbounds,random_state42)dp_nb.fit(X_train,y_train)dp_nb_accdp_nb.score(X_test,y_test)dp_lrLogisticRegression(epsiloneps,data_normdata_norm,random_state42,max_iter1000)dp_lr.fit(X_train,y_train)dp_lr_accdp_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)GaussianNB97.78%71.11%93.33%95.56%LogisticRegression100.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 鸢尾花数据集小数据集fromsklearn.datasetsimportload_iris irisload_iris()# 150 样本, 4 特征适用场景快速验证、教学演示、调参实验优点数据量小训练快特征范围明确bounds 好设结果容易解释6.2 Adult 人口普查数据集真实场景fromsklearn.datasetsimportfetch_openml# 加载 Adult 数据集adultfetch_openml(nameadult,version1,as_frameTrue)X,yadult.data,adult.target适用场景真实业务场景验证、论文实验优点数据量大约 48,000 样本特征多14 个特征二分类任务收入 50K 与否更接近真实世界的隐私保护需求建议先用 Iris 跑通代码再用 Adult 验证真实场景下的表现。七、进阶技巧7.1random_state控制可复现性# 不设 random_state每次运行结果不同modelGaussianNB(epsilon1.0,boundsbounds)# 设 random_state结果可复现modelGaussianNB(epsilon1.0,boundsbounds,random_state42)7.2 使用 BudgetAccountant 管理隐私预算fromdiffprivlib.accountantimportBudgetAccountant accountantBudgetAccountant()# 每次训练都会自动记录隐私消耗modelGaussianNB(epsilon1.0,boundsbounds)model.fit(X_train,y_train)# 查看剩余预算print(f已消耗预算:{accountant.total})print(f剩余预算:{accountant.remaining(1.0)})7.3 多次查询的隐私累积accountantBudgetAccountant()# 多次训练模拟多次查询foriinrange(5):modelGaussianNB(epsilon0.5,boundsbounds)model.fit(X_train,y_train)print(f第{i1}次训练后总消耗:{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 ] \Pr[M(D) \in S] \leq e^{\epsilon} \cdot \Pr[M(D) \in S]Pr[M(D)∈S]≤eϵ⋅Pr[M(D′)∈S]其中D DD和D ′ DD′是仅差一条记录的相邻数据集M MM是随机化算法即我们的 DP 模型。下一篇预告第 5 讲构建差分隐私 Pipeline——预处理 模型的隐私保护组合拳在实际项目中我们很少直接把原始数据丢给模型。通常需要先做标准化、降维等预处理步骤。那么问题来了预处理步骤也需要隐私保护吗答案是需要如何让整个 Pipeline 都满足差分隐私隐私预算如何在多个步骤之间分配下一篇我们将用diffprivlib.models.StandardScalerPCALogisticRegression搭建一个完整的 DP Pipeline手把手教你如何在保持隐私的同时完成端到端的机器学习流程。系列导航上一篇第 3 讲BudgetAccountant 与隐私预算管理本篇第 4 讲训练差分隐私机器学习模型 ✅下一篇第 5 讲构建差分隐私 Pipeline本文为 diffprivlib 中文教程系列第 4 讲代码基于 diffprivlib 0.6.x 版本。如遇问题欢迎在评论区讨论。
网站建设高端定制企业官网