ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python + CNN 网络入侵检测实战:从 NSL-KDD 预处理到一维卷积模型搭建

Python + CNN 网络入侵检测实战:从 NSL-KDD 预处理到一维卷积模型搭建 简介这是一份基于Python与卷积神经网络CNN的网络安全入侵检测完整项目面向深度学习初学者、网络空间安全专业学生以及需要完成课程设计或期末大作业的开发者。项目以NSL-KDD数据集为核心覆盖数据预处理、CNN模型搭建、训练、评估与预测全流程包含详细代码注释部署简单适合作为高分大作业或入门实战参考。压缩包内含33个文件以Python源码、PyTorch模型权重、数据集配套文件、PNG/JPG评估图表以及Markdown/TXT说明文档为主整体大小21.58MB目录结构清晰便于按模块阅读并配有可视化评估图表方便答辩或报告展示。其中数据预处理脚本负责清洗与归一化模型脚本完成CNN结构定义训练与预测脚本可直接运行并提供预训练权重让新手无需从零训练即可观察检测效果。目前已有117人学习下载适合需要快速搭建网络入侵检测系统并完成课程设计的同学参考。1. 用 Python 和 CNN 做网络入侵检测这份源码把课程设计的路走通了不少做网络安全课程设计的同学拿到“网络入侵检测”这个题目后的第一反应是上机器学习分类器决策树、随机森林轮番试一遍准确率卡在 90% 上下就交差了。但如果你想让期末大作业更有区分度深度学习方向的 CNN 卷积神经网络是更值得尝试的一条路。这份基于 Python 和 CNN 的网络入侵检测算法源码把 NSL-KDD 数据集的预处理、一维卷积模型构建、训练、预测和可视化完整地串了起来代码带注释结构清晰适合作为期末大作业或课程设计的骨架直接复用。下载解压后改改路径就能跑通不需要从零攒一套流程。2. 数据先行NSL-KDD 预处理与特征工程别让模型背锅2.1 为什么选 NSL-KDD 而不是 KDDCUP99做网络入侵检测数据集选择直接决定后面所有工作的说服力。KDDCUP99 是早期最常用的基准数据集但它有个被诟病已久的问题训练集和测试集中存在大量重复记录测试集里将近 78% 的样本在训练集中出现过。这意味着模型可能什么都没学会只是把训练集背了下来测试准确率照样很高。用这种数据写进课程设计答辩时一旦被问到“你的模型泛化能力怎么验证”很难自圆其说。NSL-KDD 是 KDDCUP99 的改进版它去掉了冗余记录并且合理划分了训练集和测试集的比例让评估结果更接近模型真实水平。这份源码的 DataSet 目录下放的就是 NSL-KDD 数据所以从源头上就避开了“背题”这个坑。另外NSL-KDD 的训练集和测试集每条记录的特征维度一致都是 41 维包含 TCP 连接基本特征、内容特征、基于时间的流量特征和基于主机的流量特征标签分为正常Normal和四类攻击DoS、Probe、R2L、U2R。2.2 PreHandle.py 做了什么数值化、归一化与标签编码拿到 NSL-KDD 原始 CSV不能直接喂给 CNN。原始数据里有三类问题一是协议类型、服务类型、连接状态这些列是字符串模型吃不了二是特征量纲差异巨大比如 duration 可能从 0 到几万而 src_bytes 和 dst_bytes 的数值范围更夸张不归一化的话梯度更新会被大数值特征主导三是标签需要从文本映射成数字。PreHandle.py 就是把上面三件事串起来的脚本。核心处理流程对应下面的代码逻辑import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler, LabelEncoder def load_and_preprocess(train_path, test_path): # 读取原始 NSL-KDD 数据41 维特征 1 列标签 1 列难度系数无意义丢弃 train_df pd.read_csv(train_path) test_df pd.read_csv(test_path) # 第 1 列 protocol_type、第 2 列 service、第 3 列 flag 是字符串类型 categorical_cols [protocol_type, service, flag] for col in categorical_cols: # 用 LabelEncoder 将字符串映射为整数 encoder LabelEncoder() # 注意必须在训练集上 fit再 transform 测试集防止测试集类别泄漏 encoder.fit(train_df[col]) train_df[col] encoder.transform(train_df[col]) # 测试集可能遇到训练集没见过的类别用 -1 兜底 test_df[col] test_df[col].map(lambda x: encoder.transform([x])[0] if x in encoder.classes_ else -1) # 标签编码normal - 0, dos - 1, probe - 2, r2l - 3, u2r - 4 label_encoder LabelEncoder() label_encoder.fit(train_df[label]) train_df[label] label_encoder.transform(train_df[label]) test_df[label] test_df[label].map( lambda x: label_encoder.transform([x])[0] if x in label_encoder.classes_ else -1 ) # 分离特征和标签去掉标签列和难度系数列 X_train train_df.drop(columns[label, difficulty]).values y_train train_df[label].values X_test test_df.drop(columns[label, difficulty]).values y_test test_df[label].values # MinMax 归一化把特征压缩到 [0, 1] 区间 scaler MinMaxScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) return X_train, X_test, y_train, y_test, label_encoder这里有两个容易被忽略的点。fit和transform的顺序是关键对类别特征和归一化器只能在训练集上fit然后同时用训练集的参数去transform训练集和测试集。如果对训练集和测试集分别fit两个数据集的特征分布不一致测试结果就没有参考意义。另一个点是difficulty列的处理——NSL-KDD 每条记录末尾有一个难度系数它不代表攻击类型训练和预测时都要丢掉否则模型相当于多学了一个和攻击类型高度相关的作弊特征。2.3 归一化方法的选择为什么用 MinMax预处理里有一步用了 MinMaxScaler这个选择不是随手写的。网络入侵检测数据里大部分特征的分布是有偏的很多连接记录在短时间内的字节数很少但极少数恶意流量会产生极大的字节数整体呈现明显的长尾分布。如果使用 StandardScaler标准化到均值为 0、方差为 1长尾部分会被压缩得过于集中而 CNN 对输入数值的相对差异敏感特别是后续如果使用 ReLU 激活函数输入值整体偏负或偏正都会影响神经元的激活比例。MinMax 把数据缩放到 [0, 1] 区间保留了原始分布的相对关系同时也让梯度传播更稳定。minMax.png 图就是这个归一化效果的分布展示跑完预处理后可以打开检查一下各特征列的数值范围是否都落到了预期区间。3. CNN 模型搭建从一维卷积到池化网络结构逐层拆解3.1 为什么用一维 CNN 处理入侵检测数据常见图像分类任务用二维卷积因为图像是 H×W×C 的三维张量。而 NSL-KDD 的每条记录是一个 41 维的特征向量本质上是单通道的一维序列所以这里用 Conv1d 做一维卷积卷积核沿着特征维滑动提取的是相邻特征之间的局部关联模式。比如某几个连续特征组合成一种攻击特征一维卷积可以在多个位置捕获这种模式并且具备一定的平移不变性——攻击特征出现在特征向量略靠前或略靠后的位置卷积输出依然能激活。也有不少实现把这个 41 维向量 reshape 成二维矩阵比如 7×6 或 5×9再用二维卷积但这样做的人为假设是“特征之间存在空间局部性”而 NSL-KDD 的特征顺序本身没有空间语义强行 reshape 成二维矩阵反而会破坏相邻特征之间的组合关系。所以这份源码直接用 Conv1d 是更合理的选择网络结构也更容易解释。3.2 CNNMould.py 网络结构逐层拆解CNNMould.py 定义了模型结构。整个网络按照“卷积提取特征 → 池化压缩 → 全连接分类”的经典 CNN 套路组织代码如下import torch.nn as nn class CNNMould(nn.Module): def __init__(self, input_dim41, num_classes5): super(CNNMould, self).__init__() # 第一层卷积输入 1 通道输出 64 通道卷积核大小为 3 # padding1 保证卷积后长度不变 self.conv1 nn.Sequential( nn.Conv1d(in_channels1, out_channels64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.MaxPool1d(kernel_size2, stride2) ) # 第二层卷积输入 64 通道输出 128 通道 self.conv2 nn.Sequential( nn.Conv1d(in_channels64, out_channels128, kernel_size3, padding1), nn.BatchNorm1d(128), nn.ReLU(inplaceTrue), nn.MaxPool1d(kernel_size2, stride2) ) # 第三层卷积进一步抽象特征 self.conv3 nn.Sequential( nn.Conv1d(in_channels128, out_channels256, kernel_size3, padding1), nn.BatchNorm1d(256), nn.ReLU(inplaceTrue), nn.MaxPool1d(kernel_size2, stride2) ) # 经过三次池化序列长度从 41 - 20 - 10 - 5 # 全连接层输入维度 256 * 5 self.fc nn.Sequential( nn.Linear(256 * 5, 512), nn.ReLU(inplaceTrue), nn.Dropout(p0.5), nn.Linear(512, num_classes) ) def forward(self, x): # x shape: [batch_size, 41]需要增加通道维度变成 [batch_size, 1, 41] x x.unsqueeze(1) x self.conv1(x) x self.conv2(x) x self.conv3(x) # 展平送入全连接层 x x.view(x.size(0), -1) x self.fc(x) return x网络结构上有几个设计值得在课程设计中展开写。卷积核大小为 3感受野覆盖当前特征和左右各一个相邻特征对 41 维的短序列来说这个窗口足够捕获局部关联又不会把相距太远的特征强行拉在一起。每个卷积层后接了一个 BatchNorm1d作用是让每层输入分布稳定加速收敛同时起到一定的正则化作用。三层网络的通道数从 64 逐步增加到 256符合 CNN 常见的“空间维度缩小、通道维度加深”的设计范式。有两个参数需要你根据实际数据调整第一个是池化后的序列长度输入 41 维经过三次 kernel2、stride2 的最大池化长度变化是 41 → 20 → 10 → 5所以全连接层的输入是 256×51280如果换数据集导致特征维度不是 41这组数字要重新算。第二个是 Dropout 的概率设为 0.5训练时随机丢弃一半的神经元连接防止模型在有限的 NSL-KDD 训练集上过拟合。真正预测时 Dropout 是不生效的PyTorch 会自动切换为全量连接。3.3 损失函数与评估指标准确率和 precision 都要看模型的最后一个全连接层输出 5 个值对应 5 个类别的未归一化得分。训练时的损失函数是交叉熵PyTorch 的nn.CrossEntropyLoss内部已经包含了 Softmax所以在 forward 里不需要额外加一层 Softmax直接在损失函数里做即可。评估时 torch.max 取得分最高的索引作为预测类别。课程设计里不能只贴准确率。NSL-KDD 数据集中各类别样本量极不均衡训练集中 DoS 攻击样本数量远大于 R2L 和 U2R如果模型把所有样本都预测为 DoS准确率也能到 70% 以上但这个模型没有任何实用价值。所以源码里输出 accuracy.jpg 的同时还输出了 precision.jpg——精确率precision衡量的是预测为正类的样本中有多少是真的正类对入侵检测来说误报率直接关系到模型能否落地一个误报率高的检测系统会淹没真正有价值的告警。这两个指标的组合才是课程设计答辩时能讲清楚的东西。4. 训练与预测从 Train.py 到 Predict.py 完整跑通流程4.1 Train.py 训练主流程数据预处理和模型定义就位后训练脚本负责把两者衔接起来。Train.py 的整体流程是加载预处理后的数据 → 包装成 DataLoader 按批次喂给模型 → 定义优化器和损失函数 → 循环训练多个 epoch → 每个 epoch 结束后在测试集上评估 → 保存最优模型。下面是训练主循环的核心代码import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset from CNNMould import CNNMould from PreHandle import load_and_preprocess # 加载预处理后的数据 X_train, X_test, y_train, y_test, _ load_and_preprocess( DataSet/KDDTrain.csv, DataSet/KDDTest.csv ) # 转换为 PyTorch Tensor 并包装成 DataLoader train_dataset TensorDataset( torch.tensor(X_train, dtypetorch.float32), torch.tensor(y_train, dtypetorch.long) ) batch_size 64 train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue) # 初始化模型、损失函数、优化器 model CNNMould(input_dim41, num_classes5) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) epochs 30 best_acc 0.0 for epoch in range(epochs): model.train() running_loss 0.0 for inputs, labels in train_loader: optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * inputs.size(0) # 每个 epoch 后在测试集上评估 model.eval() correct 0 total 0 with torch.no_grad(): test_inputs torch.tensor(X_test, dtypetorch.float32) test_labels torch.tensor(y_test, dtypetorch.long) outputs model(test_inputs) _, predicted torch.max(outputs, 1) total test_labels.size(0) correct (predicted test_labels).sum().item() acc correct / total print(fEpoch {epoch1}/{epochs}, Loss: {running_loss/len(train_dataset):.4f}, Test Acc: {acc:.4f}) # 保存测试集上准确率最高的模型参数 if acc best_acc: best_acc acc torch.save(model.state_dict(), best_model.pth)这段代码里有几处是课程设计和实际工程都要注意的。optimizer.zero_grad()必须在每次反向传播前调用否则梯度会累积到上一次的数值上导致参数更新方向完全错乱。model.train()和model.eval()的切换也很关键——训练模式下 Dropout 和 BatchNorm 都会按训练策略计算切换到 eval 模式后 Dropout 关闭、BatchNorm 使用全局统计量如果用训练模式的模型去测试结果会偏低且不稳定。关于超参的选择batch_size 设为 64这是 CPU 训练和显存受限场景下的折中值batch 太小梯度噪声大太大收敛慢学习率设为 0.001 是 Adam 优化器最常用的起始值如果训练曲线震荡严重可以降到 0.0005。epoch 数设为 30实际训练时可以观察 loss 曲线的收敛情况——如果 loss 在 20 个 epoch 前就趋于平稳就可以提前停止避免浪费时间。4.2 训练可视化Mould Acc.png 和 accuracy.jpg 怎么看训练完成后源码会生成 Mould Acc.png 和 accuracy.jpg 两张图像前者是模型在训练集上的准确率变化曲线后者是测试集上的准确率变化。看这两张图时重点关注两条曲线的走势差异如果训练集准确率持续上升但测试集准确率在某个 epoch 后开始下降说明模型开始过拟合前面 Dropout 设的 0.5 可以适当调大如果两条曲线都上不去问题通常不在模型结构而在预处理——检查归一化是否对测试集做了独立 fit、标签编码是否正确。需要注意的是这里有一个常见的混淆train.py 中每个 epoch 的评估是在整个测试集上进行的也就是训练和评估用的是同一份数据迭代的中间结果所以准确率曲线会比较平滑。而最终提交项目时建议在训练完成后用独立的测试集再跑一次完整评估避免把“训练过程中的最佳测试表现”当作最终泛化指标。4.3 Predict.py加载模型做单条流量预测训练好的 best_model.pth 放在那里不会自己工作Predict.py 负责把模型加载回来对新的流量记录做预测。它的核心逻辑是重新构建一次预处理流程注意要复用训练时的 LabelEncoder 和 MinMaxScaler然后加载模型权重对输入做前向传播得到预测类别。import torch import numpy as np from CNNMould import CNNMould from PreHandle import load_and_preprocess # 加载训练时的预处理参数和模型权重 _, _, _, _, label_encoder load_and_preprocess( DataSet/KDDTrain.csv, DataSet/KDDTest.csv ) model CNNMould(input_dim41, num_classes5) model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval() label_names label_encoder.classes_ def predict_one(raw_record: np.ndarray) - str: # raw_record 已经是数值化并归一化后的 41 维向量 tensor torch.tensor(raw_record, dtypetorch.float32).unsqueeze(0) with torch.no_grad(): outputs model(tensor) _, predicted torch.max(outputs, 1) return label_names[predicted.item()] # 示例预测一条测试集记录 sample X_test[0] # 实际使用时替换为自己的流量特征 print(f预测结果: {predict_one(sample)})这段代码里有一个实操中容易踩的细节torch.load(best_model.pth, map_locationcpu)里的 map_location 参数。如果训练时用的是 GPU保存的权重张量会带有 CUDA 设备信息换到没有 GPU 的机器上加载时会报设备不匹配错误加上 map_locationcpu 可以强制加载到 CPU。如果你的预测环境有 GPU可以把 cpu 改成 cuda:0。单条预测逻辑写清后批量预测只需要把多条样本堆叠成一个 batch 矩阵一次前向传播就能完成速度会快很多这在处理实时流量检测时才有实用价值。5. 网络入侵检测避坑指南数据泄漏、维度错位与效果复现5.1 现象测试集准确率奇高但换数据就崩原因训练和测试数据在预处理阶段发生了信息泄漏。具体来说有人写代码把训练集和测试集合并后一起做归一化、一起做标签编码甚至直接用全量数据的统计量去缩放。这时模型在测试集上看到的信息其实是“半开卷考试”的答案评估结果虚高。解决严格贯彻执行“先 fit 训练集再 transform 测试集”的原则类别编码和归一化器的参数全部来自训练集。这也是 PreHandle.py 里特意分开处理 train_df 和 test_df 的原因。5.2 现象RuntimeError 提示 size mismatch原因模型全连接层的输入维度与实际展平后的维度不一致。三种常见情况一是改了 CNNMould 的卷积层参数比如 kernel_size、通道数忘了重算 fc 层输入二是换了数据集特征维度不是 41池化后序列长度变了三是 MaxPool1d 的 kernel_size 不能整除输入长度时输出长度是向下取整的实际值和手算的有出入。解决不要在纸上推演直接在模型 forward 里加一行打印输出 shape 的临时代码跑一个 batch 看一眼实际维度拿这个数字去设置 nn.Linear 的第一个参数。5.3 现象训练了 30 个 epoch准确率稳定在 40% 上下不动原因数据没喂对。比较典型的错误是标签 Tensor 用了 float32 类型而 CrossEntropyLoss 要求标签是 long 类型PyTorch 在部分版本下会把 float 标签静默当作类别索引处理结果完全不可解释。另一个常见问题是标签编码有误normal 被编成了 1 而不是 0模型输出分布和标签对不上。解决在训练前打印一次 y_train 的数值分布确认是 04 的整数再检查 DataLoader 里 TensorDataset 构造时 labels 的 dtype 是否为 torch.long。5.4 现象模型完全正常但测试集 R2L、U2R 两类准确率几乎为 0原因样本极度不均衡。NSL-KDD 训练集中 R2L 和 U2R 样本数量合计只有几百条相比上万的 Normal 和 DoS 样本CNN 很难在这两个类别上学到有效特征。这是数据集本身的特性不是模型代码的 bug。解决思路有两个方向一是用类别加权损失函数按样本数的倒数给每个类别设置 loss 权重让少数类样本的梯度贡献更大二是对少数类做过采样比如在训练集里把 R2L 样本重复若干份。课程设计里可以把这两个方案提出来做对比实验反而成了加分项。5.5 现象换了一台电脑跑源码报错 ModuleNotFoundError原因依赖环境没对齐。源码依赖的 PyTorch 版本跨度较大时部分 API 行为有差异但最常见的问题还是环境中缺少 numpy、pandas、scikit-learn。解决先用pip list检查已安装的包缺哪个补哪个。建议新开一个干净的虚拟环境安装不要动系统 Python 环境避免版本冲突。如果你连 numpy 和 pandas 都是刚从 Python 安装教程里学到的那在跑这份源码之前先确保这两个基础库能正常 import再做下一步。6. 让检测结果更有说服力用混淆矩阵完成最后的验证闭环模型训练完、准确率也画出来了但课程设计的完整性还差最后一块拼图。准确率只能说明“总体上对的概率”对入侵检测来说最怕的是攻击流量被漏判。NSL-KDD 总共 5 类你需要知道每一类具体的识别情况这时候要画混淆矩阵。import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix # 用训练好的模型预测整个测试集 model.eval() with torch.no_grad(): test_inputs torch.tensor(X_test, dtypetorch.float32) outputs model(test_inputs) _, predicted torch.max(outputs, 1) cm confusion_matrix(y_test, predicted.numpy()) class_names [normal, dos, probe, r2l, u2r] # 绘制混淆矩阵热力图 plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names) plt.xlabel(Predicted Label) plt.ylabel(True Label) plt.title(Confusion Matrix on NSL-KDD Test Set) plt.savefig(confusion_matrix.png, dpi150, bbox_inchestight)拿到混淆矩阵后重点看对角线以外的数字分布在哪些位置。通常你会发现 DoS 和 Probe 的识别准确率较高而 R2L 和 U2R 的样本大量被误判为 Normal这和两类攻击的连接特征比较接近正常流量有关。在课程设计报告里针对性地分析这个现象比单贴一个 85% 的准确率图更有深度。我自己的习惯是每跑完一个模型先用测试集里 50 条 Normal 样本和 50 条攻击样本做一次盲测把预测结果和真实标签逐条对一遍人为检查模型的误判模式是否合理。这不是什么高深技术但能快速暴露代码里的低级错误——有一次我就发现所有预测结果都集中在一类上排查后定位到是标签在 DataLoader 里被 shuffle 乱了。从那以后每一份网络入侵检测项目我都强制把「混淆矩阵 小样本人工盲测」走一遍再交付。希望这份源码加这篇笔记也能帮你的课程设计少走几步弯路。本文还有配套的精品资源点击获取
返回列表