第76题:如何泛化到新项目、新语言、新CWE与未来时间?

1. 核心回答
我会把漏洞检测模型的泛化能力拆成四个独立维度:
- Cross-Project Generalization:新项目;
- Cross-Language Generalization:新编程语言;
- Unseen-CWE Generalization:训练阶段未见的漏洞类型;
- Temporal Generalization:未来时间出现的新代码和新漏洞。
这四种泛化都属于 Distribution Shift,但产生分布变化的原因不同,因此需要分别设计训练策略和测试集。
整体原则是:
减少模型对项目、语言、CWE标签和时间特征的表面记忆
↓
尽量学习漏洞形成机制
↓
针对每一个外推维度建立独立 OOD 测试集
↓
报告相对于同分布测试的性能衰减
如果只在随机划分的测试集上获得较高 F1,仍然不能证明模型能够泛化到真实的新项目和未来漏洞。
2. 为什么漏洞检测容易出现泛化问题
假设训练数据为:
Dtrain∼Ptrain(X,Y) D_{\text{train}}\sim P_{\text{train}}(X,Y) Dtrain∼Ptrain(X,Y)
真实部署环境为:
Ddeploy∼Pdeploy(X,Y) D_{\text{deploy}}\sim P_{\text{deploy}}(X,Y) Ddeploy∼Pdeploy(X,Y)
理想情况下两者接近。
实际漏洞检测中,经常存在:
Ptrain(X,Y)≠Pdeploy(X,Y) P_{\text{train}}(X,Y) \neq P_{\text{deploy}}(X,Y) Ptrain(X,Y)=Pdeploy(X,Y)
例如:
- 不同项目具有不同 API、命名习惯和代码结构;
- 不同语言具有不同语法、类型系统和运行时;
- 不同 CWE 对应不同漏洞形成机制;
- 软件框架、依赖和漏洞模式会随时间变化。
已有跨项目漏洞检测研究明确观察到:模型在同项目或随机划分数据上的性能,迁移到未见项目后可能明显下降。
因此我不会把一次随机 Train/Test Split 当作泛化能力证明。
3. 新项目:Cross-Project Generalization
3.1 泛化困难来自哪里
例如训练数据主要来自:
- Linux;
- Chromium;
- FFmpeg;
随后部署到一个从未出现过的新项目。
模型可能已经学到:
- 特定函数名;
- 项目特有 API;
- 文件路径;
- 代码风格;
- 项目模板;
- 重复代码片段;
这些都可能与漏洞标签相关,却不属于真正的漏洞机制。
这种现象会形成 Project Shortcut。
3.2 训练阶段怎么提高跨项目泛化
第一,增加训练项目的多样性。
训练集尽量覆盖:
- 不同项目;
- 不同开发团队;
- 不同代码规模;
- 不同应用领域;
- 不同框架。
第二,清理项目特有捷径。
例如检查模型是否依赖:
- repository name;
- file path;
- commit message;
- CVE编号;
- issue description;
- 漏洞修复后的标签信息。
第三,进行跨项目去重。
如果项目 A 和项目 B 中存在 fork、复制代码或高度相似函数,需要进行 clone / near-duplicate 检查。
否则:
Train Project A
和:
Test Project B
表面上属于两个项目,实际上仍然可能包含相同代码。
第四,优先学习代码语义和漏洞机制。
例如利用:
- 控制流;
- 数据流;
- source-sink关系;
- def-use关系;
- 调用关系;
减少对项目名称和表面 token 的依赖。
4. 新项目应该怎样测试
最直接的方法是 Leave-One-Project-Out。
假设有:
P1,P2,…,Pn P_1,P_2,\ldots,P_n P1,P2,…,Pn
个项目。
选择:
Pn P_n Pn
作为测试项目。
训练阶段只能使用:
P1,…,Pn−1 P_1,\ldots,P_{n-1} P1,…,Pn−1
即:
$$
D_{\text{train}}
\bigcup_{i=1}^{n-1}P_i
$$
Dtest=Pn D_{\text{test}}=P_n Dtest=Pn
然后轮换测试不同项目。
工程实现上也可以使用类似 GroupKFold 的非重叠 group 划分原则,让同一个项目只存在于训练侧或测试侧。
最终报告:
- 每个项目的 Precision;
- Recall;
- F1;
- MCC;
- PR-AUC;
- 平均值;
- 方差。
这样可以发现模型是否只在某几个项目上有效。
5. 新语言:Cross-Language Generalization
5.1 新语言为什么更困难
假设:
$$
D_{\text{train}}
{\text{C/C++}}
$$
测试:
$$
D_{\text{test}}
{\text{Java}}
$$
此时变化的不只是 token。
还包括:
- 语法结构;
- 类型系统;
- 内存模型;
- API;
- 异常处理;
- 对象模型;
- 常见漏洞模式。
已有跨语言漏洞检测研究也把语言之间的 semantic gap 和 distribution discrepancy 作为主要问题。
5.2 怎么提高跨语言泛化
第一,可以使用覆盖多种编程语言的代码预训练模型作为基础表示。
第二,可以增加语言无关的结构信息,例如:
- AST;
- Control Flow Graph;
- Data Flow Graph;
- Code Property Graph;
- source-sink关系。
例如:
C/C++:
strcpy(dst, src)
Java:
Runtime.exec(input)
具体 API 完全不同,但漏洞判断都可以进一步抽象为:
untrusted source
↓
insufficient validation
↓
sensitive sink
模型如果能够学习这一层机制,跨语言迁移通常更有依据。
第三,可以使用多语言联合训练。
例如:
$$
D_{\text{train}}
D_C
\cup
D_{C++}
\cup
D_{Java}
\cup
D_{Python}
$$
通过训练数据多样性降低模型绑定某一种语言的风险。
如果目标语言没有标签,还可以研究 Domain Adaptation 或 Zero-Shot Transfer。
6. 新语言应该怎样测试
可以使用 Leave-One-Language-Out。
例如训练:
C + C++ + Java
测试:
Python
然后轮换:
C + C++ + Python → Java
这种实验能够直接回答:
模型是否能够识别训练阶段完全没有见过的编程语言中的漏洞?
需要同时报告:
- 同语言性能;
- 跨语言性能;
- 两者之间的性能下降。
例如:
$$
\Delta_{\text{language}}
F1_{\text{ID}}
F1_{\text{Cross-Language}}
$$
Δlanguage\Delta_{\text{language}}Δlanguage 越大,说明模型越依赖语言特有模式。
7. 新CWE:Unseen-CWE Generalization
这里必须先区分任务。
7.1 如果任务是漏洞二分类
任务为:
f(x)→{Vulnerable,Safe} f(x)\rightarrow \{\text{Vulnerable},\text{Safe}\} f(x)→{Vulnerable,Safe}
这时可以直接做 Leave-One-CWE-Out。
例如训练集中包含:
- CWE-79;
- CWE-89;
- CWE-125;
- CWE-787;
测试阶段完整保留:
- CWE-416 Use After Free。
训练阶段完全不能出现 CWE-416 的漏洞样本。
然后测试:
模型能否把 CWE-416 样本识别为 Vulnerable?
这是真正意义上的 unseen-CWE vulnerability detection。
8. 新CWE为什么可能泛化
CWE 本身具有层级结构。
MITRE CWE 中,不同 Weakness 的抽象程度不同。
例如较高层的 Class 通常更加抽象,可以跨语言或技术描述弱点;更低层的 Variant 更接近特定语言或技术。
因此模型可以尝试学习较高层的漏洞机制,例如:
输入不可信
↓
缺少边界检查
↓
危险内存访问
而非只学习:
CWE-787
这个标签本身。
训练时可以进一步利用:
- CWE hierarchy;
- CWE description;
- source-sink模式;
- control/data-flow;
- vulnerability mechanism;
建立更抽象的漏洞表示。
这样能够提高向未见具体 CWE 迁移的可能性。
9. 如果任务是预测具体CWE
任务变成:
f(x)→CWEi f(x)\rightarrow CWE_i f(x)→CWEi
情况会发生变化。
如果:
CWEnew∉Ytrain CWE_{new}\notin Y_{\text{train}} CWEnew∈/Ytrain
普通 closed-set classifier 的类别空间中根本没有:
CWEnew CWE_{new} CWEnew
因此它无法正常预测这个新类别。
此时需要改成 Open-Set 或 Zero-Shot CWE Classification,例如:
- 输出
Unknown; - 先预测 CWE 的上层类别;
- 将代码表示与 CWE textual description 进行语义匹配;
- 使用层级分类;
- 使用检索或生成方式给出候选 CWE。
所以“未见 CWE 泛化”需要先明确:
检测未知类型的漏洞
和:
准确给出未知 CWE 编号
属于两个不同任务。
10. 未来时间:Temporal Generalization
这是最接近真实部署的一种测试。
假设时间点为:
t t t
模型只能使用:
T≤t T\leq t T≤t
时已经存在并已经公开的信息。
然后预测:
T>t T>t T>t
的数据。
例如:
2022及以前 → Train
2023 → Validation
2024 → Test
训练阶段不能使用 2024 年之后才公开的:
- CVE 标签;
- 修复 commit;
- issue;
- 漏洞说明;
- 补丁信息。
11. 为什么不能随机切分未来漏洞数据
漏洞标签具有明显的时间属性。
一个函数在 2022 年真实存在漏洞,但这个漏洞可能到 2024 年才被发现。
如果今天构造完整数据集,然后随机切分:
Train / Test
模型训练阶段可能获得 2022 年当时实际上尚不可知的信息。
这属于 Temporal Leakage。
已有软件漏洞预测研究专门指出,真实训练设置应只使用训练时间点当时已经发现的漏洞标签。
因此未来泛化测试应该采用 Forward-Chaining:
D≤t→Dt+1 D_{\leq t} \rightarrow D_{t+1} D≤t→Dt+1
然后:
D≤t+1→Dt+2 D_{\leq t+1} \rightarrow D_{t+2} D≤t+1→Dt+2
逐时间窗口向前评估。
12. 未来时间怎么保持泛化能力
时间分布会持续变化,因此模型部署后需要持续监控。
可以记录:
Metric(t) Metric(t) Metric(t)
随时间的变化。
如果发现:
Metric(t+1)<Metric(t) Metric(t+1)<Metric(t) Metric(t+1)<Metric(t)
并持续下降,需要分析是否出现:
- 新框架;
- 新语言特性;
- 新 CWE;
- 新 API;
- 新代码风格;
- 新漏洞利用模式。
此时可以采用:
- 定期重新训练;
- Continual Learning;
- Replay;
- 参数高效微调;
- 新样本增量学习。
但更新模型后仍然必须保留历史测试集,检查新模型是否出现 catastrophic forgetting。
13. 四轴泛化测试矩阵
最终我会建立如下测试矩阵:
| 泛化维度 | Train | Test | 主要问题 |
|---|---|---|---|
| In-Distribution | 已见项目/语言/CWE/时间 | 同分布样本 | 基础能力 |
| Cross-Project | Projects A/B/C | 未见 Project D | 项目迁移 |
| Cross-Language | C/C++/Java | 未见 Python | 语言迁移 |
| Unseen-CWE | CWE A/B/C | 未见 CWE D | 漏洞机制迁移 |
| Temporal | T≤tT\leq tT≤t | T>tT>tT>t | 未来泛化 |
这样才能把“泛化能力”从一个模糊概念转化成四个可测实验。
14. 怎么量化泛化能力下降
首先计算同分布性能:
MID M_{\text{ID}} MID
然后分别计算:
Mproject M_{\text{project}} Mproject
Mlanguage M_{\text{language}} Mlanguage
MCWE M_{\text{CWE}} MCWE
Mtime M_{\text{time}} Mtime
定义绝对性能下降:
$$
\Delta_i
M_{\text{ID}}
M_i
$$
还可以定义性能保持率:
$$
R_i
\frac{M_i}{M_{\text{ID}}}
$$
例如:
F1ID=0.85 F1_{\text{ID}}=0.85 F1ID=0.85
F1Cross-Project=0.68 F1_{\text{Cross-Project}}=0.68 F1Cross-Project=0.68
则:
$$
\Delta_{\text{project}}
0.17
$$
性能保持率:
$$
R_{\text{project}}
\frac{0.68}{0.85}
0.80
$$
也就是保留了约 80% 的同分布性能。
这样比单独说:
“Cross-Project F1 是 0.68”
更容易判断泛化损失到底有多严重。
15. 还应该报告哪些结果
不能只报告总体 F1。
至少还应该报告:
- Precision;
- Recall;
- PR-AUC;
- MCC;
- 每个项目结果;
- 每种语言结果;
- 每类 CWE 结果;
- 每个时间窗口结果;
- 多 seed 均值和方差;
- 置信区间;
- 错误案例。
对于安全检测,还应该特别关注:
Recallvulnerability Recall_{\text{vulnerability}} Recallvulnerability
因为模型在 OOD 条件下可能出现明显漏报。
如果输出概率,还应该检查 Calibration。
例如模型预测:
P(vulnerable)=0.9 P(\text{vulnerable})=0.9 P(vulnerable)=0.9
在新项目上是否仍然接近真实的 90% 命中概率。
分布发生变化后,模型可能仍然非常自信,但预测已经错误。
16. 泛化失败后如何处理
真正部署时,需要承认模型存在适用范围。
可以根据:
- OOD score;
- confidence;
- ensemble disagreement;
- retrieval evidence;
- 静态分析结果;
建立低置信检测机制。
例如:
Confidence<τ Confidence < \tau Confidence<τ
时:
模型自动判定
转换为:
模型给候选结果 → 静态分析/工具验证 → 人工复核
对于明显超出训练分布的新语言、新框架或新 CWE,也可以直接进入人工升级流程。
这样可以避免模型在未知领域中产生高置信错误。
17. 面试时可以压缩成下面这段
我会把泛化能力拆成四个轴:新项目、新语言、新 CWE 和未来时间,并分别建立 OOD 测试。
新项目采用 Leave-One-Project-Out,保证测试项目从未进入训练;新语言采用 Leave-One-Language-Out,重点验证模型是否学习了跨语言的代码语义和数据流机制;新 CWE 如果是漏洞二分类,就做 Leave-One-CWE-Out,如果要求预测具体的未见 CWE,则需要开放集、层级分类或基于 CWE 描述的零样本方法;未来时间采用严格的 temporal split,只允许模型使用预测时间点以前已经公开的数据和漏洞标签。
训练阶段我会通过多项目、多语言和多 CWE 数据提高分布覆盖,并尽量使用控制流、数据流、source-sink 等更接近漏洞机制的特征,减少项目名、函数名、CVE 标签等表面捷径。
评测时除了报告 Precision、Recall、F1、PR-AUC 和 MCC,我还会计算相对于同分布测试的性能衰减:
Δi=MID−Mi \Delta_i=M_{\text{ID}}-M_i Δi=MID−Mi
同时分析不同项目、语言、CWE 和时间窗口中的失败模式与置信度校准。
只有模型在这些独立 OOD 测试中仍然保持可接受的 Recall 和 Precision,才能说明它具有实际泛化能力。
18. 来源
- MITRE, Common Weakness Enumeration (CWE):CWE 是软件和硬件弱点的分类体系,并具有 Class、Base、Variant 等不同抽象层级。
- scikit-learn, GroupKFold:提供 group 不重叠的训练/测试划分方式,可用于实现项目级、语言级等分组隔离实验。
- scikit-learn, TimeSeriesSplit:强调时间有序数据应保持训练在过去、测试在未来,可用于构造 forward temporal evaluation。
- Safdar et al., Data and context matter: towards generalizing AI-based software vulnerability detection, International Journal of Information Security, 2026:讨论漏洞检测模型在 unseen codebases 上的明显泛化问题,并研究跨数据集性能下降。
- A zero-shot framework for cross-project vulnerability detection in source code, Empirical Software Engineering, 2025:指出不同项目之间的 coding style 和 feature distribution 差异会造成 cross-project generalization 问题。
- Multi-source cross-domain vulnerability detection based on code pre-trained model, Information and Software Technology, 2025:研究 cross-project 和 cross-language 漏洞检测中的 domain discrepancy。
- Jimenez et al.相关研究总结于 Learning from what we know: How to perform vulnerability prediction using noisy historical data, Empirical Software Engineering:指出真实时间条件下只能使用当时已经发现的漏洞标签,避免未来信息进入历史训练集。
- scikit-learn, Probability Calibration:可使用 reliability diagram、Brier score 等方法检查模型输出概率的校准情况。

394

被折叠的 条评论
为什么被折叠?



