
简介基于CNNLSTM架构的网络流量检测系统Python完整源码专为高校课程设计或深度学习入门实践而设计。该系统使用PyTorch框架采用KDD Cup 1999十折数据集进行训练10个周期内即可达到95%以上准确率覆盖从数据预处理到模型训练测试的完整闭环流程。资源包共6个文件含5个Python脚本和1个使用说明文档脚本分工明确数据预处理实现列标签添加、特征归类、可视化、线性相关特征剔除及训练测试集划分数据加载通过继承Dataset类重写接口完成模型训练与测试模块封装了核心训练流程整体压缩包仅7KB。目前已有305人学习浏览适合需要完成课程设计或快速上手CNNLSTM流量识别任务的初学者参考使用。1. 从课设到实用网络流量检测为什么选 CNNLSTM很多拿到“网络流量检测系统”课程设计题目的同学第一反应是套用随机森林或者自编码器但提交报告时往往被问一句“你的模型捕捉到时序变化了吗”。流量不是静态快照一个会话里前一个报文与后一个报文的包长、方向、到达间隔都在变化传统特征工程很难把这些动态关系完整编码。于是 CNNLSTM 这类混合结构成了课设里的常客CNN 负责从固定窗口的特征矩阵里抓取局部关联LSTM 负责消化报文之间的先后依赖这样既保留了识别能力又不会像纯序列模型那样忽略局部爆发特征。这里不打算逐行复述源码包里的注释而是把数据预处理、模型定义、训练调参和最后的验证逻辑串起来让你拿到这类项目后能快速消化成自己的内容也能在答辩时讲清楚每一个参数为什么这么设。2. 先立理论CNN 提空间特征、LSTM 抓时序依赖的判定依据流量检测的本质是一个二分类或多分类问题给定一段网络会话判断它是正常流量还是攻击流量如 DDoS、扫描、暴力破解。但输入不是一张现成的图片而是一串带时间戳的统计量。要说服答辩评委你得先解释清楚为什么把这两个网络拼起来。2.1 网络流量数据到“图像”的转换分桶与窗口化原始流量抓包得到的是 pcap 文件课设里一般不会直接用原始报文太占内存。常见做法是把 pcap 解析成流记录提取每个流的五元组源 IP、目的 IP、源端口、目的端口、协议、包长、到达时间间隔、标志位等再从这些字段里生成统计特征。单单把数据交给 CNN 还不够CNN 需要的是排列整齐的张量。所以第一步是“窗口化”把连续的网络会话按固定时间窗口比如 2 秒切块。每个窗口内再按包序号排列选取前 N 个包的特征不足的补 0。每个包的特征计 M 维于是每个窗口变成形状为 (N, M) 的特征矩阵。这个矩阵很像灰度图像只不过“宽”是报文序号“高”是特征维度像素值是归一化后的包长度、协议号等。CNN 在这种矩阵上做卷积相当于沿特征维度提取“哪个特征和哪个特征同时出现”的局部模式。我把这种转换写成简单函数时通常会保留一个关键参数window_size它决定一个样本覆盖多少个报文直接影响模型的感受野。窗口太小看不到攻击的持续过程窗口太大正常流量和异常流量混在一起模型反而学不出区分度。2.2 CNN 的卷积核为什么要一维起步对于 (N, M) 的特征矩阵优先选择一维卷积而不是二维卷积。行方向是包的先后顺序列方向是特征。如果直接用二维卷积会同时混合两个方向但列方向上的特征并不是图像那种各向同性的空间关系。一维卷积沿包序列方向滑动每个卷积核尺寸设为 k作用在连续 k 个时间步的所有特征上输出是这 k 个包组合起来的新特征。这有点像 n-gram只是参数是学出来的。# 一维卷积层的 Keras 写法示意 import tensorflow as tf from tensorflow.keras import layers conv layers.Conv1D( filters32, kernel_size3, paddingsame, activationrelu ) # 输入形状为 (batch, time_steps, features)这段代码里filters32表示这一层输出 32 个通道每个通道都是原始特征的一种融合视角kernel_size3一次看 3 个包太小学不到上下文太大会把明显的单包攻击特征抹平。padding设为same是为了保证输出长度不变方便后面继续堆层。注意特征矩阵里含有数值型特征和类别型特征类别型特征如协议类型建议提前做 one-hot 或嵌入不能直接把协议号 0、1、6、17 当像素值送进卷积。2.3 LSTM 在 CNN 输出上做什么CNN 输出以后形状一般是 (batch, time_steps, channels)。time_steps 仍然是包的序号channels 是 CNN 各组卷积核的组合特征。把这一串按时间顺序再接一个 LSTMLSTM 就能学习“前几个窗口的卷积特征如何影响当前窗口”这种时序依赖。举一个具体例子DDoS 攻击通常有缓慢的上升期一开始连接数增加不显著然后突然爆发。CNN 单个窗口看不出来但 LSTM 记住了过去几十个窗口的活跃度变化到了爆发前某个阈值时就会发出异常信号。LSTM 内部的核心是细胞状态和三个门遗忘门决定当前时刻要丢弃多少历史信息输入门决定新增多少输出门决定对外暴露多少。答辩时不需要默写公式只要说清楚“它比 RNN 多了一个细胞状态能有效缓解梯度消失所以能记忆更远的时间步”即可。2.4 三种常用方案对比为什么不是纯 CNN、纯 LSTM、或 Transformer纯 CNN 的强项是局部特征但对超出感受野的长距离依赖无能为力纯 LSTM 能捕捉时序但对每个时刻的局部模式不够敏感且训练收敛慢Transformer 的注意力机制全局建模能力强但课设数据量通常只有几千到几万个样本从零训练 Transformer 容易过拟合而且它对输入顺序的敏感性不如 LSTM需要额外加位置编码。CNNLSTM 组合的语义很直白先用 CNN 把原始特征空间压缩成高层的局部表示再用 LSTM 学习这个表示在时间上的变化。参数总量比 Transformer 小很多在一张普通 GPU 上十几分钟就能训完比较适合课设的时间预算。这里给出一份课设报告中可以直接用的对比表格模型局部特征时序依赖参数量训练难度课设推荐度纯 DNN弱无少容易低纯 CNN强弱需堆大核中中中纯 LSTM弱强中中中CNNLSTM强强中中高Transformer强全局强大高低3. 最小可用实现Python 源码结构与 CNNLSTM 模型搭建现在进入代码层面。源码包里通常包含四个模块数据加载、特征工程、模型定义、训练评估。这里拆开讲并给出可以直接修改的最小版本。3.1 数据预处理从 CSV 特征到定长序列样本课设提供的数据集通常已经抽好了特征最常见格式是每行一个流的统计量比如duration, protocol_type, src_bytes, dst_bytes, count, srv_count。做 CNNLSTM 输入时把每条记录按流 ID 聚合一个流的多个包或多个统计时段拼成序列。如果没有现成的时序结构也可以把数据集按正常行为的时间顺序重排人为构造窗口。import numpy as np import pandas as pd def make_sequences(df, feature_cols, seq_len): 把 df 按时间顺序切成定长序列 data df[feature_cols].values.astype(np.float32) # 归一化按列做 min-max避免数值溢出 eps 1e-7 mins data.min(axis0) maxs data.max(axis0) data (data - mins) / (maxs - mins eps) X, y [], [] for i in range(len(data) - seq_len): X.append(data[i:iseq_len]) # 标签取窗口结束时刻的标签 y.append(df[label].values[iseq_len-1]) return np.array(X), np.array(y)seq_len是序列长度也就是前面讲的窗口大小。这段代码有一个重要的取舍窗口滑动的步长固定为 1会产生大量重叠样本好处是数据增强坏处是相邻样本高度相关会导致训练集和测试集信息泄漏。答辩容易被问到这一点。建议在训练/测试切分前先把数据按时间排序然后以seq_len为步长切窗口而不是每次滑动一个点。3.2 模型定义Conv1D LSTM 的标准组合from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Input, Conv1D, MaxPooling1D, LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam def build_model(seq_len, feature_dim): model Sequential([ Input(shape(seq_len, feature_dim)), # 第一层卷积32 个卷积核核大小 3 Conv1D(32, kernel_size3, activationrelu, paddingsame), Dropout(0.1), # 池化压缩时间步长度 MaxPooling1D(pool_size2), # 第二层卷积64 个卷积核核大小 3 Conv1D(64, kernel_size3, activationrelu, paddingsame), Dropout(0.1), # 接 LSTM64 个单元 LSTM(64, return_sequencesFalse), Dropout(0.3), # 二分类输出 Dense(1, activationsigmoid) ]) model.compile( optimizerAdam(learning_rate0.001), lossbinary_crossentropy, metrics[accuracy] ) return model这里return_sequencesFalse让 LSTM 只返回最后一步的输出用于分类。如果还要再接一层 LSTM 做更深的时序建模则第一层需要返回完整序列。参数上的常见调节点如下表参数名建议值调整方向seq_len16-32窗口时间跨度filters32-128特征抽象能力kernel_size3-5局部时序感受野lstm_units32-128时序记忆容量dropout0.2-0.5防过拟合learning_rate0.001-0.0005收敛稳定性卷积后的 Dropout 用小值 0.1-0.2LSTM 前用大值 0.3-0.5这是很多网络流量检测论文里的经验值直接抄不会出错。3.3 模型训练与输出报告from sklearn.model_selection import train_test_split X, y make_sequences(df, feature_cols, seq_len16) # 按时间顺序切分不 shuffle train_x, test_x, train_y, test_y train_test_split( X, y, test_size0.3, shuffleFalse, stratifyNone ) model build_model(16, len(feature_cols)) history model.fit( train_x, train_y, batch_size64, epochs30, validation_data(test_x, test_y), class_weight{0: 1.0, 1: 5.0}, # 正负样本不平衡时使用 verbose1 )注意shuffleFalse因为流量数据有时间顺序随机打乱会让模型学到“同一流的前后拼图”而不是本质特征。class_weight在攻击样本较少时非常关键它让模型对少数类的误判付出更高代价。如果不设这一项数据集里正常流量占 98%模型只需要输出全正常就能拿到很高准确率但完全失去检测意义。训练完成后保存模型和归一化参数model.save(traffic_model.keras) np.savez(scaler.npz, minsmins, maxsmaxs)scaler.npz是归一化参数部署时对新的流量也需要用同一套参数做转换不能重新计算否则特征分布一旦偏移预测就会失真。4. 把系统跑起来数据集、参数设置与网络流量检测实战排错光有模型还不够课设要的是一个“系统”。系统至少要包含离线训练和在线/离线检测两条链路。4.1 数据集选择与格式对齐常见的开源网络流量数据集有 UNSW-NB15、CICIDS2017、ISCX VPN 等。课设推荐 UNSW-NB15因为它的类别层次清晰每条记录有 49 个特征和 9 类攻击适合做二分类或四分类。拿到 CSV 后第一步是检查缺失值和字符串特征。UNSW-NB15 中包含proto、service、state这类类别字段类别字段需要先因子化再参与特征拼接不能直接交给归一化函数。如果你的数据源是 pcap需要先用 wireshark 或 tshark 导成 CSV。常用命令是tshark -r input.pcap -T fields -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e frame.len -e frame.time_delta_displayed flow_features.csv这个命令只提取了几个基本字段完整的特征工程会复杂得多比如要计算累计包数、平均包长、TCP 窗口大小等。课设时间有限的话建议直接用公开的 CSV 特征集把精力放在模型和界面上。4.2 三个必调参数seq_len、class_weight、batch_size很多代码跑通但效果不好问题往往不在网络结构而在这三个地方。seq_len决定模型能看到多长的历史。对 DDoS 数据集攻击流量通常持续几秒到几分钟而数据集的记录间隔可能不是均匀的。如果每条记录是“2 秒内流的统计量”seq_len 选 16 就是让模型看 32 秒内的流量模式。经验做法是先用验证集扫描几个值画准确率曲线选择平台期的左端点。class_weight用于处理不平衡除了代码里手动设置也可以用sklearn.utils.class_weight.compute_class_weight自动计算。batch_size太小会导致梯度震荡太大则容易收敛到局部极小。对于几千到几万样本的课设32-64 是一个安全区间。4.3 训练过程常见故障与排查方向下面这张表是课设里反复出现的问题可以直接对照排查故障现象可能原因处理方式loss 不下降归一化没做或数据有 NaN检查数据范围和标签是否对齐验证集准确率低但训练集很高过拟合或窗口重叠泄漏降 epoch增加 dropout重新切分预测结果全是 0 或全是 1类别极度不平衡未设 class_weight加 class_weight调整预测阈值输入维度报错feature_dim 与输入不一致检查类别特征是否拼接完整在训练结束后一定要做一次独立的验证比如抽取 20% 数据完全不出现在训练过程中。源码包里如果有文档说明通常描述的也是这套流程。4.4 用脚本做一轮快速调参验证scores {} for seq_len in [8, 16, 32]: X, y make_sequences(df, feature_cols, seq_len) train_x, test_x, train_y, test_y train_test_split( X, y, test_size0.3, shuffleFalse) model build_model(seq_len, feature_dim) model.fit(train_x, train_y, epochs10, verbose0) val_loss, val_acc model.evaluate(test_x, test_y, verbose0) scores[seq_len] val_acc print(fseq_len{seq_len}, accuracy{val_acc:.4f})这段脚本把seq_len从 8 到 32 扫了一遍。注意每次训练前重新构建模型否则上一次训练的权重会影响下一次结果。更严谨的做法是固定随机种子并在同一验证集上比较。5. 再进一步把课设改成可验证的流量检测小服务课设答辩时训练一个模型还不够最好提供一个可运行的接口让老师看到“输入一批流量特征返回是否为攻击”。这里给一个最简单的方案用 Flask 写一个/predict接口加载训练好的模型和归一化参数接收 JSON 数据返回预测概率。# app.py from flask import Flask, request, jsonify import numpy as np import joblib from tensorflow.keras.models import load_model app Flask(__name__) model load_model(traffic_model.keras) # 假设之前用 joblib 保存了归一化参数 params joblib.load(scaler.pkl) mins params[mins] maxs params[maxs] app.route(/predict, methods[POST]) def predict(): data request.get_json() features np.array(data[features], dtypenp.float32).reshape(1, seq_len, feature_dim) features (features - mins) / (maxs - mins 1e-7) prob model.predict(features)[0][0] return jsonify({prob: float(prob), label: int(prob 0.5)}) if __name__ __main__: app.run(host0.0.0.0, port5000)这段代码里scaler.pkl可以在训练脚本里自行保存import joblib joblib.dump({mins: mins, maxs: maxs}, scaler.pkl)用curl测试接口curl -X POST http://127.0.0.1:5000/predict \ -H Content-Type: application/json \ -d {features: [[0.1, 0.2, ..., 0.5], [0.3, 0.1, ..., 0.2]]}返回结果中的label是 0 或 1但单看概率还不够验证阶段至少打印混淆矩阵和 F1 分数。一个容易加分的小技巧是画 ROC 曲线并计算 AUC然后根据约登指数选择最优分类阈值——也就是让“真正例率 - 假正例率”最大的那个阈值而不是固定用 0.5。这个阈值可以在服务启动时从验证集上算好写进配置文件这样线上预测时直接用阈值判断效果比默认 0.5 更稳定。本文还有配套的精品资源点击获取