Pythonを使用した水平連合学習のローカルシミュレーションを実装する

Python を使用して複数のクライアントをローカルでシミュレートすると、サーバーはフェデレーテッド ラーニングのためにそれらを均一に管理します。クライアントは独自のデータを使用してモデルをローカルでトレーニングします。サーバーはトレーニング結果を集約し、モデルを更新してクライアントに配布し、クライアントはトレーニングを続けています。

目次

I.はじめに

1. フェデレーテッド ラーニングとは何ですか?

2. フェデレーション ラーニングにおけるプライバシーの問題

2. 環境整備

3. 具体的な実装

1.設定ファイルの書き込み

2. トレーニングデータセットを取得する

3. サーバー側のコードを作成する

4. クライアントコードを書く

5.メイン関数を書く

6. モデルファイルの書き込み

4. テストの実施

5. フェデレーションラーニングと集中トレーニングの効果の比較

6. まとめ


I.はじめに

1. フェデレーテッド ラーニングとは何ですか?

フェデレーテッド ラーニングは機械学習の概念です。その概念は、サーバーが最初に統合モデルをクライアントに配布し、次にクライアントがトレーニングにローカル データを使用し、その後モデルを更新してサーバーに送り返すというものです。すべての関係者からの新しいモデルを受け取ると、対応する計算が実行されてグローバル モデルが更新され、その後、更新されたモデルが分割され、一定の回数に達するか収束に達するまでトレーニングが継続され、最後にモデルが共同でトレーニングされます。複数のパーティが得られます。フェデレーテッド ラーニングの中核となる概念は、「モデルはデータの動きを理解しておらず、データは利用可能ではあるが目に見えない」というものです。

(ps: ここでは簡単な紹介のみを行います。フェデレーション ラーニングの詳細については、次の記事を参照してください)

https://blog.csdn.net/cao812755156/article/details/89598410?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164941377116780357226006%2522%252C%2522scm%2522 %253A%252220140713.130102334..%2522%257D&request_id =164941377116780357226006&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~top_positive~default-1-89598410.nonecase&utm_term=%E8%81%94%E9%82%A6%E5%AD%A6%E4% B9%A0&spm=1018.2226.3001.4450https://blog.csdn.net/cao812755156/article/details/89598410?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164941377116780357226006%2522%252C%2522scm%2522 %253A%252220140713.130102334..%2522%257D&request_id =164941377116780357226006&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~top_positive~default-1-89598410.nonecase&utm_term=%E8%81%94%E9%82%A6%E5%AD%A6%E4% B9%A0&spm=1018.2226.3001.4450

2. フェデレーション ラーニングにおけるプライバシーの問題

Federated Learning の本質は依然として機械学習ですが、従来のデータの集中学習は、各クライアントが独自に学習するように変更され、パラメーターのみが交換されるため、データのセキュリティとプライバシーを確​​保できますが、これはあくまでも最大限の効果にすぎません。さまざまな問題を考慮すると、フェデレーション ラーニングに対するこの種の攻撃は、学習プロセスのセキュリティを確保するために他のテクノロジーと組み合わせる必要もあります。

(追記: ここでは、フェデレーテッド ラーニングにおけるデータ セキュリティの問題についてはあまり分析しません。興味がある場合は、次の論文を参照してください。非常によく書かれており、理解しやすいです)

http://www.jos.org.cn/jos/article/abstract/6446 http://www.jos.org.cn/jos/article/abstract/6446


2. 環境整備

この実験は Python で実装され、機械学習ライブラリ PyTorch を使用します。

  • アナコンダ、Python、PyTorch
  • Pycharmを使用したコンパイラ
  • データセット: cifar10
  • モデル: ResNet-18

基本的なプロセス:

  1. サーバーは設定ファイルに従って初期化モデルを生成し、クライアントは自身のIDに従ってデータセットを重複せずに水平方向に切り出します。
  2. サーバーはグローバル モデルをクライアントに送信します
  3. クライアントはグローバル モデルを (サーバーから) 受信し、複数回のローカル反復を通じてローカル パラメーターの差を計算し、それをサーバーに返します。
  4. サーバーは各クライアント間の差異を集約してモデルを更新し、現在のモデルのパフォーマンスを評価します。
  5. 性能が基準を満たしていない場合は手順 2 を繰り返し、基準を満たしていない場合は終了します。

3. 具体的な実装

1.設定ファイルの書き込み

