基于Python机器学习的网络入侵检测:NSL-KDD预处理与实时部署
简介这是一套基于 Python 机器学习的网络入侵检测系统源码适用于高校信息安全、网络工程、计算机等相关专业的课程设计与期末大作业。项目在导师指导下完成并获得97分完整包含数据加载、特征处理、模型构建、训练评估等环节基于 KDD Cup 1999 数据集完成入侵行为识别下载后无需修改即可直接运行。整个压缩包共16个文件大小约17.52MB核心为4个Python脚本同时配有XML工程配置、Markdown说明文档、数据压缩包、TensorFlow事件日志及备份文件目录结构清晰方便按模块阅读与复现。目前已有946人浏览学习。除CNN模型主程序外项目还包括多种训练逻辑、辅助处理脚本和运行记录有助于理解机器学习在网络流量分类与异常检测中的工程实现也为论文写作、课设答辩或后续改进提供了可参考的完整基线。1. 期末大作业里的网络入侵检测系统源码能跑只是起点答辩才是终点期末前一周很多人会在搜索框里敲基于python机器学习的网络入侵检测系统源码下载一个打包好的 zip。解压之后结构挺全NSL-KDD 数据集、预处理脚本、训练代码、可视化界面。但真正的问题从来不是代码能不能跑而是答辩时你能不能把每条链路讲清楚。这套源码背后是一条完整的技术链路流量数据怎么变成特征矩阵机器学习模型怎么区分正常与攻击流量训练好的模型又怎么部署成实时抓包检测系统。下面按这条链路走一遍把数据预处理、模型选型、在线部署和期末作业的常见坑拆开讲。适合正在做课程设计或期末大作业的学生也适合想快速搭一个入侵检测原型验证想法的工程师。每一步的参数和踩坑点都会写清楚可以直接照着复现。2. 网络入侵检测的数据地基NSL-KDD 的标签体系与预处理细节2.1 为什么课程设计的默认数据集是 NSL-KDD网络入侵检测这个方向数据集的地位比模型还高。真实企业流量不可能公开能拿到的公开基准数据集里NSL-KDD 是课程设计事实上的默认选择。它是 KDDCUP99 的改进版原始 KDDCUP99 里有大量重复记录某些攻击样本能重复几十次模型只要记住这些重复样本就能刷出高准确率完全没有泛化意义。NSL-KDD 去掉了重复训练集 KDDTrain 约 12.5 万条记录测试集 KDDTest 约 2.2 万条并且测试集特意包含训练集没出现过的攻击变体评估结果更接近真实场景。NSL-KDD 的标签是五分类normal 加上四类攻击——DoS拒绝服务如 neptune、smurf、Probe端口扫描与探测如 satan、ipsweep、R2L远程到本地的非法访问如 guess_passwd、warezmaster、U2R本地提权如 buffer_overflow、rootkit。四类攻击的样本量极不均衡这是数据集刻意保留的真实分布类别KDDTrain约KDDTest约典型攻击normal673439711—DoS459277458neptune、smurf、podProbe116562421satan、ipsweep、portsweepR2L9952887guess_passwd、warezmasterU2R5267buffer_overflow、rootkit注意看倒数两行R2L 和 U2R 在训练集里的样本量只有 995 和 52而测试集里 R2L 反而涨到 2887。这意味着哪怕你把所有流量都判成 normal准确率也不会太难看但这两类攻击基本是漏光的。这个不均衡是后面模型评估最大的坑第 5 章会专门讲。每条记录有 41 维特征这是 KDD 99 时代定下的固定格式。41 维大致分三组。第一组是 TCP 连接基本属性duration、protocol_type、service、flag、src_bytes、dst_bytes 等描述这个连接是谁连谁、用了什么协议、传了多少字节。第二组是连接内容特征hot、num_failed_logins、logged_in、root_shell 等这些是从包体内容和登录行为里提取的领域知识特征对 R2L 和 U2R 这类攻击尤其重要。第三组是基于时间窗的流量统计特征count、srv_count、serror_rate、same_srv_rate、dst_host_count 等描述过去 2 秒窗口内或者同一目标主机上的连接行为特征。第三组对检测 DoS 和 Probe 这类扫描型攻击最有效但也是初学者最容易忽略的——因为它们是窗口统计量不是单个连接能算出来的。到第 4 章做实时抓包部署时这一组特征会让不少人翻车。2.2 四类攻击标签与 41 维特征的对应关系41 维里大部分是数值型但有三列是类别型protocol_type 只有 3 个取值tcp、udp、icmpservice 有 70 个取值http、ftp、telnet、private 等flag 有 11 个取值SF、REJ、S0 等。这三列不能直接喂给 sklearn 的模型必须做 One-Hot 编码。编码后的维度变化要心里有数protocol_type 变 3 列service 变 70 列flag 变 11 列加上原 38 列数值型总维度在 122 左右。这里有个最容易踩的坑KDDTest 里会出现训练集没见过的 service 值。如果训练脚本和测试脚本各 fit 一次 OneHotEncoder两边列数就会对不齐predict 时直接报维度错误。正确做法是只对训练集 fit 编码器测试集用同一个编码器 transform。这也是很多源码包在训练集上跑分高、一交作业就被老师用测试集打穿的根本原因。提示service 列 70 个取值里很多只在测试集出现。用 handle_unknownignore 让编码器对未知取值输出全零向量而不是抛异常这是预处理第一课。2.3 预处理代码One-Hot 编码与训练测试特征对齐下面这段预处理是整个系统里最值得抄的代码它把 NSL-KDD 原始文件变成可以直接喂给机器学习模型的矩阵。整段代码只做一件事保证训练集和测试集走同一条编码路径。import numpy as np import pandas as pd from sklearn.preprocessing import OneHotEncoder FEATURE_COLS [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_nslkdd(path): df pd.read_csv(path, headerNone) df.columns FEATURE_COLS [label, difficulty] return df train_df load_nslkdd(data/KDDTrain.txt) test_df load_nslkdd(data/KDDTest.txt) cat_cols [protocol_type, service, flag] ohe OneHotEncoder(handle_unknownignore, sparse_outputFalse) ohe.fit(train_df[cat_cols]) # 只 fit 训练集 def encode(df): cat_part ohe.transform(df[cat_cols]) num_part df.drop(columnscat_cols [label, difficulty]).to_numpy() return np.hstack([num_part, cat_part]) X_train encode(train_df) X_test encode(test_df) y_train (train_df[label].str.lower() ! normal).astype(int) y_test (test_df[label].str.lower() ! normal).astype(int)三个参数值得单独说明。第一handle_unknownignore是必须项没有它测试集里出现未知 service 值时 transform 直接抛异常。第二sparse_outputFalse把 One-Hot 结果转成稠密数组才能和数值特征hstack注意老版本 sklearn 的参数名是sparse你环境里的版本如果较老需要换成对应写法。第三数值列这里故意没有做标准化——因为后面要选随机森林树模型按特征值切分不要求同量纲。但如果你打算对比 SVM 或 KNN就必须加 StandardScaler否则距离计算会被 src_bytes 这种取值上亿的大数值列主导。预处理完训练矩阵大约 122 列。接下来进入模型选型和训练这也是源码包里最该自己重新写一遍的部分。3. 用 Python 训练机器学习检测模型随机森林做基线评估不说谎3.1 模型选型随机森林为什么是性价比最高的基线在 sklearn 这套机器学习体系里入侵检测常用的模型其实就那几种决策树、随机森林、KNN、朴素贝叶斯、SVM、逻辑回归。我一般建议随机森林先跑理由有三个。第一随机森林对特征缩放不敏感。前面提到的 41 维特征里src_bytes 取值范围可以从 0 到上亿duration 从 0 到几千树模型按特征值切分天然不需要标准化直接省掉一步预处理和对应的踩坑点。第二随机森林是多棵决策树投票的结果抗过拟合能力比单棵决策树强得多在 12 万条训练样本上不需要复杂的调参就能有不错的表现期末一周时间完全够用。第三随机森林自带feature_importances_可以直接输出各特征对判断攻击的贡献度答辩时老师问为什么选这些特征你直接画特征重要性图回答比空口解释有说服力得多。相比之下KNN 在入侵检测上有两个硬伤特征到上百维之后距离度量意义明显变弱推理时又要遍历全部训练样本在线检测场景下延迟完全不可控。SVM 在 12 万样本上训练要等很久多分类还得做 OvR 拆分期末节奏下大概率等不起。如果源码包里同时给了多个模型做对比正确用法是让随机森林当基线另一个模型选逻辑回归或决策树用来论证集成方法为什么更好而不是去硬跑一个 KNN 打分对比。3.2 训练流程交叉验证、网格搜索与参数说明拿到源码包不要直接跑一遍 train.py 就完事。源码里参数往往是写死的照抄跑出来的指标不构成你的工作量。把训练流程改成交叉验证评估 小范围网格搜索指标和过程都能写进报告。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold from sklearn.metrics import classification_report # 先按类别比例切分,保证验证集攻击分布和训练集一致 X_tr, X_val, y_tr, y_val train_test_split( X_train, y_train, test_size0.2, stratifyy_train, random_state42 ) param_grid { n_estimators: [100, 200], max_depth: [10, 20, None], min_samples_leaf: [1, 2, 4], } rf RandomForestClassifier(n_jobs-1, random_state42) cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) search GridSearchCV(rf, param_grid, cvcv, scoringf1, n_jobs-1, verbose1) search.fit(X_tr, y_tr) print(best params:, search.best_params_) print(classification_report(y_val, search.predict(X_val)))这里几个参数的设置是故意的。stratifyy_train保证切分后各类攻击的比例和全量一致否则随机切分可能让验证集里某一类攻击恰好很少评估结果失真。scoringf1而不是默认的 accuracy因为入侵检测数据集天然不平衡准确率会被占多数的 normal 和 DoS 带偏。n_estimators从 100 搜到 200 就够超过 200 在 12 万样本上边际收益很小训练时间却线性增长期末作业没必要追求极致。网格搜索跑完后把best_params_记进报告再用全量训练数据重训一个最终模型在 KDDTest 上做最终评估。网格搜索阶段用的验证集本来就从训练集里切出来的最终模型用全量重训是标准做法能稍微提升一点泛化。3.3 评估指标二分类看 F1多分类看每一类的 recall期末报告里最常见的错误是只贴一个 accuracy训练集 99%测试集 91%模型很好。这个结论在入侵检测里不成立。前面说了KDDTest 里 normal 和 DoS 占绝大多数R2L 和 U2R 极少一个永远预测 normal的模型准确率也不会太难看。老师只要追问一句U2R 的检出率是多少答案就露馅了。评估要分两层。二分类层面normal vs attack综合看 precision、recall 和 F1多分类层面单独输出每一类的 recall。下面这段代码直接打印你要的指标from sklearn.metrics import confusion_matrix, f1_score, recall_score y_pred search.predict(X_val) print(F1:, f1_score(y_val, y_pred)) print(Recall:, recall_score(y_val, y_pred)) cm confusion_matrix(y_val, y_pred) # 行是真实类别,列是预测类别 print(cm)入侵检测里 precision 和 recall 的取舍是有业务含义的。把正常流量误判成攻击precision 下降后果是误报——运维人员会被大量告警淹没最终选择无视告警把攻击漏掉recall 下降后果是真实入侵无人发现。报告里建议两个指标都贴并说明最终选模型时对阈值做了误报与漏报的权衡而不是盲目追高 accuracy。4. 把离线模型部署成在线检测系统Scapy 抓包、特征对齐与 Flask 推理4.1 在线检测系统的两种常见架构源码包里的系统一般分两部分离线训练和在线检测。离线部分是前两章讲的训练流程输出一个模型文件在线部分是一个能现场演示的东西——打开界面抓取网卡实时流量对每个连接预测一次把结果实时展示。常见实现有两种Flask Web 前端或者 Tkinter 桌面窗口。Tkinter 更省事单机演示够用Flask 更好讲因为你可以说这是一个可扩展的检测服务任何客户端都能 POST 特征进来拿检测结果。我一般建议用自己的作业选 Flask接口逻辑能单独测试答辩演示时浏览器比桌面窗口稳定而且服务化部署这个词写在报告里明显比画了个窗口专业。项目目录也按这个思路组织nids-project/ ├── data/ # KDDTrain.txt / KDDTest.txt ├── preprocessing.py # 数据清洗、One-Hot 编码 ├── train.py # 网格搜索 模型保存 ├── feature_order.json # 训练时的特征列顺序,在线推理必须读取 ├── capture.py # Scapy 抓包与特征拼接 ├── predict_api.py # Flask 推理服务 ├── models/ # 模型文件与编码器 └── requirements.txtfeature_order.json是这套结构里最容易漏的一环第 5 章会专门讲为什么它能把人坑到怀疑人生。4.2 Scapy 实时抓包单包特征好取统计特征难算在线抓包Python 生态里最常用的是 Scapy。sniff抓包IP、TCP层对象解析协议字段几行就能拿出一个连接的源端口、目的端口、标志位from scapy.all import sniff, IP, TCP def handle_packet(pkt): if IP in pkt and TCP in pkt: src pkt[IP].src dst pkt[IP].dst sport pkt[TCP].sport dport pkt[TCP].dport # 把字段按 feature_order.json 拼进特征向量,交给模型推理 sniff(prnhandle_packet, count100) # 先抓 100 个包验证链路但这里会立刻撞上第 2 章埋的坑41 维里有 count、srv_count、serror_rate、same_srv_rate 这一组基于时间窗的统计特征。单看一个包你根本算不出过去 2 秒内到同一目标主机的连接数。所以在线模块几乎不可能把 41 维全对齐常见的可靠做法有两条。第一条是降维对齐只取单包可提取的字段比如 protocol_type、service、flag、src_bytes、dst_bytes、logged_in 等 15 到 20 维用这些字段重新训练一个简化模型。最不容易翻车特征来源清晰每一步能解释。第二条是窗口维护用一个字典按 2 秒窗口统计每个目标 IP 的连接数和错误包比例动态更新 count、serror_rate 这些统计特征。效果更接近完整 41 维但代码复杂度高一个量级而且窗口边界怎么对齐 NSL-KDD 的定义本身说不清。期末作业我建议走第一条在线简化模型和离线完整模型分开报告说明实时性换取了部分特征维度。提示在线演示建议在本机回环接口或自己搭的实验环境抓包不要直接在共享网络里长时间抓取他人流量。Scapy 抓包需要管理员权限Windows 上还要装 Npcap这些环境细节提前配好别等答辩现场才装。4.3 Flask 推理服务与告警输出在线模块的推理服务用 Flask 串起来很干净。模型用 joblib 保存加载抓包线程把特征拼成 JSON POST 过来服务返回预测概率。代码的关键点是特征维度校验和概率阈值import joblib import json from flask import Flask, request, jsonify app Flask(__name__) model joblib.load(models/rf_binary.joblib) feature_order json.load(open(feature_order.json, encodingutf-8)) app.route(/predict, methods[POST]) def predict(): data request.get_json() feat data[features] if len(feat) ! len(feature_order): return jsonify({error: feature length mismatch}), 400 prob model.predict_proba([feat])[0][1] pred int(prob 0.7) # 阈值抬高,降低误报 return jsonify({prediction: pred, attack_prob: round(prob, 4)}) if __name__ __main__: app.run(host0.0.0.0, port5000)predict_proba返回概率而不是硬分类这是刻意保留的把判定阈值从 0.5 抬到 0.7会牺牲少量 recall 但显著降低误报。答辩时可以现场演示阈值变化对结果的影响这是一个现成的加分点。len(feat)的校验也不是废话——在线抓包特征拼接最容易字段错位先做长度检查让错误早点暴露而不是让模型在错位上默默输出。5. 期末大作业避坑指南特征错位、数据泄漏与演示翻车现场5.1 训练集评估 98%一换 KDDTest 就报维度错误现象在 KDDTrain 上训练自己切出来的验证集 F1 有 0.98换到 KDDTest 评估时直接抛ValueError: X has 119 features, but RandomForestClassifier is expecting 122 features。原因训练和测试分别对 service 列做了 One-Hot 编码测试集里有训练集没见过的 service 取值两个编码器各自 fit 后列数不一致。这是 NSL-KDD 源码包最常见的第一道坎很多网上下载的源码就在这一步崩。解决只 fit 一个 OneHotEncoder测试集用同一个 encoder transformhandle_unknownignore。第 2.3 节的代码就是按这个标准写的直接照抄不会错。5.2 准确率虚高归一化在切分前做数据泄漏了现象随机森林没怎么调参准确率直接 99.6%但看混淆矩阵发现 U2R 一条都没识别出来整个模型对少数类攻击完全失效。原因两个问题叠加。一是数据泄漏——StandardScaler在 train_test_split 之前对整个数据集 fit验证集的均值和方差提前进入了训练流程评估结果虚高二是不平衡类别被 accuracy 掩盖。这个99.6%在答辩时是致命的老师会直接问少数类检出率。解决先train_test_split再在训练集上fit缩放器评估指标换成 F1 和各类别 recall把混淆矩阵贴进报告。如果选了树模型直接不做标准化从源头避开这个坑。5.3 在线检测只报 normal特征列顺序对不上现象离线训练 F1 0.94部署成 Flask 服务后POST 任何流量特征都返回 normal换测试数据也一样。原因训练脚本里特征列顺序是 A 顺序在线抓包脚本按 B 顺序拼接特征。维度一致模型不报错但每一列的实际含义全变了树模型的切分点全部落在错误的特征上结果自然全部偏向多数类。解决把训练时的特征列清单导出成 JSON在线推理启动时强制读取并按它拼接import json with open(feature_order.json, w, encodingutf-8) as f: json.dump(train_feature_cols, f)这是在线部署里最值钱的一条经验维度对齐只是底线顺序对齐才是真正决定模型是否生效的关键。我处理过的所有在线结果全错案例十有八九是顺序问题不是模型问题。5.4 答辩前模型文件打不开序列化版本不一致现象在自己电脑上joblib.load(rf_binary.joblib)一切正常拷到答辩机器上要么抛异常要么加载成功但predict行为明显不对。原因joblib 保存的是 pickle 字节流sklearn 版本、Python 版本、甚至 numpy 版本不匹配都会反序列化失败。源码包里的模型文件几乎都是作者本机环境导出的跨机器复用本质上就是一种玄学。解决两手都做。第一把 requirements.txt 写死版本比如scikit-learn1.3.0、numpy1.24.3答辩机器按它装环境第二演示机器上重新跑一遍完整训练脚本直接现场生成模型不依赖拷贝的模型文件。我习惯两个方案都准备哪个能用用哪个。5.5 依赖没固定版本现场装环境装到翻车现象答辩现场pip install scikit-learn装出来一个很新的版本代码里sparse_output这类参数行为不一致预处理阶段直接报错。原因requirements.txt 里裸写scikit-learn不固定版本环境差异把所有存量代码打回原形。sklearn 从 1.2 到 1.6不少 API 参数行为都在变一次升级就能让原本能跑的代码当场报废。解决requirements.txt 全部写成包名版本号并且把安装命令写进 README 的 Quick Start 里。演示前在自己的备用虚拟环境里按 requirements 从零装一遍模拟答辩环境这是期末周最值得花的一个小时。6. 从「跑得通」到「拿高分」特征重要性与混淆矩阵两个必做实验6.1 特征重要性分析答辩提问的提前量拿到源码先别急着改架构。第一个必做实验是特征重要性分析。随机森林的feature_importances_直接输出每个特征的贡献度画成柱状图前 10 名一目了然import pandas as pd import matplotlib.pyplot as plt importance pd.Series( rf.feature_importances_, indextrain_feature_cols # 数值列名 ohe.get_feature_names_out() 拼出来的完整列名 ).sort_values(ascendingFalse).head(10) importance.plot.barh() plt.title(Top-10 Features for Intrusion Detection) plt.tight_layout() plt.savefig(figs/feature_importance.png, dpi150)老师的第一个问题大概率是为什么选这些特征。有这张图你能结合 NSL-KDD 的特征定义解释比如dst_host_srv_count排前面说明同目标主机同服务的连接密度对识别扫描型攻击贡献大src_bytes排前面说明大流量单向连接往往对应攻击行为。有图有解释问题就接住了。6.2 多类混淆矩阵把数据集短板主动讲出来第二个必做实验是把模型从二分类扩展成五分类跑一遍混淆矩阵热力图。重点不是追求五分类的分数而是主动暴露并解释 R2L 和 U2R 的 recall 低训练集里 U2R 只有 52 条任何模型都很难从这个样本量里学到稳定模式。这个问题你主动讲比老师质疑时再辩解要加分得多——说明你理解数据集的局限而不是只会跑分。做完这两个实验报告结构就顺理成章了数据预处理与特征分析、基线模型与评估、多模型对比、在线部署与阈值讨论、局限与改进方向。每一章都能对应到代码和图表而不是抄来的空话。我自己的习惯是拿到任何源码包第一件事永远不是双击运行而是先打开数据目录和预处理脚本确认特征顺序是怎么定义的再决定训练和部署方案怎么写。这套先对特征、再跑模型、最后部署的顺序是我在这个方向踩过最多坑之后固定下来的流程。希望这份拆解对你的期末大作业有帮助祝答辩顺利也希望你在复现时少走几步冤枉路。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →