☰
基于Python机器学习的网络入侵检测系统实战:从规则到智能
2026/9/28 16:28:51 网站建设 项目流程

简介:基于python机器学习的网络入侵检测系统源码包,面向毕业设计、课程设计及网络与信息安全方向学习者,针对网络流量数据分类这一典型场景,提供从数据预处理、特征工程到模型训练与评估的完整代码流程。包内共16个文件,核心为4个Python脚本,包括主程序、CNN模型文件及辅助处理模块,搭配所需GZ压缩格式的KDD Cup数据集、XML工程配置文件、Markdown说明文档以及TensorBoard训练日志,可清晰还原项目结构并便于按模块学习调优。压缩包整体约17.52MB,轻量且易于部署,源码均为本地编译可运行的版本,难度适中,经测试正确率可达99.5%,有较高的实战参考价值。目前已有196人学习下载,适合正在完成相关课题或希望快速入门机器学习入侵检测开发的读者使用。

1. 从“高分项目”到能落地的入侵检测:这套 Python 机器学习方案到底在做什么

大部分人解压一份名为「基于 Python 机器学习的网络入侵检测系统源码(高分项目).zip」的压缩包时,心里其实没底:里面是一堆训练脚本,还是一个能实时报警的系统?我用这类项目做过课程设计,也帮别人审过代码,先把结论说清楚——这份源码的核心任务只有三件事:读入网络流量数据集,用机器学习训练一个分类模型,再用这个模型判断一条新流量是不是攻击。

如果目标只是应付验收,跑通一行训练命令就够了;如果想让它在真实网络环境里派上用场,你要补上特征对齐、类别不平衡和部署这一整段。这篇笔记冲着后面这个目标去,适合有 python 基础、想通过一个完整项目把机器学习落地流程走一遍的人。暂时不熟悉网络安全也没关系——所谓网络入侵检测系统源码,核心其实是机器学习那套标准流程在表格数据上的又一次应用,把这条线捋顺,整套代码的脉络就清楚了。

2. 先把检测目标说清楚:网络入侵检测在检测什么,为什么非用机器学习不可

在开始训练之前,最好先回答一个问题:你手里这套系统到底在“学”什么。如果不把这一点想清楚,后面所有的调参都是在瞎试。

2.1 传统检测的命门:规则匹配为什么在真实流量里翻车

最早的网络入侵检测系统基本都是规则驱动。原理很简单:抓一段报文,解出协议头,拿它和规则库里的签名比对,命中“SQL 注入特征串”就告警,命中“木马上线特征流量”就告警。这种方案在早年非常管用,因为攻击手法相对固定,特征码可以做得很精准。

但它有两道过不去的坎。第一,规则库只能覆盖已知攻击,攻击者改一个端口、加一层编码、把 payload 做一次简单变形,签名立刻失效。第二,加密流量越来越多,规则匹配根本看不到明文内容,面对 TLS 流量基本等于直接放弃。真实场景里,安全团队最头疼的不是一眼就能认出来的扫描流量,而是未知变种和加密通道——这两类恰好都是规则匹配的盲区。

机器学习切入的方式完全不同。它不去学攻击的“签名”,而是去学正常流量和攻击流量在统计特征上的“行为分布”。比如一个模型见过足够多的端口扫描样本之后,它能学会“短时间大量不同目的端口”这种模式,换一个源端口依然能认出来。这就是从“精确匹配”到“概率判断”的转变,也是为什么现在的 NIDS 研究方向基本都走机器学习这条路。

放在压缩包里,就是源码里的核心逻辑:每一条流量记录被转换成一组数值特征,模型拿到这组特征输出一个 0 到 1 之间的攻击概率。你不需要理解每一行网络报文,只需要理解机器学习在这个流程里的位置。

2.2 二分类还是多分类:决定你后面的模型和评估方式

公开的入侵检测数据集通常提供两种标签体系。二分类标签只有“正常”和“攻击”;多分类标签会把攻击细分,常见的有 DoS、Probe(探测)、R2L(远程到本地)、U2R(本地提权)这几类。复现源码前,先确认它做的是哪一种。

