资讯动态

基于深度学习的网络流量异常识别:从数据处理到模型评估的完整课设指南

发布时间:2026/10/5 5:21:35 来源:尧图企业网站定制
简介基于深度学习实现网络流量异常识别分类的Python源码项目面向计算机相关专业在校生、教师及企业开发者可服务于课程设计、毕业设计、大作业等场景。项目以KDD 99系列数据为基础完整覆盖数据预处理、特征工程、模型构建与评估等流程并融入A3C强化学习思路适合对智能安全运维或异常检测方向感兴趣的读者深入学习。压缩包内共50个文件含16个Python脚本负责训练、监控与结果展示20个txt/arff文档提供数据说明与配置指引另有8个data数据文件、4个eps可视化图表和2个bat批处理脚本整体约19MB模块划分清晰便于按需调阅。目前已有233人学习下载。通过该资源可系统实践从网络流量数据清洗到异常分类模型部署的完整链路亦可参考其目录结构与代码风格快速搭建同类检测项目具备较高的借鉴价值。1. 基于深度学习的网络流量异常识别课设题不只是训练一个分类器把一份网络流量数据丢给深度学习模型让它自己学会分辨哪条连接是正常访问、哪条是扫描或攻击行为再用 python 把整套流程落成一个能反复跑的源码工程——这就是基于深度学习的网络流量数据异常识别分类项目要做的事也是大量学校课设题目的标准形态。我第一次拿到这类题目时以为难点在模型结构真正做下来才发现数据处理、标签定义和评估口径才是最容易让新手翻车的三个地方。这个项目适合两类人一是计算机相关专业要做课设或毕设的学生需要一个数据量适中、能完整演示深度学习流程的题目二是刚接触安全数据分析、想看看流量分类怎么落地的从业者。后面所有内容都按一条能跑通的主线展开拿到公开流量数据集清洗成特征表训练一个二分类网络最后把模型保存成可复用的脚本。2. 流量数据从哪儿来、长什么样选对数据集等于省一半力气2.1 为什么选 UNSW-NB15 而不是 KDD99流量异常识别方向上有几个老牌公开数据集KDD Cup 1999、NSL-KDD、UNSW-NB15、CICIDS2017。课设阶段我最推荐 UNSW-NB15原因是它同时照顾了数据规模和年代感。KDD99 是 1999 年模拟军事网络的产物特征里大量依赖 TCP 连接状态离今天真实网络环境太远NSL-KDD 只是 KDD99 的去重版本质没换血。UNSW-NB15 由澳大利亚网络安全中心发布用 IXIA PerfectStorm 工具生成的混合流量时间上更贴近现代攻击方式且发布时已经按 train/test 拆成了两个 CSV 文件用起来省事。UNSW-NB15 每个样本是一条网络流经特征提取后的统计向量大约 49 维特征加两个标签列。标签列里有label0 表示正常、1 表示异常和attack_catNormal、Fuzzers、Analysis、Backdoors、DoS、Exploits、Generic、Reconnaissance、Shellcode、Worms 共 10 类。训练集规模在 17 万条左右测试集约 8 万条对课设来说跑起来不慢又足够看到模型差异。拿到压缩包后先不要急着写模型。第一步是把 CSV 读进来看看字段类型、缺失情况、类别分布这一步能避免后面百分之八十的返工。2.2 预处理第一刀清洗缺失值、无穷值和占位符网络流量数据集与经典机器学习数据集最大的不同是它不会整整齐齐给你一碗干净的数字。下载到的 CSV 里常出现空字符串、NA、null、Infinity这类占位符还有用-表示缺失的字段。直接用 pandas 读进来这些值会以 object 类型混在数值列里训练时报错只是小事更麻烦的是模型悄悄把缺失信息当规律学进去。我一般先用一段脚本把脏值统一清一遍import pandas as pd import numpy as np # 如果下载的版本不带表头需要手动指定49个特征的列名 df pd.read_csv(UNSW_NB15_training-set.csv, low_memoryFalse) print(原始形状:, df.shape) # 1. 删除整列全空的列这类列对模型没有信息量 df df.dropna(axis1, howall) # 2. object 类型列里的缺失占位符统一换成 NaN for col in df.columns: if df[col].dtype object: df[col] df[col].replace([, , NA, null, -], np.nan) # 3. 数值列里的正负无穷换成 NaN避免后续标准化算出 NaN num_cols df.select_dtypes(include[np.number]).columns df[num_cols] df[num_cols].replace([np.inf, -np.inf], np.nan) # 4. 数值列缺失值用中位数填充流量特征大多右偏均值会被大包拖高 df[num_cols] df[num_cols].fillna(df[num_cols].median()) print(清洗后形状:, df.shape)这段脚本里有两个参数值得较真。dropna(axis1, howall)是按列删全空字段安全且不会误伤样本fillna(df[num_cols].median())选了中位数而不是均值原因在于smean、dmean、tcprtt这类统计特征长尾极重少数超大流会把均值拉到一个不具代表性的位置中位数更稳。如果你的数据文件很大low_memoryFalse一定要加否则 pandas 会自动推断类型同一个列里混入缺失占位符时会把整列读成 object后面全乱。清洗完顺手跑一句df.isna().sum().sum()确认没有未处理的缺口再继续。2.3 标签到底是什么先决定做二分类还是多分类很多课设方案只做二分类即判断每一条流是正常还是异常对应label列。这是题目的最低要求也是快速跑通全流程的最短路径。但如果你希望答辩时多一点可讲的东西attack_cat列可以升级成 10 分类任务模型需要识别具体攻击类型。我建议的顺序是先二分类跑通全链路再在多分类上做扩展。因为二分类的评估指标好讲清楚混淆矩阵只有 2×2老师追问起来容易解释多分类则要面对类别严重不均衡问题比如 Worms 可能只有几百条样本而 Generic 有 5 万条这个差距会让少数类几乎学不到。实际处理时attack_cat本身也有脏值。常见做法是保留label0的样本并把attack_cat的空值填成 Normallabel1但attack_cat缺失的样本按 Unknown 处理。这一步放在切分训练集之前完成后面所有操作才不会带病前进。3. 特征工程与数据切分为什么你的模型换份数据就废3.1 先砍掉七列字段不要把环境信息当攻击特征UNSW-NB15 原始特征里包含srcip源 IP、dstip目的 IP、sport源端口、dsport目的端口、stime起始时间、dtime结束时间以及id行号。很多第一次上手的人会直接把全部列灌进模型然后发现训练集准确率接近满分、测试集却惨不忍睹——这就是典型的标签泄露。道理很简单攻击流量可能有固定的源端口模式模型学到「端口是 443 就是正常、是 31337 就是攻击」后面对新 IP、新端口立刻失效。这类字段反映的是网络环境的身份信息不是攻击行为的本质特征所以在特征工程阶段必须删掉。from sklearn.model_selection import train_test_split # 这些列反映的是会话环境不是攻击行为本身 drop_cols [id, srcip, dstip, sport, dsport, stime, dtime] df df.drop(columns[c for c in drop_cols if c in df.columns], errorsignore) # 二分类主线用 label 列0正常1异常 y df[label].astype(int) # 只保留数值型特征proto/service/state 等字符串列暂不用于二分类 feature_cols [c for c in df.columns if c not in [label, attack_cat]] X df[feature_cols].select_dtypes(include[np.number]) print(特征矩阵:, X.shape)这里有个容易被忽略的细节select_dtypes(include[np.number])会把attack_cat里的数值型编码也选进来所以要预先把它排除在feature_cols之外。字符串列如proto里的协议类型课设阶段不必急着做 Embedding 或 one-hot因为数值统计特征已经足够支撑一个 85% 以上的二分类器把有限的时间花在评估和避坑上更划算。3.2 切分顺序的讲究先划分数据再 fit 标准化器流量数据有一个让新手最想不通的坑StandardScaler到底应该在切分之前还是之后 fit答案是必须在切分之后、只 fit 训练集。如果在全量数据上计算均值方差验证集和测试集的信息就已经渗入模型训练过程测试结果会虚高。正确顺序是先切分再对训练集 fit最后对验证集和测试集只做 transform。这样才能模拟真实场景——部署时你手里只有训练时见过的统计量没有「未来数据」的均值。X_train, X_val, X_test, y_train, y_val, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_val_scaled scaler.transform(X_val) # 只 transform不重新 fit X_test_scaled scaler.transform(X_test)test_size0.3意味着验证集和测试集各占 15%对 17 万条样本完全够用。stratifyy保证切分后正负样本比例与原始数据一致UNSW-NB15 中正常流量大约占 56% 左右如果不分层某一折可能出现异常比例明显偏移影响收敛。为什么要标准化深度学习模型对输入尺度极度敏感。流量特征里smean的取值范围可能从 0 到几百万字节而sttl只是 0 到 255 的整数两者直接拼接送入网络梯度会被大数值特征主导小特征完全学不到。标准化之后每个特征均值为 0、方差为 1梯度更新才能对各个维度平等。3.3 要不要做 PCA 或特征筛选课设答辩时经常被问「你做了特征选择吗」。这里我的建议是可以不做 PCA但要能够说出理由。PCA 会把 38 维数值特征压缩成几个主成分模型训练更快缺点是主成分是原始特征的线性组合无法解释「哪个流量特征最重要」。UNCW-NB15 的 49 维特征本身是安全专家手工设计好的统计量每一列都有明确语义直接保留更容易结合 SHAP 做可解释性分析答辩时也更有底气。如果训练时间实在紧张可以用随机森林的特征重要性做一个粗筛只保留重要性前 30 的特征但对课设数据量来说收益不大。真正值得做的是检查特征之间的相关性比如ct_srv_src和ct_srv_dst语义接近去掉一个也影响不大。4. 模型选型与最小实现MLP 是课设的稳妥起点4.1 为什么不是 LSTM 也不是 Transformer看到「网络流量」四个字很多人第一反应是上 LSTM 或 Transformer觉得流量是时序数据。但这条思路要打住UNSW-NB15 的每一条样本已经是把一段网络流聚合成了统计特征比如平均包大小、TTL 变化、连接数计数。样本内部没有天然的时间步骤结构强行把它重排成序列输入 LSTM等于自己编造一份并不存在的时序关系。MLP 反而是这类表格型流量特征最稳妥的基线模型。它把每个特征当作独立维度处理数学上忠实于原始数据训练稳定且很容易在课设里把效果讲清楚。如果后续拿到的是原始 pcap 包并自行抽取时序窗口再考虑 1D-CNN 处理局部包序列这才是上有序列模型的正确时机。4.2 一个 3 层 MLP 的 PyTorch 实现模型结构不用复杂三层全连接足够拟合 17 万条样本的流量特征import torch import torch.nn as nn class TrafficMLP(nn.Module): def __init__(self, in_dim, num_classes2): super().__init__() self.net nn.Sequential( nn.Linear(in_dim, 128), nn.ReLU(), nn.BatchNorm1d(128), nn.Dropout(0.3), nn.Linear(128, 64), nn.ReLU(), nn.BatchNorm1d(64), nn.Dropout(0.3), nn.Linear(64, num_classes) ) def forward(self, x): return self.net(x)这里的三个结构参数值得解释。第一层128是隐层宽度对 38 维输入来说已经偏宽足够容纳特征交叉BatchNorm1d把每层输出拉回标准分布能明显缓解训练初期的梯度波动Dropout(0.3)随机丢弃 30% 神经元防止模型把训练集里的噪声流量背下来。一个容易踩的坑BatchNorm1d在训练和推理时行为不同训练时用当前 batch 的均值方差推理时用训练阶段累计的全局统计量。因此验证和测试前务必调用model.eval()否则结果会随机抖动。4.3 训练循环与超参数from torch.utils.data import TensorDataset, DataLoader train_ds TensorDataset( torch.tensor(X_train_scaled, dtypetorch.float32), torch.tensor(y_train.values, dtypetorch.long) ) train_loader DataLoader(train_ds, batch_size128, shuffleTrue) model TrafficMLP(in_dimX_train_scaled.shape[1]) criterion nn.CrossEntropyLoss() # 内部自带 Softmax最后一层不要加激活 optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(20): model.train() total_loss, cnt 0.0, 0 for xb, yb in train_loader: optimizer.zero_grad() out model(xb) loss criterion(out, yb) loss.backward() optimizer.step() total_loss loss.item() cnt 1 print(fepoch {epoch1:02d} | loss {total_loss/cnt:.4f})核心超参数按经验取值batch_size128在流量数据上比较稳太大容易让 BatchNorm 统计量失真太小训练震荡lr1e-3是 Adam 的默认推荐值基本不用改epoch20配合学习率衰减通常足够收敛1D 流量特征不是图像那种高维输入不需要 100 轮。训练时观察 loss 曲线前 5 轮从 0.69 左右快速掉到 0.3 以下是正常现象如果 loss 一直在 0.69 附近不动先检查特征是否标准化再看标签是否严重不均衡。数据量足够时这个模型二分类准确率能到 90% 左右但注意——准确率不是这里最重要的指标下一章专门讲这个坑。5. 模型评估与避坑为什么准确率虚高F1 才是真相5.1 现象准确率 99% 但攻击流一个没抓到我第一次训练完这个模型测试集准确率高达 99%截图都准备发朋友圈了。直到我打印混淆矩阵才发现异常类别的召回率只有 21%——模型把几乎所有样本都判成了正常类准确率是被占多数的大类撑起来的。原因是 UNSW-NB15 里类别并非严格五五开某些攻击类型样本少加上模型倾向于输出概率更大的类别。如果只用 accuracy 评价这个模型在答辩现场就是灾难。解决方法是换评估口径from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() with torch.no_grad(): logits model(torch.tensor(X_test_scaled, dtypetorch.float32)) y_pred torch.argmax(logits, dim1).numpy() print(classification_report(y_test, y_pred, target_names[Normal, Attack]))看输出时不要只盯第一行的 precision重点看macro avg的 F1。weighted avg会被大类主导macro avg才公平地反映两个类别的平均表现。如果 Attack 类的 recall 低于 0.5就说明模型把大量攻击漏过去了。把y_test和y_pred传入confusion_matrix看一眼左下角数字那才是真实漏报量。5.2 解决类不平衡给损失函数加权重类不平衡的常规解法有三条路欠采样、过采样、损失加权。流量数据量足够大不建议欠采样会把正常流量的多样性丢掉SMOTE 过采样在表格数据上可用但会人为制造样本。最省事的是给CrossEntropyLoss传入类别权重让模型对少数类错判付出更高代价。from sklearn.utils.class_weight import compute_class_weight class_weights compute_class_weight( class_weightbalanced, classesnp.array([0, 1]), yy_train.values ) criterion nn.CrossEntropyLoss( weighttorch.tensor(class_weights, dtypetorch.float32) )compute_class_weight按类别样本数量的倒数做归一化样本越少的类别权重越大。加了权重后训练 loss 数值会比之前高这是正常的不要因为 loss 不降就改回无权重版本。验证指标看的是 Attack 类 recall 是否明显提升而不是 loss 绝对值。5.3 现象换一份新数据测试效果暴跌这是课设里被问得最多的问题「你这模型换个环境还能用吗」模型部署到新网络环境后性能下降首要原因是特征泄露其次才是模型过拟合。我见过一个典型案例有人保留了sport端口列模型学会了「源端口小于是正常流量」这种环境规律换到新数据集后直接报废。避坑方式之一是时间维度切分。UNSW-NB15 的 train/test 文件已经按时间先后分开直接用官方划分做验证比随机打散后切分更接近真实场景。如果你用的是合并后的数据并自行重切记得按stime排序后再切而不是默认随机切。另一个容易忽略的泄露源是特征构造窗口。像ct_srv_src这类连接计数特征统计的是过去一段时间内同服务连接数量如果测试阶段是逐条流在线推理这类特征本身就难以精确复现。课设阶段不必深究但答辩时能主动说出这个边界比被老师逼问出来强得多。5.4 现象验证集 loss 与训练集差距越拉越大训练 10 轮后训练 loss 还在降、验证集 F1 不再涨甚至下降这叫过拟合。信号不是 loss 数值差多少而是验证指标进入平台期。解决办法按优先级排列先把Dropout从 0.3 提到 0.5再看模型是否层数过深、隐层过宽最后才考虑引入 L2 正则。还有一类玄学问题代码里漏了model.eval()和torch.no_grad()导致验证时 BatchNorm 和 Dropout 仍然按训练模式工作验证结果忽高忽低。这个问题排查起来最气人因为没有任何报错。我会在验证函数第一行写model.eval()训练循环第一行写model.train()从机制上杜绝这种翻车。5.5 现象切分时报错某些攻击类型样本太少如果升级到多分类并使用stratifyy很可能遇到ValueError: The least populated class in y has only 1 member。原因是极少数攻击类别样本太少分层抽样无法保证每类至少两个样本。解决思路是合并少数类或放弃该类别。常见做法是设定一个阈值比如某类样本数少于 500 就归并到other类保证分类器重点学习有统计意义的类别。这一步看起来只是工程处理但放在报告里写清楚老师会认为你理解了类别分布问题。6. 让模型跑出教室模型保存、推理脚本与可解释性6.1 把模型和 scaler 打包成一个推理脚本训练结束后很多人只记得torch.save(model.state_dict(), model.pt)却忘了把 scaler 也存下来。这会导致部署推理时数据没做标准化模型输出全是垃圾概率。import joblib torch.save(model.state_dict(), traffic_mlp.pt) joblib.dump(scaler, scaler.joblib)推理脚本里先加载 scaler 做 transform再喂给模型顺序不要反。这两个文件每次必须同时复制缺了 scaler 模型等于废纸。6.2 让模型说出依据用 SHAP 解释流量特征课设答辩时最常被问的一句话是「你这个模型到底学到了什么」。与其支支吾吾不如跑一个 SHAP 摘要图直接看哪些流量特征对预测贡献最大。以 UNSW-NB15 为例子通常ct_srv_src同服务源连接数、smean源到目的平均包大小、dttl目标到源 TTL 值会是高贡献特征——这个结论本身就很有说服力表示模型抓的是连接强度和包统计规律不是 IP 这种环境信息。SHAP 在 17 万条全量数据上跑会很慢抽样 2000 条做解释即可。课设不需要部署线上服务做到这一步已经超出大多数同期作品一个身位。6.3 还有一句过来人的提醒我在这类项目上最大的教训是别把时间全砸在准确率上。做完评估、修完泄露、打开 SHAP 图之后我意识到一份「能解释、有边界、可复现」的工程比一个刷高 0.5% 准确率的黑匣子有价值得多。把这个主线跑通你的课设就已经从「交差」变成了「作品」。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