基于Adapter的少样本持续学习:恶意流量识别新范式

发布时间:2026/8/28 13:08:49
基于Adapter的少样本持续学习:恶意流量识别新范式 近几年在流量安全方向一个很现实的问题是恶意流量识别模型不像图像分类那样“训完就完事”新攻击手法、新协议变种不断出现模型需要持续吸收新知识同时不能把旧攻击类型的识别能力忘掉。今天要聊的这套方案是一类比较新的研究路线名字直接概括了核心Adapter-Based Few-Shot Continual Learning for Malicious Packet Recognition翻译过来就是“基于适配器的少样本持续学习恶意流量识别”。这个方案的价值不在单个模型有多深而是把三件事拧在一起Adapter 参数高效微调、Few-Shot 少样本学习、Continual Learning 持续学习。过去处理新攻击类型通常要把整个模型重新训练成本高、数据需求大现在用这套思路只需要拿极少量新恶意流量样本冻结大部分网络参数只更新很小的 Adapter 模块就能在识别新攻击的同时保留旧攻击的记忆。这篇技术文会围绕这套路线的原理、实验设计、评估方法和落地注意点展开最后给出可复用的训练流程和排错清单适合正在做流量异常检测、IDS/IPS 规则增强、安全大模型微调方向的同学收藏。先说结论这类方案能不能直接用取决于你对“持续”的定义。如果是几十个类别的增量学习、每轮只给少量新样本Adapter 方案在参数量、显存占用、收敛速度上都有明显优势如果是面对极度稀疏、噪声很大的真实网卡流量效果会受数据预处理影响不能只看模型结构。1. 核心概念与技术背景恶意流量识别本质是一个流量分类问题输入是网络数据包、流特征或会话日志输出是正常流量或某种攻击类型比如 DDoS、端口扫描、恶意软件通信、SQL 注入探测等。传统做法是训练一个深度神经网络把大量样本一次性灌进去得到一个“静态模型”。但静态模型在真实安全场景里有几个很难受的问题新攻击出现频率高标注数据少。一个新家族刚出现时安全分析师能快速标注的样本通常只有几十条不足以从头训练一个深层模型。旧攻击类型不能丢。安全模型一旦忘记旧的恶意行为模式等于给攻击者开了一个后门。全量重训成本高。每来一批新样本就把模型整个重训一遍在 GPU 资源和人力上都不现实。所以这个方向引入持续学习。持续学习要解决的核心问题叫灾难性遗忘当模型学会新任务时对新任务拟合得越好对旧任务的表现越容易下降。为了解决遗忘研究人员提出了很多策略包括正则化方法、回放记忆、动态网络结构等等。而这次标题强调的 Adapter-Based 属于“参数隔离 高效微调”这一支通过给预训练模型插入少量额外参数让新任务的知识写进这些小模块里尽量不扰动原来的参数。为什么要结合 Few-Shot因为安全场景里新类别的标注量天然很小。Few-Shot Learning 的目标正是“用小样本学到一个可泛化的分类边界”。把 Few-Shot 和 Continual Learning 放到一起实际上是在模拟一个更真实的节奏模型已经会识别十几种攻击类型突然只给你几十条新攻击样本你希望模型既能学会这个新类又不把旧类忘掉。这个设定非常贴近安全运营中的“新漏洞利用刚被捕获”时刻。再看标题里的 Malicious Packet Recognition。这里的输入形式可以有两种主流选择一种是直接基于 PCAP 数据包解析成特征向量比如流持续时间、包长分布、协议头字段、TLS 指纹等另一种是把原始报文按照字节序列建模类似 NLP 里把词变成 token。前者特征工程成本高但解释性好后者更像“端到端”对模型容量要求更高。Adapter 方案通常搭建在预训练特征提取器之上所以不管哪种输入形态只要有一个能在无标注或弱标注数据上预训练好的编码器就能继续接 Adapter 流程。从相关研究背景看A Comprehensive Survey of Continual Learning: Theory, Method and Application持续学习理论、方法与应用综合综述里也梳理过类似观点实际应用中的持续学习很少是“理想增量场景”更多是数据分布偏移、类别重叠、标注噪声混杂的开放环境。流量识别恰恰是这种开放环境的典型代表。Adapter 方法的优势在于它把“学新知识”和“改旧知识”解耦了新知识进 Adapter旧知识留在主干网络两者通过残差连接协作理论上比直接微调整个网络更容易控制遗忘。2. 方案架构设计2.1 整体流程基于 Adapter 的少样本持续学习恶意流量识别整体可以分成四个阶段预训练或基础训练阶段用足够多的历史流量数据训练一个骨干特征提取器让它先具备通用的流量表征能力。这一步不要求覆盖所有攻击类型只需要让模型对“正常流量”和“常见恶意流量”有一个合理的向量表示。元少样本训练阶段可选如果手上有多个不同攻击类型的子任务可以先做一个 Episode 式训练让模型学会“如何快速适应新类别”。这个阶段决定了模型在新类别只有少量样本时能不能快速进入可用状态。持续学习阶段每一轮增量任务来临时冻结主干参数插入新的 Adapter用当前任务的新样本做少量迭代训练。同时通过记忆池或正则化手段控制旧任务遗忘。推理与更新阶段新任务训练完成后Adapter 与主干网络一起参与推理。模型既保留旧攻击识别能力又新增了新攻击识别能力。如果用图表示大致是这样一张流程链历史流量数据 - 基础特征提取器 - 恶意流量表征 | 新攻击少量样本 - 特征编码 - Adapter 增量学习 - 增量分类器 - 识别结果这套流程里最关键的设计决策是旧任务知识放在哪新任务知识放在哪两者如何共用一个分类输出。2.2 为什么选择 Adapter 而不是全量微调全量微调一个深度流量模型问题不只是慢。如果新任务数据只有几十条全量微调几乎必然过拟合而且会大幅改变主干网络的权重分布导致旧任务特征被破坏。Adapter 的思路是在每一层 Transformer 或卷积模块后面插入一个小型前馈网络x - 主干层 - x_out | - Adapter(下投影 - 激活 - 上投影) - adapter_out | x_final x_out adapter_outAdapter 的参数量通常只占整体模型的 0.5% 到 5%训练时只更新这些新增参数。这样新任务的知识只写入这小部分参数里对主干几乎没有影响天然具备抗遗忘能力。同时由于可训练参数少少样本条件下不易过拟合。2.3 Few-Shot 如何融入Few-Shot 一般通过两种方式融入这个框架基于度量的分类不直接让模型输出类别概率而是让模型学习一个嵌入空间新类样本和已知类样本在空间中的距离决定分类结果。这种方式很适合类别不断增多的场景因为不需要为每个新类增加独立的分类头参数。基于元学习的快速适应用 MAML 或其变体在多个模拟任务上学习“初始化参数”让模型在新任务到来时只用几步梯度更新就能收敛。这种方式更接近“学会学习”。实际工程项目中基于度量的方式更容易落地因为它不需要复杂的二阶梯度计算且增量加入新类时只需要把新类的原型向量加入查找表。本文后面的实验设计也以“原型 Adapter”为主。3. 数据集准备与预处理3.1 数据形态选择做恶意流量识别第一步是决定模型“吃什么”。常见三种数据形态优点缺点适合场景手工流量特征包长、时间、协议字段解释性好输入维度低特征工程量大对未知攻击泛化弱规则增强、传统 ML原始字节序列端到端不需要特征工程输入维度高训练成本大深度模型、预训练会话元数据 载荷组合信息更完整处理链路复杂高精度检测如果走 Adapter 路线比较推荐的是“先做字节序列预训练再在特征提取器上接 Adapter”。这样新攻击即使特征模式很隐蔽向量表征仍能保留一定语义信息。3.2 网络流切分原始 PCAP 不能直接当作一条样本输入。通常需要按五元组或流超时机制切分为一个个网络流。切分规则建议单向流 / 双向流双向流信息更全但处理复杂度更高。流超时超过 N 秒没有新包则视为流结束。包数量截断每条流只保留前 M 个包防止超长流导致计算量爆炸。负载截断每个包只保留前 N 字节很多攻击特征集中在包首部。具体的 N、M 数值需要按数据集统计分布确定。以常见公开恶意流量数据集的经验看单条流保留 20 到 50 个包、每个包保留 256 到 1024 字节通常已经能覆盖大多数关键特征。但这个判断只做参考务必用自己的数据分布验证。3.3 数据增量划分持续学习实验和普通分类实验不一样不能简单随机划分训练测试集。需要按“任务序列”构造数据# 任务划分思路把攻击类型分成多个任务模拟真实中出现顺序 task_1_classes [Benign, DDoS, PortScan] task_2_classes [BruteForce] task_3_classes [WebAttack] tasks [ {train: train_data_for(task_1_classes), test: test_data_for(task_1_classes)}, {train: train_data_for(task_2_classes), test: test_data_for(task_2_classes)}, {train: train_data_for(task_3_classes), test: test_data_for(task_3_classes)}, ]对于 Few-Shot 设定每个任务中的训练样本要刻意减少到每类 5 到 20 条。这里可以按支持集和查询集拆分支持集用于模型更新每类 5/10/20 条。查询集用于评估当前任务效果每类 50 到 100 条。3.4 数据安全与合规流量数据非常敏感。无论是抓取校内网流量还是使用公开数据集都要注意公开数据集优先选择 CIC-IDS、UNSW-NB15 这类学术数据集避免自己抓取真实生产流量。如果要使用真实流量必须做好脱敏去掉 IP、域名、载荷中的个人信息。不要用攻击工具对未授权目标发起流量。实验只能在自家虚拟机、测试靶场或公开数据集上进行。4. 环境准备与实验前置条件4.1 硬件与依赖这类模型实验整体门槛不算高重点看骨干网络的规模如果骨干是小型 1D CNN 或浅层 Transformer8GB 显存已经足够。如果骨干使用大规模预训练模型比如类 BERT 的流量模型12GB 到 24GB 显存更稳妥。纯 CPU 可以跑通流程但训练速度会很慢建议至少有一张支持 CUDA 的显卡。软件环境按通用深度学习流程准备# Python 环境建议 3.9 或 3.10 # PyTorch 需要按 CUDA 版本安装示例命令需要按实际环境调整 pip install torch torchvision matplotlib scikit-learn pandas numpy这些是通用依赖列表如果骨干模型有额外依赖按项目 README 补充即可。4.2 实验目录结构持续学习实验的中间产物很多建议从一开始就规划清楚目录project/ ├── data/ │ ├── raw/ # 原始 PCAP 或 CSV │ ├── processed/ # 切分好的流特征/字节序列 │ └── splits/ # 各任务的 train/val/test 划分 ├── models/ │ ├── backbone/ # 预训练或基础训练权重 │ └── adapters/ # 各任务保存的 Adapter 权重 ├── results/ │ ├── logs/ # 训练日志 │ ├── checkpoints/ # 增量任务后的完整模型 │ └── eval/ # 混淆矩阵、分类报告 └── scripts/ ├── preprocess.py ├── train_base.py ├── train_adapter.py └── evaluate.py5. 模型设计与训练流程5.1 基础骨干网络选择骨干网络的选择取决于输入形态。常见选择包括1D CNN输入为流特征向量或包长度序列参数量小训练快。Transformer Encoder输入为字节序列类似 BERT 处理文本适合捕获载荷中的深层模式。图神经网络如果构建了流量交互图可以考虑但工程复杂度更高。从效率和效果平衡看建议优先尝试一个中等规模的 1D CNN 作为骨干先跑通所有流程再逐步替换成更强的骨干。下面给一个简化版 1D CNN 骨干示例实际参数需要按数据维度调整import torch import torch.nn as nn class FlowBackbone(nn.Module): def __init__(self, input_dim64, hidden_dim128, num_layers3): super().__init__() layers [] in_ch 1 for i in range(num_layers): out_ch hidden_dim // (2 ** max(0, i - 1)) layers.append(nn.Conv1d(in_ch, out_ch, kernel_size3, padding1)) layers.append(nn.ReLU()) layers.append(nn.MaxPool1d(2)) in_ch out_ch self.encoder nn.Sequential(*layers) self.projection nn.Linear(in_ch, hidden_dim) def forward(self, x): # x: [batch, seq_len, feature_dim] x x.permute(0, 2, 1) # [batch, feature_dim, seq_len] x self.encoder(x) x x.mean(dim2) return self.projection(x)这里把输入当作一维序列处理输出一个向量用于后续分类或原型对比。注意代码中的卷积通道数变化逻辑简化了实际使用时建议把每层通道数写成明确的常量方便调试。5.2 Adapter 模块设计Adapter 通常放在骨干网络中每层之后。一个标准 Bottleneck Adapter 包含下投影、激活、上投影和残差连接class BottleneckAdapter(nn.Module): def __init__(self, hidden_dim128, bottleneck_dim16): super().__init__() self.down nn.Linear(hidden_dim, bottleneck_dim) self.act nn.GELU() self.up nn.Linear(bottleneck_dim, hidden_dim) def forward(self, x): # 残差连接保证主干特征不被破坏 return x self.up(self.act(self.down(x)))训练时冻结骨干参数for name, param in backbone.named_parameters(): param.requires_grad False只让 Adapter 参数参与优化优化器用 AdamW学习率可以比全量微调稍大因为可训练参数量小、收敛快。5.3 持续学习训练流程增量任务训练的核心伪代码如下def train_incremental_task(backbone, adapters, classifier, support_set, query_set, epochs, lr): # 冻结骨干 for p in backbone.parameters(): p.requires_grad False # 只训练当前任务的 adapter 和分类器相关参数 params list(adapters.parameters()) list(classifier.parameters()) optimizer torch.optim.AdamW(params, lrlr) for epoch in range(epochs): for x, y in support_set: with torch.no_grad(): feat backbone(x) logits classifier(adapters(feat)) loss nn.CrossEntropyLoss()(logits, y) optimizer.zero_grad() loss.backward() optimizer.step() # 在查询集上验证当前任务效果 acc evaluate(backbone, adapters, classifier, query_set) return acc这里有几点要说明每个新任务来了是“新增一个 Adapter”还是“复用同一组 Adapter”取决于设计。常见做法是每个任务分配独立的 Adapter推理时把所有 Adapter 的输出拼接或相加。这样做的好处是任务之间互不干扰坏处是 Adapter 数量会随任务增长。另一种做法是共享同一个 Adapter配合经验回放控制遗忘实现更简单而且容量固定。如果使用共享 Adapter建议在训练新任务时从记忆池里采样一部分旧任务数据一起参与训练比例通常控制在当前任务样本旧任务样本 11 或 21。分类头需要做特殊处理。如果新类别加入可以给分类头增加输出维度并在旧类别的原型向量上做调整。5.4 评估指标持续学习场景不能只看当前任务准确率至少要看三组指标当前任务准确率Average Accuracy, ACC模型在刚学完的任务上的测试精度。旧任务回测准确率Backward Transfer, BWT学完新任务后旧任务精度相比之前是上升还是下降。BWT 为负说明发生了遗忘。总体平均精确率 / 召回率 / F1在流量识别场景类别不平衡非常严重不能只看 Accuracy特别是恶意流量样本通常远少于正常流量F1 和召回率更关键。ACC 所有任务平均准确率 BWT 学完最后一个任务后旧任务精度均值 - 学完当时旧任务精度均值 F1 宏平均 F1对每个类别单独计算再取平均记录指标时建议每个增量任务结束后跑一次全任务评估输出一张形如下面的表任务序号新增类别ACC当前BWTF1新类F1旧类1DDoS0.94-0.910.952PortScan0.93-0.010.890.933WebAttack0.92-0.020.870.91这个表是示意数据不来自任何实际实验。真实结果需要按你的数据和模型跑出来。6. 推理与部署6.1 模型保存与加载持续学习模型的部署和普通模型不太一样。因为主干网络是共享的Adapter 和分类头是增量保存的推理时要统一加载def load_model(backbone_path, adapter_paths, classifier_path): backbone FlowBackbone() backbone.load_state_dict(torch.load(backbone_path)) adapters [BottleneckAdapter() for _ in adapter_paths] for adapter, path in zip(adapters, adapter_paths): adapter.load_state_dict(torch.load(path)) classifier torch.load(classifier_path) return backbone, adapters, classifier推理时把所有 Adapter 的输出逐层融合def inference(backbone, adapters, classifier, x): with torch.no_grad(): # 简化示例假设只在一个位置插入 adapter feat backbone(x) for adapter in adapters: feat adapter(feat) logits classifier(feat) return logits.argmax(dim-1)如果 Adapter 数量到后期很多可以在推理时只加载“相关任务”的 Adapter具体按业务配置决定。6.2 服务化部署流量识别模型上线一般有两种方式离线批量检测对 PCAP 文件批量跑流切分、特征提取、模型预测输出可疑流列表。适合事后溯源、样本分析。实时流式检测流量镜像到检测服务按流超时机制把缓冲区的报文拼成流再交给模型推理。对延迟要求较高。服务化建议用 FastAPI 包一个简单接口from fastapi import FastAPI, Request from pydantic import BaseModel app FastAPI() class FlowInput(BaseModel): features: list class Predictor: def predict(self, features): # 实际预测逻辑 return benign predictor Predictor() app.post(/predict) async def predict_endpoint(item: FlowInput): result predictor.predict(item.features) return {result: result}启动命令uvicorn api_server:app --host 0.0.0.0 --port 8000但注意uvicorn需要额外安装上面的示例只做接口框架参考实际需要按项目调整。接口上线前要确认访问权限和鉴权策略不要把检测接口暴露在公网。6.3 批处理流程真实流量检测中批量处理是刚需。建议设计一个简单的任务队列import os import glob def batch_predict(pcap_dir, output_file): for pcap_path in glob.glob(os.path.join(pcap_dir, *.pcap)): flows split_pcap_to_flows(pcap_path) for flow in flows: vec extract_features(flow) pred model.predict(vec) write_result(output_file, pcap_path, flow.id, pred)实际项目里还要加断点续跑和日志记录防止处理到一半崩溃后从头再来。7. 资源占用与性能观察7.1 可训练参数量对比Adapter 方案最直观的优势是训练时反传的梯度只流向少量参数。假设骨干模型有 500 万参数单任务 Adapter 有 2 万参数那么每个增量任务只需要优化 2 万参数占总量约 0.4%。这意味着优化器状态小显存占用可以接受。每轮迭代时间短。少样本不容易过拟合。具体到实测一个中等规模 1D CNN 骨干上这类实验显存占用通常在 4GB 到 8GB 的范围内但这只是经验参考一定要以你自己的实际环境为准。7.2 显存和内存观察方式训练和推理时可以用nvidia-smi观察显存状况watch -n 1 nvidia-smi如果显存不足先做这些调整减少 batch size比如从 64 降到 16。减少输入序列长度比如把单流包数从 50 降到 30。缩小 Adapter 中间瓶颈维度。如果骨干网络很大可以尝试梯度检查点gradient checkpointing节省显存。7.3 推理性能预估推理性能受三部分影响骨干特征提取器计算量。Adapter 数量。Adapter 越多推理计算量越大。流切分与特征提取模块耗时。这部分常常被忽略其实文本切流和特征计算可能比模型推理还慢。建议做性能测试时单独统计数据预处理耗时、模型推理耗时、总耗时这样才知道瓶颈在哪。增量任务很多时可以考虑把多个 Adapter 合并成一个等价模块或者使用模型蒸馏压缩。8. 常见问题与排查方法问题现象可能原因排查方式解决方案新任务训练完旧任务精度骤降主干参数被误更新检查冻结逻辑确认requires_gradFalse覆盖所有主干参数小样本下模型过拟合训练集精度高但测试集低Adapter 容量过大查看 Adapter 参数量缩小 bottleneck 维度增加 dropout新类别的流量识别不出来总是预测成旧类分类头没有正确扩展检查分类器输出维度为新类重建分类头或使用原型分类训练时显存溢出输入序列长度过长或 batch 过大nvidia-smi查看占用减小 batch size 或截断输入序列数据处理阶段内存暴涨同时加载太多 PCAP 文件检查代码中文件读取方式改用流式读取一次只处理一个文件增量训练耗时异常高每个任务都重新跑了全量数据查看数据加载逻辑确认支持集是否被正确过滤Adapter 数量越来越多推理变慢每个任务独立 Adapter统计推理耗时尝试共享 Adapter 或合并 Adapter训练损失不下降学习率不匹配或输入特征未归一化打印梯度统计调整学习率检查输入数据标准化类别极度不平衡导致精度高但召回低评估指标选错查看混淆矩阵和各类别 F1改用宏平均 F1必要时对少数类加权这些排查方式是通用经验不是专属于某个框架的出现问题时先看日志、再看数据、最后看模型。9. 最佳实践与合规边界9.1 工程实践建议先建立基线再上 Adapter。不要一开始直接上完整持续学习方案。先用一个静态分类器在所有历史数据上训练得到一个“理论最高”参考线。之后所有增量实验都拿这个参考线比较才能判断增量方案到底损失了多少精度。每个任务固定随机种子。持续学习实验对随机种子非常敏感。如果随机种子不固定任务顺序对结果的影响很难和算法本身的影响区分开。建议在代码初始化时固定所有随机源包括 Python、NumPy、PyTorch 和 CUDA。保留一套最小可运行配置。记录一下“用最少的数据和最小的模型也能跑完整个流程”的配置。后续调模型、换骨干时先用这套最小配置验证环境和代码链路再上全量数据能省很多排查时间。任务顺序要模拟真实出现顺序。不要按字母顺序或人工偏好排列任务最好按攻击出现时间排序。如果不知道真实顺序可以跑多个随机顺序取平均避免个别顺序带来的偶然性。批量任务要加日志和失败重试。如果做 PCAP 批处理建议把每条流的输入文件、流 ID、预测结果、耗时写入日志失败时记录异常原因。这样即使跑到第 1000 个文件崩掉也能从断点继续。9.2 数据与模型合规流量识别涉及网络安全内容安全边界尤其重要训练数据必须来自合法渠道。优先使用公开学术数据集、自己搭建的测试环境流量或获得明确授权的流量镜像。不得使用该技术对未授权系统进行扫描、探测或攻击。模型只能用于防护检测和防御性安全分析。如果模型用于企业内部流量审计需要提前告知相关方并遵循数据保护法规。流量内容可能包含个人隐私信息处理前要做脱敏和权限控制。对外发布评测结果时不要包含真实流量中的敏感字段。混淆矩阵、特征可视化要确保不泄露客户信息。不要用这类模型绕过安全检测、构造免杀流量或辅助攻击。任何反向用途都有法律风险。9.3 什么时候不要用 Adapter 方案不是所有场景都适合 Adapter如果攻击类型之间语义差异非常大比如“DDoS 泛洪”和“隐蔽隧道通信”几乎不共享任何特征单一骨干网络可能很难同时表达两者。这时可以考虑任务独立 Adapter 任务路由。如果每个新任务的样本量极少且类别数量很多比如每类只有 2 条Few-Shot 和 Continual Learning 同时面临超大挑战。要先考虑数据增强或用预训练网络做更强的初始化。如果要求模型完全可解释、每个决策都能回溯到具体规则深度模型不一定是最优解传统机器学习加规则引擎可能更合适。10. 总结与下一步这套基于 Adapter 的少样本持续学习恶意流量识别方案最值得尝试的点在于它把“持续学习”从一个学术概念拉回了工程可实现的范围不要重训整个模型只需要在主干网络上加小模块用少量样本就能学新攻击类型。对安全团队来说这意味着新样本标注速度和模型更新速度可以匹配上不需要为每一次增量维护一套复杂的重训流水线。如果你决定动手实验最先验证的应该是三件事骨干网络对基础类别的特征表征是否合理——可以在基础类别上先做一个简单的 kNN 分类看特征是否已经具备可分性。第一个增量任务的 Adapter 是否能收敛且不显著影响旧任务——这是整套方案的成立前提。分类头扩展后新旧类别之间是否存在严重的分类偏移——这是部署时最容易踩的坑。这三个问题都能跑通再往完整流程扩展。整套流程里最容易踩的坑是分类头很多人把精力花在 Adapter 结构上却忽略了一个事实——增量类别加入后分类器也必须同步更新否则骨干网络学得再多也映射不到正确的输出上。后续可以扩展的方向包括把 Adapter 替换成 LoRA 或 Prefix Tuning 看效果对比在骨干网络引入更大规模流量预训练模型把记忆池换成生成式回放让模型“复习”旧数据或者把方案接到真实 IDS 告警系统里做自动化增量学习。希望这篇从原理到落地思路的梳理对你有用后续调试时可以把精力集中在任务划分、Adapter 容量和分类头更新这三块通常解决了这三块整体效果不会差。