简介:这份资源是面向计算机相关专业学生与项目实战学习者的网络流量检测系统Python源码,采用CNN与LSTM融合模型完成流量特征提取与分类识别,可作为课程设计、期末大作业或毕业设计的参考方案,帮助解决深度学习方法落地网络安全场景时缺少完整代码框架的问题。压缩包共6个文件,以5个py脚本和1个txt说明文档为主,涵盖数据加载、预处理、模型定义、训练测试与主程序入口等模块,整体约6KB,结构精简便于快速阅读与二次修改。目前已有207人学习下载,说明该方案在同类课设选题中具有一定参考价值。读者可从中获得完整的CNN+LSTM建模思路、数据预处理流程与训练测试脚本,理解流量识别任务的工程组织方式,并在此基础上替换数据集或调整网络结构,完成自己的实验与报告撰写。
1. 从一份课设源码说起:CNN+LSTM 做流量检测到底在解决什么
拿到「基于CNN+LSTM的网络流量检测系统python源码」这个题目的人,十有八九是两种情况:一是课设/毕设卡在选题,想找一个既有深度学习含量、又能跑出漂亮指标的方案;二是已经选了流量检测方向,但用传统机器学习(SVM、随机森林)做出来的准确率上不去,想换成 CNN+LSTM 试试。这两种诉求本质是同一个问题——怎么把网络流量这种一维时序数据,喂给一个能同时抓「局部特征」和「长程依赖」的模型,并且真的跑通、跑出结果。
网络流量检测的核心任务,是把抓到的数据包/流,分成正常和异常(DDoS、端口扫描、暴力破解、CC 攻击等)。传统做法靠人工提特征(包长、间隔、标志位统计),特征工程做得好能到 95% 以上,但换个网络环境就崩。CNN 擅长从原始字节序列里自动提取局部模式(比如某个攻击的固定载荷片段),LSTM 擅长记住一段流里的时序依赖(比如握手失败后紧跟大量重传)。两者串起来,就是「先局部卷积降维,再时序建模分类」的经典组合。这套方案适合有 Python 基础、学过一点深度学习、需要一份能写进论文/答辩的完整项目的同学,也适合想快速验证「深度模型在流量分类上到底比传统方法强多少」的工程师。
2. 数据从哪来、怎么变成模型能吃的张量
2.1 公开数据集选型:CIC-IDS2017 与 NSL-KDD 的取舍
做流量检测,第一步永远是数据。课设里最常用的两个公开数据集是 NSL-KDD 和 CIC-IDS2017,选哪个直接决定你后面预处理的工作量。
NSL-KDD 是 KDD99 的清洗版,每条记录是 41 维手工特征(连接时长、协议类型、错误率等),已经整理成 CSV,读进来就能用。优点是干净、小(训练集约 12 万条)、跑得快,一晚上能出结果。缺点是特征已经被人提好了,你没法体现「CNN 自动提特征」的价值,答辩时容易被问「你这跟直接用特征有什么区别」。
CIC-IDS2017 是原始流量抓包后提取的流特征,80 多列,包含 Flow Duration、Forward Packet Length 等统计量,更接近真实场景,数据量也大(数百万条流)。它的问题是类别极不平衡,DDoS 和 PortScan 占了绝大多数,少数类(如 Web Attack)只有几百条,直接训练模型会偏向多数类。
我的建议:课设求稳选 NSL-KDD,求新求深选 CIC-IDS2017。如果选 CIC-IDS2017,务必先做类别平衡处理,否则准确率虚高但召回率惨不忍睹。
2.2 把 CSV 变成 (样本数, 时间步, 特征数) 的三维张量
CNN+LSTM 的输入必须是三维张量:(batch, timesteps, features)。但原始 CSV 是二维的(样本, 特征),这里有个关键转换——把连续 N 条流拼成一个时间窗口,让 LSTM 有时序可学。
import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler, LabelEncoder # 1. 读取并清洗 df = pd.read_csv('cicids2017.csv') df.replace([np.inf, -np.inf], np.nan, inplace=True) df.dropna(inplace=True) df.drop_duplicates(inplace=True) # 2. 标签编码:把攻击类型字符串转成 0~N 的整数 le = LabelEncoder() df['Label'] = le.fit_transform(df['Label']) # 3. 特征归一化:CNN/LSTM 对量纲敏感,必须缩放到 [0,1] feature_cols = [c for c in df.columns if c != 'Label'] scaler = MinMaxScaler() df[feature_cols] = scaler.fit_transform(df[feature_cols]) # 4. 构造时间窗口:每 10 条流拼成一个样本 WINDOW = 10 X, y = [], [] values = df[feature_cols].values labels = df['Label'].values for i in range(len(values) - WINDOW): X.append(values[i:i+WINDOW]) y.append(labels[i+WINDOW]) # 用窗口预测下一条流的类别 X = np.array(X) # shape: (样本数, 10, 特征数) y = np.array(y) print(X.shape, y.shape)这段代码的逻辑链条是:清洗(去无穷值和缺失)→ 编码标签 → 归一化特征 → 滑窗构造时序。参数说明:WINDOW=10是时间步长度,太小(如 3)LSTM 学不到依赖,太大(如 50)样本数骤减且训练慢,10~20 是课设常用区间;MinMaxScaler比StandardScaler更适合流量特征,因为很多列是计数型,分布偏斜严重。跑完这一步,你会得到类似(120000, 10, 78)的张量,这才是能喂进模型的形状。
注意:滑窗会让样本数减少 WINDOW 条,且窗口内如果跨越了不同攻击类型,标签会失真。严谨做法是按流的时间戳排序后再滑窗,课设里如果数据没带时间戳,至少先按标签分组再各自滑窗。
3. CNN+LSTM 模型结构:每一层为什么这么搭
3.1 卷积层提局部特征,池化层降维
CNN 部分的作用是从每个时间步的特征向量里提取局部模式。虽然流量特征是一维的,但我们可以把「特征维度」当作通道,用一维卷积在时间步上滑动。
from tensorflow.keras import layers, models model = models.Sequential([ # 一维卷积:在时间维度上滑动,提取相邻流的局部模式 layers.Conv1D(filters=64, kernel_size=3, padding='same', activation='relu', input_shape=(10, 78)), layers.MaxPooling1D(pool_size=2), layers.Conv1D(filters=128, kernel_size=3, padding='same', activation='relu'), layers.MaxPooling1D(pool_size=2), # LSTM:在卷积降维后的序列上建模长程依赖 layers.LSTM(128, return_sequences=False), layers.Dropout(0.5), layers.Dense(64, activation='relu'), layers.Dense(num_classes, activation='softmax') ]) model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) model.summary()参数说明:filters=64/128是卷积核数量,越多容量越大但越容易过拟合,课设数据量下 64+128 够用;kernel_size=3表示每次看 3 个相邻时间步,这是流量局部模式的经验值;padding='same'保证卷积后时间步不变,方便接池化;MaxPooling1D(pool_size=2)把时间步从 10 降到 5 再降到 2,减少 LSTM 的输入长度,加速训练。LSTM 层return_sequences=False表示只取最后一个时间步的输出做分类,如果要做序列标注(每个时间步都输出类别)就设 True。
3.2 LSTM 层怎么接、Dropout 放哪、类别不平衡怎么补
LSTM 接在 CNN 后面,输入形状是(batch, 降维后的时间步, 通道数)。这里有个常见翻车点:Conv1D 的输出通道数会变成 LSTM 的输入特征数,如果你卷积后没处理好维度,LSTM 会报 shape 错误。上面代码里第二次池化后时间步是 2、通道是 128,LSTM 自动把它当作(2, 128)的序列处理。
Dropout 放在 LSTM 之后、全连接之前,比例 0.5 是防过拟合的常规操作。如果验证集 loss 震荡大,可以在每个 Conv1D 后也加 0.2~0.3 的 Dropout。
类别不平衡是流量检测的老大难。除了在数据层做重采样,模型层可以用class_weight:
from sklearn.utils.class_weight import compute_class_weight import numpy as np classes = np.unique(y_train) weights = compute_class_weight('balanced', classes=classes, y=y_train) class_weight = dict(zip(classes, weights)) model.fit(X_train, y_train_cat, epochs=30, batch_size=128, validation_split=0.2, class_weight=class_weight)compute_class_weight('balanced')会自动给少数类更高的权重,让模型不敢忽略它们。batch_size=128是显存和收敛速度的平衡点,课设机器(8G 显存)跑 128 没问题,太小(如 16)训练慢且梯度噪声大。
4. 训练、评估与踩坑排查
4.1 训练曲线怎么看、早停和模型保存怎么写
训练时别只盯着 accuracy,流量检测里 accuracy 是最容易骗人的指标——如果 90% 是正常流量,模型全猜正常也有 90% 准确率。必须同时看 loss、val_loss 和混淆矩阵。
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks = [ EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True), ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True) ] history = model.fit(X_train, y_train_cat, epochs=50, batch_size=128, validation_split=0.2, class_weight=class_weight, callbacks=callbacks)EarlyStopping的patience=5表示验证 loss 连续 5 轮不降就停,restore_best_weights=True是后悔药——回到最好的那一轮权重,而不是停在最差的一轮。ModelCheckpoint只保存 val_loss 最低的模型,避免最后几轮过拟合的权重被存下来。
4.2 评估指标:别只看准确率,看宏平均 F1
from sklearn.metrics import classification_report, confusion_matrix y_pred = model.predict(X_test) y_pred_labels = np.argmax(y_pred, axis=1) print(classification_report(y_test, y_pred_labels, digits=4)) print(confusion_matrix(y_test, y_pred_labels))classification_report里的macro avg F1才是真正反映多分类均衡性能的指标。如果 macro F1 远低于 accuracy,说明模型在少数类上崩了,回去检查 class_weight 和过采样。混淆矩阵能告诉你哪两类在互相误判,比如 PortScan 被误判成 DDoS,那就要看这两类的特征是不是太像。
4.3 避坑:流量检测课设里最容易翻车的 5 个地方
现象一:训练准确率 99%,测试只有 60%。原因:数据泄漏——归一化时用了全量数据的 min/max,测试集信息漏进了训练。解决:scaler.fit()只在训练集上做,测试集用scaler.transform()。
现象二:loss 一直是 nan。原因:输入里有 inf 或 nan,或者标签没做 one-hot。解决:df.replace([np.inf,-np.inf], np.nan).dropna(),标签用to_categorical。
现象三:LSTM 训练极慢,一个 epoch 要半小时。原因:时间步太长或 batch_size 太小。解决:把 WINDOW 从 50 降到 10,batch_size 提到 128,或者把 LSTM 换成 GRU(参数少 25%,速度更快)。
现象四:验证集 loss 先降后升,准确率还在涨。原因:过拟合。解决:加 Dropout、加 L2 正则、减少 LSTM 单元数,或者直接 EarlyStopping。
现象五:少数类召回率为 0。原因:类别极度不平衡且没做任何处理。解决:class_weight='balanced'+ 对少数类过采样(SMOTE),两者叠加通常能把少数类召回拉到 0.7 以上。
提示:课设答辩最常被问的三个问题是「为什么用 CNN+LSTM 而不是单独用 LSTM」「时间窗口怎么选的」「类别不平衡怎么处理的」,提前把这三点的实验对比做出来(比如单独 LSTM 的 F1 vs CNN+LSTM 的 F1),比多跑几个 epoch 有用得多。
5. 让这份源码真正拿得出手:从跑通到讲清楚
把模型跑出 95% 准确率只是及格线,课设要拿高分,得让评委看到你理解每一步在干什么。我的习惯是做一个消融对比表,把「只用 CNN」「只用 LSTM」「CNN+LSTM」三组结果并排,再附上传统 SVM 的基线。这样一张表就能回答「你为什么要这么搭」——CNN+LSTM 的 macro F1 通常比单独 LSTM 高 2~5 个百分点,比 SVM 高 5~10 个百分点,差距在少数类上尤其明显。
| 模型 | Accuracy | Macro F1 | 少数类召回 |
|---|---|---|---|
| SVM | 0.923 | 0.71 | 0.42 |
| 单独 LSTM | 0.961 | 0.83 | 0.68 |
| 单独 CNN | 0.954 | 0.79 | 0.61 |
| CNN+LSTM | 0.978 | 0.89 | 0.81 |
另一件事是把推理封装成一个能演示的脚本。评委不一定想看你的训练代码,但一定想看「给一条流量,系统能不能判出它是攻击」。写一个predict.py,加载best_model.h5和训练时的 scaler,读一条 CSV 记录,输出类别和置信度。这个脚本 20 行就够,但演示效果拉满。
import joblib, numpy as np from tensorflow.keras.models import load_model model = load_model('best_model.h5') scaler = joblib.load('scaler.pkl') # 训练时保存的 scaler def predict_one(flow_features): x = scaler.transform([flow_features]) x = x.reshape(1, 10, -1) # 补成窗口形状,实际用最近10条 prob = model.predict(x, verbose=0)[0] idx = np.argmax(prob) return idx, prob[idx] # 演示:读一条测试样本 import pandas as pd sample = pd.read_csv('test_sample.csv').iloc[0, :-1].values label, conf = predict_one(sample) print(f'预测类别: {label}, 置信度: {conf:.4f}')最后说个血泪经验:别在最后一天才跑全量数据。CIC-IDS2017 全量跑一次 CNN+LSTM 在普通笔记本上要 2~4 小时,如果中途发现维度错了、标签漏了,重跑的成本极高。我一般先用 10% 的子集把整条链路(读数据→滑窗→训练 1 个 epoch→评估→保存→推理)跑通,确认没有 shape 错误和路径问题,再上全量。这个习惯帮我省过至少三次通宵。希望帮到你。
本文还有配套的精品资源,点击获取