联邦学习入侵检测实战:从NSL-KDD预处理到FedAvg聚合
简介基于联邦学习和NSL-KDD数据集的网络入侵检测Python项目面向计算机相关专业正在做课程设计、期末大作业的学生以及需要项目实战练习的学习者。项目包含完整源码与运行说明带GUI界面覆盖客户端本地训练、服务端模型聚合、通信连接、模型权重保存与结果对比等模块针对NSL-KDD多分类任务多客户端在保护数据隐私的前提下协同训练入侵检测模型清晰展示联邦学习在入侵检测场景中的落地流程。压缩包共62个文件以Python脚本及字节码为核心辅以CSV数据、日志、训练权重、结果对比图和README说明整体约26.18MB目录结构清晰便于按功能模块检索与调试。资源已有201人学习下载是经导师指导并通过的高分项目评审98分可作为课设或大作业的参考范本帮助读者快速复现实验、理解核心思路并进行二次扩展。1. 联邦入侵检测落地一个 98 分的课程项目能拆出多少东西如果你准备做网络入侵检测NIDS的课设或期末大作业大概率会遇到两个坎一是 NSL-KDD 数据集怎么预处理才能喂给模型二是如何在展示时说明白“联邦学习”和普通深度学习的区别。最近拆了一个标注 98 分的项目包里面有 client/server 两套代码、GUI 界面、预训练权重和训练日志整体结构不复杂但很完整适合当成一个可复现的 FL-IDS 基线。本文会顺着数据处理、模型设计、联邦聚合到 GUI 集成的顺序把这套方案的实现逻辑和重跑参数讲清楚最后补上排错技巧和通信压缩的优化方向。适合正在做课设的学生也适合想快速验证联邦学习在 NIDS 上效果的工程师。2. NSL-KDD 数据清洗与特征工程从原始记录到模型输入张量2.1 为什么选 NSL-KDD 而不是 KDDCup99在入侵检测研究里KDDCup99 是最早被广泛使用的基准但它存在两个致命问题训练集和测试集中有大量重复记录导致模型在重复样本上刷出虚高的准确率同时冗余样本会让学习算法偏向出现频率高的攻击类型。NSL-KDD 由 Tavallaee 等人提出剔除了重复项并为每个难度级别保留合适的样本数使得评估结果更接近真实泛化性能。本项目的训练数据是KDDTrain.txt测试数据是KDDTest.txt。每条样本有 41 个特征和 1 个标签。这 41 个特征从语义上可以分成四组基础 TCP 连接特征duration、protocol_type、service、flag 等、内容特征登录失败次数、root shell 尝试次数等、流量特征过去 2 秒内相同主机的连接数等、以及基于主机的流量特征。处理的核心是把符号型特征转成数值再把标签从 23 类攻击映射成二分类或五分类。2.2 符号特征的一热编码与标签归一化原代码里initDate.py承担了数据读取和预处理。它的处理顺序很有代表性import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler, LabelEncoder COLUMNS [ duration, protocol_type, service, flag, src_bytes, dst_bytes, land, wrong_fragment, urgent, hot, # ... 省略中间特征共 41 列 dst_host_srv_rerror_rate, label ] df pd.read_csv(KDDTrain.txt, headerNone, namesCOLUMNS) df[label] df[label].apply(lambda x: 0 if x normal else 1) # 对三个符号特征做 one-hot df pd.get_dummies(df, columns[protocol_type, service, flag]) # 分离特征和标签 X df.drop(label, axis1).values.astype(np.float32) y df[label].values # 标准化只 fit 训练集再 transform 测试集 scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)逻辑说明get_dummies会把protocol_type变成protocol_type_tcp、protocol_type_udp等列原本 41 维特征经过这一步会扩展到 120 维左右。标签这里采用二分类映射但如果你要做五分类可以直接把label映射成normal, probe, dos, u2r, r2l对应的整数。标准化时只对训练集fit再对测试集transform这点很关键——如果把测试集也参与fit相当于把测试集信息泄露给训练过程评估结果会偏乐观。2.3 参数表输入维度和类分布下表是该项目中实际使用的预处理参数和数据集规模重跑时可以直接参考。参数项数值说明原始特征数41包含 3 个符号特征one-hot 后特征数122取决于 service 的取值数量训练样本数125973去掉重复样本后的标准值测试样本数22544与训练集分布不同更具挑战性二分类标签normal / attack本项目采用标准化方式StandardScalerz-score 归一化因为 NSL-KDD 中 U2R 和 R2L 类别样本极少二分类可以避免极端类别不平衡带来的训练震荡。如果你想做细粒度检测建议对这两个类别做上采样或使用加权损失函数否则模型很容易把所有样本都预测成 normal。3. 联邦学习下的 DNN 模型本地训练与参数聚合的设计逻辑3.1 模型结构为什么用全连接网络而不是 CNN/LSTM对于 NSL-KDD 这类结构化表格数据卷积网络没有空间结构可挖LSTM 虽然能建模序列但单条网络连接记录并不是天然的时序序列。因此项目采用多层全连接网络DNN输入层接收预处理后的 122 维特征中间层用 ReLU 激活输出层是 sigmoid 二分类。模型定义在model.py中核心代码如下from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout def create_model(input_dim122): model Sequential([ Dense(64, activationrelu, input_shape(input_dim,)), Dropout(0.2), Dense(32, activationrelu), Dropout(0.2), Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) return model参数说明第一层 64 个神经元Dropout 设为 0.2 用于缓解过拟合第二层压缩到 32 维减少参数量。因为 NSL-KDD 特征之间线性相关性较强这个规模的网络已经有足够容量。实际训练时每轮本地 epoch 数不必太多联邦学习的核心不在单模型精度的极致而在多客户端协同后的全局收敛。3.2 联邦聚合FedAvg 的实现与边界条件联邦学习在入侵检测中的价值在于多个机构可以共享模型参数而无需直接交换原始流量数据。服务端采用最常见的 FedAvg 算法每个客户端在本地数据上训练若干轮然后把模型权重model.get_weights()上传给服务器服务器按样本数量加权平均所有客户端的权重再下发回客户端作为下一轮初始模型。用公式表达就是w_global Σ (n_k / n) * w_local_k其中n_k是第 k 个客户端的样本数n是总样本数。这个加权系数能防止数据量小的客户端主导全局模型。在本项目的 client/server 架构中main_client_1.py和main_client_2.py分别代表两个参与方。本地训练的逻辑如下def local_train(model, x_local, y_local, epochs3): history model.fit(x_local, y_local, batch_size32, epochsepochs, verbose0) return model.get_weights()服务端聚合代码在main_server.py里大致是client_weights [] for client_socket in client_sockets: # 接收序列化后的权重 weights recv_weights(client_socket) client_weights.append(weights) # 简单平均没有加权时 avg_weights [] for layer_idx in range(len(client_weights[0])): layer_sum sum(w[layer_idx] for w in client_weights) avg_weights.append(layer_sum / len(client_weights)) model.set_weights(avg_weights)注意这里的常见误区如果两个客户端数据量差异很大简单平均会导致全局模型偏向小数据集的一方。正确做法是让客户端同时上报n_k服务端加权平均。本项目因为把 NSL-KDD 训练集平均切分成两份所以简单平均也能得到不错的结果但你不应该在生产环境中这么做。3.3 非独立同分布数据对收敛的影响NSL-KDD 本身是人为构造的均衡数据集直接切分后每个客户端拿到的是独立同分布IID数据。实际联邦学习场景中比如不同学校的网络日志攻击类型分布差异巨大这就是非 IID 数据。此时 FedAvg 收敛速度会明显下降甚至出现灾难性遗忘——客户端 A 学过 DoS客户端 B 学过 Probe聚合后的模型可能把两者都忘掉。针对非 IID可以做的改进是服务端在聚合前对每层的权重做归一化检查或者引入弹性平均FedProx。本项目为了演示方便没有做这些增强但代码结构里权重传输和聚合的接口是保留的你可以在utils.py中增加权重裁剪逻辑。通信压缩也是一个方向比如对权重做 Top-k 稀疏化只上传梯度变化最大的 10% 参数能显著降低带宽开销代价是本地模型需要维护一个误差补偿向量。这套做法在热词搜索里通常叫作偏置压缩实现起来比想象中简单后面第 5 章会给出具体代码思路。4. 训练流程与通信机制从 client 启动到 GUI 展示的完整闭环4.1 项目目录结构与启动顺序拿到源码包后你会看到两台“虚拟节点”的代码main_client_1.py负责在本地跑模型并发送权重main_client_2.py几乎一样只是端口和数据集切片不同main_server.py监听端口、聚合权重并下发。connFun.py封装了 socket 通信的序列化和反序列化解决 numpy 数组在 TCP 上的传输问题。运行顺序固定是# 1. 启动服务端 python main_server.py # 2. 打开两个终端分别启动客户端 python main_client_1.py python main_client_2.py # 3. 所有客户端就绪后服务端开始发初始权重 python GUI.py提示GUI.py需要 matplotlib 和 tkinter 环境在无桌面服务器上可以把 GUI 关掉只保留训练日志输出。4.2 通信封装numpy 数组如何在 socket 上传输connFun.py是这套代码里容易被忽略但很重要的模块。PyTorch 或 Keras 的权重是 NumPy 数组socket 只能发送 bytes因此需要先把数组转成 bytes接收方再重建。常见做法是把数组tobytes()并带上 shape 和 dtype 元信息。项目里的实现大致是import socket import pickle def send_object(sock, obj): data pickle.dumps(obj, protocol4) length len(data) sock.sendall(length.to_bytes(8, big)) sock.sendall(data) def recv_object(sock): length_bytes b while len(length_bytes) 8: length_bytes sock.recv(8 - len(length_bytes)) length int.from_bytes(length_bytes, big) chunks [] remaining length while remaining 0: chunk sock.recv(min(65536, remaining)) chunks.append(chunk) remaining - len(chunk) return pickle.loads(b.join(chunks))说明前 8 个字节用固定大端序表示后续 payload 长度然后按这个长度循环接收。pickle对模型权重这种纯数值数据是安全的但如果你做的是在线服务建议改用 MessagePack 或自定义协议以防反序列化攻击。注意发送numpy数组前先调np.asarray(obj, dtypenp.float32)统一类型避免大端小端问题。4.3 训练超参与时间开销对照以下是本项目实测中比较稳定的超参设置来自argu.py超参数数值说明联邦通信轮数 rounds10每轮客户端做一次本地多 epoch 训练本地 epoch5每个客户端每轮训练 5 次全量数据batch_size32影响收敛平稳度过大容易震荡学习率0.001Adam 默认值已能满足客户端数量2超过 2 个也可只要改 main_client 副本验证集比例0.2服务端每轮在测试集上评估在两张无 GPU 的云主机上每轮训练大约 20 秒10 轮总共 3 分多钟就能跑完。如果你本地只有一台机器也可以开 3 个终端模拟多客户端注意修改每个客户端的host和port以及数据切分的起始偏移量否则所有客户端都在训练同一份数据联邦学习就退化成单机训练。4.4 GUI 状态机与日志输出GUI 的核心作用不是增强功能而是向评审展示“正在训练”的证据。它轮询读取data.log中新增的行解析出每轮的 loss 和 accuracy然后绘制实时曲线。data.log的格式是服务端每轮追加一行round3, client1_loss0.42, client2_loss0.38, global_acc0.93如果你没有显示环境同样可以从data.log里拿到结果。这里的技巧是把日志写到内存而不是磁盘文件减少高频轮询的 I/O 压力但课程项目为了可读性直接写文件没问题评审老师一般会打开文件看训练过程。5. 重跑实验的排错技巧从权重文件到结果对比图5.1 从预训练权重判断收敛状态压缩包里有Net.weight、local_model.weight、local_testModel.weight三个权重文件。区分它们很简单local_model.weight是某个客户端本地最后一轮的模型参数Net.weight是服务端聚合得到的全局模型参数local_testModel.weight则是客户端用本地数据测试全局模型后保存的参数。三者数值不同是正常的你要关注的是它们对应的测试准确率。判断收敛的快速方法是加载全局权重到create_model()然后用model.evaluate(X_test, y_test)打印 loss 和 accuracy。如果 loss 曲线在第 5 轮之后还在明显下降说明训练轮数不够可以把argu.py里的rounds调大如果出现 loss 降低但准确率静默不变多半是类别不平衡导致模型只预测 normal。5.2 通信链路故障定位最常见的报错是服务端ConnectionResetError原因通常是两个客户端的端口冲突或者服务端先关闭了连接。排查顺序如下# 查看两个客户端是否连上服务端 netstat -an | grep 9010 # 服务端打印接收到的权重长度 python main_server.py --debug如果客户端频繁掉线优先检查connFun.py里recv循环是否处理了socket.timeout异常。建议在send_object时用socket.setdefaulttimeout(60)避免客户端训练太久导致服务端误判超时断开。5.3 通信压缩的进阶改造最后给一个能写进“工作展望”的技巧偏置压缩。常规联邦学习上传的是完整权重向量假设权重有 50 万个浮点每个 4 字节就是 2 MB。使用稀疏化方法后只上传绝对值最大的 10% 权重其余不下发。你需要为每个客户端保存一个“误差累加器”把没上传的权重变化累积下来等下一轮和新的变化一起算。import numpy as np def compress_weights(weights, top_k_ratio0.1): # 将多层权重展平并拼成一个向量 flat np.concatenate([w.flatten() for w in weights]) k max(1, int(flat.size * top_k_ratio)) indices np.argpartition(np.abs(flat), -k)[-k:] # 只保留 top-k 的值和索引 compressed { indices: indices.astype(np.uint32), values: flat[indices].astype(np.float32) } return compressed在服务端接收后把缺失位置的权重解释为上一轮的旧值再做平均。这样每轮通信量能缩减到原来的 10% 左右在广域网上优势明显。作为对比你可以把resultCompare1.png和resultCompare2.png分别视为“未压缩”和“压缩后”的准确率曲线观察两者差异是否在可接受范围内。5.4 最终验证方法把整套代码跑通后请保留三样东西data.log、最终的模型权重文件、以及 GUI 截屏。这是课程评审最关心的证据链。用下述命令快速导出模型在测试集上的混淆矩阵import joblib from sklearn.metrics import confusion_matrix y_pred (model.predict(X_test) 0.5).astype(int) print(confusion_matrix(y_test, y_pred))注意本项目默认是二分类混淆矩阵只看 normal 和 attack 的判别情况。如果你需要五分类的细粒度结果就要回到 2.2 节重新映射标签并且把输出层的sigmoid换成softmax。到这一步你已经不只是“会跑通代码”而是能针对不同数据集和联邦场景调整模型与通信策略了。本文还有配套的精品资源点击获取