联邦学习实战基于FATE框架的MNIST手写数字识别——卷积神经网络

Posted HERODING23

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了联邦学习实战基于FATE框架的MNIST手写数字识别——卷积神经网络相关的知识,希望对你有一定的参考价值。

基于FATE框架的MNIST手写数字识别——卷积神经网络

前言

ATE是微众银行开发的联邦学习平台,是全球首个工业级的联邦学习开源框架,在github上拥有超过4000stars,可谓是相当有名气的,该平台为联邦学习提供了完整的生态和社区支持,为联邦学习初学者提供了很好的环境,否则利用python从零开发,那将会是一件非常痛苦的事情。本篇博客是FATE联邦学习实战的第二次实践,目的是在FATE框架上成功训练出手写数字识别模型,至于之后联邦学习实战内容,就是在手写数字识别模型的基础上,增加加密算法的应用,下面就让我们开始吧!


1. 下载MNIST数据集

由于在FATE中,所有的数据集都要转换为DTable格式进行训练,而DTable又是通过csv文件的转换生成的数据结构,所以MNIST数据集不是传统的图片格式,而是转化成csv文件格式。转换csv文件格式的方法有两种,一个是直接从Kaggle官网上下载,第二种是自定义转换格式的python代码实现。

1.1 Kaggle

第一种方法是直接在Kaggle的MNIST的csv格式数据集链接下载即可,如果没有注册的朋友需要注册一下才能下载,过程还是很简单的,打开的csv文件内容如下所示:可以看到每张图片以28×28像素点的数据存储下来,每个像素点中的值为灰度值,范围为0~255。

Python格式转换

第二种方法参考了博主ysk2931文章中的方法,他的思路为首先解压,将gz文件转换成-ubyte,再将ubyte文件转换为csv文件,代码如下:

def convert(imgf, labelf, outf, n):
    f = open(imgf, "rb")
    o = open(outf, "w")
    l = open(labelf, "rb")
 
    f.read(16)
    l.read(8)
    images = []
 
    for i in range(n):
        image = [ord(l.read(1))]
        for j in range(28*28):
            image.append(ord(f.read(1)))
        images.append(image)
 
    for image in images:
        o.write(",".join(str(pix) for pix in image)+"\\n")
    f.close()
    o.close()
    l.close()
 
convert("train-images.idx3-ubyte", "train-labels.idx1-ubyte",
        "mnist_train.csv", 60000)
convert("t10k-images.idx3-ubyte", "t10k-labels.idx1-ubyte",
        "mnist_test.csv", 10000)
 
print("Convert Finished!")

2. 数据集分割

将训练数据集分割为同等大小的两部分,即分为两个各有3w条数据的数据集,分别作为两个参与方参与训练的训练集。由于FATE训练必须要有id,所以首先,在第一列label前面加上新的一列id,新的一列第一行为id,之后行为序号。

awk -F'\\t' -v OFS=',' '  NR == 1 print "id",$0; next  print (NR-1),$0' mnist_train.csv > mnist_train_with_id.csv

接着将表头的label换成y,因为FATE训练的conf文件中默认把y作为标签。

sed -i "s/label/y/g" mnist_train_with_id.csv

mnist_train_with_id.csv文件进行分割,每个文件30001行,其中一行表头,其余都是数据,生成两个文件:mnist_train_3w.csvaamnist_train_3w.csvab

split -l 30001 mnist_train_with_id.csv mnist_train_3w.csv

将生成的两个文件拷贝为csv文件。

mv mnist_train_3w.csvaa mnist_train_3w_a.csv
mv mnist_train_3w.csvab mnist_train_3w_b.csv

再将mnist_train_3w_a.csv的heading复制插入到mnist_train_3w_b.csv中。

sed -i "`cat -n mnist_train_3w_a.csv |head -n 1`" mnist_train_3w_b.csv

同时对测试集数据进行相同的处理,但注意不需要分割。

3.数据集上传

数据集从本地上传到FATE中有两种方式,分别是通过docker上传和使用rz工具上传。

3.1 docker上传

在本地文件目录下的终端环境中输入如下代码,将文

docker cp mnist_train_3w_a.csv fate:fate/examples/data/
docker cp mnist_train_3w_b.csv fate:fate/examples/data/
docker cp mnist_test.csv fate:fate/examples/data/

3.2 rz工具

如果docker中没有安装rz,那么就输入如下命令安装:

sudo apt-get install lrzsz

如果是在ubuntu主机上运行的,建议更换设备用xshell远程连接,否则在主机上输入命令会报乱码,在xshell中的docker环境下输入:

rz -be

直接弹出文件框选择文件进行上传。

3.3 FATE数据上传