プロジェクトフォルダー直下にutilsフォルダーを作成し、その中に設定ファイルconf.jsonを作成します(その中のデータは必要に応じて変更できます)。(json ファイルではコメントが許可されていないため、各値は 2 回割り当てられ、1 回目はコメントとして使用され、2 回目は実際の値になります)

{
    "model_name" : "模型名称",
	"model_name" : "resnet18",

    "no_models" : "客户端总数量",
	"no_models" : 5,

    "type" : "数据集信息",
	"type" : "cifar",

	"global_epochs" : "全局迭代次数",
	"global_epochs" : 5,

	"local_epochs" : "本地迭代次数",
	"local_epochs" : 2,

	"k" : "每一轮选用k个客户端参与训练",
	"k" : 3,

	"batch_size" : "本地训练每一轮的样本数",
	"batch_size" : 32,

    "notes" : "本地训练的超参数设置",
	"lr" : 0.001,
	"momentum" : 0.0001,
	"lambda" : 0.1
}

2. トレーニングデータセットを取得する

プロジェクト フォルダーの下に datasets.py ファイルを作成します。

from torchvision import datasets, transforms

# 获取数据集
def get_dataset(dir, name):

    if name == 'mnist':
        # root: 数据路径
        # train参数表示是否是训练集或者测试集
        # download=true表示从互联网上下载数据集并把数据集放在root路径中
        # transform:图像类型的转换
        train_dataset = datasets.MNIST(dir, train=True, download=True, transform=transforms.ToTensor())
        eval_dataset = datasets.MNIST(dir, train=False, transform=transforms.ToTensor())

    elif name == 'cifar':
        # 设置两个转换格式
        # transforms.Compose 是将多个transform组合起来使用(由transform构成的列表)
        transform_train = transforms.Compose([
            # transforms.RandomCrop: 切割中心点的位置随机选取
            transforms.RandomCrop(32, padding=4),
            transforms.RandomHorizontalFlip(),
            transforms.ToTensor(),
            # transforms.Normalize: 给定均值:(R,G,B) 方差:(R,G,B),将会把Tensor正则化
            transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
        ])

        transform_test = transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
        ])

        train_dataset = datasets.CIFAR10(dir, train=True, download=True, transform=transform_train)
        eval_dataset = datasets.CIFAR10(dir, train=False, transform=transform_test)

    return train_dataset, eval_dataset

3. サーバー側のコードを作成する

プロジェクト フォルダーに server.py ファイルを作成します。サーバーの主な機能は、コンストラクター、集計関数 (FedAvg アルゴリズムを使用)、評価関数などのモデルを集計、評価、および配布することです。

import models
import torch

# 服务器类
class Server(object):
    # 定义构造函数
    def __init__(self, conf, eval_dataset):
        # 导入配置文件
        self.conf = conf
        # 根据配置文件获取模型
        self.global_model = models.get_model(self.conf["model_name"])
        # 生成测试集合加载器
        self.eval_loader = torch.utils.data.DataLoader(
          eval_dataset,
          # 根据配置文件设置单个批次大小(32)
          batch_size=self.conf["batch_size"],
          # 打乱数据集
          shuffle=True
        )

    # 模型聚合函数
    # weight_accumulator 存储了每个客户端上传参数的变化值
    def model_aggregate(self, weight_accumulator):
        # 遍历服务器的全局模型
        for name, data in self.global_model.state_dict().items():
            # 更新每一次乘以配置文件中的学习率
            update_per_layer = weight_accumulator[name] * self.conf["lambda"]
            # 累加
            if data.type() != update_per_layer.type():
                # 因为update_per_layer的type是floatTensor,所以将其转换为模型的LongTensor(损失精度)
                data.add_(update_per_layer.to(torch.int64))
            else:
                data.add_(update_per_layer)

    # 模型评估函数
    def model_eval(self):
        # 开启模型评估模式
        self.global_model.eval()
        total_loss = 0.0
        correct = 0
        dataset_size = 0
        # 遍历评估数据集合
        for batch_id, batch in enumerate(self.eval_loader):
            data, target = batch
            # 获取所有样本总量大小
            dataset_size += data.size()[0]
            # 如果可以的话存储到gpu
            if torch.cuda.is_available():
                data = data.cuda()
                target = target.cuda()
            # 加载到模型中训练
            output = self.global_model(data)
            # 聚合所有损失 cross_entropy 交叉熵函数计算损失
            total_loss += torch.nn.functional.cross_entropy(
              output,
              target,
              reduction='sum'
            ).item()
            # 获取最大的对数概率的索引值,即在所有预测结果中选择可能性最大的作为最终结果
            pred = output.data.max(1)[1]
            # 统计预测结果与真实标签的匹配个数
            correct += pred.eq(target.data.view_as(pred)).cpu().sum().item()
        # 计算准确率
        acc = 100.0 * (float(correct) / float(dataset_size))
        # 计算损失值
        total_l = total_loss / dataset_size

        return acc, total_l

