CNVD Severity Classifier: Improvements Report#
Technical report on changes made to the CNVD severity trainer (classify_severity_cnvd.py) following the independent analysis in VulnTrain#19.
Context#
An external review of the CIRCL/vulnerability-severity-classification-chinese-macbert-base model identified several issues: data leakage in the train/test split, poor Low-class recall, keyword dependency, and a suboptimal published checkpoint. The reported headline accuracy of 77.83% was inflated by ~1.7pp due to leakage.
Changes#
Data leakage fix#
Problem: CNVD reuses boilerplate descriptions across different vulnerability IDs. The original train_test_split split by row index, allowing 1,587 identical descriptions (15.6% of the test set) to appear in both splits.
Fix: New deduplicate_split() function groups all entries by description text and assigns entire groups to one split. No description appears in both train and test.
Impact: The old model evaluated on the deduplicated test set scores 85.2% (inflated — it was trained on data overlapping this test set). A model retrained on the deduplicated split scores 76.8%, matching the independently measured unleaked accuracy of 76.6%.
Class weighting experiments#
Problem: The Low class (~9% of data) had only ~41% recall on unleaked data, with 60% of Low entries misclassified as Medium.
Four loss strategies were tested on the deduplicated split:
Mode |
Low recall |
Medium recall |
High recall |
Overall acc |
|---|---|---|---|---|
Uniform (none) |
0.4099 |
0.8165 |
0.7809 |
0.7677 |
Sqrt-dampened |
0.4902 |
0.7481 |
0.8056 |
0.7457 |
Balanced |
0.6084 |
0.7024 |
0.8099 |
0.7323 |
Focal (gamma=2) |
0.6328 |
0.6441 |
0.8349 |
0.7110 |
Conclusion: Every weighting strategy that improved Low recall caused disproportionate Medium recall loss. The Low/Medium vocabulary overlap in CNVD descriptions makes this a data-level limitation, not a loss-function problem. The trainer defaults to uniform loss.
A --class-weights flag (none, sqrt, balanced, focal) was added for future experimentation.
Per-class metrics#
compute_metrics now reports precision, recall, and F1 per class (Low/Medium/High) alongside overall accuracy and macro F1 at each evaluation epoch.
Best model checkpoint selection#
metric_for_best_modelset toaccuracy(was defaulting toeval_loss)save_total_limitincreased from 2 to 3 to prevent the best checkpoint from being pruned
CodeCarbon tracker scoping#
The @track_emissions decorator wrapped the entire train() function, including push_to_hub(). The codecarbon background thread never stopped during the upload. Replaced with an explicit EmissionsTracker start/stop scoped to trainer.train() only. Also removed push_to_hub=True from TrainingArguments (it caused trainer.train() to upload internally before returning). The same fix was applied to classify_severity.py.
Dynamic model card#
The model card is now a template (model_card_cnvd_severity.md) populated with actual eval metrics from trainer.evaluate() after each training run. Documents per-class metrics, training configuration, and known limitations.
Known limitations documented#
The model card now documents:
Low severity recall (~41%): ~60% of Low entries are misclassified as Medium due to vocabulary overlap. All weighting strategies degrade Medium recall disproportionately.
Keyword dependency: the model biases toward a vulnerability type’s typical severity. Accuracy drops from ~89% to ~55% on atypical-severity entries.
Negation blindness: “does NOT allow RCE” still predicts High with high confidence.
CVE overlap: 81% of CNVD entries have a CVE equivalent. The model primarily adds value for the ~19% CNVD-only entries.
These findings align with independent results from CyberScale Phase 1 (Point 29), which plateaued at ~62% band accuracy on a 4-class CVSS classifier using ModernBERT-base with similar approaches (CWE enrichment, multi-task heads, CPE features — none moving the needle beyond ~2pp).
Dataset improvements#
CVE cross-references#
The extract_cnvd function now extracts the cve_id field from cves.cve.cveNumber in the raw CNVD JSON. This enables users to cross-reference CNVD entries with their CVE equivalents and filter CNVD-only entries.
Dataset card#
A dataset card (dataset_card_cnvd.md) was added documenting:
Field descriptions including the new
cve_idcolumnCVE overlap rate: 81% overall (68-69% in 2020-2021, 91-97% after 2022)
Severity distribution: High ~36%, Medium ~55%, Low ~9%
Coverage decline: 94% of reserved IDs published in 2015 → 4% in 2023 (post-RMSV regulations, September 2021)
Warning about duplicate descriptions and train/test split leakage
Comparison validator#
A dedicated validator (validators/severity_cnvd.py) was added to evaluate the old and new models side by side on the same deduplicated test set. It reports per-class precision/recall/F1, confusion matrices, and a summary delta table.
Commits#
Commit |
Description |
|---|---|
|
Data leakage fix, class-weighted loss, per-class metrics, best model selection |
|
CNVD severity model comparison validator |
|
Sqrt-dampened class weights |
|
Scoped codecarbon tracker (CNVD trainer) |
|
Removed push_to_hub from TrainingArguments |
|
Scoped codecarbon tracker (severity trainer) |
|
|
|
Focal loss option |
|
Default to uniform loss |
|
Model card |
|
Dynamic model card from eval metrics |
|
CVE cross-references and dataset card |
References#
Issue: VulnTrain#19
Model: CIRCL/vulnerability-severity-classification-chinese-macbert-base
Dataset: CIRCL/Vulnerability-CNVD
External validation: eromang/researches/CNVD-Dataset-Validation
Related work: CyberScale Phase 1 lessons learned