二分类的核心问题是“这条流量有没有问题”,在公开数据集上做到 99% 的准确率并不难,难的是在保持低误报率的前提下逮住少数攻击。多分类要回答“是哪类攻击”,难得多,因为类别分布极不平衡:NSL-KDD 里 DoS 样本动辄几万条,而 U2R 可能只有几十条,绝大多数模型直接放弃少数类。

评估方式也不一样。二分类不能只看 accuracy,正常流量占 80% 以上的数据集里,模型全部预测“正常”也能拿 80% 准确率,所以要看精确率、召回率和 F1,顺带看混淆矩阵。多分类不能只看整体正确率,要按类别看 F1,尤其 U2R 这种样本量极小的类别,F1 为 0 是常见现象。

把这件事想清楚的最大好处是:你能判断源码里哪些指标值得追,哪些只是虚高。很多“高分项目”为了数字好看,只输出二分类概率,并且用 accuracy 作为唯一指标,这在答辩时很容易被问倒。

2.3 数据集选型:NSL-KDD、CICIDS2017、UNSW-NB15 怎么选

拿到压缩包后第一件事不是立刻跑代码,而是确认它用的是哪个数据集。三个最常出现的数据集各有脾气,直接决定你要不要做特征工程、如何处理标签。

数据集样本量级特征数标签形式适合场景
NSL-KDD约 12 万41二分类 + 多分类课程设计、入门首选
UNSW-NB15约 25 万约 45+label + attack_cat偏研究、特征覆盖更全
CICIDS2017约 280 万80+二分类 + 14 类攻击更接近真实网络,但机器要求高

判断方法很简单:打开 data 目录下的 csv 看列名。NSL-KDD 的特征列多为连续编号,最后是标签列,部分版本还带难度系数列;UNSW-NB15 的特征列名是英文缩写,比如 state、sbytes、dbytes;CICIDS2017 的特征列名带 Flow Duration、Total Fwd Packets 这种描述性写法。如果特征是 41 个数值列,那基本可以确定是 NSL-KDD 系列。

选型思路给你一个参考:如果是 16G 内存以内的电脑,NSL-KDD 最稳;想显示自己处理过更新数据,用 UNSW-NB15;除非内存和训练时间都充裕,否则别在课设阶段碰 CICIDS2017 全量版本。

这里顺带把机器学习应用流程的路径点一下:采集环节已经被公开数据集替代,你要走的是预处理、特征工程、模型训练、评估、部署。接下来的章节按这条流程逐段展开。

3. 用 Python 跑通最小可运行版本:从数据集到一条可预测的命令

这一章的目标不是调优,而是先让整条链路转起来。标志是:读数据、预处理、训练、保存模型、再用保存的模型对一条新样本做预测。每个环节都有固定坑位,我按最省事的路径走。

3.1 先把环境钉死:python 版本、依赖和目录结构

如果你还没有 Python 环境,先花十分钟照着 python 安装教程 装好 3.8 以上的解释器,装完顺手验证 pip 可用。版本上建议 3.8 到 3.10,太新的版本偶尔会遇到旧代码里 distutils 模块缺失的问题,尤其是一些课程设计代码基于老环境写的。

不管你是用 PyCharm 还是 VSCode 来配置 Python 环境,本质都是给项目指定一个解释器,然后装依赖。这个项目需要的依赖不多,核心是 pandas、numpy、scikit-learn、joblib;后面调不平衡和部署时还会用到 imbalanced-learn、pyshark、flask。

pip install pandas numpy scikit-learn joblib imbalanced-learn pyshark flask

版本说明:scikit-learn 装 1.x 即可,不要纠结小版本;遇到 C 扩展编译问题,优先换 Python 3.9,而不是继续跟编译器搏斗。装完后把源码目录整理成下面这样再动手,能省掉大量“文件找不到”的麻烦:

project/ ├── data/ # 放训练用的 csv ├── models/ # 训练完的模型和标准化器 ├── src/ │ ├── preprocess.py │ ├── train.py │ └── predict.py └── requirements.txt

目录结构不是形式主义。数据、模型、代码分开,后面做实验对比时你才清楚自己改了什么、改完的效果是什么。我见过太多课设代码把模型保存和训练脚本全塞在一个文件里,跑完一次第二天就忘了模型在哪。

