简介:本资源面向计算机、人工智能及网络安全方向的本科生与研究生,提供一套基于机器学习的网络流量分类方法研究完整毕业设计资料,适合作为毕设、期末大作业或课程设计的高分参考。包内共36个文件,涵盖8个Python源码、6张实验图片、4个txt标签文件、4个pkl模型文件、3个xml配置、1份doc论文、1份pptx中期检查文档及1个pth权重文件等,压缩包约8.88MB,结构清晰、注释完整,新手也能快速理解。项目围绕CNN、AlexNet、VGG等网络模型展开流量分类实验,包含数据读取、模型训练与评估脚本,并附有训练标签与准确率记录,便于复现与二次开发。目前已有209人学习下载,代码经过严格调试,部署简单,可直接运行,具有较高的实际应用与学习参考价值。
1. 从一份毕业设计说起:网络流量分类到底在解决什么问题
很多人第一次接触网络流量分类,是在做毕业设计的时候。导师丢过来一个题目「基于机器学习的网络流量分类方法研究」,要求交源代码、论文和 PPT 三件套。听起来像是标准模板题,但真正动手才发现,坑比想象中多得多:数据集从哪来、特征怎么提、模型选哪个、准确率上不去怎么办、论文里的实验表格怎么填才经得起答辩老师追问。这一串问题,其实正是一线做流量识别时每天都要面对的东西。
网络流量分类要解决的核心问题很朴素:给定一段网络通信产生的数据流,判断它属于哪一类应用或协议——是网页浏览、视频流、文件传输,还是某种特定的业务流量。传统做法靠端口号匹配,后来端口随机化让这招失效,于是转向深度包检测,但加密流量普及之后又不够用了。机器学习方法的价值就在于,它不依赖 payload 明文,而是从流量的统计特征、时序特征里学出区分能力。这套思路不只用于毕业设计,在园区网管理、QoS 保障、异常流量发现里都是实打实的需求。下面我按一个能跑通、能写进论文、能扛住答辩的完整路径来讲,从数据到模型到实验设计,把每一步的参数和坑都说清楚。
2. 数据集与特征工程:决定上限的一步
2.1 为什么特征工程比模型选择更关键
做过几轮流量分类实验之后,我的血泪经验是:模型换来换去,准确率可能就差两三个点;但特征提取得对不对,直接决定结果是 95% 还是 60%。原因在于,加密流量和私有协议的 payload 不可见,模型能拿到的信息全在流的统计行为里。如果特征没覆盖到区分性维度,再深的网络也是巧妇难为无米之炊。
常见的公开数据集有几个方向:一类是带标注的抓包数据集,提供原始 pcap 和对应的应用标签;另一类是已经提取好特征的 CSV 数据集,每个流一行,列是各种统计量。毕业设计里我一般建议先用现成的特征数据集跑通流程,再自己从 pcap 提取特征做对比实验,这样论文里既有 baseline 又有自己的工作量。
特征大致分四组。第一组是包长统计:均值、方差、最大最小包长,因为不同应用的报文尺寸分布差异明显。第二组是时间间隔:流持续时长、包到达间隔的均值与方差,交互式应用和流媒体在这上面区别很大。第三组是流级别计数:上行下行包数、字节数、比值,能反映请求响应模式。第四组是标志位和协议字段:TCP 标志位计数、TLS 握手相关字段等。
2.2 用 Python 从 pcap 提取流特征的最小实现
下面这段代码演示从 pcap 提取基础流特征的核心逻辑。实际项目里我会用 scapy 或 cicflowmeter 这类库,但为了讲清楚原理,这里手写一个简化版。
from scapy.all import rdpcap, IP, TCP, UDP from collections import defaultdict import numpy as np def extract_flows(pcap_path): packets = rdpcap(pcap_path) # 用五元组作为流标识 flows = defaultdict(list) for pkt in packets: if IP not in pkt: continue proto = pkt[IP].proto src, dst = pkt[IP].src, pkt[IP].dst sport = pkt[TCP].sport if TCP in pkt else (pkt[UDP].sport if UDP in pkt else 0) dport = pkt[TCP].dport if TCP in pkt else (pkt[UDP].dport if UDP in pkt else 0) # 双向流统一 key,保证上下行归到同一条流 key = tuple(sorted([(src, sport), (dst, dport)])) + (proto,) flows[key].append(pkt) return flows def flow_features(pkts): sizes = [len(p) for p in pkts] times = [float(p.time) for p in pkts] iats = np.diff(times) if len(times) > 1 else [0] return { "pkt_count": len(pkts), "byte_total": sum(sizes), "size_mean": np.mean(sizes), "size_std": np.std(sizes), "size_max": max(sizes), "size_min": min(sizes), "iat_mean": np.mean(iats), "iat_std": np.std(iats), "duration": times[-1] - times[0] if len(times) > 1 else 0, }逻辑说明:extract_flows用五元组把包聚成流,注意这里对源和目的做了排序,目的是把双向流量合并成一条流,否则上下行会被拆成两条,特征就失真了。flow_features计算九个基础统计量,这是最小可用集合。参数上,size_std和iat_std往往比均值更有区分度,因为均值容易趋同,方差才能体现行为差异。
提示:流超时切分是个容易忽略的点。真实流量里一条五元组可能持续很久,通常设 15 秒或 120 秒无包则切分新流,这个阈值会显著影响流的数量和特征分布,论文里要写明。
2.3 特征归一化和类别不平衡的处理
特征量纲差异很大,包长是几十到上千,时间间隔可能是微秒级,直接喂给模型会让大数值特征主导距离计算。标准化是标配:
from sklearn.preprocessing import StandardScaler import pandas as pd df = pd.read_csv("flow_features.csv") X = df.drop(columns=["label"]) y = df["label"] scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 注意:scaler 只能在训练集上 fit,再 transform 测试集,否则数据泄漏类别不平衡是流量分类的常态,某些应用样本几千条,某些只有几十条。处理方式有三种:对多数类欠采样、对少数类过采样(SMOTE)、或者用带 class_weight 的模型。我一般先用 class_weight,改动最小,效果不够再上 SMOTE。要注意 SMOTE 只能在训练集上做,测试集保持原始分布,否则评估结果虚高,答辩时被问到就尴尬了。
3. 模型选型与训练:从传统算法到深度学习
3.1 传统机器学习基线怎么搭才站得住脚
论文里如果只有深度学习模型,答辩老师通常会问「为什么不用传统方法对比」。所以一套完整的实验至少要有三档:传统机器学习、浅层神经网络、深度模型。传统方法里,随机森林和 XGBoost 是流量分类的常青树,原因很简单:特征维度不高时,树模型对特征缩放不敏感,训练快,还能输出特征重要性,方便写分析。
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.3, stratify=y, random_state=42 ) rf = RandomForestClassifier( n_estimators=200, # 树的数量,200 通常够用,再多收益递减 max_depth=None, # 让树充分生长,配合 min_samples_leaf 控制过拟合 min_samples_leaf=2, # 叶子最小样本数,防止噪声样本被单独成叶 class_weight="balanced", n_jobs=-1, random_state=42 ) rf.fit(X_train, y_train) print(classification_report(y_test, rf.predict(X_test)))参数说明:n_estimators从 100 加到 200 通常有提升,超过 300 基本平了。min_samples_leaf是控制过拟合的关键,流量特征里噪声大,设成 2 到 5 比较稳。class_weight="balanced"自动按类别频率反比加权,缓解不平衡。stratify=y保证切分后各类比例一致,这个不加的话小类别可能全被分到一边,评估就废了。
3.2 一维卷积和 LSTM 在流量序列上的用法
深度学习做流量分类,主流有两种建模方式。一种是把流的前 N 个包的包长和方向组成序列,用一维卷积或 LSTM 处理,捕捉时序模式。另一种是把流统计特征当向量,用全连接网络。前者更适合包序列信息丰富的场景,后者适合特征工程已经做得很充分的情况。
import torch import torch.nn as nn class FlowCNN(nn.Module): def __init__(self, num_classes=10): super().__init__() # 输入通道 1,序列长度按前 20 个包 self.conv1 = nn.Conv1d(1, 32, kernel_size=3, padding=1) self.conv2 = nn.Conv1d(32, 64, kernel_size=3, padding=1) self.pool = nn.AdaptiveMaxPool1d(1) # 全局最大池化,输出固定长度 self.fc = nn.Linear(64, num_classes) self.relu = nn.ReLU() self.dropout = nn.Dropout(0.3) def forward(self, x): # x shape: (batch, 1, seq_len) x = self.relu(self.conv1(x)) x = self.relu(self.conv2(x)) x = self.pool(x).squeeze(-1) x = self.dropout(x) return self.fc(x)逻辑说明:两层卷积提取局部模式,AdaptiveMaxPool1d(1)把任意长度的序列压成一个向量,这样不同流长都能处理。Dropout(0.3)是防过拟合的常规操作,流量数据量通常不大,dropout 设 0.2 到 0.5 之间。序列长度选前 20 个包是个经验值,太短丢信息,太长大部分流根本凑不满,padding 会引入噪声。
训练时的几个关键设置:学习率用 1e-3 配 Adam,batch size 64 或 128,早停 patience 设 10 个 epoch。如果验证集 loss 震荡不降,先检查学习率是不是太大,再检查数据有没有归一化。
3.3 评估指标不能只看准确率
流量分类的评估,准确率是最容易骗人的指标。如果测试集里 80% 是网页流量,模型全预测成网页也有 80% 准确率,但毫无用处。必须看每个类别的 precision、recall、F1,以及宏平均和加权平均。混淆矩阵也要画出来,看看哪些类别容易被混。
from sklearn.metrics import confusion_matrix, f1_score import seaborn as sns import matplotlib.pyplot as plt y_pred = rf.predict(X_test) cm = confusion_matrix(y_test, y_pred) print("Macro F1:", f1_score(y_test, y_pred, average="macro")) print("Weighted F1:", f1_score(y_test, y_pred, average="weighted")) sns.heatmap(cm, annot=True, fmt="d", cmap="Blues") plt.xlabel("Predicted") plt.ylabel("True") plt.savefig("confusion_matrix.png", dpi=300)宏平均 F1 对小类别敏感,加权平均受大类别主导,两个都报才全面。混淆矩阵里如果发现某两类互相混得厉害,回去看特征,往往是这两类在统计行为上确实接近,需要补更有区分度的特征,比如 TLS 握手的特定字段。
4. 避坑与排查:那些让实验翻车的细节
4.1 数据泄漏:准确率 99% 的假象
现象:训练完模型测试准确率 99%,高兴得不行,结果换个数据集或者重新切分就掉到 70%。
原因:最常见的是标准化时在全集上 fit 了 scaler,测试集的信息泄漏进了训练过程。另一个隐蔽来源是同一会话的流被随机切分到了训练集和测试集,模型其实见过「近亲」样本。
解决:所有预处理只在训练集上 fit,再 transform 测试集。切分时按会话或时间切,不要按流随机切。用train_test_split时加stratify保证类别比例,但会话级别的分组要用GroupShuffleSplit。
4.2 流超时阈值设错导致特征失真
现象:提取出来的流数量异常多,大部分流只有一两个包,特征全是零或极小值。
原因:流超时阈值设得太短,正常的请求响应被切成了碎片。或者双向流没合并,上下行各算一条。
解决:超时阈值一般设 15 秒到 120 秒,交互式应用用短一点,流媒体用长一点。五元组做 key 时对源目的排序合并双向。提取完先统计一下流长度的分布,如果中位数只有两三个包,基本可以确定切分有问题。
4.3 类别不平衡导致小类别全军覆没
现象:整体准确率还行,但某个类别的 recall 是 0,混淆矩阵里那一行全错。
原因:小类别样本太少,模型为了降低整体 loss 直接放弃它们。或者用了 SMOTE 但只在训练集做了一半,测试集里小类别还是很少。
解决:先上class_weight="balanced",再考虑 SMOTE。SMOTE 的k_neighbors不能大于小类别样本数减一,否则报错。评估时重点看宏平均 F1,不要被加权平均迷惑。如果小类别实在少,考虑合并相似类别,或者用异常检测思路单独处理。
4.4 深度学习模型不收敛的排查顺序
现象:loss 一直不降,或者降到某个值就震荡。
原因:可能是学习率太大、数据没归一化、标签编码有问题、或者 batch 里有全零样本。
解决:按这个顺序查——先确认输入数据归一化了没有,再确认标签是从 0 开始的连续整数,然后把学习率降到 1e-4 试试,最后检查有没有空流或全零特征的行。如果用了 LSTM,注意序列 padding 的位置,padding 的 0 会参与计算,最好用pack_padded_sequence屏蔽掉。
4.5 论文实验表格和代码结果对不上
现象:论文里写的准确率和代码跑出来的差了几个点,答辩时被追问。
原因:改了代码没同步改论文,或者跑了多次取了最好的一次但没说明。
解决:实验做完先固定随机种子,把最终结果存成文件,论文里的数字直接从文件里抄。如果报了多次实验的最优值,要在论文里写明「取 5 次独立实验的最优值」或「均值±标准差」,不能含糊。随机种子、数据切分比例、模型超参数这些都要在论文里写清楚,这是可复现性的基本要求。
5. 论文与 PPT 的收尾技巧:让工作量和结论对得上
5.1 论文框架怎么搭才不空
毕业设计的论文框架,我一般按这个结构走:绪论讲背景和国内外现状,第二章讲相关技术,第三章讲数据集和特征工程,第四章讲模型设计和实验,第五章讲结果分析,最后结论。关键在第三、四章要有自己的东西,不能全是抄的。特征工程那章要把你提了哪些特征、为什么提、怎么算的写清楚,最好配一张特征列表的表格。实验那章要有对比实验,传统方法和深度方法各跑一遍,表格里列出准确率、宏 F1、训练时间。
论文里最容易空的是「相关技术」那章,很多人把教科书内容抄一遍。我的做法是只写和本文方法直接相关的技术,比如你用了随机森林就讲随机森林的原理和为什么适合流量分类,别把整个机器学习发展史都写进去。答辩老师看的是你的工作量和思考,不是知识面。
5.2 PPT 只讲三件事
PPT 不用把论文搬上去,讲清楚三件事就够了:问题是什么、你怎么做的、结果怎么样。页数控制在 15 到 20 页,重点页是方法框架图和实验结果表。框架图用 draw.io 画,别用截图,清晰度差很多。实验结果表只放核心指标,别把混淆矩阵、ROC 曲线全堆上去,挑最有说服力的一两张。
答辩时最常被问的问题:为什么选这个模型、特征是怎么选的、准确率为什么比别人的低或高、有没有考虑实时性。提前把这些问题想好答案,尤其是准确率对比,如果比参考文献低,要能说出原因,比如数据集不同、类别更多、没做数据增强等。
5.3 代码整理和复现说明
源代码交上去之前,至少保证别人能按 README 跑通。目录结构清晰,数据、代码、结果分开。依赖写进 requirements.txt,版本号固定。主脚本加命令行参数,别把路径写死。如果时间允许,写一个run.sh把训练和评估串起来,答辩演示时一条命令跑完,比现场改代码稳得多。
# run.sh 示例 python extract_features.py --pcap data/sample.pcap --out features.csv python train.py --data features.csv --model rf --out model.pkl python evaluate.py --model model.pkl --data features.csv --report result.txt这套流程跑通之后,论文里的实验数据、PPT 里的结果截图、代码里的输出就全对得上了,不会出现三件套互相打架的情况。我自己踩过最深的坑就是代码改完忘了同步论文,答辩前一夜对着两个版本的数字抓狂,希望你别重蹈覆辙。这个方向不难,难的是把每一步做扎实,把参数和边界都摸清楚。希望帮到你。
本文还有配套的精品资源,点击获取