
简介一套基于联邦学习与NSL-KDD数据集的网络入侵检测Python项目源码及运行指南属于经导师指导并认可的高分项目评审98分适合计算机相关专业学生用于课程设计、期末大作业以及想要进行项目实战的机器学习/网络安全学习者。资源共63个文件压缩包大小约26.19MB包含12个Python源代码、26个Pyc编译文件、10个Txt说明文档、模型权重、CSV数据、结果对比PNG以及带GUI界面的数据集等覆盖数据预处理、模型构建、联邦训练与测试等模块。已有88人学习下载。项目将联邦学习与NSL-KDD数据集结合既演示了如何在隐私保护前提下进行分布式训练也提供了带图形界面的数据操作方式借助附带的运行说明、模型文件和对比图学习者可以快速复现入侵检测实验深入理解从数据准备到模型部署的完整流程为网络安全与联邦学习方向的实践提供扎实参考。1. 联邦学习与NSL-KDD做网络入侵检测先别急着跑代码想清楚这三件事用联邦学习与NSL-KDD数据集做网络入侵检测本质上是把两个成熟技术接到一起联邦学习解决流量日志“数据不出域”的合规诉求NSL-KDD给出一道能反复验证的基准题Python则是把它们黏在一起的胶水。这几年相关高分项目几乎都从集中式往联邦上靠因为评分重点已经从“检测准不准”变成了“数据隔离条件下还能不能准”。拿到压缩包先别急着解压跑训练先想清楚三件事数据是天然按节点分片还是要人工模拟Non-IID模型做二分类还是五分类指南里说的联邦是本机模拟还是真多机通信。想清楚后再动代码每个参数都能说出改它的理由。这篇笔记写给不满足于“能运行”的Python从业者和安全方向同学重点放在可复现步骤和踩坑记录上。2. 为什么要联邦化FedAvg机制、数据不出域的价值与NSL-KDD的基准定位2.1 联邦学习在入侵检测里的角色客户端训练什么、服务器聚合什么入侵检测的常规做法是把流量特征汇集到一个训练中心在全部数据上训一个全局模型。问题在于安全日志是最敏感的数据之一跨部门、跨机房、跨公司的流量特征往往不能直接汇总别说原始报文就连统计特征也要走审批。联邦学习在这里的角色很直接各个节点用自己的日志训练本地模型训练完只上传模型权重原始特征永远留在本地服务器把权重按某种策略加权平均后下发反复迭代。这里最常用的聚合策略就是FedAvg联邦平均。每轮通信服务器把当前全局权重广播给参与节点每个节点在本地数据上做几个epoch的梯度下降再把更新后的权重和本地样本量一起传回服务器按样本量占比加权求平均得到下一轮的全局权重。整套机制里没有原始数据流动传输的只是跟模型大小等量的一堆张量这也是联邦学习能过合规审查的核心原因。在入侵检测场景里有个容易被忽略的点这种训练方式天然适配“分公司/节点/机房”的组织结构。每个节点的流量分布不同有的节点DDoS流量多有的节点主要是扫描探测联邦框架并不要求各节点数据同分布它只要求各节点把梯度朝各自数据的方向推一步整体模型再在“每个方向的平均值”上迈一步。实际项目里联邦学习还能叠一层安全聚合Secure Aggregation让服务器无法从收到的梯度反推某个节点的样本信息。NSL-KDD规模小没必要上这个复杂度但如果以后把这套代码接到真实流量上安全聚合就是必须考虑的下一步。做课程项目时在报告里提一句“联邦只解决了数据不出域梯度本身仍可能泄露分布信息”导师会认为你理解到了这一层的边界比空写“隐私保护”要有说服力。2.2 从KDD99到NSL-KDD去冗余之后测评分数才真实标题里的NSL-KDD是KDDCUP99数据集的修正版官方提供Train、Test和Test-21三份文件每行是一条网络连接记录共41个特征外加一个标签。它与旧版KDD99最大的差别在于去掉了大量重复记录旧版训练集里同一类记录重复几十万条模型记性好一点的都能靠背答案拿高分而NSL-KDD的训练集规模和重复度都被控制测试集还额外按难度分了级。因此拿NSL-KDD报告的数字更接近真实泛化能力不再是一种“背题考高分”。对比项KDD99NSL-KDD训练集冗余记录大量重复易被模型记忆已去重训练集规模约12万条测试集难度分级无分低/中/高三档实验可复现性切分混乱官方切分明确训练/测试文件固定联邦实验适配度不推荐数据分布失真常用于Non-IID与联邦消融实验这个数据集之所以到今天还在被各类项目采用是因为它足够小、有现成的二分/多分标签、还能和旧版KDD99贯通做论文做毕设都有参照系。指望它替代真实流量不现实但把联邦方案先在NSL-KDD上验证一轮再迁移到自己的流量特征上是一条性价比很高的技术路线。注意官方三份文件经常被重新打包发布你拿到的csv可能是带表头、不带表头、多一列难度系数三种版本之一属于正常现象本文第3章会给出对应处理。2.3 二分类还是五分类检测率、误报率与每类召回NSL-KDD的标签可以归成正常加上四类攻击DoS拒绝服务、Probe扫描探测、R2L远程到本地、U2R提权攻击。二分类只管正常和异常最简单训练快适合快速验证联邦链路通不通五分类要求模型分辨攻击类型难度明显上升尤其是R2L和U2R的样本量极少经常只占总量的百分之几模型天然偏向多数类。实战里我不会只看总体准确率。入侵检测更真实的指标是检测率也就是召回率攻击样本里被揪出来的比例和误报率正常样本里被冤枉的比例。在联邦场景里这两个指标还要拆到每个客户端上去看因为全局准确率高完全有可能掩盖某个节点上检测率为零的翻车情况。我一般会固定评估五分类至少要在报告里给每类召回率不然项目答辩时很难回答“你的模型到底能不能防住U2R”这种问题。多分类在联邦训练中有一个额外负担各节点的类别分布不一致会导致所谓的客户端漂移。一个节点全是R2L样本它的本地模型更新方向就偏向R2L那条梯度另一个节点只有normal和probe方向就完全不同。这两种方向平均在一起全局模型可能两头都学不好。所以第5章里的Non-IID模拟和第3章的数据分片方式是决定这个项目成败的关键步骤不能跳过。最后谈一下Python在这个方向几乎是唯一选项的原因PyTorch处理神经网络训练、pandas做特征表、sklearn出混淆矩阵和分类报告三个库一条链路能打通整个实验。对Python刚入门的人这个项目反而比纯工程项目友好因为跑通最小例子的代码量不到两百行要做的杂活不过是先装好Python环境再把numpy、pandas、scikit-learn、torch四个库用pip装到最新稳定版而已。3. 数据预处理落地用Python把NSL-KDD的41维特征变成能喂PyTorch的张量3.1 读入CSV与字段梳理先分清数值特征和符号特征读文件这一步用pandas一行就能搞定但列名要跟官方顺序对齐否则后续编码全乱。NSL-KDD的csv没有表头需要手动指定列名。读入之前先确认Python环境就绪缺库就pip install numpy pandas scikit-learn torch一条命令装齐Windows和Linux下没有区别。import pandas as pd import numpy as np FEATURES [ duration, protocol_type, service, flag, src_bytes, dst_bytes, land, wrong_fragment, urgent, hot, num_failed_logins, logged_in, num_compromised, root_shell, su_attempted, num_root, num_file_creations, num_shells, num_access_files, num_outbound_cmds, is_host_login, is_guest_login, count, srv_count, serror_rate, srv_serror_rate, rerror_rate, srv_rerror_rate, same_srv_rate, diff_srv_rate, srv_diff_host_rate, dst_host_count, dst_host_srv_count, dst_host_same_srv_rate, dst_host_diff_srv_rate, dst_host_same_src_port_rate, dst_host_srv_diff_host_rate, dst_host_serror_rate, dst_host_srv_serror_rate, dst_host_rerror_rate, dst_host_srv_rerror_rate ] def load_nsl_kdd(path): df pd.read_csv(path, headerNone, namesFEATURES [label]) return df逻辑说明FEATURES按官方文档顺序列出41个特征名read_csv时用headerNone跳过默认表头names参数把列名挂上去第42列在代码里命名为label。这里有一个本地容易踩的坑有的公开渠道放的NSL-KDD文件多了一列难度系数直接读会报“列数不匹配”这时候给names多加一个level或者读进来后用drop列处理掉。参数说明path是训练集或测试集文件路径函数返回的DataFrame要保持行顺序不变因为后面做Non-IID分片时会用它来回放标签索引。csv分隔符是英文逗号文本列里出现引号也没关系pandas会自动处理。训练集用load_nsl_kdd(KDDTrain.csv)测试集用load_nsl_kdd(KDDTest-21.csv)文件名以你实际解压出来的为准。3.2 类别特征one-hot与数值归一化先编码还是先缩放41维里有三个符号特征protocol_type协议类型tcp/udp/icmp三种、service服务类型约70种、flag连接状态标志约11种。剩下的38维都是数值或比率字段。符号特征不能直接喂给线性层常见做法是转成one-hotservice种类太多全量one-hot会把维度顶到110以上我一般先按出现频率筛出前20个其余并成other这一类把维度压在可控范围。from sklearn.preprocessing import StandardScaler TOP_SERVICE 20 def encode_and_normalize(df, scalerNone, top_serviceNone, fitFalse): label_int df[label].map(build_label_map()) label_int label_int.values.astype(np.int64) # 三个符号特征统一转 one-hot前缀区分来源 proto pd.get_dummies(df[protocol_type], prefixproto) flag pd.get_dummies(df[flag], prefixflag) if fit: # 只在训练集上统计高频 service避免测试集信息泄漏 top_service df[service].value_counts().index[:TOP_SERVICE].tolist() df df.copy() df[service] df[service].apply( lambda s: s if s in top_service else other) service pd.get_dummies(df[service], prefixsvc) # 剩余38列都是数值型注意先转换类型再缩放 numeric_cols [c for c in FEATURES if c not in (protocol_type, service, flag)] numeric df[numeric_cols].astype(np.float32).values if fit: scaler StandardScaler().fit(numeric) elif scaler is None: raise ValueError(fitFalse时必须传入训练集fit好的scaler) numeric_scaled scaler.transform(numeric) x np.concatenate([ numeric_scaled, proto.values.astype(np.float32), flag.values.astype(np.float32), service.values.astype(np.float32) ], axis1) return x, label_int, scaler, top_service逻辑说明encode_and_normalize做的事是符号特征转one-hot、数值特征用StandardScaler做z-score归一化、最后按列方向拼接成一个大矩阵。fit参数决定这次调用是“拟合scaler并统计service”还是“复用训练集返回的scaler和top_service”。用astype(np.float32)做数据类型转换是为了跟PyTorch默认的float32对齐顺便把内存减半。参数说明scaler和top_service的传递是这套代码的命门。训练集上传入fitTrue拿到scaler和top_service测试集上必须传fitFalse并原样代入否则训练和测试的特征分布不一致测试集准确率虚高这在第5章避坑中会细讲。TOP_SERVICE不是固定值看重区分度可以提到30看重维度稀疏可以降到10改了之后注意模型的in_dim要跟着变。数值特征这一列最容易出问题的是num_outbound_cmds在KDD99里全是0在NSL-KDD里接近全0StandardScaler对它做z-score得到一堆接近0的小数不会影响训练但别好奇地去删列删了维度就乱了。3.3 标签映射把攻击名归并成五分类NSL-KDD的原始标签是具体的攻击名需要归并才能做五分类。归并逻辑很简单normal是一类其余按DoS、Probe、R2L、U2R四大族归类。DOS {back, land, neptune, pod, smurf, teardrop} PROBE {ipsweep, nmap, portsweep, satan} R2L {ftp_write, guess_passwd, imap, multihop, phf, spy, warezclient, warezmaster} U2R {buffer_overflow, loadmodule, perl, rootkit} def build_label_map(): label_map {normal: 0} for idx, group in enumerate([DOS, PROBE, R2L, U2R], start1): for attack in group: label_map[attack] idx return label_map逻辑说明build_label_map返回的字典把normal映射为0四个攻击族映射为1到4。后面encode_and_normalize里用的就是这个字典如果只想做二分类把字典改成{normal: 0}后其他所有攻击归为1即可但那样会丢掉攻击类型信息联邦轮次里各个类别的差异化学习就看不清了。参数说明这个映射表要覆盖官方所有攻击名。如果你拿到的NSL-KDD文件里有字典之外的标签encode_and_normalize的map会返回NaN训练时交叉熵直接报错。保险做法是在map调用后加一行assert label_int.notna().all()。测试集里出现的攻击名不一定在训练集里出现过这属于NSL-KDD的刻意设计联邦测试时模型对“没见过”的攻击名没有先验跨类别泛化会直接体现在召回率上别把这个当成bug。3.4 Non-IID分片用Dirichlet分布模拟各节点数据不均真实联邦场景里各节点的数据分布从来不是均匀的有的节点全是web服务有的节点只跑数据库协议。要在本地验证联邦算法对“分布不均”的容忍度就用Dirichlet分布来控制每个类别在各客户端上的占比。def non_iid_split(labels, n_clients5, alpha0.5, seed42): 按Dirichlet(alpha)把样本分给n_clients个客户端。 alpha越小各客户端类别分布越倾斜alpha100时接近均匀。 rng np.random.default_rng(seed) n len(labels) client_ids np.zeros(n, dtypeint) for cat in np.unique(labels): idx np.where(labels cat)[0] if len(idx) 0: continue p rng.dirichlet([alpha] * n_clients) counts rng.multinomial(len(idx), p) counts[-1] len(idx) - counts[:-1].sum() order rng.permutation(idx) start 0 for cid, cnt in enumerate(counts): client_ids[order[start:start cnt]] cid start cnt return client_ids逻辑说明逐类别处理每个类别下的样本按Dirichlet比例随机分给各客户端。rng.multinomial保证每个类别的样本全部被分到某个客户端不会因为浮点取整丢样本。返回的client_ids和原始数据行一一对应后面训练循环里用client_ids cid做布尔索引切片就行用不着复杂的数据集类。参数说明alpha是出镜率最高的联邦实验参数。alpha0.5代表比较极端的Non-IID每个客户端可能只拥有两三个类别的样本alpha100则几乎均匀。课程项目建议两端都跑一遍在非均匀分布下模型准确率掉几个点属于正常现象报告里把这个趋势写清楚反而是加分项。这里有个数组索引的小技巧如果想检查每个客户端分到了什么用np.bincount(client_ids)配合labels[client_ids cid]看一眼类别直方图比打印几十行日志直观得多。4. Python实现FedAvg模型定义、客户端训练与服务端加权聚合4.1 网络结构选择MLP足够把复杂度留给联邦机制网络结构不需要多深。入侵检测的特征虽然维度不低但大部分是归一化后的统计量三到四层全连接加Dropout就能拟合得很好。不建议在这个数据集上上CNN或Transformer不是用不了而是模型变大之后联邦通信开销线性上涨训练时间拉长项目收益却不明显。import torch import torch.nn as nn class NIDSMLP(nn.Module): def __init__(self, in_dim, num_classes5): super().__init__() self.net nn.Sequential( nn.Linear(in_dim, 64), nn.ReLU(), nn.Dropout(0.2), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, num_classes) ) def forward(self, x): return self.net(x)逻辑说明用64宽度的两层MLP刻意不用BatchNorm。这里是有意为之——联邦聚合的时候BN层里的running_mean和running_var不能像普通权重那样直接加权平均一旦引入就得在聚合时做特殊处理很多新手在这里栽跟头。用Dropout代替BN聚合代码就只剩weight和bias的加权求和复杂度低一截。参数说明in_dim在第3章预处理后得到一般九十多维num_classes二分类传2、五分类传5。Dropout取0.2是为了在几十轮联邦训练中既提供一点正则又不至于让每轮本地更新太弱。如果你发现训练集上loss收敛慢先把Dropout调到0.1试试比动网络层数见效快。4.2 客户端本地训练一个类封装一轮本地更新把每个参与方封装成一个Client对象内部只做一件事接收全局权重在本地数据上训几个epoch返回新权重。这个设计让后续从单机模拟切到真联邦时改动量最小。class Client: def __init__(self, cid, x, y, devicecpu, lr1e-3): self.cid cid self.x torch.tensor(x, dtypetorch.float32, devicedevice) self.y torch.tensor(y, dtypetorch.long, devicedevice) self.device device self.lr lr def local_train(self, global_state, epochs2, batch_size64): model NIDSMLP(self.x.shape[1]).to(self.device) model.load_state_dict(global_state) optimizer torch.optim.Adam(model.parameters(), lrself.lr) loss_fn nn.CrossEntropyLoss() dataset torch.utils.data.TensorDataset(self.x, self.y) loader torch.utils.data.DataLoader( dataset, batch_sizebatch_size, shuffleTrue) model.train() for _ in range(epochs): for xb, yb in loader: optimizer.zero_grad() out model(xb) loss loss_fn(out, yb) loss.backward() optimizer.step() return model.state_dict(), len(dataset)逻辑说明Client对象保存一个客户端的数据和标签local_train接收服务器下发的全局权重在本地数据上做几个epoch的梯度下降最后返回更新后的state_dict和样本量。关键点是每次训练都从global_state开始而不是从上一轮本地状态继续这保证联邦学习收敛的语义是“围绕全局模型做局部修正”而不是各节点在自己的模型上一路跑到黑。定义函数和类的时候把变量名写清楚后面跑消融实验时就不用每次重读代码。参数说明epochs对应本地训练轮数是联邦里最敏感的超参一般取1到3。取20以上的话每个客户端都过拟合到自己的局部数据上聚合出来的全局模型反而变差。batch_size用64lr用1e-3这两个参数和集中训练差别不大但如果Non-IID程度高lr降到5e-4更稳。device参数默认cpu数据量大或要跑多轮实验时改成cuda即可注意把x和y都放到同一个设备否则torch会报device mismatch。4.3 服务端聚合FedAvg的加权平均实现服务端聚合是整个联邦学习的数学核心FedAvg的原理一句话就能说清按各客户端本地样本量占比对模型参数做加权平均。def fed_avg(global_model, client_states, client_sizes): new_state {} for k, v in global_model.state_dict().items(): new_state[k] torch.zeros_like(v) total sum(client_sizes) for state, size in zip(client_states, client_sizes): weight size / total for k, v in state.items(): new_state[k] v * weight global_model.load_state_dict(new_state) return new_state逻辑说明先按key初始化全零张量然后每个客户端的权重按样本数占比累加进去。torch.zeros_like保证跟原模型张量形状一致load_state_dict不会报错。这个实现只支持weight和bias这类普通张量所以4.1节特意避开了BN层否则这里还要写BN统计量的合并逻辑。参数说明client_sizes来自每个Client返回的len(dataset)。要做的是加权平均而不是简单平均如果某个客户端样本量是另一个的100倍前者的梯度方向会在聚合结果里占绝对主导。课程项目里的典型错误是只想“公平”用简单平均结果小客户端的数据直接被淹没聚合出来的模型对大节点过拟合。如果你希望加入权重衰减或模型剪枝在这个函数里对new_state统一操作即可位置就在load_state_dict之前。4.4 主训练循环与端到端跑通把前面的块拼起来主循环只需要做三件事按client_ids切片、调用local_train、调用fed_avg。为了能看到收敛过程每10轮打印一次测试集准确率。def evaluate(model, x, y): model.eval() x_t torch.tensor(x, dtypetorch.float32) with torch.no_grad(): pred model(x_t).argmax(dim1).numpy() return np.mean(pred y) def run_federated(x_train, y_train, client_ids, x_test, y_test, n_rounds60, epochs_per_client2, n_clients5): in_dim x_train.shape[1] global_model NIDSMLP(in_dim) global_state global_model.state_dict() for rnd in range(1, n_rounds 1): states, sizes [], [] for cid in range(n_clients): mask client_ids cid client Client(cid, x_train[mask], y_train[mask]) state, size client.local_train(global_state, epochsepochs_per_client) states.append(state) sizes.append(size) global_state fed_avg(global_model, states, sizes) if rnd % 10 0: acc evaluate(global_model, x_test, y_test) print(fround {rnd}: test acc {acc:.4f}) return global_model逻辑说明run_federated在循环里依次执行切片、训练、聚合三步。这里是在单机进程内顺序执行所有客户端模拟的是服务端逐个接收客户端上传的过程真实分布式环境里这些调用会被网络通信替代但聚合的数学完全一致。evaluate函数在测试集上做前向推理argmax取预测类别后和真实标签比均值。参数说明n_rounds是联邦通信轮数这个项目里50到100轮基本收敛再多收益很小。epochs_per_client2是计算量与精度的折中每轮每个客户端在本地只跑两遍数据。n_clients要和非IID分片时的数字保持一致不一致时布尔索引就会漏掉一部分样本。跑完看一下最后10轮的准确率波动波动超过±0.5%说明还没收敛把n_rounds往上加到100即可。5. 训练避坑与排查灾难性遗忘、模型漂移与测试集选择的四个翻车现场5.1 坑一训练/测试特征不一致全局准确率虚高现象训练完在KDDTest上一测准确率接近99%换成KDDTest-21直接跌到91%同一个模型两份测试集差出一大截。原因一个是测试集选择的差异另一个是更隐蔽的预处理泄漏——有的教程让scaler在训练集和测试集整份数据上一起fit测试集的信息已经被偷走了。NSL-KDD的KDDTest本身包含大量与训练集重复的连接模型见过类似样本分数天然偏高Test-21去掉重复后才是真实泛化水平。解决报告数字一律用KDDTest-21并且scaler严格只在训练集上fit。第3.2节里fit参数就是为这个设计的测试集上必须传fitFalse和训练集返回的scaler。做对比实验时固定这套流程防止不同实验间的预处理不一致影响结论。有一个小技巧如果某轮实验得到的结果异常高先检查是不是把测试集混进fit了这是这个项目里最容易犯也是最不容易发现的错误。5.2 坑二本地epochs设太大聚合后模型漂移现象本地训练20个epoch每个客户端本地准确率都98%以上聚合后的全局模型在测试集上只有70%还不如只训2个epoch的结果。原因每个客户端在本地数据上反复迭代模型被拉向本节点的局部最优方向多个方向的平均变成了一个四不像。联邦学习论文里管这叫client drift本地训练步数越多漂移越严重。解决把epochs降到1到3。这是一种“后悔药”式的参数修正如果发现已经跑了一轮epochs20的实验不用重写代码把run_federated(epochs_per_client2)重跑一遍即可。如果降到1仍然不稳可以在客户端loss里加一个近端项惩罚本地权重偏离全局权重这个思路对应FedProx在loss上再加一项mu/2 * ||w - w_global||^2mu取0.01到0.1之间。我一般先试epochs2不稳再加近端项不急着动lr。5.3 坑三Non-IID分片后训练发散loss持续增大现象alpha0.5的Non-IID分片下全局和本地loss都不下降有时直接变成NaNalpha100时一切正常。原因一个客户端只有一种攻击类型时它的梯度方向跟全局方向几乎垂直几轮平均下来形成震荡如果再用Adam的默认lr个别客户端梯度炸了就NaN了。解决两个改动一起做。一是把学习率从1e-3降到3e-4二是在每个客户端的交叉熵上做类别加权让样本量少的R2L、U2R类别即使在一个客户端上出现次数很少也不至于被遗忘。也可以在分片时先用alpha1跑通链路再逐步调低alpha观察模型退化曲线这个曲线本身就是项目报告里很好的素材。要记住在大规模联邦里Non-IID下的发散可能来自单个客户端的病态梯度打印每个客户端各自的loss而不是只看聚合后的全局loss能更快定位到是谁拖垮了全局。5.4 坑四灾难性遗忘——轮次推进后旧攻击类型“突然不会了”现象第20轮时五分类的每类召回都正常第40轮开始U2R的召回率从80%掉到30%后续轮次再也没有恢复。原因这是联邦学习里灾难性遗忘的典型表现。全局模型在新轮次里被大多数客户端的大类样本主导少数类别尤其样本量极小的U2R和R2L的梯度信号被淹没模型把参数空间里原先识别U2R的区域逐渐覆盖掉。集中式训练同样有遗忘问题但联邦环境里各客户端数据不平衡让遗忘来得更快更隐蔽。解决三个手段按成本从低到高排列。第一每轮聚合后对每个类别单独算召回一旦某类比上一轮跌超过5个点就回滚到上一轮权重再小步重训给项目加一个简单的“刹车机制”。第二在服务端保存一份验证集每轮用验证集挑出最优round的权重相当于给模型存了后悔药训练结束后加载最优权重而不是最后一轮权重。第三如果类别不平衡是常态考虑在客户端本地做少数类过采样或者干脆改用二分类加一个专门检测R2L/U2R的小模型别让一个模型背所有锅。我自己做实验时最常用的是第二条简单可靠而且在答辩时可以直接展示“最优权重出现在第几轮、为什么”。关于评估指标还有一条硬规矩五分类下只报accuracy是不够的至少要带上每类recall和误报率否则灾难性遗忘根本不会被你发现。用sklearn的classification_report一行就能输出全部类别精确率、召回率、F1画混淆矩阵时如果类别标签挤成一团把figure size调大到(8, 6)以上就好。联邦场景下我更建议额外打印每个客户端在测试集上的单独表现因为全局指标好看不能掩盖某个节点上的检测失效这一点在答辩时也是容易被追问的角落。6. 从模拟到真联邦消融实验、通信开销与三行改造清单模拟毕竟是模拟真联邦在网络不稳定的真实环境里还要面对掉线、传输延迟和梯度过期问题。做验证时有三个技巧值得用。第一个是消融实验。把集中式训练、联邦5客户端、联邦10客户端三组跑在同一份预处理代码下记录测试准确率、每类召回率和训练耗时。集中式可以看作联邦的上限参照两个联邦实验的差距体现数据分片带来的信息损失。我习惯把结果画成一张对比表集中式94.2%、联邦5客户端92.8%、联邦10客户端91.5%这种形式比文字描述“联邦效果还行”有说服力得多。第二个是通信开销的量化。单个模型参数量乘以2上传加下载再乘以通信轮数就能估算传输字节量。以文中这个MLP为例in_dim约90、两层64宽参数量大约一万出头float32一个参数4字节一轮通信约80KB60轮不到5MB。这个数字放进报告里就能正面回应“联邦学习到底多耗带宽”的质疑。第三个是迁移真联邦的改造清单把Client里的数据加载换成远程文件路径、把local_train返回值序列化后走消息队列传输、服务端fed_avg换成异步聚合以容纳掉线节点。常见做法是直接用Flower这类联邦框架改配置或者自己拉一套gRPC两者都能保留第4章代码本体的八到九成。我做这个方向时踩过最大的坑是默认“本机模拟的结果等于分布式结果”真搬到多机后才发现网络延时会放大每个round的耗时原来用60轮训练的代价在真环境下翻了不止三倍。后来我在模拟阶段就把round数压到40把epochs压到1用更多客户端替代更多轮次反而拿到更稳定的结果。把“通信轮次”当成跟学习率一样的超参去调是联邦项目跟普通深度学习项目最大的习惯差异。这个思路贯穿了我后来所有的联邦实验希望这些记录对你有所帮助。本文还有配套的精品资源点击获取