在FATE中,所有训练的数据都要转换为DTable格式进行训练,所以还需要将之前上传的csv文件通过upload转换为DTable格式。
首先进入FATE容器:

docker exec -it fate bash

csv转换为DTable格式需要编写配置文件,配置文件的实例有两种,对应v1和v2两个版本,这里仅介绍v1版本。示例文件在fate/example/dsl/v1下。
upload_data.jsonupload_host.jsonupload_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格式的表名对应的命名空间

在fate:1.6中输入如下命令就可以将csv文件数据转为DTable格式。

python /fate/python/fate_flow/fate_flow_client.py -f upload -c upload_data.json

首先编写host和guest两个参与方的训练数据文件,配置文件如下:

  • 参与方A数据上传文件。

    "file": "/fate/example/data/mnist_train_3w_a.csv",
    "head": 1,
    "partition": 8,
    "work_mode": 0,
    "table_name": "homo_mnist_1_train",
    "namespace": "homo_host_mnist_train"


  • 参与方B数据上传文件。

    "file": "/fate/example/data/mnist_train_3w_b.csv",
    "head": 1,
    "partition": 8,
    "work_mode": 0,
    "table_name": "homo_mnist_1_train",
    "namespace": "homo_guest_mnist_train"


接着编写host和guest两个参与方的测试数据文件,配置文件如下:

  • 参与方A数据上传文件。

    "file": "/fate/example/data/mnist_test.csv",
    "head": 1,
    "partition": 8,
    "work_mode": 0,
    "table_name": "homo_mnist_1_test",
    "namespace": "homo_host_mnist_test"


  • 参与方B数据上传文件。

    "file": "/fate/example/data/mnist_test.csv",
    "head": 1,
    "partition": 8,
    "work_mode": 0,
    "table_name": "homo_mnist_2_test",
    "namespace": "homo_guest_mnist_test"


如果运行的结果格式与下面代码相同,并且fate_board不报错,则数据上传成功。


    "data": 
        "board_url": "http://127.0.0.1:8080/index.html#/dashboard?job_id=202204110957045063425&role=local&party_id=0",
        "job_dsl_path": "/fate/jobs/202204110957045063425/job_dsl.json",
        "job_id": "202204110957045063425",
        "job_runtime_conf_on_party_path": "/fate/jobs/202204110957045063425/local/job_runtime_on_party_conf.json",
        "job_runtime_conf_path": "/fate/jobs/202204110957045063425/job_runtime_conf.json",
        "logs_directory": "/fate/logs/202204110957045063425",
        "model_info": 
            "model_id": "local-0#model",
            "model_version": "202204110957045063425"
        ,
        "namespace": "homo_host_mnist_test",
        "pipeline_dsl_path": "/fate/jobs/202204110957045063425/pipeline_dsl.json",
        "table_name": "homo_mnist_1_test",
        "train_runtime_conf_path": "/fate/jobs/202204110957045063425/train_runtime_conf.json"
    ,
    "jobId": "202204110957045063425",
    "retcode": 0,
    "retmsg": "success"


查看数据集,也是没有问题的。

4. 模型训练

4.1 构建模型

对于手写数字识别模型的训练,可以通过很多深度学习模型进行搭建,比如三层隐藏层的全连接神经网络,卷积神经网络等,当然FATE也内置了许多深度学习模型可用,比如ResNet等。这里我们可以采用自定义模型的方法,自定义多层卷积神经网络来训练模型。
首先创建一个python文件。

vim model.py

在python文件中构建模型,并将模型转换为json格式的数据。

import keras
from keras.models import Sequential
from keras.layers import Reshape, Dense, Conv2D, Flatten, MaxPooling2D
model = Sequential()
model.add(Reshape((28,28,1), input_shape=(784,)))
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dense(10, activation='softmax'))

json = model.to_json()
print(json)

注意FATE框架中的python环境并没有安装TensorFlow和keras,需要自己用pip安装,这里是keras与TensorFlow对应的版本号链接,各位小伙伴可以根据对应的版本进行下载,这里我提供一个样例。

# 卸载已经安装的工具包
pip uninstall tensorflow
pip uninstall tensorflow-cpu
pip uninstall keras
pip uninstall fate-client
pip uninstall numpy

# 安装对应版本的工具包
pip install tensorflow==1.14 -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install keras==2.2.5 -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install numpy==1.16.4  -i https://pypi.tuna.tsinghua.edu.cn/simple/

在终端py文件对应的目录下输入:

python model.py

可以得到如下的输出信息。

