
简介基于Python与CNN的网络入侵检测算法源码及项目说明面向计算机专业学生完成毕业设计、课程设计以及网络安全爱好者开展深度学习方法实战。项目利用卷积神经网络自动提取网络流量特征在NSL-KDD标准数据集上完成训练与评估旨在解决传统入侵检测方法对复杂攻击识别率不高的问题。压缩包共33个文件约21.58MB包含Python训练、预测与预处理脚本CSV数据集XML工程配置PNG/JPG可视化结果图训练好的模型权重以及一份系统性的说明文档完整覆盖数据清洗与归一化、模型结构设计、训练调参、测试评估和应用部署环节。借助清晰的模块划分和代码注释读者可直接运行复现实验也可替换数据集或调整网络结构进行二次开发。目前已有134人学习下载适合需要高质量可运行项目作为毕设参考或安全研究方向入门的学习者。1. 网络入侵检测为什么要用 Python CNN 而不是规则引擎把网络入侵检测当成一个图像分类问题来做听起来绕但这是深度学习方案里验证成本最低、最容易复现的一条技术路线。传统入侵检测系统靠规则库和签名匹配面对加密流量、变种攻击和未知漏洞利用时规则根本写不过来误报率也压不住。CNN 的卷积核天然擅长捕捉输入矩阵里的局部相关性——换个角度说网络会话的特征值在时间窗口里也存在类似图像纹理的结构这正是卷积神经网络的用武之地。这个标题下的项目本质就是先把流量会话编码成数值矩阵再交给一个不算深的 CNN 做多分类判别。适合正在做安全数据分析、或者准备从 sklearn 切到深度学习的工程人员。读完你至少能独立跑通「数据集预处理 — 模型训练 — 评估调参 — 导出部署」的完整闭环之后换到自己的流量特征表上改的只是数据装载和矩阵映射这一段。2. 数据预处理把 NSL-KDD 流量特征变成 CNN 的输入矩阵CNN 的输入必须是固定尺寸的数值矩阵而原始网络流量是长度不定的会话记录这两者之间的转换是整条链路里最容易出问题的一步。实际项目里这一步花的时间通常比调网络结构还要多因为特征顺序、缩放参数、列对齐一旦不一致后面模型再准都是白搭。2.1 NSL-KDD 的字段结构与选型理由学术和工程里做网络入侵检测的基准数据集最常见的是 NSL-KDD。它是对 KDD Cup 99 的改进去掉了大量冗余记录使训练集和测试集的分布更合理U2R、R2L 这类少数类样本不至于被淹没在重复数据里。NSL-KDD 每条样本包含 41 个特征归属四组特征组数量典型字段基础连接特征9duration、protocol_type、service、flag、src_bytes 等内容特征13登录失败次数、root 权限操作、文件创建次数等流量特征时间窗口9过去 2 秒内同主机的连接统计流量特征主机窗口10过去 100 条连接中同服务的统计标签是五分类Normal、DoS、Probe、R2L、U2R。训练集中 Normal 约 6.7 万条DoS 约 4.6 万条Probe 约 1.2 万条R2L 只有 995 条U2R 只有 52 条这个极端不平衡会在第四章显著影响评估方式。拿到这类项目时第一件事是确认默认加载哪个数据集。入门阶段用 NSL-KDD 就够了它的优点是免版权、可直接下载、特征列固定如果业务要贴近真实流量再迁移到 CICIDS2017那需要自己用 cicflowmeter 从 pcap 里提取特征成本高出不少。我一般先用 NSL-KDD 调通链路再换自己的特征表。2.2 特征归一化、One-Hot 编码与标签映射NSL-KDD 的构成是 37 个数值列加 4 个类别列建模前要做两件事类别列转 One-Hot、数值列标准化。这里有个新手最容易踩的坑标准化必须只用训练集的均值和标准差测试集和线上数据要复用同一组参数否则特征分布信息提前泄露评估结果虚高。import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler # 读取 NSL-KDD 官方文件最后一列是类别标签 # 如果下载的副本末尾多了一列难度等级值先把它去掉 df_train pd.read_csv(KDDTrain.txt, headerNone) df_test pd.read_csv(KDDTest.txt, headerNone) X_train, y_train df_train.iloc[:, :-1], df_train.iloc[:, -1] X_test, y_test df_test.iloc[:, :-1], df_test.iloc[:, -1] # 对三个类别列做 One-Hot协议类型约 3 个取值service 约 70 个取值 for col in [1, 2, 3]: X_train pd.concat([X_train.drop(columns[col]), pd.get_dummies(X_train[col], prefixfc{col})], axis1) X_test pd.concat([X_test.drop(columns[col]), pd.get_dummies(X_test[col], prefixfc{col})], axis1) # 训练/测试列对齐测试集里可能缺少某些取值reindex 补零 X_test X_test.reindex(columnsX_train.columns, fill_value0) # 数值列标准化scaler 只在训练集上 fit numeric_cols X_train.select_dtypes(include[np.number]).columns scaler StandardScaler().fit(X_train[numeric_cols]) X_train[numeric_cols] scaler.transform(X_train[numeric_cols]) X_test[numeric_cols] scaler.transform(X_test[numeric_cols])这段代码有两个必须注意的参数点。One-Hot 之后训练集和测试集的列数可能不一致用reindex按训练集列顺序对齐并补零是最稳妥的兜底StandardScaler只 fit 训练集避免把测试集分布预先引入。跑完一条样本会从 41 维展开到 120 维左右具体取决于 service 列的取值个数。2.3 一维特征向量到二维矩阵的三种组织方式CNN 卷积操作要求输入是二维网格结构所以要把一维向量重排成矩阵。常见做法有三种补零到 121 个值后 reshape 成 (11, 11)取前 120 个值拼成 (12, 10)或者按语义分组重排比如把时间窗口统计、主机统计、内容特征分别放在矩阵的不同区块让卷积核按语义块扫描。我一般倾向 reshape 成 (11, 11)。理由是第一组方案和第三组方案的实际精度差异通常只有一两个点但 (11, 11) 的短边更好算池化后的尺寸干净。语义分组的做法在少数项目里能多带 2 到 3 个点的精度代价是特征顺序的代码要单独维护。提示reshape 的物理意义在安全场景里没有严格规定但保持稳定复现很关键。特征顺序一旦定下来就要写死训练和推理必须走同一个映射函数否则模型换环境后精度悄悄掉几个点还查不出来。3. 用 PyTorch 写出可训练的 CNN 入侵检测模型数据准备到矩阵这一步之后模型本身反而简单。一个五分类的流量检测任务用不着 ResNet 那类深网络两层卷积加一层全连接已经能在 NSL-KDD 上拿到接近上限的精度。这一章把网络定义、训练循环、收敛判断一次说清。3.1 网络结构设计两层卷积加全连接输入是 (1, 11, 11)即单通道的 11x11 矩阵。第一层卷积用 32 个 3x3 卷积核padding1 保持宽高不变池化到 5x5第二层用 64 个 3x3 卷积核池化到 2x2最后压平接 128 维全连接和 5 类输出。两处 Dropout 和 ReLU 放在全连接之间。import torch import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self, num_classes5): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 32, kernel_size3, padding1), # (32, 11, 11) nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # (32, 5, 5) nn.Conv2d(32, 64, kernel_size3, padding1), # (64, 5, 5) nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # (64, 2, 2) ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(64 * 2 * 2, 128), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(128, num_classes), ) def forward(self, x): return self.classifier(self.features(x))选 3x3 卷积核是因为它感受野最小、参数少两层堆叠就能覆盖 5x5 的区域对 11x11 的输入足够。padding1 是为了让池化前的尺寸保持 11x11否则两层卷积后矩阵缩到 7x7 甚至更小全连接输入维度会跟着变。Dropout 放在全连接层而不是卷积层之间因为卷积层参数共享、本身抗过拟合真正容易过拟合的是最后那层 128 维。3.2 最小可运行训练代码与关键参数模型定义好之后把上一章的 Tensor 接上 DataLoader 就能开训。下面的代码默认跑 CPU单个 epoch 在两三百毫秒量级30 轮十几分钟跑完。from torch.utils.data import TensorDataset, DataLoader from torch.optim import AdamW # 预处理完的特征转 Tensorreshape 成 (样本数, 1, 11, 11) X torch.tensor(X_train.to_numpy(), dtypetorch.float32).reshape(-1, 1, 11, 11) y torch.tensor(y_train.astype(int).to_numpy(), dtypetorch.long) dataset TensorDataset(X, y) loader DataLoader(dataset, batch_size256, shuffleTrue) model SimpleCNN(num_classes5) criterion nn.CrossEntropyLoss() optimizer AdamW(model.parameters(), lr1e-3, weight_decay1e-4) for epoch in range(30): model.train() total_loss 0.0 for xb, yb in loader: optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() optimizer.step() total_loss loss.item() * xb.size(0) print(fepoch {epoch1}/30, avg_loss{total_loss / len(dataset):.4f})两个参数值得留意。weight_decay1e-4是 L2 正则在五万级样本上能明显压住全连接层的过拟合lr1e-3配合 AdamW 是这个规模任务的经验起点调高到 1e-2 容易在前几个 epoch 震荡调低到 1e-4 收敛太慢。要上 GPU 就在前面加一行device cuda if torch.cuda.is_available() else cpu然后把模型和每个 batch 都.to(device)。3.3 训练收敛判断与常见问题训练不能只看 loss 数值要对照一个预期曲线。正常情况前 5 个 epoch 损失会从 1.6 快速降到 0.3 以下20 个 epoch 后稳定在 0.06 到 0.1 之间。如果 10 个 epoch 还在 0.5 以上优先检查标准化是否生效而不是调学习率。整个网络的参数量在五万级具体分布如下层输出尺寸参数量Conv2d(1,32,k3,p1)(32, 11, 11)320MaxPool2d(2)(32, 5, 5)0Conv2d(32,64,k3,p1)(64, 5, 5)18496MaxPool2d(2)(64, 2, 2)0Linear(256,128)(128,)32896Linear(128,5)(5,)645这个体量的模型在单卡 CPU 上也能承受频繁实验是作为基线的理想选择。常见的训练期报错集中在 reshape 对不上预处理时特征维度不是 120 或 121或者 DataFrame 里混入了非数值列都会在reshape(-1, 1, 11, 11)这一行炸出来。排查时先print(X.shape)确认列数等于 121 或 120后者需要补一个常数列。4. 网络入侵检测模型的评估指标与决策阈值训练完第一件事不是看 loss而是看多分类报告。入侵检测的评估跟普通图像分类有个本质区别误报和漏报的成本不对称一个漏掉的入侵可能造成实打实的损失而一次误报只是告警噪音。所以交替评估模型精度的权重要高于单一准确率。4.1 混淆矩阵与分类报告怎么读from sklearn.metrics import classification_report, confusion_matrix model.eval() all_preds, all_labels [], [] with torch.no_grad(): for xb, yb in test_loader: logits model(xb) all_preds.extend(logits.argmax(dim1).tolist()) all_labels.extend(yb.tolist()) print(classification_report(all_labels, all_preds, target_names[Normal, DoS, Probe, R2L, U2R]))输出的每一列都值得细看。典型结果里 Normal、DoS、Probe 的 F1 都在 0.98 以上但 R2L 和 U2R 的召回会明显偏低常见只有 0.3 到 0.6大量少数类样本被误判成 Normal。原因直接指向训练集分布R2L 只有 995 条U2R 只有 52 条卷积核根本没有机会学到这两类的判别模式。这时候不要急着说模型不行先确认评估用的测试集分布再决定是调采样还是调阈值。4.2 决策阈值调整从 argmax 到拒绝式预测检测系统往往不是五选一而是「尽量把所有攻击都找出来」。一种常见做法是引入拒绝选项当模型对某个类别的最大概率低于阈值时不做硬分类而是标记为需要二次确认。probs torch.softmax(logits, dim1) max_prob, pred probs.max(dim1) # 最大置信度低于阈值的样本标记为 -1交给规则引擎或人工复核 pred[max_prob 0.6] -1阈值 0.6 是经验起点实际要根据业务里误报的成本来调调高阈值会漏掉低置信度攻击调低会引入大量误报。我一般会在验证集上枚举 0.4、0.5、0.6、0.7 四档分别统计检出率和误报率再挑业务能接受的点。对 R2L 和 U2R还可以单独降阈值到 0.3 左右只在这两类上提高敏感度。4.3 和随机森林、LightGBM 基线的横向对比CNN 并不是唯一选项也不是在所有指标上都赢。同一个预处理管道上跑一组 sklearn 基线才能确认深度学习带来的增量值不值得部署成本。以下是一组 NSL-KDD 五分类的实测量级模型AccuracyWeighted-F130 epoch 训练耗时随机森林500 棵树0.9860.985约 1 分钟LightGBM300 轮0.9890.989约 40 秒本方案 CNN0.9910.990约 5 分钟CPU这组数字受随机种子和编码顺序影响但量级是稳定的。CNN 相对 LightGBM 的收益主要落在 R2L、U2R 的召回上代价是训练时间高一个量级。如果业务里这两类攻击威胁不大直接用 LightGBM 部署反而更省事反过来如果少数类攻击恰是重点CNN 值得留。5. CNN 模型落地到真实网络的三个关键处理NSL-KDD 上精度够了不代表线上能直接用。真实流量有三个问题训练集里没有少数类被梯度淹没、流量是流式到达的、特征分布会随时间漂移。这一章讲三个对应处理都是实战里必做的。5.1 用 WeightedSampler 缓解少数类被淹没R2L 和 U2R 加起来不足 1%普通 DataLoader 里一个 epoch 的梯度几乎被 Normal 和 DoS 主导。WeightedSampler 按类别频率倒数加权抽样让少数类在一个 epoch 里被重复抽到变相提高它们对梯度的影响。from torch.utils.data import WeightedRandomSampler class_counts torch.bincount(y) weights 1.0 / class_counts.float() sample_weights weights[y] sampler WeightedRandomSampler(sample_weights, num_sampleslen(sample_weights), replacementTrue) loader DataLoader(dataset, batch_size256, samplersampler)关键参数是num_samples和replacement。保持num_samples等于原样本数每个 epoch 的计算量不变replacementTrue允许同一批少数类样本被重复抽出。副作用是总体准确率通常会掉一两个点但少数类召回明显上升这正是检测系统想要的。更进一步的替代方案是 Focal Loss通过调制因子压低易分样本的损失权重效果类似但不用改 DataLoader。5.2 滑动窗口在线推理与告警抑制真实网络流量是持续到达的检测单元通常是一个时间窗口。工程上会先按五元组把流量聚合成会话再在固定时间窗内聚合出特征向量交给模型打分。下面是一段简化的轮询伪代码import time WINDOW_SEC 30 def detection_loop(): while True: features aggregate_sessions(last_30s()) # 返回 (1, 121) 标准化特征 prob, label infer_once(features) # 内部复用矩阵映射函数 if label ! Normal and prob 0.85: alarm_queue.put((label, prob)) time.sleep(WINDOW_SEC)注意告警条件里加了一个经验做法置信度大于 0.85 才触发。生产环境还要再加一层抖动抑制要求连续 N 个窗口都报警才真正上页面告警否则单窗口的偶发误报会淹没运维注意力。这层逻辑放在热路径之外消耗可以忽略。5.3 模型漂移监控与定期重训线上特征分布会随业务变化慢慢漂移。常见做法是每周统计特征的均值、方差和模型置信度分布跟训练时的基线做 KL 散度比较超过阈值就触发增量重训。重训用最近一个月的数据加适量旧数据混合避免灾难性遗忘。这个机制不需要额外框架一支 cron 脚本加一份特征快照就能跑起来关键是提前把训练脚本做成可重复执行的流水线而不是手动点 Jupyter。6. 用 PyTorch 量化导出把 CNN 压缩到适合部署的尺寸模型只有五万参数单看不重但真实环境往往是十几个检测模型并排跑每个少几 MB、推断快几毫秒都会影响整体的资源预算。PyTorch 的动态量化在十层以内的小网络上收益很稳定而且代码量极少。import torch from torch.quantization import quantize_dynamic # 加载训练好的权重保证是在 CPU 上 model.load_state_dict(torch.load(best_model.pt, map_locationcpu)) model.eval() # 动态量化只量化 Linear 层权重从 fp32 降到 int8 qmodel quantize_dynamic(model, {torch.nn.Linear}, dtypetorch.qint8) # 导出 ONNX动态 batch 维度方便换推理引擎 dummy torch.randn(1, 1, 11, 11) torch.onnx.export(qmodel, dummy, nids_cnn.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}}, opset_version13)量化后模型文件大约从 780KB 降到 200KBCPU 上单样本推断耗时减少一半左右。动态量化只针对 Linear 层Convolution 层保持 fp32对这个结构已经够用如果想要更激进可以走torch.quantization的静态量化流程但需要准备校准数据集收益相对于复杂度并不大。量化精度的验证必须实测。加载量化模型跑一遍验证集跟原模型逐条对比准确率下降控制在 0.5% 以内才允许上线。另一个实用技巧是导出前先用几组真实流量样本跑一遍推理这一步不是为了校准而是为了暴露特征预处理的不一致——线上调用里的 reshape 顺序、列名对齐逻辑如果没有跟训练脚本共用同一份代码量化后的精度损失会被误认为是量化本身造成的。所有踩过的坑里预处理不一致造成的隐性精度损失远多于模型结构的问题。本文还有配套的精品资源点击获取