首页 / 资讯中心 / 文章详情

基于Python的联邦学习实验:三个实验+源代码+模型+图片演示

基于Python的联邦学习实验:三个实验+源代码+模型+图片演示 ★ FEATURED ARTICLE
简介本资源是一套基于Python实现的联邦学习实验项目面向人工智能、计算机及相关专业的在校学生、教师与企业研发人员可用于课程设计、毕业设计、项目立项演示或算法进阶学习。项目围绕FedAvg、FedPer、FedRep与自研FedOur等方法展开三组对比实验在Cifar-10上比较各算法准确率与目标损失在MedMNIST上测试10、50、100等不同客户端数量下的表现并在Chest X-Ray Images数据集上验证全局模型与本地模型经Meta-Transfer微调后的效果。压缩包共43个文件约631KB包含14个Python源码文件、18张png与2张jpg实验曲线图、5个xml配置及md说明文档覆盖模型定义、采样、训练与聚合等模块目录结构清晰。目前已有227人学习下载。读者可获得完整可运行的实验代码、ResNet等模型实现、多组准确率与损失对比图以及可复现的联邦学习评测流程便于快速理解算法差异并在此基础上修改扩展。1. 联邦学习实验到底在做什么从一次「模型不收敛」的翻车说起很多人第一次跑联邦学习都会遇到一个很玄学的现象本地训练准确率一路涨全局模型聚合完反而掉点甚至几轮之后直接不收敛。这不是代码写错了而是联邦学习本身和集中式训练是两套逻辑。这个标题里的「基于 Python 实现的联邦学习实验三个实验 源代码 模型 图片演示」本质上是让你用 Python 从零搭一套可复现的联邦训练流程把数据分散在多个客户端、本地训练、参数上传、服务器聚合这条链路完整跑通并且用三个递进的实验把「能不能用、怎么调、坑在哪」讲清楚。它适合两类人一类是刚接触联邦学习、想找一个能直接跑起来的最小工程的新手另一类是想验证某个聚合策略或非独立同分布场景、需要一个干净实验骨架的熟手。三个实验通常对应基础 FedAvg 跑通、数据异构下的表现、以及聚合策略或攻击场景的对比。下面我按自己搭这套东西的顺序把选型、代码、参数和踩过的坑讲一遍。2. 环境与数据准备把联邦实验的地基打牢2.1 为什么用 PyTorch NumPy 而不是纯手写联邦学习的核心计算量在本地训练这部分交给 PyTorch 最省事自动求导、优化器、DataLoader 都是现成的。服务器端的聚合逻辑其实很轻就是加权平均用 NumPy 或 PyTorch 张量操作都行。我一般会统一用 PyTorch 张量避免在 NumPy 和 Tensor 之间来回转换时踩到设备不一致的坑。选型上还有一点不要一上来就上 Flower、FedML 这类框架。框架封装太厚你调参时根本不知道聚合发生在哪一层出了问题只能看黑匣子。先用两百行左右的裸代码把 FedAvg 跑通再考虑换框架这是血泪经验。依赖清单很固定pip install torch torchvision numpy matplotlibtorch负责本地模型和训练torchvision用来加载 MNIST 或 CIFAR-10 这类标准数据集numpy做聚合时的数值处理matplotlib出准确率曲线图也就是标题里说的「图片演示」。版本上 Python 3.8 及以上都能跑CUDA 不是必须的CPU 跑小数据集完全够用。2.2 用 Dirichlet 分布造非独立同分布数据联邦学习最真实的场景就是每个客户端的数据分布不一样。如果所有客户端数据都是均匀随机的那联邦训练和集中式训练几乎没差别实验就失去意义了。常见做法是用 Dirichlet 分布给每个客户端分配类别比例alpha越小数据越异构。import numpy as np from torchvision import datasets, transforms def dirichlet_split(labels, n_clients, alpha0.5): # labels: 全部样本的标签数组 n_classes len(np.unique(labels)) # 为每个类别生成一个 Dirichlet 分布决定该类样本分给各客户端的比例 proportions np.random.dirichlet([alpha] * n_clients, n_classes) client_idx [[] for _ in range(n_clients)] for c in range(n_classes): idx_c np.where(labels c)[0] np.random.shuffle(idx_c) # 按比例切分该类样本 split_points (np.cumsum(proportions[c]) * len(idx_c)).astype(int) split_points np.clip(split_points, 0, len(idx_c)) prev 0 for i, point in enumerate(split_points): client_idx[i].extend(idx_c[prev:point]) prev point return client_idx train_set datasets.MNIST(./data, trainTrue, downloadTrue) labels np.array(train_set.targets) client_indices dirichlet_split(labels, n_clients10, alpha0.5)这段代码的逻辑是对每个类别单独做一次 Dirichlet 采样得到该类样本在 10 个客户端上的分配比例再按比例切分索引。alpha是关键参数取 0.1 时每个客户端可能只有一两个类别的数据极端异构取 1.0 以上就接近均匀分布。我一般会准备alpha0.1和alpha1.0两组数据分别对应异构和近似同构方便对比。提示np.random.dirichlet的返回值之和为 1但浮点误差可能导致cumsum最后一项略小于 1所以要用np.clip兜底否则会漏掉尾部样本。2.3 把数据封装成客户端可迭代对象切分完索引后要给每个客户端包一个 DataLoader。这里有个容易翻车的地方不同客户端的样本数差异很大如果 batch size 固定样本少的客户端可能一个 epoch 只有一两个 batch梯度噪声极大。from torch.utils.data import DataLoader, Subset def build_client_loaders(train_set, client_indices, batch_size32): loaders [] for idx in client_indices: subset Subset(train_set, idx) # 样本数少于 batch_size 时drop_lastFalse 保证至少有一个 batch loader DataLoader(subset, batch_sizebatch_size, shuffleTrue, drop_lastFalse) loaders.append(loader) return loaders client_loaders build_client_loaders(train_set, client_indices)Subset按索引取子集shuffleTrue保证每个 epoch 本地数据的顺序不同这对非独立同分布数据尤其重要能稍微缓解本地过拟合。drop_lastFalse是为了不让小客户端在训练中被完全跳过。参数上batch_size我一般设 32 或 64太小梯度不稳太大本地更新次数不够联邦的平均效果会打折。3. 三个实验的代码骨架从 FedAvg 到异构与聚合对比3.1 实验一最小 FedAvg 跑通第一个实验的目标只有一个让全局模型在测试集上的准确率随通信轮次上升。模型用两层卷积加全连接就够MNIST 上几轮就能到 90% 以上。import torch import torch.nn as nn import torch.nn.functional as F import copy class SmallCNN(nn.Module): def __init__(self): super().__init__() self.conv1 nn.Conv2d(1, 32, 3, 1) self.conv2 nn.Conv2d(32, 64, 3, 1) self.fc1 nn.Linear(9216, 128) self.fc2 nn.Linear(128, 10) def forward(self, x): x F.relu(self.conv1(x)) x F.relu(self.conv2(x)) x F.max_pool2d(x, 2) x torch.flatten(x, 1) x F.relu(self.fc1(x)) return self.fc2(x) def local_train(model, loader, epochs1, lr0.01): model.train() optimizer torch.optim.SGD(model.parameters(), lrlr) for _ in range(epochs): for data, target in loader: optimizer.zero_grad() output model(data) loss F.cross_entropy(output, target) loss.backward() optimizer.step() return model.state_dict() def fedavg(global_state, client_states, weights): # weights 为各客户端样本数占比 new_state copy.deepcopy(global_state) for key in new_state: new_state[key] sum(w * s[key] for w, s in zip(weights, client_states)) return new_statelocal_train里每个客户端复制一份全局模型本地跑epochs轮 SGD返回state_dict。fedavg按样本数加权平均这是 FedAvg 的标准做法权重必须用样本数而不是客户端数否则小客户端会被过度放大。通信轮次我一般设 20 到 50每轮随机选一部分客户端参与模拟真实场景下的部分参与。3.2 实验二数据异构下的掉点与修正第二个实验把alpha调到 0.1你会看到全局准确率明显下降甚至前几轮震荡。原因是非独立同分布下各客户端本地模型漂移方向差异太大简单平均会把它们互相抵消。修正手段常见有三种增加本地 epoch、调小学习率、改用 FedProx 加近端项。FedProx 的做法是在本地损失里加一项mu/2 * ||w - w_global||^2限制本地模型不要跑太远。def local_train_fedprox(model, global_model, loader, epochs1, lr0.01, mu0.01): model.train() optimizer torch.optim.SGD(model.parameters(), lrlr) global_params [p.detach().clone() for p in global_model.parameters()] for _ in range(epochs): for data, target in loader: optimizer.zero_grad() output model(data) loss F.cross_entropy(output, target) prox 0.0 for p, gp in zip(model.parameters(), global_params): prox ((p - gp) ** 2).sum() loss loss mu / 2 * prox loss.backward() optimizer.step() return model.state_dict()mu是近端项系数取 0.01 到 0.1 之间。太小没约束效果太大本地模型几乎不动全局也学不到东西。我一般先用 0.01 试看曲线是否比纯 FedAvg 平稳。这个实验的价值在于让你亲眼看到异构数据对联邦的杀伤力以及近端项是怎么把漂移拉回来的。3.3 实验三聚合策略对比与图片演示第三个实验做对比FedAvg、FedProx以及按样本数加权和等权平均的差异。把每轮的测试准确率记下来用 matplotlib 画曲线。import matplotlib.pyplot as plt def plot_curves(results, save_pathfl_compare.png): plt.figure(figsize(8, 5)) for name, accs in results.items(): plt.plot(range(1, len(accs) 1), accs, markero, labelname) plt.xlabel(Communication Round) plt.ylabel(Test Accuracy) plt.legend() plt.grid(True) plt.savefig(save_path, dpi150) plt.close()results是一个字典键是策略名值是每轮准确率列表。dpi150保证图片清晰方便放进报告。三条曲线放一起你能直观看到 FedProx 在异构下收敛更稳等权平均在小客户端多的时候明显掉点。这就是标题里「图片演示」的实际作用不是装饰是实验结论的可视化证据。注意画图前确认所有策略跑的是同一组客户端划分和同一随机种子否则曲线差异可能来自数据划分而不是算法本身。4. 联邦学习实验避坑5 个我真实踩过的坑4.1 现象全局准确率比本地还低。原因聚合时权重用错。解决权重必须是样本数占比第一次跑的时候我图省事直接对所有客户端state_dict求平均结果全局模型比任何一个本地模型都差。原因是各客户端样本数差异大等权平均让小客户端的话语权被放大。改成按样本数加权后曲线立刻正常。记住weights [len(idx) / total for idx in client_indices]。4.2 现象训练几轮后 loss 变 NaN。原因本地学习率过大叠加异构数据。解决调小 lr 并加梯度裁剪异构数据下本地梯度方向差异大学习率设 0.1 时很容易发散。我一般把本地 lr 降到 0.01并在loss.backward()后加torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0)。梯度裁剪在联邦里几乎是必备的后悔药。4.3 现象客户端数量多时内存爆掉。原因每轮把所有客户端模型都留在显存里。解决串行训练只保留 state_dict一开始我把 10 个客户端模型同时放到 GPU 上显存直接不够。正确做法是串行训练完一个客户端就把state_dict转到 CPU 存起来模型本身释放。聚合时只在 CPU 上做加权平均最后再加载回全局模型。4.4 现象准确率曲线抖动剧烈。原因每轮参与客户端随机性太大。解决固定随机种子或提高参与比例如果每轮只随机选 2 个客户端曲线会非常抖。要么把参与比例提到 0.5 以上要么固定每轮的客户端集合做对照实验。做算法对比时参与集合必须固定否则你分不清差异来自算法还是采样。4.5 现象FedProx 没效果。原因近端项加在了错误的参数上。解决确认 global_params 是每轮开始时的全局参数我一开始把global_params在本地训练循环里更新了导致近端项约束的是自己等于没加。正确做法是在每轮通信开始时深拷贝一份全局参数本地训练全程用它做参考训练结束后再聚合。5. 进阶技巧用灾难性遗忘视角验证联邦模型到底学到了什么跑通三个实验只是起点。真正判断这套联邦实验值不值得深入要看你能否回答一个问题全局模型聚合后对某个客户端本地数据的表现是变好了还是被其他客户端「带偏」了。这就是联邦学习里的灾难性遗忘问题也是热搜里常被提到的点。我的做法是加一个逐客户端评估环节。每轮聚合后用全局模型在每个客户端自己的测试子集上跑一遍准确率记录成矩阵。如果某个客户端的准确率在聚合后持续下降说明它的本地知识正在被覆盖。def per_client_eval(global_model, client_test_loaders): global_model.eval() accs [] with torch.no_grad(): for loader in client_test_loaders: correct, total 0, 0 for data, target in loader: output global_model(data) pred output.argmax(dim1) correct (pred target).sum().item() total target.size(0) accs.append(correct / total if total 0 else 0.0) return accsclient_test_loaders是按同样 Dirichlet 划分切出来的测试集。每轮记录accs最后画成热力图横轴是通信轮次纵轴是客户端编号。你会看到异构程度高时部分客户端的准确率曲线先升后降这就是遗忘的直接证据。参数上评估频率不用每轮都做每 5 轮一次就够省时间。判断标准很简单如果全局平均准确率在涨但客户端间方差在扩大说明模型在牺牲少数客户端换取整体指标这时候要么调alpha重新划分要么引入个性化联邦的思路给每个客户端保留一份本地微调模型。我自己的习惯是任何联邦实验跑完先看全局曲线再看逐客户端矩阵两个都正常才认为这个配置可用。只看全局平均准确率很容易被一个「平均好看但个体崩坏」的模型骗过去。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站