3.2 数据预处理:数值化、标准化与标签编码

拿到数据先别急着喂给模型。公开数据集里几乎总有非数值列,最常见的是协议类型(tcp、udp、icmp)这类字符串。机器学习模型只吃数字,所以第一步是把字符列转成数值。第二步是标准化,因为字节数、时长这类特征量纲差距巨大,不缩放会让某些算法直接失效。第三步很关键:必须先切分训练集和测试集,再做标准化,否则会发生数据泄露。

import pandas as pd from sklearn.preprocessing import LabelEncoder, StandardScaler from sklearn.model_selection import train_test_split df = pd.read_csv("data/kdd_train.csv") # 标签列名按数据集实际情况调整,比如 class / attack_cat # 二分类:normal 映射为 0,其他攻击类型全部映射为 1 X = df.drop(columns=["label"]) y = df["label"].map(lambda s: 0 if s == "normal" else 1) # 字符型特征(协议类型等)用 LabelEncoder 转成数值 cat_cols = X.select_dtypes(include=["object"]).columns for col in cat_cols: X[col] = LabelEncoder().fit_transform(X[col]) # stratify 保证训练集和测试集里正负样本比例一致 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # 标准化:只在训练集上 fit,测试集只做 transform scaler = StandardScaler() X_train = scaler.fit_transform(X_train) X_test = scaler.transform(X_test)

这里有两个参数必须解释清楚。第一个是 stratify=y,它按标签比例抽样,避免随机切分把少数类攻击样本全分到测试集去;如果没有它,训练集里可能连一条 U2R 样本都没有。第二个是 StandardScaler 的 fit 与 transform 分离,scaler 只在训练集上学习均值和方差,测试集直接使用同一套参数。如果你对全量数据先标准化再切分,测试集的信息已经渗进训练过程,评估出来的分数会虚高,这个坑在第五章还会细说。

3.3 训练一个随机森林基线模型并保存

预处理完就可以跑第一个模型了。选随机森林而不是别的,是因为它在表格数据上几乎不需要调参就能出不错的基线,自带特征重要性,能抗一点噪声,而且训练时间可控。用这个模型跑通全流程,后续换更强模型时,你有一个明确的分数参照。

from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report import joblib model = RandomForestClassifier( n_estimators=200, max_depth=None, min_samples_leaf=1, n_jobs=-1, random_state=42 ) model.fit(X_train, y_train) y_pred = model.predict(X_test) print(classification_report(y_test, y_pred, target_names=["normal", "attack"])) joblib.dump(model, "models/rf_baseline.joblib") joblib.dump(scaler, "models/scaler.joblib")

参数按这个逻辑设。n_estimators=200 是树的数量,太少模型不稳定,太多训练变慢,200 在大多数八核机器上只要几十秒。max_depth=None 表示让树充分生长,配合 min_samples_leaf 控制叶子最小样本数,防止过拟合。n_jobs=-1 用满所有 CPU 核心。random_state=42 固定随机种子,保证两次训练结果可复现——答辩时导师问“为什么每次结果不一样”会很尴尬。

保存模型用 joblib 而不是 pickle,因为 joblib 对大对象序列化更高效,而且与 scikit-learn 官方文档一致,遇到版本兼容问题时排查路径更短。scaler 也要一起保存,预测阶段没有它,前端的标准化步骤就断了。

3.4 用保存的模型对新样本做单一预测

训练完成只是上半场,入侵检测系统的最终形态是“给一条流量,给出判断”。这一步的关键是让新样本走完和训练数据完全相同的预处理路径:同样的字符编码、同样的标准化。

import joblib model = joblib.load("models/rf_baseline.joblib") scaler = joblib.load("models/scaler.joblib") # 一条 41 维的新流量特征,具体取值不重要,顺序必须和训练时一致 sample = [0, 1, 0, 2, 181, 5450] + [0] * 34 + [1] sample_2d = scaler.transform([sample]) prob = model.predict_proba(sample_2d)[0][1] pred = int(prob > 0.5) print(f"攻击概率: {prob:.3f} -> {'attack' if pred else 'normal'}")