4. クライアントコードを書く

プロジェクト フォルダーに client.py ファイルを作成します。クライアントの主な機能は、サーバーからグローバル モデルを受け取り、ローカル データを使用してモデルをトレーニングし、コンストラクターとローカル トレーニング関数を含む差分を返すことです。

import models
import torch

# 客户端类
class Client(object):
    #构造函数
    def __init__(self, conf, model, train_dataset, id=-1):
        # 读取配置文件
        self.conf = conf
        # 根据配置文件获取客户端本地模型(一般由服务器传输)
        self.local_model = models.get_model(self.conf["model_name"])
        # 客户端ID
        self.client_id = id
        # 客户端本地数据集
        self.train_dataset = train_dataset
        # 按ID对数据集集合进行拆分
        all_range = list(range(len(self.train_dataset)))
        data_len = int(len(self.train_dataset) / self.conf['no_models'])
        train_indices = all_range[id * data_len: (id + 1) * data_len]
        # 生成数据加载器
        self.train_loader = torch.utils.data.DataLoader(
            # 指定父集合
            self.train_dataset,
            # 每个batch加载多少样本
            batch_size=conf["batch_size"],
            # 指定子集合
            # sampler定义从数据集中提取样本的策略
            sampler=torch.utils.data.sampler.SubsetRandomSampler(train_indices)
        )

    # 模型本地训练函数
    def local_train(self, model):
        # 客户端获取服务器的模型,然后通过部分本地数据集进行训练
        for name, param in model.state_dict().items():
            # 用服务器下发的全局模型覆盖本地模型
            self.local_model.state_dict()[name].copy_(param.clone())
        # 定义最优化函数器用户本地模型训练
        optimizer = torch.optim.SGD(
            self.local_model.parameters(),
            lr=self.conf['lr'],
            momentum=self.conf['momentum']
        )
        # 本地训练模型
        # 设置开启模型训练
        self.local_model.train()
        # 开始训练模型
        for e in range(self.conf["local_epochs"]):
            for batch_id, batch in enumerate(self.train_loader):
                data, target = batch
                # 如果可以的话加载到gpu
                if torch.cuda.is_available():
                    data = data.cuda()
                    target = target.cuda()
                # 梯度初始化为0
                optimizer.zero_grad()
                # 训练预测
                output = self.local_model(data)
                # 计算损失函数cross_entropy交叉熵误差
                loss = torch.nn.functional.cross_entropy(output, target)
                # 反向传播
                loss.backward()
                # 更新参数
                optimizer.step()
            print("Epoch %d done." % e)
        # 创建差值字典(结构与模型参数同规格),用于记录差值
        diff = dict()
        for name, data in self.local_model.state_dict().items():
            # 计算训练后与训练前的差值
            diff[name] = (data - model.state_dict()[name])
            print("Client %d local train done" % self.client_id)
        # 客户端返回差值
        return diff

5.メイン関数を書く

コードを統合するには、プロジェクト フォルダーに main.py ファイルを作成します。

import argparse
import json
import random

import datasets
from client import *
from server import *

