ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从NSL-KDD到实时检测:入侵检测项目数据预处理与建模避坑指南

从NSL-KDD到实时检测:入侵检测项目数据预处理与建模避坑指南 简介这是一份面向计算机专业学生与初学者的入侵检测项目完整工程融合机器学习与深度学习两条技术路线覆盖数据预处理、特征工程、类别不平衡处理、模型训练与评估等环节可直接用于毕业设计、课程设计或期末大作业。压缩包采用zip格式共31个文件大小约26.58MB主体为14个Python脚本涵盖CNN、LSTM、随机森林等模型的训练与预测5个txt与3个md说明文件帮助梳理目录与使用流程另有2个zip数据压缩包、2个hdf5模型权重、3个csv数据集、1张结果展示图和doc参考论文。项目基于UNSW_NB15数据集完整呈现数据归一化、标签编码、相关性分析、特征筛选与不平衡处理等关键步骤并配有较为完整的运行说明。目前已有95人学习下载适合需要高分范例、可运行完整代码的实战学习者。1. 入侵检测项目源码到手先判断能不能要跑通只是第一步拿到一份「python基于机器学习/深度学习实现的入侵检测项目源码项目文档参考论文使用说明」别急着pip install然后python train.py。这类项目最常见的坑不是代码报错而是你把训练跑通、看到 99% 的准确率就以为自己已经做完了一个入侵检测系统——换到真实流量上立刻翻车。这个项目真正要解决的就两件事第一让网络流量或主机日志里的异常行为能被算法自动认出来第二让你有能力跟团队解释清楚模型凭什么做判断、阈值怎么定、误报从哪来、换数据集之后哪些步骤要重来。它适合两类人一个是拿来做课程设计或毕设的学生照着项目文档复现、改参、写对比实验拿分下限很高另一个是刚接手安全数据分析的工程师需要从这套源码里找到特征处理、窗口构造、模型选型的具体写法再嫁接到自己的数据管道上。下面我按数据、建模、评估、部署这条线把最容易翻车的地方逐个拆开。2. 数据准备与预处理先把 NSL-KDD 的坑填平再谈建模2.1 读 NSL-KDD 前先看这四件事文件格式、列名、标签与占比课程设计和毕设里最常用的入侵检测数据集是 NSL-KDD。它小、标签明确、论文里的对比基线多。但很多人在第一步就没做对拿pd.read_csv直接读结果第一列变成了列名。NSL-KDD 的原始文件没有表头需要按顺序给列命名。我一般会用下面这段脚本起手先把数据读进来顺便看一眼标签分布这一步决定后面所有策略。import pandas as pd # 常见做法41 个特征 1 个标签列按网络包字段顺序命名 columns [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] df pd.read_csv(KDDTrain.txt, headerNone, namescolumns) df[label] df[label].str.strip().str.replace(., , regexFalse) print(df[label].value_counts())这段代码的逻辑并不复杂但有两个点容易被忽略列名必须在读取时就给全缺失或错位会导致后续独热编码时列数对不上模型训练报维度错误。原始标签形如neptune.、normal.末尾带着一个点。这里用str.strip()去掉空格、str.replace(., , regexFalse)去掉末尾的点否则normal和normal.会被当成两个类别。读进来之后先看value_counts()的分布不要急着训练。如果发现某个攻击类别只有几十条样本后面的 SMOTE 策略、评估指标选择都要跟着调整。这一步的输出也建议直接截图或者存成统计表放进项目文档里论文的“数据集描述”章节能直接用。2.2 特征预处理脚本数值编码、标准化与测试集只 transform 的约定NSL-KDD 里有三列是符号类型protocol_typetcp/udp/icmp、servicehttp/ftp/ssh 等几十种、flag连接状态标志。这三列不能直接喂进 sklearn 或 PyTorch。常见做法是决策树类的模型可以用LabelEncoder转成整数神经网络模型建议做独热编码。但无论哪种都必须只对训练集进行拟合再对测试集做转换。from sklearn.preprocessing import LabelEncoder, StandardScaler # 符号列LabelEncoder 转成整数索引 encode_cols [protocol_type, service, flag] for col in encode_cols: le LabelEncoder() df[col] le.fit_transform(df[col]) # 特征列与标签列分离 X df.drop(columns[label]).values y (df[label] ! normal).astype(int).values # 二分类0正常, 1攻击 # 划分训练集和测试集注意 stratify from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy) # 标准化只对训练集 fit测试集只 transform scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)这里最需要记住的是scaler.fit_transform(X_train)和scaler.transform(X_test)的区分。很多复现项目把StandardScaler().fit_transform(X)放在整份数据上做看起来测试集也被“规范”了但这是标准的数据泄露测试集的均值和方差已经参与到了训练特征的构造里测试分数会虚高。真正的项目里这条约定必须写进文档和代码注释里因为换一个人来接着做大概率会在这一步把管道改坏。stratifyy也是必须的参数。入侵检测数据天然不平衡如果不按类别分层抽样切出来的测试集可能全是正常流量模型评估直接失真。带上这个参数之后训练集和测试集里正常/攻击的比例会大致保持一致。2.3 样本不均衡怎么办先看分布再 SMOTE参数不能乱给许多人在跑完value_counts()看到少数类样本很少之后第一反应就是上 SMOTE。但入侵检测里的不均衡问题比较特殊像neptune这种拒绝服务攻击样本数量其实很多真正少的是 U2R、R2L 这类提权和远程漏洞利用攻击。对这些过少的类别做 SMOTE合成的样本很可能落在特征空间的真实模式之外反而是把噪声喂进模型。我的习惯是一开始不做任何采样先训练一个随机森林看各类别的召回率尤其是少数类的召回率。如果少数类召回率低到不可接受再对少数的类别做 SMOTE而不是对所有非 normal 类别一股脑全采样。from imblearn.over_sampling import SMOTE # 只有当少数类样本量足够时才用 SMOTEk_neighbors 要调小 smote SMOTE(random_state42, k_neighbors3, sampling_strategyauto) X_train_res, y_train_res smote.fit_resample(X_train, y_train) print(SMOTE 之后各类别数量, pd.Series(y_train_res).value_counts().to_dict())k_neighbors默认是 5但少数类只有几十条样本时5 个近邻里可能挤满了不同类别的样本生成的插值样本就会“跨界”。有人直接代码复制默认参数跑完发现模型效果反而变差就是这个原因。sampling_strategyauto表示把每个类别都补齐到最多类别的数量如果你只想提高某一个少数类可以传入一个字典比如sampling_strategy{1: 5000}只把攻击类别补到 5000 条这样能减少合成样本带来的噪声。还要注意采样和标准化的先后顺序。如果先标准化再 SMOTE合成样本是在标准化后的空间里线性插值还原到原空间后可能产生不合理的数值。更稳的顺序是原始特征上做 SMOTE再做标准化。3. 两类检测模型怎么选传统机器学习与深度学习的对比实现3.1 传统机器学习路线随机森林脚本、参数与过拟合边界在绝大多数入侵检测项目里随机森林是最稳的基线模型。它对表格特征、非线性关系和类别不平衡都有不错的容忍度训练速度快还能输出特征重要性方便写进论文里做分析。下面这个脚本可以作为第一个跑通的模型。from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report rf RandomForestClassifier( n_estimators200, max_depth18, min_samples_leaf2, max_featuressqrt, class_weightbalanced, random_state42, n_jobs-1 ) rf.fit(X_train, y_train) print(classification_report(y_test, rf.predict(X_test)))几个参数的调整逻辑按优先级排是这样的max_depth是第一个要调的。默认不限制深度随机森林会逐棵长到完全拟合训练集在入侵检测这种噪声不少的数据集上过拟合很快。常见做法是从 8 开始按 2 的倍数向上扫看到测试集 F1 不再涨就停。min_samples_leaf设成 2 或 3避免叶子节点只覆盖一个样本减少异常点的记忆。class_weightbalanced让少数类的误分代价更高这比用 SMOTE 更省事而且不会引入合成样本。max_featuressqrt是表格数据的默认好习惯每次分裂只随机看一部分特征树与树之间的多样性更好。判断模型有没有过拟合不看训练集准确率而是对比训练集和测试集的加权 F1。如果训练集加权 F1 在 0.99测试集只有 0.82先回去调max_depth和min_samples_leaf而不是急着换模型或堆数据。这个判断方法在参考论文里也常被用来支撑“本文方法优于基线”的论述。3.2 深度学习路线把单条样本变成窗口序列再喂进 LSTM深度学习在入侵检测里的落地方式最容易犯的错误是“拿着表格数据硬套 LSTM”。NSL-KDD 的每一条样本是一个独立的网络连接记录样本与样本之间本来没有先后顺序直接把每一行当序列喂给 LSTM模型学到的只是特征的死记硬背效果通常还打不过随机森林。常见做法是做窗口化按时间或会话顺序把相邻的一批样本拼接成一个窗口让模型去学窗口内特征的变化模式。import numpy as np def make_windows(X, y, window32, stride16): 把单条样本按顺序切成重叠窗口标签取窗口最后一条样本的标签 Xs, ys [], [] for i in range(0, len(X) - window 1, stride): Xs.append(X[i:i window]) ys.append(y[i window - 1]) return np.array(Xs), np.array(ys) X_train_w, y_train_w make_windows(X_train, y_train, window32, stride16) X_test_w, y_test_w make_windows(X_test, y_test, window32, stride16)window32表示模型每次看 32 条连续样本stride16表示窗口每次滑动 16 条窗口之间有重叠。这样样本量会增加但也引入了相邻窗口之间的相关性后面评估时要注意这一点。窗口标签取最后一条样本的标签含义是“根据过去 32 条连接的上下文预测当前这条连接是否异常”这种做法不会让未来信息泄漏进当前预测。有了窗口数据之后再用 PyTorch 搭一个简单的 LSTM 分类器import torch import torch.nn as nn class LstmDetector(nn.Module): def __init__(self, input_dim, hidden_dim64, num_layers1): super().__init__() self.lstm nn.LSTM(input_sizeinput_dim, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue) self.head nn.Linear(hidden_dim, 2) def forward(self, x): out, _ self.lstm(x) # 取窗口最后一个时间步的输出做分类 return self.head(out[:, -1, :])batch_firstTrue让输入形状变成(batch, window, features)符合直觉。hidden_dim64在这个量级的数据上够用加大到 128 以上只会拖慢训练对 F1 提升非常有限。num_layers1是刻意为之的两层以上的 LSTM 在这个数据规模上几乎必然过拟合。训练时建议用 AdamW学习率设 1e-3跑 20 个 epoch 后看测试 F1而不是盯着 loss 曲线。3.3 对比实验怎么设计同一个评估脚本三个训练种子结果才敢写进论文这类项目里参考论文和项目文档最值钱的部分是“模型对比表”。很多项目对比结果写得没法看是因为不同模型用了不同的数据切分、不同的预处理顺序甚至不同的标签定义。对比实验的唯一原则是除了模型本身其他东西全部保持不变。seeds [42, 2024, 2025] for seed in seeds: X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_stateseed, stratifyy) # 随机森林、LSTM、其他任何模型都基于同一份切分结果跑 # 预处理脚本单独封装保证每个模型看到的是同一份训练集和测试集 # 记录每个 seed 下的 precision、recall、f1最后输出均值 ± 标准差三个种子分别跑完之后报告平均值和标准差这一组数字才经得起追问。只跑一个随机种子、又不使用stratify测试集分布一偏后面的结论全都不成立。项目文档里也应当把这三个种子下的结果都附上参考论文的复现实验部分往往就是这么写的。4. 效果验证与避坑为什么别人论文 99%你复现只有 85%4.1 评估指标准确率在入侵检测里为什么会骗人入侵检测数据天然类别不平衡攻击样本占比通常不高。就以 NSL-KDD 为例一个什么都不做、把所有样本都判成正常流量的“模型”也能拿到相当高的准确率。但这显然没有任何检测能力。所以看实验结果第一眼一定要看classification_report输出里攻击类别的recall和f1-score。召回率代表攻击样本能被抓出来的比例在 IDS 场景里漏报往往比误报更危险。F1 是精确率和召回率的调和平均用来衡量模型在“别漏报”和“别乱报”之间的综合表现。如果项目文档里只给了 accuracy 没给每类的 precision/recall这份结果基本可以判定为不可信。4.2 数据泄露的三个重灾区重叠样本、归一化时机、特征穿越入侵检测项目里“效果好到不真实”的结果多半是数据泄露造成的。我整理了三处最常见的泄露点重叠样本。NSL-KDD 测试集和训练集里存在不少同类型甚至同源特征的样本。如果不用官方划分、而是自己train_test_split测试集里很可能混进与训练样本高度相似的“熟人”。解决方法是使用官方提供的训练/测试文件划分或者在按会话切分时确保同一批网络连接只出现在一个集合里。归一化时机。前面已经提过先在全量数据上fit_transform再切分测试集的均值和方差就泄漏到了训练过程里。正确的顺序永远是先划分再训练集上fit测试集只transform。特征穿越。窗口化构造序列数据时如果标签取的是窗口中间位置的样本而特征包含了窗口后面的样本就相当于让模型拿“未来”预测“过去”。正确做法是标签取窗口最后一条或者保证特征只取自当前时刻之前的信息。4.3 复现论文结果前先核对四个配置项拿到一套源码或者参考论文想复现结果先别急着跑。往下核对这四个配置项任何一个对不上结果就会有明显偏差配置项需要核对的内容对不上会怎样数据集版本NSL-KDD 的 20% 子集和全量集合差异很大模型指标完全不同分类口径二分类正常/攻击还是多分类按攻击类型细分指标不可比少数类召回率剧烈变化采样策略与位置有没有用 SMOTE是在切分前还是切分后采样的采样后再切分会造成重叠样本指标虚高评估口径用的是 macro-F1 还是 weighted-F1是否去掉了召回率为 0 的类别“99%” 可能只是加权后的假象4.4 高频踩坑清单五条现象、原因与解决办法现象训练时准确率接近 99%测试集只剩 72%。原因训练集和测试集样本重叠模型记住了训练样本而不是学到了泛化规律。解决改用官方训练/测试划分检查两个集合里相同特征模式的样本是否被重复切分。现象加了 SMOTE 之后效果反而变差。原因对标准化之后的数据做插值合成样本脱离了真实特征分布。解决先采样再标准化把k_neighbors从 5 降到 3只对真正的少数类采样而不是所有类别无脑补齐。现象LSTM 训练时 loss 不降准确率一直在基线附近。原因输入还是单条样本没有做窗口化模型学不到序列关系。解决检查输入形状是否是(batch, window, features)用make_windows构造窗口数据。现象复现论文代码时标签预测全是 0一个攻击样本都没检出来。原因标签列里的.后缀没有去掉normal.和normal被分成两个类别模型把“不带点”的标签学成了正常样本。解决读取标签后统一strip()并去掉特殊符号打印value_counts()确认类别数。现象把模型部署到自己的流量数据上报特征列数不匹配的错误。原因新数据的协议类型或服务名出现训练集里没见过的新取值独热编码产生了新列。解决保存训练时的StandardScaler和编码器对未知类别做兜底映射统一列名后再进入标准化管道。5. 落地成可用的检测脚本滑动窗口与阈值告警的最小实现5.1 把离线模型变成在线检测器滑动窗口 阈值告警的最小实现离线训练做完项目文档也写好了接下来是常见的进阶需求把模型变成一个能持续接收流量特征、实时判别的检测脚本。最简做法是滑动窗口加概率阈值。从流量队列里取新到的特征行拼到已有窗口末尾每次只预测最新窗口的输出概率超过阈值就告警。threshold 0.85 # 先跑一段正常流量取预测分数的 95 分位作为初值 def predict_stream(model, scaler, encoder, window64, stride32): buffer [] while True: rows collect_next_rows(stride) # 从流量队列取新的一批 features preprocess(rows, scaler, encoder) # 只 transform不 fit buffer.extend(features) if len(buffer) window: continue window_data np.array(buffer[-window:]) proba model.predict_proba(window_data.reshape(1, -1))[0][1] if proba threshold: alert(proba, rows[-1])threshold是最值得花时间调的东西。我见过有人拍脑袋定成 0.9结果夜间流量一波动告警刷屏。正确的做法是先用模型跑几天的正常流量画出预测分数的分布取 95 分位作为初值再根据误报率上下调整。这个“先找分布、再定阈值”的过程比换模型更能解决真实部署里的误报问题。5.2 误报压不住时的兜底先调阈值再写规则别急着换模型如果告警还是压不住不要马上换模型先做规则兜底。我的做法是对概率落在阈值附近比如 0.8 到 0.95 之间的样本再用几条确定性规则过滤一次比如单包负载大小是否异常、目标端口是否有对应服务响应、连接是否在一秒内反复建立。模型负责给出一个宽泛的怀疑范围规则负责把“模棱两可”的样本快速否决掉。这套组合在误报率和漏报率之间会留出更从容的调节空间。项目文档里也应该把阈值设定过程记录清楚说明它来自多少天、多少条流量的统计而不是经验值。这样别人接手时至少知道该从哪里下手调。我最开始做这类系统时习惯一上来就追求模型指标的极致却忽略了阈值和规则兜底这些“脏活”。结果是在论文里指标漂亮部署到真实环境之后每天被误报消耗注意力最后还是回来老老实实调阈值。后来我把“先跑几天看分数分布、再定阈值、最后用规则兜底”写成了固定流程翻车次数才明显减少。如果你也正在做入侵检测或者打算用这份源码改自己的方案希望这个流程能帮你少走一段弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表