FATE是微众银行开发的联邦学习平台,是全球首个工业级的联邦学习开源框架,在github上拥有近4000stars,可谓是相当有名气的,该平台为联邦学习提供了完整的生态和社区支持,为联邦学习初学者提供了很好的环境,否则利用python从零开发,那将会是一件非常痛苦的事情。本篇博客内容涉及《联邦学习实战》第四章和第五章内容,使用的fate版本为1.6.0,下面就让我们开始吧。
FATE的官方文档提供了FATE的架构图,可以看到FATE的架构自上而下分为了四层,最上面一层是FATE提供的服务,包括FATE云服务,FATE面板,FATE任务调度管理,生命周期管理等,往下一层是FATE的机器学习核心组件,包括横向联邦学习、纵向联邦学习等,再往下一层是应用在FATE中的安全协议(隐私保护算法),包括同态加密,FedAvg,RSA等,最后一层是底层框架,又可以分为平行的三层,包括计算框架(TensorFlow,Pytorch,Spark),消息队列协议,以及存储框架。
FATE的主要功能如下:
FATE支持Linux和Mac系统,支持单机部署、集群部署和KubeFATE部署三种方式,具体的部署安装请查看FATE的官方文档,这里只提供利用Docker镜像配置FATE单机部署。
Docker的安装见我的另一篇博客,里面安装步骤很详细,官方文档推荐的是18.09版本,但是在Ubuntu20.04中,只提供了19以上的版本,所以我下载的是19版本,但是不影响FATE的使用。
设置部署的环境变量
$ export version={本次部署的FATE版本号, 如1.7.0}
通过镜像包
$ wget https://webank-ai-1251170195.cos.ap-guangzhou.myqcloud.com/fate/${version}/release/standalone_fate_docker_image_${version}_release.tar;
$ docker load < standalone_fate_docker_image_${version}_release.tar;
$ docker images | grep federatedai/standalone_fate
能看到对应的版本则镜像下载成功。
通过公共镜像拉取
$ docker pull federatedai/standalone_fate:${version}
$ docker run -d --name standalone_fate -p 8080:8080 federatedai/standalone_fate:${version};
$ docker ps -a | grep standalone_fate
端口映射到8080,启动的容器名称为standalone_fate
,后面是镜像名(仓库+镜像名)。后面的指令输入后能看到对应的版本容器启动则成功。
ps:在启动的时候我遇到过端口占用的情况,用指令杀死占用端口的进程,还要注意删除容器,再重复run,不然会显示容器重复,如果杀死进程还是有端口占用,建议重启。
进入容器
$ docker exec -it $(docker ps -aqf "name=standalone_fate") bash
如果报错(参数不匹配),就把$中的内容换成容器的ID即可,后面的bash表示用shell界面操作容器。
Toy测试
$ flow test toy -gid 10000 -hid 10000
如果成功,屏幕会显示
$ success to calculate secure_sum, it is 2000.0
单元测试
$ fate_test unittest federatedml --yes
如果成功,会显示下方语句
$ there are 0 failed test
有些用例算法在examples文件夹下,可以自行使用。
FATE构建联邦学习模型有两种不同编程范式。
由于脚本编程不够稳定,这里只使用组件化配置阐述。组件化配置需要提供两个配置文件。
利用FATE组件化配置的优点:
本章实验使用的数据集是威斯康星州临床科学中心开源的乳腺癌肿瘤数据集,该数据集内置在sklearn库中,可以直接加载查看。
from sklearn.datasets import load_breast_cancer
import pandas as pd
breast_dataset = load_breast_cancer()
breast = pd.DataFrame(breast_dataset.data, columns=breast_dataset.feature_names)
breast['y'] = breast_dataset.target
# 查看前五组数据
breast.head()
可以看到,数据总共有30个特征(10个属性分别以均值、标准差、最大差值出现三次),569个样本中,恶性肿瘤样本212个,良性有357个。
这是一个典型的二分类模型的训练,数据集并不大,使用简单的逻辑回归作为实验模型。
传统的线性回归定义:
y = W T X + b y=W^{T}X+b y=WTX+b
但是对于二分类模型来说,简单的函数线性映射是不能够分类的,还需要一个非线性函数映射到离散标签,
y = f ( W T X + b ) y=f(W^{T}X+b) y=f(WTX+b)
在逻辑回归中,使用logistic函数进行非线性映射,logistic表示为:
f ( z ) = 1 1 + e − z f(z)=\frac{1}{1+e^{-z} } f(z)=1+e−z1
图像表示为:
可以看出, z = W T X + b ≥ 0 z=W^{T}X+b\ge 0 z=WTX+b≥0,则判断为正例,否则判断为反例。
假设当前有两方参与横向联邦学习训练,取乳腺癌数据集前469条数据作为训练集,后100条作为测试集,数据切分的策略如下:
breast_1_train.csv
,剩余269条作为公司B的本地数据,存为breast_2_train.csv
。breast_eval.csv
。横向数据切分代码:
from sklearn.datasets import load_breast_cancer
import pandas as pd
# 导入并查看数据
breast_dataset = load_breast_cancer()
breast = pd.DataFrame(breast_dataset.data, columns=breast_dataset.feature_names)
breast.head()
# z-score标准化
breast = (breast-breast.mean()) / (breast.std())
# 获取列名
col_names = breast.columns.values.tolist()
# 更换列名
columns = {}
for idx, n in enumerate(col_names):
columns[n] = "x%d"%idx
breast = breast.rename(columns=columns)
# 插入每行序列和y
breast['y'] = breast_dataset.target
idx = range(breast.shape[0])
breast.insert(0, 'idx', idx)
# 打乱数据并生成csv
breast = breast.sample(frac=1)
train = breast.iloc[:469]
eval = breast.iloc[469:]
breast_1_train = breast.iloc[:200]
breast_2_train = breast.iloc[200:]
breast_1_train.to_csv('breast_1_train.csv', index=False, header=True)
breast_2_train.to_csv('breast_2_train.csv', index=False, header=True)
eval.to_csv('breast_eval.csv', index=False, header=True)
FATE构建联邦学习模型工作:
首先确定基目录,在运行FATE后,可以输入pwd
查看,我的基目录为:
fate_dir=/data/projects/fate/
根据数据切分的结果,把三组数据集(.csv)上传到$fate_dir/examples/data/
中。
由于文件需要上传到docker容器中,所以要特殊的文件上传工具,这里使用rz,如果docker中没有,则在docker中输入:
$ sudo apt-get install lrzsz
如果是在ubuntu本机上运行的,建议更换设备用xshell远程连接,否则在本机上输入:
$ rz -be
会出现一堆乱码,等半天也不跳出文件框,而在xshell中瞬间弹出,如果出现传输失败,那就要检查要传输的文件是否被使用。
用ls查看,左边一列即是上传的文件。
由于FATE所有运算都基于DTable格式进行,所以要把上传的文件转换为DTable。首先上传配置文件,配置文件的实例文件有两种,对应v1和v2两个版本。
upload_data.json
或 upload_host.json
或 upload_guest.json
,结构如下:{
"file": "examples/data/breast_hetero_guest.csv", // 数据文件路径,相对于当前所在路径
"head": 1, // 指定数据文件是否包含表头,1: 是,0: 否
"partition": 16, // 指定用于存储数据的分区数
"work_mode": 0, // 指定工作模式,0: 单机版,1: 集群版
"table_name": "breast_hetero_guest", // 需要转换为DTable格式的表名(相当于后续需要使用的表)
"namespace": "experiment" // DTable格式的表名对应的命名空间
}
{
"file": "/data/projects/fate/examples/data/breast_hetero_guest.csv", // 数据文件路径,相对于当前所在路径
"table_name": "breast_hetero_guest", // 需要转换为DTable格式的表名
"namespace": "experiment",// DTable格式的表名对应的命名空间
"head": 1, // 指定数据文件是否包含表头,1: 是,0: 否
"partition": 8, // 指定用于存储数据的分区数
"work_mode": 0, // 指定工作模式,0: 单机版,1: 集群版
"backend": 0 // 指定后端,0:EggRoll, 1: Spark _ RabbitMQ, 2: Spark + Pulsar
}
在FATE-1.6.0版本中,fate_flow的目录存放在fateflow/python/
中,所以执行upload的过程和书中不一样,为:
python /fate/python/fate_flow/fate_flow_client.py -f upload -c upload_data.json
从FATE-1.5开始,推荐使用FATE-Flow-Client Command Line执行FATE-Flow任务,上传命令格式为:
$ flow data upload -c example/dsl/v2/upload/upload_conf.json
本文使用v1版本的上传数据操作,如果有想使用v2的朋友,可以访问参考链接的第一个链接。
配置文件如下:
{
"file": "/fate/example/data/breast_1_train.csv",
"head": 1,
"partition": 8,
"work_mode": 0,
"table_name": "homo_breast_1_train",
"namespace": "homo_host_breast_train"
}
{
"file": "/fate/example/data/breast_2_train.csv",
"head": 1,
"partition": 8,
"work_mode": 0,
"table_name": "homo_breast_2_train",
"namespace": "homo_guest_breast_train"
}
{
"file": "/fate/example/data/breast_eval.csv",
"head": 1,
"partition": 8,
"work_mode": 0,
"table_name": "homo_breast_1_eval",
"namespace": "homo_host_breast_eval"
}
在根目录下,输入python /fate/python/fate_flow/fate_flow_client.py -f upload -c upload_data.json
(视每个人的环境而定)。返回如下代码则成功
{
"data": {
"board_url": "http://127.0.0.1:8080/index.html#/dashboard?job_id=202203110325129735931&role=local&party_id=0",
"job_dsl_path": "/fate/jobs/202203110325129735931/job_dsl.json",
"job_id": "202203110325129735931",
"job_runtime_conf_on_party_path": "/fate/jobs/202203110325129735931/local/job_runtime_on_party_conf.json",
"job_runtime_conf_path": "/fate/jobs/202203110325129735931/job_runtime_conf.json",
"logs_directory": "/fate/logs/202203110325129735931",
"model_info": {
"model_id": "local-0#model",
"model_version": "202203110325129735931"
},
"namespace": "homo_host_breast_train",
"pipeline_dsl_path": "/fate/jobs/202203110325129735931/pipeline_dsl.json",
"table_name": "homo_breast_1_train",
"train_runtime_conf_path": "/fate/jobs/202203110325129735931/train_runtime_conf.json"
},
"jobId": "202203110325129735931",
"retcode": 0,
"retmsg": "success"
}
FATE支持常用的机器学习模型,如:
可以看到在fate dsl_conf的V2版本,模型种类又比书中所描述的详细不少。
V2版本在预设任务配置上有一些改变和提升,最直观的一点是不会自动为训练任务生成预测dsl,需要用户自定义,也可以使用flow命令自动配置预测dsl。
为了在V2中使用命令行客户端,即flow命令,需要进行一系列配置。参考官方文档,FATE Client
包含了FATE项目多个客户端:Pipeline
, FATE Flow Client
和 FATE Test
。
获取所有命令分类和子命令:
[IN]
flow
[OUT]
Usage: flow COMMAND [OPTIONS]
Fate Flow Client
Options:
-h, --help Show this message and exit.
Commands:
component Component Operations
data Data Operations
init Flow CLI Init Command
job Job Operations
model Model Operations
queue Queue Operations
table Table Operations
task Task Operations
安装FATE CLient
$ pip install fate-client
# 或者
$ pip install fate-client==${version}
集群上安装请移步官方文档。
初始化
# 指定fateflow的IP地址和端口进行初始化
$ flow init --ip 192.168.0.1 --port 9380
获得如下返回视为初始化成功:
{
"retcode": 0,
"retmsg": "Fate Flow CLI has been initialized successfully."
}
验证
查询任务情况
$ flow job query
返回以下即可:
{
"data": [],
"retcode": 0,
"retmsg": "no job could be found"
}
本章使用逻辑回归模型,进入$fate_dir/examples/dsl/v1/homo_logistic_regression
目录,该目录下已经有很多预设的dsl文件和conf文件。修改homo_lr_train_dsl.json
和homo_lr_train_conf.json
。修改推荐用vim
,如果没有,用yum
或apt-get
进行安装:
$ yum install -y vim
# 如果yum没有
$ apt-get install -y vim
test_homolr_train_dsl.json:描述任务模块,将任务模块以有向无环图形式组合。包括dataio_0
、homo_lr_0
、evaluation_0
,这些组件分别用作数据格式转换、自带横向逻辑回归组件、模型评估。
test_homolr_train_conf.json:设置各个组建的参数。找到role
字段,修改三个参数,train_data
下的name
和namespace
,以及表示标签列对应的属性名label_name
。
接着是algorithm_parameters字段,它是用来设置训练的超参数信息,包括学习率,优化函数,迭代次数等,可以根据实际需要自行修改。
文件配置结束,在当前位置输入:
$ python /fate/python/fate_flow/fate_flow_client.py -f submit_job -d test_homolr_train_job_dsl.json -c test_homolr_train_job_conf.json
在FATEboard上查看任务运行情况。
常用的模型评估方法包括留出法和交叉验证法。
由于之前已经有了额外的数据集作为评估数据集,这里用留出法。为了将留出的数据用于模型评估,需要修改dsl组建配置。具体来说,在test_homolr_train_job_dsl
文件中,在components
组件下添加一个新的数据输入组件dataio_1
,用来读取测试数据,如下所示。
{
"components" : {
"dataio_0": {
"module": "DataIO",
"input": {
"data": {
"data": ["args.train_data"]
}
},
"output": {
"data": ["train"],
"model": ["dataio"]
}
},
"dataio_1": {
"module": "DataIO",
"input": {
"data": {
"data": ["args.eval_data"] # 表示测试数据采用conf文件中的args.eval_data设置的文件
},
"model": ["dataio_0.dataio"] # 使用数据训练模块"dataio_0.dataio"的输出作为"dataio_1"的模型输入
},
"output": {
"data": ["eval_data"] # 设置输出的data名称,可任意设定
}
},
"feature_scale_0": {
"module": "FeatureScale",
"input": {
"data": {
"data": ["dataio_0.train"]
}
},
"output": {
"data": ["train"],
"model": ["feature_scale"]
}
},
"feature_scale_1": {
"module": "FeatureScale",
"input": {
"data": {
"data": ["dataio_1.eval_data"]
}
},
"output": {
"data": ["eval_data"],
"model": ["feature_scale"]
}
},
"homo_lr_0": {
"module": "HomoLR",
"input": {
"data": {
"train_data": ["feature_scale_0.train"]
}
},
"output": {
"data": ["train"],
"model": ["homolr"]
}
},
"homo_lr_1": {
"module": "HomoLR",
"input": {
"data": {
"eval_data": ["feature_scale_1.eval_data"] # 指定训练数据
},
"model": ["homo_lr_0.homolr"]
},
"output": {
"data": ["eval_data"],
"model": ["homolr"]
}
},
"evaluation_0": {
"module": "Evaluation",
"input": {
"data": {
"data": ["homo_lr_0.train"]
}
}
},
"evaluation_1": {
"module": "Evaluation",
"input": {
"data": {
"data": ["homo_lr_1.eval_data"]
}
}
}
}
}
然后修改conf文件,在role_parameters
字段中为guest和host添加测试数据的DTable表名。
{
"initiator": {
"role": "guest",
"party_id": 10000
},
"job_parameters": {
"work_mode": 0
},
"role": {
"guest": [10000],
"host": [10000],
"arbiter": [10000]
},
"role_parameters": {
"guest": {
"args": {
"data": {
"train_data": [
{
"name": "homo_breast_2_train",
"namespace": "homo_guest_breast_train"
}
],
"eval_data": [
{
"name": "homo_breast_2_eval",
"namespace": "homo_guest_breast_eval"
}
]
}
},
"dataio_0": {
"with_label": [true],
"label_name": ["y"],
"label_type": ["int"],
"output_format": ["dense"]
}
},
"host": {
"args": {
"data": {
"train_data": [
{
"name": "homo_breast_1_train",
"namespace": "homo_host_breast_train"
}
],
"eval_data": [
{
"name": "homo_breast_1_eval",
"namespace": "homo_host_breast_eval"
}
]
}
},
"dataio_0": {
"with_label": [true],
"label_name": ["y"],
"label_type": ["int"],
"output_format": ["dense"]
},
"evaluation_0": {
"need_run": [false]
},
"evaluation_1": {
"need_run": [false]
}
}
},
"algorithm_parameters": {
"homo_lr_0": {
"penalty": "L2",
"optimizer": "sgd",
"tol": 1e-05,
"alpha": 0.01,
"max_iter": 20,
"early_stop": "diff",
"batch_size": 320,
"learning_rate": 0.05,
"validation_freqs": 1,
"init_param": {
"init_method": "zeros"
},
"encrypt_param": {
"method": null
},
"cv_param": {
"n_splits": 4,
"shuffle": true,
"random_seed": 33,
"need_cv": false
}
},
"evaluation_0": {
"eval_type": "binary"
}
}
}
执行submit_job
命令。
$ python /fate/python/fate_flow/fate_flow_client.py -f submit_job -d test_homolr_train_job_dsl.json -c test_homolr_train_job_conf.json
可以查看带有模型评估算法模块的有向无环图,该图由训练模块和评估模块两部分构成。
对于多个客户端参与的场景,需要在配置文件中修改部分参数值。
role_parameters
字段中,host子字段,添加对应新的客户端DTable表名和命名空间。本来这篇博客因该早在半个月前就能写完,但是编写到一半,代码跑不出来了。。。当时用的是FATE1.7.0,单元测试都没问题,但是只要运行甚至是上传数据,都会报错,报错如下:
不知道访问了多少论坛,在FATE的各个交流群都问了个遍,至今杳无音讯,只能作罢,其中重装fate,重装docker,换fate的旧版本,就差重装系统了。终于测试fate1.6.0版本时,没有这个问题了(虽然有其他小问题但不影响结果)。这半个多月来对我的心智是一个很大的考验,我甚至一度摆烂觉得永远不可能跑通了,好在功夫不负有心人,最后我想对环境有问题的小伙伴们提个建议,如果怎么都没办法跑通,沉淀一下自己,歇几天做别的事情,没准就能转换思路了。最后的最后,如果有朋友知道上图问题如何解决,请务必告诉我,万分感谢!
https://blog.csdn.net/Sisyphus_98/article/details/122933110
https://blog.csdn.net/qq_41841524/article/details/117662143?spm=1001.2014.3001.5502