☰
基于深度学习的网络流量异常识别:从数据处理到模型评估的完整课设指南
2026/10/5 5:21:33 网站建设 项目流程

简介:基于深度学习实现网络流量异常识别分类的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 维特征加两个标签列。标签列里有label(0 表示正常、1 表示异常)和attack_cat(Normal、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_memory=False) print("原始形状:", df.shape) # 1. 删除整列全空的列,这类列对模型没有信息量 df = df.dropna(axis=1, how="all") # 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(axis=1, how="all")是按列删全空字段,安全且不会误伤样本;fillna(df[num_cols].median())选了中位数而不是均值,原因在于smean、dmean、tcprtt这类统计特征长尾极重,少数超大流会把均值拉到一个不具代表性的位置,中位数更稳。

如果你的数据文件很大,low_memory=False一定要加,否则 pandas 会自动推断类型,同一个列里混入缺失占位符时会把整列读成 object,后面全乱。清洗完顺手跑一句df.isna().sum().sum(),确认没有未处理的缺口再继续。

2.3 标签到底是什么:先决定做二分类还是多分类

很多课设方案只做二分类,即判断每一条流是正常还是异常,对应label列。这是题目的最低要求,也是快速跑通全流程的最短路径。但如果你希望答辩时多一点可讲的东西,attack_cat列可以升级成 10 分类任务,模型需要识别具体攻击类型。

我建议的顺序是先二分类跑通全链路,再在多分类上做扩展。因为二分类的评估指标好讲清楚,混淆矩阵只有 2×2,老师追问起来容易解释;多分类则要面对类别严重不均衡问题,比如 Worms 可能只有几百条样本,而 Generic 有 5 万条,这个差距会让少数类几乎学不到。

实际处理时,attack_cat本身也有脏值。常见做法是保留label=0的样本并把attack_cat的空值填成 Normal,label=1但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], errors="ignore") # 二分类主线:用 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_size=0.3, stratify=y, random_state=42 ) 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_size=0.3意味着验证集和测试集各占 15%,对 17 万条样本完全够用。stratify=y保证切分后正负样本比例与原始数据一致;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_classes=2): 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, dtype=torch.float32), torch.tensor(y_train.values, dtype=torch.long) ) train_loader = DataLoader(train_ds, batch_size=128, shuffle=True) model = TrafficMLP(in_dim=X_train_scaled.shape[1]) criterion = nn.CrossEntropyLoss() # 内部自带 Softmax,最后一层不要加激活 optimizer = torch.optim.Adam(model.parameters(), lr=1e-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(f"epoch {epoch+1:02d} | loss {total_loss/cnt:.4f}")

核心超参数按经验取值:batch_size=128在流量数据上比较稳,太大容易让 BatchNorm 统计量失真,太小训练震荡;lr=1e-3是 Adam 的默认推荐值,基本不用改;epoch=20配合学习率衰减通常足够收敛,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, dtype=torch.float32)) y_pred = torch.argmax(logits, dim=1).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_weight="balanced", classes=np.array([0, 1]), y=y_train.values ) criterion = nn.CrossEntropyLoss( weight=torch.tensor(class_weights, dtype=torch.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 现象:切分时报错,某些攻击类型样本太少

如果升级到多分类并使用stratify=y,很可能遇到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% 准确率的黑匣子有价值得多。把这个主线跑通,你的课设就已经从「交差」变成了「作品」。希望帮到你。

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

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

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

立即咨询