简介:面向网络安全研究与深度学习开发者的加密流量识别模型源码包,针对传统特征工程难以捕捉加密流量隐蔽模式、误报率偏高等问题,提供一套端到端的深度学习解决方案。模型创新性融合LeNet、AlexNet与全局平均池化(GAP)结构:LeNet擅长提取基础卷积特征,AlexNet引入更深层与ReLU激活增强非线性表达能力,GAP则替代全连接层降低参数量、缓解过拟合,三者结合可有效提升加密流量分类的准确率与鲁棒性。压缩包共29个文件,体积仅73KB,包含20个Python源文件、2个gitignore版本控制文件、1个txt说明文档、1个cfg配置文件、4个keep占位文件及LICENSE许可协议,目录按AlexNet、GAP等子模块划分,结构清晰,便于快速定位模型代码。目前已有638人学习下载,体现出其对流量识别研究与实践的参考价值。使用者可获得从数据集划分、预处理到模型定义、训练、评估的完整Python脚本,能够直接运行或按需修改,辅助开展网络流量分析、异常检测与安全审计等实验。
1. 加密流量识别为什么值得自己跑一遍:LeNet、AlexNet、GAP 三模型源码拆解
解压这份源码的时候,我先把 readme.txt 翻了一遍,再扫目录结构:20 个 Python 源文件,LeNet、AlexNet、GAP 三个模型各占一个目录,每个目录里都自带 dataset.py、model.py、main.py、metrics.py。这种组织方式在开源项目里其实不多见,多数项目只给一个模型,顶多换几个参数跑实验;而这份源码把三种结构放在同一套数据流程下,天然就是为了对比实验准备的。它解决的问题很具体:在密文载荷无法直接解读的加密流量上,用卷积网络自动提取包的形态特征,把不同的应用或协议区分开。对于正在做网络安全管理、拿流量分类当毕设题目、或者想验证经典 CNN 系列在自己数据集上表现的从业者,这套代码比从零搭三套网络省事得多。而且它的取舍有点反直觉——在这个任务上,经典结构加全局平均池化的组合,往往比无脑堆深度更稳。
2. 数据怎么变成模型能吃的形状:preprocessing 与 create_train_test_set 是整套项目的地基
拿到源码的第一件事不是看模型结构,而是顺着数据文件把链路走一遍。流量识别这个方向,翻车大多发生在数据加工这一层,而不是网络结构。项目根目录 EncryptedTrafficAlanysis 下有 preprocessing.py、create_train_test_set.py、dataset.py,工具函数被拆在顶层 utils.py 和 ml 目录里的 utils.py 两份;LeNet、AlexNet、GAP 三个模型目录各自带着 model.py、main.py、metrics.py、dataset.py。这说明三套网络共用同一份数据准备流程,每套模型有独立的训练入口和评估入口,改成自己的数据集时只需要动前半条链路。
2.1 文件结构里的分工:三个模型目录共享同一套数据管线
先把核心文件的职责拆清楚,后面调参时才知道该改哪个文件。
| 文件 | 职责 |
|---|---|
| preprocessing.py | 读取原始流量数据,按包切分、定长裁剪、归一化,输出中间特征 |
| create_train_test_set.py | 按流划分训练、验证、测试集合,生成特征文件和标签文件 |
| dataset.py | 封装 PyTorch Dataset,供 DataLoader 按批次加载 |
| utils.py(顶层与 ml 目录) | 公共工具,包括包解析、特征统计、文件 IO |
| 各模型目录的 model.py | 定义各自的卷积网络结构 |
| 各模型目录的 main.py | 训练入口,读取数据、迭代 epoch、保存权重 |
| 各模型目录的 metrics.py | 计算准确率、精确率、召回率、F1 与混淆矩阵 |
我一般会把预处理输出落盘成 .npz 或 .npy,再交给 dataset.py 读取,这样调模型时不会反复重跑原始流量解析。这个项目的链路也正是顺着这个思路走的:preprocessing.py 负责把原始 pcap 或 CSV 转成中间张量,create_train_test_set.py 负责把它切成三份并写标签,dataset.py 在训练时按索引取数。三个模型目录里的 dataset.py 内容基本一致,因为喂给三套网络的应当是同一份中间产物,否则对比实验就不公平了。
2.2 加密流量里到底“看”什么:包长、方向与载荷熵
加密后的载荷是不可读的密文,基于明文关键词的检测思路直接失效。但 TLS、SSH、QUIC 这类流量仍然保留着可观测的形态特征:包长的分布、包到达的时间间隔、流的持续时间、上下行方向的比例,以及载荷字节的熵值。这些特征组合起来,足以让卷积网络学到不同协议或应用之间的差异。这就是为什么 CNN 能用在加密流量识别上——它不一定需要理解内容,只需要捕捉包的“形状”。
把一条流喂给卷积网络,最常见的做法是编码成二维矩阵。一个维度是包在流中的位置,另一个维度是每个包的特征切片。比如一条流截取前 32 个包,每个包取前 64 字节作为载荷图像,就得到 32×64 的矩阵;也可以把包长、方向、熵值这些一维序列堆叠在一起,形成多通道的“特征图”。源码 preprocessing.py 的核心工作就是完成这一步转换,具体实现思路和下面这段示意类似:
# 把一条流的前 N 个包、每包前 M 字节转成二维矩阵 def load_flow_to_matrix(flow_packets, max_packets=32, packet_slice=64): # flow_packets 是按抓包顺序排列的包列表,每个包有 payload 字段 matrix = np.zeros((max_packets, packet_slice), dtype=np.float32) for idx, packet in enumerate(flow_packets[:max_packets]): payload = bytes(packet.payload[:packet_slice]) matrix[idx, :len(payload)] = np.frombuffer(payload, dtype=np.uint8) / 255.0 return matrix从这段代码可以看到两个关键参数:max_packets 控制一条流最多取多少个包,packet_slice 控制每个包取多少个字节。归一化必须除以 255,因为载荷字节的取值范围是 0 到 255,不归一化直接进网络,梯度很容易炸。max_packets 的取值通常在 32 到 64 之间,packet_slice 在 64 到 128 之间,这是公开流量数据集上表现比较稳定的区间。取值太小,特征不够;取值太大,大量补零会把噪声带进模型,这一点后续避坑章节还会提到。
2.3 数据集划分与标签对齐:create_train_test_set 的关键参数
数据处理链路里最容易被忽略的是划分脚本。create_train_test_set.py 负责把中间特征按比例切成训练、验证、测试三个集合,并生成对应的标签文件。注意这里必须按“流”划分,不能按“包”划分——同一会话的前后包高度相关,如果同一流的一部分包进了训练集、另一部分进了验证集,验证指标会虚高,模型实际泛化能力远没有显示出来的那么好。
python create_train_test_set.py \ --data_dir ./raw_flows \ --out_dir ./data \ --train_ratio 0.7 \ --val_ratio 0.15 \ --test_ratio 0.15 \ --seed 42train_ratio、val_ratio、test_ratio 三个比例加起来必须等于 1,seed 固定成同一个值才能保证每次划分结果一致,实验才具备可复现性。跑完之后建议立刻打开生成的标签文件检查一下:类别名和数字 id 的映射是否和你的预期一致。因为很多脚本会按文件名排序后自动编号,class 0 不一定是你直觉里的第一个类别;如果标签对不上,后面所有指标都失去意义。这一步检查 30 秒,能省掉后面半天的排查时间。
3. 三个模型跑通加微调:LeNet 做基线、AlexNet 上强度、GAP 负责压参数量
数据链路跑通之后,重点才轮到模型。这个项目把 LeNet、AlexNet 和 GAP 三套结构放在同一套数据管线里,本身就说明了它的用途:用同一个训练集、同一个评估脚本,跑出三组指标做横向对照。我按这个思路跑了一遍,训练入口、参数设置和结果读取方式下面逐步拆开。
3.1 为什么选这三个结构:从感受野到参数量
三个模型的选择是有层次感的,不是随机拼凑。LeNet 是最早的卷积网络之一,只有少量卷积层和池化层,结构简单、收敛快,适合做基线;在 32×64 这种小矩阵输入上,它的 5×5 卷积核已经能覆盖足够大的感受野。AlexNet 比 LeNet 深不少,卷积核更大、通道数更多,特征提取能力更强,但参数量和计算量也明显上涨;把它用在流量矩阵上,需要先把输入调整到合适尺寸。GAP 则不是独立的完整网络,而是一个替换掉全连接层的结构设计——把最后一层卷积的特征图直接做全局平均池化,再输出到分类层。
在加密流量这个场景里,GAP 的价值尤其明显。流量数据集的规模通常远小于 ImageNet,训练样本不够多时,全连接层是最容易过拟合的部分:几千个参数去拟合少数样本,训练集准确率漂亮,验证集一塌糊涂。GAP 把全连接层参数几乎清零,让网络更依赖卷积层提取的局部特征,相当于给模型加了一层正则。用这套源码做实验时,可以把三个模型的输出放在一起比较参数量:LeNet 最轻,AlexNet 最重,GAP 融合结构通常比同等深度的全连接版本轻一个量级,而验证集指标不一定输。
3.2 每个目录下的 main.py 怎么跑:命令行参数怎么设
三个模型目录各自有独立的 main.py,参数设计大致一致。先跑基线,再跑增强结构,最后跑 GAP 融合版本,这样一组对照就有了。
# 在 LeNet 目录下跑基线模型 python main.py --data_dir ../data --epochs 50 --batch_size 64 --lr 0.001 # 在 AlexNet 目录下跑增强结构 python main.py --data_dir ../data --epochs 50 --batch_size 64 --lr 0.0005 # 在 GAP 目录下跑融合 GAP 的版本 python main.py --data_dir ../data --epochs 50 --batch_size 64 --lr 0.001--data_dir 指向 create_train_test_set.py 生成的输出目录,保证三个模型读的是同一份数据;--epochs 设成 50 是这类中小型数据集的常见选择,流量识别任务的收敛速度通常比图像分类快,30 到 50 轮足够看到趋势;--batch_size 设为 64 是兼顾显存和训练稳定性的折中;--lr 这里故意让 AlexNet 用了 0.0005,因为结构变深之后,同样的学习率容易在训练初期震荡。三个模型跑完,对比验证集准确率和 F1,基本能看出结构差异带来的影响。
3.3 训练结束后看什么:metrics.py 的输出与权重保存
训练日志会记录每个 epoch 的 loss 和验证指标,但我不建议只看最后一个 epoch 的结果,因为后期可能已经过拟合。常见做法是保存验证指标最好的那个权重文件,而不是最后一轮;源码里一般用 best_model.pt 来命名这类检查点。加载做推理时,要留意两步:
import torch from model import Net model = Net() checkpoint = torch.load("./best_model.pt", map_location="cpu") model.load_state_dict(checkpoint["model_state_dict"]) model.eval()torch.load 的 map_location 参数很关键,在 GPU 上训练的权重加载到 CPU 环境推理时,不指定它可能直接报显存相关错误。model.eval() 也必须调用,它会把 Dropout 和 BatchNorm 切到推理模式,不调用 eval 的话,同一批数据每次前向结果都可能不同。metrics.py 在训练结束后会计算准确率、精确率、召回率和混淆矩阵,这三个模型目录里都有同名文件,说明三套结构可以分别输出评估结果,方便横向对比。
4. 避坑指南:加密流量分类最容易翻车的五个原因
这套源码我前前后后跑了三轮,踩过的坑大部分不在模型本身,而在数据和评估上。这里挑五个典型的记录一下,每条按现象、原因、解决的顺序写,方便你对号入座。
4.1 准确率很高但验证集乱跳:类别极端不平衡
现象:训练 loss 下降很平稳,验证准确率却忽高忽低,甚至某些类别永远预测不对。原因:流量数据天然不平衡,某几类应用流量占比极高,模型学成了“多数派分类器”——全预测成占比最高的类别,准确率照样很高,但少数类的召回率接近零。解决:先看类别分布,训练时给少数类更高的采样权重,或者用 WeightedRandomSampler 做重采样;评估时别只看准确率,把 macro-F1 当成主要指标,它会对少数类更敏感。metrics.py 里的精确率和召回率就是用来干这个的。
4.2 验证指标好得离谱,换数据就崩:划分泄漏
现象:验证集准确率 97%,一放到新抓的流量上就掉到 75%,差距大得反常。原因:划分数据时按“包”随机切分,同一个流的前后包被分到了训练集和验证集,模型相当于提前见过了答案。加密流量的时序相关性很强,同一个 TLS 会话里前后的包特征高度相似,这种泄漏在流量识别里特别隐蔽。解决:create_train_test_set.py 必须按流维度划分,一条流的所有包只能落在一个集合里。跑完划分后抽几条流检查一下,确认同一个流标识没有同时出现在两个集合里。
4.3 载荷矩阵大量补零,模型把补零当成了特征
现象:训练曲线很漂亮,但把测试集里偏短的包裁剪到同样长度后,分类结果明显偏向某些类。原因:定长裁剪时,长度不足的包会在右侧补零,补零区域成了稳定不变的“黑色带”。如果不同类别的平均包长不同,卷积核就能靠这个补零区域的分布来分类,相当于学了一个假特征,而不是流量内容。解决:处理时用一个 mask 标记有效长度,让卷积层忽略填充区;或者在预处理阶段直接截断更长的包,而不是所有包都补到同一个长度。GAP 结构在这一点上有个小优势,全局平均池化会天然稀释补零区域的权重,但 LeNet 和 AlexNet 的全连接层会把补零特征放大。
4.4 换了个切片长度,实验结果完全对不上
现象:前一天跑 LeNet 验证 F1 是 0.88,今天换了 packet_slice 从 64 改成 128,所有模型的结果都变了,且不是单调变好。原因:包长分布是加密流量识别里区分度最高的特征之一,切片长度一变,相当于换了一个特征空间。这不是 bug,是流量数据的敏感性。解决:做模型对比实验时,预处理参数一旦定下来就不要动;三个模型必须用同一份中间产物。我一般会把预处理输出固定成文件名里带参数的 .npz,比如 raw_flows_p32_s64.npz,换参数就等于换文件,不会互相覆盖。
提示:readme.txt 里如果写了推荐的预处理参数,优先以它为准。不确定时,从 32 包、64 字节这个常见组合开始,稳定之后再微调。
4.5 显存溢出:不是模型太大,是 batch 太大
现象:AlexNet 目录下训练没跑几步就报 CUDA out of memory。原因:把输入 resize 到 224×224 的 ImageNet 尺寸,再加上默认 batch_size,显存迅速见底。加密流量识别的输入矩阵通常不需要那么大的分辨率,64×64 甚至 32×64 就够用。解决:batch_size 从 64 降到 16 或 8,同时确认数据加载时没有在 Dataset 里做多余的高分辨率变换。如果还想跑大 batch,可以考虑梯度累积,但流量识别这类小数据任务没有这个必要。
5. 把评估落到分类级别:用混淆矩阵定位误报,比盯着准确率有用
训练跑完,准确率只是一个钝指标。在加密流量识别这个任务里,真正有用的是分类级别的评估:看哪两个类别最容易互相混淆,模型在哪些流量上误报,误报成什么类别。混淆矩阵就是做这个的。
metrics.py 里通常会输出一个类别 × 类别的矩阵,行代表真实标签,列代表预测标签。对角线的值越大越好,非对角线的亮点就是你模型最薄弱的地方。具体排查时,我会先把混淆矩阵导出成 CSV,然后按误报数量排序,找出最高的几个非对角项。比如某次实验里发现 SSH 流量经常被预测成 SFTP,这两个协议都属于加密远程管理类,包长分布和方向序列确实接近;在这种情况下,调整预处理参数比换模型结构更有效,因为瓶颈在特征区分度,而不是分类器容量。
定位误报之后,还有一个技巧值得养成:把预测置信度低的样本单独导出,逐个看原始流的信息。一份典型的排查流程是这样:
# 把验证集里置信度低于 0.7 且预测错误的样本单独存出来 def dump_low_confidence_samples(model, dataloader, threshold=0.7): model.eval() with torch.no_grad(): for batch in dataloader: features, labels, flow_ids = batch probs = torch.softmax(model(features), dim=1) conf, pred = probs.max(dim=1) for i in range(len(conf)): if conf[i] < threshold and pred[i] != labels[i]: print(flow_ids[i], labels[i].item(), pred[i].item(), conf[i].item())这段代码的作用是把低置信度误判样本的 id 和真实、预测标签打出来,方便回到抓包里定位那条流。threshold 参数控制输出范围,设 0.7 是常见起点;如果你发现低置信度误判大多来自某一种协议,可以针对性调整预处理参数,而不必盲目加大模型。
从那以后,我每次跑完加密流量识别项目,都强制走一遍这条流程:看混淆矩阵、按误报数排序、导出低置信度样本、回原始流量核对。这套流程不复杂,但能避免好几次对着准确率盲目调参的弯路,希望帮到你。
本文还有配套的精品资源,点击获取