☰
CNN+LSTM网络流量检测Python源码实战与调优指南
2026/9/28 2:10:02 网站建设 项目流程

简介:一份基于CNN与LSTM融合模型的网络流量检测系统Python源码,面向计算机、人工智能、通信工程、电子信息等相关专业的学生与开发者,可用于毕业设计、课程设计、作业提交或项目初期立项演示。项目代码经过实际运行验证,功能稳定,可直接运行体验,也能基于现有结构二次开发,扩展更多流量识别场景。压缩包共6个文件,包含5个Python脚本和1个Markdown说明文档,脚本按职责划分为数据预处理、数据加载、模型定义、训练测试与主程序入口等模块,便于理解深度学习完整流程,说明文档则提供项目使用指引。整体资源包仅5KB,轻量精炼。目前已有1304人浏览学习,尤其适合希望快速掌握CNN与LSTM在流量分类检测中如何落地的初学者,借助该源码即可完成从数据准备到模型评估的完整实践流程。

1. 基于 CNN+LSTM 的网络流量检测系统:这套 Python 源码到底能跑出什么

要做一套网络流量检测系统 Python 源码课设项目,最务实的路线就是把流量记录切成交互会话,抽成数值特征矩阵,再用 CNN+LSTM 的组合去区分正常流量与攻击流量。CNN 负责抓取短窗口内的局部模式,LSTM 负责承接会话之间的时序依赖,两个模型一前一后刚好覆盖了“空间特征”和“时间特征”两条线。这个源码包包含预处理、数据加载、模型搭建、训练测试、统一入口共五个 Python 文件,外加一份项目说明文档,模块划分干净,适合计科、人工智能、网络安全方向的在校学生拿来当课设或毕设底稿。也适合刚接触深度学习的开发者,拿它理解一维卷积和循环神经网络在非图像任务上的配合方式。先说结论:这源码值得下下来完整跑一遍,因为它每个文件都能在答辩时讲出实际用途,而不是一个黑匣子。

2. 源码结构盘点:五个 Python 文件如何串联成一条检测流水线

先花几分钟把压缩包里的文件类型理清楚,这一步能替你省掉后面至少一个小时的排查时间。压缩包里的文件不多,但它不是那种几十个文件堆在一起的“全家桶”式源码,而是按职责拆分好的模块化结构。拿到手先不要急着运行main.py,先把文件之间的调用关系看清楚,你就知道该从哪个文件开始读、哪个文件是入口、哪个文件只做数据准备。

2.1 文件职责与运行顺序

这是一个典型的“数据准备 → 数据加载 → 模型定义 → 训练评估 → 统一入口”的五段式结构。常见的课设代码会把所有逻辑塞进一两个文件里,这个项目拆得比较规矩,对答辩和二次修改都更友好。

文件名职责关键点
project说明.md项目说明文档拿到后先读,里面一般会写明环境要求和运行顺序
data_preprocess.py原始数据清洗与特征预处理去空值、去重、标签编码、归一化
data_load.py批量加载数据并做窗口切分负责生成 LSTM 需要的三维输入张量
model.py定义 CNN+LSTM 网络结构卷积、池化、LSTM、全连接层的组装
train_and_test.py训练、验证、测试的完整流程早停、学习率衰减、模型保存
main.py统一运行入口调度上面四个模块按顺序执行

我一般拿到这种项目会先打开main.py看它的 import 列表,基本就能推断出数据流向。接着按“预处理 → 加载 → 组网 → 训练”的顺序读四个文件,最后再回头读项目说明文档核对数据集来源和参数设置。不要反过来先抠模型结构,因为模型输入要是没对上,后面全是维度报错。

2.2 从原始流量记录到分类标签的数据流

这条数据流是这个项目最核心的一条主线,答辩时如果能把它讲顺,基本就能拿一个不错的分数。原始流量记录进来之后,先是按会话切分,每条会话抽出一组统计特征,比如包长均值、包长方差、协议类型、端口号、标志位分布、时间间隔等。这些特征经过清洗和归一化之后,变成一条固定长度的数值向量,多个这样向量再按时间顺序叠成窗口矩阵,送给 CNN 做局部特征提取。

后续的处理顺序决定了模型为什么能work:Conv1D 先在小窗口内用卷积核扫出几组局部模式,比如“连续三个短包后跟一个大包”这种特征,MaxPooling 把时序长度压短,再把压缩后的特征序列交给 LSTM 去建模更长时间上的依赖关系。先 CNN 后 LSTM 而不是反过来,是因为卷积核能先把无关细节过滤掉,把输入序列的长度压缩到原来的二分之一甚至四分之一,LSTM 需要处理的时间步就少了,训练速度更快,梯度也更容易稳定。最后接一个全连接层和 softmax,输出每个类别的概率。

2.3 运行环境与硬性门槛

源码没有标注强制框架版本,但课程设计最常见的主选方案是 Python 3.8 到 3.10 加 TensorFlow 2.x,模型代码风格按 Keras 的Sequential组装方式来写。如果你的环境里装的是 PyTorch,对应关系也很直接:Conv1D换成nn.Conv1d,LSTM换成nn.LSTM,MaxPooling1D换成nn.MaxPool1d,其余结构不变。

硬件门槛方面,这个模型的参数量不算大。CNN 部分是一维卷积,几个卷积核加起来也就几万到十几万参数;LSTM 的参数量取决于units和输入维度,64 个隐藏单元大约会产生两三万个参数,显存压力比 CV 类项目小得多。纯 CPU 跑小数据集也能完成训练,只是 LSTM 反向传播是串行的,时间步一长就会慢得明显。我的建议是:有 NVIDIA 显卡就装 GPU 版 TensorFlow,没有就用batch_size=32强制调小,别追求一次把数据全部塞进去。

3. 数据预处理与加载:把流量记录变成模型能吃的三维张量

很多第一次跑 CNN+LSTM 流量检测的人,最容易翻车的地方不在模型,而在数据喂进去的那一刻。流量日志和图像不一样,图像天然是二维矩阵,而流量记录是一行行的 CSV,你要自己决定把哪几列当作特征、哪几列当作标签、怎么切窗口、怎么归一化。这一步做不对,模型结构再合理也训练不出来。

3.1 data_preprocess.py 里那几个关键操作

预处理模块一般按照“去脏数据 → 特征编码 → 归一化 → 保存中间结果”的顺序来。先处理空值和重复行,因为在真实的网络流量记录里,漏采、重复采集、超时记录非常普遍,不清理直接训练,loss 可能直接变成 NaN。协议类型这一类文本特征没法直接参与计算,常见做法是用LabelEncoder把字符串映射成整数,或者按协议族做 one-hot 编码,看你的特征规模决定。

归一化这一步我建议用MinMaxScaler,而不是StandardScaler。网络流量统计特征有个特点:不同特征的量纲差异极大,比如包长在几十到几千字节之间波动,而 TCP 标志位组合是一个 0/1 计数,如果用标准归一化,离群的大值会主导均值方差,反而把正常包长的分布压缩得很厉害。MinMaxScaler把所有特征压到 0 到 1 之间,数值范围统一,卷积核初始化之后更容易收敛。下面是这一模块的典型实现:

# data_preprocess.py 中的核心预处理逻辑 import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler, LabelEncoder df = pd.read_csv('traffic.csv', encoding='utf-8') df = df.replace([np.inf, -np.inf], np.nan).dropna().drop_duplicates() # 标签编码:Normal -> 0, Attack -> 1 label_encoder = LabelEncoder() df['Label'] = label_encoder.fit_transform(df['Label']) # 选出所有数值特征列,排除标签列和编号列 feature_cols = [c for c in df.columns if c not in ['Label', 'Flow_ID']] # MinMax归一化,把每个特征压到[0,1] scaler = MinMaxScaler() df[feature_cols] = scaler.fit_transform(df[feature_cols]) # 保存归一化后的结果和编号器,后续加载和预测还要用 df.to_csv('traffic_processed.csv', index=False)

关键逻辑说明:replace([np.inf, -np.inf], np.nan).dropna()这三步连写,是先处理极大值和缺省值再删除脏行,顺序不能反,如果先删行再替换,NaN 行根本查不出来。LabelEncoder必须在全部数据上fit,不能只对训练集部分调用,否则测试集里出现新类别会直接报错。scaler也要保存下来,预测新样本时必须用同一套标准化参数,否测归一化分布不一致,模型预测结果基本是废的。

3.2 data_load.py 的滑动窗口切分

LSTM 的输入要求是三维张量(batch_size, timesteps, n_features),但预处理之后的数据是二维表格(样本数, 特征数),中间差了一个维度,这就是data_load.py要解决的窗口切分问题。窗口切分的原理是:把连续若干条会话记录叠成一个时间切片,用前window_size个时间步的特征去预测当前窗口的流量类别。这样 CNN 和 LSTM 才能看到“特征的局部组合”和“特征随时间的演化”,而不是孤立地看每一条流量。

# data_load.py 中的滑动窗口切分逻辑 import numpy as np from sklearn.model_selection import train_test_split def make_windows(X, y, window_size=10, step=5): Xs, ys = [], [] for i in range(0, len(X) - window_size, step): Xs.append(X[i:i + window_size]) # 截取一个时间窗口 ys.append(y[i + window_size - 1]) # 标签取窗口内最后一条 return np.array(Xs), np.array(ys) # 加载预处理后的数据 processed = np.loadtxt('traffic_processed.csv', delimiter=',', skiprows=1) X, y = processed[:, :-1], processed[:, -1].astype(int) X_windows, y_windows = make_windows(X, y, window_size=10, step=5) # 按7:3划分训练集与验证集,保持类别比例 X_train, X_val, y_train, y_val = train_test_split( X_windows, y_windows, test_size=0.3, stratify=y_windows, random_state=42 )

参数window_size=10表示每个样本包含 10 条连续会话记录,step=5表示窗口之间重叠 5 条,重叠的目的是不丢连续模式,也相当于一种数据增强。窗口长度这个参数值得调,它直接决定 LSTM 看到的时间跨度:窗口太短抓不到长周期攻击模式,太长则训练速度下降且容易过拟合。stratify=y_windows很关键,它保证划分后每一类流量在两个集合里的比例一致,不会因为随机划分导致验证集里某类样本缺失。运行完这段,X_train的形状应该是(样本数, 10, 特征数),喂给模型之前可以在打印一行确认形状,省得后面维度报错。

3.3 类不平衡的隐患

网络流量数据集里最现实的问题就是类不平衡,正常流量数量远大于攻击流量,在多数公开数据集里能占到八成以上。如果你不做任何处理,模型会学到“全预测为正常类”,准确率照样很高,但攻击样本一条都识别不出来,这种模型在答辩现场就是靶子。常见做法有两个:一是在train_test_split之后给少数类更大的类权重,二是对少数类做重采样。类权重的方式比较省事,直接在训练时给损失函数加权,让模型把预测错少数类的代价放大:

# 按样本数量反比计算各类权重 from sklearn.utils.class_weight import compute_class_weight class_weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train) class_weight_dict = {i: w for i, w in enumerate(class_weights)}

这段代码里,compute_class_weight会自动把少数类的权重拉高。如果正常类占 80% 而攻击类占 20%,那么攻击类的权重会接近正常类的 4 倍,模型在训练时会更注意少数类样本。如果你发现加权之后模型仍然偏向多数类,再把window_size重叠率从 5 下调到 2,让少数类样本被更多窗口覆盖,也能缓解一点。

