首页 / 资讯中心 / 文章详情

KDD99数据集与一维CNN实现网络入侵检测的完整实践指南

KDD99数据集与一维CNN实现网络入侵检测的完整实践指南 ★ FEATURED ARTICLE
简介压缩包内含完整的基于Python机器学习的网络入侵检测系统源码与全部数据面向计算机相关专业正在进行课程设计、期末大作业或需要项目实战练习的学生。系统基于KDD Cup数据集完成网络流量分类与入侵检测覆盖数据预处理、CNN模型构建、训练评估与检测等环节适合具备Python和机器学习基础的学习者作为综合性实战参考。整个包共16个文件大小约17.52MB以py脚本、gz数据压缩包、xml配置及md说明等类型为主其中py文件用于模型训练与运行gz存放kddcup等原始数据xml与iml为项目工程配置md为使用说明目录结构清晰便于按模块调用。目前已有281人学习下载项目源码经过严格调试下载后即可直接运行且包含全部数据、多份训练日志与检测主程序可快速复现入侵检测流程也可作为毕业设计或课程报告的支撑材料。1. 一个 1999 年的数据集养活了 20 多年的课程设计先搞清楚手里这份资源是什么如果你在找「基于 python 机器学习的网络入侵检测系统源码」八成是要交课程设计或期末大作业。这份包里装的不是什么花哨的东西一个 KDD Cup 1999 网络入侵数据集的两个 gz 压缩包一套基于 CNN卷积神经网络的入侵检测训练脚本外加一个 README 和几份训练日志。但恰恰是这套组合每年都能救一批计算机专业学生的大作业。KDD99 数据集虽然老但它是目前课程设计里最容易讲清楚「数据预处理 → 特征工程 → 模型训练 → 评估」全流程的公开数据集。CNN 部分对应的就是mian_cnn.py和cnn_main.py数据处理入口是handle2.py数据是kddcup.data_10_percent.gz和kddcup.data.gz。这套资源适合两类人一是要交课程设计、期末大作业需要「能用、能讲、能演示」的人二是想练手机器学习完整流程又不想自己找数据、洗数据的学习者。下面从数据开始把这个包一层层拆开。2. 数据篇把 kddcup.data 变成模型能吃的张量符号特征映射与归一化2.1 先认清 42 列里有什么三类符号特征和五类标签KDD99 的每条连接记录有 42 列前 41 列是特征最后一列是标签。41 个特征里有 3 个是纯符号类型的protocol_type协议类型取值如 tcp、udp、icmp、service目标端口对应的服务如 http、ftp、telnet、flag连接状态标志如 SF、REJ、S0。剩下 38 列是数值型比如连接时长duration、从源到目的地的字节数src_bytes、失败登录次数failed_logins等。标签列分两大类normal表示正常流量剩下的就是攻击。攻击在课程设计里一般按大类归成四类DoS拒绝服务、Probe端口扫描或漏洞探测、R2L来自远程机器的未授权访问、U2R未授权访问本地超级用户权限。所以你的模型要做的是 5 分类不是二分类。这个归类直接决定了后面的标签编码方案。很多刚开始做的同学会在标签上犯迷糊KDD99 原始训练集里其实有 22 种具体的攻击名称比如neptune、satan、warezclient。你不管做课程设计还是期末作业最佳做法是先做一次大类的映射把 22 种攻击并成 4 类加一个正常类否则类别太多、每个类的样本又少模型根本训不动。2.2 数据拆包与 10% 子集的选择压缩包里的kddcup.data_10_percent.gz是完整数据集kddcup.data.gz的 10% 采样约 49 万条记录。课程设计首选 10% 版本理由很简单完整版接近 490 万条直接训练一维 CNN 在 CPU 上要跑很久甚至会把内存吃满。10% 版本在保证各类攻击都有覆盖的前提下能把单轮训练时间压缩到分钟级。解压命令如下# 在项目根目录执行解压出 10% 子集和完整数据集 gunzip -k kddcup.data_10_percent.gz gunzip -k kddcup.data.gz # 解压后确认行数和列数用 wc 校验 wc -l kddcup.data_10_percent-k参数的意思是保留原始 gz 文件不删除而wc -l用来确认解压出来的文件行数是否为 494021 行左右。如果行数对不上说明解压过程中文件损坏那就得重新下载压缩包别硬着头皮往下跑。10% 子集训练完完整版的用途是给你做「跨数据集验证」模型在 10% 数据上训练再用完整数据的一部分做测试看泛化能力。课程设计答辩时这个操作能明显加分因为绝大多数同学只在同一个数据集上自训自测。2.3 字符串编码与数值归一化两类最常见的预处理handle2.py的核心工作就是把 42 列文本变成模型能吃的数值张量。字符串特征必须映射成整数常见做法是建一个字符到索引的字典比如tcp - 0, udp - 1, icmp - 2。数值特征则需要归一化否则像src_bytes这种动辄上万的值会直接压垮 CNN 的梯度更新。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler, LabelEncoder # 读取解压后的数据集没有列名就手动指定 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,label ] df pd.read_csv(kddcup.data_10_percent, headerNone, namescols) # 符号特征分别做标签编码 for col in [protocol_type, service, flag]: le LabelEncoder() df[col] le.fit_transform(df[col]) # 标签先映射成大类再做编码 attack_map { normal: normal, neptune: dos, back: dos, smurf: dos, teardrop: dos, pod: dos, land: dos, apache2: dos, udpstorm: 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, 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 } df[label_category] df[label].map(attack_map) label_enc LabelEncoder() df[label_encode] label_enc.fit_transform(df[label_category]) # 数值特征归一化41 列特征统一缩放到 [0, 1] feature_cols cols[:-1] scaler MinMaxScaler() df[feature_cols] scaler.fit_transform(df[feature_cols])这段代码的关键有两个attack_map把 22 种攻击名映射成 4 个大类这是为了让模型学得动MinMaxScaler把数值特征压到 [0,1] 区间保证 CNN 的卷积核在不同特征的梯度更新上不偏科。fit_transform会先计算训练集的 min 和 max再执行缩放。测试集必须用同一个 scaler 做transform不能重新fit这是新手最容易犯的泄漏错误。2.4 构造模型输入从 CSV 行到 (batch, 41, 1) 张量一维 CNN 的输入一般是三维张量(样本数, 特征长度, 通道数)。在这里特征长度就是 41通道数设为 1。也就是说把每一条网络连接记录看成「一个长度为 41 的序列每个位置有一个数值」跟自然语言处理里把一个句子看成「一个长度为 N 的词序列」是一个道理。from sklearn.model_selection import train_test_split X df[feature_cols].values.astype(np.float32).reshape(-1, 41, 1) y df[label_encode].values # 按 7:3 切分stratify 保证每个类别的比例在训练/测试里一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) print(X_train.shape, y_train.shape)reshape(-1, 41, 1)把二维数组变成三维张量-1表示自动推断样本数。stratifyy是处理 KDD99 这种不均衡数据集的必备参数如果不按类别比例抽样可能测试集里一个 R2L 样本都分不到模型对这种攻击的召回率就永远是 0。random_state 固定为 42 是为了让实验结果可复现答辩时能拿出同一组数字。到这里数据层面已经全部打通解压、读入、符号编码、标签映射、归一化、切分、reshape。接下来进入模型部分。3. 模型篇一维 CNN 做入侵检测为什么不用决策树以及两个训练入口怎么分工3.1 选型CNN 凭什么处理网络连接数据做入侵检测的经典方案一直是随机森林、XGBoost 这类传统机器学习模型因为它们表格数据上稳定、可解释性强。但课程设计偏偏用 CNN原因有三第一CNN 能自动提取相邻特征之间的局部关系比如count过去 2 秒内对同一主机的连接数和srv_count过去 2 秒内对同一服务的连接数之间能组合出「短时间内大量连接同一服务」这种 DoS 攻击的特征第二CNN 在答辩时更好讲网络结构、卷积核、池化层全是可视化的内容比「一堆树投票」显得工作量足第三这份资源的定位就是 CNN 实现你没必要换成别的模型。一维 CNN 的处理流程是输入 (batch, 41, 1) 张量 → 经过几层 Conv1D 卷积 → ReLU 激活 → MaxPooling1D 池化 → Flatten 展平 → 全连接层 → Softmax 输出 5 个类别的概率。Conv1D 的卷积核在 41 个特征维度上滑动天然适合这种「特征间存在局部相关性」的数据。3.2 看懂 mian_cnn.py 的模型结构mian_cnn.py注意这是包里的原始文件名拼写就是main的变体是这个资源的核心训练脚本。它定义的模型大致结构如下from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout def build_cnn(input_shape(41, 1), num_classes5): model Sequential([ Conv1D(filters64, kernel_size3, activationrelu, input_shapeinput_shape), MaxPooling1D(pool_size2), Conv1D(filters128, kernel_size3, activationrelu), MaxPooling1D(pool_size2), Flatten(), Dense(128, activationrelu), Dropout(0.5), Dense(num_classes, activationsoftmax) ]) model.compile( optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy] ) return model model build_cnn() model.summary()两层 Conv1D 加池化是这份资源能稳定跑通的配置。filters64是第一层卷积核数量决定模型提取 64 种不同的局部特征kernel_size3是卷积核长度每次看 3 个相邻特征比如把count、srv_count、serror_rate组合起来感知异常Dropout(0.5)在训练时随机丢弃一半神经元防止过拟合——KDD99 样本量很大但特征只有 41 维全连接层非常容易过拟合dropout 是必需品。sparse_categorical_crossentropy配合整数标签使用不需要做 one-hot 编码这是代码里最常见的配置。如果你在别的参考代码里看到categorical_crossentropy那对应的标签必须先.to_categorical()变成 one-hot 格式。3.3 训练参数与两个入口脚本的分工资源包里有两个名字很接近的脚本main.py和cnn_main.py。这里面是有分工的main.py偏向数据加载与预处理的全流程演示适合第一次跑通cnn_main.py是完整模型训练脚本包含训练、验证和模型保存。mian_cnn.py则是模型的网络结构定义和训练循环你在课程设计报告中描述的「模型架构图」基本就是从它的model.summary()里抄出来的。训练参数我一般会这样设置from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint model build_cnn() callbacks [ EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue), ModelCheckpoint(best_model.h5, monitorval_accuracy, save_best_onlyTrue) ] history model.fit( X_train, y_train, batch_size256, epochs30, validation_data(X_test, y_test), callbackscallbacks, verbose1 )batch_size256是 CPU 训练时的平衡点小于 64 会让梯度更新太频繁、训练很慢大于 512 会让梯度下降方向过于「平均」收敛变慢。EarlyStopping监控验证集损失连续 5 轮不下降就停restore_best_weights 会把模型回滚到验证集最好的那一步。epochs 设 30 只是个上限实际通常在第 8 到 12 轮就会触发早停。训练时观察 loss 曲线有个规律如果训练 loss 持续下降但验证 loss 在第 6 轮左右开始反弹就是过拟合dropout 可以提到 0.6或者把全连接层神经元从 128 减到 64。3.4 跑起来从 main.py 到 cnn_main.py 的执行顺序拿到这份源码后的运行顺序不是随机的我建议按这个顺序来# 第一步先跑 main.py它会加载数据并打印数据概况 python main.py # 第二步确认数据能正确加载后再跑模型训练脚本 python cnn_main.py # 第三步训练期间另开终端看 TensorBoard 日志 tensorboard --logdirmulti_logsmulti_logs目录存放的是 TensorBoard 事件文件也就是events.out.tfevents.1482980284.zjx-24000635这个文件。用tensorboard --logdirmulti_logs打开后在浏览器访问http://localhost:6006就能看到训练过程中的 loss 和 accuracy 曲线。main.py先跑的意义在于快速暴露环境问题如果你的机器缺少 pandas、sklearn 或者 TensorFlow 版本不对main.py 会在第一行数据处理时就报错而不会让你傻等 CNN 训练几小时后才发现问题。跑通 main.py 相当于给环境做了一次体检。4. 避坑篇这份源码最容易翻车的五个位置4.1 现象import 直接报 ModuleNotFoundError 或者 AttributeErrortfevents文件的时间戳1482980284换算过来是 2016 年 12 月说明这套代码大概率是 TensorFlow 1.x 时代写的。如果你的环境装的是 TensorFlow 2.x直接import tensorflow跑旧代码会出现tf.placeholder不存在、tf.contrib找不到之类的报错。原因就是 TF 2.x 把 1.x 的大量 API 移除或改名了。解决方法是安装 TensorFlow 1.15 版本或者用兼容层import tensorflow as tf tf.compat.v1.disable_eager_execution() # 关掉 eager 模式兼容旧代码如果源码里用了slim tf.contrib.slim这类写法TF 2.x 里tf.contrib整个被移除最稳的方案是建一个 Python 3.6 TensorFlow 1.15 的虚拟环境来跑。我一般会用conda create -n ids python3.6单独建环境避免污染主环境。4.2 现象README.md 打开是乱码或截断但 README.md.bak 正常这个包里有两个 README 文件.bak是备份文件。别因为它是备份就删掉——当主 README 内容缺失或编码转换失败时.bak通常是最后一次成功保存的完整版本。解决方法是先用file README.md看编码格式再用iconv转换编码比如iconv -f gbk -t utf-8 README.md.bak README.md。如果两个文件都打不开也没关系代码结构和运行步骤从main.py和cnn_main.py的注释里基本能还原出来。4.3 现象模型训练报错 could not convert string to float这是数据处理阶段最经典的翻车现场。原因是protocol_type、service、flag这三列字符串没有做编码直接把 DataFrame 喂给了模型。pandas 的fit_transform在这里只管数值列字符串列如果不单独处理就会报这个错。解决方法是回到 2.3 节那段代码确保三个符号列用LabelEncoder编码后再合并回特征矩阵。一个检测技巧在训练前打印df[feature_cols].dtypes如果出现object类型就说明字符串列漏处理了。4.4 现象loss 一直在降accuracy 到了 90% 以上但模型基本废了KDD99 的标签分布极其不均衡DoS 类占了一半以上R2L 和 U2R 各自只占不到 1%。如果直接训练模型会「偷懒」——把所有样本都预测成 DoS 或 normal就能拿到很高的 accuracy但对 R2L 和 U2R 的召回率几乎为 0。判断方法是看训练结束后的混淆矩阵或者看每个类别的准确率。解决方法是给损失函数加类别权重让少数类样本的损失被放大from sklearn.utils.class_weight import compute_class_weight class_weights compute_class_weight( class_weightbalanced, classesnp.unique(y_train), yy_train ) class_weight_dict dict(enumerate(class_weights)) model.fit(X_train, y_train, class_weightclass_weight_dict, ...)注意compute_class_weight的 auto 模式下少数类会被分配较大的权重这会让模型在 R2L、U2R 上的召回率明显提升但代价是整体 accuracy 会掉 1-2 个百分点这是值得的。4.5 现象TensorBoard 启动后看不到任何数据启动 tensorboard 后浏览器一片空白原因通常有两个一是--logdir指向的目录不对二是 TF 版本与 TensorBoard 版本不兼容导致日志格式读不出来。先确认multi_logs目录下确实有.tfevents文件再用tensorboard --logdirmulti_logs --port6006显式指定端口。如果还是空白试试降级 TensorBoard 到 2.x 早期版本或者升级到 2.10 以上新版本对旧格式日志的兼容性通常更好。5. 评估篇不要只看 accuracy在 KDD99 上做五分类混淆矩阵与类别级召回5.1 指标怎么选偏斜数据集上 accuracy 的欺骗性KDD99 评估里最重要的一个认知是accuracy 不是核心指标。因为数据极度偏斜一个「全预测成 normal」的平庸模型在 accuracy 上也能拿到 50% 以上如果用 10% 子集里的原始分布一个永远输出 DoS 的模型甚至能到 60%。所以课程设计报告里必须重点展示 Precision、Recall、F1-score 这三个指标并且按类别分别计算。Recall召回率在这里最为关键它回答的问题是「这个类别的攻击模型漏掉了多少」。对 R2L、U2R 这两个小类漏掉一个都是不可接受的你需要在报告里明确解释为什么召回率比 accuracy 更有实际意义。5.2 用 sklearn 算五类混淆矩阵与分类报告这里直接复用测试集上模型预测结果产出分类报告和混淆矩阵from sklearn.metrics import classification_report, confusion_matrix import numpy as np y_pred model.predict(X_test) y_pred_labels np.argmax(y_pred, axis1) # 五类分类报告precision / recall / f1 全部按类别输出 class_names label_enc.classes_ # [dos, normal, probe, r2l, u2r] print(classification_report(y_test, y_pred_labels, target_namesclass_names)) # 混淆矩阵用于定位哪些类别容易被互相混淆 cm confusion_matrix(y_test, y_pred_labels) print(cm)classification_report输出的每一列对应一个大类的 Precision、Recall、F1。如果某个类别的 recall 是 0.00说明模型完全没把它识别出来这就是 4.4 节说的「类别不均衡导致模型偷懒」的直接证据。拿到这份报告之后你才有底气在课程设计报告里写「模型对 DoS 的召回率达到 99%但对 U2R 的召回率仅为 X%原因是该类别样本占比不足 0.1%」。5.3 从结果反推模型优化方向假设你跑出来的混淆矩阵长这样仅示例[[13204 91 68 7 2] # dos [ 127 9582 88 12 5] # normal [ 92 62 2471 11 2] # probe [ 54 38 23 112 25] # r2l [ 9 5 2 17 3 ]] # u2r这时你会发现三类问题第一R2L 和 normal 之间的混淆比较严重原因是两者在连接时长、登录状态等特征上确实接近第二U2R 的样本太少模型几乎没有见过足够的正例第三DoS 和 normal 的误报主要集中在小类别内部。针对这些问题课程设计报告里就可以写出合理的优化方向对 R2L 和 U2R 做上采样SMOTE 或者简单的重复采样增加网络深度看是否能学到更抽象的语义特征或者把五分类改成二分类「正常 vs 异常」并用异常行为的细粒度回溯。6. 进阶把离线模型接到会话流上滑动窗口预测替代逐条预测上面这些步骤跑通后你的课程设计在功能上是完整的但有一个短板很致命model.predict(X_test)是逐条预测的也就是每来一条连接记录模型单独判断一次。真实网络里的入侵检测不是这样工作的——攻击行为往往在时间上连续比如 DoS 的短时间高并发访问、Probe 的逐步扫描。逐条预测会丢掉这些时间上的上下文信息。我的做法是加一个滑动窗口层把连续的网络连接记录按窗口分组对窗口内每一条记录先做 CNN 预测然后对窗口内所有记录的预测概率取平均或投票再输出一个最终决策。这个窗口大小一般设 10 到 20 条记录对应几秒到十几秒的流量。def sliding_window_predict(model, X_stream, window_size10): 对连续连接流做滑窗预测 每个窗口内部逐条 CNN 预测概率取平均后输出窗口级决策。 参数: X_stream: 连续连接的张量形状 (n_samples, 41, 1) window_size: 窗口大小单位是连接记录条数 返回: 窗口级的预测标签数组 proba model.predict(X_stream) # 先一次性算出所有记录的类别概率 n len(proba) - len(proba) % window_size proba proba[:n].reshape(-1, window_size, 5) # 按窗口切分 avg_proba proba.mean(axis1) # 窗口内概率平均平滑单条误判 return np.argmax(avg_proba, axis1)这里的关键是概率平均而不是投票。如果窗口内 10 条记录里有 8 条被误判成 normal、2 条判定为 DoS投票会判成 normal概率平均则会把 2 条 DoS 的高置信度拉出来不容易被少数正常流量带偏。实际效果是整体误报率能降 3-5 个百分点小类别攻击的检测稳定性也明显变好。从那以后我每次做 KDD99 相关的课程设计都会强制走一遍「解压 → 符号编码 → 归一化 → 分层切分 → 加类权重训练 → 滑动窗口验证」的完整流程顺序一步不差。数据文件损坏、字符串未编码、类别不均衡、TF 版本冲突这些坑每一届都有同学再踩一遍希望这份梳理能帮你在动手前就把它们提前绕开。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站