基于CNN+LSTM的网络流量检测系统设计与PyTorch实现

📅 发布时间:2026/9/16 10:46:19
基于CNN+LSTM的网络流量检测系统设计与PyTorch实现
简介这是一套面向计算机相关专业学生与初学者的CNNLSTM网络流量检测系统课程设计源码涵盖数据预处理、模型搭建、训练测试与主程序调用等完整流程适合计科、人工智能、通信工程、自动化、电子信息等专业用于课设、毕设或初期项目演示。压缩包共6个文件其中5个Python脚本分别承担数据预处理、模型定义、训练测试等核心环节另附1个文本文档说明项目设计与使用要点整体仅6KB轻量易部署。该工程已本地编译运行通过评审分达95分以上内容经过助教老师审定文本说明对项目结构、运行方式做了补充即使基础一般也能按说明快速上手。目前已有296人浏览学习。代码模块划分清晰可在此基础上二次开发如调整模型结构或改进网络流量特征帮助读者从数据加载到模型训练全链路理解深度学习在流量识别中的应用。1. 基于CNNLSTM的网络流量检测系统为什么这个课设组合最稳网络流量检测在课设里通常落到两个任务上一是二分类判断一条会话是正常还是攻击二是多分类把攻击细分到 DoS、Probe、R2L、U2R 等类型。很多人一开始会用随机森林或者单层 LSTM 来交差但真正落地时很快会发现两个问题流量特征之间有明显局部关联纯树模型抓不住而单一 LSTM 在短序列上又浪费了卷积对局部模式的高效提取能力。CNN 负责把空间上的特征组合蒸出来LSTM 负责把时间上的前后依赖串起来一套 CNNLSTM 的 python 源码基本能在课设评测集上跑到一个体面的准确率而且训练时间可控、结构好讲解。这就是为什么这个标题的组合在各类课程设计里几乎成了默认选项。适合人群很明确要做课设或毕设的学生以及刚入门深度学习流量分析、想有个能跑能改的 python 工程模板的开发者。2. 网络流量检测的数据准备把特征工程做成 CNN 和 LSTM 的输入2.1 数据集选型为什么首选 NSL-KDD而不是自己抓包做网络流量检测系统第一步不是写模型而是定数据集。自己用 Wireshark 抓 pcap 再手工提特征工作量足够再开一个课设而且标签不好打。业界和课设圈用得最多的公开数据集是 NSL-KDD它是 KDD Cup 1999 数据集的改进版剔除了大量冗余记录让训练集和测试集的比例更合理评价结果不会虚高。每条记录包含 41 维特征涵盖协议类型、服务类型、标志位等基础连接属性以及基于时间窗的统计特征。CICIDS2017 是更新、更贴近真实流量的选择特征维度接近 80文件也大不少。做课设我一般建议先用 NSL-KDD 跑通全流程再根据答辩需要决定要不要换高档数据集。注意一点NSL-KDD 的原始 txt 文件用 CSV 格式存储部分版本最后还会多出一列难度系数读入时要确认特征列和标签列的位置。2.2 特征编码与标准化写进源码里最稳妥的三个步骤流量数据的 41 维特征不是全数值型的protocol_type、service、flag 这三列是符号类型直接喂给 CNN 和 LSTM 会出问题。常见做法是先做标签编码再对数值特征做标准化最后构造标签。下面是这套系统里最核心的预处理源码片段。import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder, StandardScaler def load_nsl_kdd(path): df pd.read_csv(path, headerNone) # NSL-KDD 前 41 列是特征之后依次是标签列、难度系数列 X df.iloc[:, :41].copy() y df.iloc[:, 41].copy() return X, y train_df, train_label load_nsl_kdd(KDDTrain.txt) # 对三列符号特征单独做标签编码 cat_cols [1, 2, 3] # protocol_type, service, flag 的列下标 encoders {} for c in cat_cols: le LabelEncoder() train_df[c] le.fit_transform(train_df[c].astype(str)) encoders[c] le # 数值特征标准化直接在全表上做 scaler StandardScaler() X_norm scaler.fit_transform(train_df) # 二分类normal 为 0其余攻击类型统一为 1 y_bin np.where(train_label normal, 0, 1)代码逻辑分三段读取、编码、标准化。LabelEncoder 需要单独保存到 dict 里因为测试集做 transform 时必须复用训练集拟合出的映射测试集里出现训练集没见过的 service 值比如新端口协议时编码层会直接报错这是常见坑。Scaler 同样只在训练集上 fit测试集只做 transform避免数据泄露。最后把标签映射为二分类向量这是课设系统里最常见的处理方式如果做多分类则把四类攻击分别映射为 1 到 4。2.3 把单条样本变成时间序列滑动窗口怎么构造才合理CNN 可以直接吃单条 41 维特征向量但 LSTM 必须有时间步概念。这里的关键设计是滑动窗口把连续的 k 条会话记录叠成一个序列用这个序列预测下一条或窗口末尾那条的标签。窗口长度一般取 10 到 30太短捕捉不到完整攻击会话太长会让正样本被大量正常流量稀释。def make_sequences(X, y, window20): X_seq, y_seq [], [] for i in range(len(X) - window): X_seq.append(X[i : i window]) # 标签取窗口内最后一条样本的标签 y_seq.append(y[i window - 1]) return np.array(X_seq), np.array(y_seq) X_seq, y_seq make_sequences(X_norm, y_bin, window20)标签取窗口内最后一条意味着模型在利用前 19 条会话做上下文对当前会话做判断这个设定在实时检测时最接近真实行为你已经拿到过去的 19 条要判断刚到的这一条是否异常。数据量上125973 条训练记录切出约 125953 个窗口足够训练一个千万参数以内的模型。切分测试集时要注意不要随机切按时间顺序取后 20% 做验证集否则模型会偷看到未来的分布。LSTM 做时间序列预测的 python 项目基本都是这个套路网络流量检测只是把预测目标从连续数值换成了分类标签。3. CNNLSTM模型的结构设计让卷提取特征、让循环记忆上下文3.1 CNN 在流量检测里提取的是特征邻域组合不是图像纹理很多人第一次看到 CNNLSTM 就以为要画一张类似 LeNet 的 CNN 结构图然后用卷积核在二维矩阵上滑动。实际上流量数据是 41 维的一维向量不是图像CNN 在这里使用的是 Conv1d卷积核沿特征维度滑动提取的是相邻字段之间的联合模式。比如某几个特征同时取到特定值时可能代表一次短连接的暴力猜解端口扫描行为则往往表现为特定标志位和连接时长的组合。CNN 在图像任务里找像素邻域的局部模式在流量检测里找的是特征字段的局部组合原理一致但维度只有一维。这也是为什么网络结构不要去堆 VGG 那种十几层的深度41 个特征根本支撑不了太深的卷积栈两层 Conv1d 加一个池化绰绰有余。3.2 PyTorch 实现 CNNLSTM 分类器一份可以直接抄的完整源码模型分成两个阶段每个时间步的 41 维特征先过两层 Conv1d压缩成一个 64 维向量然后 LSTM 在这个向量序列上建模时间依赖。这个结构的好处是卷积和循环的职责边界清楚答辩时好讲调参时也好定位问题。import torch import torch.nn as nn class CNNLSTM(nn.Module): def __init__(self, feature_dim41, hidden64, num_layers2, num_classes2, dropout0.3): super().__init__() # 第一段对单个时间步的特征做一维卷积 self.conv nn.Sequential( nn.Conv1d(1, 32, kernel_size5, padding2), nn.ReLU(), nn.Conv1d(32, 64, kernel_size5, padding2), nn.ReLU(), ) # 第二段LSTM 处理时间维度输入维度是卷积输出通道数 self.lstm nn.LSTM( input_size64, hidden_sizehidden, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0.0 ) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden, num_classes) def forward(self, x): # x: (batch, window, feature_dim) batch, window, feat x.size() # 将每个时间步独立送入卷积 x x.reshape(batch * window, 1, feat) # (batch*window, 1, 41) x self.conv(x) # (batch*window, 64, 41) x x.mean(dim2) # 全局平均池化 - (batch*window, 64) x x.view(batch, window, -1) # (batch, window, 64) out, _ self.lstm(x) # (batch, window, hidden) out out[:, -1, :] # 取最后一个时间步输出 out self.dropout(out) logits self.fc(out) return logitsforward 的流程分四步把 (batch, window, feat) 展平成 batch*window 条独立特征向量过卷积层后在特征维度上做全局平均池化还原成 (batch, window, 64) 的序列结构交给 LSTM最后只取最后一个时间步的隐状态接全连接分类。卷积的 padding2 让 41 维特征经过 kernel_size5 的卷积后长度不变两层卷积后仍然保持 41最后用 mean 池化把特征长度这一维压掉只保留通道数 64 作为 LSTM 的输入维度。这样做空间上每个时间步的信息量一致不会因为某一步的特征被池化过多而丢失信息。3.3 关键参数怎么调卷积核大小、LSTM 层数、单向还是双向卷积核大小决定了特征组合的视野kernel_size5 表示一次综合相邻 5 个特征字段的信息3 到 7 都是合理范围。取太小组合性不足取太大则把不相关的字段强行揉在一起。LSTM 的 hidden_size 控制在 64 到 128 之间比较经济num_layers 用两层足够层数再往上训练时间和过拟合风险同步上升。课设场景用单向 LSTM 就够双向 LSTM 在离线分析场景里有增益但实时检测时当前时刻不能依赖未来信息引入了双向结构反而让部署阶段变得别扭不要为了结构图好看无脑上双向。LSTM 的遗忘门在这里的实际含义是决定前面若干条会话的历史状态有多少要保留到当前判断如果一条恶意流量和它相关的探测行为隔了十几个窗口遗忘门决定这些旧特征是否还参与决策。这套源码里 dropout0.3 同时作用在 LSTM 层间和全连接前对抑制过拟合很关键。4. 训练与评估攻击类别不均衡是课设里最大的坑4.1 训练循环源码保存验证集上最好的那一个模型模型结构定好后训练部分要解决两件事一是窗口化后的样本连续性强不能用随机打散的方式切验证集二是多分类时 R2L 和 U2R 这两类样本极少准确率高不代表模型真的学到了攻击模式。训练循环按标准 pytorch 工程写法组织要点全部写在注释里。from torch.utils.data import TensorDataset, DataLoader # 按时间顺序切分前 80% 训练后 20% 验证 split int(len(X_seq) * 0.8) X_train, X_val X_seq[:split], X_seq[split:] y_train, y_val y_seq[:split], y_seq[split:] train_ds TensorDataset(torch.tensor(X_train, dtypetorch.float32), torch.tensor(y_train, dtypetorch.long)) train_loader DataLoader(train_ds, batch_size64, shuffleTrue) model CNNLSTM(num_classes2) loss_fn nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) best_acc 0.0 epochs 30 for epoch in range(epochs): model.train() for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss loss_fn(pred, yb) loss.backward() optimizer.step() # 验证阶段 model.eval() with torch.no_grad(): val_pred model(torch.tensor(X_val, dtypetorch.float32)) val_acc (val_pred.argmax(dim1) torch.tensor(y_val, dtypetorch.long)).float().mean().item() if val_acc best_acc: torch.save(model.state_dict(), best_flow_model.pt) best_acc val_acc print(fepoch {epoch:02d}, save best, val_acc{val_acc:.4f})训练循环里 epoch 内用 DataLoader 随机打乱样本是合理的因为同一个窗口里的序列已经固化了时间顺序跨窗口的随机不会打乱单条序列内部结构。验证阶段不做随机直接整批前向。保存模型用的是验证集准确率最优而不是最后一轮这样即使训练后期过拟合拿到的依然是最优权重。weight_decay 设置 1e-4 做正则配合 dropout 双保险。4.2 必调参数配置表从输入维度到训练轮数的完整清单下面这张表是这套系统里相对稳妥的起始配置也是课设文档里最有含金量的一页。每改一个参数记录一次验证集指标答辩时能直接展示调参过程。参数名建议值说明feature_dim41NSL-KDD 特征维度换 CICIDS 时改这里window20滑动窗口长度10-30 之间用验证集挑选conv_kernel5卷积核大小对应特征邻域组合范围hidden64LSTM 隐状态维度64-128 之间权衡num_layers2LSTM 层数超过 3 层收益很小dropout0.3正则强度过拟合时调到 0.5batch_size64显存不足时降到 32lr1e-3训练不稳定时降到 5e-4epochs30配合早停连续 5 轮不提升就停4.3 评估指标不能只看准确率多分类必须看 F1网络流量检测的正负样本天然不均衡NSL-KDD 训练集里 DoS 占大头R2L 和 U2R 样本量极少。如果模型把所有样本都预测成多数类准确率可能超过 80%但真实攻击检出能力几乎为零。所以评估代码要用 classification_report 输出每个类别的精确率、召回率和 F1。from sklearn.metrics import classification_report, confusion_matrix model.load_state_dict(torch.load(best_flow_model.pt)) model.eval() with torch.no_grad(): test_pred model(torch.tensor(X_test, dtypetorch.float32)).argmax(dim1).numpy() print(classification_report(y_test, test_pred, target_names[normal, attack])) cm confusion_matrix(y_test, test_pred) print(cm)F1 在类别不均衡时比准确率可靠得多尤其是多分类场景一定要设置 target_names否则输出里只有 0 和 1看不出哪一类被识别坏了。如果发现某个攻击类别的召回率明显偏低常见做法是给 CrossEntropyLoss 传入 class_weight 向量把少数类的损失权重放大。类别不均衡这个问题任何网上现成的网络流量检测 python 源码项目都会遇到区别只在于有没有在文档里说清楚处理方式。5. 新流量上线单条预测、滑动更新与课设文档的落地技巧单条预测和训练不一样没有批量数据可以一次喂进去需要维护一个历史窗口的缓存。下面这段源码直接放在检测模块里即可。def predict_one(raw_record, model, scaler, encoders, history, window20): # raw_record 是与训练集同格式的原始特征向量长度 41 row raw_record.copy() for c, le in encoders.items(): row[c] le.transform([str(row[c])])[0] norm scaler.transform([row])[0] history.append(norm) if len(history) window: return None, None seq np.array(history[-window:]) model.eval() with torch.no_grad(): logits model(torch.tensor(seq, dtypetorch.float32).unsqueeze(0)) prob torch.softmax(logits, dim1).squeeze().numpy() return int(prob.argmax()), prob预测函数返回正常流程的两个关键信息类别和置信度。生产环境里可以加一道置信度阈值概率低于 0.6 的流量挂起重新检测不直接下结论。增量检测时每来一条新记录就 append 一次 history窗口满后每次前进一条、丢弃最旧一条这个机制天然支持实时数据流。对于趋势漂移问题比如网络环境变化导致正常流量分布改变策略是每周用历史归档数据微调模型所谓微调不是全量重训而是用低学习率在旧模型基础上继续训练几个 epoch保留已学到的模式同时适配新分布。课设项目最后的文档说明建议把 2.3 节的滑动窗口代码和 4.2 节参数表原样放进去这两个地方是最容易让答辩老师问出细节的。准备好三个问题的答案为什么窗口取 20、为什么用 CNN 而不是直接 LSTM、少数类攻击识别失败时怎么解释。把这三条过一遍课设答辩的核心内容就都覆盖到了。本文还有配套的精品资源点击获取