|
| 1 | +# CNVD Severity Classifier: Improvements Report |
| 2 | + |
| 3 | +Technical report on changes made to the CNVD severity trainer (`classify_severity_cnvd.py`) following the independent analysis in [VulnTrain#19](https://github.com/vulnerability-lookup/VulnTrain/issues/19). |
| 4 | + |
| 5 | +## Context |
| 6 | + |
| 7 | +An external review of the [CIRCL/vulnerability-severity-classification-chinese-macbert-base](https://huggingface.co/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. |
| 8 | + |
| 9 | +## Changes |
| 10 | + |
| 11 | +### Data leakage fix |
| 12 | + |
| 13 | +**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. |
| 14 | + |
| 15 | +**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. |
| 16 | + |
| 17 | +**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%. |
| 18 | + |
| 19 | +### Class weighting experiments |
| 20 | + |
| 21 | +**Problem:** The Low class (~9% of data) had only ~41% recall on unleaked data, with 60% of Low entries misclassified as Medium. |
| 22 | + |
| 23 | +Four loss strategies were tested on the deduplicated split: |
| 24 | + |
| 25 | +| Mode | Low recall | Medium recall | High recall | Overall acc | |
| 26 | +|------|-----------|---------------|-------------|-------------| |
| 27 | +| Uniform (none) | 0.4099 | 0.8165 | 0.7809 | 0.7677 | |
| 28 | +| Sqrt-dampened | 0.4902 | 0.7481 | 0.8056 | 0.7457 | |
| 29 | +| Balanced | 0.6084 | 0.7024 | 0.8099 | 0.7323 | |
| 30 | +| Focal (gamma=2) | 0.6328 | 0.6441 | 0.8349 | 0.7110 | |
| 31 | + |
| 32 | +**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. |
| 33 | + |
| 34 | +A `--class-weights` flag (`none`, `sqrt`, `balanced`, `focal`) was added for future experimentation. |
| 35 | + |
| 36 | +### Per-class metrics |
| 37 | + |
| 38 | +`compute_metrics` now reports precision, recall, and F1 per class (Low/Medium/High) alongside overall accuracy and macro F1 at each evaluation epoch. |
| 39 | + |
| 40 | +### Best model checkpoint selection |
| 41 | + |
| 42 | +- `metric_for_best_model` set to `accuracy` (was defaulting to `eval_loss`) |
| 43 | +- `save_total_limit` increased from 2 to 3 to prevent the best checkpoint from being pruned |
| 44 | + |
| 45 | +### CodeCarbon tracker scoping |
| 46 | + |
| 47 | +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`. |
| 48 | + |
| 49 | +### Dynamic model card |
| 50 | + |
| 51 | +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. |
| 52 | + |
| 53 | +### Known limitations documented |
| 54 | + |
| 55 | +- Low severity recall (~41%) |
| 56 | +- Keyword dependency (accuracy drops from ~89% to ~55% on atypical-severity entries) |
| 57 | +- Negation blindness |
| 58 | +- CVE overlap (81% of CNVD entries have a CVE equivalent) |
| 59 | + |
| 60 | +## Commits |
| 61 | + |
| 62 | +| Commit | Description | |
| 63 | +|--------|-------------| |
| 64 | +| `6352273` | Data leakage fix, class-weighted loss, per-class metrics, best model selection | |
| 65 | +| `b1679fb` | CNVD severity model comparison validator | |
| 66 | +| `1fdee05` | Sqrt-dampened class weights | |
| 67 | +| `a81f90d` | Scoped codecarbon tracker (CNVD trainer) | |
| 68 | +| `65d3d88` | Removed push_to_hub from TrainingArguments | |
| 69 | +| `b7a2d6d` | Scoped codecarbon tracker (severity trainer) | |
| 70 | +| `ed2c230` | `--class-weights` flag (none/sqrt/balanced) | |
| 71 | +| `f1cd426` | Focal loss option | |
| 72 | +| `9fa0f86` | Default to uniform loss | |
| 73 | +| `7920361` | Static model card | |
| 74 | +| `5b2866c` | Dynamic model card from eval metrics | |
| 75 | + |
| 76 | +## References |
| 77 | + |
| 78 | +- Issue: [VulnTrain#19](https://github.com/vulnerability-lookup/VulnTrain/issues/19) |
| 79 | +- Model: [CIRCL/vulnerability-severity-classification-chinese-macbert-base](https://huggingface.co/CIRCL/vulnerability-severity-classification-chinese-macbert-base) |
| 80 | +- Dataset: [CIRCL/Vulnerability-CNVD](https://huggingface.co/datasets/CIRCL/Vulnerability-CNVD) |
| 81 | +- External validation: [eromang/researches/CNVD-Dataset-Validation](https://github.com/eromang/researches/tree/main/CNVD-Dataset-Validation) |
0 commit comments