4. 模型设计与训练评估:Conv1D 抓局部模式,LSTM 承接时序依赖

模型结构是这个项目的灵魂,也是答辩时老师最爱追着问的部分。你要能讲清楚三件事:为什么用 CNN、为什么用 LSTM、为什么把两者串起来。只贴代码不讲原理不行,因为你改成任意一个别的结构都能跑,但随机初始化一组普通全连接层和这个组合模型的差距,只有在评估环节才能真正体现出来。

4.1 model.py 里的网络结构怎么组装

Keras 风格下,这个模型的典型写法是Sequential顺序堆叠,一层一层的看非常直观。第一层用Conv1D,卷积核在特征维度上滑动,对流量特征序列做局部提取;接着过BatchNormalization稳定激活分布,再用MaxPooling1D压缩时序长度;压缩后的序列进入LSTM层做时序建模,最后接Dropout和全连接层输出分类概率。

# model.py 中的 CNN+LSTM 结构定义 from tensorflow.keras.models import Sequential from tensorflow.keras.layers import ( Conv1D, BatchNormalization, MaxPooling1D, LSTM, Dense, Dropout, Activation ) def build_cnn_lstm_model(window_size, n_features, n_classes): model = Sequential([ # 一维卷积,128个卷积核,感受野为3 Conv1D(filters=128, kernel_size=3, padding='same', input_shape=(window_size, n_features)), BatchNormalization(), Activation('relu'), MaxPooling1D(pool_size=2), # LSTM承接时序建模,输出最后一步的隐藏状态 LSTM(units=64, return_sequences=False), Dropout(0.5), Dense(units=n_classes), Activation('softmax') ]) return model model = build_cnn_lstm_model(window_size=10, n_features=78, n_classes=2) model.summary()

这里每个配置都有讲究。kernel_size=3是最常用的一维卷积核大小,感受野覆盖当前特征点相邻三个时间步的局部组合,比 5 或 7 更小的参数量就能捕捉到“短包+短包+大包”这类局部流量模式,而且堆叠多层之后感受野会变大,不需要一开始就用大卷积核。filters=128是课设项目的稳妥起点,往上加到 256 精度提升有限但训练时间翻倍,往下减到 64 也能跑但拟合能力弱一些。MaxPooling1D(pool_size=2)这一步很多新手会漏掉,它把时间步压缩一半,LSTM 要处理的步数直接减少,训练速度提升接近一倍,而且能抑制局部过拟合。return_sequences=False表示 LSTM 只输出最后一个时间步的隐藏状态,再接全连接层分类。

如果源码是 PyTorch 版本,这个结构的对应关系是nn.Conv1d、nn.BatchNorm1d、nn.MaxPool1d、nn.LSTM,最后加nn.Linear和nn.LogSoftmax。核心逻辑一致,只有维度排布上 PyTorch 默认格式是(batch, channels, length),Keras 是(batch, length, features),迁移的时候需要permute一下。还有一个容易纠结的点是上不上双向 LSTM,双向确实能提升几个百分点的精度,但训练时间和参数量翻倍,课设项目我建议先把单向跑通,有余力再改双向。

4.2 train_and_test.py 的训练配置与超参数

训练流程配置直接决定模型能不能收敛,以及收敛之后会不会过拟合。单看准确率不管训练曲线,是很多课设翻车的根源。这里用三个回调函数配合,可以做到一边训练一边自动止损:

# train_and_test.py 中的训练配置 from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint callbacks = [ # 验证损失连续10轮不降就停止,同时恢复最佳权重 EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True), # 验证损失平台期时把学习率降半,最低降到1e-5 ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-5), # 每轮保存验证损失最小的模型权重 ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True) ] model.compile( optimizer='adam', loss='sparse_categorical_crossentropy', # 多分类标签直接用整数 metrics=['accuracy'] ) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=50, batch_size=64, callbacks=callbacks, class_weight=class_weight_dict, shuffle=True )

