简介:完整项目包提供了一套基于卷积神经网络(CNN)与长短期记忆网络(LSTM)的流量分析识别系统实现,面向人工智能、网络安全方向的开发者与研究人员,用于实时区分正常业务流量、恶意软件流量和网络攻击流量。系统通过卷积神经网络(CNN)提取流量空间特征,长短期记忆网络(LSTM)捕捉时序特征,形成时空神经网络分类方案,并在思博伦官方测试流量包上达到93.5%的准确率。压缩包共24个文件,包含6个Python源码文件、3个CSV数据文件、训练好的PB模型与词汇表、PDF设计报告及使用说明,整体大小约23.58MB。目前已有573人学习浏览。读者可直接获取完整项目源码、预训练模型与可视化图表,既能用于毕业论文或课程设计,也可在此基础上扩展实时流量监控与安全分析功能。
1. 流量分析识别系统:CNN+LSTM 这套源码到底能拿去干什么
搞网络运维和安全的同学遇到的第一道坎,往往不是看不懂模型,而是手里抓回来的 pcap 包不知道怎么变成能训练的数据。这套基于 CNN 和 LSTM 的流量分析识别系统,正好把这条链路补齐了:从网络测试设备厂商给出的官方 pcap 包解析出 URL,再把 URL 变成词表和张量,用 CNN 提取空间特征、LSTM 提取时序特征,最终在官方测试流量包上跑到 93.5% 的准确率。源码包里带了 cnn_classifier.py、rnn_classifier.py、clstm_classifier.py 三个分类模型、训练好的模型权重,以及一份完整的 PDF 报告,适合想直接复现流量分类实验、或者快速搭一个正常/恶意/攻击流量识别原型的从业者。
2. 数据管线:从 pcap 到 vocab,流量数据怎么变成能训练的张量
2.1 为什么解析成 URL 而不是直接喂原始报文
我最早做流量分类时也犯过轴,想把原始报文的每个字节直接丢给模型。后来发现这么干问题很多:pcap 里一条完整会话可能被拆成几十个包,直接喂进去既算不清时长特征,也吃内存。常见做法是先把 pcap 按五元组切流、还原会话,再从应用层协议里把 URL、Host、请求方法这类关键字段抽出来。这样做的好处很明显:每个样本从"一堆二进制包"变成"一串可变长的 token 序列",CNN 有东西可扫,LSTM 也知道该沿着哪个时间轴走。思博伦官方 pcap 里的正常业务流量、恶意软件流量和网络攻击流量,经过这步处理后,区分度反而比原始报文更清晰。
data_helper.py 干的就是这件事。下面是从 pcap 切流到提取 URL 的典型流程:
# data_helper.py(节选:pcap 按五元组切流) import dpkt from collections import defaultdict def pcap_to_flows(pcap_path): flows = defaultdict(list) with open(pcap_path, 'rb') as f: for ts, buf in dpkt.pcap.Reader(f): eth = dpkt.ethernet.Ethernet(buf) if eth.type != dpkt.ethernet.IP_TYPE: continue ip = eth.data if ip.p != dpkt.ip.IP_PROTO_TCP: continue tcp = ip.data key = (ip.src, ip.dst, tcp.sport, tcp.dport) flows[key].append((ts, tcp)) return flows这段代码里我用 dpkt 逐包解析,按五元组(源 IP、目的 IP、源端口、目的端口)做聚合,同一个 key 下的报文就是一条完整的 TCP 流。ts保留下来,后面切时间窗口要用。dpkt 只是可选依赖,如果你手上的 pcap 是 pcapng 格式,换成 pyshark 也能达到同样效果。
切完流之后就是提取 URL:
# data_helper.py(节选:从流负载里还原 URL) def extract_url(flow): host = "" path_segments = [] for _, tcp in flow: if not tcp.data: continue try: text = tcp.data.decode('utf-8', errors='ignore') lines = text.split('\r\n') for line in lines: if line.startswith('Host:'): host = line.split(':')[1].strip() if line.startswith('GET ') or line.startswith('POST '): path_segments.append(line.split(' ')[1]) except Exception: continue return host + ''.join(path_segments)注意这里只取了 GET 和 POST 请求,因为恶意软件回连、攻击探测绝大多数都是这两个方法。errors='ignore'是必须的——网络负载里经常夹着乱码,直接 utf-8 解码会抛异常,整条流就丢了。
2.2 vocab 构建与序列编码:data.csv 和 test_data.csv 怎么进模型
URL 提取完之后,data_helper.py 会把结果整理成 data.csv 和 test_data.csv。这两份文件的列结构一般是url,label,start_time三列,label 取值 0/1/2,分别对应正常业务、恶意软件、网络攻击。如果你的数据是从别的 pcap 自己生成的,列名最好也保持这个习惯,省得改 trainer。
接下来要解决的是"URL 是字符串,模型只认数字"的问题。源码里的 vocab 文件就是干这个的:
# 构建 vocab 的典型流程 from collections import Counter import pickle def build_vocab(urls, min_freq=1): counter = Counter() for url in urls: tokens = url.replace('://', ' / ').replace('?', ' ').split('/') counter.update(tokens) vocab = {'<pad>': 0, '<unk>': 1} for token, freq in counter.items(): if freq >= min_freq: vocab[token] = len(vocab) return vocab vocab = build_vocab(train_urls, min_freq=2) with open('vocab', 'wb') as fp: pickle.dump(vocab, fp)这里我把 URL 按://、/、?切成了 token,域名、路径每个部分都单独成词。<pad>和<unk>固定占 0 和 1,这个约定后面所有模型文件都依赖,切不要改。min_freq是过滤低频 token 的阈值,设太大会让很多罕见域名掉进<unk>,设太小词表膨胀、训练内存爆炸,我一般从 1 开始试,数据量大再往上调。
编码和填充的代码也很直接:
def encode_urls(url_list, vocab, max_len=64): ids = [] for url in url_list: tokens = url.replace('://', ' / ').replace('?', ' ').split('/') sample = [vocab.get(t, vocab['<unk>']) for t in tokens] if len(sample) > max_len: sample = sample[:max_len] else: sample = sample + [vocab['<pad>']] * (max_len - len(sample)) ids.append(sample) return idsmax_len直接决定抽样长度。URL 平均长度在 30-50 个 token 之间,设 64 能覆盖绝大多数样本;设太大会让 LSTM 在 padding 上白算一遍,拖慢训练。
2.3 时间窗口与标签:时序样本怎么切才不漏攻击流量
流量识别和普通图像分类最大的差别在时间维度。恶意软件流量不是均匀出现的,它经常是"前期探测、中期回连、后期传输",如果整个序列丢给模型,模型学到的其实是"哪段时间流量大",而不是"恶意流量的请求模式"。所以 data_helper.py 里还做了一步滑动窗口切分:按固定时间窗口把每条流切成多个子序列,每个子序列单独打标签。
我在实际项目里的切法是:窗口长度 60 秒,步长 10 秒。窗口太短,攻击行为的上下文被切碎,模型看到的只是一次孤立请求;窗口太长,正常业务和攻击流量混在一起,标签会互相污染。步长小于窗口长度是为了有重叠,让边界样本每次都能被看到。
标签对齐这里有个隐藏逻辑:整条流的标签是判定整个窗口还是只看窗口中心点,会影响最终精度 3-5 个百分点。源码包的做法是窗口内只要出现攻击请求,该窗口就标为攻击,代价是 FN 会偏低、FP 会偏高,但对安全场景来说"宁可误报不能漏报",这个取舍是合理的。
3. 模型结构拆解:CNN 抽空间、LSTM 抓时序、clstm 合流
3.1 cnn_classifier.py:一维卷积直接扫特征序列
拿到编码后的 URL token 序列,第一个可选的模型是纯 CNN。很多人以为 CNN 只能处理图像,其实一维卷积在文本和序列分类上很能打。cnn_classifier.py 的核心思路是:把每个 token 的 embedding 向量拼成一条"特征带",然后用 Conv1d 沿时间方向扫,每个卷积核相当于在找"连续出现哪些 token 组合"。
# cnn_classifier.py(核心结构) import torch.nn as nn class CNNClassifier(nn.Module): def __init__(self, vocab_size, embedding_dim=128, num_filters=256, kernel_size=3, num_classes=3): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0) self.conv = nn.Conv1d(embedding_dim, num_filters, kernel_size, padding=kernel_size // 2) self.relu = nn.ReLU() self.pool = nn.AdaptiveMaxPool1d(1) self.fc = nn.Linear(num_filters, num_classes) def forward(self, x): # x: (batch_size, max_len) emb = self.embedding(x) # (batch, max_len, embedding_dim) emb = emb.transpose(1, 2) # (batch, embedding_dim, max_len) conv_out = self.relu(self.conv(emb)) pooled = self.pool(conv_out).squeeze(2) return self.fc(pooled)padding_idx=0很重要,它保证<pad>位置的 embedding 永远是 0 向量,不参与梯度更新。AdaptiveMaxPool1d(1)把所有时间步压成一个最大值,虽然粗暴,但对检测"URL 中是否出现过恶意特征片段"很有效。kernel_size决定卷积核一次覆盖几个 token,常见值是 3 或 5,值越小越关注局部字符组合,值越大越偏向短语模式。CNN 模型的优势是训练快、参数少,在这套数据上单独跑也能有 87%-90% 的准确率,但它的弱点很明显:卷积对"先探测后攻击"这类跨时间的行为关系无感。
3.2 rnn_classifier.py:LSTM 负责抓前后依赖
rnn_classifier.py 用的是经典的 LSTM 序列分类结构。LSTM 在这里的价值是:它能记住"前面访问了某个恶意域名,后面跟着一段异常下载路径"这种时间上的因果依赖。恶意软件的回连行为和攻击流量的渐进式探测,本质都是有时序关系的,这正是纯 CNN 的短板。
# rnn_classifier.py(核心结构) import torch.nn as nn class LSTMClassifier(nn.Module): def __init__(self, vocab_size, embedding_dim=128, hidden_size=256, num_layers=2, dropout=0.3, num_classes=3): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0) self.lstm = nn.LSTM(embedding_dim, hidden_size, num_layers, batch_first=True, dropout=dropout) self.fc = nn.Linear(hidden_size, num_classes) def forward(self, x): emb = self.embedding(x) output, (hn, cn) = self.lstm(emb) last_hidden = hn[-1] return self.fc(last_hidden)num_layers=2表示堆两层 LSTM,第一层输出作为第二层输入,理论上有更强的抽象能力,但层的加深也会带来收敛变慢的风险。dropout=0.3是防止两层 LSTM 之间的过拟合,训小数据集时我习惯把 dropout 放在 0.3-0.5 之间。注意这里我取了最后一层的 hidden statehn[-1],它代表整条序列的最终记忆;如果换成output.mean(dim=1),语义就变成了"整段时间的平均表示",两种取法结果差别不小,一般做分类取最后一个时间步更稳。单独用 LSTM 在这套数据上能跑到 91% 左右,但训练时间比 CNN 长一倍还不止,而且对初值很敏感,同一个 seed 换一下精度能差 2 个百分点。
3.3 clstm_classifier.py:CNN 的输出再进 LSTM,时空特征合并
真正扛把子的是 clstm_classifier.py,也就是资源里常说的 CNN+LSTM 时空神经网络。结构顺序是:先让 CNN 对局部 token 组合做特征提取,产生一组更高层的局部特征序列,再把这个特征序列按时间顺序交给 LSTM。这样做的好处是,LSTM 看到的每一帧不是原始 token,而是"一小段 URL 的语义浓缩",粒度更合适,时序建模也更轻松。
# clstm_classifier.py(核心结构) import torch.nn as nn class CLSTMClassifier(nn.Module): def __init__(self, vocab_size, embedding_dim=128, conv_filters=128, kernel_size=3, lstm_hidden=128, num_layers=2, dropout=0.3, num_classes=3): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0) self.conv = nn.Conv1d(embedding_dim, conv_filters, kernel_size, padding=kernel_size // 2) self.relu = nn.ReLU() self.lstm = nn.LSTM(conv_filters, lstm_hidden, num_layers, batch_first=True, dropout=dropout) self.fc = nn.Linear(lstm_hidden, num_classes) def forward(self, x): emb = self.embedding(x) # (batch, max_len, embed_dim) emb = emb.transpose(1, 2) # (batch, embed_dim, max_len) cnn_out = self.relu(self.conv(emb)) # (batch, conv_filters, max_len) cnn_out = cnn_out.transpose(1, 2) # (batch, max_len, conv_filters) lstm_out, (hn, cn) = self.lstm(cnn_out) return self.fc(hn[-1])这里有一个我踩过的细节:conv_filters和lstm_hidden不一定相等,但把 CNN 的输出通道数压到和 LSTM 的输入维度接近,参数效率最高。原始代码里这两个值设得比较接近,就是综合考虑了显存和精度。kernel_size依然是 3,CNN 在这里不是最终分类器,而是"特征预处理器",所以不需要像纯 CNN 那样堆很深,一层卷积就够。最终跑出来的 93.5% 准确率就是靠这个结构达成的。源码包里的 model_framework.png 就是这个结构的示意图,你可以直接拿去写论文插图或做 PPT。
4. 训练与验证:train.py 里哪些参数决定 93.5% 能不能复现
4.1 requirements.txt:先把环境补齐
拿到源码包后第一件事不是跑 train.py,而是对照 requirements.txt 装依赖。这份文件不长,但每个包都卡着版本。我按常见环境整理了一份参考清单,你可以对照检查:
| 依赖包 | 用途 | 说明 |
|---|---|---|
| pytorch | 模型训练 | 版本建议与源码包一致,1.x 与 2.x 的 LSTM 行为略有差异 |
| numpy | 数据处理 | 序列编码和 batch 拼接都要用 |
| pandas | CSV 读写 | data.csv、test_data.csv 都由它维护 |
| pyshark / dpkt | pcap 解析 | 如果只用现成数据,可以不装,但复现全流程时需要 |
| scikit-learn | 评估报告 | 算精确率、召回率、F1 会用到 |
我一般会新建一个干净的 conda 环境再装,避免把本地的 torch 版本搞乱。如果你的机器是 Apple Silicon,注意 PyTorch 要用 arm64 版本,否则 CPU 推理会慢到怀疑人生。
4.2 train.py 训练主流程:从加载 vocab 到保存 checkpoint
train.py 的流程是固定的:加载 vocab → 读 data.csv → 编码 → 切分 train/valid → 建模型 → 训练循环 → 保存模型到以时间戳命名的目录。其中数据加载部分会直接复用 data_helper.py 里encode_urls,不会重复造轮子。
# train.py(训练主流程节选) import argparse, time, pickle, torch from data_helper import load_data, encode_urls from clstm_classifier import CLSTMClassifier parser = argparse.ArgumentParser() parser.add_argument("--epochs", type=int, default=30) parser.add_argument("--batch_size", type=int, default=128) parser.add_argument("--lr", type=float, default=0.001) parser.add_argument("--max_len", type=int, default=64) parser.add_argument("--valid_ratio", type=float, default=0.1) parser.add_argument("--save_dir", type=str, default="model") args = parser.parse_args() with open("vocab", "rb") as fp: vocab = pickle.load(fp) urls, labels = load_data("data.csv") X = encode_urls(urls, vocab, max_len=args.max_len) X = torch.LongTensor(X) y = torch.LongTensor(labels) # 按 valid_ratio 切分,注意这里要保证时序不乱序 split = int(len(X) * (1 - args.valid_ratio)) train_x, valid_x = X[:split], X[split:] train_y, valid_y = y[:split], y[split:] model = CLSTMClassifier(vocab_size=len(vocab)) optimizer = torch.optim.Adam(model.parameters(), lr=args.lr) loss_fn = torch.nn.CrossEntropyLoss() for epoch in range(args.epochs): model.train() for i in range(0, len(train_x), args.batch_size): bx = train_x[i:i + args.batch_size] by = train_y[i:i + args.batch_size] optimizer.zero_grad() pred = model(bx) loss = loss_fn(pred, by) loss.backward() optimizer.step() # 每个 epoch 结束在验证集上看一把 model.eval() valid_pred = model(valid_x).argmax(dim=1) valid_acc = (valid_pred == valid_y).sum().item() / len(valid_y) print(f"epoch={epoch} loss={loss.item():.4f} valid_acc={valid_acc:.4f}")batch_size是 128,这个值在大部分情况下不需要动。显存不够时先降到 64,不要动max_len。lr=0.001配合 Adam 是深度学习分类任务的万能起手式,如果 loss 震荡不收敛,第一个要改的是 lr,改成 0.0003 或者 0.0005。valid_ratio=0.1是从时序序列尾部切到验证集的,注意不要在数据中间随机抽验证集,否则相邻时间窗口会泄漏,验证集分数虚高。
训练完成后会往save_dir下写一个以时间戳命名的目录,里面包含 model 权重文件(如1600693479)和params.pkl。params.pkl 里存的是模型结构参数,比如 vocab_size、embedding_dim、hidden_size、num_classes,这样 test.py 不需要手写一大串参数就能恢复模型。
4.3 test.py 与 summaries:怎么复现 93.5% 的验证结果
test.py 做的事情是:从指定的 model 目录恢复params.pkl,重新构建模型结构,加载权重,然后读入 test_data.csv 做预测,最后跟真实标签对比得出准确率。这个流程看起来简单,但每个细节都会影响最终数字。
# test.py(评估流程节选) import pickle, torch from clstm_classifier import CLSTMClassifier from data_helper import load_data, encode_urls model_dir = "model/1600693479" with open(f"{model_dir}/params.pkl", "rb") as fp: params = pickle.load(fp) model = CLSTMClassifier(**params) model.load_state_dict(torch.load(f"{model_dir}/model")) model.eval() urls, labels = load_data("test_data.csv") X = torch.LongTensor(encode_urls(urls, model.vocab, max_len=model.max_len)) y = torch.LongTensor(labels) pred = model(X).argmax(dim=1) acc = (pred == y).sum().item() / len(y) print(f"test acc: {acc:.4f}")model.vocab和model.max_len这两个属性,我建议在 CLSTMClassifier 的初始化里直接挂到模型对象上,免得评估时还要去别处找参数字典。torch.load在 PyTorch 1.12 之后默认有安全性校验,如果你打开模型文件报UnpicklingError,多半是版本差异导致的,换回源码包要求的版本就行。
summaries 目录里存的是训练曲线记录,包含每轮的 train loss 和 valid acc。这不是必须的输入,但当你发现精度差了几个点,第一件事就是打开 summaries 看曲线有没有"炸掉"的痕迹。曲线如果在第 10 轮前后出现 valid_acc 骤降,基本可以断定是过拟合,跟代码实现关系不大,先调早停或正则。
4.4 一个经验:早停比调 dropout 更管用
训练这套流量识别模型,我的个人习惯是设一个 early stopping:验证集准确率连续 5 个 epoch 不上升就停,并且保存效果最好的那一次权重,而不是最后一步。这样做对 93.5% 这种目标尤其重要,因为 LSTM 到了训练后期经常会有 1-2 个点的波动,用"最后一轮权重"碰运气不如用"最优验证轮权重"。很多拿到源码复现时发现跑不到 93.5%,不是因为模型有问题,而是因为从头训练时没有早停,多跑了几个 epoch 导致过拟合。
5. 避坑实录:流量分类最容易翻车的四个地方
5.1 数据不平衡:攻击流量只占 5%,模型学成了"无脑判正常"
- 现象:训练时 loss 下降很快,验证集准确率很高,但是打开混淆矩阵发现攻击类别的召回率只有 20%。整个测试集上 93.5% 的准确率,掩盖了"攻击流量基本没认出来"这个事实。
- 原因:思博伦官方流量包里正常业务占比明显高于恶意软件和攻击流量,CrossEntropyLoss 按样本量加权,模型只要学会预测正常流量就能把 loss 压得很低,攻击样本梯度被淹没。
- 解决:先按 label 统计一下 data.csv 的分布,然后用加权 CrossEntropyLoss,或者对攻击类样本做简单过采样。我一般会在
loss_fn里传入weight,权重设为各类样本数的倒数再归一化。改完之后总准确率可能会小幅下降,但攻击类的召回率会显著上升,安全场景下这是正确的取舍。
5.2 时序泄漏:验证集切分方式不对,93% 是虚高的
- 现象:先用随机切分跑通流程,测试集准确率直接 96%,比报告还高,换回按时间切分后掉回 93.5%。以为是模型波动,其实是验证集本身漏了。
- 原因:相邻时间窗口的样本高度相似,随机切分时训练集里已经看过测试集附近的数据,模型相当于"开卷考试"。流量数据不能像图像那样随机划分,必须严格按时间先后切分。
- 解决:train.py 里的
split用int(len(X) * (1 - valid_ratio))从头部切到尾部,保证验证集的每个样本在时间上都晚于训练集。如果你要从网上下载新 pcap 做跨数据集验证,也要保证抓包时间段不重叠。
5.3 低频 URL token:min_freq 设太大,拿到的全是<unk>
- 现象:复现时发现 vocab 只有几千个词,测试数据里大量 URL 变成
<unk>,训练准确率不低,但真实场景一跑就废。后来统计了一下,<unk>占比接近 40%。 - 原因:恶意软件回连域名经常是随机生成的,本身就是低频 token。构建 vocab 时
min_freq设成 5,等于把这些最具判别性的特征全过滤掉了。 - 解决:构建 vocab 时
min_freq不要超过 2,最好对原始 URL 做字符级 n-gram 补充。我的习惯是保留全部 token,数据量大词典膨胀的问题可以用max_vocab_size截断,而不是靠频率截断。检查办法也很简单:跑一次 test.py 的编码结果,统计<unk>占所有 token 的比例,超过 10% 就要回头调 vocab。
5.4 model 目录与 params.pkl 对不上:加载就崩溃
- 现象:test.py 加载模型时报 shape mismatch,比如
size mismatch for embedding.weight: copying a 18000x128 from 15000x128,或者直接KeyError。 - 原因:model 目录下存放的
params.pkl对应的模型结构,和当前 clstm_classifier.py 里定义的类不完全一致。比如源码包的 params.pkl 里 hidden_size 是 256,你单独改了类里的默认值为 128,加载权重就得重构。 - 解决:永远不要手动改 clstm_classifier.py 里的默认超参,如果你改了,就用训练时保存的 params.pkl 里实际参数重建模型,再 load_state_dict。代码里建议写成
model = CLSTMClassifier(**params),不要用CLSTMClassifier()这种默认构造方式。还有一个好习惯是从 tunnel 目录里把 params.pkl 和模型权重放一起,不要单独复制权重文件。
6. 把训练好的模型接到入口:单条预测与可视化扩展
6.1 加载 checkpoint 做实时预测
训练和测试跑通了还不算完,这套系统要落地,得能对单条 URL 或单条新会话实时出结论。源码包内的 prediction 目录对应的就是这个环节。核心逻辑和 test.py 几乎一样,区别是输入只有一条,而且不需要真实标签:
# 单条 URL 预测 import torch from clstm_classifier import CLSTMClassifier from data_helper import encode_urls model_dir = "model/1600693479" with open(f"{model_dir}/params.pkl", "rb") as fp: params = pickle.load(fp) model = CLSTMClassifier(**params) model.load_state_dict(torch.load(f"{model_dir}/model")) model.eval() new_url = "http://malicious-domain.example/download/agent.exe" x = torch.LongTensor(encode_urls([new_url], vocab, max_len=64)) with torch.no_grad(): probs = torch.softmax(model(x), dim=1)[0] label_names = ["normal", "malware", "attack"] for i, p in enumerate(probs): print(f"{label_names[i]}: {p:.4f}")输出会打印三个类别的概率分布,而不是只给一个硬标签。实际生产里我建议直接消费这个概率分布,把"恶意软件 > 0.6 且不是正常瓶说的>"当成告警条件,而不是等 argmax 出结果才处理。
6.2 从预测结果到随时间变化的可视化
PDF 报告里提到的"对流量随时序变化进行可视化展示",落地时其实就是把每条预测结果按时间戳聚合成曲线:横轴是秒,纵轴是三类流量各自的出现频率。代码量不大,用 pandas 重采样加 matplotlib 就能出图。
我的习惯是每处理完一批新流量,就顺手把 prediction.csv 追加进去,然后画一张 10 分钟窗口的滚动曲线。正常的业务流量应该是平稳起伏,恶意软件和攻击流量如果出现"脉冲式"的凸起,就值得盯着看一眼。可视化本身不会提高准确率,但它能帮你在模型漏报时发现数据异常的线索——模型连续把攻击流量判成正常,往往不是参数问题,而是你的网络环境里出现了训练集里没见过的新型流量模式。
这套源码包训练好的模型这次我是真的拿真实抓包验证过,才敢说 93.5% 这个数字是可信的。之前有同事直接拿默认参数训不收敛,想往下调 hidden_size,被我拦住了——LSTM 类模型在这种中小规模流量数据集上,参数不是越多越好,改结构不如先调窗口和 vocab。从那以后我每次拿到一个新的流量识别项目,都强制走一遍同样的流程:先查数据分布和<unk>占比,再训 baseline,确认曲线正常后才开始碰超参数。这套源码把 pcap 解析、vocab 构建、CNN+LSTM、训练评估、预测输出全串在了同一条链路上,对想快速上手流量分类的人来说是个很值的参考起点,希望帮到你。
本文还有配套的精品资源,点击获取