NSL-KDD上跑CNN:入侵检测实战项目全解析

发布时间:2026/10/18 19:44:53
NSL-KDD上跑CNN:入侵检测实战项目全解析 简介入侵检测是网络安全的核心防线传统方法依赖人工特征工程与机器学习分类器。深度学习中的卷积神经网络CNN在图像领域表现出色但面对表格型网络流量特征时如何有效应用成为热点。NSL-KDD作为经典的入侵检测基准数据集包含41维特征将其重构为单通道一维序列配合Conv1d卷积核设计可让CNN自动学习特征相关性并实现高精度二分类。内容覆盖从数据预处理独热编码、MinMax归一化到模型构建、训练保存最优参数、单条样本推理的完整工程流程并分享复现过程中的典型踩坑经验。基于NSL-KDD的实践能帮助初学者快速上手CNN在入侵检测中的实战应用。1. 这个项目到底解决什么问题表格数据上跑 CNN照样能把入侵流量揪出来很多人听到「用 CNN 做网络入侵检测」第一反应是质疑入侵检测的输入是 41 维表格特征又不是图像卷积核怎么下得去手但这个项目恰恰用 NSL-KDD 数据集证明了另一件事——把一维特征序列当作单通道的「窄图」来做卷积模型不仅能学而且精度曲线、召回曲线全都稳稳压过传统的机器学习方案。说白了CNN 在表格数据上并不是玄学关键在于你怎么设计卷积核的尺寸和感受野。这个项目是一套完整可跑通的毕业设计级代码从原始 NSL-KDD 数据集的预处理、独热编码、MinMax 归一化到 CNN 模型定义、训练、保存 best_model.pth再到用 Predict.py 做单条样本的推理整条链路都是闭环的。适合两类人一类是正在做毕设、需要一份能写在论文里的完整实现的学生另一类是刚入门深度学习、想找一个公开数据集加 CNN 的实战练手项目的人。评审分 98 分这个数字我不评价但代码结构确实是按「数据层、模型层、训练层、推理层」四层拆开的拿来改比拿来抄更有价值。2. 数据从哪里来NSL-KDD 的预处理链路拆解2.1 NSL-KDD 数据集的基本盘NSL-KDD 是 KDD Cup 1999 数据集的改进版本去掉了原始训练集里大量冗余记录让模型在训练时不会被重复样本带偏。每条样本包含 41 维特征外加一个标签列。41 维特征大体可以分为四组基本连接特征比如 duration、protocol_type、service、flag、内容特征比如登录失败的次数、su 命令尝试的次数、流量特征比如过去两秒内相同主机的连接数、以及主机流量统计特征。这个项目把标签统一做成了二分类Normal 是一类其余所有攻击类型包括 DoS、Probe、R2L、U2R全部归为 Attack。所以 Train.py 里最终是 2 分类的 softmax 输出而不是 5 分类。这个设计决策对刚入门的人很友好——二分类的收敛速度和模型稳定性都比五分类好控制得多而且论文里的混淆矩阵画出来也更直观。2.2 PreHandle.py 里的两个关键操作独热编码和 MinMax 归一化预处理脚本 PreHandle.py 干了两件最重要的事。第一件是把离散特征做独热编码。41 维特征里protocol_type、service、flag 这三个是离散值特别是 service 有几十种取值直接喂给模型会用数字大小引入假的顺序关系。常见做法是先把它们转成整数索引再通过独热编码展开展开之后总维度从 41 拉高到 120 左右具体取决于 service 的取值个数。# PreHandle.py 核心逻辑结构还原 import pandas as pd from sklearn.preprocessing import OneHotEncoder, MinMaxScaler def preprocess(df, is_trainTrue): # 分离标签列 labels df[label].apply(lambda x: 1 if x ! normal else 0) features df.drop(columns[label]) # 离散特征独热编码 cat_cols [protocol_type, service, flag] cat_data features[cat_cols] encoder OneHotEncoder(handle_unknownignore) cat_encoded encoder.fit_transform(cat_data).toarray() # 数值特征 MinMax 归一化 num_cols [c for c in features.columns if c not in cat_cols] num_data features[num_cols] scaler MinMaxScaler(feature_range(0, 1)) num_scaled scaler.fit_transform(num_data) # 拼接数值特征和独热特征 import numpy as np X np.hstack([num_scaled, cat_encoded]) return X, labels.values, encoder, scaler逻辑说明先分离出标签列并映射成 0/1再把离散特征交给 OneHotEncoder数值特征交给 MinMaxScaler最后横向拼接。这里有个细节值得注意——handle_unknownignore这个参数。测试集里可能出现训练集从未出现过的 service 取值NSL-KDD 确实有这种情况忽略未知类别能避免推理时特征维度对不上直接报错。参数说明cat_cols 和 num_cols 的划分必须准确否则数值列被当成离散列编码后维度会莫名其妙翻倍。feature_range(0, 1)是为了把数据压缩到 0 到 1 之间很多 CNN 的激活函数比如 ReLU对这个区间更友好梯度传播也更稳定。2.3 Change 和 minMax.png 在链路里扮演什么角色项目根目录下的 Change 文件记录的是数据集结构和预处理步骤的调整说明属于「改到哪里、为什么改」的备注文档。minMax.png 则是归一化前后数据分布的对比图通常在论文的「数据预处理」章节里充当可视化佐证。这两个文件对跑代码不构成阻塞但写论文准备答辩的时候非常有用。实际操作时你不需要手动把 MinMaxScaler 的变换矩阵记下来——在 Train.py 之前先单独跑一次 PreHandle.py得到处理后的特征矩阵和标签之后所有训练、验证都基于这份处理好的数据。我一般会在预处理这一步把处理好的结果存成 .npy 文件这样后面反复调模型结构时不用每次都重新跑一遍独热编码和归一化。3. 核心模块拆解一个请求从进入到返回的完整链路3.1 CNNMould.py模型长什么样CNNMould.py 这个文件名就很直白——Mould 不是 Model 的拼写错误它的意思是「模具」即固定好的网络骨架。项目里的 CNN 不是图像领域那种二维卷积而是针对一维特征序列设计的卷积结构。把预处理后的变长特征向量 reshape 成(1, feature_dim)即单通道一维信号然后通过 Conv1d 逐层提取局部特征。# CNNMould.py 网络结构还原 import torch.nn as nn import torch class CNNMould(nn.Module): def __init__(self, input_dim, num_classes2): super(CNNMould, self).__init__() self.conv1 nn.Sequential( nn.Conv1d(1, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.MaxPool1d(kernel_size2, stride2) ) self.conv2 nn.Sequential( nn.Conv1d(64, 128, kernel_size3, padding1), nn.BatchNorm1d(128), nn.ReLU(inplaceTrue), nn.MaxPool1d(kernel_size2, stride2) ) self.fc nn.Sequential( nn.Linear(self._compute_fc_dim(input_dim), 128), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(128, num_classes) ) def _compute_fc_dim(self, input_dim): # 经过两次 MaxPool1d(2) 后长度约为 input_dim // 4 return (input_dim // 4) * 128 def forward(self, x): # 输入形状: (batch, 1, input_dim) x self.conv1(x) x self.conv2(x) x x.view(x.size(0), -1) x self.fc(x) return x逻辑说明输入先经过两个卷积块每个卷积块由 Conv1d BatchNorm ReLU MaxPool1d 组成。Conv1d 的 kernel_size 设为 3padding 为 1所以卷积后特征长度不缩水真正缩减长度的是 MaxPool1d。两次池化后特征长度约等于 input_dim 除以 4再经过全连接层压到二分类输出。Dropout 放在全连接层中间比例 0.5是应对 NSL-KDD 这类表格数据过拟合的标准做法。参数说明kernel_size3 意味着卷积每次只看相邻 3 个特征点这种局部感受野对表格数据是有物理意义的——相邻特征往往存在相关性比如同类别的流量统计特征。BatchNorm1d 在表格数据上尤其重要它让每批数据的特征分布都过一遍归一化训练过程不至于因为某些特征尺度大而剧烈震荡。卷积核数量 64 和 128 属于中间配置如果你的机器显存足够可以往上调试试看。3.2 Train.py训练循环和 best_model.pth 的保存逻辑训练脚本是整个项目的发动机。它负责加载预处理后的样本按比例切分训练集和验证集常见的比例是 7:3 或 8:2然后进入标准的 PyTorch 训练循环。关键点在于模型保存策略不是最后一个 epoch 的模型最好而是验证集上准确率最高的那个 epoch 会被存成 best_model.pth。# Train.py 训练逻辑核心片段 import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset from CNNMould import CNNMould # 加载预处理数据 X np.load(data/X.npy) y np.load(data/y.npy) X X[:, np.newaxis, :] # 增加通道维度形状变为 (N, 1, input_dim) # 切分训练集和验证集 split_idx int(len(X) * 0.8) X_train, X_val X[:split_idx], X[split_idx:] y_train, y_val y[:split_idx], y[split_idx:] train_loader DataLoader(TensorDataset(torch.FloatTensor(X_train), torch.LongTensor(y_train)), batch_size64, shuffleTrue) val_loader DataLoader(TensorDataset(torch.FloatTensor(X_val), torch.LongTensor(y_val)), batch_size128, shuffleFalse) model CNNMould(input_dimX.shape[2], num_classes2) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) best_acc 0.0 for epoch in range(epochs): model.train() for batch_x, batch_y in train_loader: optimizer.zero_grad() out model(batch_x) loss criterion(out, batch_y) loss.backward() optimizer.step() # 验证阶段 model.eval() total, correct 0, 0 with torch.no_grad(): for batch_x, batch_y in val_loader: out model(batch_x) pred out.argmax(dim1) correct (pred batch_y).sum().item() total batch_y.size(0) val_acc correct / total if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth) print(fEpoch {epoch}: val_acc {val_acc:.4f} (saved))逻辑说明训练循环里每个 epoch 先切换成 train 模式前向传播算 loss反向传播更新梯度然后在验证集上计算准确率。model.eval()和torch.no_grad()这两行是推理阶段的标准姿势——eval 模式会关闭 Dropout 的随机行为no_grad 关掉梯度的计算图追踪加速验证并保证结果可复现。只有当验证准确率超过历史最优值时才覆盖保存 best_model.pth。参数说明batch_size 64 和 lr 1e-3 是 PyTorch 分类任务的常见起点。如果训练震荡不收敛优先把 lr 降到 1e-4 看看。优化器选 Adam 而不是 SGD 的主要原因是 Adam 对学习率的敏感度低很多适合毕设这种「没有时间精调超参数」的场景。4. 评估从纸面到代码accuracy.jpg、precision.jpg 和 Predict.py 怎么配合4.1 Mould Acc.png、accuracy.jpg、precision.jpg 各自说明什么根目录下有三张图片文件很多第一次打开项目的人会搞混。Mould Acc.png 是训练过程中每个 epoch 的准确率曲线通常是在训练脚本里通过 matplotlib 逐点画出来的用来观察模型是否收敛、什么时候开始过拟合。accuracy.jpg 和 precision.jpg 则是最终模型在验证集或测试集上的综合评估结果——前者是整体准确率后者是精确率。这条链路里值得注意的反直觉点在 NSL-KDD 这种类别不平衡的数据集上准确率可能虚高。因为大多数攻击流量都属于某些大类模型只要把大类学明白了准确率就能到 90% 以上但精确率的对比才能看出模型是不是把 Normal 也误判成了攻击。所以我通常建议把 precision、recall、F1 一起看项目里提供的 precision.jpg 就是一个很好的补充维度。# 评估阶段计算 precision 的参考代码 from sklearn.metrics import precision_score, recall_score, f1_score all_preds, all_labels [], [] model.eval() with torch.no_grad(): for batch_x, batch_y in val_loader: out model(batch_x) pred out.argmax(dim1) all_preds.extend(pred.numpy()) all_labels.extend(batch_y.numpy()) print(Precision:, precision_score(all_labels, all_preds)) print(Recall:, recall_score(all_labels, all_preds)) print(F1-Score:, f1_score(all_labels, all_preds))逻辑说明这段代码手动完成了一个二分类评估流程。把所有 batch 的预测结果和真实标签攒到两个列表里最后统一交给 sklearn 的 metrics 模块计算。不直接在 batch 内算 precision 是因为批大小太小会影响指标的稳定性全量算更可靠。参数说明precision_score 默认 pos_label1也就是说它默认把标签为 1 的类别当作正样本。你在自己的项目里如果习惯把 Normal 设成 1 而 Attack 设成 0那 precision 的含义就反了需要显式传pos_label0。这个坑很隐蔽我第一次用这个脚本的时候完全没意识到纯靠对照混淆矩阵才查出来。4.2 Predict.py加载模型做单条推理不是黑匣子Predict.py 是整套代码里最后一个环节也是演示效果最好的一环——把一条新的网络连接记录喂给训练好的模型让它判断这条记录是正常流量还是攻击流量。整个流程分三步先按训练时的预处理规则处理输入数据再加载 best_model.pth 的权重最后前向传播得到概率分布。# Predict.py 核心流程 import torch import numpy as np from CNNMould import CNNMould def predict_single(sample, encoder, scaler, model_pathbest_model.pth, input_dim): # 1. 预处理数值特征归一化 离散特征独热编码 # sample 是按原始格式传入的 41 维特征 dict cat_values [[sample[protocol_type], sample[service], sample[flag]]] cat_encoded encoder.transform(cat_values).toarray() num_values [sample[c] for c in num_cols] num_scaled scaler.transform([num_values]) feature_vec np.hstack([num_scaled, cat_encoded]) # 2. 加载模型权重 model CNNMould(input_diminput_dim) model.load_state_dict(torch.load(model_path, map_locationcpu)) model.eval() # 3. 前向推理 x torch.FloatTensor(feature_vec).unsqueeze(0) # (1, 1, input_dim) with torch.no_grad(): out model(x) prob torch.softmax(out, dim1) pred torch.argmax(prob, dim1).item() return pred, prob.numpy()逻辑说明predict_single 函数接收一条原始格式的样本先对离散特征调用训练时保存的 encoder 做 transform不是 fit_transform对数值特征调用训练时的 scaler 做 transform。这一步极其关键——测试阶段的变换规则必须完全复用训练阶段的统计参数否则特征分布漂移推理结果直接翻车。然后加载保存的模型权重单条样本前向传播softmax 转成概率后取 argmax 得到 0 或 1 的预测标签。参数说明map_locationcpu是给没有 GPU 的机器准备的。如果你的训练环境用了 GPU而推理环境只有 CPU这个参数能自动把权重加载到 CPU 上反过来在你自己的机器上训练和推理都用 GPU 的话可以去掉这个参数。5. 避坑指南我在复现这个项目时踩过的 5 个坑5.1 特征顺序错位导致精度直接崩掉现象模型训练出来的 accuracy.jpg 还算正常但用 Predict.py 推理新数据时结果明显不对几乎全判成同一个类别。原因训练时读数据用的列顺序和推理时构造 sample dict 的键顺序不一致。NSL-KDD 原始 CSV 的列顺序是有讲究的如果你在推理阶段重新指定了字段顺序encoder 和 scaler 的 transform 对不上号特征矩阵里每一列实际对应的含义已经被打乱了。解决在预处理脚本开头定义一个固定的COLUMN_ORDER常量列表训练和推理全部基于这个顺序来索引特征列不要靠列名或者位置记忆。5.2 验证集上 accuracy 高但 precision 低得离谱现象训练过程中 val_acc 已经到 95% 以上但打印出来的 precision 只有 60% 左右。原因NSL-KDD 数据集中 Normal 样本和 Attack 样本数量比例并不均衡如果模型倾向于把所有样本都预测为多数类accuracy 会被拉高但 precision 因为假阳性太多而被压低。解决不只看 accuracy在 Train.py 或单独评估脚本里加打印 confusion_matrix看看模型是把 Normal 误判成 Attack 多还是把 Attack 误判成 Normal 多。根据误判方向决定要不要换损失函数或者调整分类阈值。5.3 MinMaxScaler 在测试集上重复 fit 导致数据泄露现象自己重新写了预处理脚本后训练集精度没变但测试集表现莫名其妙地差。原因对测试集做了scaler.fit_transform()而不是只用训练集的 scaler 做 transform。重新 fit 会让测试集的 min/max 参与归一化计算相当于把测试集的信息泄漏到了归一化参数里真实场景中这是不允许的。解决训练时 fit 一次 scaler保存成文件可以 pickle 序列化之后所有测试集和推理样本全部调用 transform永远不重复 fit。5.4 Conv1d 的 kernel_size 比特征维度还大直接报错现象自己改特征维度之后模型 build 时报 kernel size 和输入维度不匹配的错误。原因如果 input_dim 很小比如只保留部分特征后压缩到 20 维而 kernel_size 还是 3 甚至 5在前向传播第一个卷积层时可能直接就维度溢出了。解决在 CNNMould 的 forward 里先打印输入形状确认通道数是否为 1、特征维度是否大于 kernel_size。更稳妥的做法是把 kernel_size 设成 min(3, input_dim - 1)动态适配。5.5 Dropout 在推理阶段还开着现象同一批测试数据反复预测结果每次都不一样而且差异还挺大。原因load 模型后没有调用model.eval()导致 Dropout 层的随机 mask 还在生效推理变成了蒙特卡洛丢弃采样。解决在推理前加一行model.eval()这个是最几行代码但最容易忘的操作没有之一。检查自己的代码时优先看有没有 eval 和 no_grad。6. 怎么改造成自己的项目三个方向从改数据到改模型如果你想拿这份代码去做自己的毕设或者课程设计不建议整篇推倒重来更建议在现有骨架上做增量式改造。第一个改造方向是把二分类换成五分类。NSL-KDD 的攻击标签其实分成 DoS、Probe、R2L、U2R 四类加上 Normal 正好五类。改动量集中在三处PreHandle.py 里把 label 映射从 0/1 变成 0 到 4 的整数、CNNMould.py 里把 num_classes 参数从 2 改成 5、Train.py 里计算 precision 时把 sklear 的 metrics 参数从pos_label1改成averagemacro。这个改动能让论文的分析层次深一个量级但训练收敛难度也会上来建议加一点数据增强或类别权重。第二个改造方向是换模型结构把 Conv1d 换成一维 Transformer 或 LSTM。表格入侵检测领域这两年有个趋势是序列模型比卷积模型更适合捕捉时序上的流量变化因为攻击行为往往不是单个数据包的特征而是连续几个数据包之间的关联。你可以保留整套现有的训练、评估、预测骨架只替换 CNNMould.py 里的 forward 逻辑这正好能体现你对模型有独立理解。第三个方向是按 NSL-KDD 特征分组分别送入不同的卷积分支。我之前在给某课程设计做改造时试过把 41 维特征按「基本连接」「内容」「流量统计」三组拆开每组一个小的 Conv1d 分支提取特征最后 concat 在一起进全连接层。效果比单一大卷积网络稳定而且解释性强——你知道哪组特征对最终分类贡献最大画特征权重图时很有素材。说到这我想起自己做这个项目时的一个习惯每次改完代码强制走一遍预处理 → 训练 → 评估 → 单条预测完整链路哪怕只是改了一个 kernel_size 也要跑通。因为很多问题不是在训练那一环暴露的而是在 Predict.py 推理那一步才炸出来。这个习惯我现在还在用希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询