"class_name": "Sequential", "config": "name": "sequential_1", "layers": ["class_name": "Reshape", "config": "name": "reshape_1", "trainable": true, "batch_input_shape": [null, 784], "dtype": "float32", "target_shape": [28, 28, 1], "class_name": "Conv2D", "config": "name": "conv2d_1", "trainable": true, "batch_input_shape": [null, 28, 28, 1], "dtype": "float32", "filters": 32, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "relu", "use_bias": true, "kernel_initializer": "class_name": "VarianceScaling", "config": "scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null, "bias_initializer": "class_name": "Zeros", "config": , "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null, "class_name": "MaxPooling2D", "config": "name": "max_pooling2d_1", "trainable": true, "dtype": "float32", "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last", "class_name": "Conv2D", "config": "name": "conv2d_2", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "relu", "use_bias": true, "kernel_initializer": "class_name": "VarianceScaling", "config": "scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null, "bias_initializer": "class_name": "Zeros", "config": , "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null, "class_name": "MaxPooling2D", "config": "name": "max_pooling2d_2", "trainable": true, "dtype": "float32", "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last", "class_name": "Conv2D", "config": "name": "conv2d_3", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "relu", "use_bias": true, "kernel_initializer": "class_name": "VarianceScaling", "config": "scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null, "bias_initializer": "class_name": "Zeros", "config": , "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null, "class_name": "Flatten", "config": "name": "flatten_1", "trainable": true, "dtype": "float32", "data_format": "channels_last", "class_name": "Dense", "config": "name": "dense_1", "trainable": true, "dtype": "float32", "units": 64, "activation": "relu", "use_bias": true, "kernel_initializer": "class_name": "VarianceScaling", "config": "scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null, "bias_initializer": "class_name": "Zeros", "config": , "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null, "class_name": "Dense", "config": "name": "dense_2", "trainable": true, "dtype": "float32", "units": 10, "activation": "softmax", "use_bias": true, "kernel_initializer": "class_name": "VarianceScaling", "config": "scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null, "bias_initializer": "class_name": "Zeros", "config": , "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null], "keras_version": "2.2.5", "backend": "tensorflow"

4.2 修改配置文件

  • conf文件
    conf文件作用是设置模型的输入数据与模型的超参数。将上面的输出json结果复制粘贴,复制样例配置文件test_homo_nn_keras_temperate.json,修改为homo_cnn_conf.json,将刚刚输出的json格式的模型拷贝到algorithm_parameters:homo_nn_0:位置:
vim /fate/examples/dsl/v1/homo_nn/homo_cnn_conf.json


此外还有特别需要注意的,在输入csv文件的时候,y值的范围是0~9,而经过模型训得到的结果是one-hot编码,所以必须把y标签的值转换为one-hot编码才能进行剃度下降,在conf文件的algorithm_parameters下增加一项“encode_label”: true,如图所示。


在conf文件中还需要修改默认的输入数据DTable的namespace和name,改成我们之前上传的namepsace和name。
最后还要对超参数进行调整,读者可以自行修改,这里只是给一个参考:

这里需要注意两点,第一是学习率不要太大,否则会出现预测结果都是7的情况。第二是注意最后一项"evaluation_0",这是评估部分的核心,表示是多分类问题,这样用训练集进行拟合才能得到正确的结果。

  • dsl文件
    dsl文件作用是描述任务模块,将任务模型以有向无环图形式组合。范例文件test_homo_nn_train_then_predict.json内容如下。

在训练阶段,只有homo_nn_0发挥了模型训练的作用,所以将文件中的homo_nn_1删除,添加评估模块,默认使用训练数据进行模型的评估预测。

4.3 训练模型

在终端对应目录下输入:

python /fate/python/fate_flow/fate_flow_client.py -f submit_job -c homo_cnn_conf.json -d homo_cnn_dsl.json

输出如下信息,则文件中没有语法上的错误:


    "data": 
        "board_url": "http://127.0.0.1:8080/index.html#/dashboard?job_id=202204301157384635175&role=guest&party_id=10000",
        "job_dsl_path": "/fate/jobs/202204301157384635175/job_dsl.json",
        "job_id": "202204301157384635175",
        "job_runtime_conf_on_party_path": "/fate/jobs/202204301157384635175/guest/job_runtime_on_party_conf.json",
        "job_runtime_conf_path": "/fate/jobs/202204301157384635175/job_runtime_conf.json",
        "logs_directory": "/fate/logs/202204301157384635175",
        

以上是关于联邦学习实战基于FATE框架的MNIST手写数字识别——卷积神经网络的主要内容,如果未能解决你的问题,请参考以下文章

阅读笔记联邦学习实战——用FATE从零实现纵向线性回归

PaddlePaddle框架学习MNIST手写数字识别

联邦学习FATE框架安装搭建 - CentOS8

深度学习笔记:基于Keras库的MNIST手写数字识别

阅读笔记联邦学习实战——用FATE从零实现横向逻辑回归

联邦学习实战-2-用FATE从零实现横向逻辑回归