简介:本资源是一套基于联邦学习框架与NSL-KDD标准数据集实现的网络入侵检测系统完整Python工程,专为计算机、电子信息及数学类专业本科生设计,适用于毕业设计、课程设计与期末大作业等高阶实践场景。项目已通过导师评审并获98分高分,集成训练/测试/可视化全流程,含GUI交互界面,显著降低部署与演示门槛。压缩包共63个文件,涵盖12个核心Python源码(含client/server双端逻辑、模型定义、GUI构建与连接函数)、3个权重文件(Net.weight、local_model.weight等)、10个说明类txt文档、2个png结果对比图及CSV日志数据,整体26.18MB,结构清晰、模块解耦度高,便于理解联邦学习在安全领域的分布式建模实践。目前已有307人学习下载,读者可直接运行复现完整检测流程,获取带注释的联邦训练代码、本地/全局模型评估对比结果、GUI操作逻辑及多客户端协同调试范例,是深入理解隐私保护型IDS落地的优质参考项目。
1. 联邦学习做入侵检测,真能不传原始流量数据就训出可用模型?——NSL-KDD+Python+GUI全栈可运行项目实测
你手头有一批真实网络流量日志,但合规红线卡得死:不能把原始pcap或连接记录上传到中心服务器,更不能让第三方看到用户IP、端口、协议载荷。这时候,传统集中式训练直接废掉。而这个98分毕设项目,用纯Python实现了三客户端+单服务器的联邦学习闭环,所有原始NSL-KDD数据全程保留在本地client目录下,只交换加密后的模型权重(local_model.weight、Net.weight),最后在GUI界面里实时对比各客户端本地测试准确率与全局聚合模型效果。它不是玩具Demo——文件列表里明晃晃躺着resultCompare1.png和resultCompare2.png两张可视化结果图,data.log和data3.log记录着每轮通信的loss与acc变化,initDate.py里甚至做了NSL-KDD字段对齐与one-hot编码预处理。适合正在赶毕设 deadline 的计算机/网安专业学生,也适合想亲手拆解“联邦学习如何落地安全场景”的工程师:代码不依赖GPU,PyTorch 1.8+即可跑通,GUI用tkinter写成,连打包成exe都留了server.zip和client.zip两个现成压缩包。别被“高分设计”唬住——它真正硬核的地方,在于把联邦学习里最棘手的非独立同分布(Non-IID)数据切分、客户端异步更新冲突、本地模型过拟合监控这些黑匣子,全摊开在main_client_1.py、main_server.py和model.py的注释行里。
2. 从NSL-KDD原始数据到联邦训练:四步完成数据准备与本地模型初始化
2.1 NSL-KDD数据集结构解析与本地化切分逻辑
NSL-KDD有KDDTrain+.txt和KDDTest+.txt两个主文件,共41个特征(如duration、protocol_type、service、flag等)加1个标签attack_type。本项目没用官方train/test划分,而是通过initDate.py做了按攻击类型比例保留在各客户端的Non-IID切分:
client/目录下data/子文件夹存放各客户端专属数据(如client1_data.csv)- 切分规则在
initDate.py第47行:train_ratio = [0.4, 0.35, 0.25]—— 三个客户端分别拿到40%、35%、25%的总训练样本,且每个客户端的数据中normal、dos、probe、r2l、u2r五类攻击占比严格按原始数据分布浮动±3% - 标签映射表硬编码在
initDate.py第22行:label_map = {'normal':0, 'dos':1, 'probe':2, 'r2l':3, 'u2r':4},避免pandas读取时字符串标签导致torch.nn.CrossEntropyLoss报错
提示:原始NSL-KDD的
KDDTrain+.txt是无header的CSV,initDate.py第63行用pd.read_csv(..., header=None)强制读取,再通过columns=feature_names注入列名。若你替换为自定义流量数据,必须确保feature_names列表长度=41,且顺序与NSL-KDD官方文档一致。
2.2 客户端本地模型构建:轻量CNN+全连接双分支设计
model.py定义了Net类,结构比常规入侵检测模型更克制:
- 输入层:41维特征 →
nn.Linear(41, 64)→ ReLU - 特征提取层:
nn.Conv1d(in_channels=1, out_channels=16, kernel_size=3)(将41维展平为[1,41]后卷积) - 分类头:两路并行输出——
self.fc1输出5类攻击概率,self.fc2额外输出二分类(normal/attack)用于置信度校验 - 关键细节:
forward()函数第32行x = x.view(x.size(0), -1)执行flatten,避免Conv1d输出维度与Linear输入不匹配;__init__中self.dropout = nn.Dropout(0.3)防止客户端小数据集过拟合
# model.py 关键片段(已补全缺失注释) class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv1d(1, 16, 3) # 输入: [batch, 1, 41] → 输出: [batch, 16, 39] self.fc1 = nn.Linear(16*39, 5) # 改变此处可适配不同特征数 self.fc2 = nn.Linear(16*39, 2) # 二分类辅助头,提升泛化性 self.dropout = nn.Dropout(0.3) def forward(self, x): x = x.unsqueeze(1) # 增加channel维度: [batch, 41] → [batch, 1, 41] x = F.relu(self.conv1(x)) # 卷积后尺寸: [batch, 16, 39] x = x.view(x.size(0), -1) # flatten: [batch, 16*39] x = self.dropout(x) out1 = self.fc1(x) # 5类攻击输出 out2 = self.fc2(x) # normal/attack二分类输出 return out1, out22.3 本地训练循环:带早停与梯度裁剪的鲁棒实现
main_client_1.py中train_local_model()函数封装了完整训练流程,核心参数全部外置到argu.py:
args.local_epochs = 5:每个客户端每轮联邦通信只训5 epoch,避免本地过拟合args.lr = 0.01:学习率设为0.01而非常规0.001,因NSL-KDD特征尺度差异大(duration范围0~58000,count仅0~511),需更高lr加速收敛args.clip_grad_norm = 1.0:梯度裁剪阈值设为1.0,防止Non-IID数据导致梯度爆炸(实测不裁剪时client2的loss在第3轮突增至inf)- 早停机制:
patience=3,连续3轮验证集acc不升则终止本轮本地训练,保存local_model.weight
# main_client_1.py 训练循环节选(含关键参数说明) def train_local_model(model, train_loader, val_loader, args): optimizer = torch.optim.SGD(model.parameters(), lr=args.lr) criterion = nn.CrossEntropyLoss() best_val_acc = 0.0 patience_counter = 0 for epoch in range(args.local_epochs): model.train() for data, target in train_loader: optimizer.zero_grad() output, _ = model(data) # 只用5分类头计算loss loss = criterion(output, target) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), args.clip_grad_norm) # 防梯度爆炸 optimizer.step() # 验证阶段 val_acc = validate(model, val_loader) if val_acc > best_val_acc: best_val_acc = val_acc torch.save(model.state_dict(), 'local_model.weight') # 仅保存最优模型 patience_counter = 0 else: patience_counter += 1 if patience_counter >= args.patience: break # 早停触发2.4 全局模型聚合:FedAvg实现与权重衰减策略
main_server.py中的aggregate_models()函数执行标准FedAvg,但增加了客户端贡献度加权:
- 权重计算公式:
weight_i = client_data_size_i / total_data_size(client_data_size_i来自initDate.py生成的client_stats.json) - 衰减策略:第
k轮聚合时,全局模型W^k=0.9 * W^{k-1} + 0.1 * Σ(weight_i * W_i^k),避免某客户端突发异常权重污染全局模型 - 验证逻辑:聚合后立即用
server_test_loader(NSL-KDD完整test集)评估,结果写入data.log,格式为[Round 5] Global Acc: 89.2%, Loss: 0.321
注意:
server.zip中main_server.py第89行global_model.load_state_dict(aggregated_state_dict)后,必须调用global_model.eval(),否则后续validate()会因BatchNorm层统计量未冻结导致acc虚高。
3. GUI界面交互与联邦训练监控:从启动到结果可视化的全流程控制
3.1 GUI主界面功能分区与事件绑定逻辑
GUI.py基于tkinter构建,采用ttk.Notebook实现多标签页:
- 首页(Start Page):三个按钮分别触发
start_server()、start_client1()、start_client2(),底层调用subprocess.Popen(['python', 'main_server.py'])启动进程 - 训练监控页(Training Monitor):嵌入
matplotlib画布,实时绘制data.log中解析出的Round、Global Acc、Client1 Acc、Client2 Acc四条曲线 - 结果对比页(Result Compare):显示
resultCompare1.png(各客户端本地测试混淆矩阵)和resultCompare2.png(全局模型vs各客户端模型在统一test集上的acc对比柱状图)
关键事件绑定在GUI.py第127行:
# GUI.py 事件绑定示例 self.start_btn = ttk.Button(self.start_frame, text="启动联邦训练", command=self.start_federated_training) self.start_btn.grid(row=0, column=0, padx=10, pady=5) def start_federated_training(self): # 启动server进程并监听端口 self.server_proc = subprocess.Popen(['python', 'main_server.py'], stdout=open('server.log', 'w'), stderr=subprocess.STDOUT) time.sleep(2) # 确保server已监听 # 并行启动两个client self.client1_proc = subprocess.Popen(['python', 'main_client_1.py']) self.client2_proc = subprocess.Popen(['python', 'main_client_2.py'])3.2 日志解析引擎:从data.log提取指标并驱动图表更新
GUI.py中parse_log_file()函数是监控页的核心,它持续读取data.log末尾新增行:
- 正则匹配模式:
r'\[Round (\d+)\] Global Acc: ([\d.]+)%, Loss: ([\d.]+)' - 每次匹配到新轮次,将
round_num、global_acc、client1_acc(从data.log中Client1 Acc:字段提取)存入self.round_history列表 - 图表更新逻辑:
self.ax.clear()→self.ax.plot(rounds, global_accs, label='Global')→self.canvas.draw()
提示:
data.log默认每轮写入3行(server一行,client1一行,client2一行),但parse_log_file()使用tail -f式轮询(while True: with open('data.log') as f: f.seek(0, 2); line = f.readline()),避免日志过大时全量读取卡顿。
3.3 结果对比图生成原理:Matplotlib动态绘图与PNG导出
resultCompare1.png由utils.py中plot_confusion_matrix()生成:
- 输入:各客户端
local_testModel.weight在NSL-KDD test集上的预测结果(y_true,y_pred) - 绘图:
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues'),x轴/y轴标注['normal','dos','probe','r2l','u2r'] - 导出:
plt.savefig('resultCompare1.png', dpi=300, bbox_inches='tight'),确保论文打印清晰
resultCompare2.png则用柱状图对比:
| 模型类型 | Client1 Acc | Client2 Acc | Global Acc |
|---|---|---|---|
| 本地模型 | 86.2% | 82.7% | — |
| 全局模型 | 89.5% | 89.5% | 89.5% |
# utils.py 中 resultCompare2.png 生成代码 def plot_accuracy_comparison(client1_acc, client2_acc, global_acc): models = ['Client1 Local', 'Client2 Local', 'Global Model'] accs = [client1_acc, client2_acc, global_acc] plt.figure(figsize=(8, 5)) bars = plt.bar(models, accs, color=['#1f77b4', '#ff7f0e', '#2ca02c']) plt.ylabel('Accuracy (%)') plt.title('Model Accuracy Comparison on NSL-KDD Test Set') plt.ylim(80, 95) # 在柱子顶部添加数值标签 for bar, acc in zip(bars, accs): plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.3, f'{acc:.1f}%', ha='center', va='bottom') plt.savefig('resultCompare2.png', dpi=300, bbox_inches='tight')3.4 进程管理与异常捕获:GUI如何优雅处理训练中断
GUI.py中stop_training()函数不仅发送kill信号,还执行清理:
- 终止server/client进程:
self.server_proc.terminate()→self.server_proc.wait(timeout=5) - 若超时未退出,则强制
kill:os.kill(self.server_proc.pid, signal.SIGKILL) - 清理临时文件:
os.remove('local_model.weight')、os.remove('Net.weight'),防止下次启动加载旧权重 - 异常捕获:
try...except subprocess.TimeoutExpired捕获进程僵死,弹窗提示“检测到进程无响应,请检查端口占用”
注意:Windows系统需在
subprocess.Popen中添加creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,否则terminate()无法杀死子进程树。
4. 联邦学习实战避坑指南:五个血泪经验总结(现象→原因→解决)
4.1 现象:客户端训练loss在第2轮突然飙升至inf,server日志显示RuntimeError: invalid gradient at index 0
原因:NSL-KDD数据中存在极少数duration=0且src_bytes=0的记录,经initDate.py标准化后产生nan值,nn.CrossEntropyLoss反向传播时梯度爆炸。
解决:在initDate.py数据清洗环节(第102行)增加df = df.replace([np.inf, -np.inf], np.nan).dropna(),并在main_client_*.py中train_loader创建前添加dataset = dataset[~dataset.isnull().any(axis=1)]。
4.2 现象:GUI监控页曲线停滞,data.log最新记录停留在[Round 3],但main_server.py进程仍在运行
原因:server与client间socket通信超时,默认connFun.py中socket.settimeout(30),当某客户端因CPU占用过高未及时响应,server等待30秒后断开连接,但未重试机制导致训练卡死。
解决:修改connFun.py第45行sock.settimeout(120),并在main_server.py的receive_from_client()函数中增加重试逻辑:for attempt in range(3): try: ... except socket.timeout: continue。
4.3 现象:resultCompare1.png混淆矩阵中r2l类召回率为0,但全局模型acc达89%,明显过拟合
原因:r2l类在NSL-KDD中仅占训练集0.12%,initDate.py切分时未做SMOTE过采样,导致client2分到的r2l样本不足5个,本地模型根本学不会该类别。
解决:在initDate.py切分后插入SMOTE模块(需pip install imblearn):
from imblearn.over_sampling import SMOTE smote = SMOTE(random_state=42, sampling_strategy={'r2l': 50}) # 强制r2l类扩增至50样本 X_res, y_res = smote.fit_resample(X_train, y_train)4.4 现象:main_client_1.py报错ModuleNotFoundError: No module named 'torch',但终端python -c "import torch"可成功
原因:GUI启动时subprocess.Popen未继承当前Python环境,而是调用系统默认python(可能为Python 2.7),而项目依赖PyTorch需Python 3.6+。
解决:在GUI.py中显式指定Python路径:
# 替换原subprocess调用 python_path = sys.executable # 获取当前运行GUI的Python解释器路径 self.client1_proc = subprocess.Popen([python_path, 'main_client_1.py'])4.5 现象:resultCompare2.png中Global Model柱子高度低于Client1 Local,违背联邦学习“全局优于局部”常识
原因:main_server.py中validate()函数误用train_loader而非test_loader计算全局acc,导致用训练集评估——这在Non-IID场景下必然虚高。
解决:检查main_server.py第156行val_loader = get_test_loader(),确认get_test_loader()返回的是NSL-KDD完整test集,而非某个客户端的train数据。
5. 模型性能深度验证:用NSL-KDD官方评估指标替代accuracy单一指标
5.1 为什么accuracy在入侵检测中具有欺骗性?
NSL-KDD中normal样本占比78.5%,即使模型把所有样本全判为normal,accuracy也能达到78.5%。而实际安全需求关注的是漏报率(False Negative Rate)——把dos攻击判为normal,后果远比把normal判为dos严重。本项目在utils.py中内置了NSL-KDD官方推荐的四大指标计算:
- Detection Rate (DR)= TP / (TP + FN)
- False Alarm Rate (FAR)= FP / (FP + TN)
- Accuracy= (TP + TN) / (TP + TN + FP + FN)
- Precision= TP / (TP + FP)
其中TP/TN/FP/FN按攻击大类统计:normal为负样本,其余四类(dos/probe/r2l/u2r)合并为正样本。
5.2 四类攻击的DR/FAR分离计算与可视化
utils.py中calculate_detailed_metrics()函数将混淆矩阵按行展开:
| 真实标签 \ 预测 | normal | dos | probe | r2l | u2r |
|---|---|---|---|---|---|
| normal | TN | FP1 | FP2 | FP3 | FP4 |
| dos | FN1 | TP1 | 0 | 0 | 0 |
| probe | FN2 | 0 | TP2 | 0 | 0 |
| r2l | FN3 | 0 | 0 | TP3 | 0 |
| u2r | FN4 | 0 | 0 | 0 | TP4 |
然后逐类计算:
dos_DR = TP1 / (TP1 + FN1)dos_FAR = (FP1 + FP2 + FP3 + FP4) / (FP1 + FP2 + FP3 + FP4 + TN)- 其余类同理
# utils.py 指标计算核心逻辑 def calculate_detailed_metrics(y_true, y_pred): cm = confusion_matrix(y_true, y_pred) # cm[i][j] = 真实i类被预测为j类的样本数 metrics = {} for i, attack in enumerate(['normal', 'dos', 'probe', 'r2l', 'u2r']): if i == 0: # normal类作为负样本 tn = cm[i][i] fp = cm[i].sum() - tn fn = cm[:, i].sum() - tn tp = cm.sum() - (tn + fp + fn) else: # 攻击类作为正样本 tp = cm[i][i] fn = cm[i].sum() - tp fp = cm[:, i].sum() - tp tn = cm.sum() - (tp + fn + fp) metrics[attack] = { 'DR': tp / (tp + fn) if (tp + fn) > 0 else 0, 'FAR': fp / (fp + tn) if (fp + tn) > 0 else 0 } return metrics5.3 联邦学习特有的灾难性遗忘验证方法
联邦学习中,客户端每轮只用本地数据微调,可能导致对其他攻击类别的识别能力退化。本项目通过跨轮次保留测试集验证:
- 在
initDate.py中预留10%原始NSL-KDD test样本作为cross_round_test.csv - 每轮联邦训练结束后,用
global_model和client1_model分别在该保留集上运行calculate_detailed_metrics() - 结果写入
cross_round_log.txt,格式为:Round 1: Client1 dos_DR=0.82, Global dos_DR=0.79Round 5: Client1 dos_DR=0.75, Global dos_DR=0.85
→ 发现Client1对dos的DR下降7%,而Global上升6%,证明联邦聚合有效缓解了客户端灾难性遗忘
提示:
cross_round_test.csv必须在所有客户端切分前生成,否则Non-IID切分会导致保留集分布失真。initDate.py第188行cross_test_df = full_test_df.sample(frac=0.1, random_state=42)即为此目的。
5.4 从GUI一键导出符合论文要求的评估报告
GUI.py中“Export Report”按钮触发generate_academic_report()函数,自动生成LaTeX兼容的Markdown表格:
| 指标 | Client1 Local | Client2 Local | Global Model |
|---|---|---|---|
| dos DR | 82.3% | 76.1% | 85.7% |
| r2l DR | 41.2% | 52.8% | 48.3% |
| FAR | 12.4% | 15.6% | 9.8% |
| Accuracy | 86.2% | 82.7% | 89.5% |
该表格直接复制进论文Results章节,无需手动调整。导出的report.md还包含resultCompare1.png和resultCompare2.png的base64内联引用,确保离线提交时图片不丢失。
从那以后我每次部署联邦学习项目,都强制走一遍cross_round_test.csv的跨轮次验证——哪怕多花2小时,也要确认全局模型没有在悄悄遗忘某个攻击类型。毕竟在安全领域,一次漏报的成本,远高于一百次误报。希望帮到你。
本文还有配套的精品资源,点击获取