Upload DeBERTa-v3-large PII models, tokenizers, and code
Browse files- README.md +33 -0
- added_tokens.json +3 -0
- code/inference-single-model-no-pp-privatelb-0-963.ipynb +1677 -0
- code/inference-single-model-no-pp-privatelb-0-963.py +264 -0
- code/inference_actual_orig.py +172 -0
- code/kaggle_upload.py +21 -0
- code/submission.csv +36 -0
- code/training-actualorig-withotherdata.py +446 -0
- code/training-single-model-to-get-privatelb-0-963.ipynb +0 -0
- code/training-single-model-to-get-privatelb-0-963_actualorig.py +446 -0
- code/training-single-model-to-get-privatelb-0-963_orig.py +594 -0
- code/training-single-model-v8.py +572 -0
- code/training-single-model-v9.py +564 -0
- code/training-subm-moredata.py +531 -0
- config.json +62 -0
- deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_best.pth +3 -0
- deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_last.pth +3 -0
- special_tokens_map.json +15 -0
- spm.model +3 -0
- tokenizer.json +0 -0
- tokenizer_config.json +58 -0
README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
tags:
|
| 4 |
+
- deberta
|
| 5 |
+
- pii-detection
|
| 6 |
+
- token-classification
|
| 7 |
+
- kaggle
|
| 8 |
+
datasets:
|
| 9 |
+
- pii-data-detection
|
| 10 |
+
metrics:
|
| 11 |
+
- f5-score
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# DeBERTa-v3-large PII Data Detection (Final Model)
|
| 15 |
+
|
| 16 |
+
This repository contains the fine-tuned DeBERTa-v3-large model checkpoint from the Kaggle PII Data Detection Competition.
|
| 17 |
+
|
| 18 |
+
## Model Details
|
| 19 |
+
- **Base Architecture**: microsoft/deberta-v3-large
|
| 20 |
+
- **Task**: Token classification for Personally Identifiable Information (PII) detection
|
| 21 |
+
- **Max Sequence Length**: 4096
|
| 22 |
+
- **Training Setup**:
|
| 23 |
+
- Competition training data + moredata_dataset_fixed.json
|
| 24 |
+
- Optimizer: AdamW, lr=1e-5, weight_decay=0.0
|
| 25 |
+
- Batch size: 2
|
| 26 |
+
- Epochs: 5 (Fold 0)
|
| 27 |
+
- **Validation Metric**: **0.9692** at Epoch 3
|
| 28 |
+
|
| 29 |
+
## Checkpoints Included
|
| 30 |
+
- `deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_best.pth` (1.74 GB): Model weights with highest validation score (0.9692).
|
| 31 |
+
- `deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_last.pth` (5.21 GB): Full training checkpoint including optimizer, scheduler, and scaler states.
|
| 32 |
+
- Tokenizer files (`spm.model`, `tokenizer.json`, `config.json`, etc.)
|
| 33 |
+
- Training and inference code under `code/`.
|
added_tokens.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"[MASK]": 128000
|
| 3 |
+
}
|
code/inference-single-model-no-pp-privatelb-0-963.ipynb
ADDED
|
@@ -0,0 +1,1677 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "8020243e",
|
| 6 |
+
"metadata": {
|
| 7 |
+
"papermill": {
|
| 8 |
+
"duration": 0.003262,
|
| 9 |
+
"end_time": "2025-04-30T15:27:34.703550",
|
| 10 |
+
"exception": false,
|
| 11 |
+
"start_time": "2025-04-30T15:27:34.700288",
|
| 12 |
+
"status": "completed"
|
| 13 |
+
},
|
| 14 |
+
"tags": []
|
| 15 |
+
},
|
| 16 |
+
"source": [
|
| 17 |
+
"## Introduction\n",
|
| 18 |
+
"\n",
|
| 19 |
+
"Congratulations to everyone who persevered through this challenging competition!\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"I've been eagerly looking forward to reading the top solutions, and now that the competition has finally concluded, I am thrilled.\n",
|
| 22 |
+
"\n",
|
| 23 |
+
"It has been a great learning journey for all of us. I am excited to share that I plan to publish a training notebook that demonstrates how to achieve a score of `Private Leaderboard 0.963` using a single model.\n",
|
| 24 |
+
"\n",
|
| 25 |
+
"[Training Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/training-single-model-to-get-privatelb-0-963/)\n",
|
| 26 |
+
"\n",
|
| 27 |
+
"And this notebook shows how to submit the model trained above.\n",
|
| 28 |
+
"\n",
|
| 29 |
+
"\n",
|
| 30 |
+
"## Acknowledge\n",
|
| 31 |
+
"\n",
|
| 32 |
+
"I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.\n",
|
| 33 |
+
"\n",
|
| 34 |
+
"For training a single model the above notebook shows, it takes less than 40min!"
|
| 35 |
+
]
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"cell_type": "code",
|
| 39 |
+
"execution_count": 1,
|
| 40 |
+
"id": "4e44c5cf",
|
| 41 |
+
"metadata": {
|
| 42 |
+
"_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19",
|
| 43 |
+
"_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5",
|
| 44 |
+
"execution": {
|
| 45 |
+
"iopub.execute_input": "2025-04-30T15:27:34.710901Z",
|
| 46 |
+
"iopub.status.busy": "2025-04-30T15:27:34.710600Z",
|
| 47 |
+
"iopub.status.idle": "2025-04-30T15:27:56.410746Z",
|
| 48 |
+
"shell.execute_reply": "2025-04-30T15:27:56.410065Z"
|
| 49 |
+
},
|
| 50 |
+
"papermill": {
|
| 51 |
+
"duration": 21.706244,
|
| 52 |
+
"end_time": "2025-04-30T15:27:56.412829",
|
| 53 |
+
"exception": false,
|
| 54 |
+
"start_time": "2025-04-30T15:27:34.706585",
|
| 55 |
+
"status": "completed"
|
| 56 |
+
},
|
| 57 |
+
"tags": []
|
| 58 |
+
},
|
| 59 |
+
"outputs": [
|
| 60 |
+
{
|
| 61 |
+
"name": "stderr",
|
| 62 |
+
"output_type": "stream",
|
| 63 |
+
"text": [
|
| 64 |
+
"2025-04-30 15:27:44.649168: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:9261] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n",
|
| 65 |
+
"2025-04-30 15:27:44.649298: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:607] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n",
|
| 66 |
+
"2025-04-30 15:27:44.786214: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1515] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n"
|
| 67 |
+
]
|
| 68 |
+
}
|
| 69 |
+
],
|
| 70 |
+
"source": [
|
| 71 |
+
"import re\n",
|
| 72 |
+
"import os\n",
|
| 73 |
+
"import json\n",
|
| 74 |
+
"import torch\n",
|
| 75 |
+
"import argparse\n",
|
| 76 |
+
"from pathlib import Path\n",
|
| 77 |
+
"from itertools import chain\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"import numpy as np\n",
|
| 80 |
+
"import pandas as pd\n",
|
| 81 |
+
"from transformers import AutoTokenizer, AutoModelForTokenClassification, AutoModel, AutoConfig, DataCollatorForTokenClassification\n",
|
| 82 |
+
"from datasets import Dataset\n",
|
| 83 |
+
"from spacy.lang.en import English\n",
|
| 84 |
+
"\n",
|
| 85 |
+
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')"
|
| 86 |
+
]
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"cell_type": "code",
|
| 90 |
+
"execution_count": 2,
|
| 91 |
+
"id": "d7fc24ad",
|
| 92 |
+
"metadata": {
|
| 93 |
+
"execution": {
|
| 94 |
+
"iopub.execute_input": "2025-04-30T15:27:56.421309Z",
|
| 95 |
+
"iopub.status.busy": "2025-04-30T15:27:56.420333Z",
|
| 96 |
+
"iopub.status.idle": "2025-04-30T15:27:57.444502Z",
|
| 97 |
+
"shell.execute_reply": "2025-04-30T15:27:57.443587Z"
|
| 98 |
+
},
|
| 99 |
+
"papermill": {
|
| 100 |
+
"duration": 1.030446,
|
| 101 |
+
"end_time": "2025-04-30T15:27:57.446580",
|
| 102 |
+
"exception": false,
|
| 103 |
+
"start_time": "2025-04-30T15:27:56.416134",
|
| 104 |
+
"status": "completed"
|
| 105 |
+
},
|
| 106 |
+
"tags": []
|
| 107 |
+
},
|
| 108 |
+
"outputs": [
|
| 109 |
+
{
|
| 110 |
+
"name": "stdout",
|
| 111 |
+
"output_type": "stream",
|
| 112 |
+
"text": [
|
| 113 |
+
"pii-dbtv3-large-9688 pii-detection-removal-from-educational-data\r\n"
|
| 114 |
+
]
|
| 115 |
+
}
|
| 116 |
+
],
|
| 117 |
+
"source": [
|
| 118 |
+
"!ls ../input/"
|
| 119 |
+
]
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"cell_type": "code",
|
| 123 |
+
"execution_count": 3,
|
| 124 |
+
"id": "dea14c4c",
|
| 125 |
+
"metadata": {
|
| 126 |
+
"execution": {
|
| 127 |
+
"iopub.execute_input": "2025-04-30T15:27:57.454606Z",
|
| 128 |
+
"iopub.status.busy": "2025-04-30T15:27:57.454035Z",
|
| 129 |
+
"iopub.status.idle": "2025-04-30T15:27:57.459265Z",
|
| 130 |
+
"shell.execute_reply": "2025-04-30T15:27:57.458458Z"
|
| 131 |
+
},
|
| 132 |
+
"papermill": {
|
| 133 |
+
"duration": 0.011012,
|
| 134 |
+
"end_time": "2025-04-30T15:27:57.460840",
|
| 135 |
+
"exception": false,
|
| 136 |
+
"start_time": "2025-04-30T15:27:57.449828",
|
| 137 |
+
"status": "completed"
|
| 138 |
+
},
|
| 139 |
+
"tags": []
|
| 140 |
+
},
|
| 141 |
+
"outputs": [],
|
| 142 |
+
"source": [
|
| 143 |
+
"backbone = 'microsoft/deberta-v3-large'\n",
|
| 144 |
+
"model_dir = '/kaggle/input/pii-dbtv3-large-9688'\n",
|
| 145 |
+
"th = 0.5\n",
|
| 146 |
+
"\n",
|
| 147 |
+
"id2label = {0: 'B-EMAIL', 1: 'B-ID_NUM', 2: 'B-NAME_STUDENT', 3: 'B-PHONE_NUM', 4: 'B-STREET_ADDRESS', 5: 'B-URL_PERSONAL', 6: 'B-USERNAME', 7: 'I-ID_NUM', 8: 'I-NAME_STUDENT', 9: 'I-PHONE_NUM', 10: 'I-STREET_ADDRESS', 11: 'I-URL_PERSONAL', 12: 'O'}"
|
| 148 |
+
]
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"cell_type": "code",
|
| 152 |
+
"execution_count": 4,
|
| 153 |
+
"id": "e1da943a",
|
| 154 |
+
"metadata": {
|
| 155 |
+
"execution": {
|
| 156 |
+
"iopub.execute_input": "2025-04-30T15:27:57.468276Z",
|
| 157 |
+
"iopub.status.busy": "2025-04-30T15:27:57.467797Z",
|
| 158 |
+
"iopub.status.idle": "2025-04-30T15:27:57.473293Z",
|
| 159 |
+
"shell.execute_reply": "2025-04-30T15:27:57.472311Z"
|
| 160 |
+
},
|
| 161 |
+
"papermill": {
|
| 162 |
+
"duration": 0.011058,
|
| 163 |
+
"end_time": "2025-04-30T15:27:57.474961",
|
| 164 |
+
"exception": false,
|
| 165 |
+
"start_time": "2025-04-30T15:27:57.463903",
|
| 166 |
+
"status": "completed"
|
| 167 |
+
},
|
| 168 |
+
"tags": []
|
| 169 |
+
},
|
| 170 |
+
"outputs": [],
|
| 171 |
+
"source": [
|
| 172 |
+
"def tokenize(example, tokenizer):\n",
|
| 173 |
+
" text = []\n",
|
| 174 |
+
" token_map = []\n",
|
| 175 |
+
" \n",
|
| 176 |
+
" idx = 0\n",
|
| 177 |
+
" \n",
|
| 178 |
+
" for t, ws in zip(example[\"tokens\"], example[\"trailing_whitespace\"]):\n",
|
| 179 |
+
" \n",
|
| 180 |
+
" text.append(t)\n",
|
| 181 |
+
" token_map.extend([idx]*len(t))\n",
|
| 182 |
+
" if ws:\n",
|
| 183 |
+
" text.append(\" \")\n",
|
| 184 |
+
" token_map.append(-1)\n",
|
| 185 |
+
" \n",
|
| 186 |
+
" idx += 1\n",
|
| 187 |
+
" \n",
|
| 188 |
+
" \n",
|
| 189 |
+
" tokenized = tokenizer(\"\".join(text), return_offsets_mapping=True, truncation=False)\n",
|
| 190 |
+
" \n",
|
| 191 |
+
" \n",
|
| 192 |
+
" return {\n",
|
| 193 |
+
" **tokenized,\n",
|
| 194 |
+
" \"token_map\": token_map,\n",
|
| 195 |
+
" }"
|
| 196 |
+
]
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"cell_type": "code",
|
| 200 |
+
"execution_count": 5,
|
| 201 |
+
"id": "e9da25fc",
|
| 202 |
+
"metadata": {
|
| 203 |
+
"execution": {
|
| 204 |
+
"iopub.execute_input": "2025-04-30T15:27:57.482197Z",
|
| 205 |
+
"iopub.status.busy": "2025-04-30T15:27:57.481934Z",
|
| 206 |
+
"iopub.status.idle": "2025-04-30T15:27:58.693956Z",
|
| 207 |
+
"shell.execute_reply": "2025-04-30T15:27:58.693020Z"
|
| 208 |
+
},
|
| 209 |
+
"papermill": {
|
| 210 |
+
"duration": 1.218282,
|
| 211 |
+
"end_time": "2025-04-30T15:27:58.696470",
|
| 212 |
+
"exception": false,
|
| 213 |
+
"start_time": "2025-04-30T15:27:57.478188",
|
| 214 |
+
"status": "completed"
|
| 215 |
+
},
|
| 216 |
+
"tags": []
|
| 217 |
+
},
|
| 218 |
+
"outputs": [
|
| 219 |
+
{
|
| 220 |
+
"name": "stdout",
|
| 221 |
+
"output_type": "stream",
|
| 222 |
+
"text": [
|
| 223 |
+
" "
|
| 224 |
+
]
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"data": {
|
| 228 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 229 |
+
"model_id": "5951514148bd4f938c2559866f91be40",
|
| 230 |
+
"version_major": 2,
|
| 231 |
+
"version_minor": 0
|
| 232 |
+
},
|
| 233 |
+
"text/plain": [
|
| 234 |
+
"#0: 0%| | 0/5 [00:00<?, ?ex/s]"
|
| 235 |
+
]
|
| 236 |
+
},
|
| 237 |
+
"metadata": {},
|
| 238 |
+
"output_type": "display_data"
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"data": {
|
| 242 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 243 |
+
"model_id": "88430d270ddf4976af32c09d1afb0ad8",
|
| 244 |
+
"version_major": 2,
|
| 245 |
+
"version_minor": 0
|
| 246 |
+
},
|
| 247 |
+
"text/plain": [
|
| 248 |
+
"#1: 0%| | 0/5 [00:00<?, ?ex/s]"
|
| 249 |
+
]
|
| 250 |
+
},
|
| 251 |
+
"metadata": {},
|
| 252 |
+
"output_type": "display_data"
|
| 253 |
+
}
|
| 254 |
+
],
|
| 255 |
+
"source": [
|
| 256 |
+
"with open(\"/kaggle/input/pii-detection-removal-from-educational-data/test.json\", \"r\") as f:\n",
|
| 257 |
+
" data = json.load(f)\n",
|
| 258 |
+
"\n",
|
| 259 |
+
"ds = Dataset.from_dict({\n",
|
| 260 |
+
" \"full_text\": [x[\"full_text\"] for x in data],\n",
|
| 261 |
+
" \"document\": [x[\"document\"] for x in data],\n",
|
| 262 |
+
" \"tokens\": [x[\"tokens\"] for x in data],\n",
|
| 263 |
+
" \"trailing_whitespace\": [x[\"trailing_whitespace\"] for x in data],\n",
|
| 264 |
+
"})\n",
|
| 265 |
+
"\n",
|
| 266 |
+
"tokenizer = AutoTokenizer.from_pretrained(os.path.join(model_dir, 'fold0'))\n",
|
| 267 |
+
"collator = DataCollatorForTokenClassification(tokenizer)\n",
|
| 268 |
+
"ds = ds.map(tokenize, fn_kwargs={\"tokenizer\": tokenizer}, num_proc=2)"
|
| 269 |
+
]
|
| 270 |
+
},
|
| 271 |
+
{
|
| 272 |
+
"cell_type": "code",
|
| 273 |
+
"execution_count": 6,
|
| 274 |
+
"id": "babea7d6",
|
| 275 |
+
"metadata": {
|
| 276 |
+
"execution": {
|
| 277 |
+
"iopub.execute_input": "2025-04-30T15:27:58.705593Z",
|
| 278 |
+
"iopub.status.busy": "2025-04-30T15:27:58.704818Z",
|
| 279 |
+
"iopub.status.idle": "2025-04-30T15:27:58.716765Z",
|
| 280 |
+
"shell.execute_reply": "2025-04-30T15:27:58.715954Z"
|
| 281 |
+
},
|
| 282 |
+
"papermill": {
|
| 283 |
+
"duration": 0.018147,
|
| 284 |
+
"end_time": "2025-04-30T15:27:58.718406",
|
| 285 |
+
"exception": false,
|
| 286 |
+
"start_time": "2025-04-30T15:27:58.700259",
|
| 287 |
+
"status": "completed"
|
| 288 |
+
},
|
| 289 |
+
"tags": []
|
| 290 |
+
},
|
| 291 |
+
"outputs": [
|
| 292 |
+
{
|
| 293 |
+
"data": {
|
| 294 |
+
"text/plain": [
|
| 295 |
+
"dict_keys(['full_text', 'document', 'tokens', 'trailing_whitespace', 'input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'token_map'])"
|
| 296 |
+
]
|
| 297 |
+
},
|
| 298 |
+
"execution_count": 6,
|
| 299 |
+
"metadata": {},
|
| 300 |
+
"output_type": "execute_result"
|
| 301 |
+
}
|
| 302 |
+
],
|
| 303 |
+
"source": [
|
| 304 |
+
"ds[0].keys()"
|
| 305 |
+
]
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"cell_type": "code",
|
| 309 |
+
"execution_count": 7,
|
| 310 |
+
"id": "e8da3b76",
|
| 311 |
+
"metadata": {
|
| 312 |
+
"execution": {
|
| 313 |
+
"iopub.execute_input": "2025-04-30T15:27:58.727230Z",
|
| 314 |
+
"iopub.status.busy": "2025-04-30T15:27:58.726677Z",
|
| 315 |
+
"iopub.status.idle": "2025-04-30T15:28:24.372489Z",
|
| 316 |
+
"shell.execute_reply": "2025-04-30T15:28:24.371483Z"
|
| 317 |
+
},
|
| 318 |
+
"papermill": {
|
| 319 |
+
"duration": 25.652138,
|
| 320 |
+
"end_time": "2025-04-30T15:28:24.374509",
|
| 321 |
+
"exception": false,
|
| 322 |
+
"start_time": "2025-04-30T15:27:58.722371",
|
| 323 |
+
"status": "completed"
|
| 324 |
+
},
|
| 325 |
+
"tags": []
|
| 326 |
+
},
|
| 327 |
+
"outputs": [
|
| 328 |
+
{
|
| 329 |
+
"data": {
|
| 330 |
+
"text/plain": [
|
| 331 |
+
"DebertaV2ForTokenClassification(\n",
|
| 332 |
+
" (deberta): DebertaV2Model(\n",
|
| 333 |
+
" (embeddings): DebertaV2Embeddings(\n",
|
| 334 |
+
" (word_embeddings): Embedding(128100, 1024, padding_idx=0)\n",
|
| 335 |
+
" (LayerNorm): LayerNorm((1024,), eps=1e-07, elementwise_affine=True)\n",
|
| 336 |
+
" (dropout): StableDropout()\n",
|
| 337 |
+
" )\n",
|
| 338 |
+
" (encoder): DebertaV2Encoder(\n",
|
| 339 |
+
" (layer): ModuleList(\n",
|
| 340 |
+
" (0-23): 24 x DebertaV2Layer(\n",
|
| 341 |
+
" (attention): DebertaV2Attention(\n",
|
| 342 |
+
" (self): DisentangledSelfAttention(\n",
|
| 343 |
+
" (query_proj): Linear(in_features=1024, out_features=1024, bias=True)\n",
|
| 344 |
+
" (key_proj): Linear(in_features=1024, out_features=1024, bias=True)\n",
|
| 345 |
+
" (value_proj): Linear(in_features=1024, out_features=1024, bias=True)\n",
|
| 346 |
+
" (pos_dropout): StableDropout()\n",
|
| 347 |
+
" (dropout): StableDropout()\n",
|
| 348 |
+
" )\n",
|
| 349 |
+
" (output): DebertaV2SelfOutput(\n",
|
| 350 |
+
" (dense): Linear(in_features=1024, out_features=1024, bias=True)\n",
|
| 351 |
+
" (LayerNorm): LayerNorm((1024,), eps=1e-07, elementwise_affine=True)\n",
|
| 352 |
+
" (dropout): StableDropout()\n",
|
| 353 |
+
" )\n",
|
| 354 |
+
" )\n",
|
| 355 |
+
" (intermediate): DebertaV2Intermediate(\n",
|
| 356 |
+
" (dense): Linear(in_features=1024, out_features=4096, bias=True)\n",
|
| 357 |
+
" (intermediate_act_fn): GELUActivation()\n",
|
| 358 |
+
" )\n",
|
| 359 |
+
" (output): DebertaV2Output(\n",
|
| 360 |
+
" (dense): Linear(in_features=4096, out_features=1024, bias=True)\n",
|
| 361 |
+
" (LayerNorm): LayerNorm((1024,), eps=1e-07, elementwise_affine=True)\n",
|
| 362 |
+
" (dropout): StableDropout()\n",
|
| 363 |
+
" )\n",
|
| 364 |
+
" )\n",
|
| 365 |
+
" )\n",
|
| 366 |
+
" (rel_embeddings): Embedding(512, 1024)\n",
|
| 367 |
+
" (LayerNorm): LayerNorm((1024,), eps=1e-07, elementwise_affine=True)\n",
|
| 368 |
+
" )\n",
|
| 369 |
+
" )\n",
|
| 370 |
+
" (dropout): Dropout(p=0.1, inplace=False)\n",
|
| 371 |
+
" (classifier): Linear(in_features=1024, out_features=13, bias=True)\n",
|
| 372 |
+
")"
|
| 373 |
+
]
|
| 374 |
+
},
|
| 375 |
+
"execution_count": 7,
|
| 376 |
+
"metadata": {},
|
| 377 |
+
"output_type": "execute_result"
|
| 378 |
+
}
|
| 379 |
+
],
|
| 380 |
+
"source": [
|
| 381 |
+
"def get_model(conf):\n",
|
| 382 |
+
" return AutoModelForTokenClassification.from_config(\n",
|
| 383 |
+
" conf\n",
|
| 384 |
+
" )\n",
|
| 385 |
+
"\n",
|
| 386 |
+
"fold = 0\n",
|
| 387 |
+
"conf = AutoConfig.from_pretrained(os.path.join(model_dir, f'fold{fold}'))\n",
|
| 388 |
+
"model = get_model(conf)\n",
|
| 389 |
+
"model_file = os.path.join(model_dir, f'fold{fold}', f'genv1_noext1_ds3_ht1024_dbtv3_large_wu0.1_w10_ado4_wd0_bs2_lr1e5_5f_5epo_fold{fold}_best.pth')\n",
|
| 390 |
+
"sd = torch.load(model_file, map_location='cpu')\n",
|
| 391 |
+
"model.load_state_dict(sd, strict=True)\n",
|
| 392 |
+
"model.eval()\n",
|
| 393 |
+
"model.cuda()\n"
|
| 394 |
+
]
|
| 395 |
+
},
|
| 396 |
+
{
|
| 397 |
+
"cell_type": "code",
|
| 398 |
+
"execution_count": 8,
|
| 399 |
+
"id": "d6469695",
|
| 400 |
+
"metadata": {
|
| 401 |
+
"execution": {
|
| 402 |
+
"iopub.execute_input": "2025-04-30T15:28:24.383514Z",
|
| 403 |
+
"iopub.status.busy": "2025-04-30T15:28:24.382837Z",
|
| 404 |
+
"iopub.status.idle": "2025-04-30T15:28:25.508686Z",
|
| 405 |
+
"shell.execute_reply": "2025-04-30T15:28:25.507737Z"
|
| 406 |
+
},
|
| 407 |
+
"papermill": {
|
| 408 |
+
"duration": 1.132525,
|
| 409 |
+
"end_time": "2025-04-30T15:28:25.510805",
|
| 410 |
+
"exception": false,
|
| 411 |
+
"start_time": "2025-04-30T15:28:24.378280",
|
| 412 |
+
"status": "completed"
|
| 413 |
+
},
|
| 414 |
+
"tags": []
|
| 415 |
+
},
|
| 416 |
+
"outputs": [
|
| 417 |
+
{
|
| 418 |
+
"name": "stdout",
|
| 419 |
+
"output_type": "stream",
|
| 420 |
+
"text": [
|
| 421 |
+
"Wed Apr 30 15:28:25 2025 \r\n",
|
| 422 |
+
"+-----------------------------------------------------------------------------------------+\r\n",
|
| 423 |
+
"| NVIDIA-SMI 560.35.03 Driver Version: 560.35.03 CUDA Version: 12.6 |\r\n",
|
| 424 |
+
"|-----------------------------------------+------------------------+----------------------+\r\n",
|
| 425 |
+
"| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |\r\n",
|
| 426 |
+
"| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |\r\n",
|
| 427 |
+
"| | | MIG M. |\r\n",
|
| 428 |
+
"|=========================================+========================+======================|\r\n",
|
| 429 |
+
"| 0 Tesla P100-PCIE-16GB Off | 00000000:00:04.0 Off | 0 |\r\n",
|
| 430 |
+
"| N/A 38C P0 31W / 250W | 1933MiB / 16384MiB | 0% Default |\r\n",
|
| 431 |
+
"| | | N/A |\r\n",
|
| 432 |
+
"+-----------------------------------------+------------------------+----------------------+\r\n",
|
| 433 |
+
" \r\n",
|
| 434 |
+
"+-----------------------------------------------------------------------------------------+\r\n",
|
| 435 |
+
"| Processes: |\r\n",
|
| 436 |
+
"| GPU GI CI PID Type Process name GPU Memory |\r\n",
|
| 437 |
+
"| ID ID Usage |\r\n",
|
| 438 |
+
"|=========================================================================================|\r\n",
|
| 439 |
+
"+-----------------------------------------------------------------------------------------+\r\n"
|
| 440 |
+
]
|
| 441 |
+
}
|
| 442 |
+
],
|
| 443 |
+
"source": [
|
| 444 |
+
"!nvidia-smi"
|
| 445 |
+
]
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"cell_type": "code",
|
| 449 |
+
"execution_count": 9,
|
| 450 |
+
"id": "22f8828a",
|
| 451 |
+
"metadata": {
|
| 452 |
+
"execution": {
|
| 453 |
+
"iopub.execute_input": "2025-04-30T15:28:25.520396Z",
|
| 454 |
+
"iopub.status.busy": "2025-04-30T15:28:25.519800Z",
|
| 455 |
+
"iopub.status.idle": "2025-04-30T15:28:28.012067Z",
|
| 456 |
+
"shell.execute_reply": "2025-04-30T15:28:28.011227Z"
|
| 457 |
+
},
|
| 458 |
+
"papermill": {
|
| 459 |
+
"duration": 2.499221,
|
| 460 |
+
"end_time": "2025-04-30T15:28:28.014108",
|
| 461 |
+
"exception": false,
|
| 462 |
+
"start_time": "2025-04-30T15:28:25.514887",
|
| 463 |
+
"status": "completed"
|
| 464 |
+
},
|
| 465 |
+
"tags": []
|
| 466 |
+
},
|
| 467 |
+
"outputs": [],
|
| 468 |
+
"source": [
|
| 469 |
+
"preds_final = []\n",
|
| 470 |
+
"with torch.no_grad():\n",
|
| 471 |
+
" for sample in ds:\n",
|
| 472 |
+
" input_ids = torch.tensor([sample['input_ids']]).to(device)\n",
|
| 473 |
+
" attention_mask = torch.tensor([sample['attention_mask']]).to(device)\n",
|
| 474 |
+
"\n",
|
| 475 |
+
" preds = model(input_ids, attention_mask=attention_mask, labels=None)['logits'].softmax(2)\n",
|
| 476 |
+
" preds[:, :, -1][preds[:, :, -1] < th] = 0\n",
|
| 477 |
+
" preds = torch.argmax(preds, axis=2).cpu().numpy()\n",
|
| 478 |
+
" preds_final.append(preds[0])\n"
|
| 479 |
+
]
|
| 480 |
+
},
|
| 481 |
+
{
|
| 482 |
+
"cell_type": "code",
|
| 483 |
+
"execution_count": 10,
|
| 484 |
+
"id": "9c2a0aed",
|
| 485 |
+
"metadata": {
|
| 486 |
+
"execution": {
|
| 487 |
+
"iopub.execute_input": "2025-04-30T15:28:28.022790Z",
|
| 488 |
+
"iopub.status.busy": "2025-04-30T15:28:28.022520Z",
|
| 489 |
+
"iopub.status.idle": "2025-04-30T15:28:29.163583Z",
|
| 490 |
+
"shell.execute_reply": "2025-04-30T15:28:29.162604Z"
|
| 491 |
+
},
|
| 492 |
+
"papermill": {
|
| 493 |
+
"duration": 1.147856,
|
| 494 |
+
"end_time": "2025-04-30T15:28:29.165845",
|
| 495 |
+
"exception": false,
|
| 496 |
+
"start_time": "2025-04-30T15:28:28.017989",
|
| 497 |
+
"status": "completed"
|
| 498 |
+
},
|
| 499 |
+
"tags": []
|
| 500 |
+
},
|
| 501 |
+
"outputs": [
|
| 502 |
+
{
|
| 503 |
+
"name": "stdout",
|
| 504 |
+
"output_type": "stream",
|
| 505 |
+
"text": [
|
| 506 |
+
"Wed Apr 30 15:28:29 2025 \r\n",
|
| 507 |
+
"+-----------------------------------------------------------------------------------------+\r\n",
|
| 508 |
+
"| NVIDIA-SMI 560.35.03 Driver Version: 560.35.03 CUDA Version: 12.6 |\r\n",
|
| 509 |
+
"|-----------------------------------------+------------------------+----------------------+\r\n",
|
| 510 |
+
"| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |\r\n",
|
| 511 |
+
"| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |\r\n",
|
| 512 |
+
"| | | MIG M. |\r\n",
|
| 513 |
+
"|=========================================+========================+======================|\r\n",
|
| 514 |
+
"| 0 Tesla P100-PCIE-16GB Off | 00000000:00:04.0 Off | 0 |\r\n",
|
| 515 |
+
"| N/A 39C P0 36W / 250W | 4065MiB / 16384MiB | 0% Default |\r\n",
|
| 516 |
+
"| | | N/A |\r\n",
|
| 517 |
+
"+-----------------------------------------+------------------------+----------------------+\r\n",
|
| 518 |
+
" \r\n",
|
| 519 |
+
"+-----------------------------------------------------------------------------------------+\r\n",
|
| 520 |
+
"| Processes: |\r\n",
|
| 521 |
+
"| GPU GI CI PID Type Process name GPU Memory |\r\n",
|
| 522 |
+
"| ID ID Usage |\r\n",
|
| 523 |
+
"|=========================================================================================|\r\n",
|
| 524 |
+
"+-----------------------------------------------------------------------------------------+\r\n"
|
| 525 |
+
]
|
| 526 |
+
}
|
| 527 |
+
],
|
| 528 |
+
"source": [
|
| 529 |
+
"!nvidia-smi"
|
| 530 |
+
]
|
| 531 |
+
},
|
| 532 |
+
{
|
| 533 |
+
"cell_type": "code",
|
| 534 |
+
"execution_count": 11,
|
| 535 |
+
"id": "710d5fa1",
|
| 536 |
+
"metadata": {
|
| 537 |
+
"execution": {
|
| 538 |
+
"iopub.execute_input": "2025-04-30T15:28:29.175351Z",
|
| 539 |
+
"iopub.status.busy": "2025-04-30T15:28:29.174744Z",
|
| 540 |
+
"iopub.status.idle": "2025-04-30T15:28:29.229118Z",
|
| 541 |
+
"shell.execute_reply": "2025-04-30T15:28:29.228370Z"
|
| 542 |
+
},
|
| 543 |
+
"papermill": {
|
| 544 |
+
"duration": 0.061412,
|
| 545 |
+
"end_time": "2025-04-30T15:28:29.231214",
|
| 546 |
+
"exception": false,
|
| 547 |
+
"start_time": "2025-04-30T15:28:29.169802",
|
| 548 |
+
"status": "completed"
|
| 549 |
+
},
|
| 550 |
+
"tags": []
|
| 551 |
+
},
|
| 552 |
+
"outputs": [],
|
| 553 |
+
"source": [
|
| 554 |
+
"pairs = []\n",
|
| 555 |
+
"processed = []\n",
|
| 556 |
+
"for p, token_map, offsets, tokens, doc in zip(preds_final, ds[\"token_map\"],\n",
|
| 557 |
+
" ds[\"offset_mapping\"], ds[\"tokens\"],\n",
|
| 558 |
+
" ds[\"document\"]):\n",
|
| 559 |
+
" for token_pred, (start_idx, end_idx) in zip(p, offsets):\n",
|
| 560 |
+
" label_pred = id2label[token_pred]\n",
|
| 561 |
+
"\n",
|
| 562 |
+
" if start_idx + end_idx == 0:\n",
|
| 563 |
+
" continue\n",
|
| 564 |
+
"\n",
|
| 565 |
+
" if token_map[start_idx] == -1:\n",
|
| 566 |
+
" start_idx += 1\n",
|
| 567 |
+
"\n",
|
| 568 |
+
" # ignore \"\\n\\n\"\n",
|
| 569 |
+
" while start_idx < len(token_map) and tokens[token_map[start_idx]].isspace():\n",
|
| 570 |
+
" start_idx += 1\n",
|
| 571 |
+
"\n",
|
| 572 |
+
" if start_idx >= len(token_map):\n",
|
| 573 |
+
" break\n",
|
| 574 |
+
"\n",
|
| 575 |
+
" token_id = token_map[start_idx]\n",
|
| 576 |
+
"\n",
|
| 577 |
+
" # ignore \"O\" predictions and whitespace preds\n",
|
| 578 |
+
" if label_pred not in (\"O\") and token_id != -1:\n",
|
| 579 |
+
"# if label_pred not in (\"O\", \"B-EMAIL\", \"B-PHONE_NUM\", \"I-PHONE_NUM\") and token_id != -1:\n",
|
| 580 |
+
" pair = (doc, token_id)\n",
|
| 581 |
+
"\n",
|
| 582 |
+
" if pair not in pairs:\n",
|
| 583 |
+
" processed.append({\"document\": doc, \"token\": token_id, \"label\": label_pred, \"token_str\": tokens[token_id]})\n",
|
| 584 |
+
" pairs.append(pair)"
|
| 585 |
+
]
|
| 586 |
+
},
|
| 587 |
+
{
|
| 588 |
+
"cell_type": "code",
|
| 589 |
+
"execution_count": 12,
|
| 590 |
+
"id": "551a878b",
|
| 591 |
+
"metadata": {
|
| 592 |
+
"execution": {
|
| 593 |
+
"iopub.execute_input": "2025-04-30T15:28:29.241209Z",
|
| 594 |
+
"iopub.status.busy": "2025-04-30T15:28:29.240471Z",
|
| 595 |
+
"iopub.status.idle": "2025-04-30T15:28:29.262801Z",
|
| 596 |
+
"shell.execute_reply": "2025-04-30T15:28:29.261886Z"
|
| 597 |
+
},
|
| 598 |
+
"papermill": {
|
| 599 |
+
"duration": 0.0292,
|
| 600 |
+
"end_time": "2025-04-30T15:28:29.264560",
|
| 601 |
+
"exception": false,
|
| 602 |
+
"start_time": "2025-04-30T15:28:29.235360",
|
| 603 |
+
"status": "completed"
|
| 604 |
+
},
|
| 605 |
+
"tags": []
|
| 606 |
+
},
|
| 607 |
+
"outputs": [
|
| 608 |
+
{
|
| 609 |
+
"data": {
|
| 610 |
+
"text/html": [
|
| 611 |
+
"<div>\n",
|
| 612 |
+
"<style scoped>\n",
|
| 613 |
+
" .dataframe tbody tr th:only-of-type {\n",
|
| 614 |
+
" vertical-align: middle;\n",
|
| 615 |
+
" }\n",
|
| 616 |
+
"\n",
|
| 617 |
+
" .dataframe tbody tr th {\n",
|
| 618 |
+
" vertical-align: top;\n",
|
| 619 |
+
" }\n",
|
| 620 |
+
"\n",
|
| 621 |
+
" .dataframe thead th {\n",
|
| 622 |
+
" text-align: right;\n",
|
| 623 |
+
" }\n",
|
| 624 |
+
"</style>\n",
|
| 625 |
+
"<table border=\"1\" class=\"dataframe\">\n",
|
| 626 |
+
" <thead>\n",
|
| 627 |
+
" <tr style=\"text-align: right;\">\n",
|
| 628 |
+
" <th></th>\n",
|
| 629 |
+
" <th>document</th>\n",
|
| 630 |
+
" <th>token</th>\n",
|
| 631 |
+
" <th>label</th>\n",
|
| 632 |
+
" <th>token_str</th>\n",
|
| 633 |
+
" <th>row_id</th>\n",
|
| 634 |
+
" </tr>\n",
|
| 635 |
+
" </thead>\n",
|
| 636 |
+
" <tbody>\n",
|
| 637 |
+
" <tr>\n",
|
| 638 |
+
" <th>0</th>\n",
|
| 639 |
+
" <td>7</td>\n",
|
| 640 |
+
" <td>9</td>\n",
|
| 641 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 642 |
+
" <td>Nathalie</td>\n",
|
| 643 |
+
" <td>0</td>\n",
|
| 644 |
+
" </tr>\n",
|
| 645 |
+
" <tr>\n",
|
| 646 |
+
" <th>1</th>\n",
|
| 647 |
+
" <td>7</td>\n",
|
| 648 |
+
" <td>10</td>\n",
|
| 649 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 650 |
+
" <td>Sylla</td>\n",
|
| 651 |
+
" <td>1</td>\n",
|
| 652 |
+
" </tr>\n",
|
| 653 |
+
" <tr>\n",
|
| 654 |
+
" <th>2</th>\n",
|
| 655 |
+
" <td>7</td>\n",
|
| 656 |
+
" <td>482</td>\n",
|
| 657 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 658 |
+
" <td>Nathalie</td>\n",
|
| 659 |
+
" <td>2</td>\n",
|
| 660 |
+
" </tr>\n",
|
| 661 |
+
" <tr>\n",
|
| 662 |
+
" <th>3</th>\n",
|
| 663 |
+
" <td>7</td>\n",
|
| 664 |
+
" <td>483</td>\n",
|
| 665 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 666 |
+
" <td>Sylla</td>\n",
|
| 667 |
+
" <td>3</td>\n",
|
| 668 |
+
" </tr>\n",
|
| 669 |
+
" <tr>\n",
|
| 670 |
+
" <th>4</th>\n",
|
| 671 |
+
" <td>7</td>\n",
|
| 672 |
+
" <td>741</td>\n",
|
| 673 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 674 |
+
" <td>Nathalie</td>\n",
|
| 675 |
+
" <td>4</td>\n",
|
| 676 |
+
" </tr>\n",
|
| 677 |
+
" <tr>\n",
|
| 678 |
+
" <th>5</th>\n",
|
| 679 |
+
" <td>7</td>\n",
|
| 680 |
+
" <td>742</td>\n",
|
| 681 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 682 |
+
" <td>Sylla</td>\n",
|
| 683 |
+
" <td>5</td>\n",
|
| 684 |
+
" </tr>\n",
|
| 685 |
+
" <tr>\n",
|
| 686 |
+
" <th>6</th>\n",
|
| 687 |
+
" <td>10</td>\n",
|
| 688 |
+
" <td>0</td>\n",
|
| 689 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 690 |
+
" <td>Diego</td>\n",
|
| 691 |
+
" <td>6</td>\n",
|
| 692 |
+
" </tr>\n",
|
| 693 |
+
" <tr>\n",
|
| 694 |
+
" <th>7</th>\n",
|
| 695 |
+
" <td>10</td>\n",
|
| 696 |
+
" <td>1</td>\n",
|
| 697 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 698 |
+
" <td>Estrada</td>\n",
|
| 699 |
+
" <td>7</td>\n",
|
| 700 |
+
" </tr>\n",
|
| 701 |
+
" <tr>\n",
|
| 702 |
+
" <th>8</th>\n",
|
| 703 |
+
" <td>10</td>\n",
|
| 704 |
+
" <td>464</td>\n",
|
| 705 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 706 |
+
" <td>Diego</td>\n",
|
| 707 |
+
" <td>8</td>\n",
|
| 708 |
+
" </tr>\n",
|
| 709 |
+
" <tr>\n",
|
| 710 |
+
" <th>9</th>\n",
|
| 711 |
+
" <td>10</td>\n",
|
| 712 |
+
" <td>465</td>\n",
|
| 713 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 714 |
+
" <td>Estrada</td>\n",
|
| 715 |
+
" <td>9</td>\n",
|
| 716 |
+
" </tr>\n",
|
| 717 |
+
" <tr>\n",
|
| 718 |
+
" <th>10</th>\n",
|
| 719 |
+
" <td>16</td>\n",
|
| 720 |
+
" <td>4</td>\n",
|
| 721 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 722 |
+
" <td>Gilberto</td>\n",
|
| 723 |
+
" <td>10</td>\n",
|
| 724 |
+
" </tr>\n",
|
| 725 |
+
" <tr>\n",
|
| 726 |
+
" <th>11</th>\n",
|
| 727 |
+
" <td>16</td>\n",
|
| 728 |
+
" <td>5</td>\n",
|
| 729 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 730 |
+
" <td>Gamboa</td>\n",
|
| 731 |
+
" <td>11</td>\n",
|
| 732 |
+
" </tr>\n",
|
| 733 |
+
" <tr>\n",
|
| 734 |
+
" <th>12</th>\n",
|
| 735 |
+
" <td>20</td>\n",
|
| 736 |
+
" <td>5</td>\n",
|
| 737 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 738 |
+
" <td>Sindy</td>\n",
|
| 739 |
+
" <td>12</td>\n",
|
| 740 |
+
" </tr>\n",
|
| 741 |
+
" <tr>\n",
|
| 742 |
+
" <th>13</th>\n",
|
| 743 |
+
" <td>20</td>\n",
|
| 744 |
+
" <td>6</td>\n",
|
| 745 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 746 |
+
" <td>Samaca</td>\n",
|
| 747 |
+
" <td>13</td>\n",
|
| 748 |
+
" </tr>\n",
|
| 749 |
+
" <tr>\n",
|
| 750 |
+
" <th>14</th>\n",
|
| 751 |
+
" <td>56</td>\n",
|
| 752 |
+
" <td>12</td>\n",
|
| 753 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 754 |
+
" <td>Nadine</td>\n",
|
| 755 |
+
" <td>14</td>\n",
|
| 756 |
+
" </tr>\n",
|
| 757 |
+
" <tr>\n",
|
| 758 |
+
" <th>15</th>\n",
|
| 759 |
+
" <td>56</td>\n",
|
| 760 |
+
" <td>13</td>\n",
|
| 761 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 762 |
+
" <td>Born</td>\n",
|
| 763 |
+
" <td>15</td>\n",
|
| 764 |
+
" </tr>\n",
|
| 765 |
+
" <tr>\n",
|
| 766 |
+
" <th>16</th>\n",
|
| 767 |
+
" <td>86</td>\n",
|
| 768 |
+
" <td>6</td>\n",
|
| 769 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 770 |
+
" <td>Eladio</td>\n",
|
| 771 |
+
" <td>16</td>\n",
|
| 772 |
+
" </tr>\n",
|
| 773 |
+
" <tr>\n",
|
| 774 |
+
" <th>17</th>\n",
|
| 775 |
+
" <td>86</td>\n",
|
| 776 |
+
" <td>7</td>\n",
|
| 777 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 778 |
+
" <td>Amaya</td>\n",
|
| 779 |
+
" <td>17</td>\n",
|
| 780 |
+
" </tr>\n",
|
| 781 |
+
" <tr>\n",
|
| 782 |
+
" <th>18</th>\n",
|
| 783 |
+
" <td>93</td>\n",
|
| 784 |
+
" <td>0</td>\n",
|
| 785 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 786 |
+
" <td>Silvia</td>\n",
|
| 787 |
+
" <td>18</td>\n",
|
| 788 |
+
" </tr>\n",
|
| 789 |
+
" <tr>\n",
|
| 790 |
+
" <th>19</th>\n",
|
| 791 |
+
" <td>93</td>\n",
|
| 792 |
+
" <td>1</td>\n",
|
| 793 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 794 |
+
" <td>Villalobos</td>\n",
|
| 795 |
+
" <td>19</td>\n",
|
| 796 |
+
" </tr>\n",
|
| 797 |
+
" <tr>\n",
|
| 798 |
+
" <th>20</th>\n",
|
| 799 |
+
" <td>104</td>\n",
|
| 800 |
+
" <td>8</td>\n",
|
| 801 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 802 |
+
" <td>Sakir</td>\n",
|
| 803 |
+
" <td>20</td>\n",
|
| 804 |
+
" </tr>\n",
|
| 805 |
+
" <tr>\n",
|
| 806 |
+
" <th>21</th>\n",
|
| 807 |
+
" <td>104</td>\n",
|
| 808 |
+
" <td>9</td>\n",
|
| 809 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 810 |
+
" <td>Ahmad</td>\n",
|
| 811 |
+
" <td>21</td>\n",
|
| 812 |
+
" </tr>\n",
|
| 813 |
+
" <tr>\n",
|
| 814 |
+
" <th>22</th>\n",
|
| 815 |
+
" <td>112</td>\n",
|
| 816 |
+
" <td>5</td>\n",
|
| 817 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 818 |
+
" <td>Francisco</td>\n",
|
| 819 |
+
" <td>22</td>\n",
|
| 820 |
+
" </tr>\n",
|
| 821 |
+
" <tr>\n",
|
| 822 |
+
" <th>23</th>\n",
|
| 823 |
+
" <td>112</td>\n",
|
| 824 |
+
" <td>6</td>\n",
|
| 825 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 826 |
+
" <td>Ferreira</td>\n",
|
| 827 |
+
" <td>23</td>\n",
|
| 828 |
+
" </tr>\n",
|
| 829 |
+
" <tr>\n",
|
| 830 |
+
" <th>24</th>\n",
|
| 831 |
+
" <td>123</td>\n",
|
| 832 |
+
" <td>32</td>\n",
|
| 833 |
+
" <td>B-NAME_STUDENT</td>\n",
|
| 834 |
+
" <td>Stefano</td>\n",
|
| 835 |
+
" <td>24</td>\n",
|
| 836 |
+
" </tr>\n",
|
| 837 |
+
" <tr>\n",
|
| 838 |
+
" <th>25</th>\n",
|
| 839 |
+
" <td>123</td>\n",
|
| 840 |
+
" <td>33</td>\n",
|
| 841 |
+
" <td>I-NAME_STUDENT</td>\n",
|
| 842 |
+
" <td>Lovato</td>\n",
|
| 843 |
+
" <td>25</td>\n",
|
| 844 |
+
" </tr>\n",
|
| 845 |
+
" </tbody>\n",
|
| 846 |
+
"</table>\n",
|
| 847 |
+
"</div>"
|
| 848 |
+
],
|
| 849 |
+
"text/plain": [
|
| 850 |
+
" document token label token_str row_id\n",
|
| 851 |
+
"0 7 9 B-NAME_STUDENT Nathalie 0\n",
|
| 852 |
+
"1 7 10 I-NAME_STUDENT Sylla 1\n",
|
| 853 |
+
"2 7 482 B-NAME_STUDENT Nathalie 2\n",
|
| 854 |
+
"3 7 483 I-NAME_STUDENT Sylla 3\n",
|
| 855 |
+
"4 7 741 B-NAME_STUDENT Nathalie 4\n",
|
| 856 |
+
"5 7 742 I-NAME_STUDENT Sylla 5\n",
|
| 857 |
+
"6 10 0 B-NAME_STUDENT Diego 6\n",
|
| 858 |
+
"7 10 1 I-NAME_STUDENT Estrada 7\n",
|
| 859 |
+
"8 10 464 B-NAME_STUDENT Diego 8\n",
|
| 860 |
+
"9 10 465 I-NAME_STUDENT Estrada 9\n",
|
| 861 |
+
"10 16 4 B-NAME_STUDENT Gilberto 10\n",
|
| 862 |
+
"11 16 5 I-NAME_STUDENT Gamboa 11\n",
|
| 863 |
+
"12 20 5 B-NAME_STUDENT Sindy 12\n",
|
| 864 |
+
"13 20 6 I-NAME_STUDENT Samaca 13\n",
|
| 865 |
+
"14 56 12 B-NAME_STUDENT Nadine 14\n",
|
| 866 |
+
"15 56 13 I-NAME_STUDENT Born 15\n",
|
| 867 |
+
"16 86 6 B-NAME_STUDENT Eladio 16\n",
|
| 868 |
+
"17 86 7 I-NAME_STUDENT Amaya 17\n",
|
| 869 |
+
"18 93 0 B-NAME_STUDENT Silvia 18\n",
|
| 870 |
+
"19 93 1 I-NAME_STUDENT Villalobos 19\n",
|
| 871 |
+
"20 104 8 B-NAME_STUDENT Sakir 20\n",
|
| 872 |
+
"21 104 9 I-NAME_STUDENT Ahmad 21\n",
|
| 873 |
+
"22 112 5 B-NAME_STUDENT Francisco 22\n",
|
| 874 |
+
"23 112 6 I-NAME_STUDENT Ferreira 23\n",
|
| 875 |
+
"24 123 32 B-NAME_STUDENT Stefano 24\n",
|
| 876 |
+
"25 123 33 I-NAME_STUDENT Lovato 25"
|
| 877 |
+
]
|
| 878 |
+
},
|
| 879 |
+
"metadata": {},
|
| 880 |
+
"output_type": "display_data"
|
| 881 |
+
}
|
| 882 |
+
],
|
| 883 |
+
"source": [
|
| 884 |
+
"df = pd.DataFrame(processed)\n",
|
| 885 |
+
"df[\"row_id\"] = list(range(len(df)))\n",
|
| 886 |
+
"display(df.head(100))"
|
| 887 |
+
]
|
| 888 |
+
},
|
| 889 |
+
{
|
| 890 |
+
"cell_type": "code",
|
| 891 |
+
"execution_count": 13,
|
| 892 |
+
"id": "e3c5b306",
|
| 893 |
+
"metadata": {
|
| 894 |
+
"execution": {
|
| 895 |
+
"iopub.execute_input": "2025-04-30T15:28:29.274581Z",
|
| 896 |
+
"iopub.status.busy": "2025-04-30T15:28:29.273853Z",
|
| 897 |
+
"iopub.status.idle": "2025-04-30T15:28:29.287091Z",
|
| 898 |
+
"shell.execute_reply": "2025-04-30T15:28:29.286356Z"
|
| 899 |
+
},
|
| 900 |
+
"papermill": {
|
| 901 |
+
"duration": 0.01995,
|
| 902 |
+
"end_time": "2025-04-30T15:28:29.288780",
|
| 903 |
+
"exception": false,
|
| 904 |
+
"start_time": "2025-04-30T15:28:29.268830",
|
| 905 |
+
"status": "completed"
|
| 906 |
+
},
|
| 907 |
+
"tags": []
|
| 908 |
+
},
|
| 909 |
+
"outputs": [],
|
| 910 |
+
"source": [
|
| 911 |
+
"df[[\"row_id\", \"document\", \"token\", \"label\"]].to_csv(\"submission.csv\", index=False)"
|
| 912 |
+
]
|
| 913 |
+
},
|
| 914 |
+
{
|
| 915 |
+
"cell_type": "code",
|
| 916 |
+
"execution_count": null,
|
| 917 |
+
"id": "493a9eb6",
|
| 918 |
+
"metadata": {
|
| 919 |
+
"papermill": {
|
| 920 |
+
"duration": 0.003789,
|
| 921 |
+
"end_time": "2025-04-30T15:28:29.297088",
|
| 922 |
+
"exception": false,
|
| 923 |
+
"start_time": "2025-04-30T15:28:29.293299",
|
| 924 |
+
"status": "completed"
|
| 925 |
+
},
|
| 926 |
+
"tags": []
|
| 927 |
+
},
|
| 928 |
+
"outputs": [],
|
| 929 |
+
"source": []
|
| 930 |
+
}
|
| 931 |
+
],
|
| 932 |
+
"metadata": {
|
| 933 |
+
"kaggle": {
|
| 934 |
+
"accelerator": "gpu",
|
| 935 |
+
"dataSources": [
|
| 936 |
+
{
|
| 937 |
+
"databundleVersionId": 7500999,
|
| 938 |
+
"sourceId": 66653,
|
| 939 |
+
"sourceType": "competition"
|
| 940 |
+
},
|
| 941 |
+
{
|
| 942 |
+
"datasetId": 4626838,
|
| 943 |
+
"sourceId": 7882793,
|
| 944 |
+
"sourceType": "datasetVersion"
|
| 945 |
+
}
|
| 946 |
+
],
|
| 947 |
+
"dockerImageVersionId": 30665,
|
| 948 |
+
"isGpuEnabled": true,
|
| 949 |
+
"isInternetEnabled": false,
|
| 950 |
+
"language": "python",
|
| 951 |
+
"sourceType": "notebook"
|
| 952 |
+
},
|
| 953 |
+
"kernelspec": {
|
| 954 |
+
"display_name": "Python 3",
|
| 955 |
+
"language": "python",
|
| 956 |
+
"name": "python3"
|
| 957 |
+
},
|
| 958 |
+
"language_info": {
|
| 959 |
+
"codemirror_mode": {
|
| 960 |
+
"name": "ipython",
|
| 961 |
+
"version": 3
|
| 962 |
+
},
|
| 963 |
+
"file_extension": ".py",
|
| 964 |
+
"mimetype": "text/x-python",
|
| 965 |
+
"name": "python",
|
| 966 |
+
"nbconvert_exporter": "python",
|
| 967 |
+
"pygments_lexer": "ipython3",
|
| 968 |
+
"version": "3.10.13"
|
| 969 |
+
},
|
| 970 |
+
"papermill": {
|
| 971 |
+
"default_parameters": {},
|
| 972 |
+
"duration": 60.15036,
|
| 973 |
+
"end_time": "2025-04-30T15:28:32.295763",
|
| 974 |
+
"environment_variables": {},
|
| 975 |
+
"exception": null,
|
| 976 |
+
"input_path": "__notebook__.ipynb",
|
| 977 |
+
"output_path": "__notebook__.ipynb",
|
| 978 |
+
"parameters": {},
|
| 979 |
+
"start_time": "2025-04-30T15:27:32.145403",
|
| 980 |
+
"version": "2.5.0"
|
| 981 |
+
},
|
| 982 |
+
"widgets": {
|
| 983 |
+
"application/vnd.jupyter.widget-state+json": {
|
| 984 |
+
"state": {
|
| 985 |
+
"06a9c6b88ead4f49a96be340378209a1": {
|
| 986 |
+
"model_module": "@jupyter-widgets/controls",
|
| 987 |
+
"model_module_version": "1.5.0",
|
| 988 |
+
"model_name": "ProgressStyleModel",
|
| 989 |
+
"state": {
|
| 990 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 991 |
+
"_model_module_version": "1.5.0",
|
| 992 |
+
"_model_name": "ProgressStyleModel",
|
| 993 |
+
"_view_count": null,
|
| 994 |
+
"_view_module": "@jupyter-widgets/base",
|
| 995 |
+
"_view_module_version": "1.2.0",
|
| 996 |
+
"_view_name": "StyleView",
|
| 997 |
+
"bar_color": null,
|
| 998 |
+
"description_width": ""
|
| 999 |
+
}
|
| 1000 |
+
},
|
| 1001 |
+
"131b74fe075f4174b6362f1d1d8d6c18": {
|
| 1002 |
+
"model_module": "@jupyter-widgets/base",
|
| 1003 |
+
"model_module_version": "1.2.0",
|
| 1004 |
+
"model_name": "LayoutModel",
|
| 1005 |
+
"state": {
|
| 1006 |
+
"_model_module": "@jupyter-widgets/base",
|
| 1007 |
+
"_model_module_version": "1.2.0",
|
| 1008 |
+
"_model_name": "LayoutModel",
|
| 1009 |
+
"_view_count": null,
|
| 1010 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1011 |
+
"_view_module_version": "1.2.0",
|
| 1012 |
+
"_view_name": "LayoutView",
|
| 1013 |
+
"align_content": null,
|
| 1014 |
+
"align_items": null,
|
| 1015 |
+
"align_self": null,
|
| 1016 |
+
"border": null,
|
| 1017 |
+
"bottom": null,
|
| 1018 |
+
"display": null,
|
| 1019 |
+
"flex": null,
|
| 1020 |
+
"flex_flow": null,
|
| 1021 |
+
"grid_area": null,
|
| 1022 |
+
"grid_auto_columns": null,
|
| 1023 |
+
"grid_auto_flow": null,
|
| 1024 |
+
"grid_auto_rows": null,
|
| 1025 |
+
"grid_column": null,
|
| 1026 |
+
"grid_gap": null,
|
| 1027 |
+
"grid_row": null,
|
| 1028 |
+
"grid_template_areas": null,
|
| 1029 |
+
"grid_template_columns": null,
|
| 1030 |
+
"grid_template_rows": null,
|
| 1031 |
+
"height": null,
|
| 1032 |
+
"justify_content": null,
|
| 1033 |
+
"justify_items": null,
|
| 1034 |
+
"left": null,
|
| 1035 |
+
"margin": null,
|
| 1036 |
+
"max_height": null,
|
| 1037 |
+
"max_width": null,
|
| 1038 |
+
"min_height": null,
|
| 1039 |
+
"min_width": null,
|
| 1040 |
+
"object_fit": null,
|
| 1041 |
+
"object_position": null,
|
| 1042 |
+
"order": null,
|
| 1043 |
+
"overflow": null,
|
| 1044 |
+
"overflow_x": null,
|
| 1045 |
+
"overflow_y": null,
|
| 1046 |
+
"padding": null,
|
| 1047 |
+
"right": null,
|
| 1048 |
+
"top": null,
|
| 1049 |
+
"visibility": null,
|
| 1050 |
+
"width": null
|
| 1051 |
+
}
|
| 1052 |
+
},
|
| 1053 |
+
"1a471d08224e42b095b0283f8c3dd9e9": {
|
| 1054 |
+
"model_module": "@jupyter-widgets/base",
|
| 1055 |
+
"model_module_version": "1.2.0",
|
| 1056 |
+
"model_name": "LayoutModel",
|
| 1057 |
+
"state": {
|
| 1058 |
+
"_model_module": "@jupyter-widgets/base",
|
| 1059 |
+
"_model_module_version": "1.2.0",
|
| 1060 |
+
"_model_name": "LayoutModel",
|
| 1061 |
+
"_view_count": null,
|
| 1062 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1063 |
+
"_view_module_version": "1.2.0",
|
| 1064 |
+
"_view_name": "LayoutView",
|
| 1065 |
+
"align_content": null,
|
| 1066 |
+
"align_items": null,
|
| 1067 |
+
"align_self": null,
|
| 1068 |
+
"border": null,
|
| 1069 |
+
"bottom": null,
|
| 1070 |
+
"display": null,
|
| 1071 |
+
"flex": null,
|
| 1072 |
+
"flex_flow": null,
|
| 1073 |
+
"grid_area": null,
|
| 1074 |
+
"grid_auto_columns": null,
|
| 1075 |
+
"grid_auto_flow": null,
|
| 1076 |
+
"grid_auto_rows": null,
|
| 1077 |
+
"grid_column": null,
|
| 1078 |
+
"grid_gap": null,
|
| 1079 |
+
"grid_row": null,
|
| 1080 |
+
"grid_template_areas": null,
|
| 1081 |
+
"grid_template_columns": null,
|
| 1082 |
+
"grid_template_rows": null,
|
| 1083 |
+
"height": null,
|
| 1084 |
+
"justify_content": null,
|
| 1085 |
+
"justify_items": null,
|
| 1086 |
+
"left": null,
|
| 1087 |
+
"margin": null,
|
| 1088 |
+
"max_height": null,
|
| 1089 |
+
"max_width": null,
|
| 1090 |
+
"min_height": null,
|
| 1091 |
+
"min_width": null,
|
| 1092 |
+
"object_fit": null,
|
| 1093 |
+
"object_position": null,
|
| 1094 |
+
"order": null,
|
| 1095 |
+
"overflow": null,
|
| 1096 |
+
"overflow_x": null,
|
| 1097 |
+
"overflow_y": null,
|
| 1098 |
+
"padding": null,
|
| 1099 |
+
"right": null,
|
| 1100 |
+
"top": null,
|
| 1101 |
+
"visibility": null,
|
| 1102 |
+
"width": null
|
| 1103 |
+
}
|
| 1104 |
+
},
|
| 1105 |
+
"2767366afecc4147be4dfcabbe18f673": {
|
| 1106 |
+
"model_module": "@jupyter-widgets/base",
|
| 1107 |
+
"model_module_version": "1.2.0",
|
| 1108 |
+
"model_name": "LayoutModel",
|
| 1109 |
+
"state": {
|
| 1110 |
+
"_model_module": "@jupyter-widgets/base",
|
| 1111 |
+
"_model_module_version": "1.2.0",
|
| 1112 |
+
"_model_name": "LayoutModel",
|
| 1113 |
+
"_view_count": null,
|
| 1114 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1115 |
+
"_view_module_version": "1.2.0",
|
| 1116 |
+
"_view_name": "LayoutView",
|
| 1117 |
+
"align_content": null,
|
| 1118 |
+
"align_items": null,
|
| 1119 |
+
"align_self": null,
|
| 1120 |
+
"border": null,
|
| 1121 |
+
"bottom": null,
|
| 1122 |
+
"display": null,
|
| 1123 |
+
"flex": null,
|
| 1124 |
+
"flex_flow": null,
|
| 1125 |
+
"grid_area": null,
|
| 1126 |
+
"grid_auto_columns": null,
|
| 1127 |
+
"grid_auto_flow": null,
|
| 1128 |
+
"grid_auto_rows": null,
|
| 1129 |
+
"grid_column": null,
|
| 1130 |
+
"grid_gap": null,
|
| 1131 |
+
"grid_row": null,
|
| 1132 |
+
"grid_template_areas": null,
|
| 1133 |
+
"grid_template_columns": null,
|
| 1134 |
+
"grid_template_rows": null,
|
| 1135 |
+
"height": null,
|
| 1136 |
+
"justify_content": null,
|
| 1137 |
+
"justify_items": null,
|
| 1138 |
+
"left": null,
|
| 1139 |
+
"margin": null,
|
| 1140 |
+
"max_height": null,
|
| 1141 |
+
"max_width": null,
|
| 1142 |
+
"min_height": null,
|
| 1143 |
+
"min_width": null,
|
| 1144 |
+
"object_fit": null,
|
| 1145 |
+
"object_position": null,
|
| 1146 |
+
"order": null,
|
| 1147 |
+
"overflow": null,
|
| 1148 |
+
"overflow_x": null,
|
| 1149 |
+
"overflow_y": null,
|
| 1150 |
+
"padding": null,
|
| 1151 |
+
"right": null,
|
| 1152 |
+
"top": null,
|
| 1153 |
+
"visibility": null,
|
| 1154 |
+
"width": null
|
| 1155 |
+
}
|
| 1156 |
+
},
|
| 1157 |
+
"281fad7ccc0949d9b376b9bc51a5a37c": {
|
| 1158 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1159 |
+
"model_module_version": "1.5.0",
|
| 1160 |
+
"model_name": "ProgressStyleModel",
|
| 1161 |
+
"state": {
|
| 1162 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1163 |
+
"_model_module_version": "1.5.0",
|
| 1164 |
+
"_model_name": "ProgressStyleModel",
|
| 1165 |
+
"_view_count": null,
|
| 1166 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1167 |
+
"_view_module_version": "1.2.0",
|
| 1168 |
+
"_view_name": "StyleView",
|
| 1169 |
+
"bar_color": null,
|
| 1170 |
+
"description_width": ""
|
| 1171 |
+
}
|
| 1172 |
+
},
|
| 1173 |
+
"35b69818cee842c1bcdb21fa19a0df48": {
|
| 1174 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1175 |
+
"model_module_version": "1.5.0",
|
| 1176 |
+
"model_name": "FloatProgressModel",
|
| 1177 |
+
"state": {
|
| 1178 |
+
"_dom_classes": [],
|
| 1179 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1180 |
+
"_model_module_version": "1.5.0",
|
| 1181 |
+
"_model_name": "FloatProgressModel",
|
| 1182 |
+
"_view_count": null,
|
| 1183 |
+
"_view_module": "@jupyter-widgets/controls",
|
| 1184 |
+
"_view_module_version": "1.5.0",
|
| 1185 |
+
"_view_name": "ProgressView",
|
| 1186 |
+
"bar_style": "success",
|
| 1187 |
+
"description": "",
|
| 1188 |
+
"description_tooltip": null,
|
| 1189 |
+
"layout": "IPY_MODEL_2767366afecc4147be4dfcabbe18f673",
|
| 1190 |
+
"max": 5.0,
|
| 1191 |
+
"min": 0.0,
|
| 1192 |
+
"orientation": "horizontal",
|
| 1193 |
+
"style": "IPY_MODEL_06a9c6b88ead4f49a96be340378209a1",
|
| 1194 |
+
"value": 5.0
|
| 1195 |
+
}
|
| 1196 |
+
},
|
| 1197 |
+
"41df8caea2b9469dbf1adc1b5e056087": {
|
| 1198 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1199 |
+
"model_module_version": "1.5.0",
|
| 1200 |
+
"model_name": "DescriptionStyleModel",
|
| 1201 |
+
"state": {
|
| 1202 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1203 |
+
"_model_module_version": "1.5.0",
|
| 1204 |
+
"_model_name": "DescriptionStyleModel",
|
| 1205 |
+
"_view_count": null,
|
| 1206 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1207 |
+
"_view_module_version": "1.2.0",
|
| 1208 |
+
"_view_name": "StyleView",
|
| 1209 |
+
"description_width": ""
|
| 1210 |
+
}
|
| 1211 |
+
},
|
| 1212 |
+
"5512699220b84dbe87b51b77be6ac346": {
|
| 1213 |
+
"model_module": "@jupyter-widgets/base",
|
| 1214 |
+
"model_module_version": "1.2.0",
|
| 1215 |
+
"model_name": "LayoutModel",
|
| 1216 |
+
"state": {
|
| 1217 |
+
"_model_module": "@jupyter-widgets/base",
|
| 1218 |
+
"_model_module_version": "1.2.0",
|
| 1219 |
+
"_model_name": "LayoutModel",
|
| 1220 |
+
"_view_count": null,
|
| 1221 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1222 |
+
"_view_module_version": "1.2.0",
|
| 1223 |
+
"_view_name": "LayoutView",
|
| 1224 |
+
"align_content": null,
|
| 1225 |
+
"align_items": null,
|
| 1226 |
+
"align_self": null,
|
| 1227 |
+
"border": null,
|
| 1228 |
+
"bottom": null,
|
| 1229 |
+
"display": null,
|
| 1230 |
+
"flex": null,
|
| 1231 |
+
"flex_flow": null,
|
| 1232 |
+
"grid_area": null,
|
| 1233 |
+
"grid_auto_columns": null,
|
| 1234 |
+
"grid_auto_flow": null,
|
| 1235 |
+
"grid_auto_rows": null,
|
| 1236 |
+
"grid_column": null,
|
| 1237 |
+
"grid_gap": null,
|
| 1238 |
+
"grid_row": null,
|
| 1239 |
+
"grid_template_areas": null,
|
| 1240 |
+
"grid_template_columns": null,
|
| 1241 |
+
"grid_template_rows": null,
|
| 1242 |
+
"height": null,
|
| 1243 |
+
"justify_content": null,
|
| 1244 |
+
"justify_items": null,
|
| 1245 |
+
"left": null,
|
| 1246 |
+
"margin": null,
|
| 1247 |
+
"max_height": null,
|
| 1248 |
+
"max_width": null,
|
| 1249 |
+
"min_height": null,
|
| 1250 |
+
"min_width": null,
|
| 1251 |
+
"object_fit": null,
|
| 1252 |
+
"object_position": null,
|
| 1253 |
+
"order": null,
|
| 1254 |
+
"overflow": null,
|
| 1255 |
+
"overflow_x": null,
|
| 1256 |
+
"overflow_y": null,
|
| 1257 |
+
"padding": null,
|
| 1258 |
+
"right": null,
|
| 1259 |
+
"top": null,
|
| 1260 |
+
"visibility": null,
|
| 1261 |
+
"width": null
|
| 1262 |
+
}
|
| 1263 |
+
},
|
| 1264 |
+
"5951514148bd4f938c2559866f91be40": {
|
| 1265 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1266 |
+
"model_module_version": "1.5.0",
|
| 1267 |
+
"model_name": "HBoxModel",
|
| 1268 |
+
"state": {
|
| 1269 |
+
"_dom_classes": [],
|
| 1270 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1271 |
+
"_model_module_version": "1.5.0",
|
| 1272 |
+
"_model_name": "HBoxModel",
|
| 1273 |
+
"_view_count": null,
|
| 1274 |
+
"_view_module": "@jupyter-widgets/controls",
|
| 1275 |
+
"_view_module_version": "1.5.0",
|
| 1276 |
+
"_view_name": "HBoxView",
|
| 1277 |
+
"box_style": "",
|
| 1278 |
+
"children": [
|
| 1279 |
+
"IPY_MODEL_680ee12021f24e88969f766421390554",
|
| 1280 |
+
"IPY_MODEL_35b69818cee842c1bcdb21fa19a0df48",
|
| 1281 |
+
"IPY_MODEL_dcf6b07ee9cb4f16b314dbe42a5e7abe"
|
| 1282 |
+
],
|
| 1283 |
+
"layout": "IPY_MODEL_1a471d08224e42b095b0283f8c3dd9e9"
|
| 1284 |
+
}
|
| 1285 |
+
},
|
| 1286 |
+
"59596944958e440695e4ba4f96570ada": {
|
| 1287 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1288 |
+
"model_module_version": "1.5.0",
|
| 1289 |
+
"model_name": "HTMLModel",
|
| 1290 |
+
"state": {
|
| 1291 |
+
"_dom_classes": [],
|
| 1292 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1293 |
+
"_model_module_version": "1.5.0",
|
| 1294 |
+
"_model_name": "HTMLModel",
|
| 1295 |
+
"_view_count": null,
|
| 1296 |
+
"_view_module": "@jupyter-widgets/controls",
|
| 1297 |
+
"_view_module_version": "1.5.0",
|
| 1298 |
+
"_view_name": "HTMLView",
|
| 1299 |
+
"description": "",
|
| 1300 |
+
"description_tooltip": null,
|
| 1301 |
+
"layout": "IPY_MODEL_74ff34caa9cb4410ad25cb99dbccb46f",
|
| 1302 |
+
"placeholder": "",
|
| 1303 |
+
"style": "IPY_MODEL_41df8caea2b9469dbf1adc1b5e056087",
|
| 1304 |
+
"value": "#1: 100%"
|
| 1305 |
+
}
|
| 1306 |
+
},
|
| 1307 |
+
"6735ff3730f74fed8b055a7f19afd508": {
|
| 1308 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1309 |
+
"model_module_version": "1.5.0",
|
| 1310 |
+
"model_name": "DescriptionStyleModel",
|
| 1311 |
+
"state": {
|
| 1312 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1313 |
+
"_model_module_version": "1.5.0",
|
| 1314 |
+
"_model_name": "DescriptionStyleModel",
|
| 1315 |
+
"_view_count": null,
|
| 1316 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1317 |
+
"_view_module_version": "1.2.0",
|
| 1318 |
+
"_view_name": "StyleView",
|
| 1319 |
+
"description_width": ""
|
| 1320 |
+
}
|
| 1321 |
+
},
|
| 1322 |
+
"680ee12021f24e88969f766421390554": {
|
| 1323 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1324 |
+
"model_module_version": "1.5.0",
|
| 1325 |
+
"model_name": "HTMLModel",
|
| 1326 |
+
"state": {
|
| 1327 |
+
"_dom_classes": [],
|
| 1328 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1329 |
+
"_model_module_version": "1.5.0",
|
| 1330 |
+
"_model_name": "HTMLModel",
|
| 1331 |
+
"_view_count": null,
|
| 1332 |
+
"_view_module": "@jupyter-widgets/controls",
|
| 1333 |
+
"_view_module_version": "1.5.0",
|
| 1334 |
+
"_view_name": "HTMLView",
|
| 1335 |
+
"description": "",
|
| 1336 |
+
"description_tooltip": null,
|
| 1337 |
+
"layout": "IPY_MODEL_ac3981001ad041e997dbc05dad677c06",
|
| 1338 |
+
"placeholder": "",
|
| 1339 |
+
"style": "IPY_MODEL_d2c93cda9bed48ccb273911aaf36048a",
|
| 1340 |
+
"value": "#0: 100%"
|
| 1341 |
+
}
|
| 1342 |
+
},
|
| 1343 |
+
"6fe5d4d95b844ceba24521a27090e7ae": {
|
| 1344 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1345 |
+
"model_module_version": "1.5.0",
|
| 1346 |
+
"model_name": "DescriptionStyleModel",
|
| 1347 |
+
"state": {
|
| 1348 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1349 |
+
"_model_module_version": "1.5.0",
|
| 1350 |
+
"_model_name": "DescriptionStyleModel",
|
| 1351 |
+
"_view_count": null,
|
| 1352 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1353 |
+
"_view_module_version": "1.2.0",
|
| 1354 |
+
"_view_name": "StyleView",
|
| 1355 |
+
"description_width": ""
|
| 1356 |
+
}
|
| 1357 |
+
},
|
| 1358 |
+
"74ff34caa9cb4410ad25cb99dbccb46f": {
|
| 1359 |
+
"model_module": "@jupyter-widgets/base",
|
| 1360 |
+
"model_module_version": "1.2.0",
|
| 1361 |
+
"model_name": "LayoutModel",
|
| 1362 |
+
"state": {
|
| 1363 |
+
"_model_module": "@jupyter-widgets/base",
|
| 1364 |
+
"_model_module_version": "1.2.0",
|
| 1365 |
+
"_model_name": "LayoutModel",
|
| 1366 |
+
"_view_count": null,
|
| 1367 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1368 |
+
"_view_module_version": "1.2.0",
|
| 1369 |
+
"_view_name": "LayoutView",
|
| 1370 |
+
"align_content": null,
|
| 1371 |
+
"align_items": null,
|
| 1372 |
+
"align_self": null,
|
| 1373 |
+
"border": null,
|
| 1374 |
+
"bottom": null,
|
| 1375 |
+
"display": null,
|
| 1376 |
+
"flex": null,
|
| 1377 |
+
"flex_flow": null,
|
| 1378 |
+
"grid_area": null,
|
| 1379 |
+
"grid_auto_columns": null,
|
| 1380 |
+
"grid_auto_flow": null,
|
| 1381 |
+
"grid_auto_rows": null,
|
| 1382 |
+
"grid_column": null,
|
| 1383 |
+
"grid_gap": null,
|
| 1384 |
+
"grid_row": null,
|
| 1385 |
+
"grid_template_areas": null,
|
| 1386 |
+
"grid_template_columns": null,
|
| 1387 |
+
"grid_template_rows": null,
|
| 1388 |
+
"height": null,
|
| 1389 |
+
"justify_content": null,
|
| 1390 |
+
"justify_items": null,
|
| 1391 |
+
"left": null,
|
| 1392 |
+
"margin": null,
|
| 1393 |
+
"max_height": null,
|
| 1394 |
+
"max_width": null,
|
| 1395 |
+
"min_height": null,
|
| 1396 |
+
"min_width": null,
|
| 1397 |
+
"object_fit": null,
|
| 1398 |
+
"object_position": null,
|
| 1399 |
+
"order": null,
|
| 1400 |
+
"overflow": null,
|
| 1401 |
+
"overflow_x": null,
|
| 1402 |
+
"overflow_y": null,
|
| 1403 |
+
"padding": null,
|
| 1404 |
+
"right": null,
|
| 1405 |
+
"top": null,
|
| 1406 |
+
"visibility": null,
|
| 1407 |
+
"width": null
|
| 1408 |
+
}
|
| 1409 |
+
},
|
| 1410 |
+
"87d695ef5aea48a882031c27f85a3752": {
|
| 1411 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1412 |
+
"model_module_version": "1.5.0",
|
| 1413 |
+
"model_name": "FloatProgressModel",
|
| 1414 |
+
"state": {
|
| 1415 |
+
"_dom_classes": [],
|
| 1416 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1417 |
+
"_model_module_version": "1.5.0",
|
| 1418 |
+
"_model_name": "FloatProgressModel",
|
| 1419 |
+
"_view_count": null,
|
| 1420 |
+
"_view_module": "@jupyter-widgets/controls",
|
| 1421 |
+
"_view_module_version": "1.5.0",
|
| 1422 |
+
"_view_name": "ProgressView",
|
| 1423 |
+
"bar_style": "success",
|
| 1424 |
+
"description": "",
|
| 1425 |
+
"description_tooltip": null,
|
| 1426 |
+
"layout": "IPY_MODEL_131b74fe075f4174b6362f1d1d8d6c18",
|
| 1427 |
+
"max": 5.0,
|
| 1428 |
+
"min": 0.0,
|
| 1429 |
+
"orientation": "horizontal",
|
| 1430 |
+
"style": "IPY_MODEL_281fad7ccc0949d9b376b9bc51a5a37c",
|
| 1431 |
+
"value": 5.0
|
| 1432 |
+
}
|
| 1433 |
+
},
|
| 1434 |
+
"88430d270ddf4976af32c09d1afb0ad8": {
|
| 1435 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1436 |
+
"model_module_version": "1.5.0",
|
| 1437 |
+
"model_name": "HBoxModel",
|
| 1438 |
+
"state": {
|
| 1439 |
+
"_dom_classes": [],
|
| 1440 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1441 |
+
"_model_module_version": "1.5.0",
|
| 1442 |
+
"_model_name": "HBoxModel",
|
| 1443 |
+
"_view_count": null,
|
| 1444 |
+
"_view_module": "@jupyter-widgets/controls",
|
| 1445 |
+
"_view_module_version": "1.5.0",
|
| 1446 |
+
"_view_name": "HBoxView",
|
| 1447 |
+
"box_style": "",
|
| 1448 |
+
"children": [
|
| 1449 |
+
"IPY_MODEL_59596944958e440695e4ba4f96570ada",
|
| 1450 |
+
"IPY_MODEL_87d695ef5aea48a882031c27f85a3752",
|
| 1451 |
+
"IPY_MODEL_d79bec3cedfb4ded91d9d164a266ea3c"
|
| 1452 |
+
],
|
| 1453 |
+
"layout": "IPY_MODEL_5512699220b84dbe87b51b77be6ac346"
|
| 1454 |
+
}
|
| 1455 |
+
},
|
| 1456 |
+
"aa22d45919ae45e4852612397edf8e6b": {
|
| 1457 |
+
"model_module": "@jupyter-widgets/base",
|
| 1458 |
+
"model_module_version": "1.2.0",
|
| 1459 |
+
"model_name": "LayoutModel",
|
| 1460 |
+
"state": {
|
| 1461 |
+
"_model_module": "@jupyter-widgets/base",
|
| 1462 |
+
"_model_module_version": "1.2.0",
|
| 1463 |
+
"_model_name": "LayoutModel",
|
| 1464 |
+
"_view_count": null,
|
| 1465 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1466 |
+
"_view_module_version": "1.2.0",
|
| 1467 |
+
"_view_name": "LayoutView",
|
| 1468 |
+
"align_content": null,
|
| 1469 |
+
"align_items": null,
|
| 1470 |
+
"align_self": null,
|
| 1471 |
+
"border": null,
|
| 1472 |
+
"bottom": null,
|
| 1473 |
+
"display": null,
|
| 1474 |
+
"flex": null,
|
| 1475 |
+
"flex_flow": null,
|
| 1476 |
+
"grid_area": null,
|
| 1477 |
+
"grid_auto_columns": null,
|
| 1478 |
+
"grid_auto_flow": null,
|
| 1479 |
+
"grid_auto_rows": null,
|
| 1480 |
+
"grid_column": null,
|
| 1481 |
+
"grid_gap": null,
|
| 1482 |
+
"grid_row": null,
|
| 1483 |
+
"grid_template_areas": null,
|
| 1484 |
+
"grid_template_columns": null,
|
| 1485 |
+
"grid_template_rows": null,
|
| 1486 |
+
"height": null,
|
| 1487 |
+
"justify_content": null,
|
| 1488 |
+
"justify_items": null,
|
| 1489 |
+
"left": null,
|
| 1490 |
+
"margin": null,
|
| 1491 |
+
"max_height": null,
|
| 1492 |
+
"max_width": null,
|
| 1493 |
+
"min_height": null,
|
| 1494 |
+
"min_width": null,
|
| 1495 |
+
"object_fit": null,
|
| 1496 |
+
"object_position": null,
|
| 1497 |
+
"order": null,
|
| 1498 |
+
"overflow": null,
|
| 1499 |
+
"overflow_x": null,
|
| 1500 |
+
"overflow_y": null,
|
| 1501 |
+
"padding": null,
|
| 1502 |
+
"right": null,
|
| 1503 |
+
"top": null,
|
| 1504 |
+
"visibility": null,
|
| 1505 |
+
"width": null
|
| 1506 |
+
}
|
| 1507 |
+
},
|
| 1508 |
+
"ac3981001ad041e997dbc05dad677c06": {
|
| 1509 |
+
"model_module": "@jupyter-widgets/base",
|
| 1510 |
+
"model_module_version": "1.2.0",
|
| 1511 |
+
"model_name": "LayoutModel",
|
| 1512 |
+
"state": {
|
| 1513 |
+
"_model_module": "@jupyter-widgets/base",
|
| 1514 |
+
"_model_module_version": "1.2.0",
|
| 1515 |
+
"_model_name": "LayoutModel",
|
| 1516 |
+
"_view_count": null,
|
| 1517 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1518 |
+
"_view_module_version": "1.2.0",
|
| 1519 |
+
"_view_name": "LayoutView",
|
| 1520 |
+
"align_content": null,
|
| 1521 |
+
"align_items": null,
|
| 1522 |
+
"align_self": null,
|
| 1523 |
+
"border": null,
|
| 1524 |
+
"bottom": null,
|
| 1525 |
+
"display": null,
|
| 1526 |
+
"flex": null,
|
| 1527 |
+
"flex_flow": null,
|
| 1528 |
+
"grid_area": null,
|
| 1529 |
+
"grid_auto_columns": null,
|
| 1530 |
+
"grid_auto_flow": null,
|
| 1531 |
+
"grid_auto_rows": null,
|
| 1532 |
+
"grid_column": null,
|
| 1533 |
+
"grid_gap": null,
|
| 1534 |
+
"grid_row": null,
|
| 1535 |
+
"grid_template_areas": null,
|
| 1536 |
+
"grid_template_columns": null,
|
| 1537 |
+
"grid_template_rows": null,
|
| 1538 |
+
"height": null,
|
| 1539 |
+
"justify_content": null,
|
| 1540 |
+
"justify_items": null,
|
| 1541 |
+
"left": null,
|
| 1542 |
+
"margin": null,
|
| 1543 |
+
"max_height": null,
|
| 1544 |
+
"max_width": null,
|
| 1545 |
+
"min_height": null,
|
| 1546 |
+
"min_width": null,
|
| 1547 |
+
"object_fit": null,
|
| 1548 |
+
"object_position": null,
|
| 1549 |
+
"order": null,
|
| 1550 |
+
"overflow": null,
|
| 1551 |
+
"overflow_x": null,
|
| 1552 |
+
"overflow_y": null,
|
| 1553 |
+
"padding": null,
|
| 1554 |
+
"right": null,
|
| 1555 |
+
"top": null,
|
| 1556 |
+
"visibility": null,
|
| 1557 |
+
"width": null
|
| 1558 |
+
}
|
| 1559 |
+
},
|
| 1560 |
+
"b7789187719a4774a9d31cb1f5b82884": {
|
| 1561 |
+
"model_module": "@jupyter-widgets/base",
|
| 1562 |
+
"model_module_version": "1.2.0",
|
| 1563 |
+
"model_name": "LayoutModel",
|
| 1564 |
+
"state": {
|
| 1565 |
+
"_model_module": "@jupyter-widgets/base",
|
| 1566 |
+
"_model_module_version": "1.2.0",
|
| 1567 |
+
"_model_name": "LayoutModel",
|
| 1568 |
+
"_view_count": null,
|
| 1569 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1570 |
+
"_view_module_version": "1.2.0",
|
| 1571 |
+
"_view_name": "LayoutView",
|
| 1572 |
+
"align_content": null,
|
| 1573 |
+
"align_items": null,
|
| 1574 |
+
"align_self": null,
|
| 1575 |
+
"border": null,
|
| 1576 |
+
"bottom": null,
|
| 1577 |
+
"display": null,
|
| 1578 |
+
"flex": null,
|
| 1579 |
+
"flex_flow": null,
|
| 1580 |
+
"grid_area": null,
|
| 1581 |
+
"grid_auto_columns": null,
|
| 1582 |
+
"grid_auto_flow": null,
|
| 1583 |
+
"grid_auto_rows": null,
|
| 1584 |
+
"grid_column": null,
|
| 1585 |
+
"grid_gap": null,
|
| 1586 |
+
"grid_row": null,
|
| 1587 |
+
"grid_template_areas": null,
|
| 1588 |
+
"grid_template_columns": null,
|
| 1589 |
+
"grid_template_rows": null,
|
| 1590 |
+
"height": null,
|
| 1591 |
+
"justify_content": null,
|
| 1592 |
+
"justify_items": null,
|
| 1593 |
+
"left": null,
|
| 1594 |
+
"margin": null,
|
| 1595 |
+
"max_height": null,
|
| 1596 |
+
"max_width": null,
|
| 1597 |
+
"min_height": null,
|
| 1598 |
+
"min_width": null,
|
| 1599 |
+
"object_fit": null,
|
| 1600 |
+
"object_position": null,
|
| 1601 |
+
"order": null,
|
| 1602 |
+
"overflow": null,
|
| 1603 |
+
"overflow_x": null,
|
| 1604 |
+
"overflow_y": null,
|
| 1605 |
+
"padding": null,
|
| 1606 |
+
"right": null,
|
| 1607 |
+
"top": null,
|
| 1608 |
+
"visibility": null,
|
| 1609 |
+
"width": null
|
| 1610 |
+
}
|
| 1611 |
+
},
|
| 1612 |
+
"d2c93cda9bed48ccb273911aaf36048a": {
|
| 1613 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1614 |
+
"model_module_version": "1.5.0",
|
| 1615 |
+
"model_name": "DescriptionStyleModel",
|
| 1616 |
+
"state": {
|
| 1617 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1618 |
+
"_model_module_version": "1.5.0",
|
| 1619 |
+
"_model_name": "DescriptionStyleModel",
|
| 1620 |
+
"_view_count": null,
|
| 1621 |
+
"_view_module": "@jupyter-widgets/base",
|
| 1622 |
+
"_view_module_version": "1.2.0",
|
| 1623 |
+
"_view_name": "StyleView",
|
| 1624 |
+
"description_width": ""
|
| 1625 |
+
}
|
| 1626 |
+
},
|
| 1627 |
+
"d79bec3cedfb4ded91d9d164a266ea3c": {
|
| 1628 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1629 |
+
"model_module_version": "1.5.0",
|
| 1630 |
+
"model_name": "HTMLModel",
|
| 1631 |
+
"state": {
|
| 1632 |
+
"_dom_classes": [],
|
| 1633 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1634 |
+
"_model_module_version": "1.5.0",
|
| 1635 |
+
"_model_name": "HTMLModel",
|
| 1636 |
+
"_view_count": null,
|
| 1637 |
+
"_view_module": "@jupyter-widgets/controls",
|
| 1638 |
+
"_view_module_version": "1.5.0",
|
| 1639 |
+
"_view_name": "HTMLView",
|
| 1640 |
+
"description": "",
|
| 1641 |
+
"description_tooltip": null,
|
| 1642 |
+
"layout": "IPY_MODEL_aa22d45919ae45e4852612397edf8e6b",
|
| 1643 |
+
"placeholder": "",
|
| 1644 |
+
"style": "IPY_MODEL_6735ff3730f74fed8b055a7f19afd508",
|
| 1645 |
+
"value": " 5/5 [00:00<00:00, 82.52ex/s]"
|
| 1646 |
+
}
|
| 1647 |
+
},
|
| 1648 |
+
"dcf6b07ee9cb4f16b314dbe42a5e7abe": {
|
| 1649 |
+
"model_module": "@jupyter-widgets/controls",
|
| 1650 |
+
"model_module_version": "1.5.0",
|
| 1651 |
+
"model_name": "HTMLModel",
|
| 1652 |
+
"state": {
|
| 1653 |
+
"_dom_classes": [],
|
| 1654 |
+
"_model_module": "@jupyter-widgets/controls",
|
| 1655 |
+
"_model_module_version": "1.5.0",
|
| 1656 |
+
"_model_name": "HTMLModel",
|
| 1657 |
+
"_view_count": null,
|
| 1658 |
+
"_view_module": "@jupyter-widgets/controls",
|
| 1659 |
+
"_view_module_version": "1.5.0",
|
| 1660 |
+
"_view_name": "HTMLView",
|
| 1661 |
+
"description": "",
|
| 1662 |
+
"description_tooltip": null,
|
| 1663 |
+
"layout": "IPY_MODEL_b7789187719a4774a9d31cb1f5b82884",
|
| 1664 |
+
"placeholder": "",
|
| 1665 |
+
"style": "IPY_MODEL_6fe5d4d95b844ceba24521a27090e7ae",
|
| 1666 |
+
"value": " 5/5 [00:00<00:00, 59.91ex/s]"
|
| 1667 |
+
}
|
| 1668 |
+
}
|
| 1669 |
+
},
|
| 1670 |
+
"version_major": 2,
|
| 1671 |
+
"version_minor": 0
|
| 1672 |
+
}
|
| 1673 |
+
}
|
| 1674 |
+
},
|
| 1675 |
+
"nbformat": 4,
|
| 1676 |
+
"nbformat_minor": 5
|
| 1677 |
+
}
|
code/inference-single-model-no-pp-privatelb-0-963.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# %%
|
| 3 |
+
import re
|
| 4 |
+
import os
|
| 5 |
+
import json
|
| 6 |
+
import torch
|
| 7 |
+
import argparse
|
| 8 |
+
from tqdm import tqdm
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from itertools import chain
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import pandas as pd
|
| 14 |
+
from transformers import AutoTokenizer, AutoModelForTokenClassification, AutoModel, AutoConfig, DataCollatorForTokenClassification
|
| 15 |
+
from datasets import Dataset
|
| 16 |
+
from spacy.lang.en import English
|
| 17 |
+
|
| 18 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 19 |
+
|
| 20 |
+
USE_SIMPLIFIED_LABELS_INFERENCE = True # <<< CHANGE THIS TO False TO USE ORIGINAL BIO LABELS FOR INFERENCE
|
| 21 |
+
|
| 22 |
+
# %%
|
| 23 |
+
backbone = 'microsoft/deberta-v3-large'
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
model_dir_for_inference = '/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/deberta_v3_large_w10_ado1_wd0_bs2_lr1e5_5epo_simplified_labels_fold0_best_hf'
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# th = 0.5 # Threshold for 'O' class - replaced by 'O' scaling
|
| 30 |
+
o_class_scaler = 0.1 # Factor to scale down 'O' class probability
|
| 31 |
+
|
| 32 |
+
# Define label sets based on the flag
|
| 33 |
+
if USE_SIMPLIFIED_LABELS_INFERENCE:
|
| 34 |
+
pii_types_active = [
|
| 35 |
+
'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
|
| 36 |
+
'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
|
| 37 |
+
]
|
| 38 |
+
all_labels_active_inf = sorted(pii_types_active + ['O'])
|
| 39 |
+
else:
|
| 40 |
+
# Original BIO labels (ensure this matches the set used in training if USE_SIMPLIFIED_LABELS was False)
|
| 41 |
+
original_target_bio_inf = sorted([
|
| 42 |
+
'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
|
| 43 |
+
'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
|
| 44 |
+
'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL',
|
| 45 |
+
'O'
|
| 46 |
+
])
|
| 47 |
+
all_labels_active_inf = original_target_bio_inf
|
| 48 |
+
|
| 49 |
+
id2label_active_inf = {i: l for i, l in enumerate(all_labels_active_inf)}
|
| 50 |
+
# Determine O_LABEL_INDEX for thresholding
|
| 51 |
+
O_LABEL_STR = "O"
|
| 52 |
+
O_LABEL_INDEX = -1
|
| 53 |
+
for i, label_str_val in id2label_active_inf.items():
|
| 54 |
+
if label_str_val == O_LABEL_STR:
|
| 55 |
+
O_LABEL_INDEX = i
|
| 56 |
+
break
|
| 57 |
+
if O_LABEL_INDEX == -1:
|
| 58 |
+
raise ValueError(f"Could not find '{O_LABEL_STR}' in id2label_active_inf: {id2label_active_inf}")
|
| 59 |
+
|
| 60 |
+
print(f"Using {'simplified' if USE_SIMPLIFIED_LABELS_INFERENCE else 'original BIO'} id2label for inference: {id2label_active_inf}")
|
| 61 |
+
print(f"Index for 'O' label is: {O_LABEL_INDEX}")
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# %%
|
| 65 |
+
def tokenize(example, tokenizer):
|
| 66 |
+
text = []
|
| 67 |
+
token_map = []
|
| 68 |
+
|
| 69 |
+
idx = 0
|
| 70 |
+
|
| 71 |
+
for t, ws in zip(example["tokens"], example["trailing_whitespace"]):
|
| 72 |
+
|
| 73 |
+
text.append(t)
|
| 74 |
+
token_map.extend([idx]*len(t))
|
| 75 |
+
if ws:
|
| 76 |
+
text.append(" ")
|
| 77 |
+
token_map.append(-1)
|
| 78 |
+
|
| 79 |
+
idx += 1
|
| 80 |
+
|
| 81 |
+
tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
return {
|
| 85 |
+
**tokenized,
|
| 86 |
+
"token_map": token_map,
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
# %%
|
| 90 |
+
with open("/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/test.json", "r") as f:
|
| 91 |
+
data = json.load(f)
|
| 92 |
+
|
| 93 |
+
ds = Dataset.from_dict({
|
| 94 |
+
"full_text": [x["full_text"] for x in data],
|
| 95 |
+
"document": [x["document"] for x in data],
|
| 96 |
+
"tokens": [x["tokens"] for x in data],
|
| 97 |
+
"trailing_whitespace": [x["trailing_whitespace"] for x in data],
|
| 98 |
+
})
|
| 99 |
+
|
| 100 |
+
tokenizer = AutoTokenizer.from_pretrained(model_dir_for_inference)
|
| 101 |
+
collator = DataCollatorForTokenClassification(tokenizer)
|
| 102 |
+
ds = ds.map(tokenize, fn_kwargs={"tokenizer": tokenizer}, num_proc=2)
|
| 103 |
+
|
| 104 |
+
# %%
|
| 105 |
+
ds[0].keys()
|
| 106 |
+
|
| 107 |
+
# %%
|
| 108 |
+
|
| 109 |
+
print(f"Loading model from: {model_dir_for_inference}")
|
| 110 |
+
model = AutoModelForTokenClassification.from_pretrained(model_dir_for_inference)
|
| 111 |
+
model.eval()
|
| 112 |
+
model.to(device)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# %%
|
| 116 |
+
preds_final_simplified_ids = []
|
| 117 |
+
with torch.no_grad():
|
| 118 |
+
for sample in tqdm(ds, desc="Inferencing"):
|
| 119 |
+
input_ids = torch.tensor([sample['input_ids']]).to(device)
|
| 120 |
+
attention_mask = torch.tensor([sample['attention_mask']]).to(device)
|
| 121 |
+
|
| 122 |
+
outputs = model(input_ids, attention_mask=attention_mask)
|
| 123 |
+
logits = outputs.logits # Shape (1, seq_len, num_active_labels)
|
| 124 |
+
|
| 125 |
+
all_probs = logits.softmax(dim=2) # Shape (1, seq_len, num_active_labels)
|
| 126 |
+
|
| 127 |
+
# scale down 'O' probability
|
| 128 |
+
all_probs_scaled = all_probs.clone()
|
| 129 |
+
if O_LABEL_INDEX != -1:
|
| 130 |
+
all_probs_scaled[:, :, O_LABEL_INDEX] *= o_class_scaler
|
| 131 |
+
else:
|
| 132 |
+
print("Warning: O_LABEL_INDEX not found, 'O' class scaling not applied.")
|
| 133 |
+
|
| 134 |
+
predicted_ids = torch.argmax(all_probs_scaled, dim=2).cpu().numpy()
|
| 135 |
+
preds_final_simplified_ids.append(predicted_ids[0]) # remove batch dim
|
| 136 |
+
|
| 137 |
+
# %%
|
| 138 |
+
# Store simplified predictions mapped to original tokens first
|
| 139 |
+
intermediate_processed = []
|
| 140 |
+
unique_token_preds = set() # To handle cases where multiple subwords map to the same original token
|
| 141 |
+
|
| 142 |
+
for pred_ids_for_doc, token_map_for_doc, offsets_for_doc, tokens_for_doc, doc_id in zip(
|
| 143 |
+
preds_final_simplified_ids, ds["token_map"], ds["offset_mapping"], ds["tokens"], ds["document"]
|
| 144 |
+
):
|
| 145 |
+
for subword_pred_id, (start_char_idx, end_char_idx) in zip(pred_ids_for_doc, offsets_for_doc):
|
| 146 |
+
active_label_pred_str = id2label_active_inf[subword_pred_id]
|
| 147 |
+
|
| 148 |
+
if start_char_idx == end_char_idx == 0: # Skip special tokens like [CLS], [SEP] based on offset
|
| 149 |
+
continue
|
| 150 |
+
|
| 151 |
+
# Find the original token_id using the start_char_idx from offset_mapping
|
| 152 |
+
current_char_idx = start_char_idx
|
| 153 |
+
if current_char_idx < len(token_map_for_doc) and token_map_for_doc[current_char_idx] == -1 and current_char_idx + 1 < len(token_map_for_doc):
|
| 154 |
+
current_char_idx +=1
|
| 155 |
+
|
| 156 |
+
if current_char_idx >= len(token_map_for_doc):
|
| 157 |
+
continue
|
| 158 |
+
|
| 159 |
+
original_token_id = token_map_for_doc[current_char_idx]
|
| 160 |
+
|
| 161 |
+
# Ignore "O" predictions and invalid token_ids
|
| 162 |
+
if active_label_pred_str != "O" and original_token_id != -1:
|
| 163 |
+
token_text_content = tokens_for_doc[original_token_id].strip()
|
| 164 |
+
# Only proceed if the token is not empty after stripping whitespace
|
| 165 |
+
if token_text_content:
|
| 166 |
+
prediction_key = (doc_id, original_token_id)
|
| 167 |
+
if prediction_key not in unique_token_preds:
|
| 168 |
+
intermediate_processed.append({
|
| 169 |
+
"document": doc_id,
|
| 170 |
+
"token": original_token_id,
|
| 171 |
+
"label_pred": active_label_pred_str, # Store the predicted label
|
| 172 |
+
"token_str": tokens_for_doc[original_token_id] # Store original token
|
| 173 |
+
})
|
| 174 |
+
unique_token_preds.add(prediction_key)
|
| 175 |
+
|
| 176 |
+
# %%
|
| 177 |
+
df_intermediate = pd.DataFrame(intermediate_processed)
|
| 178 |
+
|
| 179 |
+
final_predictions_for_submission = []
|
| 180 |
+
|
| 181 |
+
if not df_intermediate.empty:
|
| 182 |
+
if USE_SIMPLIFIED_LABELS_INFERENCE:
|
| 183 |
+
# NAME_STUDENT capitalization check
|
| 184 |
+
name_student_mask = df_intermediate['label_pred'] == 'NAME_STUDENT'
|
| 185 |
+
|
| 186 |
+
def is_potentially_name_case(s):
|
| 187 |
+
if not s: return False
|
| 188 |
+
return (s[0].isalpha() and s[0].isupper()) or s.isupper()
|
| 189 |
+
|
| 190 |
+
not_name_case_mask = ~df_intermediate['token_str'].apply(is_potentially_name_case)
|
| 191 |
+
numeric_token_mask = df_intermediate['token_str'].apply(lambda x: x.isdigit())
|
| 192 |
+
rows_to_filter_out_indices = df_intermediate[name_student_mask & (not_name_case_mask | numeric_token_mask)].index
|
| 193 |
+
|
| 194 |
+
if not rows_to_filter_out_indices.empty:
|
| 195 |
+
print(f"Applying NAME_STUDENT capitalization/numeric heuristic: Filtering out {len(rows_to_filter_out_indices)} rows.")
|
| 196 |
+
df_intermediate = df_intermediate.drop(rows_to_filter_out_indices)
|
| 197 |
+
|
| 198 |
+
if not df_intermediate.empty:
|
| 199 |
+
df_intermediate = df_intermediate.sort_values(by=["document", "token"])
|
| 200 |
+
|
| 201 |
+
for doc_id, group in df_intermediate.groupby("document"):
|
| 202 |
+
sorted_group = group.sort_values("token")
|
| 203 |
+
|
| 204 |
+
previous_token_id = -1
|
| 205 |
+
previous_pii_type_simplified = None
|
| 206 |
+
|
| 207 |
+
for _, row in sorted_group.iterrows():
|
| 208 |
+
current_token_id = row["token"]
|
| 209 |
+
current_label_pred = row["label_pred"] #direct model output
|
| 210 |
+
|
| 211 |
+
final_label_to_submit = ""
|
| 212 |
+
|
| 213 |
+
if USE_SIMPLIFIED_LABELS_INFERENCE:
|
| 214 |
+
# Reconstruct BIO
|
| 215 |
+
# A new entity starts if:
|
| 216 |
+
# 1. It's the first PII token for the document.
|
| 217 |
+
# 2. The current token is not contiguous with the previous PII token.
|
| 218 |
+
# 3. The PII type changes.
|
| 219 |
+
if (previous_pii_type_simplified is None) or \
|
| 220 |
+
(current_token_id != previous_token_id + 1) or \
|
| 221 |
+
(previous_pii_type_simplified != current_label_pred): # current_label_pred is simplified here
|
| 222 |
+
bio_tag = "B-"
|
| 223 |
+
else:
|
| 224 |
+
bio_tag = "I-"
|
| 225 |
+
final_label_to_submit = bio_tag + current_label_pred
|
| 226 |
+
previous_pii_type_simplified = current_label_pred
|
| 227 |
+
else:
|
| 228 |
+
if current_label_pred != "O":
|
| 229 |
+
final_label_to_submit = current_label_pred
|
| 230 |
+
else:
|
| 231 |
+
continue
|
| 232 |
+
|
| 233 |
+
if final_label_to_submit:
|
| 234 |
+
final_predictions_for_submission.append({
|
| 235 |
+
"document": doc_id,
|
| 236 |
+
"token": current_token_id,
|
| 237 |
+
"label": final_label_to_submit,
|
| 238 |
+
"token_str": row["token_str"]
|
| 239 |
+
})
|
| 240 |
+
|
| 241 |
+
previous_token_id = current_token_id
|
| 242 |
+
else:
|
| 243 |
+
print("No PII predictions made after initial filtering.")
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
# %%
|
| 247 |
+
df_final_submission = pd.DataFrame(final_predictions_for_submission)
|
| 248 |
+
if not df_final_submission.empty:
|
| 249 |
+
df_final_submission["row_id"] = list(range(len(df_final_submission)))
|
| 250 |
+
print(df_final_submission.head(20))
|
| 251 |
+
df_submission = df_final_submission[["row_id", "document", "token", "label"]]
|
| 252 |
+
else:
|
| 253 |
+
df_submission = pd.DataFrame(columns=["row_id", "document", "token", "label"])
|
| 254 |
+
print("Final DataFrame is empty. No PII entities to submit.")
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
# %%
|
| 258 |
+
df_submission.to_csv("submission.csv", index=False)
|
| 259 |
+
print("Submission file created.")
|
| 260 |
+
|
| 261 |
+
# %%
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
|
code/inference_actual_orig.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# %% [markdown]
|
| 2 |
+
# ## Introduction
|
| 3 |
+
#
|
| 4 |
+
# Congratulations to everyone who persevered through this challenging competition!
|
| 5 |
+
#
|
| 6 |
+
# I've been eagerly looking forward to reading the top solutions, and now that the competition has finally concluded, I am thrilled.
|
| 7 |
+
#
|
| 8 |
+
# It has been a great learning journey for all of us. I am excited to share that I plan to publish a training notebook that demonstrates how to achieve a score of `Private Leaderboard 0.963` using a single model.
|
| 9 |
+
#
|
| 10 |
+
# [Training Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/training-single-model-to-get-privatelb-0-963/)
|
| 11 |
+
#
|
| 12 |
+
# And this notebook shows how to submit the model trained above.
|
| 13 |
+
#
|
| 14 |
+
#
|
| 15 |
+
# ## Acknowledge
|
| 16 |
+
#
|
| 17 |
+
# I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
|
| 18 |
+
#
|
| 19 |
+
# For training a single model the above notebook shows, it takes less than 40min!
|
| 20 |
+
|
| 21 |
+
# %%
|
| 22 |
+
import re
|
| 23 |
+
import os
|
| 24 |
+
import json
|
| 25 |
+
import torch
|
| 26 |
+
import argparse
|
| 27 |
+
from pathlib import Path
|
| 28 |
+
from itertools import chain
|
| 29 |
+
|
| 30 |
+
import numpy as np
|
| 31 |
+
import pandas as pd
|
| 32 |
+
from transformers import AutoTokenizer, AutoModelForTokenClassification, AutoModel, AutoConfig, DataCollatorForTokenClassification
|
| 33 |
+
from datasets import Dataset
|
| 34 |
+
from spacy.lang.en import English
|
| 35 |
+
|
| 36 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 37 |
+
|
| 38 |
+
# %%
|
| 39 |
+
!ls ../input/
|
| 40 |
+
|
| 41 |
+
# %%
|
| 42 |
+
backbone = 'microsoft/deberta-v3-large'
|
| 43 |
+
model_dir = '/kaggle/input/pii-dbtv3-large-9688'
|
| 44 |
+
th = 0.5
|
| 45 |
+
|
| 46 |
+
id2label = {0: 'B-EMAIL', 1: 'B-ID_NUM', 2: 'B-NAME_STUDENT', 3: 'B-PHONE_NUM', 4: 'B-STREET_ADDRESS', 5: 'B-URL_PERSONAL', 6: 'B-USERNAME', 7: 'I-ID_NUM', 8: 'I-NAME_STUDENT', 9: 'I-PHONE_NUM', 10: 'I-STREET_ADDRESS', 11: 'I-URL_PERSONAL', 12: 'O'}
|
| 47 |
+
|
| 48 |
+
# %%
|
| 49 |
+
def tokenize(example, tokenizer):
|
| 50 |
+
text = []
|
| 51 |
+
token_map = []
|
| 52 |
+
|
| 53 |
+
idx = 0
|
| 54 |
+
|
| 55 |
+
for t, ws in zip(example["tokens"], example["trailing_whitespace"]):
|
| 56 |
+
|
| 57 |
+
text.append(t)
|
| 58 |
+
token_map.extend([idx]*len(t))
|
| 59 |
+
if ws:
|
| 60 |
+
text.append(" ")
|
| 61 |
+
token_map.append(-1)
|
| 62 |
+
|
| 63 |
+
idx += 1
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
return {
|
| 70 |
+
**tokenized,
|
| 71 |
+
"token_map": token_map,
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
# %%
|
| 75 |
+
with open("/kaggle/input/pii-detection-removal-from-educational-data/test.json", "r") as f:
|
| 76 |
+
data = json.load(f)
|
| 77 |
+
|
| 78 |
+
ds = Dataset.from_dict({
|
| 79 |
+
"full_text": [x["full_text"] for x in data],
|
| 80 |
+
"document": [x["document"] for x in data],
|
| 81 |
+
"tokens": [x["tokens"] for x in data],
|
| 82 |
+
"trailing_whitespace": [x["trailing_whitespace"] for x in data],
|
| 83 |
+
})
|
| 84 |
+
|
| 85 |
+
tokenizer = AutoTokenizer.from_pretrained(os.path.join(model_dir, 'fold0'))
|
| 86 |
+
collator = DataCollatorForTokenClassification(tokenizer)
|
| 87 |
+
ds = ds.map(tokenize, fn_kwargs={"tokenizer": tokenizer}, num_proc=2)
|
| 88 |
+
|
| 89 |
+
# %%
|
| 90 |
+
ds[0].keys()
|
| 91 |
+
|
| 92 |
+
# %%
|
| 93 |
+
def get_model(conf):
|
| 94 |
+
return AutoModelForTokenClassification.from_config(
|
| 95 |
+
conf
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
fold = 0
|
| 99 |
+
conf = AutoConfig.from_pretrained(os.path.join(model_dir, f'fold{fold}'))
|
| 100 |
+
model = get_model(conf)
|
| 101 |
+
# model_file = os.path.join(model_dir, f'fold{fold}', f'genv1_noext1_ds3_ht1024_dbtv3_large_wu0.1_w10_ado4_wd0_bs2_lr1e5_5f_5epo_fold{fold}_best.pth')
|
| 102 |
+
model_file = "/kaggle/input/single_model_only_comp_data/pytorch/training_length_4096/1/deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_best.pth"
|
| 103 |
+
sd = torch.load(model_file, map_location='cpu')
|
| 104 |
+
model.load_state_dict(sd, strict=True)
|
| 105 |
+
model.eval()
|
| 106 |
+
model.cuda()
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# %%
|
| 110 |
+
!nvidia-smi
|
| 111 |
+
|
| 112 |
+
# %%
|
| 113 |
+
preds_final = []
|
| 114 |
+
with torch.no_grad():
|
| 115 |
+
for sample in ds:
|
| 116 |
+
input_ids = torch.tensor([sample['input_ids']]).to(device)
|
| 117 |
+
attention_mask = torch.tensor([sample['attention_mask']]).to(device)
|
| 118 |
+
|
| 119 |
+
preds = model(input_ids, attention_mask=attention_mask, labels=None)['logits'].softmax(2)
|
| 120 |
+
preds[:, :, -1][preds[:, :, -1] < th] = 0
|
| 121 |
+
preds = torch.argmax(preds, axis=2).cpu().numpy()
|
| 122 |
+
preds_final.append(preds[0])
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
# %%
|
| 126 |
+
!nvidia-smi
|
| 127 |
+
|
| 128 |
+
# %%
|
| 129 |
+
pairs = []
|
| 130 |
+
processed = []
|
| 131 |
+
for p, token_map, offsets, tokens, doc in zip(preds_final, ds["token_map"],
|
| 132 |
+
ds["offset_mapping"], ds["tokens"],
|
| 133 |
+
ds["document"]):
|
| 134 |
+
for token_pred, (start_idx, end_idx) in zip(p, offsets):
|
| 135 |
+
label_pred = id2label[token_pred]
|
| 136 |
+
|
| 137 |
+
if start_idx + end_idx == 0:
|
| 138 |
+
continue
|
| 139 |
+
|
| 140 |
+
if token_map[start_idx] == -1:
|
| 141 |
+
start_idx += 1
|
| 142 |
+
|
| 143 |
+
# ignore "\n\n"
|
| 144 |
+
while start_idx < len(token_map) and tokens[token_map[start_idx]].isspace():
|
| 145 |
+
start_idx += 1
|
| 146 |
+
|
| 147 |
+
if start_idx >= len(token_map):
|
| 148 |
+
break
|
| 149 |
+
|
| 150 |
+
token_id = token_map[start_idx]
|
| 151 |
+
|
| 152 |
+
# ignore "O" predictions and whitespace preds
|
| 153 |
+
if label_pred not in ("O") and token_id != -1:
|
| 154 |
+
# if label_pred not in ("O", "B-EMAIL", "B-PHONE_NUM", "I-PHONE_NUM") and token_id != -1:
|
| 155 |
+
pair = (doc, token_id)
|
| 156 |
+
|
| 157 |
+
if pair not in pairs:
|
| 158 |
+
processed.append({"document": doc, "token": token_id, "label": label_pred, "token_str": tokens[token_id]})
|
| 159 |
+
pairs.append(pair)
|
| 160 |
+
|
| 161 |
+
# %%
|
| 162 |
+
df = pd.DataFrame(processed)
|
| 163 |
+
df["row_id"] = list(range(len(df)))
|
| 164 |
+
display(df.head(100))
|
| 165 |
+
|
| 166 |
+
# %%
|
| 167 |
+
df[["row_id", "document", "token", "label"]].to_csv("submission.csv", index=False)
|
| 168 |
+
|
| 169 |
+
# %%
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
|
code/kaggle_upload.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import kagglehub
|
| 2 |
+
|
| 3 |
+
kagglehub.login()
|
| 4 |
+
|
| 5 |
+
# Replace with path to directory containing model files.
|
| 6 |
+
LOCAL_MODEL_DIR = '/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/deberta_v3_large_w10_ado1_wd0_bs2_lr1e5_5epo_simplified_labels_fold0_best_hf'
|
| 7 |
+
|
| 8 |
+
MODEL_SLUG = 'Single_model_only_comp_data' # Replace with model slug.
|
| 9 |
+
|
| 10 |
+
# Learn more about naming model variations at
|
| 11 |
+
# https://www.kaggle.com/docs/models#name-model.
|
| 12 |
+
VARIATION_SLUG = 'training_length_4096_8labels_moredata' # Replace with variation slug.
|
| 13 |
+
|
| 14 |
+
kagglehub.model_upload(
|
| 15 |
+
handle = f"andreifrcanu/{MODEL_SLUG}/transformers/{VARIATION_SLUG}",
|
| 16 |
+
local_model_dir = LOCAL_MODEL_DIR,
|
| 17 |
+
version_notes = 'training_length_4096_8labels_moredata')
|
| 18 |
+
# kagglehub.model_upload(
|
| 19 |
+
# handle = f"andreifrcanu/{MODEL_SLUG}/pyTorch/{VARIATION_SLUG}",
|
| 20 |
+
# local_model_dir = LOCAL_MODEL_DIR,
|
| 21 |
+
# version_notes = 'training_length_4096_origlabels_actualmoredata')
|
code/submission.csv
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
row_id,document,token,label
|
| 2 |
+
0,7,9,B-NAME_STUDENT
|
| 3 |
+
1,7,10,I-NAME_STUDENT
|
| 4 |
+
2,7,52,B-NAME_STUDENT
|
| 5 |
+
3,7,482,B-NAME_STUDENT
|
| 6 |
+
4,7,483,I-NAME_STUDENT
|
| 7 |
+
5,7,741,B-NAME_STUDENT
|
| 8 |
+
6,7,742,I-NAME_STUDENT
|
| 9 |
+
7,10,0,B-NAME_STUDENT
|
| 10 |
+
8,10,1,I-NAME_STUDENT
|
| 11 |
+
9,10,465,B-NAME_STUDENT
|
| 12 |
+
10,16,4,B-NAME_STUDENT
|
| 13 |
+
11,16,5,I-NAME_STUDENT
|
| 14 |
+
12,20,5,B-NAME_STUDENT
|
| 15 |
+
13,20,6,I-NAME_STUDENT
|
| 16 |
+
14,56,13,B-NAME_STUDENT
|
| 17 |
+
15,86,6,B-NAME_STUDENT
|
| 18 |
+
16,86,7,I-NAME_STUDENT
|
| 19 |
+
17,93,0,B-NAME_STUDENT
|
| 20 |
+
18,93,1,I-NAME_STUDENT
|
| 21 |
+
19,104,8,B-NAME_STUDENT
|
| 22 |
+
20,104,9,I-NAME_STUDENT
|
| 23 |
+
21,112,6,B-NAME_STUDENT
|
| 24 |
+
22,123,33,B-NAME_STUDENT
|
| 25 |
+
23,123,35,B-ID_NUM
|
| 26 |
+
24,123,38,B-NAME_STUDENT
|
| 27 |
+
25,123,224,B-NAME_STUDENT
|
| 28 |
+
26,123,741,B-URL_PERSONAL
|
| 29 |
+
27,123,1509,B-URL_PERSONAL
|
| 30 |
+
28,123,1549,B-URL_PERSONAL
|
| 31 |
+
29,123,1550,I-URL_PERSONAL
|
| 32 |
+
30,123,1575,B-URL_PERSONAL
|
| 33 |
+
31,123,1591,B-URL_PERSONAL
|
| 34 |
+
32,123,1648,B-URL_PERSONAL
|
| 35 |
+
33,123,1690,B-URL_PERSONAL
|
| 36 |
+
34,123,1691,I-URL_PERSONAL
|
code/training-actualorig-withotherdata.py
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# %% [markdown]
|
| 2 |
+
# **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
|
| 3 |
+
#
|
| 4 |
+
# # Introduction
|
| 5 |
+
#
|
| 6 |
+
# Hi all,
|
| 7 |
+
#
|
| 8 |
+
# In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
|
| 9 |
+
#
|
| 10 |
+
# Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
|
| 11 |
+
#
|
| 12 |
+
# Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
|
| 13 |
+
#
|
| 14 |
+
# Let's dive into the details and uncover the techniques that led to a successful submission!
|
| 15 |
+
#
|
| 16 |
+
# [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
|
| 17 |
+
#
|
| 18 |
+
# # Summary
|
| 19 |
+
#
|
| 20 |
+
# * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
|
| 21 |
+
# * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
|
| 22 |
+
# * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
|
| 23 |
+
# * For inference, no truncation.
|
| 24 |
+
# * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
|
| 25 |
+
# * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
|
| 26 |
+
# * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
|
| 27 |
+
# * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
|
| 28 |
+
# * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
|
| 29 |
+
# * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
|
| 30 |
+
# * cosine with warmup learning rate schedule -- almost became a standard setting.
|
| 31 |
+
# * warmup 0.5 epo
|
| 32 |
+
# * cosine 4.5 epo
|
| 33 |
+
#
|
| 34 |
+
# # Acknowledge
|
| 35 |
+
#
|
| 36 |
+
# I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
|
| 37 |
+
#
|
| 38 |
+
# For training a single model this notebook shows, it takes less than 40min!
|
| 39 |
+
#
|
| 40 |
+
|
| 41 |
+
# %%
|
| 42 |
+
import os
|
| 43 |
+
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
|
| 44 |
+
import warnings
|
| 45 |
+
warnings.filterwarnings("ignore")
|
| 46 |
+
DEBUG=False
|
| 47 |
+
|
| 48 |
+
# %%
|
| 49 |
+
# !pip install -q seqeval
|
| 50 |
+
|
| 51 |
+
# %%
|
| 52 |
+
import gc
|
| 53 |
+
import json
|
| 54 |
+
import copy
|
| 55 |
+
import time
|
| 56 |
+
import pickle
|
| 57 |
+
import numpy as np
|
| 58 |
+
import pandas as pd
|
| 59 |
+
from itertools import chain
|
| 60 |
+
from transformers import AutoTokenizer
|
| 61 |
+
from transformers import AutoModelForTokenClassification
|
| 62 |
+
from transformers import get_cosine_schedule_with_warmup
|
| 63 |
+
from tqdm import tqdm
|
| 64 |
+
|
| 65 |
+
from seqeval.metrics import recall_score, precision_score
|
| 66 |
+
from seqeval.metrics import f1_score
|
| 67 |
+
|
| 68 |
+
import torch
|
| 69 |
+
from torch import nn
|
| 70 |
+
from torch.utils.data import Dataset, DataLoader
|
| 71 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 72 |
+
from torch.nn import CrossEntropyLoss
|
| 73 |
+
from torch.optim import AdamW
|
| 74 |
+
|
| 75 |
+
import matplotlib.pyplot as plt
|
| 76 |
+
from pylab import rcParams
|
| 77 |
+
|
| 78 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 79 |
+
|
| 80 |
+
# %% [markdown]
|
| 81 |
+
# # Config
|
| 82 |
+
|
| 83 |
+
# %%
|
| 84 |
+
exp_name = 'deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo'
|
| 85 |
+
|
| 86 |
+
backbone = 'microsoft/deberta-v3-large'
|
| 87 |
+
|
| 88 |
+
TRAINING_MAX_LENGTH = 4096
|
| 89 |
+
batch_size = 2
|
| 90 |
+
lr = 1e-5
|
| 91 |
+
ds_rate = 0.3
|
| 92 |
+
attention_probs_dropout_prob = 0.4
|
| 93 |
+
hidden_dropout_prob = 0.1
|
| 94 |
+
wd = 0.
|
| 95 |
+
pos_weight = 10
|
| 96 |
+
n_epochs = 5
|
| 97 |
+
|
| 98 |
+
use_amp = True
|
| 99 |
+
|
| 100 |
+
# %% [markdown]
|
| 101 |
+
# # Data
|
| 102 |
+
|
| 103 |
+
# %%
|
| 104 |
+
from sklearn.model_selection import train_test_split
|
| 105 |
+
|
| 106 |
+
data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
|
| 107 |
+
for sample in data:
|
| 108 |
+
if len(set(sample['labels'])) > 1:
|
| 109 |
+
sample['is_pos'] = 1
|
| 110 |
+
else:
|
| 111 |
+
sample['is_pos'] = 0
|
| 112 |
+
print("original datapoints: ", len(data))
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
moredata = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/moredata_dataset_fixed.json'))
|
| 116 |
+
print("moredata datapoints: ", len(moredata))
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# %%
|
| 120 |
+
from sklearn.model_selection import GroupKFold
|
| 121 |
+
import random
|
| 122 |
+
random.seed(0)
|
| 123 |
+
kf = GroupKFold(n_splits=5)
|
| 124 |
+
folds = []
|
| 125 |
+
for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
|
| 126 |
+
folds.append([data[i]['document'] for i in valid_index])
|
| 127 |
+
|
| 128 |
+
# %%
|
| 129 |
+
all_labels = sorted(list(set(chain(*[x["labels"] for x in data]))))
|
| 130 |
+
label2id = {l: i for i,l in enumerate(all_labels)}
|
| 131 |
+
id2label = {v:k for k,v in label2id.items()}
|
| 132 |
+
|
| 133 |
+
target = [
|
| 134 |
+
'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
|
| 135 |
+
'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
|
| 136 |
+
'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL'
|
| 137 |
+
]
|
| 138 |
+
|
| 139 |
+
print(len(all_labels), id2label)
|
| 140 |
+
|
| 141 |
+
# %% [markdown]
|
| 142 |
+
# # Dataset
|
| 143 |
+
|
| 144 |
+
# %%
|
| 145 |
+
def tokenize(example, tokenizer, label2id, max_length):
|
| 146 |
+
|
| 147 |
+
# rebuild text from tokens
|
| 148 |
+
text = []
|
| 149 |
+
labels = []
|
| 150 |
+
|
| 151 |
+
for t, l, ws in zip(
|
| 152 |
+
example["tokens"], example["provided_labels"], example["trailing_whitespace"]
|
| 153 |
+
):
|
| 154 |
+
text.append(t)
|
| 155 |
+
labels.extend([l] * len(t))
|
| 156 |
+
|
| 157 |
+
if ws:
|
| 158 |
+
text.append(" ")
|
| 159 |
+
labels.append("O")
|
| 160 |
+
|
| 161 |
+
tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
|
| 162 |
+
if len(tokenized['input_ids']) > max_length:
|
| 163 |
+
for key in tokenized.keys():
|
| 164 |
+
tokenized[key] = tokenized[key][:max_length//2] + tokenized[key][-max_length//2:]
|
| 165 |
+
|
| 166 |
+
labels = np.array(labels)
|
| 167 |
+
|
| 168 |
+
text = "".join(text)
|
| 169 |
+
token_labels = []
|
| 170 |
+
|
| 171 |
+
for start_idx, end_idx in tokenized.offset_mapping:
|
| 172 |
+
# CLS token
|
| 173 |
+
if start_idx == 0 and end_idx == 0:
|
| 174 |
+
token_labels.append(label2id["O"])
|
| 175 |
+
continue
|
| 176 |
+
|
| 177 |
+
# case when token starts with whitespace
|
| 178 |
+
if text[start_idx].isspace():
|
| 179 |
+
start_idx += 1
|
| 180 |
+
|
| 181 |
+
token_labels.append(label2id[labels[start_idx]])
|
| 182 |
+
|
| 183 |
+
length = len(tokenized.input_ids)
|
| 184 |
+
|
| 185 |
+
return {**tokenized, "labels": token_labels, "length": length}
|
| 186 |
+
|
| 187 |
+
# %%
|
| 188 |
+
tokenizer = AutoTokenizer.from_pretrained(backbone)
|
| 189 |
+
|
| 190 |
+
class PIIDataset(Dataset):
|
| 191 |
+
def __init__(self, tokenizer, data, label2id, max_length):
|
| 192 |
+
self.tokenizer = tokenizer
|
| 193 |
+
self.data = copy.deepcopy(data)
|
| 194 |
+
self.label2id = label2id
|
| 195 |
+
self.max_length = max_length
|
| 196 |
+
|
| 197 |
+
for i in tqdm(range(len(self.data))):
|
| 198 |
+
if self.data[i].get('input_ids') is None:
|
| 199 |
+
self.data[i]['provided_labels'] = self.data[i]['labels']
|
| 200 |
+
self.data[i] = tokenize(
|
| 201 |
+
self.data[i],
|
| 202 |
+
self.tokenizer,
|
| 203 |
+
self.label2id,
|
| 204 |
+
self.max_length
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
def __len__(self):
|
| 208 |
+
return len(self.data)
|
| 209 |
+
|
| 210 |
+
def __getitem__(self, idx):
|
| 211 |
+
return self.data[idx]
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def collate_fn(batch):
|
| 215 |
+
input_ids = [torch.tensor(x['input_ids']) for x in batch]
|
| 216 |
+
attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
|
| 217 |
+
labels = [torch.tensor(x['labels']) for x in batch]
|
| 218 |
+
|
| 219 |
+
input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=0)
|
| 220 |
+
attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
|
| 221 |
+
labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
|
| 222 |
+
|
| 223 |
+
return {
|
| 224 |
+
'input_ids': input_ids_padded,
|
| 225 |
+
'attention_mask': attention_masks_padded,
|
| 226 |
+
'labels': labels_padded,
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
ds = PIIDataset(tokenizer, data[:20], label2id, TRAINING_MAX_LENGTH)
|
| 230 |
+
len(ds[9]['input_ids'])
|
| 231 |
+
|
| 232 |
+
# %% [markdown]
|
| 233 |
+
# # Model
|
| 234 |
+
|
| 235 |
+
# %%
|
| 236 |
+
model = AutoModelForTokenClassification.from_pretrained(
|
| 237 |
+
backbone,
|
| 238 |
+
attention_probs_dropout_prob=attention_probs_dropout_prob,
|
| 239 |
+
hidden_dropout_prob=hidden_dropout_prob,
|
| 240 |
+
num_labels=len(all_labels),
|
| 241 |
+
id2label=id2label,
|
| 242 |
+
label2id=label2id,
|
| 243 |
+
ignore_mismatched_sizes=True
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
# %% [markdown]
|
| 247 |
+
# # Loss
|
| 248 |
+
|
| 249 |
+
# %%
|
| 250 |
+
weights = torch.ones(13) * pos_weight
|
| 251 |
+
weights[12] = 1
|
| 252 |
+
|
| 253 |
+
CE = nn.CrossEntropyLoss(weight=weights.to(device), ignore_index=-100)
|
| 254 |
+
def criterion(logits, labels):
|
| 255 |
+
return CE(logits.view(-1, len(all_labels)), labels.view(-1))
|
| 256 |
+
|
| 257 |
+
# %% [markdown]
|
| 258 |
+
# # Metric
|
| 259 |
+
|
| 260 |
+
# %%
|
| 261 |
+
def compute_metrics(p, all_labels, th=0.5):
|
| 262 |
+
predictions, labels = p
|
| 263 |
+
true_predictions = []
|
| 264 |
+
true_labels = []
|
| 265 |
+
for tmp, batch_l in zip(predictions, labels):
|
| 266 |
+
batch_p = copy.deepcopy(tmp)
|
| 267 |
+
batch_p[:, :, -1][batch_p[:, :, -1] < th] = -9999
|
| 268 |
+
batch_p = np.argmax(batch_p, axis=2)
|
| 269 |
+
true_predictions.extend([all_labels[p] for (p, l) in zip(batch_p.reshape(-1), batch_l.reshape(-1)) if l != -100])
|
| 270 |
+
true_labels.extend([all_labels[l] for l in batch_l.reshape(-1) if l != -100])
|
| 271 |
+
|
| 272 |
+
recall = recall_score([true_labels], [true_predictions])
|
| 273 |
+
precision = precision_score([true_labels], [true_predictions])
|
| 274 |
+
f1_score = (1 + 5*5) * recall * precision / (5*5*precision + recall)
|
| 275 |
+
|
| 276 |
+
results = {
|
| 277 |
+
'recall': recall,
|
| 278 |
+
'precision': precision,
|
| 279 |
+
'f1': f1_score
|
| 280 |
+
}
|
| 281 |
+
return results
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
# %% [markdown]
|
| 285 |
+
# # Train & Valid Func
|
| 286 |
+
|
| 287 |
+
# %%
|
| 288 |
+
def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
|
| 289 |
+
model.train()
|
| 290 |
+
running_loss = []
|
| 291 |
+
bar = tqdm(loader, total=len(loader))
|
| 292 |
+
for batch in bar:
|
| 293 |
+
optimizer.zero_grad()
|
| 294 |
+
scheduler.step()
|
| 295 |
+
|
| 296 |
+
input_ids = batch['input_ids'].to(device)
|
| 297 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 298 |
+
labels = batch['labels'].to(device)
|
| 299 |
+
|
| 300 |
+
if scaler is not None:
|
| 301 |
+
with torch.cuda.amp.autocast():
|
| 302 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 303 |
+
loss = criterion(outputs['logits'], labels)
|
| 304 |
+
scaler.scale(loss).backward()
|
| 305 |
+
scaler.step(optimizer)
|
| 306 |
+
scaler.update()
|
| 307 |
+
else:
|
| 308 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 309 |
+
loss = criterion(outputs['logits'], labels)
|
| 310 |
+
loss.backward()
|
| 311 |
+
optimizer.step()
|
| 312 |
+
|
| 313 |
+
running_loss.append(loss.item())
|
| 314 |
+
bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
|
| 315 |
+
|
| 316 |
+
return np.mean(running_loss)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def valid_one_epoch(model, loader):
|
| 320 |
+
model.eval()
|
| 321 |
+
running_loss = []
|
| 322 |
+
bar = tqdm(loader, total=len(loader))
|
| 323 |
+
predictions = []
|
| 324 |
+
labels = []
|
| 325 |
+
|
| 326 |
+
with torch.no_grad():
|
| 327 |
+
for batch in bar:
|
| 328 |
+
input_ids = batch['input_ids'].to(device)
|
| 329 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 330 |
+
labels_ = batch['labels'].to(device)
|
| 331 |
+
|
| 332 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels_)
|
| 333 |
+
loss = criterion(outputs['logits'], labels_)
|
| 334 |
+
|
| 335 |
+
running_loss.append(loss.item())
|
| 336 |
+
predictions.append(outputs['logits'].detach().cpu().numpy())
|
| 337 |
+
labels.append(labels_.detach().cpu().numpy())
|
| 338 |
+
|
| 339 |
+
bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
|
| 340 |
+
|
| 341 |
+
return np.mean(running_loss), compute_metrics((predictions, labels), all_labels)
|
| 342 |
+
|
| 343 |
+
# %% [markdown]
|
| 344 |
+
# # Scheduler
|
| 345 |
+
|
| 346 |
+
# %%
|
| 347 |
+
rcParams['figure.figsize'] = 20, 2
|
| 348 |
+
|
| 349 |
+
def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
|
| 350 |
+
return get_cosine_schedule_with_warmup(
|
| 351 |
+
optimizer,
|
| 352 |
+
int(num_training_steps * warmup_steps_ratio),
|
| 353 |
+
num_training_steps
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
opt = AdamW(model.parameters(), lr=lr)
|
| 357 |
+
sch = get_scheduler(opt, 100, 0.1)
|
| 358 |
+
|
| 359 |
+
lrs = []
|
| 360 |
+
for epoch in range(1, 100+1):
|
| 361 |
+
sch.step()
|
| 362 |
+
lrs.append(opt.param_groups[0]["lr"])
|
| 363 |
+
|
| 364 |
+
plt.plot(range(len(lrs)), lrs)
|
| 365 |
+
|
| 366 |
+
# %% [markdown]
|
| 367 |
+
# # Run
|
| 368 |
+
|
| 369 |
+
# %%
|
| 370 |
+
def run(fold,model):
|
| 371 |
+
|
| 372 |
+
log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
|
| 373 |
+
model_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best.pth')
|
| 374 |
+
|
| 375 |
+
valid_doc_ids = folds[fold]
|
| 376 |
+
train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
|
| 377 |
+
|
| 378 |
+
valid_data = [x for x in data if x['document'] in valid_doc_ids]
|
| 379 |
+
|
| 380 |
+
dataset_train = PIIDataset(tokenizer, train_data, label2id, TRAINING_MAX_LENGTH)
|
| 381 |
+
dataset_valid = PIIDataset(tokenizer, valid_data, label2id, TRAINING_MAX_LENGTH)
|
| 382 |
+
print(len(dataset_train), len(dataset_valid))
|
| 383 |
+
|
| 384 |
+
loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=8, collate_fn=collate_fn)
|
| 385 |
+
loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=8, collate_fn=collate_fn)
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
# model = get_model()
|
| 389 |
+
model = model.to(device)
|
| 390 |
+
|
| 391 |
+
optimizer = AdamW(model.parameters(), lr=lr, weight_decay=wd)
|
| 392 |
+
scaler = torch.cuda.amp.GradScaler() if use_amp else None
|
| 393 |
+
from_epoch = 0
|
| 394 |
+
metric_best = 0
|
| 395 |
+
|
| 396 |
+
scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1)
|
| 397 |
+
|
| 398 |
+
for epoch in range(1, n_epochs+1):
|
| 399 |
+
if epoch < from_epoch + 1:
|
| 400 |
+
print(logs[epoch-1])
|
| 401 |
+
continue
|
| 402 |
+
|
| 403 |
+
print(time.ctime(), 'Epoch:', epoch)
|
| 404 |
+
|
| 405 |
+
train_loss = train_one_epoch(model, loader_train, optimizer, scheduler, scaler)
|
| 406 |
+
valid_loss, result = valid_one_epoch(model, loader_valid)
|
| 407 |
+
metric = result["f1"]
|
| 408 |
+
|
| 409 |
+
content = time.ctime() + ' ' + \
|
| 410 |
+
f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
|
| 411 |
+
f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
|
| 412 |
+
f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric: {metric:.4f}.'
|
| 413 |
+
print(content)
|
| 414 |
+
with open(log_file, 'a') as appender:
|
| 415 |
+
appender.write(content + '\n')
|
| 416 |
+
|
| 417 |
+
if metric > metric_best:
|
| 418 |
+
print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model ...')
|
| 419 |
+
# if not DEBUG:
|
| 420 |
+
torch.save(model.state_dict(), model_file)
|
| 421 |
+
metric_best = metric
|
| 422 |
+
|
| 423 |
+
# Save Last
|
| 424 |
+
if not DEBUG:
|
| 425 |
+
torch.save(
|
| 426 |
+
{
|
| 427 |
+
'epoch': epoch,
|
| 428 |
+
'model_state_dict': model.state_dict(),
|
| 429 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 430 |
+
'scaler_state_dict': scaler.state_dict() if scaler else None,
|
| 431 |
+
'metric_best': metric_best,
|
| 432 |
+
},
|
| 433 |
+
model_file.replace('_best', '_last')
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
del model
|
| 437 |
+
torch.cuda.empty_cache()
|
| 438 |
+
gc.collect()
|
| 439 |
+
|
| 440 |
+
# %%
|
| 441 |
+
run(0,model)
|
| 442 |
+
|
| 443 |
+
# %%
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
|
code/training-single-model-to-get-privatelb-0-963.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
code/training-single-model-to-get-privatelb-0-963_actualorig.py
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# %% [markdown]
|
| 2 |
+
# **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
|
| 3 |
+
#
|
| 4 |
+
# # Introduction
|
| 5 |
+
#
|
| 6 |
+
# Hi all,
|
| 7 |
+
#
|
| 8 |
+
# In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
|
| 9 |
+
#
|
| 10 |
+
# Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
|
| 11 |
+
#
|
| 12 |
+
# Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
|
| 13 |
+
#
|
| 14 |
+
# Let's dive into the details and uncover the techniques that led to a successful submission!
|
| 15 |
+
#
|
| 16 |
+
# [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
|
| 17 |
+
#
|
| 18 |
+
# # Summary
|
| 19 |
+
#
|
| 20 |
+
# * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
|
| 21 |
+
# * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
|
| 22 |
+
# * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
|
| 23 |
+
# * For inference, no truncation.
|
| 24 |
+
# * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
|
| 25 |
+
# * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
|
| 26 |
+
# * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
|
| 27 |
+
# * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
|
| 28 |
+
# * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
|
| 29 |
+
# * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
|
| 30 |
+
# * cosine with warmup learning rate schedule -- almost became a standard setting.
|
| 31 |
+
# * warmup 0.5 epo
|
| 32 |
+
# * cosine 4.5 epo
|
| 33 |
+
#
|
| 34 |
+
# # Acknowledge
|
| 35 |
+
#
|
| 36 |
+
# I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
|
| 37 |
+
#
|
| 38 |
+
# For training a single model this notebook shows, it takes less than 40min!
|
| 39 |
+
#
|
| 40 |
+
|
| 41 |
+
# %%
|
| 42 |
+
import os
|
| 43 |
+
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
|
| 44 |
+
import warnings
|
| 45 |
+
warnings.filterwarnings("ignore")
|
| 46 |
+
DEBUG=False
|
| 47 |
+
|
| 48 |
+
# %%
|
| 49 |
+
# !pip install -q seqeval
|
| 50 |
+
|
| 51 |
+
# %%
|
| 52 |
+
import gc
|
| 53 |
+
import json
|
| 54 |
+
import copy
|
| 55 |
+
import time
|
| 56 |
+
import pickle
|
| 57 |
+
import numpy as np
|
| 58 |
+
import pandas as pd
|
| 59 |
+
from itertools import chain
|
| 60 |
+
from transformers import AutoTokenizer
|
| 61 |
+
from transformers import AutoModelForTokenClassification
|
| 62 |
+
from transformers import get_cosine_schedule_with_warmup
|
| 63 |
+
from tqdm import tqdm
|
| 64 |
+
|
| 65 |
+
from seqeval.metrics import recall_score, precision_score
|
| 66 |
+
from seqeval.metrics import f1_score
|
| 67 |
+
|
| 68 |
+
import torch
|
| 69 |
+
from torch import nn
|
| 70 |
+
from torch.utils.data import Dataset, DataLoader
|
| 71 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 72 |
+
from torch.nn import CrossEntropyLoss
|
| 73 |
+
from torch.optim import AdamW
|
| 74 |
+
|
| 75 |
+
import matplotlib.pyplot as plt
|
| 76 |
+
from pylab import rcParams
|
| 77 |
+
|
| 78 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 79 |
+
|
| 80 |
+
# %% [markdown]
|
| 81 |
+
# # Config
|
| 82 |
+
|
| 83 |
+
# %%
|
| 84 |
+
exp_name = 'deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo'
|
| 85 |
+
|
| 86 |
+
backbone = 'microsoft/deberta-v3-large'
|
| 87 |
+
|
| 88 |
+
TRAINING_MAX_LENGTH = 4096
|
| 89 |
+
batch_size = 2
|
| 90 |
+
lr = 1e-5
|
| 91 |
+
ds_rate = 0.3
|
| 92 |
+
attention_probs_dropout_prob = 0.4
|
| 93 |
+
hidden_dropout_prob = 0.1
|
| 94 |
+
wd = 0.
|
| 95 |
+
pos_weight = 10
|
| 96 |
+
n_epochs = 5
|
| 97 |
+
|
| 98 |
+
use_amp = True
|
| 99 |
+
|
| 100 |
+
# %% [markdown]
|
| 101 |
+
# # Data
|
| 102 |
+
|
| 103 |
+
# %%
|
| 104 |
+
from sklearn.model_selection import train_test_split
|
| 105 |
+
|
| 106 |
+
data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
|
| 107 |
+
for sample in data:
|
| 108 |
+
if len(set(sample['labels'])) > 1:
|
| 109 |
+
sample['is_pos'] = 1
|
| 110 |
+
else:
|
| 111 |
+
sample['is_pos'] = 0
|
| 112 |
+
print("original datapoints: ", len(data))
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# moredata = json.load(open('/kaggle/input/fix-punctuation-tokenization-external-dataset/moredata_dataset_fixed.json'))
|
| 116 |
+
# print("moredata datapoints: ", len(moredata))
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# %%
|
| 120 |
+
from sklearn.model_selection import GroupKFold
|
| 121 |
+
import random
|
| 122 |
+
random.seed(0)
|
| 123 |
+
kf = GroupKFold(n_splits=5)
|
| 124 |
+
folds = []
|
| 125 |
+
for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
|
| 126 |
+
folds.append([data[i]['document'] for i in valid_index])
|
| 127 |
+
|
| 128 |
+
# %%
|
| 129 |
+
all_labels = sorted(list(set(chain(*[x["labels"] for x in data]))))
|
| 130 |
+
label2id = {l: i for i,l in enumerate(all_labels)}
|
| 131 |
+
id2label = {v:k for k,v in label2id.items()}
|
| 132 |
+
|
| 133 |
+
target = [
|
| 134 |
+
'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
|
| 135 |
+
'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
|
| 136 |
+
'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL'
|
| 137 |
+
]
|
| 138 |
+
|
| 139 |
+
print(len(all_labels), id2label)
|
| 140 |
+
|
| 141 |
+
# %% [markdown]
|
| 142 |
+
# # Dataset
|
| 143 |
+
|
| 144 |
+
# %%
|
| 145 |
+
def tokenize(example, tokenizer, label2id, max_length):
|
| 146 |
+
|
| 147 |
+
# rebuild text from tokens
|
| 148 |
+
text = []
|
| 149 |
+
labels = []
|
| 150 |
+
|
| 151 |
+
for t, l, ws in zip(
|
| 152 |
+
example["tokens"], example["provided_labels"], example["trailing_whitespace"]
|
| 153 |
+
):
|
| 154 |
+
text.append(t)
|
| 155 |
+
labels.extend([l] * len(t))
|
| 156 |
+
|
| 157 |
+
if ws:
|
| 158 |
+
text.append(" ")
|
| 159 |
+
labels.append("O")
|
| 160 |
+
|
| 161 |
+
tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
|
| 162 |
+
if len(tokenized['input_ids']) > max_length:
|
| 163 |
+
for key in tokenized.keys():
|
| 164 |
+
tokenized[key] = tokenized[key][:max_length//2] + tokenized[key][-max_length//2:]
|
| 165 |
+
|
| 166 |
+
labels = np.array(labels)
|
| 167 |
+
|
| 168 |
+
text = "".join(text)
|
| 169 |
+
token_labels = []
|
| 170 |
+
|
| 171 |
+
for start_idx, end_idx in tokenized.offset_mapping:
|
| 172 |
+
# CLS token
|
| 173 |
+
if start_idx == 0 and end_idx == 0:
|
| 174 |
+
token_labels.append(label2id["O"])
|
| 175 |
+
continue
|
| 176 |
+
|
| 177 |
+
# case when token starts with whitespace
|
| 178 |
+
if text[start_idx].isspace():
|
| 179 |
+
start_idx += 1
|
| 180 |
+
|
| 181 |
+
token_labels.append(label2id[labels[start_idx]])
|
| 182 |
+
|
| 183 |
+
length = len(tokenized.input_ids)
|
| 184 |
+
|
| 185 |
+
return {**tokenized, "labels": token_labels, "length": length}
|
| 186 |
+
|
| 187 |
+
# %%
|
| 188 |
+
tokenizer = AutoTokenizer.from_pretrained(backbone)
|
| 189 |
+
|
| 190 |
+
class PIIDataset(Dataset):
|
| 191 |
+
def __init__(self, tokenizer, data, label2id, max_length):
|
| 192 |
+
self.tokenizer = tokenizer
|
| 193 |
+
self.data = copy.deepcopy(data)
|
| 194 |
+
self.label2id = label2id
|
| 195 |
+
self.max_length = max_length
|
| 196 |
+
|
| 197 |
+
for i in tqdm(range(len(self.data))):
|
| 198 |
+
if self.data[i].get('input_ids') is None:
|
| 199 |
+
self.data[i]['provided_labels'] = self.data[i]['labels']
|
| 200 |
+
self.data[i] = tokenize(
|
| 201 |
+
self.data[i],
|
| 202 |
+
self.tokenizer,
|
| 203 |
+
self.label2id,
|
| 204 |
+
self.max_length
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
def __len__(self):
|
| 208 |
+
return len(self.data)
|
| 209 |
+
|
| 210 |
+
def __getitem__(self, idx):
|
| 211 |
+
return self.data[idx]
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def collate_fn(batch):
|
| 215 |
+
input_ids = [torch.tensor(x['input_ids']) for x in batch]
|
| 216 |
+
attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
|
| 217 |
+
labels = [torch.tensor(x['labels']) for x in batch]
|
| 218 |
+
|
| 219 |
+
input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=0)
|
| 220 |
+
attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
|
| 221 |
+
labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
|
| 222 |
+
|
| 223 |
+
return {
|
| 224 |
+
'input_ids': input_ids_padded,
|
| 225 |
+
'attention_mask': attention_masks_padded,
|
| 226 |
+
'labels': labels_padded,
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
ds = PIIDataset(tokenizer, data[:20], label2id, TRAINING_MAX_LENGTH)
|
| 230 |
+
len(ds[9]['input_ids'])
|
| 231 |
+
|
| 232 |
+
# %% [markdown]
|
| 233 |
+
# # Model
|
| 234 |
+
|
| 235 |
+
# %%
|
| 236 |
+
model = AutoModelForTokenClassification.from_pretrained(
|
| 237 |
+
backbone,
|
| 238 |
+
attention_probs_dropout_prob=attention_probs_dropout_prob,
|
| 239 |
+
hidden_dropout_prob=hidden_dropout_prob,
|
| 240 |
+
num_labels=len(all_labels),
|
| 241 |
+
id2label=id2label,
|
| 242 |
+
label2id=label2id,
|
| 243 |
+
ignore_mismatched_sizes=True
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
# %% [markdown]
|
| 247 |
+
# # Loss
|
| 248 |
+
|
| 249 |
+
# %%
|
| 250 |
+
weights = torch.ones(13) * pos_weight
|
| 251 |
+
weights[12] = 1
|
| 252 |
+
|
| 253 |
+
CE = nn.CrossEntropyLoss(weight=weights.to(device), ignore_index=-100)
|
| 254 |
+
def criterion(logits, labels):
|
| 255 |
+
return CE(logits.view(-1, len(all_labels)), labels.view(-1))
|
| 256 |
+
|
| 257 |
+
# %% [markdown]
|
| 258 |
+
# # Metric
|
| 259 |
+
|
| 260 |
+
# %%
|
| 261 |
+
def compute_metrics(p, all_labels, th=0.5):
|
| 262 |
+
predictions, labels = p
|
| 263 |
+
true_predictions = []
|
| 264 |
+
true_labels = []
|
| 265 |
+
for tmp, batch_l in zip(predictions, labels):
|
| 266 |
+
batch_p = copy.deepcopy(tmp)
|
| 267 |
+
batch_p[:, :, -1][batch_p[:, :, -1] < th] = -9999
|
| 268 |
+
batch_p = np.argmax(batch_p, axis=2)
|
| 269 |
+
true_predictions.extend([all_labels[p] for (p, l) in zip(batch_p.reshape(-1), batch_l.reshape(-1)) if l != -100])
|
| 270 |
+
true_labels.extend([all_labels[l] for l in batch_l.reshape(-1) if l != -100])
|
| 271 |
+
|
| 272 |
+
recall = recall_score([true_labels], [true_predictions])
|
| 273 |
+
precision = precision_score([true_labels], [true_predictions])
|
| 274 |
+
f1_score = (1 + 5*5) * recall * precision / (5*5*precision + recall)
|
| 275 |
+
|
| 276 |
+
results = {
|
| 277 |
+
'recall': recall,
|
| 278 |
+
'precision': precision,
|
| 279 |
+
'f1': f1_score
|
| 280 |
+
}
|
| 281 |
+
return results
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
# %% [markdown]
|
| 285 |
+
# # Train & Valid Func
|
| 286 |
+
|
| 287 |
+
# %%
|
| 288 |
+
def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
|
| 289 |
+
model.train()
|
| 290 |
+
running_loss = []
|
| 291 |
+
bar = tqdm(loader, total=len(loader))
|
| 292 |
+
for batch in bar:
|
| 293 |
+
optimizer.zero_grad()
|
| 294 |
+
scheduler.step()
|
| 295 |
+
|
| 296 |
+
input_ids = batch['input_ids'].to(device)
|
| 297 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 298 |
+
labels = batch['labels'].to(device)
|
| 299 |
+
|
| 300 |
+
if scaler is not None:
|
| 301 |
+
with torch.cuda.amp.autocast():
|
| 302 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 303 |
+
loss = criterion(outputs['logits'], labels)
|
| 304 |
+
scaler.scale(loss).backward()
|
| 305 |
+
scaler.step(optimizer)
|
| 306 |
+
scaler.update()
|
| 307 |
+
else:
|
| 308 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 309 |
+
loss = criterion(outputs['logits'], labels)
|
| 310 |
+
loss.backward()
|
| 311 |
+
optimizer.step()
|
| 312 |
+
|
| 313 |
+
running_loss.append(loss.item())
|
| 314 |
+
bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
|
| 315 |
+
|
| 316 |
+
return np.mean(running_loss)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def valid_one_epoch(model, loader):
|
| 320 |
+
model.eval()
|
| 321 |
+
running_loss = []
|
| 322 |
+
bar = tqdm(loader, total=len(loader))
|
| 323 |
+
predictions = []
|
| 324 |
+
labels = []
|
| 325 |
+
|
| 326 |
+
with torch.no_grad():
|
| 327 |
+
for batch in bar:
|
| 328 |
+
input_ids = batch['input_ids'].to(device)
|
| 329 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 330 |
+
labels_ = batch['labels'].to(device)
|
| 331 |
+
|
| 332 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels_)
|
| 333 |
+
loss = criterion(outputs['logits'], labels_)
|
| 334 |
+
|
| 335 |
+
running_loss.append(loss.item())
|
| 336 |
+
predictions.append(outputs['logits'].detach().cpu().numpy())
|
| 337 |
+
labels.append(labels_.detach().cpu().numpy())
|
| 338 |
+
|
| 339 |
+
bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
|
| 340 |
+
|
| 341 |
+
return np.mean(running_loss), compute_metrics((predictions, labels), all_labels)
|
| 342 |
+
|
| 343 |
+
# %% [markdown]
|
| 344 |
+
# # Scheduler
|
| 345 |
+
|
| 346 |
+
# %%
|
| 347 |
+
rcParams['figure.figsize'] = 20, 2
|
| 348 |
+
|
| 349 |
+
def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
|
| 350 |
+
return get_cosine_schedule_with_warmup(
|
| 351 |
+
optimizer,
|
| 352 |
+
int(num_training_steps * warmup_steps_ratio),
|
| 353 |
+
num_training_steps
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
opt = AdamW(model.parameters(), lr=lr)
|
| 357 |
+
sch = get_scheduler(opt, 100, 0.1)
|
| 358 |
+
|
| 359 |
+
lrs = []
|
| 360 |
+
for epoch in range(1, 100+1):
|
| 361 |
+
sch.step()
|
| 362 |
+
lrs.append(opt.param_groups[0]["lr"])
|
| 363 |
+
|
| 364 |
+
plt.plot(range(len(lrs)), lrs)
|
| 365 |
+
|
| 366 |
+
# %% [markdown]
|
| 367 |
+
# # Run
|
| 368 |
+
|
| 369 |
+
# %%
|
| 370 |
+
def run(fold,model):
|
| 371 |
+
|
| 372 |
+
log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
|
| 373 |
+
model_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best.pth')
|
| 374 |
+
|
| 375 |
+
valid_doc_ids = folds[fold]
|
| 376 |
+
# train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
|
| 377 |
+
train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))]
|
| 378 |
+
valid_data = [x for x in data if x['document'] in valid_doc_ids]
|
| 379 |
+
|
| 380 |
+
dataset_train = PIIDataset(tokenizer, train_data, label2id, TRAINING_MAX_LENGTH)
|
| 381 |
+
dataset_valid = PIIDataset(tokenizer, valid_data, label2id, TRAINING_MAX_LENGTH)
|
| 382 |
+
print(len(dataset_train), len(dataset_valid))
|
| 383 |
+
|
| 384 |
+
loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=8, collate_fn=collate_fn)
|
| 385 |
+
loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=8, collate_fn=collate_fn)
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
# model = get_model()
|
| 389 |
+
model = model.to(device)
|
| 390 |
+
|
| 391 |
+
optimizer = AdamW(model.parameters(), lr=lr, weight_decay=wd)
|
| 392 |
+
scaler = torch.cuda.amp.GradScaler() if use_amp else None
|
| 393 |
+
from_epoch = 0
|
| 394 |
+
metric_best = 0
|
| 395 |
+
|
| 396 |
+
scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1)
|
| 397 |
+
|
| 398 |
+
for epoch in range(1, n_epochs+1):
|
| 399 |
+
if epoch < from_epoch + 1:
|
| 400 |
+
print(logs[epoch-1])
|
| 401 |
+
continue
|
| 402 |
+
|
| 403 |
+
print(time.ctime(), 'Epoch:', epoch)
|
| 404 |
+
|
| 405 |
+
train_loss = train_one_epoch(model, loader_train, optimizer, scheduler, scaler)
|
| 406 |
+
valid_loss, result = valid_one_epoch(model, loader_valid)
|
| 407 |
+
metric = result["f1"]
|
| 408 |
+
|
| 409 |
+
content = time.ctime() + ' ' + \
|
| 410 |
+
f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
|
| 411 |
+
f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
|
| 412 |
+
f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric: {metric:.4f}.'
|
| 413 |
+
print(content)
|
| 414 |
+
with open(log_file, 'a') as appender:
|
| 415 |
+
appender.write(content + '\n')
|
| 416 |
+
|
| 417 |
+
if metric > metric_best:
|
| 418 |
+
print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model ...')
|
| 419 |
+
# if not DEBUG:
|
| 420 |
+
torch.save(model.state_dict(), model_file)
|
| 421 |
+
metric_best = metric
|
| 422 |
+
|
| 423 |
+
# Save Last
|
| 424 |
+
if not DEBUG:
|
| 425 |
+
torch.save(
|
| 426 |
+
{
|
| 427 |
+
'epoch': epoch,
|
| 428 |
+
'model_state_dict': model.state_dict(),
|
| 429 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 430 |
+
'scaler_state_dict': scaler.state_dict() if scaler else None,
|
| 431 |
+
'metric_best': metric_best,
|
| 432 |
+
},
|
| 433 |
+
model_file.replace('_best', '_last')
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
del model
|
| 437 |
+
torch.cuda.empty_cache()
|
| 438 |
+
gc.collect()
|
| 439 |
+
|
| 440 |
+
# %%
|
| 441 |
+
run(0,model)
|
| 442 |
+
|
| 443 |
+
# %%
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
|
code/training-single-model-to-get-privatelb-0-963_orig.py
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# %% [markdown]
|
| 2 |
+
# **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
|
| 3 |
+
#
|
| 4 |
+
# # Introduction
|
| 5 |
+
#
|
| 6 |
+
# Hi all,
|
| 7 |
+
#
|
| 8 |
+
# In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
|
| 9 |
+
#
|
| 10 |
+
# Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
|
| 11 |
+
#
|
| 12 |
+
# Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
|
| 13 |
+
#
|
| 14 |
+
# Let's dive into the details and uncover the techniques that led to a successful submission!
|
| 15 |
+
#
|
| 16 |
+
# [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
|
| 17 |
+
#
|
| 18 |
+
# # Summary
|
| 19 |
+
#
|
| 20 |
+
# * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
|
| 21 |
+
# * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
|
| 22 |
+
# * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
|
| 23 |
+
# * For inference, no truncation.
|
| 24 |
+
# * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
|
| 25 |
+
# * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
|
| 26 |
+
# * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
|
| 27 |
+
# * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
|
| 28 |
+
# * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
|
| 29 |
+
# * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
|
| 30 |
+
# * cosine with warmup learning rate schedule -- almost became a standard setting.
|
| 31 |
+
# * warmup 0.5 epo
|
| 32 |
+
# * cosine 4.5 epo
|
| 33 |
+
#
|
| 34 |
+
# # Acknowledge
|
| 35 |
+
#
|
| 36 |
+
# I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
|
| 37 |
+
#
|
| 38 |
+
# For training a single model this notebook shows, it takes less than 40min!
|
| 39 |
+
#
|
| 40 |
+
|
| 41 |
+
# %%
|
| 42 |
+
import os
|
| 43 |
+
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
|
| 44 |
+
import warnings
|
| 45 |
+
warnings.filterwarnings("ignore")
|
| 46 |
+
DEBUG=False
|
| 47 |
+
|
| 48 |
+
USE_SIMPLIFIED_LABELS = False # <<< CHANGE THIS TO False TO USE ORIGINAL BIO LABELS
|
| 49 |
+
|
| 50 |
+
# %%
|
| 51 |
+
# !pip install -q seqeval
|
| 52 |
+
|
| 53 |
+
# %%
|
| 54 |
+
import gc
|
| 55 |
+
import json
|
| 56 |
+
import copy
|
| 57 |
+
import time
|
| 58 |
+
import pickle
|
| 59 |
+
import numpy as np
|
| 60 |
+
import pandas as pd
|
| 61 |
+
from itertools import chain
|
| 62 |
+
from transformers import AutoTokenizer
|
| 63 |
+
from transformers import AutoModelForTokenClassification
|
| 64 |
+
from transformers import get_cosine_schedule_with_warmup
|
| 65 |
+
from tqdm import tqdm
|
| 66 |
+
|
| 67 |
+
from seqeval.metrics import recall_score, precision_score
|
| 68 |
+
from seqeval.metrics import f1_score
|
| 69 |
+
|
| 70 |
+
import torch
|
| 71 |
+
from torch import nn
|
| 72 |
+
from torch.utils.data import Dataset, DataLoader
|
| 73 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 74 |
+
from torch.nn import CrossEntropyLoss
|
| 75 |
+
from torch.optim import AdamW
|
| 76 |
+
|
| 77 |
+
import matplotlib.pyplot as plt
|
| 78 |
+
from pylab import rcParams
|
| 79 |
+
|
| 80 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 81 |
+
|
| 82 |
+
# %% [markdown]
|
| 83 |
+
# # Config
|
| 84 |
+
|
| 85 |
+
# %%
|
| 86 |
+
exp_name_base = 'deberta_v3_large_w10_ado1_wd0_bs2_lr1e5_5epo' # Changed ado4 to ado1
|
| 87 |
+
exp_name = f"{exp_name_base}_{'simplified_labels' if USE_SIMPLIFIED_LABELS else 'original_bio_labels'}"
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
backbone = 'microsoft/deberta-v3-large'
|
| 91 |
+
|
| 92 |
+
TRAINING_MAX_LENGTH = 4096
|
| 93 |
+
batch_size = 2
|
| 94 |
+
lr = 1e-5
|
| 95 |
+
ds_rate = 0.3
|
| 96 |
+
attention_probs_dropout_prob = 0.1 # Changed from 0.4 to 0.1 to match config_29th.json
|
| 97 |
+
hidden_dropout_prob = 0.1
|
| 98 |
+
wd = 0.
|
| 99 |
+
pos_weight = 10
|
| 100 |
+
n_epochs = 5
|
| 101 |
+
|
| 102 |
+
use_amp = True
|
| 103 |
+
|
| 104 |
+
# %% [markdown]
|
| 105 |
+
# # Data
|
| 106 |
+
|
| 107 |
+
# %%
|
| 108 |
+
from sklearn.model_selection import train_test_split
|
| 109 |
+
|
| 110 |
+
data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
|
| 111 |
+
for sample in data:
|
| 112 |
+
if len(set(sample['labels'])) > 1:
|
| 113 |
+
sample['is_pos'] = 1
|
| 114 |
+
else:
|
| 115 |
+
sample['is_pos'] = 0
|
| 116 |
+
print("original datapoints: ", len(data))
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# moredata = json.load(open('/kaggle/input/fix-punctuation-tokenization-external-dataset/moredata_dataset_fixed.json'))
|
| 120 |
+
# print("moredata datapoints: ", len(moredata))
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
# %%
|
| 124 |
+
from sklearn.model_selection import GroupKFold
|
| 125 |
+
import random
|
| 126 |
+
random.seed(0)
|
| 127 |
+
kf = GroupKFold(n_splits=5)
|
| 128 |
+
folds = []
|
| 129 |
+
for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
|
| 130 |
+
folds.append([data[i]['document'] for i in valid_index])
|
| 131 |
+
|
| 132 |
+
# %%
|
| 133 |
+
# Define simplified labels
|
| 134 |
+
pii_types = [
|
| 135 |
+
'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
|
| 136 |
+
'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
|
| 137 |
+
]
|
| 138 |
+
all_labels_simplified = sorted(pii_types + ['O'])
|
| 139 |
+
label2id_simplified = {l: i for i, l in enumerate(all_labels_simplified)}
|
| 140 |
+
id2label_simplified = {v: k for k, v in label2id_simplified.items()}
|
| 141 |
+
|
| 142 |
+
# Original target BIO labels
|
| 143 |
+
original_target_bio = sorted([
|
| 144 |
+
'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
|
| 145 |
+
'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
|
| 146 |
+
'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL',
|
| 147 |
+
'O' # Ensure 'O' is part of the original set as well
|
| 148 |
+
])
|
| 149 |
+
# Add I-EMAIL, I-USERNAME if they are possible, though not in the provided list.
|
| 150 |
+
# For now, sticking to the provided original_target_bio and adding 'O'.
|
| 151 |
+
# Ensure 'O' is consistently handled.
|
| 152 |
+
if 'O' not in original_target_bio:
|
| 153 |
+
original_target_bio_with_o = sorted(list(set(original_target_bio + ['O'])))
|
| 154 |
+
else:
|
| 155 |
+
original_target_bio_with_o = sorted(list(set(original_target_bio)))
|
| 156 |
+
|
| 157 |
+
label2id_original_bio = {l: i for i, l in enumerate(original_target_bio_with_o)}
|
| 158 |
+
id2label_original_bio = {v: k for k, v in label2id_original_bio.items()}
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
if USE_SIMPLIFIED_LABELS:
|
| 162 |
+
all_labels = all_labels_simplified
|
| 163 |
+
label2id = label2id_simplified
|
| 164 |
+
id2label = id2label_simplified
|
| 165 |
+
print(f"Using SIMPLIFIED labels ({len(all_labels)}): {id2label}")
|
| 166 |
+
else:
|
| 167 |
+
all_labels = original_target_bio_with_o
|
| 168 |
+
label2id = label2id_original_bio
|
| 169 |
+
id2label = id2label_original_bio
|
| 170 |
+
print(f"Using ORIGINAL BIO labels ({len(all_labels)}): {id2label}")
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# %% [markdown]
|
| 174 |
+
# # Dataset
|
| 175 |
+
|
| 176 |
+
# %%
|
| 177 |
+
def tokenize(example, tokenizer, current_label2id, max_length, use_simplified): # Pass current_label2id and use_simplified flag
|
| 178 |
+
|
| 179 |
+
# rebuild text from tokens
|
| 180 |
+
text = []
|
| 181 |
+
char_labels = []
|
| 182 |
+
|
| 183 |
+
for t, l, ws in zip(
|
| 184 |
+
example["tokens"], example["provided_labels"], example["trailing_whitespace"]
|
| 185 |
+
):
|
| 186 |
+
text.append(t)
|
| 187 |
+
current_label = l
|
| 188 |
+
if use_simplified and l != "O":
|
| 189 |
+
current_label = l[2:] # Strip "B-" or "I-"
|
| 190 |
+
|
| 191 |
+
# Ensure current_label is in the target label set (either simplified or original BIO)
|
| 192 |
+
# This is a safeguard. If a label from data isn't in the defined 'all_labels', map it to 'O'.
|
| 193 |
+
if current_label not in current_label2id:
|
| 194 |
+
# This case should ideally not happen if data labels are consistent with chosen scheme.
|
| 195 |
+
# For BIO, 'l' itself should be in label2id. For simplified, l[2:] should be.
|
| 196 |
+
# print(f"Warning: Label \'{current_label}\' (from original \'{l}\') not in target label set. Mapping to 'O'.")
|
| 197 |
+
current_label = "O"
|
| 198 |
+
|
| 199 |
+
char_labels.extend([current_label] * len(t))
|
| 200 |
+
|
| 201 |
+
if ws:
|
| 202 |
+
text.append(" ")
|
| 203 |
+
char_labels.append("O") # Whitespace is always "O"
|
| 204 |
+
|
| 205 |
+
tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
|
| 206 |
+
# Truncation strategy
|
| 207 |
+
if len(tokenized['input_ids']) > max_length:
|
| 208 |
+
# Implement the "first N/2 + last N/2" truncation strategy
|
| 209 |
+
first_half_len = max_length // 2
|
| 210 |
+
second_half_len = max_length - first_half_len # Ensures total length is max_length
|
| 211 |
+
|
| 212 |
+
keys_to_truncate = ['input_ids', 'attention_mask', 'offset_mapping']
|
| 213 |
+
for key in keys_to_truncate:
|
| 214 |
+
if key in tokenized:
|
| 215 |
+
original_list = tokenized[key]
|
| 216 |
+
# This slicing takes the first 'first_half_len' elements and the last 'second_half_len' elements
|
| 217 |
+
tokenized[key] = original_list[:first_half_len] + original_list[-second_half_len:]
|
| 218 |
+
# Consider adding a warning if a key is expected but not found, though unlikely for these standard keys.
|
| 219 |
+
|
| 220 |
+
char_labels_arr = np.array(char_labels) # This remains after truncation, using the original full char_labels
|
| 221 |
+
|
| 222 |
+
text_str = "".join(text) # Renamed from 'text' to avoid conflict
|
| 223 |
+
token_labels = []
|
| 224 |
+
|
| 225 |
+
# Access offset_mapping using dictionary style access as tokenized might be a dict
|
| 226 |
+
current_offset_mapping = tokenized["offset_mapping"]
|
| 227 |
+
|
| 228 |
+
for start_idx, end_idx in current_offset_mapping:
|
| 229 |
+
# Handle special tokens like [CLS], [SEP] which have (0,0) offset_mapping
|
| 230 |
+
# or any other token that doesn't map to actual characters in the original text.
|
| 231 |
+
if start_idx == end_idx:
|
| 232 |
+
token_labels.append(current_label2id["O"]) # Assign 'O' to special/empty tokens
|
| 233 |
+
continue
|
| 234 |
+
|
| 235 |
+
# Ensure indices are within the bounds of char_labels_arr and form a valid span
|
| 236 |
+
# end_idx is exclusive for slicing, so it can be == len(char_labels_arr)
|
| 237 |
+
if not (start_idx < end_idx and start_idx < len(char_labels_arr) and end_idx <= len(char_labels_arr)):
|
| 238 |
+
token_labels.append(current_label2id["O"]) # Assign 'O' if token is outside char_labels range or invalid span
|
| 239 |
+
continue
|
| 240 |
+
|
| 241 |
+
char_span_labels = char_labels_arr[start_idx:end_idx]
|
| 242 |
+
|
| 243 |
+
# If char_span_labels is somehow empty (e.g. if previous check was less strict)
|
| 244 |
+
if not char_span_labels.size:
|
| 245 |
+
token_labels.append(current_label2id["O"])
|
| 246 |
+
continue
|
| 247 |
+
|
| 248 |
+
unique_labels, counts = np.unique(char_span_labels, return_counts=True)
|
| 249 |
+
|
| 250 |
+
pii_labels_present_in_token = []
|
| 251 |
+
pii_counts_present_in_token = []
|
| 252 |
+
|
| 253 |
+
for lbl, count in zip(unique_labels, counts): # lbl is already simplified or original based on earlier logic
|
| 254 |
+
if lbl != "O":
|
| 255 |
+
pii_labels_present_in_token.append(lbl)
|
| 256 |
+
pii_counts_present_in_token.append(count)
|
| 257 |
+
|
| 258 |
+
if pii_labels_present_in_token:
|
| 259 |
+
# If PII labels are present, choose the most frequent PII label
|
| 260 |
+
most_frequent_pii_idx = np.argmax(pii_counts_present_in_token)
|
| 261 |
+
most_frequent_pii_label = pii_labels_present_in_token[most_frequent_pii_idx]
|
| 262 |
+
token_labels.append(current_label2id[most_frequent_pii_label])
|
| 263 |
+
else:
|
| 264 |
+
# If only \'O\' is present or no PII labels found in the span
|
| 265 |
+
token_labels.append(current_label2id["O"])
|
| 266 |
+
|
| 267 |
+
length = len(tokenized["input_ids"]) # Use dictionary-style access
|
| 268 |
+
|
| 269 |
+
return {**tokenized, "labels": token_labels, "length": length}
|
| 270 |
+
|
| 271 |
+
# %%
|
| 272 |
+
tokenizer = AutoTokenizer.from_pretrained(backbone)
|
| 273 |
+
|
| 274 |
+
class PIIDataset(Dataset):
|
| 275 |
+
def __init__(self, tokenizer, data, current_label2id, max_length, use_simplified): # Pass current_label2id and use_simplified
|
| 276 |
+
self.tokenizer = tokenizer
|
| 277 |
+
self.data = copy.deepcopy(data)
|
| 278 |
+
self.current_label2id = current_label2id # Store the active label2id
|
| 279 |
+
self.max_length = max_length
|
| 280 |
+
self.use_simplified = use_simplified
|
| 281 |
+
|
| 282 |
+
for i in tqdm(range(len(self.data))):
|
| 283 |
+
if self.data[i].get('input_ids') is None:
|
| 284 |
+
self.data[i]['provided_labels'] = self.data[i]['labels']
|
| 285 |
+
self.data[i] = tokenize(
|
| 286 |
+
self.data[i],
|
| 287 |
+
self.tokenizer,
|
| 288 |
+
self.current_label2id, # Pass the active label2id
|
| 289 |
+
self.max_length,
|
| 290 |
+
self.use_simplified # Pass the flag
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
def __len__(self):
|
| 294 |
+
return len(self.data)
|
| 295 |
+
|
| 296 |
+
def __getitem__(self, idx):
|
| 297 |
+
return self.data[idx]
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def collate_fn(batch):
|
| 301 |
+
input_ids = [torch.tensor(x['input_ids']) for x in batch]
|
| 302 |
+
attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
|
| 303 |
+
labels = [torch.tensor(x['labels']) for x in batch]
|
| 304 |
+
|
| 305 |
+
input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0)
|
| 306 |
+
attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
|
| 307 |
+
labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
|
| 308 |
+
|
| 309 |
+
return {
|
| 310 |
+
'input_ids': input_ids_padded,
|
| 311 |
+
'attention_mask': attention_masks_padded,
|
| 312 |
+
'labels': labels_padded,
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
# Test with simplified labels
|
| 316 |
+
# The global label2id and USE_SIMPLIFIED_LABELS will be used here by PIIDataset
|
| 317 |
+
ds = PIIDataset(tokenizer, data[:20], label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
|
| 318 |
+
print(f"Example tokenized input IDs length: {len(ds[9]['input_ids'])}")
|
| 319 |
+
print(f"Example tokenized labels: {ds[9]['labels']}")
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
# %% [markdown]
|
| 323 |
+
# # Model
|
| 324 |
+
|
| 325 |
+
# %%
|
| 326 |
+
model = AutoModelForTokenClassification.from_pretrained(
|
| 327 |
+
backbone,
|
| 328 |
+
attention_probs_dropout_prob=attention_probs_dropout_prob,
|
| 329 |
+
hidden_dropout_prob=hidden_dropout_prob,
|
| 330 |
+
num_labels=len(all_labels), # Use length of active labels
|
| 331 |
+
id2label=id2label, # Use active id2label
|
| 332 |
+
label2id=label2id, # Use active label2id
|
| 333 |
+
ignore_mismatched_sizes=True
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
# %% [markdown]
|
| 337 |
+
# # Loss
|
| 338 |
+
|
| 339 |
+
# %%
|
| 340 |
+
# Adjust weights for the active label set
|
| 341 |
+
weights = torch.ones(len(all_labels)) * pos_weight
|
| 342 |
+
if 'O' in label2id:
|
| 343 |
+
weights[label2id['O']] = 1
|
| 344 |
+
else:
|
| 345 |
+
print(f"Warning: 'O' label not found in active label2id for weight assignment: {label2id}. Assuming last index for 'O'.")
|
| 346 |
+
weights[-1] = 1
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
CE = nn.CrossEntropyLoss(weight=weights.to(device), ignore_index=-100) # Use general CE
|
| 350 |
+
def criterion(logits, labels):
|
| 351 |
+
# logits shape: (batch_size, seq_len, num_active_labels)
|
| 352 |
+
# labels shape: (batch_size, seq_len)
|
| 353 |
+
return CE(logits.view(-1, len(all_labels)), labels.view(-1)) # Use len(all_labels)
|
| 354 |
+
|
| 355 |
+
# %% [markdown]
|
| 356 |
+
# # Metric
|
| 357 |
+
|
| 358 |
+
# %%
|
| 359 |
+
def compute_metrics(p, current_all_labels_list, th=0.5): # Pass the active list of all label strings
|
| 360 |
+
predictions, labels = p
|
| 361 |
+
|
| 362 |
+
o_label_str = "O"
|
| 363 |
+
o_label_idx = -1
|
| 364 |
+
# current_all_labels_list is the list of string names for labels, e.g., all_labels
|
| 365 |
+
for i, lbl_str in enumerate(current_all_labels_list):
|
| 366 |
+
if lbl_str == o_label_str:
|
| 367 |
+
o_label_idx = i
|
| 368 |
+
break
|
| 369 |
+
if o_label_idx == -1:
|
| 370 |
+
print(f"Warning: Could not find 'O' label in {current_all_labels_list} for metric thresholding. Using last index.")
|
| 371 |
+
o_label_idx = len(current_all_labels_list) - 1
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
true_predictions = []
|
| 375 |
+
true_labels = []
|
| 376 |
+
for tmp, batch_l in zip(predictions, labels):
|
| 377 |
+
batch_p_probs = torch.softmax(torch.from_numpy(tmp), dim=-1).numpy()
|
| 378 |
+
|
| 379 |
+
o_column_probs_slice = batch_p_probs[:, :, o_label_idx]
|
| 380 |
+
|
| 381 |
+
condition_mask = o_column_probs_slice < th
|
| 382 |
+
|
| 383 |
+
o_column_probs_slice[condition_mask] = -9999
|
| 384 |
+
|
| 385 |
+
batch_p_final_ids = np.argmax(batch_p_probs, axis=-1)
|
| 386 |
+
|
| 387 |
+
true_predictions.extend([current_all_labels_list[p_id] for (p_id, l_id) in zip(batch_p_final_ids.reshape(-1), batch_l.reshape(-1)) if l_id != -100])
|
| 388 |
+
true_labels.extend([current_all_labels_list[l_id] for l_id in batch_l.reshape(-1) if l_id != -100])
|
| 389 |
+
|
| 390 |
+
# Using seqeval for P, R, F1 (F5)
|
| 391 |
+
# seqeval expects lists of lists of strings.
|
| 392 |
+
# Here true_labels and true_predictions are flat lists.
|
| 393 |
+
# For proper seqeval, we'd need to group them back by sequence.
|
| 394 |
+
# However, the original script's compute_metrics calculates overall P, R, F5 on flat lists.
|
| 395 |
+
# Let's keep that structure for now, as it's what the script used for its internal F5.
|
| 396 |
+
# Note: This F5 is on simplified labels, not BIO.
|
| 397 |
+
|
| 398 |
+
recall = recall_score([true_labels], [true_predictions], average='micro') # Use micro for overall
|
| 399 |
+
precision = precision_score([true_labels], [true_predictions], average='micro')
|
| 400 |
+
# Calculate F5 score manually based on P and R
|
| 401 |
+
beta = 5
|
| 402 |
+
f5_score = (1 + beta**2) * precision * recall / ((beta**2 * precision) + recall + 1e-100)
|
| 403 |
+
if precision + recall == 0: # Handle case where P and R are both zero
|
| 404 |
+
f5_score = 0.0
|
| 405 |
+
|
| 406 |
+
results = {
|
| 407 |
+
'recall': recall,
|
| 408 |
+
'precision': precision,
|
| 409 |
+
'f1': f5_score # Reporting F5 as 'f1' to match original key, or add 'f5'
|
| 410 |
+
}
|
| 411 |
+
return results
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
# %% [markdown]
|
| 415 |
+
# # Train & Valid Func
|
| 416 |
+
|
| 417 |
+
# %%
|
| 418 |
+
def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
|
| 419 |
+
model.train()
|
| 420 |
+
running_loss = []
|
| 421 |
+
bar = tqdm(loader, total=len(loader))
|
| 422 |
+
for batch in bar:
|
| 423 |
+
optimizer.zero_grad()
|
| 424 |
+
|
| 425 |
+
input_ids = batch['input_ids'].to(device)
|
| 426 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 427 |
+
labels = batch['labels'].to(device)
|
| 428 |
+
|
| 429 |
+
if scaler is not None:
|
| 430 |
+
with torch.cuda.amp.autocast():
|
| 431 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 432 |
+
loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
|
| 433 |
+
scaler.scale(loss).backward()
|
| 434 |
+
scaler.step(optimizer)
|
| 435 |
+
scaler.update()
|
| 436 |
+
else:
|
| 437 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 438 |
+
loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
|
| 439 |
+
loss.backward()
|
| 440 |
+
optimizer.step()
|
| 441 |
+
|
| 442 |
+
scheduler.step() # Moved scheduler step here, common practice per batch
|
| 443 |
+
running_loss.append(loss.item())
|
| 444 |
+
bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
|
| 445 |
+
|
| 446 |
+
return np.mean(running_loss)
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
def valid_one_epoch(model, loader):
|
| 450 |
+
model.eval()
|
| 451 |
+
running_loss = []
|
| 452 |
+
bar = tqdm(loader, total=len(loader))
|
| 453 |
+
predictions_logits = []
|
| 454 |
+
true_labels_list = []
|
| 455 |
+
|
| 456 |
+
with torch.no_grad():
|
| 457 |
+
for batch in bar:
|
| 458 |
+
input_ids = batch['input_ids'].to(device)
|
| 459 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 460 |
+
labels_batch = batch['labels'].to(device) # Renamed to avoid conflict
|
| 461 |
+
|
| 462 |
+
outputs = model(input_ids, attention_mask=attention_mask)
|
| 463 |
+
loss = criterion(outputs.logits, labels_batch) # Use labels_batch
|
| 464 |
+
running_loss.append(loss.item())
|
| 465 |
+
|
| 466 |
+
# Store logits and true labels for metric calculation
|
| 467 |
+
predictions_logits.append(outputs.logits.cpu().numpy())
|
| 468 |
+
true_labels_list.append(labels_batch.cpu().numpy()) # Use labels_batch
|
| 469 |
+
|
| 470 |
+
bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
|
| 471 |
+
|
| 472 |
+
# Pass the active all_labels (list of strings) to compute_metrics
|
| 473 |
+
metrics_results = compute_metrics((predictions_logits, true_labels_list), all_labels)
|
| 474 |
+
return np.mean(running_loss), metrics_results
|
| 475 |
+
|
| 476 |
+
# %% [markdown]
|
| 477 |
+
# # Scheduler
|
| 478 |
+
|
| 479 |
+
# %%
|
| 480 |
+
rcParams['figure.figsize'] = 20, 2
|
| 481 |
+
|
| 482 |
+
def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
|
| 483 |
+
return get_cosine_schedule_with_warmup(
|
| 484 |
+
optimizer,
|
| 485 |
+
int(num_training_steps * warmup_steps_ratio),
|
| 486 |
+
num_training_steps
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
+
opt = AdamW(model.parameters(), lr=lr)
|
| 490 |
+
sch = get_scheduler(opt, 100, 0.1)
|
| 491 |
+
|
| 492 |
+
lrs = []
|
| 493 |
+
for epoch in range(1, 100+1):
|
| 494 |
+
sch.step()
|
| 495 |
+
lrs.append(opt.param_groups[0]["lr"])
|
| 496 |
+
|
| 497 |
+
plt.plot(range(len(lrs)), lrs)
|
| 498 |
+
|
| 499 |
+
# %% [markdown]
|
| 500 |
+
# # Run
|
| 501 |
+
|
| 502 |
+
# %%
|
| 503 |
+
def run(fold,model_to_train):
|
| 504 |
+
|
| 505 |
+
log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt') # exp_name is now dynamic
|
| 506 |
+
# Define a directory for saving Hugging Face model (config, weights, tokenizer_config etc.)
|
| 507 |
+
model_save_dir_best = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best_hf') # exp_name is now dynamic
|
| 508 |
+
model_save_dir_last = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_last_hf') # exp_name is now dynamic
|
| 509 |
+
|
| 510 |
+
# Create directories if they don't exist
|
| 511 |
+
os.makedirs(model_save_dir_best, exist_ok=True)
|
| 512 |
+
os.makedirs(model_save_dir_last, exist_ok=True)
|
| 513 |
+
# Path for optimizer and other states (still using torch.save for these with custom loop)
|
| 514 |
+
optimizer_etc_file_last = os.path.join(model_save_dir_last, 'optimizer_etc_states.pth')
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
valid_doc_ids = folds[fold]
|
| 518 |
+
# train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
|
| 519 |
+
train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))]
|
| 520 |
+
valid_data = [x for x in data if x['document'] in valid_doc_ids]
|
| 521 |
+
|
| 522 |
+
# Use active label2id and USE_SIMPLIFIED_LABELS flag
|
| 523 |
+
dataset_train = PIIDataset(tokenizer, train_data, label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
|
| 524 |
+
dataset_valid = PIIDataset(tokenizer, valid_data, label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
|
| 525 |
+
print(len(dataset_train), len(dataset_valid))
|
| 526 |
+
|
| 527 |
+
loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=2, collate_fn=collate_fn) # Reduced num_workers for potential local issues
|
| 528 |
+
loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=2, collate_fn=collate_fn)
|
| 529 |
+
|
| 530 |
+
|
| 531 |
+
# model = get_model() # Original script had this, but model is passed now
|
| 532 |
+
current_model = model_to_train.to(device) # Use the passed model
|
| 533 |
+
|
| 534 |
+
optimizer = AdamW(current_model.parameters(), lr=lr, weight_decay=wd)
|
| 535 |
+
scaler = torch.cuda.amp.GradScaler() if use_amp else None
|
| 536 |
+
from_epoch = 0 # Assuming fresh train, not resuming
|
| 537 |
+
metric_best = 0
|
| 538 |
+
|
| 539 |
+
scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1) # warmup_steps_ratio is 0.1
|
| 540 |
+
|
| 541 |
+
for epoch in range(1, n_epochs+1):
|
| 542 |
+
# if epoch < from_epoch + 1:
|
| 543 |
+
# print(logs[epoch-1]) # Assuming logs is not used for fresh train
|
| 544 |
+
# continue
|
| 545 |
+
|
| 546 |
+
print(time.ctime(), 'Epoch:', epoch)
|
| 547 |
+
|
| 548 |
+
train_loss = train_one_epoch(current_model, loader_train, optimizer, scheduler, scaler)
|
| 549 |
+
valid_loss, result = valid_one_epoch(current_model, loader_valid)
|
| 550 |
+
metric = result["f1"] # This is F5 score on simplified labels
|
| 551 |
+
|
| 552 |
+
content = time.ctime() + ' ' + \
|
| 553 |
+
f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
|
| 554 |
+
f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
|
| 555 |
+
f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric (F5 on simplified): {metric:.4f}.'
|
| 556 |
+
print(content)
|
| 557 |
+
with open(log_file, 'a') as appender:
|
| 558 |
+
appender.write(content + '\n')
|
| 559 |
+
|
| 560 |
+
if metric > metric_best:
|
| 561 |
+
print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model to {model_save_dir_best} ...')
|
| 562 |
+
if not DEBUG:
|
| 563 |
+
current_model.save_pretrained(model_save_dir_best, safe_serialization=True)
|
| 564 |
+
tokenizer.save_pretrained(model_save_dir_best) # Save tokenizer too
|
| 565 |
+
metric_best = metric
|
| 566 |
+
|
| 567 |
+
# Save Last states (model, optimizer, scheduler)
|
| 568 |
+
if not DEBUG:
|
| 569 |
+
current_model.save_pretrained(model_save_dir_last, safe_serialization=True)
|
| 570 |
+
tokenizer.save_pretrained(model_save_dir_last)
|
| 571 |
+
torch.save(
|
| 572 |
+
{
|
| 573 |
+
'epoch': epoch,
|
| 574 |
+
# model_state_dict is not needed if using save_pretrained for the model itself
|
| 575 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 576 |
+
'scheduler_state_dict': scheduler.state_dict(), # Save scheduler state
|
| 577 |
+
'scaler_state_dict': scaler.state_dict() if scaler else None,
|
| 578 |
+
'metric_best': metric_best,
|
| 579 |
+
},
|
| 580 |
+
optimizer_etc_file_last # Save optimizer and other states separately
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
del current_model # Use current_model
|
| 584 |
+
torch.cuda.empty_cache()
|
| 585 |
+
gc.collect()
|
| 586 |
+
|
| 587 |
+
# %%
|
| 588 |
+
# model is defined globally from "Model" cell. Pass it to run.
|
| 589 |
+
run(0, model)
|
| 590 |
+
|
| 591 |
+
# %%
|
| 592 |
+
|
| 593 |
+
|
| 594 |
+
|
code/training-single-model-v8.py
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# %% [markdown]
|
| 2 |
+
# **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
|
| 3 |
+
#
|
| 4 |
+
# # Introduction
|
| 5 |
+
#
|
| 6 |
+
# Hi all,
|
| 7 |
+
#
|
| 8 |
+
# In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
|
| 9 |
+
#
|
| 10 |
+
# Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
|
| 11 |
+
#
|
| 12 |
+
# Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
|
| 13 |
+
#
|
| 14 |
+
# Let's dive into the details and uncover the techniques that led to a successful submission!
|
| 15 |
+
#
|
| 16 |
+
# [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
|
| 17 |
+
#
|
| 18 |
+
# # Summary
|
| 19 |
+
#
|
| 20 |
+
# * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
|
| 21 |
+
# * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
|
| 22 |
+
# * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
|
| 23 |
+
# * For inference, no truncation.
|
| 24 |
+
# * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
|
| 25 |
+
# * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
|
| 26 |
+
# * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
|
| 27 |
+
# * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
|
| 28 |
+
# * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
|
| 29 |
+
# * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
|
| 30 |
+
# * cosine with warmup learning rate schedule -- almost became a standard setting.
|
| 31 |
+
# * warmup 0.5 epo
|
| 32 |
+
# * cosine 4.5 epo
|
| 33 |
+
#
|
| 34 |
+
# # Acknowledge
|
| 35 |
+
#
|
| 36 |
+
# I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
|
| 37 |
+
#
|
| 38 |
+
# For training a single model this notebook shows, it takes less than 40min!
|
| 39 |
+
#
|
| 40 |
+
|
| 41 |
+
# %%
|
| 42 |
+
import os
|
| 43 |
+
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
|
| 44 |
+
import warnings
|
| 45 |
+
warnings.filterwarnings("ignore")
|
| 46 |
+
DEBUG=False
|
| 47 |
+
|
| 48 |
+
# %%
|
| 49 |
+
# !pip install -q seqeval
|
| 50 |
+
|
| 51 |
+
# %%
|
| 52 |
+
import gc
|
| 53 |
+
import json
|
| 54 |
+
import copy
|
| 55 |
+
import time
|
| 56 |
+
import pickle
|
| 57 |
+
import numpy as np
|
| 58 |
+
import pandas as pd
|
| 59 |
+
from itertools import chain
|
| 60 |
+
from transformers import AutoTokenizer
|
| 61 |
+
from transformers import AutoModelForTokenClassification
|
| 62 |
+
from transformers import get_cosine_schedule_with_warmup
|
| 63 |
+
from tqdm import tqdm
|
| 64 |
+
|
| 65 |
+
from seqeval.metrics import recall_score, precision_score
|
| 66 |
+
from seqeval.metrics import f1_score
|
| 67 |
+
|
| 68 |
+
import torch
|
| 69 |
+
from torch import nn
|
| 70 |
+
from torch.utils.data import Dataset, DataLoader
|
| 71 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 72 |
+
from torch.nn import CrossEntropyLoss
|
| 73 |
+
from torch.optim import AdamW
|
| 74 |
+
|
| 75 |
+
import matplotlib.pyplot as plt
|
| 76 |
+
from pylab import rcParams
|
| 77 |
+
|
| 78 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 79 |
+
|
| 80 |
+
# %% [markdown]
|
| 81 |
+
# # Config
|
| 82 |
+
|
| 83 |
+
# %%
|
| 84 |
+
exp_name = 'deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_simplified_labels' # Updated exp_name
|
| 85 |
+
|
| 86 |
+
backbone = 'microsoft/deberta-v3-large'
|
| 87 |
+
|
| 88 |
+
TRAINING_MAX_LENGTH = 4096
|
| 89 |
+
batch_size = 2
|
| 90 |
+
lr = 1e-5
|
| 91 |
+
ds_rate = 0.3
|
| 92 |
+
attention_probs_dropout_prob = 0.4
|
| 93 |
+
hidden_dropout_prob = 0.1
|
| 94 |
+
wd = 0.
|
| 95 |
+
pos_weight = 10
|
| 96 |
+
n_epochs = 5
|
| 97 |
+
|
| 98 |
+
use_amp = True
|
| 99 |
+
|
| 100 |
+
# %% [markdown]
|
| 101 |
+
# # Data
|
| 102 |
+
|
| 103 |
+
# %%
|
| 104 |
+
from sklearn.model_selection import train_test_split
|
| 105 |
+
|
| 106 |
+
data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
|
| 107 |
+
for sample in data:
|
| 108 |
+
if len(set(sample['labels'])) > 1:
|
| 109 |
+
sample['is_pos'] = 1
|
| 110 |
+
else:
|
| 111 |
+
sample['is_pos'] = 0
|
| 112 |
+
print("original datapoints: ", len(data))
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# moredata = json.load(open('/kaggle/input/fix-punctuation-tokenization-external-dataset/moredata_dataset_fixed.json'))
|
| 116 |
+
# print("moredata datapoints: ", len(moredata))
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# %%
|
| 120 |
+
from sklearn.model_selection import GroupKFold
|
| 121 |
+
import random
|
| 122 |
+
random.seed(0)
|
| 123 |
+
kf = GroupKFold(n_splits=5)
|
| 124 |
+
folds = []
|
| 125 |
+
for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
|
| 126 |
+
folds.append([data[i]['document'] for i in valid_index])
|
| 127 |
+
|
| 128 |
+
# %%
|
| 129 |
+
# Define simplified labels
|
| 130 |
+
pii_types = [
|
| 131 |
+
'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
|
| 132 |
+
'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
|
| 133 |
+
]
|
| 134 |
+
# all_labels_simplified will be pii_types + ['O']
|
| 135 |
+
all_labels_simplified = sorted(pii_types + ['O'])
|
| 136 |
+
label2id_simplified = {l: i for i, l in enumerate(all_labels_simplified)}
|
| 137 |
+
id2label_simplified = {v: k for k, v in label2id_simplified.items()}
|
| 138 |
+
|
| 139 |
+
# Original target for reference or other logic if needed, but not for model head
|
| 140 |
+
original_target_bio = [
|
| 141 |
+
'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
|
| 142 |
+
'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
|
| 143 |
+
'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL'
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
print(f"Simplified labels ({len(all_labels_simplified)}): {id2label_simplified}")
|
| 147 |
+
|
| 148 |
+
# %% [markdown]
|
| 149 |
+
# # Dataset
|
| 150 |
+
|
| 151 |
+
# %%
|
| 152 |
+
def tokenize(example, tokenizer, label2id, max_length): # label2id here will be label2id_simplified
|
| 153 |
+
|
| 154 |
+
# rebuild text from tokens
|
| 155 |
+
text = []
|
| 156 |
+
char_labels = [] # Renamed from 'labels' to avoid confusion with example['provided_labels']
|
| 157 |
+
|
| 158 |
+
for t, l, ws in zip(
|
| 159 |
+
example["tokens"], example["provided_labels"], example["trailing_whitespace"]
|
| 160 |
+
):
|
| 161 |
+
text.append(t)
|
| 162 |
+
# Simplify label: strip B-/I- prefix if it's a PII label
|
| 163 |
+
current_label = l
|
| 164 |
+
if l != "O":
|
| 165 |
+
current_label = l[2:] # Strip "B-" or "I-"
|
| 166 |
+
char_labels.extend([current_label] * len(t))
|
| 167 |
+
|
| 168 |
+
if ws:
|
| 169 |
+
text.append(" ")
|
| 170 |
+
char_labels.append("O") # Whitespace is always "O"
|
| 171 |
+
|
| 172 |
+
tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
|
| 173 |
+
# Truncation strategy
|
| 174 |
+
if len(tokenized['input_ids']) > max_length:
|
| 175 |
+
# Truncate by taking first max_length//2 and last max_length//2 tokens
|
| 176 |
+
# This strategy is mentioned in the original notebook's markdown for inputs longer than max_length
|
| 177 |
+
# For 'offset_mapping', we need to be careful. If we truncate input_ids, we must truncate offset_mapping too.
|
| 178 |
+
# The original code did this:
|
| 179 |
+
# for key in tokenized.keys():
|
| 180 |
+
# tokenized[key] = tokenized[key][:max_length//2] + tokenized[key][-max_length//2:]
|
| 181 |
+
# This is complex for offset_mapping as the second half's offsets are relative to the original string.
|
| 182 |
+
# A simpler truncation for now, or ensure max_length is sufficient.
|
| 183 |
+
# For simplicity, let's use Hugging Face's truncation, but it needs to be enabled in tokenizer call if not already.
|
| 184 |
+
# The original code had truncation=False then a manual truncation.
|
| 185 |
+
# Let's stick to the original manual truncation for input_ids and attention_mask,
|
| 186 |
+
# and apply it carefully to offset_mapping.
|
| 187 |
+
# However, the provided code snippet for tokenize in the original script does:
|
| 188 |
+
# tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
|
| 189 |
+
# if len(tokenized['input_ids']) > max_length:
|
| 190 |
+
# for key in tokenized.keys():
|
| 191 |
+
# tokenized[key] = tokenized[key][:max_length//2] + tokenized[key][-max_length//2:]
|
| 192 |
+
# This manual truncation is applied *after* tokenization.
|
| 193 |
+
# Let's re-enable truncation in the tokenizer call for safety, though the manual one might override.
|
| 194 |
+
# For this modification, we'll assume the existing truncation logic is what the user wants to keep.
|
| 195 |
+
# The key change is how labels are derived.
|
| 196 |
+
|
| 197 |
+
# Re-applying the original truncation logic if input_ids are too long
|
| 198 |
+
if len(tokenized['input_ids']) > max_length:
|
| 199 |
+
first_half_len = max_length // 2
|
| 200 |
+
second_half_start = len(tokenized['input_ids']) - (max_length - first_half_len) # Ensure total is max_length
|
| 201 |
+
|
| 202 |
+
new_tokenized = {}
|
| 203 |
+
for key in tokenized.keys():
|
| 204 |
+
if key == 'offset_mapping': # Special handling for offset_mapping if needed, or just truncate
|
| 205 |
+
new_tokenized[key] = tokenized[key][:first_half_len] + tokenized[key][second_half_start:]
|
| 206 |
+
else:
|
| 207 |
+
new_tokenized[key] = tokenized[key][:first_half_len] + tokenized[key][second_half_start:]
|
| 208 |
+
tokenized = new_tokenized
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
char_labels_arr = np.array(char_labels) # Use the simplified char_labels
|
| 212 |
+
|
| 213 |
+
text_str = "".join(text) # Renamed from 'text' to avoid conflict
|
| 214 |
+
token_labels = []
|
| 215 |
+
|
| 216 |
+
# Access offset_mapping using dictionary style access as tokenized might be a dict
|
| 217 |
+
current_offset_mapping = tokenized["offset_mapping"]
|
| 218 |
+
|
| 219 |
+
for start_idx, end_idx in current_offset_mapping:
|
| 220 |
+
# CLS token
|
| 221 |
+
if start_idx == 0 and end_idx == 0:
|
| 222 |
+
token_labels.append(label2id["O"]) # Use the passed simplified label2id
|
| 223 |
+
continue
|
| 224 |
+
|
| 225 |
+
# case when token starts with whitespace
|
| 226 |
+
# Ensure start_idx is within bounds of text_str
|
| 227 |
+
if start_idx < len(text_str) and text_str[start_idx].isspace():
|
| 228 |
+
start_idx += 1
|
| 229 |
+
|
| 230 |
+
# Ensure start_idx is within bounds of char_labels_arr after potential increment
|
| 231 |
+
# And handle cases where start_idx might become >= len(char_labels_arr) due to offsets near end
|
| 232 |
+
if start_idx >= len(char_labels_arr):
|
| 233 |
+
# This can happen if offset_mapping points beyond the original text due to truncation or special tokens
|
| 234 |
+
# A safe fallback is 'O' or to ensure char_labels_arr is long enough / handle offsets carefully
|
| 235 |
+
# Given the original code's `while start_idx >= len(labels): start_idx -= 1`, let's adapt that.
|
| 236 |
+
current_char_label_idx = min(start_idx, len(char_labels_arr) - 1)
|
| 237 |
+
else:
|
| 238 |
+
current_char_label_idx = start_idx
|
| 239 |
+
|
| 240 |
+
token_labels.append(label2id[char_labels_arr[current_char_label_idx]])
|
| 241 |
+
|
| 242 |
+
length = len(tokenized["input_ids"]) # Use dictionary-style access
|
| 243 |
+
|
| 244 |
+
return {**tokenized, "labels": token_labels, "length": length}
|
| 245 |
+
|
| 246 |
+
# %%
|
| 247 |
+
tokenizer = AutoTokenizer.from_pretrained(backbone)
|
| 248 |
+
|
| 249 |
+
class PIIDataset(Dataset):
|
| 250 |
+
def __init__(self, tokenizer, data, label2id, max_length): # label2id will be simplified
|
| 251 |
+
self.tokenizer = tokenizer
|
| 252 |
+
self.data = copy.deepcopy(data)
|
| 253 |
+
self.label2id = label2id
|
| 254 |
+
self.max_length = max_length
|
| 255 |
+
|
| 256 |
+
for i in tqdm(range(len(self.data))):
|
| 257 |
+
if self.data[i].get('input_ids') is None:
|
| 258 |
+
self.data[i]['provided_labels'] = self.data[i]['labels'] # Keep original BIO labels here for tokenize
|
| 259 |
+
self.data[i] = tokenize(
|
| 260 |
+
self.data[i],
|
| 261 |
+
self.tokenizer,
|
| 262 |
+
self.label2id, # Pass simplified label2id
|
| 263 |
+
self.max_length
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
def __len__(self):
|
| 267 |
+
return len(self.data)
|
| 268 |
+
|
| 269 |
+
def __getitem__(self, idx):
|
| 270 |
+
return self.data[idx]
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def collate_fn(batch):
|
| 274 |
+
input_ids = [torch.tensor(x['input_ids']) for x in batch]
|
| 275 |
+
attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
|
| 276 |
+
labels = [torch.tensor(x['labels']) for x in batch]
|
| 277 |
+
|
| 278 |
+
input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0)
|
| 279 |
+
attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
|
| 280 |
+
labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
|
| 281 |
+
|
| 282 |
+
return {
|
| 283 |
+
'input_ids': input_ids_padded,
|
| 284 |
+
'attention_mask': attention_masks_padded,
|
| 285 |
+
'labels': labels_padded,
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
# Test with simplified labels
|
| 289 |
+
ds = PIIDataset(tokenizer, data[:20], label2id_simplified, TRAINING_MAX_LENGTH)
|
| 290 |
+
print(f"Example tokenized input IDs length: {len(ds[9]['input_ids'])}")
|
| 291 |
+
print(f"Example tokenized labels: {ds[9]['labels']}")
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
# %% [markdown]
|
| 295 |
+
# # Model
|
| 296 |
+
|
| 297 |
+
# %%
|
| 298 |
+
model = AutoModelForTokenClassification.from_pretrained(
|
| 299 |
+
backbone,
|
| 300 |
+
attention_probs_dropout_prob=attention_probs_dropout_prob,
|
| 301 |
+
hidden_dropout_prob=hidden_dropout_prob,
|
| 302 |
+
num_labels=len(all_labels_simplified), # Use length of simplified labels (8)
|
| 303 |
+
id2label=id2label_simplified, # Use simplified id2label
|
| 304 |
+
label2id=label2id_simplified, # Use simplified label2id
|
| 305 |
+
ignore_mismatched_sizes=True # Good practice if changing head
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
# %% [markdown]
|
| 309 |
+
# # Loss
|
| 310 |
+
|
| 311 |
+
# %%
|
| 312 |
+
# Adjust weights for simplified labels
|
| 313 |
+
weights_simplified = torch.ones(len(all_labels_simplified)) * pos_weight
|
| 314 |
+
# Assuming 'O' is the label for non-PII and its ID is known via label2id_simplified
|
| 315 |
+
if 'O' in label2id_simplified:
|
| 316 |
+
weights_simplified[label2id_simplified['O']] = 1
|
| 317 |
+
else:
|
| 318 |
+
# Fallback if 'O' is not named exactly like that, though it should be.
|
| 319 |
+
# This assumes 'O' is the last class if not found by name.
|
| 320 |
+
print("Warning: 'O' label not found in label2id_simplified for weight assignment. Assuming last index for 'O'.")
|
| 321 |
+
weights_simplified[-1] = 1
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
CE_simplified = nn.CrossEntropyLoss(weight=weights_simplified.to(device), ignore_index=-100)
|
| 325 |
+
def criterion(logits, labels):
|
| 326 |
+
# logits shape: (batch_size, seq_len, num_simplified_labels)
|
| 327 |
+
# labels shape: (batch_size, seq_len)
|
| 328 |
+
return CE_simplified(logits.view(-1, len(all_labels_simplified)), labels.view(-1))
|
| 329 |
+
|
| 330 |
+
# %% [markdown]
|
| 331 |
+
# # Metric
|
| 332 |
+
|
| 333 |
+
# %%
|
| 334 |
+
def compute_metrics(p, current_all_labels, th=0.5): # Pass simplified all_labels
|
| 335 |
+
predictions, labels = p # predictions are logits
|
| 336 |
+
|
| 337 |
+
# Determine index of 'O' label in the current_all_labels (simplified)
|
| 338 |
+
o_label_str = "O" # Assuming 'O' is always named this way
|
| 339 |
+
o_label_idx = -1
|
| 340 |
+
for i, lbl_str in enumerate(current_all_labels): # current_all_labels is a list of strings like ['EMAIL', ..., 'O']
|
| 341 |
+
if lbl_str == o_label_str:
|
| 342 |
+
o_label_idx = i
|
| 343 |
+
break
|
| 344 |
+
if o_label_idx == -1:
|
| 345 |
+
print(f"Warning: Could not find 'O' label in {current_all_labels} for metric thresholding. Using last index.")
|
| 346 |
+
o_label_idx = len(current_all_labels) - 1
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
true_predictions = []
|
| 350 |
+
true_labels = []
|
| 351 |
+
for tmp, batch_l in zip(predictions, labels): # tmp contains logits for a batch item
|
| 352 |
+
batch_p_probs = torch.softmax(torch.from_numpy(tmp), dim=-1).numpy() # Convert logits to probs
|
| 353 |
+
|
| 354 |
+
# Get a view of the 'O' column probabilities
|
| 355 |
+
# batch_p_probs shape is (batch_size, seq_len, num_simplified_labels)
|
| 356 |
+
o_column_probs_slice = batch_p_probs[:, :, o_label_idx] # Shape (batch_size, seq_len)
|
| 357 |
+
|
| 358 |
+
# Create the boolean mask based on this slice
|
| 359 |
+
condition_mask = o_column_probs_slice < th # Shape (batch_size, seq_len)
|
| 360 |
+
|
| 361 |
+
# Assign to the 'O' column slice using this mask. This modifies batch_p_probs in place.
|
| 362 |
+
o_column_probs_slice[condition_mask] = -9999
|
| 363 |
+
|
| 364 |
+
batch_p_final_ids = np.argmax(batch_p_probs, axis=-1) # Get final predicted IDs
|
| 365 |
+
|
| 366 |
+
true_predictions.extend([current_all_labels[p_id] for (p_id, l_id) in zip(batch_p_final_ids.reshape(-1), batch_l.reshape(-1)) if l_id != -100])
|
| 367 |
+
true_labels.extend([current_all_labels[l_id] for l_id in batch_l.reshape(-1) if l_id != -100])
|
| 368 |
+
|
| 369 |
+
# Using seqeval for P, R, F1 (F5)
|
| 370 |
+
# seqeval expects lists of lists of strings.
|
| 371 |
+
# Here true_labels and true_predictions are flat lists.
|
| 372 |
+
# For proper seqeval, we'd need to group them back by sequence.
|
| 373 |
+
# However, the original script's compute_metrics calculates overall P, R, F5 on flat lists.
|
| 374 |
+
# Let's keep that structure for now, as it's what the script used for its internal F5.
|
| 375 |
+
# Note: This F5 is on simplified labels, not BIO.
|
| 376 |
+
|
| 377 |
+
recall = recall_score([true_labels], [true_predictions], average='micro') # Use micro for overall
|
| 378 |
+
precision = precision_score([true_labels], [true_predictions], average='micro')
|
| 379 |
+
# Calculate F5 score manually based on P and R
|
| 380 |
+
beta = 5
|
| 381 |
+
f5_score = (1 + beta**2) * precision * recall / ((beta**2 * precision) + recall + 1e-100)
|
| 382 |
+
if precision + recall == 0: # Handle case where P and R are both zero
|
| 383 |
+
f5_score = 0.0
|
| 384 |
+
|
| 385 |
+
results = {
|
| 386 |
+
'recall': recall,
|
| 387 |
+
'precision': precision,
|
| 388 |
+
'f1': f5_score # Reporting F5 as 'f1' to match original key, or add 'f5'
|
| 389 |
+
}
|
| 390 |
+
return results
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
# %% [markdown]
|
| 394 |
+
# # Train & Valid Func
|
| 395 |
+
|
| 396 |
+
# %%
|
| 397 |
+
def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
|
| 398 |
+
model.train()
|
| 399 |
+
running_loss = []
|
| 400 |
+
bar = tqdm(loader, total=len(loader))
|
| 401 |
+
for batch in bar:
|
| 402 |
+
optimizer.zero_grad()
|
| 403 |
+
|
| 404 |
+
input_ids = batch['input_ids'].to(device)
|
| 405 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 406 |
+
labels = batch['labels'].to(device)
|
| 407 |
+
|
| 408 |
+
if scaler is not None:
|
| 409 |
+
with torch.cuda.amp.autocast():
|
| 410 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 411 |
+
loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
|
| 412 |
+
scaler.scale(loss).backward()
|
| 413 |
+
scaler.step(optimizer)
|
| 414 |
+
scaler.update()
|
| 415 |
+
else:
|
| 416 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 417 |
+
loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
|
| 418 |
+
loss.backward()
|
| 419 |
+
optimizer.step()
|
| 420 |
+
|
| 421 |
+
scheduler.step() # Moved scheduler step here, common practice per batch
|
| 422 |
+
running_loss.append(loss.item())
|
| 423 |
+
bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
|
| 424 |
+
|
| 425 |
+
return np.mean(running_loss)
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def valid_one_epoch(model, loader):
|
| 429 |
+
model.eval()
|
| 430 |
+
running_loss = []
|
| 431 |
+
bar = tqdm(loader, total=len(loader))
|
| 432 |
+
predictions_logits = [] # Store logits
|
| 433 |
+
true_labels_list = [] # Store true labels
|
| 434 |
+
|
| 435 |
+
with torch.no_grad():
|
| 436 |
+
for batch in bar:
|
| 437 |
+
input_ids = batch['input_ids'].to(device)
|
| 438 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 439 |
+
labels_ = batch['labels'].to(device)
|
| 440 |
+
|
| 441 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels_)
|
| 442 |
+
loss = criterion(outputs['logits'], labels_) # criterion uses simplified label count
|
| 443 |
+
|
| 444 |
+
running_loss.append(loss.item())
|
| 445 |
+
predictions_logits.append(outputs['logits'].detach().cpu().numpy())
|
| 446 |
+
true_labels_list.append(labels_.detach().cpu().numpy())
|
| 447 |
+
|
| 448 |
+
bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
|
| 449 |
+
|
| 450 |
+
# Pass simplified all_labels_simplified to compute_metrics
|
| 451 |
+
metrics_results = compute_metrics((predictions_logits, true_labels_list), all_labels_simplified)
|
| 452 |
+
return np.mean(running_loss), metrics_results
|
| 453 |
+
|
| 454 |
+
# %% [markdown]
|
| 455 |
+
# # Scheduler
|
| 456 |
+
|
| 457 |
+
# %%
|
| 458 |
+
rcParams['figure.figsize'] = 20, 2
|
| 459 |
+
|
| 460 |
+
def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
|
| 461 |
+
return get_cosine_schedule_with_warmup(
|
| 462 |
+
optimizer,
|
| 463 |
+
int(num_training_steps * warmup_steps_ratio),
|
| 464 |
+
num_training_steps
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
opt = AdamW(model.parameters(), lr=lr)
|
| 468 |
+
sch = get_scheduler(opt, 100, 0.1)
|
| 469 |
+
|
| 470 |
+
lrs = []
|
| 471 |
+
for epoch in range(1, 100+1):
|
| 472 |
+
sch.step()
|
| 473 |
+
lrs.append(opt.param_groups[0]["lr"])
|
| 474 |
+
|
| 475 |
+
plt.plot(range(len(lrs)), lrs)
|
| 476 |
+
|
| 477 |
+
# %% [markdown]
|
| 478 |
+
# # Run
|
| 479 |
+
|
| 480 |
+
# %%
|
| 481 |
+
def run(fold,model_to_train): # Renamed model to model_to_train to avoid conflict
|
| 482 |
+
|
| 483 |
+
log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
|
| 484 |
+
# Define a directory for saving Hugging Face model (config, weights, tokenizer_config etc.)
|
| 485 |
+
model_save_dir_best = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best_hf')
|
| 486 |
+
model_save_dir_last = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_last_hf')
|
| 487 |
+
|
| 488 |
+
# Create directories if they don't exist
|
| 489 |
+
os.makedirs(model_save_dir_best, exist_ok=True)
|
| 490 |
+
os.makedirs(model_save_dir_last, exist_ok=True)
|
| 491 |
+
# Path for optimizer and other states (still using torch.save for these with custom loop)
|
| 492 |
+
optimizer_etc_file_last = os.path.join(model_save_dir_last, 'optimizer_etc_states.pth')
|
| 493 |
+
|
| 494 |
+
|
| 495 |
+
valid_doc_ids = folds[fold]
|
| 496 |
+
# train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
|
| 497 |
+
train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))]
|
| 498 |
+
valid_data = [x for x in data if x['document'] in valid_doc_ids]
|
| 499 |
+
|
| 500 |
+
# Use simplified label2id
|
| 501 |
+
dataset_train = PIIDataset(tokenizer, train_data, label2id_simplified, TRAINING_MAX_LENGTH)
|
| 502 |
+
dataset_valid = PIIDataset(tokenizer, valid_data, label2id_simplified, TRAINING_MAX_LENGTH)
|
| 503 |
+
print(len(dataset_train), len(dataset_valid))
|
| 504 |
+
|
| 505 |
+
loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=2, collate_fn=collate_fn) # Reduced num_workers for potential local issues
|
| 506 |
+
loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=2, collate_fn=collate_fn)
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
# model = get_model() # Original script had this, but model is passed now
|
| 510 |
+
current_model = model_to_train.to(device) # Use the passed model
|
| 511 |
+
|
| 512 |
+
optimizer = AdamW(current_model.parameters(), lr=lr, weight_decay=wd)
|
| 513 |
+
scaler = torch.cuda.amp.GradScaler() if use_amp else None
|
| 514 |
+
from_epoch = 0 # Assuming fresh train, not resuming
|
| 515 |
+
metric_best = 0
|
| 516 |
+
|
| 517 |
+
scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1) # warmup_steps_ratio is 0.1
|
| 518 |
+
|
| 519 |
+
for epoch in range(1, n_epochs+1):
|
| 520 |
+
# if epoch < from_epoch + 1:
|
| 521 |
+
# print(logs[epoch-1]) # Assuming logs is not used for fresh train
|
| 522 |
+
# continue
|
| 523 |
+
|
| 524 |
+
print(time.ctime(), 'Epoch:', epoch)
|
| 525 |
+
|
| 526 |
+
train_loss = train_one_epoch(current_model, loader_train, optimizer, scheduler, scaler)
|
| 527 |
+
valid_loss, result = valid_one_epoch(current_model, loader_valid)
|
| 528 |
+
metric = result["f1"] # This is F5 score on simplified labels
|
| 529 |
+
|
| 530 |
+
content = time.ctime() + ' ' + \
|
| 531 |
+
f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
|
| 532 |
+
f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
|
| 533 |
+
f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric (F5 on simplified): {metric:.4f}.'
|
| 534 |
+
print(content)
|
| 535 |
+
with open(log_file, 'a') as appender:
|
| 536 |
+
appender.write(content + '\n')
|
| 537 |
+
|
| 538 |
+
if metric > metric_best:
|
| 539 |
+
print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model to {model_save_dir_best} ...')
|
| 540 |
+
if not DEBUG:
|
| 541 |
+
current_model.save_pretrained(model_save_dir_best, safe_serialization=True)
|
| 542 |
+
tokenizer.save_pretrained(model_save_dir_best) # Save tokenizer too
|
| 543 |
+
metric_best = metric
|
| 544 |
+
|
| 545 |
+
# Save Last states (model, optimizer, scheduler)
|
| 546 |
+
if not DEBUG:
|
| 547 |
+
current_model.save_pretrained(model_save_dir_last, safe_serialization=True)
|
| 548 |
+
tokenizer.save_pretrained(model_save_dir_last)
|
| 549 |
+
torch.save(
|
| 550 |
+
{
|
| 551 |
+
'epoch': epoch,
|
| 552 |
+
# model_state_dict is not needed if using save_pretrained for the model itself
|
| 553 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 554 |
+
'scheduler_state_dict': scheduler.state_dict(), # Save scheduler state
|
| 555 |
+
'scaler_state_dict': scaler.state_dict() if scaler else None,
|
| 556 |
+
'metric_best': metric_best,
|
| 557 |
+
},
|
| 558 |
+
optimizer_etc_file_last # Save optimizer and other states separately
|
| 559 |
+
)
|
| 560 |
+
|
| 561 |
+
del current_model # Use current_model
|
| 562 |
+
torch.cuda.empty_cache()
|
| 563 |
+
gc.collect()
|
| 564 |
+
|
| 565 |
+
# %%
|
| 566 |
+
# model is defined globally from "Model" cell. Pass it to run.
|
| 567 |
+
run(0, model)
|
| 568 |
+
|
| 569 |
+
# %%
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
|
code/training-single-model-v9.py
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# %% [markdown]
|
| 2 |
+
# **Notice: You should run this notebook on a GPU with at least 24GB RAM for avoiding OOM**
|
| 3 |
+
#
|
| 4 |
+
# # Introduction
|
| 5 |
+
#
|
| 6 |
+
# Hi all,
|
| 7 |
+
#
|
| 8 |
+
# In this notebook, I will share the comprehensive steps and strategies I employed to achieve `0.963` on the Private Leaderboard using a single model.
|
| 9 |
+
#
|
| 10 |
+
# Throughout this competition, I explored various methodologies and refined my approaches to push the model to its limits. This notebook is structured to guide you through each phase of my process.
|
| 11 |
+
#
|
| 12 |
+
# Whether you're a beginner eager to learn the ropes of competitive machine learning or an experienced practitioner looking for new insights, this notebook aims to provide valuable perspectives and techniques that can be adapted to your future projects.
|
| 13 |
+
#
|
| 14 |
+
# Let's dive into the details and uncover the techniques that led to a successful submission!
|
| 15 |
+
#
|
| 16 |
+
# [Submission Notebook PrivateLB 0.963](https://www.kaggle.com/code/haqishen/single-model-no-pp-privatelb-0-963)
|
| 17 |
+
#
|
| 18 |
+
# # Summary
|
| 19 |
+
#
|
| 20 |
+
# * With [2k external training samples](https://www.kaggle.com/code/valentinwerner/fix-punctuation-tokenization-external-dataset)
|
| 21 |
+
# * `TRAINING_MAX_LENGTH = 1024`, which should be afforded by many people
|
| 22 |
+
# * When the text length exceeds 1024, only the first 512 and the last 512 characters of the text will be used for training.
|
| 23 |
+
# * For inference, no truncation.
|
| 24 |
+
# * `downsample rate = 0.3`, randomly downsample negative samples (articles without any PII tokens) to 30%
|
| 25 |
+
# * `attention_probs_dropout_prob = 0.4`, default value was `0.1`
|
| 26 |
+
# * `weight_decay = 0.`, do not use weight decay for optimizer. default value was `0.01`
|
| 27 |
+
# * `pos_weight = 10`, means for positive samples, weighted 10x for loss calculation.
|
| 28 |
+
# * As many people knows, for most public submission notebooks, they use predictions with `th > 0.9` to get higher score.
|
| 29 |
+
# * By adapting `pos_weight`, I was able to achieve best performance with `th around 0.5`
|
| 30 |
+
# * cosine with warmup learning rate schedule -- almost became a standard setting.
|
| 31 |
+
# * warmup 0.5 epo
|
| 32 |
+
# * cosine 4.5 epo
|
| 33 |
+
#
|
| 34 |
+
# # Acknowledge
|
| 35 |
+
#
|
| 36 |
+
# I was provided Z8G5 Fury workstation with dual RTX6000 ada GPU.
|
| 37 |
+
#
|
| 38 |
+
# For training a single model this notebook shows, it takes less than 40min!
|
| 39 |
+
#
|
| 40 |
+
|
| 41 |
+
# %%
|
| 42 |
+
import os
|
| 43 |
+
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
|
| 44 |
+
import warnings
|
| 45 |
+
warnings.filterwarnings("ignore")
|
| 46 |
+
DEBUG=False
|
| 47 |
+
|
| 48 |
+
# %%
|
| 49 |
+
# !pip install -q seqeval
|
| 50 |
+
|
| 51 |
+
# %%
|
| 52 |
+
import gc
|
| 53 |
+
import json
|
| 54 |
+
import copy
|
| 55 |
+
import time
|
| 56 |
+
import pickle
|
| 57 |
+
import numpy as np
|
| 58 |
+
import pandas as pd
|
| 59 |
+
from itertools import chain
|
| 60 |
+
from transformers import AutoTokenizer
|
| 61 |
+
from transformers import AutoModelForTokenClassification
|
| 62 |
+
from transformers import get_cosine_schedule_with_warmup
|
| 63 |
+
from tqdm import tqdm
|
| 64 |
+
|
| 65 |
+
from seqeval.metrics import recall_score, precision_score
|
| 66 |
+
from seqeval.metrics import f1_score
|
| 67 |
+
|
| 68 |
+
import torch
|
| 69 |
+
from torch import nn
|
| 70 |
+
from torch.utils.data import Dataset, DataLoader
|
| 71 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 72 |
+
from torch.nn import CrossEntropyLoss
|
| 73 |
+
from torch.optim import AdamW
|
| 74 |
+
|
| 75 |
+
import matplotlib.pyplot as plt
|
| 76 |
+
from pylab import rcParams
|
| 77 |
+
|
| 78 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 79 |
+
|
| 80 |
+
# %% [markdown]
|
| 81 |
+
# # Config
|
| 82 |
+
|
| 83 |
+
# %%
|
| 84 |
+
exp_name = 'deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_simplified_labels' # Updated exp_name
|
| 85 |
+
|
| 86 |
+
backbone = 'microsoft/deberta-v3-large'
|
| 87 |
+
|
| 88 |
+
TRAINING_MAX_LENGTH = 4096
|
| 89 |
+
batch_size = 2
|
| 90 |
+
lr = 1e-5
|
| 91 |
+
ds_rate = 0.3
|
| 92 |
+
attention_probs_dropout_prob = 0.4
|
| 93 |
+
hidden_dropout_prob = 0.1
|
| 94 |
+
wd = 0.
|
| 95 |
+
pos_weight = 10
|
| 96 |
+
n_epochs = 5
|
| 97 |
+
|
| 98 |
+
use_amp = True
|
| 99 |
+
|
| 100 |
+
# %% [markdown]
|
| 101 |
+
# # Data
|
| 102 |
+
|
| 103 |
+
# %%
|
| 104 |
+
from sklearn.model_selection import train_test_split
|
| 105 |
+
|
| 106 |
+
data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
|
| 107 |
+
for sample in data:
|
| 108 |
+
if len(set(sample['labels'])) > 1:
|
| 109 |
+
sample['is_pos'] = 1
|
| 110 |
+
else:
|
| 111 |
+
sample['is_pos'] = 0
|
| 112 |
+
print("original datapoints: ", len(data))
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# moredata = json.load(open('/kaggle/input/fix-punctuation-tokenization-external-dataset/moredata_dataset_fixed.json'))
|
| 116 |
+
# print("moredata datapoints: ", len(moredata))
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# %%
|
| 120 |
+
from sklearn.model_selection import GroupKFold
|
| 121 |
+
import random
|
| 122 |
+
random.seed(0)
|
| 123 |
+
kf = GroupKFold(n_splits=5)
|
| 124 |
+
folds = []
|
| 125 |
+
for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
|
| 126 |
+
folds.append([data[i]['document'] for i in valid_index])
|
| 127 |
+
|
| 128 |
+
# %%
|
| 129 |
+
# Define simplified labels
|
| 130 |
+
pii_types = [
|
| 131 |
+
'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
|
| 132 |
+
'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
|
| 133 |
+
]
|
| 134 |
+
# all_labels_simplified will be pii_types + ['O']
|
| 135 |
+
all_labels_simplified = sorted(pii_types + ['O'])
|
| 136 |
+
label2id_simplified = {l: i for i, l in enumerate(all_labels_simplified)}
|
| 137 |
+
id2label_simplified = {v: k for k, v in label2id_simplified.items()}
|
| 138 |
+
|
| 139 |
+
# Original target for reference or other logic if needed, but not for model head
|
| 140 |
+
original_target_bio = [
|
| 141 |
+
'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
|
| 142 |
+
'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
|
| 143 |
+
'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL'
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
print(f"Simplified labels ({len(all_labels_simplified)}): {id2label_simplified}")
|
| 147 |
+
|
| 148 |
+
# %% [markdown]
|
| 149 |
+
# # Dataset
|
| 150 |
+
|
| 151 |
+
# %%
|
| 152 |
+
def tokenize(example, tokenizer, label2id, max_length): # label2id here will be label2id_simplified
|
| 153 |
+
|
| 154 |
+
# rebuild text from tokens
|
| 155 |
+
text = []
|
| 156 |
+
char_labels = [] # Renamed from 'labels' to avoid confusion with example['provided_labels']
|
| 157 |
+
|
| 158 |
+
for t, l, ws in zip(
|
| 159 |
+
example["tokens"], example["provided_labels"], example["trailing_whitespace"]
|
| 160 |
+
):
|
| 161 |
+
text.append(t)
|
| 162 |
+
# Simplify label: strip B-/I- prefix if it's a PII label
|
| 163 |
+
current_label = l
|
| 164 |
+
if l != "O":
|
| 165 |
+
current_label = l[2:] # Strip "B-" or "I-"
|
| 166 |
+
char_labels.extend([current_label] * len(t))
|
| 167 |
+
|
| 168 |
+
if ws:
|
| 169 |
+
text.append(" ")
|
| 170 |
+
char_labels.append("O") # Whitespace is always "O"
|
| 171 |
+
|
| 172 |
+
tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
|
| 173 |
+
# Truncation strategy
|
| 174 |
+
if len(tokenized['input_ids']) > max_length:
|
| 175 |
+
# Implement the "first N/2 + last N/2" truncation strategy
|
| 176 |
+
first_half_len = max_length // 2
|
| 177 |
+
second_half_len = max_length - first_half_len # Ensures total length is max_length
|
| 178 |
+
|
| 179 |
+
keys_to_truncate = ['input_ids', 'attention_mask', 'offset_mapping']
|
| 180 |
+
for key in keys_to_truncate:
|
| 181 |
+
if key in tokenized:
|
| 182 |
+
original_list = tokenized[key]
|
| 183 |
+
# This slicing takes the first 'first_half_len' elements and the last 'second_half_len' elements
|
| 184 |
+
tokenized[key] = original_list[:first_half_len] + original_list[-second_half_len:]
|
| 185 |
+
# Consider adding a warning if a key is expected but not found, though unlikely for these standard keys.
|
| 186 |
+
|
| 187 |
+
char_labels_arr = np.array(char_labels) # This remains after truncation, using the original full char_labels
|
| 188 |
+
|
| 189 |
+
text_str = "".join(text) # Renamed from 'text' to avoid conflict
|
| 190 |
+
token_labels = []
|
| 191 |
+
|
| 192 |
+
# Access offset_mapping using dictionary style access as tokenized might be a dict
|
| 193 |
+
current_offset_mapping = tokenized["offset_mapping"]
|
| 194 |
+
|
| 195 |
+
for start_idx, end_idx in current_offset_mapping:
|
| 196 |
+
# Handle special tokens like [CLS], [SEP] which have (0,0) offset_mapping
|
| 197 |
+
# or any other token that doesn't map to actual characters in the original text.
|
| 198 |
+
if start_idx == end_idx:
|
| 199 |
+
token_labels.append(label2id["O"]) # Assign 'O' to special/empty tokens
|
| 200 |
+
continue
|
| 201 |
+
|
| 202 |
+
# Ensure indices are within the bounds of char_labels_arr and form a valid span
|
| 203 |
+
# end_idx is exclusive for slicing, so it can be == len(char_labels_arr)
|
| 204 |
+
if not (start_idx < end_idx and start_idx < len(char_labels_arr) and end_idx <= len(char_labels_arr)):
|
| 205 |
+
token_labels.append(label2id["O"]) # Assign 'O' if token is outside char_labels range or invalid span
|
| 206 |
+
continue
|
| 207 |
+
|
| 208 |
+
char_span_labels = char_labels_arr[start_idx:end_idx]
|
| 209 |
+
|
| 210 |
+
# If char_span_labels is somehow empty (e.g. if previous check was less strict)
|
| 211 |
+
if not char_span_labels.size:
|
| 212 |
+
token_labels.append(label2id["O"])
|
| 213 |
+
continue
|
| 214 |
+
|
| 215 |
+
unique_labels, counts = np.unique(char_span_labels, return_counts=True)
|
| 216 |
+
|
| 217 |
+
pii_labels_present_in_token = []
|
| 218 |
+
pii_counts_present_in_token = []
|
| 219 |
+
|
| 220 |
+
for label, count in zip(unique_labels, counts):
|
| 221 |
+
if label != "O": # Assuming "O" is the string for non-PII
|
| 222 |
+
pii_labels_present_in_token.append(label)
|
| 223 |
+
pii_counts_present_in_token.append(count)
|
| 224 |
+
|
| 225 |
+
if pii_labels_present_in_token:
|
| 226 |
+
# If PII labels are present, choose the most frequent PII label
|
| 227 |
+
most_frequent_pii_idx = np.argmax(pii_counts_present_in_token)
|
| 228 |
+
most_frequent_pii_label = pii_labels_present_in_token[most_frequent_pii_idx]
|
| 229 |
+
token_labels.append(label2id[most_frequent_pii_label])
|
| 230 |
+
else:
|
| 231 |
+
# If only 'O' is present or no PII labels found in the span
|
| 232 |
+
token_labels.append(label2id["O"])
|
| 233 |
+
|
| 234 |
+
length = len(tokenized["input_ids"]) # Use dictionary-style access
|
| 235 |
+
|
| 236 |
+
return {**tokenized, "labels": token_labels, "length": length}
|
| 237 |
+
|
| 238 |
+
# %%
|
| 239 |
+
tokenizer = AutoTokenizer.from_pretrained(backbone)
|
| 240 |
+
|
| 241 |
+
class PIIDataset(Dataset):
|
| 242 |
+
def __init__(self, tokenizer, data, label2id, max_length): # label2id will be simplified
|
| 243 |
+
self.tokenizer = tokenizer
|
| 244 |
+
self.data = copy.deepcopy(data)
|
| 245 |
+
self.label2id = label2id
|
| 246 |
+
self.max_length = max_length
|
| 247 |
+
|
| 248 |
+
for i in tqdm(range(len(self.data))):
|
| 249 |
+
if self.data[i].get('input_ids') is None:
|
| 250 |
+
self.data[i]['provided_labels'] = self.data[i]['labels'] # Keep original BIO labels here for tokenize
|
| 251 |
+
self.data[i] = tokenize(
|
| 252 |
+
self.data[i],
|
| 253 |
+
self.tokenizer,
|
| 254 |
+
self.label2id, # Pass simplified label2id
|
| 255 |
+
self.max_length
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
def __len__(self):
|
| 259 |
+
return len(self.data)
|
| 260 |
+
|
| 261 |
+
def __getitem__(self, idx):
|
| 262 |
+
return self.data[idx]
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def collate_fn(batch):
|
| 266 |
+
input_ids = [torch.tensor(x['input_ids']) for x in batch]
|
| 267 |
+
attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
|
| 268 |
+
labels = [torch.tensor(x['labels']) for x in batch]
|
| 269 |
+
|
| 270 |
+
input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0)
|
| 271 |
+
attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
|
| 272 |
+
labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
|
| 273 |
+
|
| 274 |
+
return {
|
| 275 |
+
'input_ids': input_ids_padded,
|
| 276 |
+
'attention_mask': attention_masks_padded,
|
| 277 |
+
'labels': labels_padded,
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
# Test with simplified labels
|
| 281 |
+
ds = PIIDataset(tokenizer, data[:20], label2id_simplified, TRAINING_MAX_LENGTH)
|
| 282 |
+
print(f"Example tokenized input IDs length: {len(ds[9]['input_ids'])}")
|
| 283 |
+
print(f"Example tokenized labels: {ds[9]['labels']}")
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
# %% [markdown]
|
| 287 |
+
# # Model
|
| 288 |
+
|
| 289 |
+
# %%
|
| 290 |
+
model = AutoModelForTokenClassification.from_pretrained(
|
| 291 |
+
backbone,
|
| 292 |
+
attention_probs_dropout_prob=attention_probs_dropout_prob,
|
| 293 |
+
hidden_dropout_prob=hidden_dropout_prob,
|
| 294 |
+
num_labels=len(all_labels_simplified), # Use length of simplified labels (8)
|
| 295 |
+
id2label=id2label_simplified, # Use simplified id2label
|
| 296 |
+
label2id=label2id_simplified, # Use simplified label2id
|
| 297 |
+
ignore_mismatched_sizes=True # Good practice if changing head
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
# %% [markdown]
|
| 301 |
+
# # Loss
|
| 302 |
+
|
| 303 |
+
# %%
|
| 304 |
+
# Adjust weights for simplified labels
|
| 305 |
+
weights_simplified = torch.ones(len(all_labels_simplified)) * pos_weight
|
| 306 |
+
# Assuming 'O' is the label for non-PII and its ID is known via label2id_simplified
|
| 307 |
+
if 'O' in label2id_simplified:
|
| 308 |
+
weights_simplified[label2id_simplified['O']] = 1
|
| 309 |
+
else:
|
| 310 |
+
# Fallback if 'O' is not named exactly like that, though it should be.
|
| 311 |
+
# This assumes 'O' is the last class if not found by name.
|
| 312 |
+
print("Warning: 'O' label not found in label2id_simplified for weight assignment. Assuming last index for 'O'.")
|
| 313 |
+
weights_simplified[-1] = 1
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
CE_simplified = nn.CrossEntropyLoss(weight=weights_simplified.to(device), ignore_index=-100)
|
| 317 |
+
def criterion(logits, labels):
|
| 318 |
+
# logits shape: (batch_size, seq_len, num_simplified_labels)
|
| 319 |
+
# labels shape: (batch_size, seq_len)
|
| 320 |
+
return CE_simplified(logits.view(-1, len(all_labels_simplified)), labels.view(-1))
|
| 321 |
+
|
| 322 |
+
# %% [markdown]
|
| 323 |
+
# # Metric
|
| 324 |
+
|
| 325 |
+
# %%
|
| 326 |
+
def compute_metrics(p, current_all_labels, th=0.5): # Pass simplified all_labels
|
| 327 |
+
predictions, labels = p # predictions are logits
|
| 328 |
+
|
| 329 |
+
# Determine index of 'O' label in the current_all_labels (simplified)
|
| 330 |
+
o_label_str = "O" # Assuming 'O' is always named this way
|
| 331 |
+
o_label_idx = -1
|
| 332 |
+
for i, lbl_str in enumerate(current_all_labels): # current_all_labels is a list of strings like ['EMAIL', ..., 'O']
|
| 333 |
+
if lbl_str == o_label_str:
|
| 334 |
+
o_label_idx = i
|
| 335 |
+
break
|
| 336 |
+
if o_label_idx == -1:
|
| 337 |
+
print(f"Warning: Could not find 'O' label in {current_all_labels} for metric thresholding. Using last index.")
|
| 338 |
+
o_label_idx = len(current_all_labels) - 1
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
true_predictions = []
|
| 342 |
+
true_labels = []
|
| 343 |
+
for tmp, batch_l in zip(predictions, labels): # tmp contains logits for a batch item
|
| 344 |
+
batch_p_probs = torch.softmax(torch.from_numpy(tmp), dim=-1).numpy() # Convert logits to probs
|
| 345 |
+
|
| 346 |
+
# Get a view of the 'O' column probabilities
|
| 347 |
+
# batch_p_probs shape is (batch_size, seq_len, num_simplified_labels)
|
| 348 |
+
o_column_probs_slice = batch_p_probs[:, :, o_label_idx] # Shape (batch_size, seq_len)
|
| 349 |
+
|
| 350 |
+
# Create the boolean mask based on this slice
|
| 351 |
+
condition_mask = o_column_probs_slice < th # Shape (batch_size, seq_len)
|
| 352 |
+
|
| 353 |
+
# Assign to the 'O' column slice using this mask. This modifies batch_p_probs in place.
|
| 354 |
+
o_column_probs_slice[condition_mask] = -9999
|
| 355 |
+
|
| 356 |
+
batch_p_final_ids = np.argmax(batch_p_probs, axis=-1) # Get final predicted IDs
|
| 357 |
+
|
| 358 |
+
true_predictions.extend([current_all_labels[p_id] for (p_id, l_id) in zip(batch_p_final_ids.reshape(-1), batch_l.reshape(-1)) if l_id != -100])
|
| 359 |
+
true_labels.extend([current_all_labels[l_id] for l_id in batch_l.reshape(-1) if l_id != -100])
|
| 360 |
+
|
| 361 |
+
# Using seqeval for P, R, F1 (F5)
|
| 362 |
+
# seqeval expects lists of lists of strings.
|
| 363 |
+
# Here true_labels and true_predictions are flat lists.
|
| 364 |
+
# For proper seqeval, we'd need to group them back by sequence.
|
| 365 |
+
# However, the original script's compute_metrics calculates overall P, R, F5 on flat lists.
|
| 366 |
+
# Let's keep that structure for now, as it's what the script used for its internal F5.
|
| 367 |
+
# Note: This F5 is on simplified labels, not BIO.
|
| 368 |
+
|
| 369 |
+
recall = recall_score([true_labels], [true_predictions], average='micro') # Use micro for overall
|
| 370 |
+
precision = precision_score([true_labels], [true_predictions], average='micro')
|
| 371 |
+
# Calculate F5 score manually based on P and R
|
| 372 |
+
beta = 5
|
| 373 |
+
f5_score = (1 + beta**2) * precision * recall / ((beta**2 * precision) + recall + 1e-100)
|
| 374 |
+
if precision + recall == 0: # Handle case where P and R are both zero
|
| 375 |
+
f5_score = 0.0
|
| 376 |
+
|
| 377 |
+
results = {
|
| 378 |
+
'recall': recall,
|
| 379 |
+
'precision': precision,
|
| 380 |
+
'f1': f5_score # Reporting F5 as 'f1' to match original key, or add 'f5'
|
| 381 |
+
}
|
| 382 |
+
return results
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
# %% [markdown]
|
| 386 |
+
# # Train & Valid Func
|
| 387 |
+
|
| 388 |
+
# %%
|
| 389 |
+
def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
|
| 390 |
+
model.train()
|
| 391 |
+
running_loss = []
|
| 392 |
+
bar = tqdm(loader, total=len(loader))
|
| 393 |
+
for batch in bar:
|
| 394 |
+
optimizer.zero_grad()
|
| 395 |
+
|
| 396 |
+
input_ids = batch['input_ids'].to(device)
|
| 397 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 398 |
+
labels = batch['labels'].to(device)
|
| 399 |
+
|
| 400 |
+
if scaler is not None:
|
| 401 |
+
with torch.cuda.amp.autocast():
|
| 402 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 403 |
+
loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
|
| 404 |
+
scaler.scale(loss).backward()
|
| 405 |
+
scaler.step(optimizer)
|
| 406 |
+
scaler.update()
|
| 407 |
+
else:
|
| 408 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 409 |
+
loss = criterion(outputs['logits'], labels) # criterion uses simplified label count
|
| 410 |
+
loss.backward()
|
| 411 |
+
optimizer.step()
|
| 412 |
+
|
| 413 |
+
scheduler.step() # Moved scheduler step here, common practice per batch
|
| 414 |
+
running_loss.append(loss.item())
|
| 415 |
+
bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
|
| 416 |
+
|
| 417 |
+
return np.mean(running_loss)
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
def valid_one_epoch(model, loader):
|
| 421 |
+
model.eval()
|
| 422 |
+
running_loss = []
|
| 423 |
+
bar = tqdm(loader, total=len(loader))
|
| 424 |
+
predictions_logits = [] # Store logits
|
| 425 |
+
true_labels_list = [] # Store true labels
|
| 426 |
+
|
| 427 |
+
with torch.no_grad():
|
| 428 |
+
for batch in bar:
|
| 429 |
+
input_ids = batch['input_ids'].to(device)
|
| 430 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 431 |
+
labels_ = batch['labels'].to(device)
|
| 432 |
+
|
| 433 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels_)
|
| 434 |
+
loss = criterion(outputs['logits'], labels_) # criterion uses simplified label count
|
| 435 |
+
|
| 436 |
+
running_loss.append(loss.item())
|
| 437 |
+
predictions_logits.append(outputs['logits'].detach().cpu().numpy())
|
| 438 |
+
true_labels_list.append(labels_.detach().cpu().numpy())
|
| 439 |
+
|
| 440 |
+
bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
|
| 441 |
+
|
| 442 |
+
# Pass simplified all_labels_simplified to compute_metrics
|
| 443 |
+
metrics_results = compute_metrics((predictions_logits, true_labels_list), all_labels_simplified)
|
| 444 |
+
return np.mean(running_loss), metrics_results
|
| 445 |
+
|
| 446 |
+
# %% [markdown]
|
| 447 |
+
# # Scheduler
|
| 448 |
+
|
| 449 |
+
# %%
|
| 450 |
+
rcParams['figure.figsize'] = 20, 2
|
| 451 |
+
|
| 452 |
+
def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
|
| 453 |
+
return get_cosine_schedule_with_warmup(
|
| 454 |
+
optimizer,
|
| 455 |
+
int(num_training_steps * warmup_steps_ratio),
|
| 456 |
+
num_training_steps
|
| 457 |
+
)
|
| 458 |
+
|
| 459 |
+
opt = AdamW(model.parameters(), lr=lr)
|
| 460 |
+
sch = get_scheduler(opt, 100, 0.1)
|
| 461 |
+
|
| 462 |
+
lrs = []
|
| 463 |
+
for epoch in range(1, 100+1):
|
| 464 |
+
sch.step()
|
| 465 |
+
lrs.append(opt.param_groups[0]["lr"])
|
| 466 |
+
|
| 467 |
+
plt.plot(range(len(lrs)), lrs)
|
| 468 |
+
|
| 469 |
+
# %% [markdown]
|
| 470 |
+
# # Run
|
| 471 |
+
|
| 472 |
+
# %%
|
| 473 |
+
def run(fold,model_to_train): # Renamed model to model_to_train to avoid conflict
|
| 474 |
+
|
| 475 |
+
log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
|
| 476 |
+
# Define a directory for saving Hugging Face model (config, weights, tokenizer_config etc.)
|
| 477 |
+
model_save_dir_best = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best_hf')
|
| 478 |
+
model_save_dir_last = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_last_hf')
|
| 479 |
+
|
| 480 |
+
# Create directories if they don't exist
|
| 481 |
+
os.makedirs(model_save_dir_best, exist_ok=True)
|
| 482 |
+
os.makedirs(model_save_dir_last, exist_ok=True)
|
| 483 |
+
# Path for optimizer and other states (still using torch.save for these with custom loop)
|
| 484 |
+
optimizer_etc_file_last = os.path.join(model_save_dir_last, 'optimizer_etc_states.pth')
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
valid_doc_ids = folds[fold]
|
| 488 |
+
# train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
|
| 489 |
+
train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))]
|
| 490 |
+
valid_data = [x for x in data if x['document'] in valid_doc_ids]
|
| 491 |
+
|
| 492 |
+
# Use simplified label2id
|
| 493 |
+
dataset_train = PIIDataset(tokenizer, train_data, label2id_simplified, TRAINING_MAX_LENGTH)
|
| 494 |
+
dataset_valid = PIIDataset(tokenizer, valid_data, label2id_simplified, TRAINING_MAX_LENGTH)
|
| 495 |
+
print(len(dataset_train), len(dataset_valid))
|
| 496 |
+
|
| 497 |
+
loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=2, collate_fn=collate_fn) # Reduced num_workers for potential local issues
|
| 498 |
+
loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=2, collate_fn=collate_fn)
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
# model = get_model() # Original script had this, but model is passed now
|
| 502 |
+
current_model = model_to_train.to(device) # Use the passed model
|
| 503 |
+
|
| 504 |
+
optimizer = AdamW(current_model.parameters(), lr=lr, weight_decay=wd)
|
| 505 |
+
scaler = torch.cuda.amp.GradScaler() if use_amp else None
|
| 506 |
+
from_epoch = 0 # Assuming fresh train, not resuming
|
| 507 |
+
metric_best = 0
|
| 508 |
+
|
| 509 |
+
scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1) # warmup_steps_ratio is 0.1
|
| 510 |
+
|
| 511 |
+
for epoch in range(1, n_epochs+1):
|
| 512 |
+
# if epoch < from_epoch + 1:
|
| 513 |
+
# print(logs[epoch-1]) # Assuming logs is not used for fresh train
|
| 514 |
+
# continue
|
| 515 |
+
|
| 516 |
+
print(time.ctime(), 'Epoch:', epoch)
|
| 517 |
+
|
| 518 |
+
train_loss = train_one_epoch(current_model, loader_train, optimizer, scheduler, scaler)
|
| 519 |
+
valid_loss, result = valid_one_epoch(current_model, loader_valid)
|
| 520 |
+
metric = result["f1"] # This is F5 score on simplified labels
|
| 521 |
+
|
| 522 |
+
content = time.ctime() + ' ' + \
|
| 523 |
+
f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
|
| 524 |
+
f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
|
| 525 |
+
f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric (F5 on simplified): {metric:.4f}.'
|
| 526 |
+
print(content)
|
| 527 |
+
with open(log_file, 'a') as appender:
|
| 528 |
+
appender.write(content + '\n')
|
| 529 |
+
|
| 530 |
+
if metric > metric_best:
|
| 531 |
+
print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model to {model_save_dir_best} ...')
|
| 532 |
+
if not DEBUG:
|
| 533 |
+
current_model.save_pretrained(model_save_dir_best, safe_serialization=True)
|
| 534 |
+
tokenizer.save_pretrained(model_save_dir_best) # Save tokenizer too
|
| 535 |
+
metric_best = metric
|
| 536 |
+
|
| 537 |
+
# Save Last states (model, optimizer, scheduler)
|
| 538 |
+
if not DEBUG:
|
| 539 |
+
current_model.save_pretrained(model_save_dir_last, safe_serialization=True)
|
| 540 |
+
tokenizer.save_pretrained(model_save_dir_last)
|
| 541 |
+
torch.save(
|
| 542 |
+
{
|
| 543 |
+
'epoch': epoch,
|
| 544 |
+
# model_state_dict is not needed if using save_pretrained for the model itself
|
| 545 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 546 |
+
'scheduler_state_dict': scheduler.state_dict(), # Save scheduler state
|
| 547 |
+
'scaler_state_dict': scaler.state_dict() if scaler else None,
|
| 548 |
+
'metric_best': metric_best,
|
| 549 |
+
},
|
| 550 |
+
optimizer_etc_file_last # Save optimizer and other states separately
|
| 551 |
+
)
|
| 552 |
+
|
| 553 |
+
del current_model # Use current_model
|
| 554 |
+
torch.cuda.empty_cache()
|
| 555 |
+
gc.collect()
|
| 556 |
+
|
| 557 |
+
# %%
|
| 558 |
+
# model is defined globally from "Model" cell. Pass it to run.
|
| 559 |
+
run(0, model)
|
| 560 |
+
|
| 561 |
+
# %%
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
|
code/training-subm-moredata.py
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# %%
|
| 3 |
+
import os
|
| 4 |
+
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
|
| 5 |
+
import warnings
|
| 6 |
+
warnings.filterwarnings("ignore")
|
| 7 |
+
DEBUG=False
|
| 8 |
+
|
| 9 |
+
USE_SIMPLIFIED_LABELS = True # <<< CHANGE THIS TO False TO USE ORIGINAL BIO LABELS
|
| 10 |
+
|
| 11 |
+
# %%
|
| 12 |
+
# !pip install -q seqeval
|
| 13 |
+
|
| 14 |
+
# %%
|
| 15 |
+
import gc
|
| 16 |
+
import json
|
| 17 |
+
import copy
|
| 18 |
+
import time
|
| 19 |
+
import pickle
|
| 20 |
+
import numpy as np
|
| 21 |
+
import pandas as pd
|
| 22 |
+
from itertools import chain
|
| 23 |
+
from transformers import AutoTokenizer
|
| 24 |
+
from transformers import AutoModelForTokenClassification
|
| 25 |
+
from transformers import get_cosine_schedule_with_warmup
|
| 26 |
+
from tqdm import tqdm
|
| 27 |
+
|
| 28 |
+
from seqeval.metrics import recall_score, precision_score
|
| 29 |
+
from seqeval.metrics import f1_score
|
| 30 |
+
|
| 31 |
+
import torch
|
| 32 |
+
from torch import nn
|
| 33 |
+
from torch.utils.data import Dataset, DataLoader
|
| 34 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 35 |
+
from torch.nn import CrossEntropyLoss
|
| 36 |
+
from torch.optim import AdamW
|
| 37 |
+
|
| 38 |
+
import matplotlib.pyplot as plt
|
| 39 |
+
from pylab import rcParams
|
| 40 |
+
|
| 41 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 42 |
+
|
| 43 |
+
# %% [markdown]
|
| 44 |
+
# # Config
|
| 45 |
+
|
| 46 |
+
# %%
|
| 47 |
+
exp_name_base = 'deberta_v3_large_w10_ado1_wd0_bs2_lr1e5_5epo' # Changed ado4 to ado1
|
| 48 |
+
exp_name = f"{exp_name_base}_{'simplified_labels' if USE_SIMPLIFIED_LABELS else 'original_bio_labels'}"
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
backbone = 'microsoft/deberta-v3-large'
|
| 52 |
+
|
| 53 |
+
TRAINING_MAX_LENGTH = 4096
|
| 54 |
+
batch_size = 2
|
| 55 |
+
lr = 1e-5
|
| 56 |
+
ds_rate = 0.3
|
| 57 |
+
attention_probs_dropout_prob = 0.4
|
| 58 |
+
hidden_dropout_prob = 0.1
|
| 59 |
+
wd = 0.
|
| 60 |
+
pos_weight = 10
|
| 61 |
+
n_epochs = 5
|
| 62 |
+
|
| 63 |
+
use_amp = True
|
| 64 |
+
|
| 65 |
+
# %% [markdown]
|
| 66 |
+
# # Data
|
| 67 |
+
|
| 68 |
+
# %%
|
| 69 |
+
from sklearn.model_selection import train_test_split
|
| 70 |
+
|
| 71 |
+
data = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/train.json'))
|
| 72 |
+
for sample in data:
|
| 73 |
+
if len(set(sample['labels'])) > 1:
|
| 74 |
+
sample['is_pos'] = 1
|
| 75 |
+
else:
|
| 76 |
+
sample['is_pos'] = 0
|
| 77 |
+
print("original datapoints: ", len(data))
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
moredata = json.load(open('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/pii-data-detection-3rd-place-solution/datasets/moredata_dataset_fixed.json'))
|
| 81 |
+
print("moredata datapoints: ", len(moredata))
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
# %%
|
| 85 |
+
from sklearn.model_selection import GroupKFold
|
| 86 |
+
import random
|
| 87 |
+
random.seed(0)
|
| 88 |
+
kf = GroupKFold(n_splits=5)
|
| 89 |
+
folds = []
|
| 90 |
+
for i, (_, valid_index) in enumerate(kf.split(np.array(data), groups=[x['document'] for x in data])):
|
| 91 |
+
folds.append([data[i]['document'] for i in valid_index])
|
| 92 |
+
|
| 93 |
+
# %%
|
| 94 |
+
# Define simplified labels
|
| 95 |
+
pii_types = [
|
| 96 |
+
'EMAIL', 'ID_NUM', 'NAME_STUDENT', 'PHONE_NUM',
|
| 97 |
+
'STREET_ADDRESS', 'URL_PERSONAL', 'USERNAME'
|
| 98 |
+
]
|
| 99 |
+
all_labels_simplified = sorted(pii_types + ['O'])
|
| 100 |
+
label2id_simplified = {l: i for i, l in enumerate(all_labels_simplified)}
|
| 101 |
+
id2label_simplified = {v: k for k, v in label2id_simplified.items()}
|
| 102 |
+
|
| 103 |
+
# Original target BIO labels
|
| 104 |
+
original_target_bio = sorted([
|
| 105 |
+
'B-EMAIL', 'B-ID_NUM', 'B-NAME_STUDENT', 'B-PHONE_NUM',
|
| 106 |
+
'B-STREET_ADDRESS', 'B-URL_PERSONAL', 'B-USERNAME', 'I-ID_NUM',
|
| 107 |
+
'I-NAME_STUDENT', 'I-PHONE_NUM', 'I-STREET_ADDRESS', 'I-URL_PERSONAL',
|
| 108 |
+
'O'
|
| 109 |
+
])
|
| 110 |
+
|
| 111 |
+
if 'O' not in original_target_bio:
|
| 112 |
+
original_target_bio_with_o = sorted(list(set(original_target_bio + ['O'])))
|
| 113 |
+
else:
|
| 114 |
+
original_target_bio_with_o = sorted(list(set(original_target_bio)))
|
| 115 |
+
|
| 116 |
+
label2id_original_bio = {l: i for i, l in enumerate(original_target_bio_with_o)}
|
| 117 |
+
id2label_original_bio = {v: k for k, v in label2id_original_bio.items()}
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
if USE_SIMPLIFIED_LABELS:
|
| 121 |
+
all_labels = all_labels_simplified
|
| 122 |
+
label2id = label2id_simplified
|
| 123 |
+
id2label = id2label_simplified
|
| 124 |
+
print(f"Using SIMPLIFIED labels ({len(all_labels)}): {id2label}")
|
| 125 |
+
else:
|
| 126 |
+
all_labels = original_target_bio_with_o
|
| 127 |
+
label2id = label2id_original_bio
|
| 128 |
+
id2label = id2label_original_bio
|
| 129 |
+
print(f"Using ORIGINAL BIO labels ({len(all_labels)}): {id2label}")
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
# %% [markdown]
|
| 133 |
+
# # Dataset
|
| 134 |
+
|
| 135 |
+
# %%
|
| 136 |
+
def tokenize(example, tokenizer, current_label2id, max_length, use_simplified): # Pass current_label2id and use_simplified flag
|
| 137 |
+
|
| 138 |
+
# rebuild text from tokens
|
| 139 |
+
text = []
|
| 140 |
+
char_labels = []
|
| 141 |
+
|
| 142 |
+
for t, l, ws in zip(
|
| 143 |
+
example["tokens"], example["provided_labels"], example["trailing_whitespace"]
|
| 144 |
+
):
|
| 145 |
+
text.append(t)
|
| 146 |
+
current_label = l
|
| 147 |
+
if use_simplified and l != "O":
|
| 148 |
+
current_label = l[2:] # Strip "B-" or "I-"
|
| 149 |
+
|
| 150 |
+
# Ensure current_label is in the target label set (either simplified or original BIO)
|
| 151 |
+
# If a label from data isn't in the defined 'all_labels', map it to 'O'.
|
| 152 |
+
if current_label not in current_label2id:
|
| 153 |
+
current_label = "O"
|
| 154 |
+
|
| 155 |
+
char_labels.extend([current_label] * len(t))
|
| 156 |
+
|
| 157 |
+
if ws:
|
| 158 |
+
text.append(" ")
|
| 159 |
+
char_labels.append("O") # Whitespace is always "O"
|
| 160 |
+
|
| 161 |
+
tokenized = tokenizer("".join(text), return_offsets_mapping=True, truncation=False)
|
| 162 |
+
|
| 163 |
+
if len(tokenized['input_ids']) > max_length:
|
| 164 |
+
# "first N/2 + last N/2" truncation strategy
|
| 165 |
+
first_half_len = max_length // 2
|
| 166 |
+
second_half_len = max_length - first_half_len
|
| 167 |
+
|
| 168 |
+
keys_to_truncate = ['input_ids', 'attention_mask', 'offset_mapping']
|
| 169 |
+
for key in keys_to_truncate:
|
| 170 |
+
if key in tokenized:
|
| 171 |
+
original_list = tokenized[key]
|
| 172 |
+
# takes the first 'first_half_len' elements and the last 'second_half_len' elements
|
| 173 |
+
tokenized[key] = original_list[:first_half_len] + original_list[-second_half_len:]
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
char_labels_arr = np.array(char_labels)
|
| 177 |
+
|
| 178 |
+
text_str = "".join(text)
|
| 179 |
+
token_labels = []
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
current_offset_mapping = tokenized["offset_mapping"]
|
| 183 |
+
|
| 184 |
+
for start_idx, end_idx in current_offset_mapping:
|
| 185 |
+
# Handle special tokens like [CLS], [SEP] which have (0,0) offset_mapping
|
| 186 |
+
|
| 187 |
+
if start_idx == end_idx:
|
| 188 |
+
token_labels.append(current_label2id["O"]) # Assign 'O' to special/empty tokens
|
| 189 |
+
continue
|
| 190 |
+
|
| 191 |
+
# Ensure indices are within the bounds of char_labels_arr and form a valid span
|
| 192 |
+
# end_idx is exclusive for slicing, so it can be == len(char_labels_arr)
|
| 193 |
+
if not (start_idx < end_idx and start_idx < len(char_labels_arr) and end_idx <= len(char_labels_arr)):
|
| 194 |
+
token_labels.append(current_label2id["O"]) # Assign 'O' if token is outside char_labels range or invalid span
|
| 195 |
+
continue
|
| 196 |
+
|
| 197 |
+
char_span_labels = char_labels_arr[start_idx:end_idx]
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
if not char_span_labels.size:
|
| 201 |
+
token_labels.append(current_label2id["O"])
|
| 202 |
+
continue
|
| 203 |
+
|
| 204 |
+
unique_labels, counts = np.unique(char_span_labels, return_counts=True)
|
| 205 |
+
|
| 206 |
+
pii_labels_present_in_token = []
|
| 207 |
+
pii_counts_present_in_token = []
|
| 208 |
+
|
| 209 |
+
for lbl, count in zip(unique_labels, counts): # lbl is already simplified or original based on earlier logic
|
| 210 |
+
if lbl != "O":
|
| 211 |
+
pii_labels_present_in_token.append(lbl)
|
| 212 |
+
pii_counts_present_in_token.append(count)
|
| 213 |
+
|
| 214 |
+
if pii_labels_present_in_token:
|
| 215 |
+
# If PII labels are present, choose the most frequent PII label
|
| 216 |
+
most_frequent_pii_idx = np.argmax(pii_counts_present_in_token)
|
| 217 |
+
most_frequent_pii_label = pii_labels_present_in_token[most_frequent_pii_idx]
|
| 218 |
+
token_labels.append(current_label2id[most_frequent_pii_label])
|
| 219 |
+
else:
|
| 220 |
+
# If only \'O\' is present or no PII labels found in the span
|
| 221 |
+
token_labels.append(current_label2id["O"])
|
| 222 |
+
|
| 223 |
+
length = len(tokenized["input_ids"]) # Use dictionary-style access
|
| 224 |
+
|
| 225 |
+
return {**tokenized, "labels": token_labels, "length": length}
|
| 226 |
+
|
| 227 |
+
# %%
|
| 228 |
+
tokenizer = AutoTokenizer.from_pretrained(backbone)
|
| 229 |
+
|
| 230 |
+
class PIIDataset(Dataset):
|
| 231 |
+
def __init__(self, tokenizer, data, current_label2id, max_length, use_simplified): # Pass current_label2id and use_simplified
|
| 232 |
+
self.tokenizer = tokenizer
|
| 233 |
+
self.data = copy.deepcopy(data)
|
| 234 |
+
self.current_label2id = current_label2id # Store the active label2id
|
| 235 |
+
self.max_length = max_length
|
| 236 |
+
self.use_simplified = use_simplified
|
| 237 |
+
|
| 238 |
+
for i in tqdm(range(len(self.data))):
|
| 239 |
+
if self.data[i].get('input_ids') is None:
|
| 240 |
+
self.data[i]['provided_labels'] = self.data[i]['labels']
|
| 241 |
+
self.data[i] = tokenize(
|
| 242 |
+
self.data[i],
|
| 243 |
+
self.tokenizer,
|
| 244 |
+
self.current_label2id, # Pass the active label2id
|
| 245 |
+
self.max_length,
|
| 246 |
+
self.use_simplified # Pass the flag
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
def __len__(self):
|
| 250 |
+
return len(self.data)
|
| 251 |
+
|
| 252 |
+
def __getitem__(self, idx):
|
| 253 |
+
return self.data[idx]
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def collate_fn(batch):
|
| 257 |
+
input_ids = [torch.tensor(x['input_ids']) for x in batch]
|
| 258 |
+
attention_masks = [torch.tensor(x['attention_mask']) for x in batch]
|
| 259 |
+
labels = [torch.tensor(x['labels']) for x in batch]
|
| 260 |
+
|
| 261 |
+
input_ids_padded = pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0)
|
| 262 |
+
attention_masks_padded = pad_sequence(attention_masks, batch_first=True, padding_value=0)
|
| 263 |
+
labels_padded = pad_sequence(labels, batch_first=True, padding_value=-100)
|
| 264 |
+
|
| 265 |
+
return {
|
| 266 |
+
'input_ids': input_ids_padded,
|
| 267 |
+
'attention_mask': attention_masks_padded,
|
| 268 |
+
'labels': labels_padded,
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
# Test with simplified labels
|
| 272 |
+
|
| 273 |
+
ds = PIIDataset(tokenizer, data[:20], label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
|
| 274 |
+
print(f"Example tokenized input IDs length: {len(ds[9]['input_ids'])}")
|
| 275 |
+
print(f"Example tokenized labels: {ds[9]['labels']}")
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
# %% [markdown]
|
| 279 |
+
# # Model
|
| 280 |
+
|
| 281 |
+
# %%
|
| 282 |
+
model = AutoModelForTokenClassification.from_pretrained(
|
| 283 |
+
backbone,
|
| 284 |
+
attention_probs_dropout_prob=attention_probs_dropout_prob,
|
| 285 |
+
hidden_dropout_prob=hidden_dropout_prob,
|
| 286 |
+
num_labels=len(all_labels), # Use length of active labels
|
| 287 |
+
id2label=id2label, # Use active id2label
|
| 288 |
+
label2id=label2id, # Use active label2id
|
| 289 |
+
ignore_mismatched_sizes=True
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
# %% [markdown]
|
| 293 |
+
# # Loss
|
| 294 |
+
|
| 295 |
+
# %%
|
| 296 |
+
|
| 297 |
+
weights = torch.ones(len(all_labels)) * pos_weight
|
| 298 |
+
if 'O' in label2id:
|
| 299 |
+
weights[label2id['O']] = 1
|
| 300 |
+
else:
|
| 301 |
+
print(f"Warning: 'O' label not found in active label2id for weight assignment: {label2id}. Assuming last index for 'O'.")
|
| 302 |
+
weights[-1] = 1
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
CE = nn.CrossEntropyLoss(weight=weights.to(device), ignore_index=-100)
|
| 306 |
+
def criterion(logits, labels):
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
return CE(logits.view(-1, len(all_labels)), labels.view(-1))
|
| 310 |
+
|
| 311 |
+
# %% [markdown]
|
| 312 |
+
# # Metric
|
| 313 |
+
|
| 314 |
+
# %%
|
| 315 |
+
def compute_metrics(p, current_all_labels_list, th=0.5):
|
| 316 |
+
predictions, labels = p
|
| 317 |
+
|
| 318 |
+
o_label_str = "O"
|
| 319 |
+
o_label_idx = -1
|
| 320 |
+
|
| 321 |
+
for i, lbl_str in enumerate(current_all_labels_list):
|
| 322 |
+
if lbl_str == o_label_str:
|
| 323 |
+
o_label_idx = i
|
| 324 |
+
break
|
| 325 |
+
if o_label_idx == -1:
|
| 326 |
+
print(f"Warning: Could not find 'O' label in {current_all_labels_list} for metric thresholding. Using last index.")
|
| 327 |
+
o_label_idx = len(current_all_labels_list) - 1
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
true_predictions = []
|
| 331 |
+
true_labels = []
|
| 332 |
+
for tmp, batch_l in zip(predictions, labels):
|
| 333 |
+
batch_p_probs = torch.softmax(torch.from_numpy(tmp), dim=-1).numpy()
|
| 334 |
+
|
| 335 |
+
o_column_probs_slice = batch_p_probs[:, :, o_label_idx]
|
| 336 |
+
|
| 337 |
+
condition_mask = o_column_probs_slice < th
|
| 338 |
+
|
| 339 |
+
o_column_probs_slice[condition_mask] = -9999
|
| 340 |
+
|
| 341 |
+
batch_p_final_ids = np.argmax(batch_p_probs, axis=-1)
|
| 342 |
+
|
| 343 |
+
true_predictions.extend([current_all_labels_list[p_id] for (p_id, l_id) in zip(batch_p_final_ids.reshape(-1), batch_l.reshape(-1)) if l_id != -100])
|
| 344 |
+
true_labels.extend([current_all_labels_list[l_id] for l_id in batch_l.reshape(-1) if l_id != -100])
|
| 345 |
+
|
| 346 |
+
recall = recall_score([true_labels], [true_predictions], average='micro') # Use micro for overall
|
| 347 |
+
precision = precision_score([true_labels], [true_predictions], average='micro')
|
| 348 |
+
|
| 349 |
+
beta = 5
|
| 350 |
+
f5_score = (1 + beta**2) * precision * recall / ((beta**2 * precision) + recall + 1e-100)
|
| 351 |
+
if precision + recall == 0: # Handle case where P and R are both zero
|
| 352 |
+
f5_score = 0.0
|
| 353 |
+
|
| 354 |
+
results = {
|
| 355 |
+
'recall': recall,
|
| 356 |
+
'precision': precision,
|
| 357 |
+
'f1': f5_score
|
| 358 |
+
}
|
| 359 |
+
return results
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
# %% [markdown]
|
| 363 |
+
# # Train & Valid
|
| 364 |
+
|
| 365 |
+
# %%
|
| 366 |
+
def train_one_epoch(model, loader, optimizer, scheduler, scaler=None):
|
| 367 |
+
model.train()
|
| 368 |
+
running_loss = []
|
| 369 |
+
bar = tqdm(loader, total=len(loader))
|
| 370 |
+
for batch in bar:
|
| 371 |
+
optimizer.zero_grad()
|
| 372 |
+
|
| 373 |
+
input_ids = batch['input_ids'].to(device)
|
| 374 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 375 |
+
labels = batch['labels'].to(device)
|
| 376 |
+
|
| 377 |
+
if scaler is not None:
|
| 378 |
+
with torch.cuda.amp.autocast():
|
| 379 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 380 |
+
loss = criterion(outputs['logits'], labels)
|
| 381 |
+
scaler.scale(loss).backward()
|
| 382 |
+
scaler.step(optimizer)
|
| 383 |
+
scaler.update()
|
| 384 |
+
else:
|
| 385 |
+
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
|
| 386 |
+
loss = criterion(outputs['logits'], labels)
|
| 387 |
+
loss.backward()
|
| 388 |
+
optimizer.step()
|
| 389 |
+
|
| 390 |
+
scheduler.step()
|
| 391 |
+
running_loss.append(loss.item())
|
| 392 |
+
bar.set_description(f'train loss: {np.mean(running_loss[-20:]):.4f}')
|
| 393 |
+
|
| 394 |
+
return np.mean(running_loss)
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def valid_one_epoch(model, loader):
|
| 398 |
+
model.eval()
|
| 399 |
+
running_loss = []
|
| 400 |
+
bar = tqdm(loader, total=len(loader))
|
| 401 |
+
predictions_logits = []
|
| 402 |
+
true_labels_list = []
|
| 403 |
+
|
| 404 |
+
with torch.no_grad():
|
| 405 |
+
for batch in bar:
|
| 406 |
+
input_ids = batch['input_ids'].to(device)
|
| 407 |
+
attention_mask = batch['attention_mask'].to(device)
|
| 408 |
+
labels_batch = batch['labels'].to(device)
|
| 409 |
+
|
| 410 |
+
outputs = model(input_ids, attention_mask=attention_mask)
|
| 411 |
+
loss = criterion(outputs.logits, labels_batch)
|
| 412 |
+
running_loss.append(loss.item())
|
| 413 |
+
|
| 414 |
+
predictions_logits.append(outputs.logits.cpu().numpy())
|
| 415 |
+
true_labels_list.append(labels_batch.cpu().numpy())
|
| 416 |
+
|
| 417 |
+
bar.set_description(f'valid loss: {np.mean(running_loss[-20:]):.4f}')
|
| 418 |
+
|
| 419 |
+
# Pass the active all_labels (list of strings) to compute_metrics
|
| 420 |
+
metrics_results = compute_metrics((predictions_logits, true_labels_list), all_labels)
|
| 421 |
+
return np.mean(running_loss), metrics_results
|
| 422 |
+
|
| 423 |
+
# %% [markdown]
|
| 424 |
+
# # Scheduler
|
| 425 |
+
|
| 426 |
+
# %%
|
| 427 |
+
rcParams['figure.figsize'] = 20, 2
|
| 428 |
+
|
| 429 |
+
def get_scheduler(optimizer, num_training_steps, warmup_steps_ratio=0.1):
|
| 430 |
+
return get_cosine_schedule_with_warmup(
|
| 431 |
+
optimizer,
|
| 432 |
+
int(num_training_steps * warmup_steps_ratio),
|
| 433 |
+
num_training_steps
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
opt = AdamW(model.parameters(), lr=lr)
|
| 437 |
+
sch = get_scheduler(opt, 100, 0.1)
|
| 438 |
+
|
| 439 |
+
lrs = []
|
| 440 |
+
for epoch in range(1, 100+1):
|
| 441 |
+
sch.step()
|
| 442 |
+
lrs.append(opt.param_groups[0]["lr"])
|
| 443 |
+
|
| 444 |
+
plt.plot(range(len(lrs)), lrs)
|
| 445 |
+
|
| 446 |
+
# %% [markdown]
|
| 447 |
+
# # Run
|
| 448 |
+
|
| 449 |
+
# %%
|
| 450 |
+
def run(fold,model_to_train):
|
| 451 |
+
|
| 452 |
+
log_file = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model/logs', f'{exp_name}.txt')
|
| 453 |
+
|
| 454 |
+
model_save_dir_best = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_best_hf')
|
| 455 |
+
model_save_dir_last = os.path.join('/export/home/acs/stud/t/tudor.farcasanu/SSL_Assignment/single_model/model', f'{exp_name}_fold{fold}_last_hf')
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
os.makedirs(model_save_dir_best, exist_ok=True)
|
| 459 |
+
os.makedirs(model_save_dir_last, exist_ok=True)
|
| 460 |
+
|
| 461 |
+
optimizer_etc_file_last = os.path.join(model_save_dir_last, 'optimizer_etc_states.pth')
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
valid_doc_ids = folds[fold]
|
| 465 |
+
train_data = [x for x in data if (x['document'] not in valid_doc_ids) and ((x['is_pos'] == 1) or (x['is_pos'] == 0 and random.random() < ds_rate))] + moredata
|
| 466 |
+
|
| 467 |
+
valid_data = [x for x in data if x['document'] in valid_doc_ids]
|
| 468 |
+
|
| 469 |
+
dataset_train = PIIDataset(tokenizer, train_data, label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
|
| 470 |
+
dataset_valid = PIIDataset(tokenizer, valid_data, label2id, TRAINING_MAX_LENGTH, USE_SIMPLIFIED_LABELS)
|
| 471 |
+
print(len(dataset_train), len(dataset_valid))
|
| 472 |
+
|
| 473 |
+
loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, num_workers=2, collate_fn=collate_fn)
|
| 474 |
+
loader_valid = DataLoader(dataset_valid, batch_size=batch_size, shuffle=False, num_workers=2, collate_fn=collate_fn)
|
| 475 |
+
|
| 476 |
+
current_model = model_to_train.to(device)
|
| 477 |
+
|
| 478 |
+
optimizer = AdamW(current_model.parameters(), lr=lr, weight_decay=wd)
|
| 479 |
+
scaler = torch.cuda.amp.GradScaler() if use_amp else None
|
| 480 |
+
from_epoch = 0
|
| 481 |
+
metric_best = 0
|
| 482 |
+
|
| 483 |
+
scheduler = get_scheduler(optimizer, n_epochs*len(loader_train), 0.1)
|
| 484 |
+
|
| 485 |
+
for epoch in range(1, n_epochs+1):
|
| 486 |
+
print(time.ctime(), 'Epoch:', epoch)
|
| 487 |
+
|
| 488 |
+
train_loss = train_one_epoch(current_model, loader_train, optimizer, scheduler, scaler)
|
| 489 |
+
valid_loss, result = valid_one_epoch(current_model, loader_valid)
|
| 490 |
+
metric = result["f1"] # F5 score on simplified labels
|
| 491 |
+
|
| 492 |
+
content = time.ctime() + ' ' + \
|
| 493 |
+
f'Fold {fold}, Epoch {epoch}, lr: {optimizer.param_groups[0]["lr"]:.7f}, ' + \
|
| 494 |
+
f'train loss: {train_loss:.5f}, valid loss: {valid_loss:.5f}, ' + \
|
| 495 |
+
f'recall: {result["recall"]:.4f} precision: {result["precision"]:.4f} metric (F5 on simplified): {metric:.4f}.'
|
| 496 |
+
print(content)
|
| 497 |
+
with open(log_file, 'a') as appender:
|
| 498 |
+
appender.write(content + '\n')
|
| 499 |
+
|
| 500 |
+
if metric > metric_best:
|
| 501 |
+
print(f'metric_best ({metric_best:.6f} --> {metric:.6f}). Saving model to {model_save_dir_best} ...')
|
| 502 |
+
if not DEBUG:
|
| 503 |
+
current_model.save_pretrained(model_save_dir_best, safe_serialization=True)
|
| 504 |
+
tokenizer.save_pretrained(model_save_dir_best)
|
| 505 |
+
metric_best = metric
|
| 506 |
+
|
| 507 |
+
if not DEBUG:
|
| 508 |
+
current_model.save_pretrained(model_save_dir_last, safe_serialization=True)
|
| 509 |
+
tokenizer.save_pretrained(model_save_dir_last)
|
| 510 |
+
torch.save(
|
| 511 |
+
{
|
| 512 |
+
'epoch': epoch,
|
| 513 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 514 |
+
'scheduler_state_dict': scheduler.state_dict(),
|
| 515 |
+
'scaler_state_dict': scaler.state_dict() if scaler else None,
|
| 516 |
+
'metric_best': metric_best,
|
| 517 |
+
},
|
| 518 |
+
optimizer_etc_file_last
|
| 519 |
+
)
|
| 520 |
+
|
| 521 |
+
del current_model
|
| 522 |
+
torch.cuda.empty_cache()
|
| 523 |
+
gc.collect()
|
| 524 |
+
|
| 525 |
+
# %%
|
| 526 |
+
run(0, model)
|
| 527 |
+
|
| 528 |
+
# %%
|
| 529 |
+
|
| 530 |
+
|
| 531 |
+
|
config.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_name_or_path": "microsoft/deberta-v3-large",
|
| 3 |
+
"attention_probs_dropout_prob": 0.1,
|
| 4 |
+
"hidden_act": "gelu",
|
| 5 |
+
"hidden_dropout_prob": 0.1,
|
| 6 |
+
"hidden_size": 1024,
|
| 7 |
+
"id2label": {
|
| 8 |
+
"0": "LABEL_0",
|
| 9 |
+
"1": "LABEL_1",
|
| 10 |
+
"2": "LABEL_2",
|
| 11 |
+
"3": "LABEL_3",
|
| 12 |
+
"4": "LABEL_4",
|
| 13 |
+
"5": "LABEL_5",
|
| 14 |
+
"6": "LABEL_6",
|
| 15 |
+
"7": "LABEL_7",
|
| 16 |
+
"8": "LABEL_8",
|
| 17 |
+
"9": "LABEL_9",
|
| 18 |
+
"10": "LABEL_10",
|
| 19 |
+
"11": "LABEL_11",
|
| 20 |
+
"12": "LABEL_12"
|
| 21 |
+
},
|
| 22 |
+
"initializer_range": 0.02,
|
| 23 |
+
"intermediate_size": 4096,
|
| 24 |
+
"label2id": {
|
| 25 |
+
"LABEL_0": 0,
|
| 26 |
+
"LABEL_1": 1,
|
| 27 |
+
"LABEL_10": 10,
|
| 28 |
+
"LABEL_11": 11,
|
| 29 |
+
"LABEL_12": 12,
|
| 30 |
+
"LABEL_2": 2,
|
| 31 |
+
"LABEL_3": 3,
|
| 32 |
+
"LABEL_4": 4,
|
| 33 |
+
"LABEL_5": 5,
|
| 34 |
+
"LABEL_6": 6,
|
| 35 |
+
"LABEL_7": 7,
|
| 36 |
+
"LABEL_8": 8,
|
| 37 |
+
"LABEL_9": 9
|
| 38 |
+
},
|
| 39 |
+
"layer_norm_eps": 1e-07,
|
| 40 |
+
"max_position_embeddings": 512,
|
| 41 |
+
"max_relative_positions": -1,
|
| 42 |
+
"model_type": "deberta-v2",
|
| 43 |
+
"norm_rel_ebd": "layer_norm",
|
| 44 |
+
"num_attention_heads": 16,
|
| 45 |
+
"num_hidden_layers": 24,
|
| 46 |
+
"pad_token_id": 0,
|
| 47 |
+
"pooler_dropout": 0,
|
| 48 |
+
"pooler_hidden_act": "gelu",
|
| 49 |
+
"pooler_hidden_size": 1024,
|
| 50 |
+
"pos_att_type": [
|
| 51 |
+
"p2c",
|
| 52 |
+
"c2p"
|
| 53 |
+
],
|
| 54 |
+
"position_biased_input": false,
|
| 55 |
+
"position_buckets": 256,
|
| 56 |
+
"relative_attention": true,
|
| 57 |
+
"share_att_key": true,
|
| 58 |
+
"target_modules": null,
|
| 59 |
+
"transformers_version": "4.38.2",
|
| 60 |
+
"type_vocab_size": 0,
|
| 61 |
+
"vocab_size": 128100
|
| 62 |
+
}
|
deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_best.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:49842d0c1521ba11d9d9d0c86a4ecb5c1b519f72b7a15a6a173be165b72a706f
|
| 3 |
+
size 1736286050
|
deberta_v3_large_w10_ado4_wd0_bs2_lr1e5_5epo_fold0_last.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6c0b7f029d3421c9078ef5f341ce237ff8bc3395beeb625c484343a69204ec13
|
| 3 |
+
size 5208909490
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token": "[CLS]",
|
| 3 |
+
"cls_token": "[CLS]",
|
| 4 |
+
"eos_token": "[SEP]",
|
| 5 |
+
"mask_token": "[MASK]",
|
| 6 |
+
"pad_token": "[PAD]",
|
| 7 |
+
"sep_token": "[SEP]",
|
| 8 |
+
"unk_token": {
|
| 9 |
+
"content": "[UNK]",
|
| 10 |
+
"lstrip": false,
|
| 11 |
+
"normalized": true,
|
| 12 |
+
"rstrip": false,
|
| 13 |
+
"single_word": false
|
| 14 |
+
}
|
| 15 |
+
}
|
spm.model
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c679fbf93643d19aab7ee10c0b99e460bdbc02fedf34b92b05af343b4af586fd
|
| 3 |
+
size 2464616
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"added_tokens_decoder": {
|
| 3 |
+
"0": {
|
| 4 |
+
"content": "[PAD]",
|
| 5 |
+
"lstrip": false,
|
| 6 |
+
"normalized": false,
|
| 7 |
+
"rstrip": false,
|
| 8 |
+
"single_word": false,
|
| 9 |
+
"special": true
|
| 10 |
+
},
|
| 11 |
+
"1": {
|
| 12 |
+
"content": "[CLS]",
|
| 13 |
+
"lstrip": false,
|
| 14 |
+
"normalized": false,
|
| 15 |
+
"rstrip": false,
|
| 16 |
+
"single_word": false,
|
| 17 |
+
"special": true
|
| 18 |
+
},
|
| 19 |
+
"2": {
|
| 20 |
+
"content": "[SEP]",
|
| 21 |
+
"lstrip": false,
|
| 22 |
+
"normalized": false,
|
| 23 |
+
"rstrip": false,
|
| 24 |
+
"single_word": false,
|
| 25 |
+
"special": true
|
| 26 |
+
},
|
| 27 |
+
"3": {
|
| 28 |
+
"content": "[UNK]",
|
| 29 |
+
"lstrip": false,
|
| 30 |
+
"normalized": true,
|
| 31 |
+
"rstrip": false,
|
| 32 |
+
"single_word": false,
|
| 33 |
+
"special": true
|
| 34 |
+
},
|
| 35 |
+
"128000": {
|
| 36 |
+
"content": "[MASK]",
|
| 37 |
+
"lstrip": false,
|
| 38 |
+
"normalized": false,
|
| 39 |
+
"rstrip": false,
|
| 40 |
+
"single_word": false,
|
| 41 |
+
"special": true
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"bos_token": "[CLS]",
|
| 45 |
+
"clean_up_tokenization_spaces": true,
|
| 46 |
+
"cls_token": "[CLS]",
|
| 47 |
+
"do_lower_case": false,
|
| 48 |
+
"eos_token": "[SEP]",
|
| 49 |
+
"mask_token": "[MASK]",
|
| 50 |
+
"model_max_length": 1000000000000000019884624838656,
|
| 51 |
+
"pad_token": "[PAD]",
|
| 52 |
+
"sep_token": "[SEP]",
|
| 53 |
+
"sp_model_kwargs": {},
|
| 54 |
+
"split_by_punct": false,
|
| 55 |
+
"tokenizer_class": "DebertaV2Tokenizer",
|
| 56 |
+
"unk_token": "[UNK]",
|
| 57 |
+
"vocab_type": "spm"
|
| 58 |
+
}
|