predict_proba 返回一组概率,第 0 个位置是 normal 的概率,第 1 个是 attack 的概率。这里取 index 1 作为攻击概率,再和 0.5 比较。如果觉得误报率太高,可以把阈值从 0.5 往上抬,比如 0.7,只有模型足够确信攻击才告警——这个折中就是安全运营里最常见的操作:调阈值而不是换模型。

上面用了一组拼出来的 41 维特征向量作示例,含义不必逐列深究。你只需要记住:保存的时候把特征顺序一起保存,预测的时候才能保证新样本和训练样本用的是同一套语义,否则模型分数再高也是假的。

4. 把分数从“能跑”提到“能答辩”:特征工程与模型调优

基线跑通后,你要面对的现实是:这套网络入侵检测系统在答辩时会被追问很多“为什么”。为什么选这个算法?为什么特征这么多?少数类攻击是不是一条都检测不到?这一章就是把分数从“能跑”变成“能讲清楚”。

4.1 特征选择:卡方检验和互信息法砍掉冗余

41 个特征里真正有效的可能只有 20 多个。特征冗余对树模型影响不大,但对 KNN、SVM 这类距离敏感算法是灾难,还会拖慢推理速度。特征选择最简单的做法是用卡方检验或互信息法,筛出与标签相关度最高的 K 个特征。

from sklearn.feature_selection import SelectKBest, chi2, mutual_info_classif from sklearn.preprocessing import StandardScaler # 卡方检验要求特征非负,先标准化再平移 X_scaled = StandardScaler().fit_transform(X) X_nonneg = X_scaled - X_scaled.min(axis=0) + 1e-6 selector = SelectKBest(chi2, k=30).fit(X_nonneg, y) kept_cols = X.columns[selector.get_support()] print(f"保留 {len(kept_cols)} 列: {list(kept_cols)}") # 如果特征全是连续值,用互信息法更合适 mi_selector = SelectKBest(mutual_info_classif, k=30).fit(X, y)

卡方检验适合离散计数型特征,计算快;互信息法能捕捉非线性关系,对连续特征更友好。NIDS 数据里既有字节数这类连续量,也有标志位这类离散量,我的习惯是先用卡方快速筛一遍,再对留下的特征做相关性热力图,把相关系数超过 0.9 的重复特征去掉。

砍特征后必须重训一份模型做对比。拿随机森林自带的 feature_importances 画一张条形图,挑出排序前 10 的特征,这就是答辩时最有力的图表:用数据证明哪些流量特征对检测最有区分度。

4.2 类别不平衡:SMOTE 不是唯一解

网络流量本身极不平衡,正常流量占绝大多数,少数攻击类别可能只占 1% 不到。直接训练,模型会学会“永远预测正常”。处理不平衡的主流手段是过采样、降采样或给少数类加权重,SMOTE 是答辩里最常用的过采样方法,但它的正确用法和坑位都有讲究。

from imblearn.over_sampling import SMOTE from imblearn.pipeline import Pipeline from sklearn.ensemble import RandomForestClassifier pipe = Pipeline([ ("smote", SMOTE(random_state=42, k_neighbors=5)), ("clf", RandomForestClassifier(n_estimators=200, n_jobs=-1, random_state=42)) ]) # 假设 X_train_raw 是切分后、标准化前的原始特征 # SMOTE 必须在训练集内部做,绝不能在切分前对全量数据做 pipe.fit(X_train_raw, y_train) y_pred = pipe.predict(X_test_raw)

SMOTE 的原理是在少数类样本之间线性插值生成新样本,k_neighbors=5 表示每个样本挑 5 个最近邻。最大的坑是:SMOTE 必须在训练集内部做,绝对不能在切分前对全量数据做,否则合成的样本会同时出现在训练集和测试集里,评估结果虚高到没法看。

另一个坑是 SMOTE 只对离散度足够的少数类有效。U2R 本来就只有几十条样本,合成的数据很容易和真实分布脱节,此时不如直接给模型加 class_weight 参数,让多数类和少数类按比例分配权重。下采样则是粗暴地丢掉多数类样本,适合训练时间极短的基线实验,但信息损失大。给课设项目的建议是:二分类用 SMOTE 或 class_weight 都行,多分类只给少数类加权,不要无脑过采样。

