Skip to content

Commit 5b2866c

Browse files
chg: Generate CNVD model card dynamically from eval metrics
Convert the static model card into a template with placeholders filled from trainer.evaluate() results after each training run. Metrics, class distribution, training hyperparameters, and model/dataset references are now always accurate regardless of when the model is retrained. Related to #19 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7920361 commit 5b2866c

2 files changed

Lines changed: 62 additions & 25 deletions

File tree

vulntrain/trainers/classify_severity_cnvd.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,17 +322,56 @@ def tokenize_function(examples):
322322
trainer.push_to_hub()
323323
tokenizer.push_to_hub(repo_id)
324324

325-
# Push model card
326-
model_card_path = Path(__file__).parent / "model_card_cnvd_severity.md"
327-
if model_card_path.exists():
325+
# Generate and push model card with actual eval metrics
326+
eval_results = trainer.evaluate()
327+
test_labels = [
328+
SEVERITY_MAPPING[ex["severity_label"]] for ex in dataset["test"]
329+
]
330+
test_samples = len(test_labels)
331+
label_counts = Counter(test_labels)
332+
333+
loss_descriptions = {
334+
"none": "uniform cross-entropy (no class weighting)",
335+
"sqrt": "class-weighted cross-entropy (sqrt-dampened)",
336+
"balanced": "class-weighted cross-entropy (balanced)",
337+
"focal": "focal loss (gamma=2.0, balanced alpha)",
338+
}
339+
340+
template_vars = {
341+
"base_model": base_model,
342+
"dataset_id": dataset_id,
343+
"repo_id": repo_id,
344+
"test_samples": f"{test_samples:,}",
345+
"accuracy": eval_results.get("eval_accuracy", 0),
346+
"f1_macro": eval_results.get("eval_f1_macro", 0),
347+
"loss_description": loss_descriptions.get(class_weights_mode, "uniform cross-entropy"),
348+
"learning_rate": training_args.learning_rate,
349+
"batch_size": training_args.per_device_train_batch_size,
350+
"num_epochs": int(training_args.num_train_epochs),
351+
}
352+
353+
for label_name, label_id in SEVERITY_MAPPING.items():
354+
count = label_counts.get(label_id, 0)
355+
template_vars[f"{label_name}_precision"] = eval_results.get(f"eval_{label_name}_precision", 0)
356+
template_vars[f"{label_name}_recall"] = eval_results.get(f"eval_{label_name}_recall", 0)
357+
template_vars[f"{label_name}_f1"] = eval_results.get(f"eval_{label_name}_f1", 0)
358+
template_vars[f"{label_name}_support"] = f"{count:,}"
359+
template_vars[f"{label_name}_pct"] = 100 * count / test_samples if test_samples else 0
360+
361+
model_card_template = Path(__file__).parent / "model_card_cnvd_severity.md"
362+
if model_card_template.exists():
328363
from huggingface_hub import HfApi
329364

365+
card_content = model_card_template.read_text().format(**template_vars)
366+
card_path = Path(model_save_dir) / "README.md"
367+
card_path.write_text(card_content)
368+
330369
api = HfApi()
331370
api.upload_file(
332-
path_or_fileobj=str(model_card_path),
371+
path_or_fileobj=str(card_path),
333372
path_in_repo="README.md",
334373
repo_id=repo_id,
335-
commit_message="Update model card with honest metrics and known limitations",
374+
commit_message="Update model card with evaluation metrics",
336375
)
337376
logger.info(f"Model card pushed to {repo_id}")
338377

vulntrain/trainers/model_card_cnvd_severity.md

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,32 @@ tags:
1111
- cnvd
1212
datasets:
1313
- CIRCL/Vulnerability-CNVD
14-
base_model: hfl/chinese-macbert-base
14+
base_model: {base_model}
1515
pipeline_tag: text-classification
1616
---
1717

1818
# VLAI: Automated Vulnerability Severity Classification (Chinese Text)
1919

