基于CNN与KDD99的入侵检测实战:从数据预处理到模型调优
简介这套毕业设计资源是一份基于CNN卷积神经网络的网络入侵检测完整项目面向计算机相关专业毕业生及需要深度学习实战练习的学习者解决了传统入侵检测中特征工程繁琐、模型泛化能力不足等痛点。压缩包共16个文件包含4个可运行的Python脚本、KDD Cup数据集压缩包、XML配置文件与Markdown说明文档整体大小17.51MB按代码、数据、文档划分明确便于快速定位与复现实验。当前已有120人学习下载项目经导师审定并本地调试通过评审得分98分可放心使用。代码覆盖数据预处理、CNN模型搭建、入侵行为分类、训练过程记录与结果分析等完整流程并附原始数据压缩包和说明文档适合毕业设计参考、课程项目复现以及入侵检测方向的入门实践和二次开发。1. 从KDD99到CNN这个入侵检测项目到底在检测什么这个毕设项目用KDD Cup 99数据集将网络连接记录整理成数值矩阵再用一维卷积神经网络做二分类正常/攻击。最适合两类人正在做网络安全或深度学习毕业设计的学生以及想快速上手一个完整CNN流程的开发者。我先说一个反直觉的结论CNN在表格型流量特征上初始表现不一定比随机森林好但只要把特征顺序和卷积核尺寸对齐它能自动找出相邻特征间的组合模式在NSL-KDD这类基准上通常能拿到比多层感知机更高的召回率。项目自带源码、数据、文档和TensorBoard日志拿到手可以直接复现。下面从数据、模型、训练和改造四个层面拆开讲。2. 数据形态与预处理为什么把网络流量当作图像输入CNN开始跑源码之前先想清楚数据层面的三个问题41维特征怎么来字符特征怎么数值化以及为什么能按“图像”去理解。KDD Cup 99来源于DARPA 1998年采集的军事网络环境流量每一条连接记录有41个连续或离散特征外加一个攻击标签。CNN不是直接把原始pcap丢进去而是把特征向量组织成固定形状的输入张量。2.1 KDD99数据集的结构与41维特征KDD99的特征被划分为四个组基本连接特征、连接内容特征、基于时间的流量特征和基于主机的流量特征。下面这张表是常见分组方式。特征分组特征索引典型字段说明基本连接特征1-9duration, protocol_type, service, flag连接持续时间、协议、目的端口服务、连接状态连接内容特征10-22hot, num_failed_logins, logged_in与TCP内容相关的统计主要针对R2L和U2R攻击基于时间的流量特征23-31count, srv_count, serror_rate过去2秒内同一主机/服务的连接统计基于主机的流量特征32-41dst_host_count, dst_host_srv_count过去100条连接中基于目标主机的统计这些特征的数值范围差异非常大比如duration可以是0到几千src_bytes可以到上亿而num_failed_logins大多是0。直接喂给CNN第一轮卷积会被大数值特征支配所以归一化是必需步骤。项目里的kddcup.data_10_percent.gz是10%采样版本标签覆盖了normal、dos、probe、r2l、u2r五类但r2l和u2r的样本量很少做五分类时会遇到严重不平衡。常见做法是合并成二分类源代码也支持这个选择。2.2 标签编码与数值归一化的实际处理41维特征里有3列是字符型分别是protocol_type、service、flag。PyTorch的Conv1d只接受数值张量所以必须先编码。项目里的handle2.py做的就是这件事我重写了一个精简版import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder, StandardScaler 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] def load_kdd(path): df pd.read_csv(path, headerNone, namesCOLUMNS) for col in [protocol_type, service, flag]: df[col] LabelEncoder().fit_transform(df[col].astype(str)) df[label] df[label].apply(lambda x: 0 if x.strip(.) normal else 1) return df df load_kdd(kddcup.data_10_percent) X df.drop(label, axis1).values.astype(np.float32) y df[label].valuesLabelEncoder会把tcp、udp、icmp转成0、1、2service和flag同理。这里有一个关键点在训练集上fit之后测试集和未来预测时不能用同一个LabelEncoder实例重复fit因为类别可能缺失或顺序变化导致编码错位。应该用joblib把encoder保存下来后面预测时直接transform。归一化我用StandardScaler但只对训练集fit再transform验证集和测试集。如果你把全部数据拿去fit会发生数据泄漏测试结果虚高。下面这段是正确顺序scaler StandardScaler() X_train scaler.fit_transform(X_train) X_val scaler.transform(X_val) X_test scaler.transform(X_test)StandardScaler将每个特征变成均值为0、方差为1的分布。对CNN的卷积核来说输入尺度统一后梯度更新更稳定BatchNorm层的压力也小很多。有些实现会改成MinMaxScaler缩放到[0,1]在流量数据上也可以只是对极端大值更敏感。注意LabelEncoder的fit一定要先在训练集上完成测试集和预测时只调用transform否则编码会完全错位。这是数据处理和模型评估中最容易导致结果失真的一个环节。2.3 特征重排与一维序列构造原始特征顺序是按DARPA测量时的采集逻辑排的不一定是卷积核最友好的顺序。我在拿到别人代码后一般会先做一次特征重排实验把四个组内的特征挪到相邻位置。比如把count、srv_count、serror_rate这些时间窗口特征连续放置这样kernel_size5的卷积核扫过时可以同时看到同一窗口内的多个统计量。重排后的预处理会多一步索引映射# 按组重排后的特征索引把第23-31号流量特征移动到前面 group_order [22, 23, 24, 25, 26, 27, 28, 29, 30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] X_reordered X[:, group_order]这段代码用索引数组直接重排numpy矩阵不需要复制数据。重排后输入张量从(batch, 41)变成(batch, 41, 1)再送进Conv1d。如果你不想重排也可以把41维分成4段用4个不同的一维卷积分支并行处理然后在全连接层拼接这种设计在论文里称为多尺度CNN会对最终精度有1到2个百分点的提升但代价是训练时间变长。项目源码里没有这条路但数据接口完全兼容可自行扩展。3. 模型搭建一维卷积如何作用于流量特征数据准备好后就是模型部分。项目里有两个入口文件mian_cnn.py和cnn_main.py前者估计是笔误或保留的实验版本后者才是主训练脚本。这两个文件最终核心网络结构一致下面以PyTorch风格说明如何实现一个可运行的CNN入侵检测模型。3.1 从MLP到CNN的选型理由一开始我也觉得41维特征用全连接网络最简单为什么非要卷积但实践下来有几个原因一流量特征内部存在局部相关性比如serror_rate和srv_serror_rate放在一起卷积核能学到它们联合突变与SYN Flood的关系二卷积是参数共享的同样层数下参数量比全连接少一个数量级KDD99训练集虽然几十万条但模型太大照样过拟合三一维卷积可以替换成二维卷积把特征向量resize成比如(7,7,1)直接套用图像分类的经典卷积神经网络结构图。下面是几种模型在KDD99 10%数据上的典型表现对比基于相似预处理和训练时长模型参数量估计准确率区间训练速度备注逻辑回归约4291-92%极快只能做线性边界随机森林中等93-94%快对特征尺度不敏感MLP(3层)约5万94-95%中无局部组合能力CNN(2层卷积)约6万95-97%中能捕捉相邻特征组合这个对比不是定论但能说明CNN的价值在特征工程有限时更容易体现。如果数据量很大CNN优势还会扩大如果只有几千条样本随机森林更稳。3.2 cnn_main.py中的网络结构解析项目源码里网络结构大致如下我按PyTorch复刻并加了注释import torch.nn as nn class CnnIDS(nn.Module): def __init__(self, input_dim41, num_classes2): super(CnnIDS, self).__init__() self.conv1 nn.Sequential( nn.Conv1d(in_channels1, out_channels32, kernel_size5, padding2), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(kernel_size2) ) self.conv2 nn.Sequential( nn.Conv1d(in_channels32, out_channels64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(kernel_size2) ) self.fc nn.Sequential( nn.Flatten(), nn.Linear(64 * 10, 128), nn.ReLU(), nn.Dropout(0.5), nn.Linear(128, num_classes) ) def forward(self, x): # x形状 (batch, 41)增加通道维变 (batch, 1, 41) x x.unsqueeze(1) x self.conv1(x) x self.conv2(x) x self.fc(x) return x从输入维度反推41经过kernel_size5、padding2的卷积后长度仍为41MaxPool1d(2)后变成20第二层卷积后仍是20池化后变成10所以全连接输入是64 * 10。这里的64是第二层卷积核数量。如果你调大过滤器数量全连接层维度要同步改。BatchNorm1d放在卷积后、激活前作用是对每个通道做标准化。KDD99特征不是图像像素分布偏度很大BatchNorm能缓解内部协变量偏移让ReLU不会因为负输入过多而大面积失活。Dropout放在全连接层隐藏层节点越少越要小心过拟合0.3到0.5之间可以试试。3.3 训练参数与调参要点训练配置直接决定能否收敛。我从项目README和代码里看到了Adam优化器和交叉熵损失函数学习率没有写死但常见做法是0.001起20到30个epoch开始衰减。下面这段训练循环具备实际操作意义import torch from torch.utils.data import TensorDataset, DataLoader train_dataset TensorDataset(torch.from_numpy(X_train), torch.from_numpy(y_train)) train_loader DataLoader(train_dataset, batch_size128, shuffleTrue) model CnnIDS() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.5) criterion torch.nn.CrossEntropyLoss() for epoch in range(30): model.train() for xb, yb in train_loader: optimizer.zero_grad() out model(xb.float()) loss criterion(out, yb.long()) loss.backward() optimizer.step() scheduler.step() # 每个epoch结束做一次验证 model.eval() with torch.no_grad(): val_out model(torch.from_numpy(X_val).float()) val_acc (val_out.argmax(1) torch.from_numpy(y_val).long()).float().mean() print(fepoch {epoch}, loss {loss.item():.4f}, val_acc {val_acc.item():.4f})weight_decay1e-4是L2正则能限制权重不会增长过快。StepLR(step_size5, gamma0.5)表示每5个epoch学习率减半。如果训练20个epoch还不够收敛把step_size改8如果验证集波动大改小batch_size到64。这里有一个容易犯的错验证时忘记model.eval()导致BatchNorm和Dropout行为不一致验证指标忽高忽低。我排错时总会先检查这一行。数据量方面KDD99 10%版本约49万条如果用全量训练显存8G的GPU够用但建议先用前2万条做冒烟测试确认loss在下降再放开全量。CPU上训练时间会长很多可以把epoch数降到10先看趋势。项目里的multi_logs目录就是用来记录这些不同参数实验日志的方便对比。4. 训练与评估从tfevents到混淆矩阵训练结束之后真正的挑战是评估。如果你只保存了最终准确率答辩时很难说清楚模型为什么好。项目自带train和test目录以及TensorBoard事件文件events.out.tfevents.1482980284.zjx-24000635说明训练过程已经被记录下来。下面我会说明如何利用这些日志和指标把实验做完整。4.1 训练流程与checkpoint管理训练过程中通常要保存checkpoint以便从断点恢复。一个完整的checkpoint保存内容应该包括模型权重、优化器状态、当前epoch和最好指标best_acc 0 for epoch in range(epochs): # ...训练代码... val_acc evaluate(model, val_loader) if val_acc best_acc: best_acc val_acc torch.save({ model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), epoch: epoch, val_acc: val_acc }, best_model.pt)加载时用model.load_state_dict(torch.load(best_model.pt)[model_state_dict])然后optimizer.load_state_dict(...)恢复训练。如果你只保存了权重加载模型时需要先实例化同样的网络结构结构对不上会报unexpected key这是常见坑。对于日志项目文件名里的events.out.tfevents.1482980284.zjx-24000635是TensorBoard的标准日志格式。使用方法如下tensorboard --logdirmulti_logs在浏览器打开http://localhost:6006选择SCALARS页面就能看loss和acc曲线。同时看训练集和验证集曲线比只看验证集准确率更能定位问题。4.2 指标解读与过拟合判断入侵检测数据天然不平衡准确率不能作为唯一指标。假设10%数据集里84%是dos类简单把所有样本预测为dos就能拿到84%的准确率但完全没有检测R2L的能力。所以在评估阶段至少要看精确率、召回率和F1from sklearn.metrics import classification_report, confusion_matrix y_pred model(test_x).argmax(dim1).numpy() print(confusion_matrix(y_true, y_pred)) print(classification_report(y_true, y_pred, target_names[normal, attack]))classification_report会输出每一类的precision、recall、f1。在入侵检测里攻击类attack的recall尤其重要recall低意味着真实攻击被漏掉。precision低则意味着大量正常流量被拦截影响业务可用性。通常根据场景权衡银行风控更看重recall运维告警更看重precision。再看训练曲线。如果训练loss降到很低验证loss却开始上升说明模型记住了训练集特性没有泛化到新流量。除了Dropout还可以早停early_stop_patience 3 best_loss float(inf) counter 0 for epoch in range(epochs): val_loss validate(model, val_loader) if val_loss best_loss: best_loss val_loss counter 0 else: counter 1 if counter early_stop_patience: print(early stop) break早停逻辑是验证loss连续n个epoch不下降就停止训练保存最优模型。这个机制能省去手动观察曲线的时间项目里没有但建议加上。4.3 用multi_logs/train/test目录做增量对比train和test目录应该是项目作者划分好的原始数据集multi_logs是多次实验的TensorBoard日志聚合点。做对比实验时我习惯每次运行指定一个子目录保证曲线可视化不乱from torch.utils.tensorboard import SummaryWriter # 把run_id作为参数传入这样切分目录清晰 writer SummaryWriter(fmulti_logs/run_cnn_lr1e3_dropout05)在命令行里加--name run1这样的参数训练脚本内用它拼接日志目录。这样可以分别比较不同学习率、卷积核数、是否使用BatchNorm等变量对精度的影响。增量实验时数据可以按比例递增例如先使用10%数据训练再使用50%最后100%观察精度增长斜率这能从侧面反映模型容量是否饱和。5. 复现与改造把毕业设计变成可用的检测模块最后这部分不是把所有代码再贴一遍而是说配置环境和改造的关键动作。项目本身是一个毕业设计但它的结构足够让你把它改成一个轻量级的入侵检测模块。5.1 运行环境配置与快速启动建议用Anaconda建一个独立环境避免污染系统Python。参考requirements实际核心依赖与版本大致如下依赖包用途备注python解释器3.7-3.10均可tensorflow或torch深度学习框架取决于main.py用的是哪个后端pandas数据读取与清洗处理CSV和label映射numpy数值计算预处理必备scikit-learn数据切分与指标LabelEncoder、混淆矩阵命令行里这样conda create -n ids python3.8 -y conda activate ids pip install pandas numpy scikit-learn torch tensorboard python main.py如果main.py是用TensorFlow的就安装tensorflow。项目根目录有README.md但有一个README.md.bak文件说明README被修改过。遇到报错时先看README.md.bak里的原始说明可能会找到一些被覆盖的环境要求。5.2 代码改造从KDD99到自定义流量特征如果你想把训练好的CNN用到新流量上需要用抓包工具提取连接特征。这里以scapy为例提取TCP连接的关键字段from scapy.all import rdpcap def packet_to_features(packets): features [] src_bytes sum(len(p) for p in packets if p.haslayer(Raw)) dst_bytes sum(len(p.payload) for p in packets if p.haslayer(TCP)) duration packets[-1].time - packets[0].time if len(packets) 1 else 0 features.extend([duration, src_bytes, dst_bytes]) # 将补齐的18个统计特征按实际流量填充 return features实际工程里还要考虑无状态连接、端口混淆、加密流量等问题但作为毕设延伸足够。核心是训练和预测时的特征顺序必须完全一致字符型编码映射表需要保持同一份。5.3 易踩的坑与验证技巧根据我复现这类项目的经验最容易卡住的问题集中在这几个位置问题现象解决方案label没有去掉点号准确率只有20%左右统一用strip(.)处理标签LabelEncoder重复fit预测时特征错位用joblib.dump保存编码器全连接层维度对不上RuntimeError: size mismatch打印conv输出shape用torchsummary测试集做过归一化指标虚高但实际不可用scaler只fit训练集路径带中文Windows下找不到文件用pathlib处理路径验证技巧分两步。第一步从测试集中挑出少量攻击样本和正常样本单独构造一个mini batch用训练好的模型预测打印每条样本的置信度。如果置信度普遍接近0.5说明模型没学到区分性特征优先检查预处理。第二步把所有测试样本的预测结果里找出预测置信度最高的若干条以及最低的若干条人工看下对应特征值分布。置信度高的样本特征如果看起来明显合理那么模型行为就是健康的。用这样一个小而完整的验证流程每次改动超参或特征后都能在5分钟内判断改动是正向还是反向。这比跑完整个训练集再对比损失值要高效得多也能在写报告时提供更细的证据。本文还有配套的精品资源点击获取