4.3 模型对比选型:KNN、SVM、XGBoost 哪个适合做基线

不少机器学习入门文章喜欢一上来就上深度学习,但在入侵检测这种表格数据场景里,树模型才是性价比之王。深度学习需要海量数据和长时间训练,而 NSL-KDD 这种规模的数据集,随机森林和 XGBoost 就能打出很好的效果。

模型训练时间推理速度F1 表现可解释性适用角色
随机森林短快良好高基线,首选
KNN无训练慢中等中小样本演示
线性 SVM短快中上中高维特征
XGBoost长快最优高提精度阶段

KNN 最大的问题是懒学习:训练阶段什么都不做,预测时却要计算新样本和所有历史样本的距离,在大数据集上推理速度极慢,根本不适合实时网络检测。SVM 在特征维度高、样本量小的场景里效果好,但样本量上万以后训练时间急剧上升。结论很直接:随机森林做基线,XGBoost 做提升,深度学习留到确实有足够数据和算力再考虑。

不同机器学习算法之间的差异,在 NIDS 里主要体现实时性和可解释性的取舍。答辩时把模型选型这层逻辑讲清楚,比报一个 99% 的准确率更有说服力。

5. 网络入侵检测的 5 个常见坑与避坑清单

这部分内容来自我跑课程设计和帮别人改代码时反复踩过的坑。每一条都按“现象 → 原因 → 解决”的结构写,照着自查一遍,能省下大半天调试时间。

5.1 数据泄露:在标准化之前切分数据集

现象:训练集准确率 95%,测试集准确率也是 95%,模型效果“好得不真实”,但拿到真实流量上一测立刻垮掉。

原因:预处理时先对全量数据做 StandardScaler 或 MinMaxScaler,再切分训练测试集。scaler 在 fit 时已经看过测试集的数据分布,测试集的统计信息被“泄露”给了训练过程。

解决:严格按 3.2 的顺序,先 train_test_split,再 fit scaler 和 transform。同样的规则也适用于缺失值填充,imputer 也只能在训练集上 fit。自查方法很简单:把随机种子换掉重新跑一次,如果分数剧烈波动,说明流程里有数据泄露。

5.2 假高准确率:多数类主导下的评估陷阱

现象:打印出来 accuracy 99%,但分类报告里 attack 类的精确率或召回率是 0,所有攻击样本都被预测成 normal。

原因:正常流量占 80% 以上,模型学到的最优策略就是全部预测 normal,准确率当然高,但检测系统形同虚设。

解决:不要只看 accuracy。打印 classification_report 或混淆矩阵,确认 attack 类别的 F1;如果少数类 F1 为 0,按 4.2 加 SMOTE 或 class_weight;评估指标换成 AUC 或加权 F1。答辩时主动展示混淆矩阵,比念准确率专业得多。

5.3 训练时好好的,单条预测结果乱跳

现象:模型在测试集上 F1 超过 90%,但拿一条训练集里的样本丢进 predict.py,输出概率和训练时的评估结果对不上,甚至把正常流量判成攻击。

原因:最常见的原因是特征列顺序不一致。预处理时 pandas 按列名处理,但预测脚本里直接 list(df.values),列的顺序和训练时不一样,模型输入就被打乱了。

解决:训练后把特征列顺序保存成一个 list,和模型一起打包;预测时先用这个 list 对输入数据 reindex,再走标准化。更推荐的做法是把模型、scaler、特征列表封装成一个类,一次性加载,从根上杜绝错位。

5.4 真实抓包流量没有标签,模型评估无从下手

现象:把训练好的模型接到 pyshark 抓到的真实流量上后,预测结果看起来“都正常”,反而某些明显是扫描的流量也报了 normal,你不知道是模型错了还是流量本身没问题。

原因:公开数据集的特征定义和真实抓包提取的特征之间存在语义差异。比如数据集里的“持续时间”是完整连接时长,而实时抓包只看到连接中途的数据包,特征值天然对不上。

