news 2026/10/1 18:57:58

基于Python机器学习的网络入侵检测系统实战:NSL-KDD与随机森林

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python机器学习的网络入侵检测系统实战:NSL-KDD与随机森林

简介:基于Python机器学习的网络入侵检测系统源码包,面向高校学生与机器学习初学者,适用于网络安全课程设计、期末大作业及毕业设计等场景,解决入侵检测任务中模型搭建困难、代码不完整、复现难度大等问题。项目已经导师指导并获97分高分,核心功能完善,下载后无需修改即可直接运行,满足课设与答辩的交付要求。压缩包共16个文件,大小约17.52MB,其中4个Python脚本负责模型训练、检测与评估,XML文件为工程配置,MD说明文档描述项目设计与使用方式,GZ格式存放KDD Cup数据集,另有TensorFlow事件日志与备份文件,数据、配置、代码分层存放,结构清晰。针对KDD Cup数据集中类别多、特征量大的特点,源码给出了具体的预处理与特征选取策略,训练阶段可输出事件日志用于观察损失变化,方便调参和复现。已有946人学习/下载,认可度较高。除可直接运行的完整系统外,还能从中学到网络流量特征处理、CNN模型构建、训练参数调优及结果分析的完整链路,对完成同类入侵检测课题有直接参考价值。

1. 基于Python机器学习的网络入侵检测系统:期末大作业里最“稳”的安全方向

如果你正为期末大作业选题发愁,网络入侵检测系统(NIDS)几乎是机器学习方向里最好落地、最能讲出东西的题目之一。它解决的问题很直白:从网络流量或连接记录里判断哪些是正常访问、哪些是攻击行为,本质是一个二分类(正常/攻击)或多分类(正常/DoS/Probe/R2L/U2R)任务。用Python跑通这份源码,你只需要一件事——把历史流量数据喂给机器学习模型,让模型学会区分“好人”和“坏人”,然后对新来的流量做预测。这个方案不需要真实攻击环境,用公开数据集就能完成训练和验证,适合期末答辩、课程设计,也适合安全方向零基础入门的人练手。我下面按我实际做过的完整路径来讲:从数据集选择、特征预处理、模型训练,到集成成能演示的系统,以及那些期末周最容易让你翻车的坑。

2. 网络入侵检测系统先选对数据集:NSL-KDD 与特征预处理

2.1 为什么 NSL-KDD 是这个题目的默认选择

做入侵检测的机器学习项目,数据源基本绕不开两个公开数据集:KDD Cup 1999 和它的改进版 NSL-KDD。KDD99 名气大,但它有个致命问题:训练集里大量重复记录,模型很容易把重复样本背下来,准确率虚高到 99% 以上,答辩时老师一问真实场景就露馅。NSL-KDD 是 KDD99 的去重和平衡版本,训练集和测试集的记录不重复,每类样本数量也更合理,是目前课程设计和论文里最常见的基准数据集。

NSL-KDD 的每条记录有 41 个特征加 1 个标签。41 个特征分成三组:第一组是 TCP 连接基本特征,比如 duration、protocol_type、service、src_bytes、dst_bytes;第二组是内容特征,比如登录失败的次数、是否 root 登录;第三组是流量统计特征,比如过去 2 秒内相同目标主机的连接数、相同服务的连接数。这些特征里有连续数值(字节数、时长),也有离散字符(协议类型、服务类型),所以数据预处理的核心工作就是:把字符特征转数值,把标签映射成模型能理解的数字,再统一量纲。

我一般会建一个这样的目录结构来组织这个项目:

nids_ml/ ├── data/ │ ├── KDDTrain+.txt │ ├── KDDTest+.txt │ └── KDDTest-21.txt ├── preprocess.py # 特征处理 ├── train.py # 模型训练与评估 ├── detect.py # 命令行检测 ├── app.py # Web 检测界面(可选) └── models/ # 训练好的模型文件

