ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

联邦学习+NSL-KDD:从原理到代码复现网络入侵检测

联邦学习+NSL-KDD:从原理到代码复现网络入侵检测 简介基于联邦学习与NSL-KDD数据集的网络入侵检测完整项目面向机器学习、网络安全方向的开发者与学生适合课程设计、毕业设计或实践参考。项目完整覆盖客户端与服务端联邦训练流程包括数据预处理、模型定义、参数聚合、结果对比与GUI可视化可在本地直接编译运行难度适中内容经过助教审定。压缩包共63个文件包含12个Python脚本和26个pyc缓存另有多份txt说明、log日志、weight权重、csv数据及png对比图等整体26.19MB。目录按server、client、utils等模块划分便于检索学习。目前已有351人学习下载。资源提供一套可复现的联邦学习入侵检测方案从NSL-KDD数据初始化、本地模型训练、参数上传到服务端聚合与效果对比均有完整实现附README和文档说明。对理解联邦学习在网络异常检测中的应用、掌握分布式训练框架搭建有实际帮助可在此基础上扩展实验或继续优化。1. 联邦学习 NSL-KDD这个入侵检测源码项目究竟要解决什么联邦学习和 NSL-KDD 数据集加上网络入侵检测这三个词组合在一起常见于毕设选题和求职项目里。你拿到一个 python 实现的压缩包里面大概率有源码、文档说明和训练好的结果图但真正决定它是不是“高分项目”的不是代码排版而是能不能回答一个问题——为什么必须在联邦学习框架下做入侵检测。道理很简单集中式训练精度通常更高可真实网络场景里流量数据分散在多个运营商、多个部门甚至多家公司手里合规上根本不允许汇总到一台机器。联邦学习就是为这种“数据不动模型动”的场景设计的。本文会带你从原理到代码把这个方向完整复现一遍。2. 为什么是联邦学习和 NSL-KDD拆清原理与数据边界2.1 数据孤岛让集中式训练失效联邦学习的立场网络入侵检测的常规做法是把流量特征汇总进一个训练池跑 XGBoost 或深度学习模型效果确实不错。可一旦换成真实的多方环境问题就来了A 公司的出口流量、B 政务网的审计日志、C 云的攻击告警分别属于不同责任主体谁也不会把原始数据打包发给某个中心节点。这时候数据集本身就变成孤岛集中式训练在合规层面就不成立。联邦学习解决的是“怎么在数据不离开本地的情况下联合建模”。标准的横向联邦拓扑里有一个中心服务端和若干客户端。每个客户端保留自己的训练集本地算梯度或更新模型参数只把“模型增量”发回服务端服务端聚合出一版新模型再下发。整个过程中训练数据始终留在客户端侧这就是它和分布式训练最大的区别。要提醒的是模型参数照样可能泄露信息所以联邦学习不等于绝对安全。实际项目中后续常叠加差分隐私、安全多方计算。但在 NSL-KDD 这个公开数据集上做验证首要目标是把 FedAvg 训练链路跑通让各客户端扬长避短。2.2 NSL-KDD 的数据结构41 维特征、三类符号属性与标签体系NSL-KDD 是 KDD99 的改良版由加拿大新不伦瑞克大学整理发布。它保留了原始网络的 41 维特征但剔除了大量冗余记录解决训练集和测试集里重复样本过多导致的指标失真问题。文件里常用到三份数据KDDTrain训练集KDDTest完整测试集KDDTest-21测试集中去掉“难度最低”记录后的子集专门用来检验模型对困难样本的识别能力41 维特征大致分成三组第一组是 TCP 连接基本属性比如 duration、protocol_type、service、flag、src_bytes第二组是 2 秒窗口内的流量统计例如 count、srv_count、serror_rate第三组是主机维度的统计列名以 dst_host_ 开头。其中 protocol_type、service、flag 是符号型直接喂给神经网络前必须编码。剩下基本都是连续性数值少数列有极端长尾分布归一化时要注意。标签体系有两种用法。第一种是二分类Normal 记作 0其它攻击类型统一记作 1简单直接第二种是五分类Normal、DoS、Probe、R2L、U2R。NSL-KDD 里 DoS 和 Probe 样本量充足R2L 和 U2R 非常稀少直接做五分类会遭遇严重类别不平衡。我一般建议先跑通二分类验证联邦框架再扩展五分类看细粒度表现。2.3 FedAvg 的完整工作流分发、局部更新、按样本量加权聚合联邦平均 FedAvg 是现在最常用的基线算法流程可以拆成四步。第一步服务端初始化一个全局模型把参数广播给本轮选中的客户端第二步每个客户端用自己的私有数据在本地训练若干 epoch得到一个新模型第三步客户端把新模型的参数或梯度返回服务端第四步服务端按各客户端训练样本量的占比对参数做加权平均更新全局模型再进入下一轮。关键细节在第四步。加权平均不是简单把模型参数求均值。假设客户端 A 有 10 万条样本客户端 B 只有 1 万条两者平权会让 A 的信息被严重稀释。正确做法是用样本量占比当权重。用公式表示就是全局参数 Σ (客户端样本量 / 总样本量) × 客户端参数如果你用的是 PyTorch实现起来并不复杂取出每个参数字典按权重累加最后 load_state_dict。后面第三章会给出可直接复制的代码。3. 从零复现把 NSL-KDD 切给 5 个客户端并跑通 FedAvg3.1 环境准备与数据下载torch、pandas、sklearn 复用先准备环境。这个项目依赖不重CPU 就能跑有 GPU 当然更快。我建议用 python 3.9 以上版本装以下库torch模型训练和参数聚合pandas读入 CSV 格式的 KDD 数据numpy矩阵运算scikit-learn数据切分、归一化、指标计算安装命令示意如下pip install torch pandas numpy scikit-learn数据可以到 NSL-KDD 官方页面下载三个 txt 文件KDDTrain.txt、KDDTest.txt、KDDTest-21.txt放到项目 data/ 目录。文件本质是 CSV只是后缀是 txt读的时候直接用 pd.read_csv 就行。如果网络不方便也可以用镜像仓库里的副本三个文件加起来不大。下载后先确认文件是否齐全ls -lh data/正常情况下能看到三个文件的名称。最后把文件路径和列名定义写进一个常量文件方便后面脚本复用。3.2 预处理与特征编码别让客户端之间的特征空间不一致NSL-KDD 的特征由 41 列加一个标签列构成但 3 个符号列必须单独处理。物理链路里 TCP、UDP、ICMP 协议类型是类别service 有几十种取值flag 也有 11 种状态。常见做法是 one-hot 编码。下面这段代码定义完整列名并完成编码import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler, OneHotEncoder col_names [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] categorical_cols [protocol_type, service, flag] numeric_cols [c for c in col_names if c not in categorical_cols] def load_data(path): df pd.read_csv(path, headerNone, namescol_names [label]) y df.pop(label).values return df, y这段代码里headerNone是因为源文件没有表头names参数直接指定列名。y取出的是字符串标签下一步需要映射成数值。接着做编码和归一化。这里有一个联邦学习独有的坑如果直接对整个训练集一次性 fit MinMaxScaler 和 OneHotEncoder相当于把测试集统计信息泄露给了每个客户端后面验证联邦效果时指标会虚高。正确做法是先在服务端用一份“公共参考数据”拟合编码器再复制给各客户端。公共参考数据可以随机采样一部分也可以理解为所有参与方约定的公共特征字典。def fit_encoder(df, ref_idx): ref_df df.iloc[ref_idx] enc OneHotEncoder(handle_unknownignore, sparse_outputFalse) scaler MinMaxScaler() enc.fit(ref_df[categorical_cols]) scaler.fit(ref_df[numeric_cols]) return enc, scaler def transform(df, enc, scaler): cat enc.transform(df[categorical_cols]) num scaler.transform(df[numeric_cols]) X np.hstack([num, cat]) return X.astype(np.float32)注意handle_unknownignore很关键。不同客户端只见过各自协议子集测试时如果出现训练时没见过的 service 值不会导致编码崩溃而是补齐零向量。3.3 把训练集分层切给 5 个客户端FedAvg 的数据分片方式真实的联邦场景里每个客户端数据分布天然不同比如有的客户端流量大多是 HTTP有的全是 SSH。但直接用随机切分会让所有客户端分布几乎一样太理想了。为了更接近真实我通常采用分层切分保证每个客户端都有 Normal 样本同时保留类别比例差异。from sklearn.model_selection import train_test_split def federated_split(X, y, num_clients5, seed42): 按分层采样把数据集切成 num_clients 份返回 [(X0,y0),...]每份类别结构与原数据一致 client_data [] tmp_X, tmp_y X.copy(), y.copy() for c in range(num_clients - 1): tmp_X, rem_X, tmp_y, rem_y train_test_split( tmp_X, tmp_y, test_size0.5, stratifytmp_y, random_stateseed c ) client_data.append((tmp_X, tmp_y)) tmp_X, tmp_y rem_X, rem_y client_data.append((tmp_X, tmp_y)) return client_data这个函数每轮保留一半数据把另一半留给后续客户端最后一次全部分给末尾的客户端。stratifytmp_y保证每个分片里 Normal 和攻击的比例和原始数据一致避免某客户端只有纯攻击样本。test_size0.5可以按需调比如 6 个客户端切三次参数保持不变即可。得到的client_data是 NumPy 数组或 PyTorch Tensor 的组合。训练时还需要封装成 DataLoader每个客户端持有自己的加载器。3.4 FedAvg 核心循环服务端聚合与客户端更新的最小实现接下来是重头戏实现 FedAvg。模型用三层 MLP 已经够用了NSL-KDD 不是图像任务不需要复杂网络。输入维度根据预处理后的 X.shape[1] 动态确定。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset class IDSMLP(nn.Module): def __init__(self, in_dim): super().__init__() self.net nn.Sequential( nn.Linear(in_dim, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, x): return self.net(x).squeeze(-1) def local_update(model, X_local, y_local, epochs3, lr1e-3, batch_size64): 客户端本地训练返回更新后的 state_dict dataset TensorDataset(torch.from_numpy(X_local), torch.from_numpy(y_local).float()) loader DataLoader(dataset, batch_sizebatch_size, shuffleTrue) optimizer torch.optim.Adam(model.parameters(), lrlr) loss_fn nn.BCEWithLogitsLoss() model.train() for _ in range(epochs): for xb, yb in loader: optimizer.zero_grad() logits model(xb) loss loss_fn(logits, yb) loss.backward() optimizer.step() return model.state_dict()这段代码中BCEWithLogitsLoss在最后一层不加 sigmoid损失函数内部会替你做数值稳定处理训练更稳。TensorDataset把 numpy 数组包成数据集DataLoader 自动按batch_size打乱。关键聚合函数如下使用样本数加权def fed_aggregate(global_model, client_sd_list, sample_nums): 按样本数加权平均客户端参数更新全局模型 global_dict global_model.state_dict() weights torch.tensor(sample_nums, dtypetorch.float32) weights weights / weights.sum() for k in global_dict.keys(): global_dict[k] torch.zeros_like(global_dict[k]) for sd, w in zip(client_sd_list, weights): for k in global_dict.keys(): global_dict[k] sd[k].float() * w global_model.load_state_dict(global_dict)这里sample_nums是每个客户端的本地样本量列表。先把所有权重缩放到和为 1再逐参数字典加权累加。注意一定要把参数转 float 再乘权重否则整数类型会报错。最后是完整的训练循环def run_fedavg(client_data, test_data, num_rounds10, local_epochs3): in_dim client_data[0][0].shape[1] global_model IDSMLP(in_dim) logs [] for rnd in range(num_rounds): client_sds, sample_nums [], [] for X_local, y_local in client_data: model_copy IDSMLP(in_dim) model_copy.load_state_dict(global_model.state_dict()) sd local_update(model_copy, X_local, y_local, epochslocal_epochs) client_sds.append(sd) sample_nums.append(len(X_local)) fed_aggregate(global_model, client_sds, sample_nums) acc evaluate(global_model, test_data) # 自行实现 logs.append((rnd, acc[accuracy], acc[f1])) return global_model, logsnum_rounds10和local_epochs3是起步配置。你会观察到前几轮精度上升较快后面变缓这很正常。每轮所有客户端都参与当客户端数量变大后需要改成随机挑一部分参与控制通信开销。4. 复现避坑手册从训练到评估最容易翻车的地方4.1 特征归一化用全量数据拟合联邦结果虚高得离谱现象代码在全局模型训练之前拿整个 KDDTrain 的数值列 fit 了一个 MinMaxScaler然后才切分给客户端。训练完的 Accuracy 高达 99%比集中式还高明显不科学。原因这是典型的信息泄露。全局 scaler 已经看到了所有客户端的数据分布相当于每个客户端都在“开卷考试”。联邦场景下服务端不可能持有全部数据这种预处理方式让对比实验失效。解决把编码器和归一化器都看作联邦协议的一部分。常见做法是服务端在初始化时公开一份“参考特征统计量”可以来自公共先验数据也可以由参与方通过安全聚合计算全局均值和方差。在你的实验代码里至少要把 fit 过程放在全局模型之外并且确保测试集不参与 fit。建议在文档说明里明确写出“预处理器版本”这样别人复现时才不会有玄学差异。4.2 模型参数聚合没有按样本量加权小客户端被平权现象5 个客户端里有一个数据量特别大占 70%剩下四个加起来才 30%。跑出来的联邦模型精度明显低于集中式而且大客户端的表现不升反降。原因聚合时直接对 state_dict 求平均相当于每个客户端有相同投票权大客户端的信息被四个小客户端稀释了。FedAvg 论文里明确指出加权平均是按数据量比例而不是模型数量。解决检查聚合函数里有没有weights sample_nums / sum(sample_nums)这一步。还有一个小坑sample_nums必须用本地实际训练样本数而不是 DataLoader 的迭代次数因为最后一个 batch 可能不满。4.3 本地训练轮数设太大客户端各自漂移引发灾难性遗忘现象把 local_epochs 从 3 调到 20想提升本地拟合效果结果全局模型反而开始震荡准确性忽高忽低。原因客户端本地训练太多轮各自模型严重偏向本地数据分布梯度方向彼此背离。聚合后把这些极端参数做平均得到的全局模型哪个分布都没吃透。这和联邦学习里的“客户端漂移”本质相关严重时会出现灾难性遗忘客户端学到的知识互相覆盖。解决控制 local_epochs 在 1~5 之间。想提升精度优先增加 num_rounds 而不是 local_epochs。每轮通信后再评估一次全局模型如果发现连续两轮测试指标下滑就调低本地学习率或加一个余弦衰减。4.4 用 Accuracy 评估极度不平衡测试集模型看起来满分实则偏科现象二分类模型在 KDDTest 上 Accuracy 有 97%但换上 KDDTest-21 就只有 81%再看 R2L 和 U2R 两个攻击类别的召回率几乎为零。原因KDDTest 里绝大多数样本是 Normal 和 DoS样本量占比极高。模型只要把这两类学好Accuracy 就很高R2L 和 U2R 被淹没在大部分类别里评估指标没有惩罚这种遗漏。解决评估指标换成一个表格至少包含 Accuracy、Weighted-F1、Macro-F1 和被攻击类别的 Recall。其中 Macro-F1 对小类别更敏感能直观反映联邦模型对稀有攻击的检测边界。建议同时输出 KDDTest 和 KDDTest-21 两张结果表后者才是真正的压力测试。4.5 标签映射出错Normal 与攻击类别编码颠倒现象训练时 loss 不下降或者一开始就崩到十几。检查数据发现“normal”被映射成了 1其它 attack 映射成了 0逻辑完全反了。原因label列的取值大小写不一有的记录是normal有的是NORMAL。直接 compare 字符串会漏项。解决统一转小写再映射。用一个最简单字典label_map {} for v in y_all: is_attack 0 if v.lower() normal else 1 label_map[v] is_attack y_all np.array([label_map[v] for v in y_all])注意先统计所有出现的标签再一次性映射避免测试集出现训练集没见过的类别字符串。5. 验证联邦 IDS 究竟有没有价值三步对比与两个收尾习惯验证一个联邦项目不是把训练曲线画出来就完事你得证明联邦模型逼近集中式模型且优于单客户端独训。我的做法分三步。第一步固定随机种子跑三个对照单客户端本地模型、集中式模型、FedAvg 全局模型。对比在 KDDTest-21 上的 Accuracy 和 Macro-F1。通常集中式最高FedAvg 会低 1 到 2 个百分点而单个客户端独训会明显更差。如果 FedAvg 高过集中式多半是你踩了 4.1 的信息泄露坑。第二步调参与率。把 clients_per_round 从 100% 降到 20%看测试精度损失。精度损失在 3% 以内说明聚合算法稳超过这个值就要检查客户端数据异构程度。第三步把标签从二分类扩展成五分类。你只需要把y的映射改成五个类别模型输出层改成 5 个节点损失函数换成 CrossEntropyLoss。特征、切分、聚合逻辑完全不用动。这一步能看出稀有类别 R2L/U2R 的瓶颈在哪里。我自己第一次跑这类源码时栽在了 4.1 的归一化信息泄露上当时还以为是联邦占优。后来把随机种子、数据划分文件和预处理器版本全部固化到 Git当作复现契约的一部分。也希望这份踩坑经验能帮你在联邦学习与网络入侵检测这个方向上少被“玄学指标”骗一次真正做出一个可信、能讲清原理的高分项目。希望这些细节对你有用。本文还有配套的精品资源点击获取
返回列表