隐语纵向树模型原理及工程实现介绍
讲师:邹沛成 (帅气小哥哥)
一、纵向数据分割场景和树模型
1. 什么是纵向树模型
-
常规的树模型是不区分横向和纵向概念的,纵向树模型就是基于纵向分割数据集训练的决策树模型
-
纵向数据集
一般来自于前置步骤特征隐私求交得到。
特征来自于各个参与方,只有一方拥有标签。各个参与方不期望将特征以明文的形式直接传输给其他参与方,也不希望泄漏相关重要信息。
2. 纵向树模型的使用场景
- 联合建模:不同特征维度、合作得到的联邦模型比单方训练的模型效果更优
- 树模型:可解释性
3. 隐语提供的纵向树模型算法
根据安全程度不同,SF实现了以下两种:
- 可证安全算法SS-XGB(SecretShared eXtremeGradientBoost)
- 基于秘密分享
- 实现了XGB的经典功能。
- 采用MPC 进行密态计算,无信息泄漏,安全性保证基于数学证明。
- 网络条件好时效率高。
- 纵向联邦算法SGB(SecureGradientBoost)
- 基于联邦学习和同态加密,即明密文混合计算
- 吸收了部分来自XGB 和 lightGBM功能。
- 采用同态加密来保护标签数据。
- 非可证安全
- 计算量大,算力高时效率高。但是在网络延迟较高时,性能优势明显。
4. XGBoost的一阶导数 g g g和二阶导数 h h h
在XGBoost(eXtreme Gradient Boosting)中, g g g和 h h h分别表示一阶和二阶导数(即梯度和Hessian),用于加速模型的训练过程。这些值是损失函数相对于预测值的导数。
-
说明
假设我们有一个损失函数 L ( y , y ^ ) L(y, \hat{y}) L(y,y^),其中 y y y是实际值, y ^ \hat{y} y^是预测值。
- 梯度(Gradient, g g g): 梯度是损失函数对预测值的一阶导数,反映了当前预测值与实际值之间的差距。它用于调整模型的参数,使预测值逐渐接近实际值。
g i = ∂ L ( y i , y i ^ ) ∂ y ^ i g_i = \frac{\partial L(y_i, \hat{y_i})}{\partial \hat{y}_i} gi=∂y^i∂L(yi,yi^)
- Hessian( h h h): Hessian是损失函数对预测值的二阶导数,反映了梯度的变化情况。它用于调整学习率,从而使梯度下降更平滑、更稳定。
h i = ∂ L 2 ( y i , y i ^ ) ∂ y ^ i 2 h_i=\frac{\partial L^2(y_i, \hat{y_i})}{\partial \hat{y}_i^2} hi=∂y^i2∂L2(yi,yi^)
-
平方误差损失下的梯度计算
L ( y , y ^ ) = 1 2 ( y − y ^ ) 2 g i = − ( y i − y ^ ) = y ^ − y i h i = 1 L(y, \hat{y}) = \frac{1}{2}(y - \hat{y})^2\\ g_i=-(y_i-\hat{y})=\hat{y}-y_i \\ h_i=1 L(y,y^)=21(y−y^)2gi=−(yi−y^)=y^−yihi=1
二、隐语纵向树模型
原生XGBoost使用教程:https://xgboost.readthedocs.io/en/stable/tutorials/model.html
1. 使用方法
和传统机器学习一致,主要包括three steps:
- 准备:环境和数据集
- 训练:参数设置和执行
- 模型评估:计算指标和采取进一步决策
2. SS-XGB介绍
教程链接:https://www.secretflow.org.cn/zh-CN/docs/secretflow/v1.6.1b0/user_guide/mpc_ml/decision_tree
论文链接:https://arxiv.org/pdf/2005.08479
-
单棵树分裂的主要过程如下:
-
预计算:根据损失函数定义、样本标签、当前预测值,计算每个样本可以求得其一阶导 g i g_i gi和二阶导 h i h_i hi
-
结点分裂:通过枚举所有分裂方案,选出带来最优增益值的方式执行分裂。分裂方案包含分裂特征和分裂阈值,可以将当前结点样本集合 I I I分裂为左子树样本集合 I L I_L IL 和右子树样本集合 I R I_R IR, 并由如下公式计算出此分裂方案的增益值:
L s p l i t = 1 2 [ ( ∑ i ∈ I L g i ) 2 ∑ i ∈ I L h i + λ + ( ∑ i ∈ I R g i ) 2 ∑ i ∈ I R h i + λ − ( ∑ i ∈ I g i ) 2 ∑ i ∈ I h i + λ ] − γ L_{split} = \frac{1}{2}[\frac{(\sum_{i\in I_L}g_i)^2}{\sum_{i\in I_L}h_i+\lambda}+\frac{(\sum_{i\in I_R}g_i)^2}{\sum_{i\in I_R}h_i+\lambda}-\frac{(\sum_{i\in I}g_i)^2}{\sum_{i\in I}h_i+\lambda}]-\gamma Lsplit=21[∑i∈ILhi+λ(∑i∈ILgi)2+∑i∈IRhi+λ(∑i∈IRgi)2−∑i∈Ihi+λ(∑i∈Igi)2]−γ
其中, λ \lambda λ 和 γ \gamma γ分别为叶节点数和叶节点权重的惩罚因子。 -
权重计算:由落入该结点的样本计算得到
w j ∗ = − ∑ i ∈ I j g i ∑ i ∈ I j h i + λ w_j^*=-\frac{\sum_{i\in I_j}g_i}{\sum_{i\in I_j}h_i+\lambda} wj∗=−∑i∈Ijhi+λ∑i∈Ijgi
-
-
回归问题和分类问题的训练流程是相同的,除了:
- 损失函数的选择(回归-MSE,分类-Logloss)。
- 分类问题需要将预测值通过sigmoid函数转化为概率。
-
使用秘密分享计算分裂增益值和叶权重。
根据秘密分享协议提供的加/乘等操作来实现安全的多方联合计算
-
特别需要关注的问题是:如何在计算分桶加和时,不泄漏任何样本分布相关的信息。
对于每一个分桶,XGBoost会计算该分桶中所有样本的梯度和Hessian值的加和。这些统计量用于后续的增益计算。其中,梯度加和是指所有样本在该分桶内的梯度值之和,Hessian加和是指所有样本在该分桶内的Hessian值之和。
-
SF通过引入一个密态下的向量 S \mathbf{S} S来解决这个问题。
例如,这里分裂点选择了
Age
的15。那么,在计算左子节点的梯度 G L G_L GL就需要拿到 g 1 g_1 g1和 g 4 g_4 g4的值,右子节点 G R G_R GR要拿到 g 2 g_2 g2、 g 3 g_3 g3和 g 5 g_5 g5的梯度。 如何不期望参与方得知哪些样本用于这次分裂,于是使用一个映射向量 S = [ 1 , 0 , 0 , 1 , 0 ] T \mathbf{S} = [1,0,0,1,0]^T S=[1,0,0,1,0]T,标记为1的样本是被选中的样本需要加和,0相反。因此,为了保证样本分布不泄漏,这个向量需要通过秘密分享协议保护。在秘密分享协议的保护下,计算向量 S \mathbf{S} S和梯度向量的内积,即可得到梯度在分桶内的累加和。
向量𝑆中标记为1的样本是被选中的样本需要加和,0相反
-
准备环境和数据
- 加载纵向数据:https://www.secretflow.org.cn/zh-CN/docs/secretflow/v1.6.1b0/tutorial/FedDataFrameDataLoader
- SPU配置:https://www.secretflow.org.cn/zh-CN/docs/secretflow/v1.6.1b0/tutorial/spu_basics
-
设置训练参数:因为XGBoost的参数比较多,需要精心设置
https://www.secretflow.org.cn/zh-CN/docs/secretflow/v1.6.1b0/source/secretflow.ml.boost.ml.boost.ss_xgb_v#secretflow.ml.boost.ss_xgb_v.Xgb.train
点击上面链接,可以看到常用的参数说明,我这里截取了一些:
-
模型评估
- 不安全的评估方法:直接将预测结果和标签进行reveal,会泄漏标签。
- 安全的评估方法:使用
secretflow.stats.BiClassificationEval
,只在拥有标签的参与方上进行,因为评估需要标签计算。
3. SGB介绍
SecureBoost
https://www.secretflow.org.cn/zh-CN/docs/secretflow/v1.6.1b0/tutorial/SecureBoost
HEU 是个虚拟设备,每个 HEU 实例由多个参与方组成。由于 HE 是一种非对称加密算法,组成 HEU 实例的参与方分为 sk_keeper 和 evaluator, sk_keeper 有且仅有一个参与方,其拥有私钥和公钥,俱备解密、运算能力,其余参与方皆为 evaluator,仅有公钥,俱备运算能力。
-
准备环境和数据
-
需要设置HEU
heu_config = { 'sk_keeper': {'party': 'alice'}, 'evaluators': [{'party': 'bob'}], 'mode': 'PHEU', 'he_parameters': { # ou is a fast encryption schema that is as secure as paillier. 'schema': 'ou', 'key_pair': { 'generate': { # bit size should be 2048 to provide sufficient security. 'bit_size': 2048, }, }, }, 'encoding': { 'cleartext_type': 'DT_I32', 'encoder': "IntegerEncoder", 'encoder_args': {"scale": 1}, }, } heu = sf.HEU(heu_config, cluster_def['runtime_config']['field'])
- 有标签的一方设置为sk_keeper,其他参与方对应evaluator
-
-
设置具体参数
-
模型评估和保存
模型是分布式保存的,我们将保存到多个参与方,并从多个参与方中加载。
4. 对比
SS-XGB | SGB | |
---|---|---|
准备设备和数据 | 基于MPC协议,主要使用SPU设备 | 基于HE(OU/Paillier)和联邦学习,主要使用HEU设备(标签拥有方作为sk_keeper) |
训练参数 | 支持经典XGB功能,10个参数 | 参数更为丰富,吸收了XGB和LightGBM的特点。共24个参数,训练参数有18个。 |
模型保存 | 密态模型,各方持有分片,不联合,无法得到任何信息。 | 联邦模型,各方持有部分明文参数:分裂点、叶权重 |
预测结果 | MPC密文(SPUObject) | 标签持有方可以看到预测的明文信息, |
5. 隐语优势
-
设备抽象
底层提供的PYU让使用者无需感知MPC协议(ABY3、CHEETAH等),提供的HEU让使用者无需感知同态加密协议
-
分层架构—算法,Python实现
-
分层架构—原语,对瓶颈算子进行优化
-
共享共建:众人拾柴火焰高
三、原理和实现-从理论到工程
1. SS-XGB从经典算法到MPC算法
Paper:(2021)https://arxiv.org/pdf/2005.08479,可证安全树算法
(1) 确定保护的数据部分
确定哪些数据在秘密分享下进行,其中任何数据都被MPC保护
- 数据源(标签和特征)
- 数据处理中间特征的顺序
- 数据样本在节点上的分布
(2) 准备安全原语
设计一些用于MPC计算的算子,例如秘密分享、初始化、加法、乘法、Sigmoid/Argmax
(3) 改造数据结构和算法
原生XGBoost步骤 | SS-XGBoost(纵向) |
---|---|
各方进行特征分桶 | 每个参与方在本地进行特征分桶,记录各个特征对应的分裂点和分箱结果 |
计算一阶导数 G G G/二阶导数 H H H | 在MPC下计算各个样本的 G G G和 H H H |
计算每个分裂点的评分,选择最佳分裂点 | 在MPC中计算 |
同步分裂点产生新节点 | 各方计算(特征持有方获得真实分裂点,其他获得dummy信息) |
计算下一层每个节点样本分布 | 各方计算单方的已知信息,到MPC聚合 |
叶子节点权重计算 | 在MPC中计算 |
预测一个样本在哪一个叶子节点 | 各方计算单方的已知信息,到MPC聚合 |
2. SGB从经典算法到联邦算法
Paper:(2019) https://arxiv.org/pdf/2005.08479
- SF的SGB结合了secureboost的训练部分和ss-xgb的预测部分
- 没有精度损失,主要采用联邦学习和同态加密的方式保护隐私
(1) 确定保护的数据部分
确定哪些数据在加密下进行,其中任何数据都被同态加密保护
-
数据源:重点保护标签
-
数据处理中间特征的顺序
不保护样本在节点上的分布
(2) 准备安全原语
设计一些用于同态加密的算子:例如选择半同态加密Paillier主要需要实现加法算子
(3) 改造数据结构和算法
原生XGBoost步骤 | SS-XGBoost(纵向) |
---|---|
各方进行特征分桶 | 每个参与方在本地进行特征分桶,记录各个特征对应的分裂点和分箱结果 |
计算一阶导数 G G G/二阶导数 H H H | 在标签持有方计算 |
计算每个分裂点的评分,选择最佳分裂点 | 在标签持有方计算 |
同步分裂点产生新节点 | 各方计算(特征持有方获得真实分裂点,其他获得dummy信息) |
计算下一层每个节点样本分布 | 各方计算单方的已知信息,到标签持有方聚合 |
叶子节点权重计算 | 在标签持有方计算 |
预测一个样本在哪一个叶子节点 | 各方计算单方的已知信息,到标签持有方聚合 |
3. 开发实战
(1) 组件封装
如何在组件中封装算法
- 定义组件主体,方便外部调用:确定组件封装的功能和信息
- 名字
- 版本(用于组件升级)
-
定义参数和IO:根据算子已经实现的功能,定义组件参数和输入输出
(新参数定义、旧参数更改)
-
定义调用方法:建立安全设备,调用引擎方法和存储结果
(2) 算法修改
如何增加训练参数、修改功能
-
阅读代码:确定大的结构,熟悉主要的算法流程
例如sgb的主要代码:https://github.com/secretflow/secretflow/blob/v1.6.1b0/secretflow/ml/boost/sgb_v/factory/factory.py#L157
-
树的训练:(GlobalOrdermapBooster,https://github.com/secretflow/secretflow/blob/v1.6.1b0/secretflow/ml/boost/sgb_v/factory/booster/global_ordermap_booster.py#L135)
关键步骤:
- 首先,遍历
num_boost_round
用于生成每棵树 - 之后拟合得到当前迭代的树
- 将该树插入到已经训练好的树中,这里的
model_builder
实际调用的是SgbModel
的_insert_distributed_tree
(https://github.com/secretflow/secretflow/blob/v1.6.1b0/secretflow/ml/boost/sgb_v/model.py#L58C9-L58C33)来插入已经训练好的树。 - 最后返回得到的
SgbModel
- 首先,遍历
-
-
修改算法和对应的模块:明确要修改的算法模块,找到对应的代码模块
-
例如希望实现
goss
功能GOSS(Gradient-based One-Side Sampling)的核心思想是在训练过程中对样本进行有策略的采样,以减少计算量,同时尽可能保留模型的准确性。具体来说,GOSS在每次迭代中选择一部分样本基于它们的梯度值,分为两个部分:一部分是梯度值较大的样本,另一部分是随机选择的样本。梯度值较大的样本通常对模型的损失贡献较大,因此优先选择这些样本可以加速收敛。
-
步骤:
-
找到采样模块
implement core functions in actors在actor中定义相关核心实现
-
Modify params in corresponding component修改对应的参数
-
在secretflow/ml/boost/sgb_v/core/params.py中添加参数
-
-
-
修改单元测试,覆盖修改部分
(3) 模型导出
-
阅读代码:确认是否提供了模型保存的方法,当前SF只为SGB提供了相关代码。
- https://github.com/secretflow/secretflow/blob/v1.6.1b0/secretflow/ml/boost/sgb_v/model.py#L143
- 保存通用信息
- 保存叶子节点权重
- 保存split tree
- https://github.com/secretflow/secretflow/blob/v1.6.1b0/secretflow/ml/boost/sgb_v/model.py#L143
-
按照接口存读模型
https://www.secretflow.org.cn/zh-CN/docs/secretflow/v1.6.1b0/tutorial/SecureBoost
注意,模型是分布式的,我们将保存到多个参与方,并从多个参与方中加载。
-
模型保存
# each participant party needs a location to store saving_path_dict = { # in production we may use remote oss, for example. device: "./" + device.party for device in v_data.partitions.keys() } r = model.save_model(saving_path_dict) wait(r)
-
模型加载
# alice is our label holder model_loaded = load_model(saving_path_dict, alice) fed_yhat_loaded = model_loaded.predict(v_data, alice) yhat_loaded = reveal(fed_yhat_loaded.partitions[alice]) assert ( yhat == yhat_loaded ).all(), "loaded model predictions should match original, yhat {} vs yhat_loaded {}".format( yhat, yhat_loaded )
-
-
自定义模型序列化方法
-
理解模型数据类型,区分SPUObject/PYUObject 和所属方
-
自定义哪一方获取什么样的数据https://github.com/secretflow/secretflow/blob/v1.6.1b0/secretflow/ml/boost/sgb_v/core/distributed_tree/distributed_tree.py#L96
def to_dict(self) -> Dict: """Serialize to a Dictionary. Note this dict contain PYUObjects, cannot dump to file at this level.""" split_tree_dict = { device: device(lambda t: t.to_dict())(tree) for device, tree in self.split_tree_dict.items() } return { 'split_tree_dict': split_tree_dict, 'leaf_weight': self.label_holder(lambda arr: arr.tolist())( self.leaf_weight ), 'label_holder': self.label_holder, 'partition_column_counts': self.partition_column_counts, }
序列化为一个字典,但是由于
split_tree_dict
中包含PYUObject,不能直接进行pickle.dump
-
四、实践
-
数据集:
银行营销数据集https://archive.ics.uci.edu/dataset/222/bank+marketing
将原始数据集垂直切分,因为我们要进行纵向建模。
- Bank_0_8.csv:id列+8个特征
- Bank_8_16.csv:id列+8个特征
- Bank_y.csv:id列+标签
然后,将Bank_0_8.csv和Bank_y.csv放到参与方alice的数据目录下,将Bank_8_16.csv放到Bob的数据目录下。
1. 传统建模流程—single play
利用本方已经拥有的数据进行建模,直接使用sklearn和原生XGBoost
- 数据加载
# XGB alice single play
from sklearn.model_selection import train_test_split
import pandas as pd
import os
import xgboost as xgb
from sklearn.metrics import roc_auc_score
def load_pandas_data(path):
return pd.read_csv(path, index_col=0)
current_dir = os.getcwd()
data = load_pandas_data(f"{current_dir}/bank_0_8.csv")
label = load_pandas_data(f"{current_dir}/bank_y.csv")
这里的label中为yes的大概有5289,为no的样本大概有39922个,是一个不平衡数据集。
数据前五行差不多是这样的:
- 特征工程
可以看到有类别型特征,因此我们使用labelEncoder对数据进行处理。
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
data['job'] = encoder.fit_transform(data['job'])
data['marital'] = encoder.fit_transform(data['marital'])
data['education'] = encoder.fit_transform(data['education'])
data['default'] = encoder.fit_transform(data['default'])
data['housing'] = encoder.fit_transform(data['housing'])
data['loan'] = encoder.fit_transform(data['loan'])
X = data.to_numpy()
y = encoder.fit_transform(label)
拆分一下数据集,这里设置stratify。我们把数据集拆分了三份:训练集、验证集和测试集。
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=94, stratify=y
)
X_train, X_val, y_train, y_val = train_test_split(
X_train, y_train, test_size=0.2, random_state=94, stratify=y_train
)
-
模型训练
# Use "hist" for constructing the trees, with early stopping enabled. clf = xgb.XGBClassifier( tree_method="hist", n_estimators=20, max_depth=5, learning_rate=0.3, max_bin=10, early_stopping_rounds=5, base_score=0.5, eval_metric="auc", reg_lambda=0.1, min_child_weight=0, ) clf.fit(X_train, y_train, eval_set=[(X_val, y_val)])
...... [14] validation_0-auc:0.67392 [15] validation_0-auc:0.67285 fit time = 6.161916017532349s train set AUC score: 0.7141216984329877 test set AUC score: 0.6913379647330238 num_trees: 12
- 速度很快,总共用时约 6s。
- 在验证集上的auc约为0.69
2. 联合建模SecureBoost—double play
使用SF的1.6.1b0版本
- 联合建模,特征维度增加,模型效果一般会有所提升。
(1) 环境配置
- Ray cluster config
import secretflow as sf
cluster_config = {
"parties": {
"alice": {
# replace with alice's real address
"address": "alice方的rayfed通信地址",
"listen_addr": "alice方的rayfed监听地址"
},
"bob": {
"address": "bob方的rayfed通信地址",
"listen_addr": "bob方的rayfed监听地址"
},
},
'self_party': 'alice'
}
sf.shutdown()
sf.init(address="alice的ray head地址", cluster_config=cluster_config)
bob同理,不过要替换ray head地址
-
spu
import spu cluster_conf = { "nodes": [ { "party": "alice", "address": "alice的spu地址" }, { "party": "bob", "address": "bob的spu地址" }, ], "runtime_config": { "protocol": spu.spu_pb2.SEMI2K, "field": spu.spu_pb2.FM128, "sigmoid_mode": spu.spu_pb2.RuntimeConfig.SIGMOID_REAL, }, } spu_obj = sf.SPU( cluster_def=cluster_conf, link_desc={ "connect_retry_times": 60, "connect_retry_interval_ms": 1000 }, )
-
HEU配置
- sk_keeper:设置为标签拥有方
- ou:一种比较快的半同态加密协议
import spu heu_config = { 'sk_keeper': {'party': 'alice'}, 'evaluators': [{'party': 'bob'}], 'mode': 'PHEU', 'he_parameters': { # ou is a fast encryption schema that is as secure as paillier. 'schema': 'ou', 'key_pair': { 'generate': { # bit size should be 2048 to provide sufficient security. 'bit_size': 2048, }, }, }, 'encoding': { 'cleartext_type': 'DT_I32', 'encoder': "IntegerEncoder", 'encoder_args': {"scale": 1}, }, } heu = sf.HEU(heu_config, spu.spu_pb2.FM128)
(2) 数据集加载
- 加载特征
import pandas as pd
import os
from secretflow.data.vertical import read_csv as v_read_csv, VDataFrame
from secretflow.data.core import partition
current_dir = os.getcwd()
current_dir
alice = sf.PYU("alice")
bob = sf.PYU("bob")
data = v_read_csv(
{alice: f"{current_dir}/bank_0_8.csv",
bob: f"{current_dir}/bank_8_16.csv"},
keys = "id",
drop_keys = "id"
)
data.shape (45211, 16)
数据特征:
['age', 'job', 'marital', 'education', 'default', 'balance', 'housing', 'loan', 'contact', 'day', 'month', 'duration', 'campaign', 'pdays', 'previous', 'poutcome']
-
加载标签
label只在alice方拥有。
alice_y_pyu_object = alice(lambda path: pd.read_csv(path, index_col=0))(f"{current_dir}/bank_y.csv") label = VDataFrame(partitions={alice: partition(alice_y_pyu_object)})
(3) 特征工程
主要对类别型特征进行编码,这里使用secretflow提供的LabelEncoder代码进行编码。
# from data descryptions we know we need to encode data
from secretflow.preprocessing import LabelEncoder
encoder = LabelEncoder()
data['job'] = encoder.fit_transform(data['job'])
data['marital'] = encoder.fit_transform(data['marital'])
data['education'] = encoder.fit_transform(data['education'])
data['default'] = encoder.fit_transform(data['default'])
data['housing'] = encoder.fit_transform(data['housing'])
data['loan'] = encoder.fit_transform(data['loan'])
data['contact'] = encoder.fit_transform(data['contact'])
data['poutcome'] = encoder.fit_transform(data['poutcome'])
data['month'] = encoder.fit_transform(data['month'])
label = encoder.fit_transform(label)
-
数据集拆分
from secretflow.data.split import train_test_split as train_test_split_fed X_train_fed, X_test_fed = train_test_split_fed(data, test_size=0.2, random_state=94)
(4) SecureBoost
- 设置参数
from secretflow.ml.boost.sgb_v import (
get_classic_XGB_params,
Sgb,
)
sgb = Sgb(heu)
params = get_classic_XGB_params()
params['num_boost_round'] = 14
params['max_depth'] = 5
params['base_score'] = 0.5
params['reg_lambda'] = 0.1
params['learning_rate'] = 0.3
params['sketch_eps'] = 1 / 10 # 1 / 10 means maximum number of bins = 10
# only effective for sf 1.4 and above, but keeping it here is ok
params['enable_early_stop'] = True
params['enable_monitor'] = True
params['validation_fraction'] = 0.2
params['stopping_rounds'] = 5
params['stopping_tolerance'] = 0.01
params['seed'] = 94
params['first_tree_with_label_holder_feature'] = True
params['save_best_model'] = True
model = sgb.train(params, X_train_fed, y_train_fed)
get_classic_XGB_params
设置XGB参数模版
- 开始训练
model = sgb.train(params, X_train_fed, y_train_fed)
大概运行了1min多
(5) 模型评估
评估在训练集和测试集上的auc。
from secretflow.device.driver import reveal
from sklearn.metrics import roc_auc_score
# we reveal and look at the evaluation score in cleartext, but there are safer alternatives
print(
"train set AUC score: ",
roc_auc_score(reveal(y_train_fed.partitions[alice].data), reveal(model.predict(X_train_fed))),
"test set AUC score: ",
roc_auc_score(reveal(y_test_fed.partitions[alice].data), reveal(model.predict(X_test_fed))),
)
输出:
train set AUC score: 0.9015009566774648 test set AUC score: 0.8932670395547175
- 可以看到auc相比于单方训练有所提升,从0.79提升到0.89。
3. 联合建模SSXGB—double play
from secretflow.ml.boost.ss_xgb_v import Xgb, XgbModel
ss_xgb = Xgb(spu_obj)
model = ss_xgb.train(
params={
"num_boost_round": 14,
"max_depth": 5,
"learning_rate": 0.3,
"objective": "logistic",
"reg_lambda": 0.1,
"sketch_eps": 0.1,
"seed": 94,
},
dtrain=X_train_fed,
label=y_train_fed
)
不过,相比于SecureBoost,XGB的速度相对慢一些,同参数下,差不多用了5min多,速度慢了5倍。
- How much gain does ss-XGB achieve
train set AUC score: 0.9184086164491229 test set AUC score: 0.9056833629118386
性能相对高一些哦,SGB的test auc为0.89,而SS-XGB的test auc为0.9
4. 如果一方有特征,一方有标签会怎么样
What about if Alice has 15 features while Bob has 1 feature? Can SGB and ss-XGB outperform alice’s single party XGB’s model
- 如果特征仅在一方,那么本地计算会比纯SS要快得多。SGB的运行速度肯定会比SS-XGB快
(1) 数据准备
-
我们把数据集合并,bank_0_8.csv和bank_8_16.csv合并,放在alice方
>>> import pandas as pd >>> feat_df1 = pd.read_csv("bank_0_8.csv") >>> feat_df2 = pd.read_csv("bank_8_16.csv") >>> feat_all = pd.merge(feat_df1, feat_df2, on="id")
-
数据集bank_y.csv放到bob方
(2) 运行上述代码
假设模型参数一致,注意配置heu的时候把sk_keeper切换为bob,因为现在label在bob方。
SGB | SS-XGB | |
---|---|---|
fit time | 53s | 4min 54s |
train-auc | 0.9018640 | 0.9184116 |
Test-arc | 0.8942516 | 0.9056858 |