KDD99网络入侵检测实战:Python机器学习与CNN双路线实现
发布时间:2026/10/1 5:18:02来源:尧图网络
简介这是一套基于Python机器学习实现的网络入侵检测系统完整源码适合计算机相关专业学生用于课程设计、期末大作业或毕业设计参考。项目已完成数据预处理、特征提取、模型训练与评估等环节包含KDD Cup数据集压缩文件与多个Python主程序可直接运行无需额外修改。压缩包共16个文件以py源码、xml工程配置、gz数据集和md说明文档为主整体大小17.52MB便于下载后快速导入开发环境。目前已有946人学习下载。除核心训练脚本外资源还附有README说明、IDE配置文件及TensorBoard事件文件方便读者理解项目结构并复现实验过程。对希望完成高质量网络入侵检测课题的学生而言是一份结构完整、经过高分验证的实用参考。1. 先把结论说透这份入侵检测课设源码能跑、能交、还能接着改网络入侵检测这个方向课设和期末大作业最常踩的坑是“数据集太大跑不动”或者“模型只会背数据”。这份基于 Python 机器学习的网络入侵检测系统源码之所以值得拆是因为它同时给了两条路线main.py 走传统机器学习随机森林那一套cnn_main.py 和 mian_cnn.py 走 CNN 深度学习数据用的是 KDD Cup 1999 标准数据集的 10% 版本解压后约 49 万条记录普通笔记本十分钟内能完整跑一轮。项目本身是拿了 97 分的期末大作业结构上把数据预处理、模型训练、评估报告串成了完整闭环适合期末要交机器学习课程设计的学生、想快速搭 IDS 基线的入门工程师以及需要一份能改的代码库来做对比实验的研究者。解压、装环境、按 README 跑你得到的不只是一个分数而是一套能继续往上加东西的框架。2. 把数据喂明白KDD99 的 42 列结构、类别不平衡与预处理脚本2.1 先认识 KDD99为什么过了二十多年它还是课设标配KDD Cup 1999 数据集是网络入侵检测方向最容易被低估的东西。它把每一次 TCP 连接抽象成一条记录41 个特征加一个标签标签要么是 normal要么是某一类攻击。它解决了课设最头疼的问题——数据标注。自己抓流量做标注光清洗就得耗掉大半个学期而 KDD99 拿来就能用标签还是公认的答辩时不需要跟老师解释你的标注标准是什么。这份源码里带两个压缩文件kddcup.data_10_percent.gz 是 10% 采样版约 49 万条kddcup.data.gz 是完整版约 490 万条。10% 版本足够把流程跑通完整版适合最后做一次“模型在大数据量下是否还能稳定”的验证。这里有个小技巧pandas 的 read_csv 可以直接读 gz 文件不需要先手动用 zip 工具解压传参时把路径写对就行。数据集的类别分布极度不平衡这是它最大的特点也是后面所有调参的根源。10% 版本里 smurf 和 neptune 这两类 DoS 攻击占了绝大多数而 R2L远程到本地和 U2R提权类攻击样本少得可怜。这种分布本身就是入侵检测的真实写照——攻击类型天然不平衡。如果直接拿原始数据训练打印出来的准确率会虚高到 99% 以上但模型其实只学会了“只要连接密集就是攻击”R2L 和 U2R 基本全军覆没。所以做预处理时标签映射和采样策略比模型选择更关键。2.2 42 列里装了什么四组特征分别回答什么问题41 个特征可以分为四组理解分组比记住每个特征的名字更重要。第一组是 TCP 连接基本特征包括 duration、protocol_type协议类型tcp/udp/icmp、service目标端口对应的服务、flag连接状态标志、src_bytes 和 dst_bytes 等回答“这条连接是谁发给谁的、发了多少数据”。第二组是内容特征比如 hot敏感文件访问次数、num_failed_logins失败登录次数、logged_in是否登录成功、root_shell是否获得 root shell这些特征看着像主机日志实际是模拟从网络包内容里提取出来的行为信号专门用来抓 R2L 和 U2R 这类需要交互的攻击。第三组是基于时间的流量特征count 和 srv_count 分别统计过去 2 秒内与当前连接同目标主机、同服务的连接数serror_rate、rerror_rate 统计 SYN 错误和 REJ 错误的比例。DoS 攻击的特征是短时间内连接数暴涨这组特征几乎是专门为抓 DoS 设计的。第四组是基于主机的流量特征以 dst_host_ 开头统计过去 100 条连接里目标主机的行为模式。四组特征合起来就是 3 个符号型protocol_type、service、flag加 38 个数值型总共 41 个最后一个字段是标签列。特征组代表特征回答的问题连接基本特征duration, protocol_type, service, flag, src_bytes连接本身是什么形态内容特征hot, num_failed_logins, root_shell连接里有没有攻击交互行为时间流量特征count, srv_count, serror_rate过去 2 秒内这个主机/服务是不是异常密集主机流量特征dst_host_count, dst_host_srv_count过去 100 条连接里目标主机的整体画像标签列原始值有二十几种攻击名加 normal比如 neptune、smurf、satan、guess_passwd、buffer_overflow 等等。项目里 handle2.py 做的事就是把它们映射到五个大类normal、DoS、Probe端口扫描探测、R2L、U2R。不映射直接训练也可以但二十几个类别的分类器在样本不平衡下会学到一团浆糊而且答辩时老师大概率会问“为什么不做成二分类或者五大类”提前映射好是最稳的。2.3 handle2.py 做了什么一条命令把原始数据变成能训练的中间文件handle2.py 是项目里的数据预处理入口它把 42 列数据清洗成后续两个模型都能直接读的中间格式。一段常见还原逻辑如下实际脚本里顺序可能略有差异但核心步骤就是这四步。# handle2.py 核心逻辑常见还原版 import pandas as pd import numpy as np 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(kddcup.data_10_percent.gz, headerNone, namescolumns) # 第一步去重。KDD99 原始记录里重复样本占比很高 before len(df) df.drop_duplicates(inplaceTrue) print(f去重前 {before} 条 - 去重后 {len(df)} 条) # 第二步把 label 映射成五大类 attack_map { neptune: DoS, smurf: DoS, back: DoS, teardrop: DoS, pod: DoS, land: DoS, apache2: DoS, udpstorm: DoS, processtable: DoS, worm: DoS, satan: Probe, ipsweep: Probe, nmap: Probe, portsweep: Probe, mscan: Probe, saint: Probe, guess_passwd: R2L, warezmaster: R2L, warezclient: R2L, imap: R2L, ftp_write: R2L, multihop: R2L, phf: R2L, spy: R2L, xlock: R2L, xsnoop: R2L, snmpguess: R2L, snmpgetattack: R2L, httptunnel: R2L, named: R2L, sendmail: R2L, buffer_overflow: U2R, loadmodule: U2R, perl: U2R, rootkit: U2R, ps: U2R, sqlattack: U2R, xterm: U2R } df[label_group] df[label].map(attack_map).fillna(normal) # 第三步把 inf 替换成 0避免后面标准化炸掉 df.replace([np.inf, -np.inf], 0, inplaceTrue) # 第四步输出清洗后的中间文件 df.to_csv(kdd_clean.csv, indexFalse) print(df[label_group].value_counts())这段代码有四个关键点。第一pd.read_csv 直接读 gz 压缩包headerNone 配合 namescolumns 手动指定 411 个列名因为原始文件里没有表头。第二drop_duplicates 必须放在标签映射之前否则重复样本的标签统计会失真。第三attack_map 里的攻击名覆盖了 KDD99 10% 数据集里出现的主要类型fillna(normal) 保证没有映射到的原始标签都落入 normal避免出现“未知类别”导致训练报错。第四inf 替换成 0 这一步看着不起眼实际上 KDD99 原始数据里确实存在无穷值不处理的话后面 StandardScaler 会直接抛异常。执行完这个脚本kdd_clean.csv 就是干净的输入。项目里 train 和 test 两个目录如果存在通常是按日期或按主机切好的子集main.py 和 cnn_main.py 读的路径不一样跑之前先看一眼 README.md.bak 里的说明。那个 bak 后缀文件其实就是原始 README 的备份作者保留它估计是怕自己改坏你也可以直接拿它当参考。3. 机器学习路线main.py 的特征工程、模型对比与阈值调整3.1 为什么先跑传统机器学习而不是直接上 CNN拿到这份源码我建议你先跑 main.py再碰 CNN。理由有三层。第一KDD99 的 41 个特征里有 38 个是数值型的特征含义清晰随机森林这类树模型能直接利用特征本身的语义不需要像 CNN 那样先把数据重排成图像。第二传统机器学习训练快、可解释性强答辩时老师问“为什么这条连接被判成 DoS”你可以用特征重要性和树的分裂规则讲明白而 CNN 的黑匣子属性在这个数据集上反而成了减分项。第三main.py 跑出来的结果可以作为 CNN 的基线如果 CNN 的准确率还比不过随机森林说明数据预处理或模型结构有问题这个对照逻辑在答辩时非常加分。从文件命名也能看出作者的思路。main.py 是主入口handle2.py 是数据预处理cnn_main.py 是 CNN 版本mian_cnn.py 是原始文件名就这样拼的不是笔误运行不受影响。但如果你要交报告最好在代码里统一改名避免答辩时被老师挑刺。这种细节属于血泪经验文件名里的小瑕疵不影响运行却影响印象分。3.2 预处理管线ColumnTransformer 一次搞定数值标准化和 One-Hotmain.py 里最值得学的不是模型本身而是预处理管线的写法。41 个特征里 protocol_type、service、flag 是符号型其余是数值型。符号型不能直接塞进模型——如果 sklearn 的 LabelEncoder 把 tcp 编成 0、udp 编成 1、icmp 编成 2模型就会误以为 icmp 和 udp 之间“比 udp 和 tcp 更接近”这种不存在的顺序关系会污染整个特征空间。正确做法是 One-Hot三个符号列展开成几十个 0/1 列每列回答“是不是这个取值”。数值型特征则需要标准化。src_bytes 的取值范围可能从 0 到几百万而 serror_rate 是 0 到 1 的小数如果不做标准化树的决策不受影响但逻辑回归和 MLP 这类基于梯度的模型会被量纲大的特征带偏。用 ColumnTransformer 把两类预处理拼进一条管线是最简洁的写法。# main.py 预处理与模型训练核心逻辑还原 import pandas as pd from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, StandardScaler from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report df pd.read_csv(kdd_clean.csv) cate_cols [protocol_type, service, flag] num_cols [c for c in df.columns if c not in cate_cols and c not in (label, label_group)] pre ColumnTransformer([ (num, StandardScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cate_cols) ]) X pre.fit_transform(df) y df[label_group] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) print(f训练集 {X_train.shape[0]} 条测试集 {X_test.shape[0]} 条) clf RandomForestClassifier( n_estimators200, max_depth20, min_samples_leaf2, n_jobs-1, random_state42 ) clf.fit(X_train, y_train) print(classification_report(y_test, clf.predict(X_test)))逻辑说明ColumnTransformer 的第一个元组对 num_cols 做标准化第二个元组对三个符号列做 One-Hothandle_unknownignore 保证测试集里出现训练集没见过的服务名时不报错。随机森林的 max_depth 限制在 20不是越大越好KDD99 上深层树容易记住样本噪声min_samples_leaf2 让每个叶子至少有两个样本削弱过拟合。stratifyy 这个参数值得单独说——它保证切分后训练集和测试集里 normal、DoS、Probe、R2L、U2R 的比例与原数据一致否则随机切分可能把原本就很少的 U2R 样本全切进训练集或测试集后续评估完全失真。3.3 模型对比与评估别只盯 Accuracy要看每一类的召回率跑完这个脚本你大概率会看到 DoS 召回率 0.99 以上Probe 也不错但 R2L 和 U2R 的召回率惨不忍睹甚至可能只有 0.0 到 0.2。这是 KDD99 上所有模型都要面对的坎不是代码写错了。原因有二样本量太少R2L 和 U2R 在数据集中占比通常不到 1%模型很难学到它们和 normal 的区分边界特征表达不够强这两类攻击在单条连接级别跟正常连接几乎没有差异需要结合多条连接的上下文行为才能识别。我的习惯是把 classification_report 转成混淆矩阵按行看召回、按列看精度。召回低说明该抓的没抓到比如 U2R 被大量判成 normal这是最危险的情况精度低说明抓错了比如把一堆 normal 判成 DoS会造成误杀。main.py 输出的 report 里重点关注 weighted avg 的 f1-score 和 macro avg 的差距。差距大说明模型在多数类上强、在少数类上弱需要做类别重加权或过采样。sklearn 的 RandomForestClassifier 有个 class_weightbalanced 参数开了之后模型会给少数类更高的误分惩罚代码只改一个参数R2L 和 U2R 的召回率通常能从 0.1 拉到 0.4 左右代价是 normal 的误报率轻微上升。这个权衡在报告里写清楚比单纯堆准确率有说服力得多。如果你用的是逻辑回归同样的参数叫 class_weight效果类似。4. CNN 路线把 41 维流量特征重排成 8x8 图像再交给卷积网络4.1 表格数据为什么要用 CNN卷积的局部感受野能学到什么看到 CNN 用在 KDD99 这种表格数据上第一个问题一定是“图像卷积为什么能处理一行特征向量”。这里要理清一个概念CNN 不是只能在图像上用它学的是局部模式。把 41 个特征排成 8x8 的矩阵后相邻格子比如 count 和 srv_count或者 src_bytes 和 dst_bytes在空间上靠近卷积核就能学到“这两个特征同时异常”的组合模式。DoS 攻击的特征组合是 count 高、serror_rate 高、same_srv_rate 高这三个特征如果被卷积核覆盖在同一感受野里网络就能学到它们的联合响应。这是 CNN 在这个场景下比全连接网络更有解释力的地方。当然这种重排本身有玄学成分——8x8 的排列顺序不唯一不同的排法会让不同特征在空间上相邻最终效果确实有差异。常见的做法是先按四组特征的语义分组排列把时间流量特征放在一起、主机流量特征放在一起这样卷积核更容易学到组内的联合模式而不是随意打乱。cnn_main.py 里用的就是这种分组重排的思路跑代码前可以先看一眼 README.md.bak 确认输入通道数。4.2 从 41 维到 8x8补零 reshape 的代码与注意事项CNN 需要固定尺寸的输入而 KDD99 每条记录是 41 维特征。41 不是完全平方数不能直接 reshape 成方阵所以要先补零到 64再 reshape 成 8x8。补零的思路是原特征放在前 41 位第 42 到 64 位填 0对应的图像区域相当于“灰色空白”。这样做不会引入虚假信号卷积核在这片区域学到的是零响应等于把特征矩阵“左上角对齐”地放进一张图里。# CNN 输入构造41 维特征 - 补零到 64 - reshape 成 (8, 8, 1) import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder df pd.read_csv(kdd_clean.csv) # 注意符号列要在这里先做序数编码保证 41 维输入的总列数不变 for col in [protocol_type, service, flag]: df[col] LabelEncoder().fit_transform(df[col]) feature_cols [c for c in df.columns if c not in (label, label_group)] X_raw df[feature_cols].values.astype(np.float32) y_raw df[label_group].values # 标签转成五类整数 label_map {normal: 0, DoS: 1, Probe: 2, R2L: 3, U2R: 4} y np.array([label_map[v] for v in y_raw]) # 补零到 64 再 reshape 成 8x8 单通道 X_img np.zeros((len(X_raw), 64), dtypenp.float32) X_img[:, :41] X_raw X_img X_img.reshape(-1, 8, 8, 1) print(f输入形状: {X_img.shape}, 标签形状: {y.shape})逻辑说明这段代码先把三个符号列用 LabelEncoder 转成整数因为 CNN 路线不再做 One-Hot——One-Hot 会把 41 维撑成上百维补零到 64 的方案就失效了。补零到 64 后 reshape 成 (样本数, 8, 8, 1)最后一个 1 是通道数相当于灰度图。如果想让输入更接近彩色图的 3 通道可以把同样的特征复制三份或者用三种不同的归一化方式生成三个通道cnn_main.py 的输入层定义会告诉你它用的是哪种通道数跑之前先打印 model.summary() 看一眼。4.3 cnn_main.py 的模型结构与训练参数CNN 主体是两层卷积加全连接配合 Dropout 防过拟合。输入 8x8x1经过 32 个 3x3 卷积核提取局部特征再池化缩小尺寸第二层用 64 个卷积核学习更抽象的组合最后展平接 128 维全连接层输出 5 分类概率。# cnn_main.py 模型定义与训练核心逻辑还原 import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import (Conv2D, MaxPooling2D, Flatten, Dense, Dropout) from tensorflow.keras.optimizers import Adam from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X_img, y, test_size0.2, stratifyy, random_state42 ) model Sequential([ Conv2D(32, (3, 3), activationrelu, paddingsame, input_shape(8, 8, 1)), MaxPooling2D((2, 2)), Conv2D(64, (3, 3), activationrelu, paddingsame), Flatten(), Dense(128, activationrelu), Dropout(0.5), Dense(5, activationsoftmax) ]) model.compile( optimizerAdam(learning_rate1e-3), losssparse_categorical_crossentropy, metrics[accuracy] ) history model.fit( X_train, y_train, validation_data(X_test, y_test), epochs20, batch_size128, verbose1 ) model.save(ids_cnn.h5)参数上这几个值值得解释。paddingsame 让 3x3 卷积在 8x8 边缘不缩尺寸8x8 本身很小一旦缩到 6x6 再池化就只剩 3x3第二次卷积就没法做了所以边界填充是必须的。Dropout(0.5) 放在全连接层之前随机让一半神经元失活防止 128 维全连接把训练集记住。loss 用 sparse_categorical_crossentropy 而不是 categorical_crossentropy因为标签是整数而非 One-Hot用错 loss 会在训练时报 shape 不匹配。batch_size 128 对应约 40 万条训练样本20 个 epoch 在 GTX 1060 级别显卡上大约几分钟纯 CPU 也就十几分钟这个量级完全不用上云。训练完的 ids_cnn.h5 是 H5 格式权重后续接新数据直接 model.load_weights 就行。如果观察 history 里 validation_loss 在训练后期反弹说明过拟合了常见做法是把 Dropout 从 0.5 提到 0.7或者把 batch_size 翻倍。如果训练集和验证集准确率差距一直很大先回去检查 handle2.py 的输出很大概率是符号列的编码方式不一致导致 CNN 学到的模式在测试集上对不上。5. 避坑手册跑这个项目必踩的五个坑与排查方法这五个坑是我在复现过程中真实踩过的排在前两位的几乎每个跑这个项目的人都会遇到。下面每条都按现象、原因、解决的顺序写你可以直接对号入座。5.1 坑一数据里有 inf标准化直接翻车现象跑 main.py 时 sklearn 在 fit 阶段直接抛 ValueError提示输入里有 NaN 或 infinite或者更隐蔽——训练能跑完但 loss 在第一个 epoch 就变成 nan后面全是 nan模型等于白训。第一次跑项目时最容易遇到因为 KDD99 的原始数据文件看起来干干净净read_csv 也不报错问题藏在数据里面。原因KDD99 原始数据里确实存在 inf 值最典型的是 duration 字段在连接未完成时写入无穷大。StandardScaler 在计算均值和方差时遇到 inf结果直接就毁了后续所有特征都跟着错。解决handle2.py 里 df.replace([np.inf, -np.inf], 0, inplaceTrue) 这一步不能省。如果你自己重写预处理记得在 fit 之前打印 df.describe()看每个特征的 max 列凡是出现 inf 的列都要处理不能只处理 duration。血的教训是光靠 StandardScaler 的 with_meanFalse 救不回来inf 必须清洗在前。5.2 坑二符号特征直接 LabelEncoder模型学出不存在的顺序现象训练不报错classification_report 里准确率看着还行但把 tcp/udp/icmp 单独拿出来看模型对 udp 的识别总是比 icmp 好或者换了数据集之后效果崩掉。原因LabelEncoder 给符号列编的序数没有语义tcp0、udp1、icmp2 让树模型和神经网络误以为三者之间有大小关系。如果恰好 udp1、icmp2模型学到的是“icmp 的特征强度是 udp 的两倍”这完全是假的。解决传统机器学习路线用 One-Hotmain.py 的 ColumnTransformer 里就是这么做的。CNN 路线因为要控制维度才退而求其次用 LabelEncoder但要在报告里明确这个取舍。如果介意CNN 侧也可以改成 Embedding 层把符号特征映射成稠密向量再接进去效果一般比直接 LabelEncoder 好但模型复杂度会上一个台阶。5.3 坑三类别不平衡导致准确率虚高答辩被问住现象main.py 打印出的准确率 99.2%但 R2L 和 U2R 的召回率是 0.0precision、recall、f1 三列全是 0。一眼看去报告很漂亮实际上模型对少数类完全失效。原因smurf 和 neptune 两类 DoS 占了样本绝大部分模型只要“见到密集流量就报 DoS”就能拿到 99% 准确率R2L 和 U2R 这些少数类根本没学到。解决三层手段。第一切分时 stratifyy保证训练集和测试集的类别比例一致这是前提。第二给模型加 class_weightbalanced让少数类误分代价翻倍。第三如果还不行用 imbalanced-learn 的 SMOTE 对少数类过采样但要注意 SMOTE 不能用在测试集上否则评估结果失真。报告里把三层手段写清楚从“虚高 99%”到“各类别都过 0.8”这个改进过程本身就是很好的课设工作量。5.4 坑四TensorFlow 版本不兼容cnn_main.py 跑不起来现象运行 cnn_main.py 报 module tensorflow has no attribute placeholder或者 keras 导入直接失败又或者模型 summary 里的层名称跟你预期完全对不上。原因项目压缩包里 events.out.tfevents.1482980284.zjx-24000635 这个文件暴露了时间——1482980284 是 2016 年底的时间戳当时的 TensorFlow 还是 1.xKeras 也是独立包。现在主流环境装的是 TF 2.xAPI 变化非常大直接跑旧代码必翻车。解决两种路线。省事路线是创建 Python 3.6 TensorFlow 1.15 的虚拟环境conda create -n ids python3.6然后 pip install tensorflow1.15 keras2.3.1这条路线跑最老的代码完全不用改。现代路线是把代码里 tf. 开头的 API 逐个迁移到 tf.compat.v1或者像第 4.3 节那样直接用 TF 2.x 的 keras 重写模型定义效果一样但代码更干净。我一般推荐后者因为交完作业模型还要继续用旧环境总有一天会装不上。5.5 坑五环境与路径问题解压后第一波报错都是这个现象解压后直接跑 main.py报 FileNotFoundError 找不到 kddcup.data_10_percent.gz或者 ImportError 找不到 sklearn/tensorflow一堆红字刷屏人直接懵掉。原因两个问题叠加——压缩包解压到了带中文或空格的路径py 文件里的相对路径失效pip 依赖没装全常见的缺 scikit-learn、pandas、tensorflow。解决第一步项目解压到纯英文路径比如 D:\ids-kdd99避免空格和中文字符。第二步建虚拟环境python -m venv ids_env 然后激活再 pip install pandas scikit-learn tensorflow matplotlib用 vscode 打开项目根目录并选中这个虚拟环境作为解释器避免跑脚本时用的还是全局 Python。第三步确认 main.py 和两个 gz 数据文件在同一个目录下handle2.py 里的相对路径才不会断。这一套环境配置做完九成以上的“跑不起来”都能被解决剩下的再逐个看报错。6. 进阶用法用混淆矩阵定位误报把模型接到新数据上6.1 打开混淆矩阵看准说“模型哪里不行”classification_report 只能告诉你数字混淆矩阵能告诉你模式。跑完 main.py 后顺手把混淆矩阵打印出来重点看两个格子normal 被判成 DoS 的误报格和 R2L/U2R 被判成 normal 的漏报格。# 混淆矩阵与误报样本定位 from sklearn.metrics import confusion_matrix import pandas as pd y_pred clf.predict(X_test) labels [normal, DoS, Probe, R2L, U2R] cm confusion_matrix(y_test, y_pred, labelslabels) # 找出 normal 被误报成 DoS 的样本回溯原始特征 mis (y_test normal) (y_pred DoS) print(fnormal 误报成 DoS 的样本数: {mis.sum()})这一步会暴露一个隐蔽问题被误报的 normal 样本往往有高 count 和高 serror_rate也就是它本身长得很像攻击流量。这种误报在入侵检测场景里是最难处理的——不是模型错了而是特征层面确实分不开。常见做法是提高正常流量的类别权重让模型在拿不准时更倾向于判 normal毕竟误杀比漏报在业务上更伤信任。6.2 把模型接到新流量上特征顺序必须严格一致模型不是玩具训练完要能用。把训练好的 RandomForestClassifier 或 CNN 权重接到新数据上最大的坑是列顺序。新数据的 41 个特征必须和 handle2.py 输出的列顺序完全一致一个位置都不能错。最优做法是把预处理管线保存成 joblib 文件预测时用同一个 pipeline 处理新数据。# 保存与加载保证预测时预处理逻辑与训练时一致 import joblib joblib.dump(pre, pre_pipeline.joblib) joblib.dump(clf, ids_rf.joblib) # 新数据预测 # new_df pd.read_csv(new_traffic.csv, namescolumns) # pre joblib.load(pre_pipeline.joblib) # clf joblib.load(ids_rf.joblib) # X_new pre.transform(new_df) # pred clf.predict(X_new)这里有三个习惯值得养成。第一模型和预处理管线一起保存永远不要单独存模型否则换环境后你根本不知道特征是怎么处理的。第二新数据的符号列里如果出现训练集没见过的服务名OneHotEncoder 的 handle_unknownignore 会把它全部置 0不会报错但会损失信息预测前先看 service 分布。第三CNN 侧的新样本要重复第 4.2 节的补零 reshape 流程前后逻辑不一致模型输出会直接错乱。这套项目我从拿到到跑通前后花了两天大部分时间都耗在环境兼容和数据处理上真正调模型只用了半天。从那以后我每次交机器学习课设都会强制走一遍完整流程先看数据分布再做基线模型再上复杂模型最后用混淆矩阵复盘误报。顺序一乱返工是必然的。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网