这套配置里的几个超参数是循序渐进的结果。epochs=50不是让你真跑满 50 轮,而是给一个上限,配合patience=10让模型在验证损失不再下降的 10 轮之后自动停止,既不错过最佳点又不过度训练。ReduceLROnPlateau很实用,Adam 虽然自适应学习率,但训练后期仍然会出现验证损失震荡不下的情况,factor=0.5把学习率从 0.001 降到 0.0005,再到 0.00025,能帮模型跨过平台期。batch_size=64对大多数课设机器是安全的,显存不够就降到 32。class_weight=class_weight_dict接了上一章算出的类权重,处理不平衡数据,这一步不能省。

4.3 评估阶段要看哪些指标

训练完之后,直接用model.evaluate看准确率是远远不够的。流量检测场景里正常类样本占大头,就算模型把所有样本都判成正常类,准确率也能到 80% 以上,但这个模型没有任何实际价值。正确做法是把预测结果和真实标签导出,计算混淆矩阵和精确率、召回率、F1:

from sklearn.metrics import classification_report, confusion_matrix y_pred = np.argmax(model.predict(X_val), axis=1) print(classification_report(y_val, y_pred, target_names=['Normal', 'Attack'])) print(confusion_matrix(y_val, y_pred))

看结果的时候重点关注攻击类的召回率和 F1。召回率表示“真实攻击流量里被识别出来的比例”,漏报一次攻击的代价比误报一次正常流量大得多,所以我一般会盯着攻击类召回率是否在 90% 以上。如果精确率和召回率差距很大,说明模型置信度分布有问题,可以调整分类阈值,不一定要非得用默认的 0.5。答辩时能讲清漏报和误报的权衡,比报一个 99% 的准确率更有说服力。

5. 避坑排查:训练不收敛、预测全一类的五个常见翻车点

写 CNN+LSTM 流量检测这种项目,真正花时间的地方不是搭模型,而是调试那些“看起来跑通了但结果明显不对”的状态。我把这个领域常见的问题整理成五条踩坑记录,每条都按现象、原因、解决三个步骤写,照着排查能省不少时间。

5.1 训练与评估阶段的三个经典坑

第一条,loss 变成 NaN。现象是训练刚开始几轮 loss 突然变成nan,accuracy 跟着卡死不动,最终权重全部失效。原因通常是输入数据里有 NaN 或 Inf,常见于流量日志中异常大的时间戳或包长被直接读入,还有一个可能是学习率设得太大,梯度更新一步跨过了有效区域。解决的顺序是先确认数据干净,在预处理里强制df.replace([np.inf, -np.inf], np.nan).dropna();如果数据没问题,再在compile时给优化器加上梯度裁剪,把 Adam 的clipnorm设为 1.0,然后学习率从 0.001 降到 0.0001 试一轮,这组操作能解决大多数 NaN 问题。

第二条,验证集全部预测成同一类。现象是训练集准确率一路走高,但拿到测试集上一看,所有样本都被预测为正常流量,攻击类别一个都没识别出。原因基本可以断定是类不平衡,模型发现只要全猜多数类就可以拿到很高的训练准确率,于是收敛到了这种“偷懒”的解。我一般的处理是加class_weight,把少数类的损失权重拉高;如果效果不明显,再尝试对少数类样本做简单的复制过采样,把攻击类样本复制两到三倍,让训练集里两类比例从 1:9 拉到 1:3 左右。

第三条,训练 loss 下降但验证 loss 剧烈震荡。现象是训练曲线稳定向下,验证曲线像锯齿一样大起大落,最终早停触发,模型停在了一个不稳定的权重上。原因多半是batch_size太小,每个 batch 的梯度方向差异太大,导致参数更新忽左忽右;另一个常见原因是不同类别的样本在验证集里分布不均匀,恰好某一批验证数据里攻击样本很多,loss 就被少数样本拉高。解决方法是先把batch_size从 32 提到 64 或 128,再确认train_test_split里用了stratify=y_windows保持类别比例,最后设置一个固定的随机种子,保证每次实验可复现。

