资讯动态

联邦学习与NSL-KDD结合的入侵检测实践指南

发布时间:2026/10/6 14:24:41 来源:尧图企业网站定制
简介基于联邦学习与NSL-KDD数据集的网络入侵检测Python项目是面向计算机相关专业学生的课程设计/期末大作业高分参考重点展示如何在保护数据隐私的前提下实现分布式模型训练与入侵行为识别。压缩包共63个文件约26.19MB主要包含Python源码py、编译后的pyc、运行说明与日志txt/log、模型权重weight、结果对比图png及数据文件csv等代码结构清晰涵盖数据预处理、客户端训练、服务端聚合等模块便于按流程阅读。项目由导师指导并认可配套带GUI界面的数据集操作工具和运行指南从数据准备、模型搭建到分布式训练均有覆盖可帮助理解NSL-KDD数据特征与联邦学习参数交换机制。资源已有88人学习下载适合需要完成课程设计、期末大作业或进行安全方向项目实战的读者参考。1. 联邦学习 NSL-KDD 入侵检测不共享数据也能训出可用模型网络入侵检测NIDS最反直觉的一点是真正生产环境里最有价值的数据往往被防火墙、IDS 节点分隔在不同位置集中到一台服务器训练既受限于隐私合规又受限于带宽。这份资源用联邦学习Federated Learning解决这个问题——多个客户端各自拿本地网络流量数据训练模型只把模型权重上传由服务端按 FedAvg 聚合全程不碰原始数据。配套的数据集是 NSL-KDD正好是 KDD Cup 99 的去冗余、去偏版本。资源包含完整的 Python 代码、数据预处理脚本、模型定义和运行指南能直接跑出准确率、精确率、召回率和 F1 对比曲线。适合正在做网络方向课程设计或毕业设计、想在一周内搭出实验闭环的学生也适合想快速验证联邦学习在异常检测场景效果的一线工程师。2. NSL-KDD 数据预处理拆解 41 维特征与 IID/Non-IID 划分2.1 为什么选 NSL-KDD 而不是 KDD Cup 99一份干净的基线数据如果你在网上搜入侵检测相关项目大概率碰过 KDD Cup 99。那套数据最大的问题是重复记录太多训练集里大量重复样本直接把分类器惯坏了模型在测试集上的表现很大程度依赖于见过多少同样的样本而不是真正的泛化能力。NSL-KDD 就是针对这些问题做的修正版它剔除了冗余记录把每一类样本的数量控制到合理区间让训练集和测试集的难度分布更接近真实情况。做联邦学习实验时这种可控的数据分布尤其重要因为你要验证的是聚合算法的有效性而不是数据集本身带来的红利。资源包里的 NSL-KDD 数据是 CSV 格式共 41 个特征加上最后两列label 和 difficulty level。41 个特征按来源可以分为四组TCP 连接基本特征1~9 号、内容是入侵检测特有的领域特征10~22 号、基于时间的流量特征23~31 号、基于主机的流量特征32~41 号。前几组偏连接自身属性后两组刻画的是流量统计规律。标签是类别型字符串二分类任务里只需要区分 normal 和 attack但多分类实验可以扩展到 5 类。特征分组代表特征用途连接基本特征1~9protocol_type、service、flag、src_bytes、dst_bytes描述 TCP 连接的基础属性Protocol/Service 是字符串需要编码内容特征10~22hot、failed_logins、root_shell、num_root检测 U2R、R2L 这类隐蔽攻击攻击特征藏在内容里时间流量特征23~31count、srv_count、serror_rate2 秒窗口内的连接统计对 DoS/Probe 敏感主机流量特征32~41dst_host_count、dst_host_srv_count100 个连接窗口的主机侧统计用于慢速扫描检测攻击类型方面NSL-KDD 里四类攻击是 DoS拒绝服务、Probe端口扫描探测、R2L远程到本地越权、U2R本地提权。四类样本数量差异极大DoS 占了大头U2R 和 R2L 非常少这也是后面联邦训练评估时最容易翻车的点我放到第 5 章细说。2.2 预处理脚本从原始 CSV 到可直接训练的张量先把数据加载和标签处理拆开。NSL-KDD 的 CSV 文件本身没带列名但资源包里代码通常会把 41 个特征名写死我一般建议你直接读进来之后手动指定列名省得后续取特征列下标时出错。import pandas as pd from sklearn.preprocessing import LabelEncoder, StandardScaler import numpy as np COLUMN_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 ] def load_nsl_kdd(path, multi_classFalse): df pd.read_csv(path, headerNone, namesCOLUMN_NAMES [label, difficulty]) if multi_class: le LabelEncoder() df[label_code] le.fit_transform(df[label]) else: df[label_code] (df[label] ! normal).astype(int) return df, le if multi_class else None这段代码里multi_class开关决定你是做二分类还是五分类。二分类下label_code为 0 表示 normal、1 表示 attack多分类下用LabelEncoder把字符串标签转成 0~4 的数字比如 normal0、DoS1、Probe2、R2L3、U2R4。唯一的坑是LabelEncoder必须在训练集上 fit测试集直接 transform否则类别编号会错位。接下来是特征编码和缩放。三个字符串特征protocol_type、service、flag需要 One-Hot 编码因为它们是离散取值且没有天然顺序用LabelEncoder硬编成 0/1/2 会让模型误以为存在线性关系。service这个特征取值特别多One-Hot 之后维度会膨胀到 70 左右这个是正常现象。数值特征则用StandardScaler做标准化。def preprocess_data(df, scalerNone, save_scaler_pathNone): categorical_cols [protocol_type, service, flag] numeric_cols [c for c in COLUMN_NAMES if c not in categorical_cols] df_cat pd.get_dummies(df[categorical_cols], prefixcategorical_cols) df_num df[numeric_cols].copy() if scaler is None: scaler StandardScaler() scaled scaler.fit_transform(df_num) else: scaled scaler.transform(df_num) if save_scaler_path is not None: import pickle with open(save_scaler_path, wb) as f: pickle.dump(scaler, f) X np.hstack([scaled, df_cat.values]) y df[label_code].values return X, y, scaler注意我刻意没有对 One-Hot 后的列做标准化。One-Hot 列本来就是 0/1 稀疏结构标准化会把它变成均值为负、方差很大的连续值反而破坏稀疏性和可解释性。还有一点测试集的scaler必须用训练集 fit 出来的那一份所以这个函数只在scalerNone时 fit后续测试全部传入已保存的 scaler 实例。资源包里的代码一般会留一个save_scaler_path参数就是为了防止你测试时重新计算均值方差然后翻车。2.3 客户端数据划分IID 与 Non-IID 都要能切换联邦学习的实验设计和普通深度学习不一样数据不是全部喂给一个模型而是按客户端划分成多份每个客户端拿到的子集参与本地训练。如果只是随机打乱后均匀切分那叫 IID独立同分布每个客户端的数据分布几乎相同联邦聚合过程会很平稳。但真实场景里不同节点的流量模式差异很大比如一个 Web 服务器节点看到的大多是 HTTP 流量另一个数据库节点看到的是频繁的访问尝试这叫 Non-IID。做实验时两种模式都要支持才能在论文里对比。def split_iid(X, y, num_clients, seed42): rng np.random.default_rng(seed) indices rng.permutation(len(X)) split_size len(indices) // num_clients client_data [] for i in range(num_clients): start i * split_size end start split_size if i num_clients - 1 else len(indices) idx indices[start:end] client_data.append((X[idx], y[idx])) return client_data def split_non_iid(X, y, num_clients, alpha0.5, seed42): # dirichlet 分布采样每个客户端上各类别的比例 from numpy.random import default_rng rng default_rng(seed) n_classes len(np.unique(y)) class_indices [np.where(y c)[0] for c in range(n_classes)] client_data [] for _ in range(num_clients): prop rng.dirichlet([alpha] * n_classes) client_idx [] for c in range(n_classes): n_sample int(len(class_indices[c]) * prop[c]) chosen rng.choice(class_indices[c], sizen_sample, replaceFalse) client_idx.append(chosen) idx np.concatenate(client_idx) rng.shuffle(idx) client_data.append((X[idx], y[idx])) return client_datasplit_non_iid里alpha是 Dirichlet 分布的浓度参数alpha越小每个客户端上各类别比例越极端可能某个客户端几乎全是 DoS另一个几乎全是 normal。这种划分方式比手动调比例更可复现也是联邦学习论文里的常见做法。我自己的习惯是alpha0.5做一组、alpha0.1做一组极端 Non-IID再配一组 IID 做基线三组结果画在同一张图里对比。3. 联邦学习核心实现FedAvg 聚合与本地模型选型3.1 FedAvg 的四个步骤初始化、下发、本地训练、加权聚合FedAvgFederated Averaging是整个联邦学习里最经典的算法思路不复杂服务端初始化一个全局模型把参数广播给部分客户端每个客户端用自己的本地数据训练若干轮把训练后的模型权重返回服务端把所有客户端返回的权重按样本数量加权平均得到新的全局模型。这个过程重复多个通信轮次。注意它传输的不是梯度而是完整的模型权重。这样做的好处是客户端可以自由选择本地优化器、epoch 数和 batch size服务端只做聚合。缺点也直接——每轮传输的权重文件比梯度大网络带宽会成为瓶颈这也是后面第 4 章调参时要考虑的。在这个资源包里服务端聚合逻辑是这样实现的def fed_avg_aggregate(weights_list, num_samples_list): total_samples sum(num_samples_list) aggregated [] for layer_idx in range(len(weights_list[0])): layer_sum None for client_weights, num_samples in zip(weights_list, num_samples_list): weight client_weights[layer_idx] coef num_samples / total_samples if layer_sum is None: layer_sum weight * coef else: layer_sum weight * coef aggregated.append(layer_sum) return aggregated逐层加权累加权重系数是num_samples / total_samples也就是样本数越多的客户端对全局模型影响越大。这样做比简单平均更合理如果一个客户端有 5000 条数据另一个只有 200 条前者训练的模型显然更值得信任。真正跑实验时这个函数会在每个通信轮次末尾调用一次聚合结果再广播回所有参与客户端。3.2 本地模型选型MLP 做第一版基线本地模型不需要太复杂MLP多层感知机在这个场景下够用且收敛快。NSL-KDD 特征预处理后维度大约 120 左右不是图像这种高维输入CNN 的优势发挥不出来。资源包里给的模型一般就是 3 层 MLP第一层把输入压到 128 维第二层降到 64 维中间加 ReLU 和 Dropout最后一层输出 2二分类或 5多分类。import torch import torch.nn as nn class NIDSMLP(nn.Module): def __init__(self, input_dim, num_classes2, hidden_dim128): super(NIDSMLP, self).__init__() self.net nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Dropout(0.3), nn.Linear(hidden_dim, hidden_dim // 2), nn.ReLU(), nn.Dropout(0.2), nn.Linear(hidden_dim // 2, num_classes) ) def forward(self, x): return self.net(x)input_dim在预处理完成后打印一下X.shape[1]就能确定我跑出来大约是 120 左右。Dropout(0.3)是防止本地模型在单个客户端上过拟合——联邦场景每个客户端数据量未必大本地训练又只做几个 epochDropout 能有效稳定收敛。如果发现训练集准确率极高而测试集跟不上把 Dropout 调高到 0.5 试试。3.3 超参数旋钮参与比例、本地迭代次数、batch sizeFedAvg 有三个全局超参数直接决定训练效率和最终精度每轮参与客户端比例C、本地训练 epoch 数E、batch sizeB。它们相互制约需要一起调。参数名推荐区间对实验的影响C参与比例0.2 ~ 1.0C越小每轮通信成本越低但全局模型更新步子不稳C1.0收敛最稳适合小规模实验E本地 epoch1 ~ 10E越大本地拟合越充分但 Non-IID 下容易让客户端模型漂移导致聚合效果变差E5是折中值Bbatch size32 ~ 128B越小本地优化越频繁更新方向噪声越大B64起步比较稳我一般会先把三个参数都设成偏保守的值跑通一次然后只动其中一个看曲线变化。比如C从 1.0 降到 0.2总轮次不变的情况下每轮只是少训练几个客户端训练时间几乎等比例缩短但准确率可能会有 1~2 个百分点的回落。E的影响更微妙IID 场景下增大E有助于收敛Non-IID 场景下E过大会导致客户端各自为政聚合时震荡。这个坑在下一章实战里有更具体的踩法。4. 运行指南环境配置、文件结构与可复现参数4.1 环境准备先把版本钉死在运行这份资源之前硬件不挑CPU 就能跑完整个实验但软件版本一定要对齐。PyTorch 版本太新或太旧都可能带来小问题Python 版本如果过低sklearn的接口行为也会不一致。我建议你用虚拟环境把环境隔离出来别直接在系统 Python 上装。python -m venv fl_nids_env source fl_nids_env/bin/activate # Windows 下执行 fl_nids_env\Scripts\activate pip install torch2.0.1 --index-url https://download.pytorch.org/whl/cpu pip install pandas numpy scikit-learn matplotlibtorch装 CPU 版就够用这个实验的模型和数据量都不大GPU 不会带来数量级提升。pandas和scikit-learn分别负责数据预处理和评估指标matplotlib画混淆矩阵。如果pip下载太慢把--index-url换成国内镜像源会快很多。4.2 项目结构每个文件负责一段流水线解压资源包后核心文件大概是下面这些我直接标注它们的分工文件职责data_loader.py读取 KDDTrain.csv 和 KDDTest.csv完成预处理、One-Hot、标准化client.py定义客户端类包含本地训练函数train()和模型评估函数evaluate()server.py服务端聚合逻辑FedAvg 权重平均管理每轮通信流程model.pyMLP 模型定义或你想换成的其他网络结构main.py参数解析、训练主流程、日志输出、结果保存requirements.txt完整依赖清单有些资源包会把client.py和server.py合并成一个federated.py逻辑上没差别只是函数调用层级不同。我拿到包的习惯是先打开main.py看argparse部分设置了哪些参数基本就能知道整个包的灵活性边界。4.3 一条命令跑通实验从启动到输出在项目根目录执行python main.py --num_clients 5 --rounds 20 --local_epochs 5 --batch_size 64 --lr 0.01命令参数解释--num_clients是模拟的客户端总数--rounds是联邦通信轮次--local_epochs是每个客户端本地训练的 epoch 数--lr是本地 SGD 学习率。跑起来之后输出大致长这样[Round 1/20] Global Acc: 0.8123 | Precision: 0.8021 | Recall: 0.8110 | F1: 0.8065 [Round 2/20] Global Acc: 0.8345 | Precision: 0.8280 | Recall: 0.8317 | F1: 0.8298 ... [Round 20/20] Global Acc: 0.8910 | Precision: 0.8842 | Recall: 0.8895 | F1: 0.8868每个 round 结束后服务端先把聚合模型下发在所有客户端上评估一次打印全局指标。注意这里的准确率是加权平均后的结果反映的是全局模型在所有参与者上的综合表现。训练结束后代码通常会把混淆矩阵存成 PNG 图片把每轮指标写进 CSV 文件方便后面画曲线。4.4 一组好用的基线参数与理由如果不想在参数上纠结太久直接用下面这组配置就能跑出可用的结果参数值理由num_clients5客户端太少聚合效果不稳定太多单轮训练太久5 是课程设计和论文实验的通用值rounds2020 轮足够看到收敛趋势想画漂亮收敛曲线可以加到 50local_epochs5本地训练 5 个 epoch 能学到特征又不至于过拟合batch_size64兼顾梯度稳定性和训练速度lr0.01SGD 默认学习率IID 下稳定Non-IID 下需要调低到 0.005non_iid_alpha0.5只做 IID 实验时忽略做 Non-IID 对比时用 0.5 和 0.1 各跑一组这个参数组合的关键思路是先跑通再调优一开始把rounds设成 20是因为跑完只要十几分钟能快速验证代码和流程有没有问题。确认数值合理后再加大轮次或调local_epochs看指标的边际变化这样最省时间。5. 避坑指南这份资源最容易翻车的 5 个地方5.1 现象训练时报特征维度对不上第一次跑的时候本地训练报mat1 and mat2 shapes cannot be multiplied。原因是预处理后特征维度是 122但model.py里写死的input_dim是 119。原因service特征的取值在不同版本 NSL-KDD 数据里略有差异One-Hot 后的列数不一致资源包里写死的维度是作者当时那份数据的值。解决启动训练前打印一行X.shape[1]把真实维度回填到模型初始化处。我自己的做法是在main.py里动态读取X.shape[1]再创建模型不要硬编码。model NIDSMLP(input_dimX.shape[1], num_classes2)5.2 现象整体准确率 85%但 R2L 和 U2R 的 F1 是 0实验报告里出现了准确率不错、攻击检测一塌糊涂的结果尤其是 R2L远程到本地和 U2R本地提权这两类精确率和召回率全是 0。原因NSL-KDD 里 R2L 和 U2R 样本本来就少联邦学习按样本量加权聚合时这两类对全局模型的贡献极小。模型学习到的最优策略是无脑预测为 normal 和 DoS整体准确率照样高。解决评估阶段必须看 per-class 指标不要只看整体 F1。训练层面给客户端本地损失函数加类别权重或者对少数类做简单的过采样。最直接的办法是降低local_epochs防止模型在本地把多数类拟合过头。5.3 现象Non-IID 划分后 loss 震荡准确率不涨反跌从 IID 切到 Non-IIDalpha0.1之后训练 loss 在前几轮剧烈震荡全局准确率反而低于随机猜测。原因每个客户端拿到的数据是偏斜的本地模型朝着各自的方向更新服务端平均之后产生了一个四不像的全局模型。local_epochs越大漂移越严重。解决把local_epochs从 5 降到 1学习率从 0.01 降到 0.005同时把每轮参与比例C设成 1.0。损失函数降低的是客户端被带跑的风险代价是收敛速度变慢但至少 loss 曲线能稳定下降。5.4 现象每个 round 训练时间越来越长越跑越慢跑第 1 轮很快到第 10 轮之后每轮耗时明显增加甚至接近翻倍。原因FedAvg 每轮都要把模型权重序列化后下发和回收服务端聚合时 PyTorch 在 CPU 上频繁做张量转换和拷贝。Python 列表里存了太多历史权重对象内存碎片化严重。解决训练循环里用del weights_list手动释放不再使用的引用或者把权重拷贝改成原地累加避免每层创建新张量。如果客户端数多把客户端训练并行改成ThreadPoolExecutor但注意 PyTorch 的线程安全。from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workersnum_clients) as executor: futures [executor.submit(client.train, global_weights) for client in clients] results [f.result() for f in futures]5.5 现象测试阶段加载模型后预测结果全是同一个类别训练过程指标正常但用torch.load加载模型权重预测时所有测试样本的输出都一样。原因训练时保存的是联邦聚合后的全局模型权重但评估代码里没有把模型切换到 eval 模式或者权重 key 的顺序和测试模型定义不一致。更常见的原因是训练时只把准确率最高的全局模型保存了而最高点恰好出现在 Non-IID 震荡后的过拟合位置。解决保存模型时同时保存input_dim、类别数等元信息加载后先.eval()再推理。评估脚本里加一行 assert 判断权重形状和模型层数是否匹配不匹配立刻报错而不是静默预测。checkpoint torch.load(best_model.pt) model.load_state_dict(checkpoint[model_state]) model.eval() with torch.no_grad(): pred model(X_test_tensor)6. 进阶验证从二分类扩展到攻击分类用混淆矩阵看模型边界如果你跑通了二分类实验下一步建议把它扩展到五分类——Normal、DoS、Probe、R2L、U2R 各算一类。这一步改动不大但价值很高二分类只能证明模型知道有没有攻击五分类才能看出模型对不同攻击类型的敏感度差异。改动点只有三处data_loader.py里multi_classTruemodel.py输出层从2改成5损失函数从BCEWithLogitsLoss换成CrossEntropyLoss。评估时用classification_report输出每一类的精确率、召回率和 F1重点关注 U2R 那一行。训练完成后我习惯把测试集推理结果画成混淆矩阵热力图。这一步虽然简单但对论文和报告来说是最直观的证据也能快速定位模型把哪两类攻击互相混淆。import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix cm confusion_matrix(y_test, y_pred) plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabels[Normal, DoS, Probe, R2L, U2R], yticklabels[Normal, DoS, Probe, R2L, U2R]) plt.xlabel(Predicted) plt.ylabel(Actual) plt.savefig(confusion_matrix.png, dpi150)跑完五分类我建议做一件事先用单客户端即num_clients1跑一遍同样的数据记录准确率和 F1 作为集中式上限再把num_clients加到 5、10 对比。这样你能量化联邦学习带来的精度损失也能看出 Non-IID 下的差距是算法问题还是数据划分问题。这个基线对照方法我几乎每个项目都强制自己先跑一遍。从那以后我每次拿到一个新数据集都会先做一次单客户端训练确认任务上限再谈联邦聚合的优化方向否则排查问题就像在黑匣子里猜答案。这份资源我是按这个顺序跑通的希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