20-
A fine-tuned [hfl/chinese-macbert-base](https://huggingface.co/hfl/chinese-macbert-base) model for classifying Chinese vulnerability descriptions from the [China National Vulnerability Database (CNVD)](https://www.cnvd.org.cn/) into three severity levels: **Low**, **Medium**, and **High**.
20+
A fine-tuned [{base_model}](https://huggingface.co/{base_model}) model for classifying Chinese vulnerability descriptions from the [China National Vulnerability Database (CNVD)](https://www.cnvd.org.cn/) into three severity levels: **Low**, **Medium**, and **High**.
2121

22-
Trained on the [CIRCL/Vulnerability-CNVD](https://huggingface.co/datasets/CIRCL/Vulnerability-CNVD) dataset as part of the [VulnTrain](https://github.com/vulnerability-lookup/VulnTrain) project.
22+
Trained on the [{dataset_id}](https://huggingface.co/datasets/{dataset_id}) dataset as part of the [VulnTrain](https://github.com/vulnerability-lookup/VulnTrain) project.
2323

2424
## Evaluation results
2525

26-
Evaluated on a **deduplicated test set** (25,845 samples) where no description text appears in both train and test splits, preventing data leakage from CNVD's reuse of boilerplate descriptions across different vulnerability IDs.
26+
Evaluated on a **deduplicated test set** ({test_samples} samples) where no description text appears in both train and test splits, preventing data leakage from CNVD's reuse of boilerplate descriptions across different vulnerability IDs.
2727

2828
| Class | Precision | Recall | F1-score | Support |
2929
|--------|-----------|--------|----------|---------|
30-
| Low | 0.5968 | 0.4099 | 0.4860 | 2,293 |
31-
| Medium | 0.7867 | 0.8165 | 0.8013 | 14,351 |
32-
| High | 0.7662 | 0.7809 | 0.7735 | 9,201 |
30+
| Low | {Low_precision:.4f} | {Low_recall:.4f} | {Low_f1:.4f} | {Low_support} |
31+
| Medium | {Medium_precision:.4f} | {Medium_recall:.4f} | {Medium_f1:.4f} | {Medium_support} |
32+
| High | {High_precision:.4f} | {High_recall:.4f} | {High_f1:.4f} | {High_support} |
3333

34-
- **Overall accuracy**: 76.8%
35-
- **Macro F1**: 0.6870
36-
- **Weighted F1**: 0.7634
34+
- **Overall accuracy**: {accuracy:.2%}
35+
- **Macro F1**: {f1_macro:.4f}
3736

3837
### Class distribution
3938

40-
The dataset is imbalanced: Low (8.9%), Medium (55.5%), High (35.6%).
39+
The dataset is imbalanced: Low ({Low_pct:.1f}%), Medium ({Medium_pct:.1f}%), High ({High_pct:.1f}%).
4140

4241
## Usage
4342

@@ -46,18 +45,17 @@ from transformers import pipeline
4645

4746
classifier = pipeline(
4847
"text-classification",
49-
model="CIRCL/vulnerability-severity-classification-chinese-macbert-base"
48+
model="{repo_id}"
5049
)
5150

5251
description = "TOTOLINK A3600R存在缓冲区溢出漏洞,攻击者可利用该漏洞在系统上执行任意代码或者导致拒绝服务。"
5352
result = classifier(description)
5453
print(result)
55-
# [{'label': 'High', 'score': 0.98}]
5654
```
5755

5856
## Known limitations
5957

60-
- **Low severity recall is ~41%**: approximately 60% of Low-severity entries are misclassified, mostly as Medium. This reflects the vocabulary overlap between Low and Medium descriptions in CNVD data. Class-weighted loss and focal loss were tested but all degraded Medium recall disproportionately without a net benefit.
58+
- **Low severity recall**: the Low class has the lowest recall. Approximately 60% of Low-severity entries are misclassified, mostly as Medium. This reflects the vocabulary overlap between Low and Medium descriptions in CNVD data. Class-weighted loss and focal loss were tested but all degraded Medium recall disproportionately without a net benefit.
6159

6260
- **Keyword dependency**: the model biases toward a vulnerability type's typical severity. For example, buffer overflow descriptions are predicted as High regardless of the actual assigned severity. On entries where the actual severity deviates from the type's typical severity, accuracy drops from ~89% to ~55%.
6361

@@ -69,13 +67,13 @@ These limitations were identified through independent analysis in [VulnTrain#19]
6967

7068
## Training details
7169

72-
- **Base model**: [hfl/chinese-macbert-base](https://huggingface.co/hfl/chinese-macbert-base)
73-
- **Dataset**: [CIRCL/Vulnerability-CNVD](https://huggingface.co/datasets/CIRCL/Vulnerability-CNVD)
70+
- **Base model**: [{base_model}](https://huggingface.co/{base_model})
71+
- **Dataset**: [{dataset_id}](https://huggingface.co/datasets/{dataset_id})
7472
- **Train/test split**: deduplicated on description text (no leakage), 80/20 split
75-
- **Loss**: uniform cross-entropy (no class weighting)
76-
- **Learning rate**: 3e-05
77-
- **Batch size**: 16
78-
- **Epochs**: 5
73+
- **Loss**: {loss_description}
74+
- **Learning rate**: {learning_rate}
75+
- **Batch size**: {batch_size}
76+
- **Epochs**: {num_epochs}
7977
- **Best model selection**: by accuracy
8078

8179
## References

0 commit comments

Comments
 (0)