5.2 数据处理与环境的两个坑

第四条,模型输入维度报错。现象是训练时报错信息类似expected shape=(None, 10, 78), found shape=(None, 78),一看就是喂进去的数据少了一个维度。原因是没跑窗口切分,直接把二维预处理结果送进了需要三维输入的网络。解决办法是回到data_load.py的make_windows确认输出形状,打印一行print(X_train.shape),如果发现还是二维,检查窗口切分函数的调用顺序有没有被执行到。实际项目里还有人把window_size错设为 0 或 1,导致每个样本只包含一条会话,LSTM 的时间步维度完全失效,这种也要避免。

第五条,框架版本冲突导致无法导入。现象是import tensorflow时直接抛错,或者报出numpy.core.multiarray failed to import。原因基本是系统环境里 NumPy 版本和 TensorFlow 预编译版本不匹配,尤其是新装 Python 3.12 之后直接pip install tensorflow,大概率撞上这类问题。我一般会新建一个独立虚拟环境,把 Python 固定到 3.9,装 TensorFlow 2.10 配 NumPy 1.23.5,这个组合在课设场景里验证过多次,兼容性最稳。Python 3.8 用户则可以把 NumPy 降到 1.24 以内。如果源码跑在 PyTorch 上,常见坑是torch和torchvision版本不匹配,用官方推荐的手动指定版本方式安装,别用一条命令无脑装最新版。

6. 进阶用法:把训练好的最佳模型接到新的分类入口

训练完成并保存了best_model.h5之后,这个项目的实用价值才真正体现出来,因为你可以脱离训练脚本,独立加载模型对一个新样本做预测。这也是答辩时一个很好的加分点:说明这不是只会在训练集上跑分的玩具,而是一个可以接新数据的可用入口。加载模型的方式很简单,load_model之后把新样本的特征向量按训练时的顺序组装好,做同样的归一化和窗口切分,然后predict输出每个类别的概率。

# 新样本预测入口 from tensorflow.keras.models import load_model model = load_model('best_model.h5') # 假设一条新流量的特征已经按训练时的列顺序排列好 new_feature = np.load('new_flow_feature.npy') new_feature = scaler.transform(new_feature.reshape(1, -1)) # 若原模型窗口大小为10,则复制/滑窗构造10步输入 new_window = np.tile(new_feature, (10, 1)).reshape(1, 10, -1) prob = model.predict(new_window)[0] label = int(np.argmax(prob)) print('预测标签:', label, '概率分布:', prob)

这段代码里最容易踩的坑是scaler和窗口构造方式。scaler必须是预处理阶段训练好的同一个对象,不能对新样本重新fit_transform,否则归一化的分布基准完全不一致,预测结果就没有参考意义。窗口构造这里用了最简单的时间维复制,真实项目中你应该是从连续流量会话里切出最近 10 条记录再对齐特征列顺序。

如果你想继续微调模型而不是只用现成权重,可以试试冻结 CNN 部分只训练 LSTM 和全连接层,在新数据集上做轻量迁移。常见做法是把前几层设为trainable=False,用较小的学习率 0.0001 再训练几个 epoch,能在保留旧特征提取能力的同时适配新数据分布,比从头训练快得多也稳得多。从那以后我每跑一个实训项目,都强制先拿 200 条样本把整个链路走通,再上全量数据,这套流程已经帮我绕过了无数次“看似能跑实则白跑”的低级问题。你拿到这套源码后也按同样的顺序走一遍:先预处理,再窗口切分,做一次最小训练确认链路通了,再换全量数据调参,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询