ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于CNN+LSTM的网络流量检测系统:PyTorch实现与KDD Cup实战

基于CNN+LSTM的网络流量检测系统:PyTorch实现与KDD Cup实战 简介基于CNNLSTM的网络流量检测系统源码面向高校Python课程设计、毕业设计以及入门深度学习流量识别方向的开发者项目采用PyTorch框架实现使用kddcup.data_10_percent数据集训练模型10个训练周期即可达到95%以上的准确率兼顾学习成本与实验效果。资源共6个文件以5个Python脚本和1份使用说明txt文档为主压缩包仅7KB轻量易部署。脚本模块划分清晰data_preprocess.py负责数据预处理、特征归类、可视化与去除线性相关特征并划分训练测试集data_load.py重写Dataset接口完成数据装载train_and_test.py封装了模型训练与验证流程。已有305人学习下载。对于想快速上手CNNLSTM组合模型、完成课程设计或理解网络流量分类完整流程的读者这份源码提供了从数据清洗、模型搭建到训练测试的一体化参考代码结构紧凑便于二次修改与扩展。1. 基于CNNLSTM的网络流量检测系统从KDD Cup数据集到95%准确率的PyTorch实现这套基于 CNNLSTM 的网络流量检测系统是典型的「深度学习 网络安全」课设项目但比大多数停留在 MNIST 分类的作业要扎实得多。它用 KDD Cup 1999 的 10% 数据集把网络连接记录映射成特征矩阵再交给 CNN 提取局部模式、LSTM 捕捉时序依赖10 个 epoch 就能在测试集上拿到 95% 以上的准确率。整套代码拆成 data_preprocess.py、data_load.py、model.py、train_and_test.py、main.py 五个文件职责划分得很清楚适合作为课程设计提交也适合刚开始接触 PyTorch 的工程师拿来练手。KDD Cup 99 虽然年代久远但作为分类基准依然有参考价值尤其是特征工程的处理思路放在今天的入侵检测系统 IDS 项目里依然通用。2. 数据管道设计从 data_preprocess.py 到标准 CSV 的关键转换2.1 KDD Cup 99 数据集的先天缺陷必须处理KDD Cup 99 的原始数据格式是逗号分隔的文本行每行 41 个特征加 1 个标签。但直接用原始文件喂给 PyTorch 的 Dataset 会踩三个坑第一数据集没有列名你根本不知道第 7 列是src_bytes还是dst_bytes第二里面含有protocol_type、service、flag这样的字符串特征神经网络只吃数值第三连续特征之间的数值量级差异极大比如src_bytes可能到数十万而count只有个位数不归一化会导致训练震荡。data_preprocess.py处理这些问题的顺序非常标准而且作为课设展示文档性很好。它首先为原始数据补充列名KDD Cup 99 官方文档给出了 41 个特征名从duration、protocol_type一直到dst_host_srv_rerror_rate加上最后一列label正好 42 列。其次是特征归类把 41 个特征分成连续型和离散型离散型特征做独热编码连续型特征做标准化。这里有个值得注意的细节41 个特征转独热编码后维度会膨胀到 120 左右这正好为后面 CNN 的 1D 卷积提供了合适的输入宽度。2.2 去除线性相关特征的可视化判断数据预处理里比较有信息量的一步是去除线性相关特征。代码用df.corr()计算特征间的皮尔逊相关系数矩阵然后用热力图展示。在实际操作中你会发现srv_count和serror_rate这类特征之间相关度能到 0.9 以上。两个强相关特征同时进模型相当于把同一信息重复喂了两遍不仅增加计算量还可能放大噪声。常见做法是设定阈值 0.8超过阈值的两两组合中保留其中一个。但这里建议保留与标签相关性更高的那个特征而不是简单地随机丢弃否则可能丢失有效判别信息。# 基于相关性矩阵剔除冗余特征 import pandas as pd import numpy as np def drop_high_corr_features(df, label_collabel, threshold0.8): corr_matrix df.corr().abs() upper_tri corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k1).astype(bool)) to_drop [column for column in upper_tri.columns if any(upper_tri[column] threshold)] # 与标签的相关性排序保留更相关的 label_corr df.corr()[label_col].abs() keep [] for col in to_drop: if col not in keep and label_corr[col] ! 1.0: keep.append(col) df_reduced df.drop(columnskeep) return df_reduced, keep这段代码的逻辑是先取出相关系数矩阵的上三角区域避免A-B和B-A重复判断然后筛出所有相关系数超过 0.8 的特征列。最后一步是关键根据这些候选列与label列的相关性强弱决定去留。一个容易忽略的问题是这个阈值的选择阈值设太低会误删有效特征0.8 偏保守适合特征数量多但样本量一般的场景如果样本量很大可以放宽到 0.9 以下让 CNN 自己去学习特征组合。2.3 训练集与测试集的划分策略数据预处理的末段是划分训练集和测试集这里有一个对最终成绩影响很大的细节KDD Cup 99 的原始数据里训练集和测试集的分布并不一致测试集包含训练集中未出现过的攻击类型。所以如果直接train_test_split(X, y, test_size0.2, random_state42)你可能在测试集上拿到虚高的分数但换到真实流量场景就崩了。更稳妥的做法是使用stratifyy参数做分层采样确保训练集和测试集中各类别比例一致。from sklearn.model_selection import train_test_split # stratify 保证类别分布一致random_state 固定复现 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) train_df pd.concat([X_train, y_train], axis1) test_df pd.concat([X_test, y_test], axis1) train_df.to_csv(./data/train_dataset.csv, indexFalse) test_df.to_csv(./data/test_dataset.csv, indexFalse)这里还有一个可选的补充处理KDD Cup 99 的标签有 23 种攻击类型如果按原始标签做多分类类别不均衡问题会非常严重——smurf和neptune两类就占了 60% 以上的样本。常见的解法是把攻击类型归并为四大类DoS、Probe、R2L、U2R加上正常流量Normal共 5 类。这个映射表建议在预处理阶段就做好因为它直接影响后续模型输出层的神经元数量和损失函数的计算方式。3. 模型实现PyTorch 中 CNN 与 LSTM 的拼接方式与维度演算3.1 Dataset 类的封装data_load.py 的接口设计data_load.py的核心工作是继承torch.utils.data.Dataset把 CSV 文件读入并封装成模型可迭代的数据源。关键代码在__getitem__方法里返回特征张量和标签张量。PyTorch 的 DataLoader 会调用这个接口按 batch 取数据所以这一层的主要职责是类型转换。import torch from torch.utils.data import Dataset import pandas as pd class TrafficDataset(Dataset): def __init__(self, csv_path): df pd.read_csv(csv_path) # 假设 label 在最后一列 self.features df.iloc[:, :-1].values.astype(float32) self.labels df.iloc[:, -1].values.astype(int64) def __len__(self): return len(self.features) def __getitem__(self, idx): x torch.tensor(self.features[idx], dtypetorch.float32) y torch.tensor(self.labels[idx], dtypetorch.long) return x, y这里有个维度上的坑要提前说明CNN 1D 卷积层期望的输入形状是(batch, channels, length)而上面__getitem__返回的特征形状是(feature_dim,)也就是没有通道维度。所以在main.py里加载数据后需要手动加一个维度用torch.unsqueeze(1)把形状变成(batch, 1, feature_dim)。这一步经常被初学者漏掉直接导致模型前向传播时报维度不匹配的错误。dtypetorch.float32的显式声明也要注意如果 CSV 里混入了缺失值变成NaN后续训练 loss 直接变成nan排查起来比较费劲。3.2 model.py 中的网络结构设计模型结构的设计思路是先用 CNN 做局部特征提取再用 LSTM 做时序依赖建模。对于网络流量数据一个连接记录里的 41 个特征之间并不是完全独立的例如src_bytes和dst_bytes的比值关系、count与srv_count的交互这些都是局部特征。1D 卷积通过在特征维度上滑动窗口可以自动学习相邻特征之间的组合模式这是 MLP 做不到的。import torch.nn as nn class CNNLSTM(nn.Module): def __init__(self, input_dim, num_classes, hidden_size64, num_layers2): super(CNNLSTM, self).__init__() # 1D 卷积层提取局部特征 self.cnn nn.Sequential( nn.Conv1d(in_channels1, out_channels32, kernel_size3, padding1), nn.ReLU(), nn.MaxPool1d(kernel_size2, stride2), nn.Conv1d(in_channels32, out_channels64, kernel_size3, padding1), nn.ReLU(), nn.MaxPool1d(kernel_size2, stride2), ) # LSTM 捕捉序列依赖 self.lstm nn.LSTM( input_size64, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, bidirectionalTrue ) self.fc nn.Linear(hidden_size * 2, num_classes) self.dropout nn.Dropout(0.3) def forward(self, x): # x: (batch, feature_dim) - (batch, 1, feature_dim) x x.unsqueeze(1) x self.cnn(x) # (batch, 64, seq_len) x x.permute(0, 2, 1) # (batch, seq_len, 64) lstm_out, _ self.lstm(x) out lstm_out[:, -1, :] # 取最后一个时间步 out self.dropout(out) return self.fc(out)维度演算是理解这个模型的关键。假设输入特征维度为 120第一次卷积加池化后序列长度从 120 降到 60第二次卷积加池化后降到 30。所以进入 LSTM 时序列长度为 30每个时间步的特征维度是 64即最后一层卷积的输出通道数。permute的作用是把通道维换到序列维之后因为 LSTM 希望输入格式是(batch, seq_len, input_size)。选用bidirectionalTrue意味着模型同时看当前特征往前和往后的信息最后的hidden_size * 2就是因为双向 LSTM 的隐藏层输出各占一半。3.3 kernel_size、padding 与特征长度的边界约束写这个模型时最容易翻车的是kernel_size和padding的配合。padding1保证了卷积前后序列长度不变但MaxPool1d(kernel_size2)会把长度减半。如果输入特征维度是奇数池化后会出现长度取整的问题。比如特征维度 121两次池化后变成60 - 30没问题但如果特征维度是 122第二次池化后是61 - 30多出来的那一个位置被丢弃了。解决方式是让预处理脚本在独热编码后检查维度必要时补零对齐。另一个容易忽略的是 LSTM 的batch_firstTrue参数如果不设置输入形状得是(seq_len, batch, input_size)和 CNN 的输出格式对不上代码里忘了设就会报RuntimeError。4. 训练与测试train_and_test.py 中的配置参数与调优策略4.1 损失函数的选择类别不均衡场景下的 CrossEntropy 变体训练函数的核心配置是损失函数和优化器。KDD Cup 99 的类别极不均衡如果直接使用nn.CrossEntropyLoss()模型会倾向把样本预测为样本量大的类别导致Normal类的召回率极低。常见做法是给损失函数传入weight参数让样本量少的类别获得更高的惩罚权重。权重按类别样本数的倒数计算再归一化到均值为 1。import torch.nn as nn def compute_class_weight(labels, num_classes): class_counts torch.bincount(labels, minlengthnum_classes).float() total class_counts.sum() # 倒数并归一化防止权重差异过大导致训练不稳 weight total / (class_counts * num_classes) return weight class_weight compute_class_weight(train_labels, num_classes5) criterion nn.CrossEntropyLoss(weightclass_weight.to(device)) optimizer torch.optim.Adam(model.parameters(), lr0.001, weight_decay1e-5)weight_decay1e-5是 L2 正则化对防止过拟合有实际帮助尤其在训练样本只有几十万条这个量级时。优化器选择 Adam 而不是 SGD因为 Adam 对学习率不敏感课设场景下不需要精细调整学习率策略。如果追求更高精度可以换用 SGD CosineAnnealingLR 学习率调度但 Adam 在 10 个 epoch 内到 95% 已经足够。4.2 训练循环的关键细节梯度清零、loss 累积与设备一致性train_and_test.py里的训练循环逻辑值得逐段说明。每轮 epoch 的流程是遍历 DataLoader 取 batch前向传播得到输出计算损失反向传播求梯度然后优化器更新参数。这里有两个细节直接影响训练效果一是optimizer.zero_grad()必须在loss.backward()之前调用否则梯度会跨 batch 累积等同于增大了 batch size容易导致 loss 爆炸二是模型和输入数据都要.to(device)如果模型在 GPU 上而数据在 CPU 上PyTorch 会抛类型不匹配错误。def train_one_epoch(model, dataloader, criterion, optimizer, device): model.train() total_loss 0.0 correct 0 total 0 for batch_x, batch_y in dataloader: batch_x batch_x.to(device) batch_y batch_y.to(device) optimizer.zero_grad() outputs model(batch_x) loss criterion(outputs, batch_y) loss.backward() optimizer.step() total_loss loss.item() * batch_x.size(0) _, predicted torch.max(outputs, 1) correct (predicted batch_y).sum().item() total batch_y.size(0) return total_loss / total, correct / total测试函数需要额外注意torch.no_grad()的包裹这个上下文管理器会关闭梯度计算减少显存占用和计算开销。因为在测试阶段不需要反向传播如果不用no_grad()测试过程会把计算图保存下来batch 多的时候内存会持续增长。另一个测试阶段的坑是model.eval()必须被调用它会把 Dropout 层切换为验证模式否则测试时的输出会因为 Dropout 的随机性而不稳定表现为同样的输入在两次测试中得到不同的结果且精度偏低。4.3 10 个 epoch 达到 95% 以上的训练策略拆解项目摘要里说 10 个 epoch 达到 95% 的准确率这个结果依赖于几个参数的配合。第一batch size 通常设为 128 或 256太小则梯度噪声大太大则一个 epoch 内参数更新次数太少第二学习率 0.001 配合 Adam 属于标准配置如果 loss 下降缓慢可以调整到 0.003但超过 0.01 容易出现前期震荡第三特征归一化必须是预处理阶段完成不能在模型内部做否则测试集的数据分布和训练集不一致。训练过程中监控训练集和验证集的 loss 曲线是一个有效的诊断手段。如果训练 loss 持续下降但验证 loss 在某个 epoch 后开始回升说明模型开始过拟合应对方法是减小 LSTM 的hidden_size或者增加dropout比例。如果训练集上 loss 就下不去优先检查数据预处理——比如有没有NaN、有没有忘记归一化、label 是不是从 0 开始编号。# 训练过程中的典型输出示例 Epoch [1/10], Train Loss: 0.8473, Train Acc: 0.7621 Epoch [2/10], Train Loss: 0.4120, Train Acc: 0.8715 Epoch [3/10], Train Loss: 0.2874, Train Acc: 0.9112 Epoch [4/10], Train Loss: 0.2105, Train Acc: 0.9360 Epoch [5/10], Train Loss: 0.1683, Train Acc: 0.9512这是典型的收敛曲线前两个 epoch 准确率快速上升后面逐步平缓。如果第二个 epoch 后 loss 还在 0.6 以上徘徊优先怀疑学习率是不是被weight_decay拖累或者 CNN 的卷积核数量太少提取不到有效特征。4.4 误报与漏报准确率之外的评估指标很多课设项目只报告准确率但这在流量检测场景下不够充分。KDD Cup 99 的测试集中有大量非Normal样本模型只需要把所有样本都预测为DoS就能拿到不错的准确率但对R2L和U2R这两类少数攻击的召回率会非常低。所以建议在测试代码里补上混淆矩阵和每类别的 precision、recall、F1-score。from sklearn.metrics import classification_report, confusion_matrix import numpy as np def evaluate_model(model, dataloader, device): model.eval() all_preds [] all_labels [] with torch.no_grad(): for batch_x, batch_y in dataloader: batch_x batch_x.to(device) outputs model(batch_x) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(batch_y.numpy()) cm confusion_matrix(all_labels, all_preds) print(classification_report(all_labels, all_preds, digits4)) return cm运行这段代码后通常会发现DoS的 F1-score 在 0.98 以上但U2R可能只有 0.2 左右因为这类样本在数据集中太少。如果课程设计报告里讨论到这个现象并解释类别不均衡和少数类样本数量不足的影响会比单纯报一个准确率分数更有深度。5. 流量检测系统的进阶用法从课设到真实场景的迁移技巧课设项目交付后如果想把它改造成一个可用的检测工具有几个实用方向。第一个是把离线评估改成流式检测即每次只输入一条或一个窗口的连接记录实时判断是否异常。实现方式是去掉train_test_split和 batch 的概念模型加载权重后用.eval()模式做单条推理输出各类别的概率分布而不是直接argmax这样可以看到模型对某条流量的「置信度」——置信度低于阈值时标记为可疑交给人工复核。第二个有用的技巧是把训练好的模型通过torch.jit.trace序列化为 TorchScript 格式这样在生产环境用 C 调用或者部署到移动端时不需要完整的 Python 环境。import torch model CNNLSTM(input_dim120, num_classes5) model.load_state_dict(torch.load(./checkpoints/best_model.pth)) model.eval() dummy_input torch.randn(1, 1, 120) traced_model torch.jit.trace(model, dummy_input) traced_model.save(./traffic_model.pt)这里dummy_input的第二个维度是 1对应 CNN 的输入通道数。保存后的模型在加载时直接用torch.jit.load即可推理速度比原模型有提升。第三个方向是针对本课程设计做一个简单的实时流量抓取和分析管线。常见做法是用scapy库从网卡抓包提取连接的五元组和包长、计数等信息填充成和 KDD Cup 99 一致的特征向量格式。但需要注意KDD Cup 99 的特征定义是基于连接会话的比如dst_host_count指的是过去 100 个连接中与当前连接目标主机相同的数量这需要一个滑动窗口来维护历史状态不是单包能算出来的。所以实际改造时要先写一个会话状态维护模块难度比模型本身大不少。对于学有余力的场景值得把精力放在这里因为在真实网络环境中特征的可计算性和统计口径才是决定检测系统能否落地的关键因素。本文还有配套的精品资源点击获取
返回列表