解决:不要把公开数据集训练的模型直接部署。正确的落地路径是:先用离线 pcap 文件回放,把训练集的特征提取逻辑完全复刻到抓包脚本上,再拿带标注的 pcap 做一次离线验证,确认特征对齐之后再谈实时。这条坑几乎每个做毕设做实时检测的人都会踩到。

5.5 内存不足:全量读入大数据集直接卡死

现象:预处理阶段 MemoryError,或者 Pandas 读 csv 后风扇狂转,16G 内存的电脑直接卡死。

原因:CICIDS2017 这类数据集体积超过 1G,Pandas 默认读入会把每个数值列都按 64 位浮点数存,内存占用瞬间涨到好几个 G。

解决:读入时指定 dtype 和低精度类型,数值特征全部用 np.float32,标签用 int8,内存直接减半;如果还不够,用 chunksize 分块读取,预处理后再合并。NSL-KDD 这种 12 万行的小数据不需要这么折腾,但养成这个习惯,后面跑大数据集不用返工。

6. 把课设升级成可演示的产品:实时抓包检测与结果可视化

最后这层是我建议你加上的,不是题目要求,但对“高分项目”来说,一份只停留在训练脚本里的源码,比不上一个能实时演示的检测服务。

6.1 用 pyshark 抓取本机流量并按条预测

pyshark 是对 tshark 的 python 封装,能实时抓包取协议字段。抓包得到的字段和 NSL-KDD 的特征不一样,所以这段代码只能作为演示思路,真实集成时你需要写一个特征提取函数 extract_features,把 pcap 解析成和训练集顺序一致的特征向量。

import pyshark import joblib model = joblib.load("models/rf_baseline.joblib") scaler = joblib.load("models/scaler.joblib") cap = pyshark.LiveCapture(interface="eth0") for pkt in cap.sniff_continuously(packet_count=20): feats = extract_features(pkt) # 复用训练时的特征提取逻辑 prob = model.predict_proba(scaler.transform([feats]))[0][1] src = pkt.ip.src if hasattr(pkt, "ip") else "?" print(src, round(prob, 3), "attack" if prob > 0.5 else "normal")

关键是 extract_features 必须复用训练时的特征提取逻辑,而不是现场新写一套。真实抓包的字段缺失要填充默认值,比如没有 TTL 字段就填 0,否则特征维度对不上直接报错。

6.2 用 Flask 暴露一个最小 HTTP 检测接口

实时抓包适合自己看,要给别人演示,还是 Flask 接口更方便:把特征向量 POST 过来,模型返回攻击概率和判定结果。

from flask import Flask, request, jsonify import joblib app = Flask(__name__) model = joblib.load("models/rf_baseline.joblib") scaler = joblib.load("models/scaler.joblib") @app.route("/detect", methods=["POST"]) def detect(): features = request.get_json()["features"] prob = model.predict_proba(scaler.transform([features]))[0][1] return jsonify({ "prob": round(prob, 4), "label": "attack" if prob > 0.5 else "normal" })

model 和 scaler 在启动时加载一次,不要每次请求都读文件;实际落地还要加输入长度校验,防止特征数量不对时模型直接抛异常。有了这个接口,你可以顺手用 python 数据分析与可视化 那套工具把攻击概率按时间画成趋势曲线,演示时比命令行输出直观得多。

6.3 给答辩演示留一条可回放路径

实时抓包最大的问题是不可控:网络里其他设备的突发流量会干扰结果,当场抓出来的结果可能和预期不符。更稳的做法是先用 tcpdump 录一小段 pcap,离线回放时把每条请求的时间、源 IP、攻击概率追加到 csv,演示时重放这条记录,效果稳定且可以复讲。

我自己做课设时吃过没保存特征顺序的亏,临答辩前一晚所有预测结果乱跳,后来养成的习惯是:模型、scaler、特征列顺序、预处理函数打包成一个固定的 artifact,不管谁拿它做预测,结果都一致。把这条链路封装好,再配一个可视化面板去演示,这套基于 Python 机器学习的网络入侵检测系统源码才真正从“能跑”变成“能讲”。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询