答辩时老师通常会问“为什么选这个数据集”,你只需要讲清楚两点:NSL-KDD 解决了 KDD99 的重复记录和不平衡问题;它是目前学术界和工业界验证入侵检测算法最通用的基准。这就够了。

2.2 加载数据与特征数值化:写清楚每一行的转换逻辑

KDDTrain+.txt 是带表头的 CSV 风格文件,但列名需要自己定义。下面是加载数据并完成特征数值化的标准代码,我做了注释说明:

import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder, StandardScaler # NSL-KDD 的 41 个特征名,顺序不能乱 col_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" ] label_col = "label" train_df = pd.read_csv("data/KDDTrain+.txt", names=col_names + [label_col]) test_df = pd.read_csv("data/KDDTest+.txt", names=col_names + [label_col]) # 标签列:原始标签是 'normal.' 或具体的攻击名,做编码前先统一成可读形式 train_df["label"] = train_df["label"].str.strip(".") test_df["label"] = test_df["label"].str.strip(".") # 字符特征列:protocol_type/service/flag 这三列需要转为数值 categorical_cols = ["protocol_type", "service", "flag"] for col in categorical_cols: # 用 fit 在训练集上建立映射,transform 同时应用到训练集和测试集 le = LabelEncoder() train_df[col] = le.fit_transform(train_df[col]) test_df[col] = le.transform(test_df[col])

这里有两个关键点。第一,LabelEncoder 必须在训练集上fit,再把同一个编码器应用到测试集,不能在测试集上重新fit。如果训练集里没有出现测试集的某个 service 值,transform会直接报错,这一点后面避坑章节还会单独讲。第二,str.strip(".")是为了把normal.和neptune.这类带句点的标签清理干净,不然做标签映射时会出现匹配不上的问题。

2.3 标签映射:二分类还是多分类

入侵检测的标签处理有两种路线:二分类(只判断是否异常)和多分类(具体识别攻击类型)。期末大作业我建议两条路都做,答辩时更出彩。二分类的映射逻辑是:把normal映射为 0,其余全部映射为 1。多分类则是把正常和每类攻击分别编码,NSL-KDD 中共有 4 大类攻击:DoS(拒绝服务)、Probe(端口扫描探测)、R2L(远程到本地越权)、U2R(用户到 root 提权)。

多分类的做法是建立攻击名到攻击大类的映射表,而不是直接对几十个细分的攻击名做编码,否则类别太多、样本不均衡,训练效果会很差:

# 攻击名 -> 攻击大类,NSL-KDD 标准映射 attack_map = { "normal": "normal", "back": "dos", "land": "dos", "neptune": "dos", "pod": "dos", "smurf": "dos", "teardrop": "dos", "apache2": "dos", "udpstorm": "dos", "processtable": "dos", "worm": "dos", "satan": "probe", "ipsweep": "probe", "nmap": "probe", "portsweep": "probe", "mscan": "probe", "saint": "probe", "guess_passwd": "r2l", "ftp_write": "r2l", "imap": "r2l", "multihop": "r2l", "phf": "r2l", "spy": "r2l", "warezclient": "r2l", "warezmaster": "r2l", "sendmail": "r2l", "named": "r2l", "snmpgetattack": "r2l", "snmpguess": "r2l", "xlock": "r2l", "xsnoop": "r2l", "httptunnel": "r2l", "buffer_overflow": "u2r", "loadmodule": "u2r", "perl": "u2r", "rootkit": "u2r", "sqlattack": "u2r", "xterm": "u2r", "ps": "u2r" } def map_label(label): return attack_map.get(label, "unknown") train_df["label_binary"] = (train_df["label"] != "normal").astype(int) train_df["label_multi"] = train_df["label"].map(map_label)

这段代码的逻辑不复杂,但映射表是抄作业时最容易抄错的地方。unknown兜底很重要,因为 NSL-KDD 测试集里有一部分攻击名是训练集没有的,测试时遇到新攻击名至少不会直接崩溃。二分类用的(train_df["label"] != "normal").astype(int)是一个很简洁的布尔转整数操作,答辩时可以提一句。