if __name__ == '__main__':
    # 设置命令行程序
    parser = argparse.ArgumentParser(description='Federated Learning')
    parser.add_argument('-c', '--conf', dest='conf')
    # 获取所有参数
    args = parser.parse_args()
    # 读取配置文件,指定编码格式为utf-8
    with open(args.conf, 'r', encoding='utf-8') as f:
        conf = json.load(f)
    # 获取数据集,加载描述信息
    train_datasets, eval_datasets = datasets.get_dataset("./data/", conf["type"])
    # 启动服务器
    server = Server(conf, eval_datasets)
    # 定义客户端列表
    clients = []
    # 创建10个客户端到列表中
    for c in range(conf["no_models"]):
        clients.append(Client(conf, server.global_model, train_datasets, c))

    print("\n\n")
    # 全局模型训练
    for e in range(conf["global_epochs"]):
        print("Global Epoch %d" % e)
        # 每次训练从clients列表中随机抽取k个进行训练
        candidates = random.sample(clients, conf["k"])
        print("select clients is: ")
        for c in candidates:
            print(c.client_id)
        # 累计权重
        weight_accumulator = {}
        # 初始化空模型参数weight_accumulator
        for name, params in server.global_model.state_dict().items():
            # 生成一个和参数矩阵大小相同的0矩阵
            weight_accumulator[name] = torch.zeros_like(params)
        # 遍历选中的客户端,每个客户端本地进行训练
        for c in candidates:
            diff = c.local_train(server.global_model)
            # 根据客户端返回的参数差值字典更新总体权重
            for name, params in server.global_model.state_dict().items():
                weight_accumulator[name].add_(diff[name])
        # 模型参数聚合
        server.model_aggregate(weight_accumulator)
        # 模型评估
        acc, loss = server.model_eval()

        print("Epoch %d, acc: %f, loss: %f\n" % (e, acc, loss))

6. モデルファイルの書き込み

プロジェクト フォルダーに models.py ファイルを作成して、使用するさまざまな機械学習モデルを定義します。

import torch
from torchvision import models

# 各种机器学习模型
def get_model(name="vgg16", pretrained=True):
    if name == "resnet18":
        model = models.resnet18(pretrained=pretrained)
    elif name == "resnet50":
        model = models.resnet50(pretrained=pretrained)
    elif name == "densenet121":
        model = models.densenet121(pretrained=pretrained)
    elif name == "alexnet":
        model = models.alexnet(pretrained=pretrained)
    elif name == "vgg16":
        model = models.vgg16(pretrained=pretrained)
    elif name == "vgg19":
        model = models.vgg19(pretrained=pretrained)
    elif name == "inception_v3":
        model = models.inception_v3(pretrained=pretrained)
    elif name == "googlenet":
        model = models.googlenet(pretrained=pretrained)

    if torch.cuda.is_available():
        return model.cuda()
    else:
        return model

4. テストの実施

プロジェクト全体の構造:

プロジェクト ディレクトリを入力し、コマンド ラインを使用して次のコマンドを実行します。

python main.py -c ./utils/conf.json

まず、データ セットがダウンロードされ、使用するデータ セットが含まれるデータ フォルダーがプロジェクト ディレクトリに表示されます。次に、トレーニング対象のクライアントがランダムに選択され、指定された数のトレーニングが完了すると停止します。トレーニングのラウンド。

5. フェデレーションラーニングと集中トレーニングの効果の比較

フェデレーション トレーニング: 合計 10 台のクライアント デバイスがあり、各ラウンドのトレーニングに参加するために 5 台が選択されます。各ローカル トレーニングの反復は 3 回、グローバルの反復数は 20 回です。

集中トレーニング: クライアント デバイスの数を 1 に変更し、各ラウンドでトレーニング用に 1 つのデバイスを選択します。集中トレーニングの効果は、構成を変更することで実現できます。

 

 図の単一点トレーニングは、特定のクライアントの下でローカル データを使用したモデル トレーニングの結果にすぎません。

  • シングルポイント トレーニング (青いバー) のモデル効果はフェデレーション トレーニング (緑色のバーと赤色のバー) の効果よりも大幅に低いことがわかります。これは、単一のクライアントのデータだけではデータを適切に学習できないことも示しています。グローバル分布特性があり、モデルの汎化能力が低い。

  • また、各ラウンドのフェデレーション トレーニングに参加するクライアントの数 (k 値) によってもパフォーマンスが異なり、k 値が大きいほど、各ラウンドのトレーニングに参加するクライアントの数が多くなり、パフォーマンスが向上します。良いですが、各ラウンドを完了するには比較的長い時間がかかります。


6. まとめ

現在、フェデレーテッド ラーニングは広く使用されており、さまざまな大企業が独自のフェデレーテッド ラーニング フレームワーク (WeBank の FATE、Google の TensorFlow、OpenMind の PySyft、Baidu の PaddleFL、Byte の FedLearner など) を開発しています。非常に優れたフレームワークです。この記事はシミュレーションのみです。クライアントとサーバーをローカルでトレーニングします。複数のマシンでトレーニングするには追加の学習が必要です。

おすすめ

転載: blog.csdn.net/SAGIRIsagiri/article/details/124048502