2.4 标准化、切分训练集和测试集:顺序错了就是数据泄露

数据标准化用StandardScaler,让每个特征的均值为 0、方差为 1。这里有一个新手最容易犯的错:对整个数据集先标准化再切分,导致测试集的信息混入标准化的均值和方差里,这属于数据泄露。正确做法是先切分,再在训练集上fit,然后用同一个 scaler 转换测试集:

feature_cols = col_names # 41 个原始特征 X_train = train_df[feature_cols].values.astype(float) y_train_bin = train_df["label_binary"].values y_train_mul = train_df["label_multi"].values X_test = test_df[feature_cols].values.astype(float) y_test_bin = (test_df["label"] != "normal").astype(int).values y_test_mul = test_df["label"].map(map_label).values # 先切分(这里直接用原始训练/测试集),再 fit scaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) from sklearn.model_selection import train_test_split # 在训练集内部再切出 10% 作为验证集 X_tr, X_val, y_tr, y_val = train_test_split( X_train_scaled, y_train_bin, test_size=0.1, random_state=42, stratify=y_train_bin )

stratify=y_train_bin表示按标签比例分层抽样,保证验证集和训练集的攻击样本比例一致。NSL-KDD 里正常和攻击样本比例原本就比较平衡,加分层是习惯问题,不加也不会差太多,但答辩被问到“你怎么保证验证集分布一致”时,这个参数就是你的加分项。

3. 训练一个能交差的入侵检测模型:算法选型与核心参数

3.1 算法怎么选:期末大作业不是算法竞赛

很多人在这个环节会纠结:是不是要用深度学习?是不是要上 LSTM?我的看法是,期末大作业的评分重点是“你能不能把机器学习流程讲完整”,不是“你用的模型多新”。在 NSL-KDD 这种表格数据上,深度学习不一定打得过传统模型,而且训练时间更长、调参更玄学。我推荐在下面几个模型里做对比,兼顾效果和答辩可讲性:

模型优点缺点期末作业适配度
决策树训练极快、完全可解释、效果不差容易过拟合、深度大时规则复杂高,适合讲原理
随机森林准确率高、抗过拟合、对异常值不敏感模型体积大、预测稍慢高,主力模型
KNN逻辑简单、无需训练预测慢、维度高时效果退化中,适合做对比
逻辑回归训练快、概率输出易解释线性假设、对非线性特征效果差中,作为基线
SVM(RBF核)小样本效果好、边界清晰数据量大时训练慢低,NSL-KDD 上用会慢

我一般会选随机森林作为主模型,决策树作为对比模型,KNN 作为第三个参考。答辩时老师问“为什么选随机森林”,你可以回答:它通过 Bagging 采样和多棵树的投票降低方差,对入侵检测里特征维度高、存在噪声和异常值的场景有天然优势;同时它不需要复杂的特征缩放(但我们已经做了标准化,不影响)。这个回答比“因为它准确率高”要有说服力得多。

3.2 完整训练脚本:从数据到模型保存

下面是我常用的训练脚本骨架,包含训练、评估、模型持久化三步,模型用joblib保存,后面检测系统直接加载使用:

import joblib from sklearn.ensemble import RandomForestClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import classification_report, confusion_matrix, accuracy_score # 随机森林:上一步切好的 X_tr, y_tr(二分类标签) rf = RandomForestClassifier( n_estimators=100, # 树的数量,100 棵是性能和时间的平衡点 max_depth=20, # 限制单棵树深度,防止过拟合 min_samples_split=5, # 内部节点再划分所需最小样本数 min_samples_leaf=2, # 叶节点最少样本数,配合上一项用 random_state=42, n_jobs=-1 # 用所有 CPU 核,训练加速 ) rf.fit(X_tr, y_tr) # 验证集评估 y_pred_val = rf.predict(X_val) print("验证集准确率: {:.4f}".format(accuracy_score(y_val, y_pred_val))) print(classification_report(y_val, y_pred_val, target_names=["normal", "attack"])) # 在真正的测试集上做最终评估(KDDTest+,训练集从未见过) y_pred_test = rf.predict(X_test_scaled) print("测试集准确率: {:.4f}".format(accuracy_score(y_test_bin, y_pred_test))) print(confusion_matrix(y_test_bin, y_pred_test)) # 保存模型和训练时用到的 scaler、编码器,检测阶段全部要复用 joblib.dump(rf, "models/rf_binary.joblib") joblib.dump(scaler, "models/scaler.joblib")

参数说明要细讲。n_estimators=100不是越大越好,树多了训练时间和模型体积线性增长,100 棵在几万条数据上几十秒就能跑完,而 300 棵的效果提升通常不到 1%,不值当。max_depth=20是对单棵树的深度限制,无限深度的决策树纯属背题,限制深度是随机森林“抗过拟合”的关键来源。min_samples_split=5表示一个节点至少有 5 个样本才允许继续分裂,min_samples_leaf=2表示叶子节点至少要有 2 个样本,这两个参数一起控制树的生长粒度,值越小树越贴近训练集,值越大越粗糙。n_jobs=-1是 sklearn 的并行参数,-1 表示使用所有 CPU 核心。

3.3 评估指标:准确率不是唯一标准

入侵检测场景里有个特殊现象:如果测试集里攻击样本占 40%,一个“永远预测正常”的模型准确率也有 60%。所以答辩时老师一定会追问评估指标。你至少要能讲清楚这四个:准确率(Accuracy)是整体预测正确的比例,但它会被多数类主导;精确率(Precision)是你预测为攻击的样本里真正是攻击的比例,精确率低说明误报多;召回率(Recall)是所有真实攻击里被找出来的比例,召回率低说明漏报多;F1-score 是精确率和召回率的调和平均,类别不均衡时比准确率可靠。

在入侵检测里,漏报的代价往往比误报高——放走一次攻击可能整个内网被打穿,而误报只是让运维多看一条告警。所以我一般要求模型对攻击类别的召回率达到 0.95 以上,否则就是不合格。classification_report输出的每一行都要能看懂,你的期末报告里用表格呈现这些指标,比贴一段输出更专业:

类别精确率召回率F1-score样本数
normal(正常)0.980.950.969711
attack(攻击)0.930.980.9512833

3.4 多分类训练:一个模型识别攻击类型

二分类能拿基础分,多分类是加分项。多分类的做法和二分类几乎一样,只是标签从 0/1 变成 normal/dos/probe/r2l/u2r 五个类别:

from sklearn.ensemble import RandomForestClassifier rf_multi = RandomForestClassifier( n_estimators=120, max_depth=25, min_samples_split=5, min_samples_leaf=2, class_weight="balanced", # 自动对少数类加权,缓解 R2L/U2R 样本太少的问题 random_state=42, n_jobs=-1 ) rf_multi.fit(X_train_scaled, y_train_mul) y_pred_multi = rf_multi.predict(X_test_scaled) print(classification_report(y_test_mul, y_pred_multi))

class_weight="balanced"会根据各类别样本数量自动调整权重,样本量少的 R2L 和 U2R 会获得更高的误分类代价,从而让模型更重视它们。但说实话,NSL-KDD 里 R2L 和 U2R 的样本数量太少(U2R 训练集只有几十条),任何模型在这两个类别上效果都不好,这不完全是你的问题。答辩时主动说“U2R 因为样本量太少,效果不理想,这是数据集本身的不平衡问题”,比你等老师问出来再解释要好得多。

4. 入侵检测系统的避坑手册:5个常见翻车点与排查记录

4.1 坑一:标准化做早了,数据泄露导致测试集分数虚高

现象:训练集准确率 0.99,验证集 0.99,测试集也 0.99,但你把模型用到自己抓的流量上,效果稀碎。

原因:你在切分训练集和测试集之前就做了标准化,scaler.fit是在全量数据上做的,测试集的均值和方差已经混进了标准化参数。模型训练时“见过”测试集分布,评估分数虚高。这是一种非常典型的数据泄露。

解决:把fit_transform放到train_test_split之后,测试集只用transform。检查方法很简单——打印出scaler.mean_,然后对照只用训练集计算出的均值,如果两者不同,说明泄露了。

4.2 坑二:检测阶段特征顺序错位,预测结果全乱

现象:训练时模型好好的,检测脚本接手后,对一条明显是攻击的记录预测成了 normal,或者直接报维度错误。

原因:检测阶段重新构造特征时,特征的排列顺序和训练时不一致。比如训练时第 4 列是flag(已经编码成数值),检测时你手写的特征列表把src_bytes放到了第 4 列。模型不关心列名,只认位置,顺序一错全是黑匣子。

解决:在训练阶段把特征列顺序存下来,检测阶段严格按这个顺序构造特征。保存模型时顺手把feature_cols也joblib.dump一份:

joblib.dump({"model": rf, "scaler": scaler, "features": feature_cols}, "models/rf_binary_full.joblib")

检测时先加载这个字典,拿features列表去对齐输入数据。这个习惯能救你一命。

4.3 坑三:LabelEncoder 在测试集上重新 fit,遇到新类别直接崩

现象:测试集加载后做特征编码时报错:y contains previously unseen labels,或者编码结果训练和测试对不上。

原因:test_df[col] = le.fit_transform(test_df[col])在测试集上新做了一个编码器,测试集里出现了训练集没有的 service(比如http_8001),新编码器会把它编成新数字,整个特征空间错位。

解决:训练阶段的编码器用le.transform处理测试集,不要在测试集上再fit。如果测试集里真有训练集没见过的类别,用handle_unknown="ignore"或手动把未知值填充为 -1:

# 检测阶段,未知类别兜底处理 def safe_transform(le, values): known = set(le.classes_) return [le.transform([v])[0] if v in known else -1 for v in values]

4.4 坑四:混淆矩阵和分类报告对不上,标签映射错位

现象:混淆矩阵里 normal 类别下出现了大量数值,但分类报告里 normal 的召回率却很低,两个指标对不上。

原因:做二分类时标签映射不一致——训练时把normal映射成 0,检测脚本把attack当作 0,正负标签完全反了。混淆矩阵的四个格子位置是固定的(左上 TP、右上 FN、左下 FP、右下 TN),但传入confusion_matrix(y_true, y_pred)时,labels参数如果不显式指定,会按两个数组里出现的最小值排序。你的y_true里是 0/1,y_pred如果全是 1,矩阵形状都会变。

解决:调用confusion_matrix时显式传labels=[0, 1],确保两个维度顺序固定。同时打印一份交叉表:

import pandas as pd result = pd.DataFrame({"真实标签": y_test_bin, "预测标签": y_pred_test}) print(pd.crosstab(result["真实标签"], result["预测标签"]))

用交叉表核对比直接看混淆矩阵直观得多。

4.5 坑五:测试数据不是给的 KDDTest+,而是从训练集里抽的

现象:测试集准确率比训练集还高,或者验证集上表现优秀、测试集上暴跌。

原因:你为了“省事”直接从 KDDTrain+.txt 里用了train_test_split切出 20% 当测试集,而没有使用 NSL-KDD 官方提供的KDDTest+.txt。KDDTest+ 和 KDDTrain+ 有分布差异,里面包含训练集没见过的攻击变种,这才是真正的考验。只在自己的切分里自嗨,答辩现场演示时用官方测试集,分数断崖式下跌。

解决:训练用 KDDTrain+,评估用 KDDTest+,这是这个项目的标准流程。KDDTest-21.txt是更难的子集,过滤掉了最容易判别的记录,可以拿来作为“困难模式”加分项。

5. 把模型包成能演示的系统:命令行检测与 Web 展示的落地写法

5.1 文件清单与系统流程

模型训练完之后,要把它变成能演示的系统。期末大作业的系统一般有三种形态:命令行工具、Web 界面、离线批量检测脚本。我推荐做前两个:命令行工具用于答辩时快速验证单条记录,Web 界面用于打开网页点两下演示效果,屏幕前展示比敲命令更有视觉冲击力。系统整体流程是:加载保存的模型和参数 → 接收输入流量记录 → 预处理(字符编码 + 特征对齐 + 标准化) → 模型预测 → 输出结果和概率。

这个阶段的项目结构继续沿用前面的目录,确保models/里有训练阶段保存的完整模型包:

nids_ml/ ├── models/ │ └── rf_binary_full.joblib # 包含 model/scaler/features ├── detect.py ├── app.py ├── requirements.txt └── README.md

5.2 命令行检测脚本:单条记录和批量文件都能跑

detect.py的职责是读取特征输入,调用模型预测。它的核心难点不在预测,而在把输入转换成和训练时完全一致的特征向量。我推荐把它写成支持两种输入模式:单条记录用命令行参数传入,批量记录用 CSV 文件传入:

import sys import numpy as np import pandas as pd import joblib def load_model_package(path="models/rf_binary_full.joblib"): pkg = joblib.load(path) return pkg["model"], pkg["scaler"], pkg["features"] def preprocess_input(raw_record, feature_cols): """把一条原始记录转成 41 维特征向量,顺序必须以 feature_cols 为准""" if len(raw_record) != len(feature_cols): raise ValueError(f"特征数量不对:期望 {len(feature_cols)},实际 {len(raw_record)}") # 这里假设 raw_record 已经是数值型;如果是原始 KDD 记录,需要先做字符编码 vec = np.array([float(x) for x in raw_record]).reshape(1, -1) return vec def detect_single(raw_record, model, scaler, feature_cols): vec = preprocess_input(raw_record, feature_cols) scaled = scaler.transform(vec) # 注意是 transform,不是 fit_transform prob = model.predict_proba(scaled)[0] pred = model.predict(scaled)[0] return pred, prob if __name__ == "__main__": model, scaler, feature_cols = load_model_package() # 单条:python detect.py --record 0 tcp http SF 181 5450 0 ...(41个值) if "--record" in sys.argv: idx = sys.argv.index("--record") raw = sys.argv[idx + 1].split(",") pred, prob = detect_single(raw, model, scaler, feature_cols) label = "攻击" if pred == 1 else "正常" print(f"检测结果: {label},攻击概率: {prob[1]:.4f}")

这段代码的逻辑说明:predict_proba返回的是每个类别的概率,二分类时prob[0]是正常概率、prob[1]是攻击概率。答辩时输出攻击概率比只输出 0/1 更有说服力,因为老师会问“模型凭什么判断这是攻击”,概率就是你给出置信度依据。

5.3 用 Flask 包一层 Web 界面:简单的就是最好的

如果答辩环境允许演示,Flask 写一个最小可用 Web 界面是最稳妥的方案。不要追求复杂的前端框架,一个 HTML 表单足够:

from flask import Flask, request, render_template_string import joblib import numpy as np app = Flask(__name__) model, scaler, feature_cols = load_model_package() HTML_PAGE = """ <!DOCTYPE html> <html> <body> <h2>网络入侵检测系统</h2> <form method="post"> <textarea name="features" rows="4" cols="100" placeholder="输入 41 个特征值,用逗号分隔"></textarea><br> <button type="submit">检测</button> </form> <p>检测结果: {{ result }}</p> </body> </html> """ @app.route("/", methods=["GET", "POST"]) def index(): result = "等待输入..." if request.method == "POST": raw_text = request.form["features"].strip() raw = [float(x) for x in raw_text.split(",")] vec = np.array(raw).reshape(1, -1) scaled = scaler.transform(vec) prob = model.predict_proba(scaled)[0] pred = model.predict(scaled)[0] result = "攻击 (概率: {:.4f})".format(prob[1]) if pred == 1 \ else "正常 (置信度: {:.4f})".format(prob[0]) return render_template_string(HTML_PAGE, result=result) if __name__ == "__main__": app.run(host="127.0.0.1", port=5000, debug=False)

这里有个安全细节:feature_cols有 41 个特征,如果用户在文本框里只输入 20 个值,raw长度不对,会直接崩。上线演示前一定要加长度校验并返回友好提示,不然临场翻车很尴尬。Web 界面只是壳,核心逻辑和命令行完全一致,复用load_model_package()保证两处行为一致。

5.4 进阶:用 Scapy 抓真实流量做实时检测(可选加分项)

NSL-KDD 的特征是历史连接记录,不是原始数据包。如果你想演示“实时检测”,常见做法是用 Scapy 监听网卡数据包,解析出五元组(源 IP、目的 IP、源端口、目的端口、协议)和包长、标志位等信息,再映射到 41 个特征。这个方案能做,但有两个坑:一是真实流量的特征分布和 NSL-KDD 完全不同,模型准确率会明显下降;二是很多特征(如dst_host_srv_count)需要统计窗口内的历史连接,单包无法计算。我的建议是:如果时间紧,实时检测作为“概念演示”在答辩 PPT 里带过就行,不要真做,否则你会陷入特征对齐的泥潭。

6. 比同学多拿分的进阶操作:模型对比实验与期末报告可视化

到了这个阶段,系统已经能跑、能演示、能检测了。如果你想在答辩里拉开差距,不要只交“一个模型跑通了”。我强烈建议做一组模型对比实验,把结果直接做成图表放进期末报告。这个操作的成本很低,但在老师眼里代表你真的理解了机器学习评估方法论,而不仅仅是一个“调包侠”。

多模型对比的脚本很简单。把第 3 章的随机森林换成几个模型循环训练,记录每个模型在测试集上的准确率、精确率、召回率、F1 和训练时间:

from sklearn.ensemble import RandomForestClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, f1_score, recall_score import time models = { "决策树": DecisionTreeClassifier(max_depth=15, random_state=42), "随机森林": RandomForestClassifier(n_estimators=100, max_depth=20, random_state=42, n_jobs=-1), "KNN": KNeighborsClassifier(n_neighbors=5), "逻辑回归": LogisticRegression(max_iter=500, random_state=42) } results = [] for name, model in models.items(): t0 = time.time() model.fit(X_train_scaled, y_train_bin) elapsed = time.time() - t0 y_pred = model.predict(X_test_scaled) results.append({ "模型": name, "准确率": accuracy_score(y_test_bin, y_pred), "召回率": recall_score(y_test_bin, y_pred), "F1": f1_score(y_test_bin, y_pred), "训练时间(s)": round(elapsed, 2) }) df_results = pd.DataFrame(results) print(df_results.to_string(index=False))

然后画一张柱状图,把四个模型的准确率和召回率并列展示:

import matplotlib.pyplot as plt x = df_results["模型"] acc = df_results["准确率"] rec = df_results["召回率"] plt.figure(figsize=(8, 5)) plt.bar(x, acc, width=0.4, label="准确率", alpha=0.8) plt.bar(x, rec, width=0.4, label="召回率", alpha=0.8, align="edge") plt.ylabel("分数") plt.ylim(0.5, 1.0) plt.legend() plt.title("不同模型在NSL-KDD测试集上的表现对比") plt.savefig("results/model_comparison.png", dpi=150)

这张图放进期末报告的价值远大于它 10 行代码的成本。答辩时你指着图说:“逻辑回归的召回率明显低于随机森林,说明线性模型无法捕捉入侵流量的非线性特征;KNN 的准确率尚可但训练时间随样本量线性增长,不适合大规模流量检测。”这就不是报参数,而是在做分析。

做对比实验时有一个血泪经验:不同模型的默认参数直接跑就行,没必要每个都调参。决策树用max_depth=15,随机森林用默认n_estimators=100,KNN 用n_neighbors=5,逻辑回归把max_iter调大避免不收敛。这个对比的目的是给大家看“不同算法家族的差异”,不是看调参技巧——课程设计阶段没人指望你做超参数搜索。

最后,把模型持久化时,我习惯把实验记录写进模型文件名里,比如rf_binary_acc095.joblib,这样模型多了之后不会忘记哪个是最终版本。期末周熬夜改方案是常态,这个习惯能让你少改一次 bug。希望这篇文章能帮你把期末大作业做得又快又稳,答辩顺利。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 18:57:57

Windows 10 LTSC 2021企业版部署:稳定性优先的生产环境构建指南

1. 项目概述&#xff1a;为什么重装 Windows 10 企业版 LTSC 2021 不是“换系统”&#xff0c;而是“重建工作基座”我干IT运维和桌面支持这行十多年&#xff0c;经手过上万台电脑的系统部署——从学校机房批量刷Win7&#xff0c;到金融客户终端统一迁移到Win10专业版&#xff…

作者头像 李华
网站建设 2026/10/1 18:57:34

GitHub热榜深度拆解:从日榜数据到开源项目学习与上榜实战

每天早上打开电脑&#xff0c;第一件事不是回聊天软件&#xff0c;而是先点开GitHub热点页面&#xff0c;这个习惯我保持了三年多。所谓GitHub热榜&#xff0c;是指官方Trending页面按过去24小时里star增长量给开源项目排的座次&#xff1b;日榜就是时间窗口最短的那一档。别小…

作者头像 李华
网站建设 2026/10/1 18:57:28

电脑截图录屏工具,长截图任意截图都能用

软件介绍 今天这款叫 very capture&#xff0c;是一款完全免费的截图软件。它的功能主要分成两块&#xff1a;截图和视频录制。截图这块又细分为全屏截图、矩形截图、任意截图、延时截图和长截图&#xff1b;视频录制这边则包括 Gif 录制和视频录制。 默认英文界面&#xff0…

作者头像 李华
网站建设 2026/10/1 18:57:03

Ubuntu中文输入法:Fcitx5安装配置与故障排查指南

1. 别急着敲安装命令&#xff1a;先搞懂 Ubuntu 输入法的三层结构在 Ubuntu 上装中文拼音输入法&#xff0c;最容易犯的错是上来就sudo apt install一通&#xff0c;装完重启&#xff0c;然后发现——右下角托盘里那个键盘图标是灰色的&#xff0c;或者能打字但没有候选框。这类…

作者头像 李华
网站建设 2026/10/1 18:56:41

接口自动化测试:Jmeter下数据初始化与脏数据清理实践

做接口自动化测试的同行应该都遇到过这种糟心事&#xff1a;用例第一次跑&#xff0c;全绿&#xff1b;隔几分钟再跑一次&#xff0c;一批用例红了。一看日志&#xff0c;什么"唯一键冲突"、"数据已存在"、"订单号重复"。这时候多半不是代码逻辑…

作者头像 李华
网站建设 2026/10/1 18:56:03

Vue纯前端自研甘特图:零依赖实现拖拽排期与依赖线

做过排期、进度管理类后台系统的人应该都有这个体感&#xff1a;列表和表格搭起来很快&#xff0c;真正卡住工期的是中间那块甘特图。需求方要拖拽调时间、要显示依赖箭头、要按周月切换粒度&#xff0c;最好还能看到关键路径和基线对比&#xff0c;可预算又卡死在“不采购商业…

作者头像 李华