training ntbs
Browse files- src/notebooks/train_bert.ipynb +211 -0
- src/notebooks/train_deberta.ipynb +211 -0
- src/notebooks/train_distilbert.ipynb +211 -0
- src/notebooks/train_models.ipynb +280 -0
src/notebooks/train_bert.ipynb
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "m0",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# Train & Evaluate BERT\n",
|
| 9 |
+
"\n",
|
| 10 |
+
"Sentence-pair boundary classification on combined PubMed + Wikipedia + Gutenberg data."
|
| 11 |
+
]
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"cell_type": "code",
|
| 15 |
+
"id": "c0",
|
| 16 |
+
"execution_count": null,
|
| 17 |
+
"metadata": {},
|
| 18 |
+
"outputs": [],
|
| 19 |
+
"source": [
|
| 20 |
+
"import os, sys\n",
|
| 21 |
+
"os.chdir(os.path.join(os.path.dirname(os.getcwd()), \"..\"))\n",
|
| 22 |
+
"print(\"Working dir:\", os.getcwd())\n",
|
| 23 |
+
"import wandb\n",
|
| 24 |
+
"from dotenv import load_dotenv\n",
|
| 25 |
+
"\n",
|
| 26 |
+
"\n",
|
| 27 |
+
"load_dotenv(\"env.txt\")\n",
|
| 28 |
+
"wandb.login(key=os.getenv(\"WB_TOKEN\"))"
|
| 29 |
+
]
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"cell_type": "code",
|
| 33 |
+
"id": "c1",
|
| 34 |
+
"execution_count": null,
|
| 35 |
+
"metadata": {},
|
| 36 |
+
"outputs": [],
|
| 37 |
+
"source": [
|
| 38 |
+
"import json\n",
|
| 39 |
+
"import logging\n",
|
| 40 |
+
"from pathlib import Path\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"import numpy as np\n",
|
| 43 |
+
"import torch\n",
|
| 44 |
+
"import torch.nn as nn\n",
|
| 45 |
+
"from sklearn.metrics import classification_report, confusion_matrix, f1_score, matthews_corrcoef\n",
|
| 46 |
+
"from transformers import AutoModelForSequenceClassification, AutoTokenizer, EarlyStoppingCallback, Trainer, TrainingArguments\n",
|
| 47 |
+
"\n",
|
| 48 |
+
"from src.datasets.combined_pairs_dataset import CombinedPairsDataset, CombinedPairsConfig, NUM_LABELS, ID2LABEL, LABEL2ID\n",
|
| 49 |
+
"from src.models.bert import load_bert, load_bert_tokenizer\n",
|
| 50 |
+
"from src.models.train import WeightedTrainer, compute_metrics\n",
|
| 51 |
+
"from src.schemas.training_args import BertTrainingArgs\n",
|
| 52 |
+
"\n",
|
| 53 |
+
"logging.basicConfig(level=logging.INFO, format=\"%(asctime)s %(levelname)s %(message)s\")\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
|
| 56 |
+
"print(f\"Device: {device}\")"
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"cell_type": "markdown",
|
| 61 |
+
"id": "m1",
|
| 62 |
+
"metadata": {},
|
| 63 |
+
"source": [
|
| 64 |
+
"## 1. Configuration & Data"
|
| 65 |
+
]
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"cell_type": "code",
|
| 69 |
+
"id": "c2",
|
| 70 |
+
"execution_count": null,
|
| 71 |
+
"metadata": {},
|
| 72 |
+
"outputs": [],
|
| 73 |
+
"source": [
|
| 74 |
+
"args = BertTrainingArgs()\n",
|
| 75 |
+
"print(f\"epochs: {args.epochs}, lr: {args.lr}, batch_size: {args.batch_size}, patience: {args.patience}\")\n",
|
| 76 |
+
"\n",
|
| 77 |
+
"os.environ[\"WANDB_PROJECT\"] = \"bottlecap\"\n",
|
| 78 |
+
"os.environ[\"WANDB_RUN_NAME\"] = \"bert\""
|
| 79 |
+
]
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"cell_type": "code",
|
| 83 |
+
"id": "c3",
|
| 84 |
+
"execution_count": null,
|
| 85 |
+
"metadata": {},
|
| 86 |
+
"outputs": [],
|
| 87 |
+
"source": [
|
| 88 |
+
"cfg = CombinedPairsConfig(data_root=\"data\", gutenberg_train_cap=args.gutenberg_cap, seed=args.seed, max_length=args.max_length)\n",
|
| 89 |
+
"builder = CombinedPairsDataset(cfg)\n",
|
| 90 |
+
"raw_splits = builder.build_splits()\n",
|
| 91 |
+
"class_weights = builder.compute_class_weights(raw_splits[\"train\"])\n",
|
| 92 |
+
"print(f\"Class weights: {class_weights.tolist()}\")"
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"cell_type": "markdown",
|
| 97 |
+
"id": "m2",
|
| 98 |
+
"metadata": {},
|
| 99 |
+
"source": [
|
| 100 |
+
"## 2. Train"
|
| 101 |
+
]
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"cell_type": "code",
|
| 105 |
+
"id": "c4",
|
| 106 |
+
"execution_count": null,
|
| 107 |
+
"metadata": {},
|
| 108 |
+
"outputs": [],
|
| 109 |
+
"source": [
|
| 110 |
+
"model = load_bert()\n",
|
| 111 |
+
"tokenizer = load_bert_tokenizer()\n",
|
| 112 |
+
"dd = builder.build_hf_dataset_dict(tokenizer, raw_splits=raw_splits)\n",
|
| 113 |
+
"\n",
|
| 114 |
+
"print(f\"Params: {sum(p.numel() for p in model.parameters()):,}\"\n",
|
| 115 |
+
" f\" Train: {len(dd['train']):,} Val: {len(dd['val']):,} Test: {len(dd['test']):,}\")"
|
| 116 |
+
]
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"cell_type": "code",
|
| 120 |
+
"id": "c5",
|
| 121 |
+
"execution_count": null,
|
| 122 |
+
"metadata": {},
|
| 123 |
+
"outputs": [],
|
| 124 |
+
"source": [
|
| 125 |
+
"trainer = WeightedTrainer(\n",
|
| 126 |
+
" class_weights=class_weights,\n",
|
| 127 |
+
" model=model,\n",
|
| 128 |
+
" args=args.to_training_arguments(),\n",
|
| 129 |
+
" train_dataset=dd[\"train\"],\n",
|
| 130 |
+
" eval_dataset=dd[\"val\"],\n",
|
| 131 |
+
" compute_metrics=compute_metrics,\n",
|
| 132 |
+
" callbacks=[EarlyStoppingCallback(early_stopping_patience=args.patience)] if args.patience > 0 else [],\n",
|
| 133 |
+
")\n",
|
| 134 |
+
"\n",
|
| 135 |
+
"trainer.train()"
|
| 136 |
+
]
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"cell_type": "markdown",
|
| 140 |
+
"id": "m3",
|
| 141 |
+
"metadata": {},
|
| 142 |
+
"source": [
|
| 143 |
+
"## 3. Loss Curves"
|
| 144 |
+
]
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"cell_type": "code",
|
| 148 |
+
"id": "c6",
|
| 149 |
+
"execution_count": null,
|
| 150 |
+
"metadata": {},
|
| 151 |
+
"outputs": [],
|
| 152 |
+
"source": "import matplotlib.pyplot as plt\n\nplots_dir = Path(args.output_dir) / \"plots\"\nplots_dir.mkdir(parents=True, exist_ok=True)\n\nhistory = trainer.state.log_history\ntrain_steps = [e[\"step\"] for e in history if \"loss\" in e and \"eval_loss\" not in e]\ntrain_loss = [e[\"loss\"] for e in history if \"loss\" in e and \"eval_loss\" not in e]\neval_steps = [e[\"step\"] for e in history if \"eval_loss\" in e]\neval_loss = [e[\"eval_loss\"] for e in history if \"eval_loss\" in e]\neval_epochs = [e.get(\"epoch\", 0) for e in history if \"eval_loss\" in e]\neval_f1 = [e.get(\"eval_macro_f1\", 0) for e in history if \"eval_loss\" in e]\n\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 4))\nax1.plot(train_steps, train_loss, label=\"Train\", alpha=0.7)\nax1.plot(eval_steps, eval_loss, \"o-\", label=\"Val\")\nax1.set_xlabel(\"Step\"); ax1.set_ylabel(\"Loss\"); ax1.set_title(\"BERT — Loss\"); ax1.legend(); ax1.grid(True, alpha=0.3)\nax2.plot(eval_epochs, eval_f1, \"s-\", label=\"Macro F1\")\nax2.set_xlabel(\"Epoch\"); ax2.set_ylabel(\"F1\"); ax2.set_title(\"BERT — Macro F1\"); ax2.legend(); ax2.grid(True, alpha=0.3)\nplt.tight_layout()\nfig.savefig(plots_dir / \"loss_curves.png\", dpi=150)\nplt.show()\nprint(f\"Saved to {plots_dir / 'loss_curves.png'}\")"
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"cell_type": "markdown",
|
| 156 |
+
"id": "m4",
|
| 157 |
+
"metadata": {},
|
| 158 |
+
"source": [
|
| 159 |
+
"## 4. Save Best Model"
|
| 160 |
+
]
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"cell_type": "code",
|
| 164 |
+
"id": "c7",
|
| 165 |
+
"execution_count": null,
|
| 166 |
+
"metadata": {},
|
| 167 |
+
"outputs": [],
|
| 168 |
+
"source": [
|
| 169 |
+
"best_dir = Path(args.output_dir) / \"best\"\n",
|
| 170 |
+
"best_dir.mkdir(parents=True, exist_ok=True)\n",
|
| 171 |
+
"trainer.save_model(str(best_dir))\n",
|
| 172 |
+
"tokenizer.save_pretrained(str(best_dir))\n",
|
| 173 |
+
"torch.save(class_weights, best_dir / \"class_weights.pt\")\n",
|
| 174 |
+
"\n",
|
| 175 |
+
"train_config = {\"model_type\": \"bert\", \"pretrained\": model.config._name_or_path, \"epochs\": args.epochs, \"batch_size\": args.batch_size, \"lr\": args.lr, \"max_length\": args.max_length, \"class_weights\": class_weights.tolist(), \"num_labels\": NUM_LABELS, \"id2label\": ID2LABEL, \"label2id\": LABEL2ID}\n",
|
| 176 |
+
"with open(best_dir / \"train_config.json\", \"w\") as f:\n",
|
| 177 |
+
" json.dump(train_config, f, indent=2)\n",
|
| 178 |
+
"print(f\"Saved to {best_dir}\")"
|
| 179 |
+
]
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"cell_type": "markdown",
|
| 183 |
+
"id": "m5",
|
| 184 |
+
"metadata": {},
|
| 185 |
+
"source": [
|
| 186 |
+
"## 5. Evaluate"
|
| 187 |
+
]
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"cell_type": "code",
|
| 191 |
+
"id": "c8",
|
| 192 |
+
"execution_count": null,
|
| 193 |
+
"metadata": {},
|
| 194 |
+
"outputs": [],
|
| 195 |
+
"source": "import matplotlib.pyplot as plt\nfrom sklearn.metrics import ConfusionMatrixDisplay\n\ntarget_names = [ID2LABEL[i] for i in range(3)]\n\nfor split in [\"val\", \"test\"]:\n eval_model = AutoModelForSequenceClassification.from_pretrained(str(best_dir))\n eval_trainer = Trainer(model=eval_model, args=TrainingArguments(output_dir=\"/tmp/eval\", per_device_eval_batch_size=args.batch_size*2, report_to=\"none\"))\n preds_out = eval_trainer.predict(dd[split])\n preds = np.argmax(preds_out.predictions, axis=-1)\n labels = preds_out.label_ids\n\n print(f\"\\n{'='*60}\")\n print(f\" BERT — {split} ({len(dd[split]):,} samples)\")\n print(f\"{'='*60}\")\n print(f\" Macro F1: {f1_score(labels, preds, average='macro'):.4f}\")\n print(f\" MCC: {matthews_corrcoef(labels, preds):.4f}\\n\")\n print(classification_report(labels, preds, target_names=target_names, digits=4))\n\n cm = confusion_matrix(labels, preds, labels=[0, 1, 2])\n fig, ax = plt.subplots(figsize=(6, 5))\n ConfusionMatrixDisplay(cm, display_labels=target_names).plot(ax=ax, cmap=\"Blues\", values_format=\",\")\n ax.set_title(f\"BERT — {split}\")\n plt.tight_layout()\n fig.savefig(plots_dir / f\"confusion_matrix_{split}.png\", dpi=150)\n plt.show()\n print(f\"Saved to {plots_dir / f'confusion_matrix_{split}.png'}\")"
|
| 196 |
+
}
|
| 197 |
+
],
|
| 198 |
+
"metadata": {
|
| 199 |
+
"kernelspec": {
|
| 200 |
+
"display_name": "Python 3",
|
| 201 |
+
"language": "python",
|
| 202 |
+
"name": "python3"
|
| 203 |
+
},
|
| 204 |
+
"language_info": {
|
| 205 |
+
"name": "python",
|
| 206 |
+
"version": "3.11.0"
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
"nbformat": 4,
|
| 210 |
+
"nbformat_minor": 5
|
| 211 |
+
}
|
src/notebooks/train_deberta.ipynb
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "m0",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# Train & Evaluate DeBERTa\n",
|
| 9 |
+
"\n",
|
| 10 |
+
"Sentence-pair boundary classification on combined PubMed + Wikipedia + Gutenberg data."
|
| 11 |
+
]
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"cell_type": "code",
|
| 15 |
+
"id": "c0",
|
| 16 |
+
"execution_count": null,
|
| 17 |
+
"metadata": {},
|
| 18 |
+
"outputs": [],
|
| 19 |
+
"source": [
|
| 20 |
+
"import os, sys\n",
|
| 21 |
+
"os.chdir(os.path.join(os.path.dirname(os.getcwd()), \"..\"))\n",
|
| 22 |
+
"print(\"Working dir:\", os.getcwd())\n",
|
| 23 |
+
"import wandb\n",
|
| 24 |
+
"from dotenv import load_dotenv\n",
|
| 25 |
+
"\n",
|
| 26 |
+
"\n",
|
| 27 |
+
"load_dotenv(\"env.txt\")\n",
|
| 28 |
+
"wandb.login(key=os.getenv(\"WB_TOKEN\"))"
|
| 29 |
+
]
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"cell_type": "code",
|
| 33 |
+
"id": "c1",
|
| 34 |
+
"execution_count": null,
|
| 35 |
+
"metadata": {},
|
| 36 |
+
"outputs": [],
|
| 37 |
+
"source": [
|
| 38 |
+
"import json\n",
|
| 39 |
+
"import logging\n",
|
| 40 |
+
"from pathlib import Path\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"import numpy as np\n",
|
| 43 |
+
"import torch\n",
|
| 44 |
+
"import torch.nn as nn\n",
|
| 45 |
+
"from sklearn.metrics import classification_report, confusion_matrix, f1_score, matthews_corrcoef\n",
|
| 46 |
+
"from transformers import AutoModelForSequenceClassification, AutoTokenizer, EarlyStoppingCallback, Trainer, TrainingArguments\n",
|
| 47 |
+
"\n",
|
| 48 |
+
"from src.datasets.combined_pairs_dataset import CombinedPairsDataset, CombinedPairsConfig, NUM_LABELS, ID2LABEL, LABEL2ID\n",
|
| 49 |
+
"from src.models.deberta import load_deberta, load_deberta_tokenizer\n",
|
| 50 |
+
"from src.models.train import WeightedTrainer, compute_metrics\n",
|
| 51 |
+
"from src.schemas.training_args import DebertaTrainingArgs\n",
|
| 52 |
+
"\n",
|
| 53 |
+
"logging.basicConfig(level=logging.INFO, format=\"%(asctime)s %(levelname)s %(message)s\")\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
|
| 56 |
+
"print(f\"Device: {device}\")"
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"cell_type": "markdown",
|
| 61 |
+
"id": "m1",
|
| 62 |
+
"metadata": {},
|
| 63 |
+
"source": [
|
| 64 |
+
"## 1. Configuration & Data"
|
| 65 |
+
]
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"cell_type": "code",
|
| 69 |
+
"id": "c2",
|
| 70 |
+
"execution_count": null,
|
| 71 |
+
"metadata": {},
|
| 72 |
+
"outputs": [],
|
| 73 |
+
"source": [
|
| 74 |
+
"args = DebertaTrainingArgs()\n",
|
| 75 |
+
"print(f\"epochs: {args.epochs}, lr: {args.lr}, batch_size: {args.batch_size}, patience: {args.patience}\")\n",
|
| 76 |
+
"\n",
|
| 77 |
+
"os.environ[\"WANDB_PROJECT\"] = \"bottlecap\"\n",
|
| 78 |
+
"os.environ[\"WANDB_RUN_NAME\"] = \"deberta\""
|
| 79 |
+
]
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"cell_type": "code",
|
| 83 |
+
"id": "c3",
|
| 84 |
+
"execution_count": null,
|
| 85 |
+
"metadata": {},
|
| 86 |
+
"outputs": [],
|
| 87 |
+
"source": [
|
| 88 |
+
"cfg = CombinedPairsConfig(data_root=\"data\", gutenberg_train_cap=args.gutenberg_cap, seed=args.seed, max_length=args.max_length)\n",
|
| 89 |
+
"builder = CombinedPairsDataset(cfg)\n",
|
| 90 |
+
"raw_splits = builder.build_splits()\n",
|
| 91 |
+
"class_weights = builder.compute_class_weights(raw_splits[\"train\"])\n",
|
| 92 |
+
"print(f\"Class weights: {class_weights.tolist()}\")"
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"cell_type": "markdown",
|
| 97 |
+
"id": "m2",
|
| 98 |
+
"metadata": {},
|
| 99 |
+
"source": [
|
| 100 |
+
"## 2. Train"
|
| 101 |
+
]
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"cell_type": "code",
|
| 105 |
+
"id": "c4",
|
| 106 |
+
"execution_count": null,
|
| 107 |
+
"metadata": {},
|
| 108 |
+
"outputs": [],
|
| 109 |
+
"source": [
|
| 110 |
+
"model = load_deberta()\n",
|
| 111 |
+
"tokenizer = load_deberta_tokenizer()\n",
|
| 112 |
+
"dd = builder.build_hf_dataset_dict(tokenizer, raw_splits=raw_splits)\n",
|
| 113 |
+
"\n",
|
| 114 |
+
"print(f\"Params: {sum(p.numel() for p in model.parameters()):,}\"\n",
|
| 115 |
+
" f\" Train: {len(dd['train']):,} Val: {len(dd['val']):,} Test: {len(dd['test']):,}\")"
|
| 116 |
+
]
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"cell_type": "code",
|
| 120 |
+
"id": "c5",
|
| 121 |
+
"execution_count": null,
|
| 122 |
+
"metadata": {},
|
| 123 |
+
"outputs": [],
|
| 124 |
+
"source": [
|
| 125 |
+
"trainer = WeightedTrainer(\n",
|
| 126 |
+
" class_weights=class_weights,\n",
|
| 127 |
+
" model=model,\n",
|
| 128 |
+
" args=args.to_training_arguments(),\n",
|
| 129 |
+
" train_dataset=dd[\"train\"],\n",
|
| 130 |
+
" eval_dataset=dd[\"val\"],\n",
|
| 131 |
+
" compute_metrics=compute_metrics,\n",
|
| 132 |
+
" callbacks=[EarlyStoppingCallback(early_stopping_patience=args.patience)] if args.patience > 0 else [],\n",
|
| 133 |
+
")\n",
|
| 134 |
+
"\n",
|
| 135 |
+
"trainer.train()"
|
| 136 |
+
]
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"cell_type": "markdown",
|
| 140 |
+
"id": "m3",
|
| 141 |
+
"metadata": {},
|
| 142 |
+
"source": [
|
| 143 |
+
"## 3. Loss Curves"
|
| 144 |
+
]
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"cell_type": "code",
|
| 148 |
+
"id": "c6",
|
| 149 |
+
"execution_count": null,
|
| 150 |
+
"metadata": {},
|
| 151 |
+
"outputs": [],
|
| 152 |
+
"source": "import matplotlib.pyplot as plt\n\nplots_dir = Path(args.output_dir) / \"plots\"\nplots_dir.mkdir(parents=True, exist_ok=True)\n\nhistory = trainer.state.log_history\ntrain_steps = [e[\"step\"] for e in history if \"loss\" in e and \"eval_loss\" not in e]\ntrain_loss = [e[\"loss\"] for e in history if \"loss\" in e and \"eval_loss\" not in e]\neval_steps = [e[\"step\"] for e in history if \"eval_loss\" in e]\neval_loss = [e[\"eval_loss\"] for e in history if \"eval_loss\" in e]\neval_epochs = [e.get(\"epoch\", 0) for e in history if \"eval_loss\" in e]\neval_f1 = [e.get(\"eval_macro_f1\", 0) for e in history if \"eval_loss\" in e]\n\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 4))\nax1.plot(train_steps, train_loss, label=\"Train\", alpha=0.7)\nax1.plot(eval_steps, eval_loss, \"o-\", label=\"Val\")\nax1.set_xlabel(\"Step\"); ax1.set_ylabel(\"Loss\"); ax1.set_title(\"DeBERTa — Loss\"); ax1.legend(); ax1.grid(True, alpha=0.3)\nax2.plot(eval_epochs, eval_f1, \"s-\", label=\"Macro F1\")\nax2.set_xlabel(\"Epoch\"); ax2.set_ylabel(\"F1\"); ax2.set_title(\"DeBERTa — Macro F1\"); ax2.legend(); ax2.grid(True, alpha=0.3)\nplt.tight_layout()\nfig.savefig(plots_dir / \"loss_curves.png\", dpi=150)\nplt.show()\nprint(f\"Saved to {plots_dir / 'loss_curves.png'}\")"
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"cell_type": "markdown",
|
| 156 |
+
"id": "m4",
|
| 157 |
+
"metadata": {},
|
| 158 |
+
"source": [
|
| 159 |
+
"## 4. Save Best Model"
|
| 160 |
+
]
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"cell_type": "code",
|
| 164 |
+
"id": "c7",
|
| 165 |
+
"execution_count": null,
|
| 166 |
+
"metadata": {},
|
| 167 |
+
"outputs": [],
|
| 168 |
+
"source": [
|
| 169 |
+
"best_dir = Path(args.output_dir) / \"best\"\n",
|
| 170 |
+
"best_dir.mkdir(parents=True, exist_ok=True)\n",
|
| 171 |
+
"trainer.save_model(str(best_dir))\n",
|
| 172 |
+
"tokenizer.save_pretrained(str(best_dir))\n",
|
| 173 |
+
"torch.save(class_weights, best_dir / \"class_weights.pt\")\n",
|
| 174 |
+
"\n",
|
| 175 |
+
"train_config = {\"model_type\": \"deberta\", \"pretrained\": model.config._name_or_path, \"epochs\": args.epochs, \"batch_size\": args.batch_size, \"lr\": args.lr, \"max_length\": args.max_length, \"class_weights\": class_weights.tolist(), \"num_labels\": NUM_LABELS, \"id2label\": ID2LABEL, \"label2id\": LABEL2ID}\n",
|
| 176 |
+
"with open(best_dir / \"train_config.json\", \"w\") as f:\n",
|
| 177 |
+
" json.dump(train_config, f, indent=2)\n",
|
| 178 |
+
"print(f\"Saved to {best_dir}\")"
|
| 179 |
+
]
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"cell_type": "markdown",
|
| 183 |
+
"id": "m5",
|
| 184 |
+
"metadata": {},
|
| 185 |
+
"source": [
|
| 186 |
+
"## 5. Evaluate"
|
| 187 |
+
]
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"cell_type": "code",
|
| 191 |
+
"id": "c8",
|
| 192 |
+
"execution_count": null,
|
| 193 |
+
"metadata": {},
|
| 194 |
+
"outputs": [],
|
| 195 |
+
"source": "import matplotlib.pyplot as plt\nfrom sklearn.metrics import ConfusionMatrixDisplay\n\ntarget_names = [ID2LABEL[i] for i in range(3)]\n\nfor split in [\"val\", \"test\"]:\n eval_model = AutoModelForSequenceClassification.from_pretrained(str(best_dir))\n eval_trainer = Trainer(model=eval_model, args=TrainingArguments(output_dir=\"/tmp/eval\", per_device_eval_batch_size=args.batch_size*2, report_to=\"none\"))\n preds_out = eval_trainer.predict(dd[split])\n preds = np.argmax(preds_out.predictions, axis=-1)\n labels = preds_out.label_ids\n\n print(f\"\\n{'='*60}\")\n print(f\" DeBERTa — {split} ({len(dd[split]):,} samples)\")\n print(f\"{'='*60}\")\n print(f\" Macro F1: {f1_score(labels, preds, average='macro'):.4f}\")\n print(f\" MCC: {matthews_corrcoef(labels, preds):.4f}\\n\")\n print(classification_report(labels, preds, target_names=target_names, digits=4))\n\n cm = confusion_matrix(labels, preds, labels=[0, 1, 2])\n fig, ax = plt.subplots(figsize=(6, 5))\n ConfusionMatrixDisplay(cm, display_labels=target_names).plot(ax=ax, cmap=\"Blues\", values_format=\",\")\n ax.set_title(f\"DeBERTa — {split}\")\n plt.tight_layout()\n fig.savefig(plots_dir / f\"confusion_matrix_{split}.png\", dpi=150)\n plt.show()\n print(f\"Saved to {plots_dir / f'confusion_matrix_{split}.png'}\")"
|
| 196 |
+
}
|
| 197 |
+
],
|
| 198 |
+
"metadata": {
|
| 199 |
+
"kernelspec": {
|
| 200 |
+
"display_name": "Python 3",
|
| 201 |
+
"language": "python",
|
| 202 |
+
"name": "python3"
|
| 203 |
+
},
|
| 204 |
+
"language_info": {
|
| 205 |
+
"name": "python",
|
| 206 |
+
"version": "3.11.0"
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
"nbformat": 4,
|
| 210 |
+
"nbformat_minor": 5
|
| 211 |
+
}
|
src/notebooks/train_distilbert.ipynb
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "m0",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# Train & Evaluate DistilBERT\n",
|
| 9 |
+
"\n",
|
| 10 |
+
"Sentence-pair boundary classification on combined PubMed + Wikipedia + Gutenberg data."
|
| 11 |
+
]
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"cell_type": "code",
|
| 15 |
+
"id": "c0",
|
| 16 |
+
"execution_count": null,
|
| 17 |
+
"metadata": {},
|
| 18 |
+
"outputs": [],
|
| 19 |
+
"source": [
|
| 20 |
+
"import os, sys\n",
|
| 21 |
+
"os.chdir(os.path.join(os.path.dirname(os.getcwd()), \"..\"))\n",
|
| 22 |
+
"print(\"Working dir:\", os.getcwd())\n",
|
| 23 |
+
"import wandb\n",
|
| 24 |
+
"from dotenv import load_dotenv\n",
|
| 25 |
+
"\n",
|
| 26 |
+
"\n",
|
| 27 |
+
"load_dotenv(\"env.txt\")\n",
|
| 28 |
+
"wandb.login(key=os.getenv(\"WB_TOKEN\"))"
|
| 29 |
+
]
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"cell_type": "code",
|
| 33 |
+
"id": "c1",
|
| 34 |
+
"execution_count": null,
|
| 35 |
+
"metadata": {},
|
| 36 |
+
"outputs": [],
|
| 37 |
+
"source": [
|
| 38 |
+
"import json\n",
|
| 39 |
+
"import logging\n",
|
| 40 |
+
"from pathlib import Path\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"import numpy as np\n",
|
| 43 |
+
"import torch\n",
|
| 44 |
+
"import torch.nn as nn\n",
|
| 45 |
+
"from sklearn.metrics import classification_report, confusion_matrix, f1_score, matthews_corrcoef\n",
|
| 46 |
+
"from transformers import AutoModelForSequenceClassification, AutoTokenizer, EarlyStoppingCallback, Trainer, TrainingArguments\n",
|
| 47 |
+
"\n",
|
| 48 |
+
"from src.datasets.combined_pairs_dataset import CombinedPairsDataset, CombinedPairsConfig, NUM_LABELS, ID2LABEL, LABEL2ID\n",
|
| 49 |
+
"from src.models.distilbert import load_distilbert, load_distilbert_tokenizer\n",
|
| 50 |
+
"from src.models.train import WeightedTrainer, compute_metrics\n",
|
| 51 |
+
"from src.schemas.training_args import DistilBertTrainingArgs\n",
|
| 52 |
+
"\n",
|
| 53 |
+
"logging.basicConfig(level=logging.INFO, format=\"%(asctime)s %(levelname)s %(message)s\")\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
|
| 56 |
+
"print(f\"Device: {device}\")"
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"cell_type": "markdown",
|
| 61 |
+
"id": "m1",
|
| 62 |
+
"metadata": {},
|
| 63 |
+
"source": [
|
| 64 |
+
"## 1. Configuration & Data"
|
| 65 |
+
]
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"cell_type": "code",
|
| 69 |
+
"id": "c2",
|
| 70 |
+
"execution_count": null,
|
| 71 |
+
"metadata": {},
|
| 72 |
+
"outputs": [],
|
| 73 |
+
"source": [
|
| 74 |
+
"args = DistilBertTrainingArgs()\n",
|
| 75 |
+
"print(f\"epochs: {args.epochs}, lr: {args.lr}, batch_size: {args.batch_size}, patience: {args.patience}\")\n",
|
| 76 |
+
"\n",
|
| 77 |
+
"os.environ[\"WANDB_PROJECT\"] = \"bottlecap\"\n",
|
| 78 |
+
"os.environ[\"WANDB_RUN_NAME\"] = \"distilbert\""
|
| 79 |
+
]
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"cell_type": "code",
|
| 83 |
+
"id": "c3",
|
| 84 |
+
"execution_count": null,
|
| 85 |
+
"metadata": {},
|
| 86 |
+
"outputs": [],
|
| 87 |
+
"source": [
|
| 88 |
+
"cfg = CombinedPairsConfig(data_root=\"data\", gutenberg_train_cap=args.gutenberg_cap, seed=args.seed, max_length=args.max_length)\n",
|
| 89 |
+
"builder = CombinedPairsDataset(cfg)\n",
|
| 90 |
+
"raw_splits = builder.build_splits()\n",
|
| 91 |
+
"class_weights = builder.compute_class_weights(raw_splits[\"train\"])\n",
|
| 92 |
+
"print(f\"Class weights: {class_weights.tolist()}\")"
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"cell_type": "markdown",
|
| 97 |
+
"id": "m2",
|
| 98 |
+
"metadata": {},
|
| 99 |
+
"source": [
|
| 100 |
+
"## 2. Train"
|
| 101 |
+
]
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"cell_type": "code",
|
| 105 |
+
"id": "c4",
|
| 106 |
+
"execution_count": null,
|
| 107 |
+
"metadata": {},
|
| 108 |
+
"outputs": [],
|
| 109 |
+
"source": [
|
| 110 |
+
"model = load_distilbert()\n",
|
| 111 |
+
"tokenizer = load_distilbert_tokenizer()\n",
|
| 112 |
+
"dd = builder.build_hf_dataset_dict(tokenizer, raw_splits=raw_splits)\n",
|
| 113 |
+
"\n",
|
| 114 |
+
"print(f\"Params: {sum(p.numel() for p in model.parameters()):,}\"\n",
|
| 115 |
+
" f\" Train: {len(dd['train']):,} Val: {len(dd['val']):,} Test: {len(dd['test']):,}\")"
|
| 116 |
+
]
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"cell_type": "code",
|
| 120 |
+
"id": "c5",
|
| 121 |
+
"execution_count": null,
|
| 122 |
+
"metadata": {},
|
| 123 |
+
"outputs": [],
|
| 124 |
+
"source": [
|
| 125 |
+
"trainer = WeightedTrainer(\n",
|
| 126 |
+
" class_weights=class_weights,\n",
|
| 127 |
+
" model=model,\n",
|
| 128 |
+
" args=args.to_training_arguments(),\n",
|
| 129 |
+
" train_dataset=dd[\"train\"],\n",
|
| 130 |
+
" eval_dataset=dd[\"val\"],\n",
|
| 131 |
+
" compute_metrics=compute_metrics,\n",
|
| 132 |
+
" callbacks=[EarlyStoppingCallback(early_stopping_patience=args.patience)] if args.patience > 0 else [],\n",
|
| 133 |
+
")\n",
|
| 134 |
+
"\n",
|
| 135 |
+
"trainer.train()"
|
| 136 |
+
]
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"cell_type": "markdown",
|
| 140 |
+
"id": "m3",
|
| 141 |
+
"metadata": {},
|
| 142 |
+
"source": [
|
| 143 |
+
"## 3. Loss Curves"
|
| 144 |
+
]
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"cell_type": "code",
|
| 148 |
+
"id": "c6",
|
| 149 |
+
"execution_count": null,
|
| 150 |
+
"metadata": {},
|
| 151 |
+
"outputs": [],
|
| 152 |
+
"source": "import matplotlib.pyplot as plt\n\nplots_dir = Path(args.output_dir) / \"plots\"\nplots_dir.mkdir(parents=True, exist_ok=True)\n\nhistory = trainer.state.log_history\ntrain_steps = [e[\"step\"] for e in history if \"loss\" in e and \"eval_loss\" not in e]\ntrain_loss = [e[\"loss\"] for e in history if \"loss\" in e and \"eval_loss\" not in e]\neval_steps = [e[\"step\"] for e in history if \"eval_loss\" in e]\neval_loss = [e[\"eval_loss\"] for e in history if \"eval_loss\" in e]\neval_epochs = [e.get(\"epoch\", 0) for e in history if \"eval_loss\" in e]\neval_f1 = [e.get(\"eval_macro_f1\", 0) for e in history if \"eval_loss\" in e]\n\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 4))\nax1.plot(train_steps, train_loss, label=\"Train\", alpha=0.7)\nax1.plot(eval_steps, eval_loss, \"o-\", label=\"Val\")\nax1.set_xlabel(\"Step\"); ax1.set_ylabel(\"Loss\"); ax1.set_title(\"DistilBERT — Loss\"); ax1.legend(); ax1.grid(True, alpha=0.3)\nax2.plot(eval_epochs, eval_f1, \"s-\", label=\"Macro F1\")\nax2.set_xlabel(\"Epoch\"); ax2.set_ylabel(\"F1\"); ax2.set_title(\"DistilBERT — Macro F1\"); ax2.legend(); ax2.grid(True, alpha=0.3)\nplt.tight_layout()\nfig.savefig(plots_dir / \"loss_curves.png\", dpi=150)\nplt.show()\nprint(f\"Saved to {plots_dir / 'loss_curves.png'}\")"
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"cell_type": "markdown",
|
| 156 |
+
"id": "m4",
|
| 157 |
+
"metadata": {},
|
| 158 |
+
"source": [
|
| 159 |
+
"## 4. Save Best Model"
|
| 160 |
+
]
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"cell_type": "code",
|
| 164 |
+
"id": "c7",
|
| 165 |
+
"execution_count": null,
|
| 166 |
+
"metadata": {},
|
| 167 |
+
"outputs": [],
|
| 168 |
+
"source": [
|
| 169 |
+
"best_dir = Path(args.output_dir) / \"best\"\n",
|
| 170 |
+
"best_dir.mkdir(parents=True, exist_ok=True)\n",
|
| 171 |
+
"trainer.save_model(str(best_dir))\n",
|
| 172 |
+
"tokenizer.save_pretrained(str(best_dir))\n",
|
| 173 |
+
"torch.save(class_weights, best_dir / \"class_weights.pt\")\n",
|
| 174 |
+
"\n",
|
| 175 |
+
"train_config = {\"model_type\": \"distilbert\", \"pretrained\": model.config._name_or_path, \"epochs\": args.epochs, \"batch_size\": args.batch_size, \"lr\": args.lr, \"max_length\": args.max_length, \"class_weights\": class_weights.tolist(), \"num_labels\": NUM_LABELS, \"id2label\": ID2LABEL, \"label2id\": LABEL2ID}\n",
|
| 176 |
+
"with open(best_dir / \"train_config.json\", \"w\") as f:\n",
|
| 177 |
+
" json.dump(train_config, f, indent=2)\n",
|
| 178 |
+
"print(f\"Saved to {best_dir}\")"
|
| 179 |
+
]
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"cell_type": "markdown",
|
| 183 |
+
"id": "m5",
|
| 184 |
+
"metadata": {},
|
| 185 |
+
"source": [
|
| 186 |
+
"## 5. Evaluate"
|
| 187 |
+
]
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"cell_type": "code",
|
| 191 |
+
"id": "c8",
|
| 192 |
+
"execution_count": null,
|
| 193 |
+
"metadata": {},
|
| 194 |
+
"outputs": [],
|
| 195 |
+
"source": "import matplotlib.pyplot as plt\nfrom sklearn.metrics import ConfusionMatrixDisplay\n\ntarget_names = [ID2LABEL[i] for i in range(3)]\n\nfor split in [\"val\", \"test\"]:\n eval_model = AutoModelForSequenceClassification.from_pretrained(str(best_dir))\n eval_trainer = Trainer(model=eval_model, args=TrainingArguments(output_dir=\"/tmp/eval\", per_device_eval_batch_size=args.batch_size*2, report_to=\"none\"))\n preds_out = eval_trainer.predict(dd[split])\n preds = np.argmax(preds_out.predictions, axis=-1)\n labels = preds_out.label_ids\n\n print(f\"\\n{'='*60}\")\n print(f\" DistilBERT — {split} ({len(dd[split]):,} samples)\")\n print(f\"{'='*60}\")\n print(f\" Macro F1: {f1_score(labels, preds, average='macro'):.4f}\")\n print(f\" MCC: {matthews_corrcoef(labels, preds):.4f}\\n\")\n print(classification_report(labels, preds, target_names=target_names, digits=4))\n\n # Confusion matrix\n cm = confusion_matrix(labels, preds, labels=[0, 1, 2])\n fig, ax = plt.subplots(figsize=(6, 5))\n ConfusionMatrixDisplay(cm, display_labels=target_names).plot(ax=ax, cmap=\"Blues\", values_format=\",\")\n ax.set_title(f\"DistilBERT — {split}\")\n plt.tight_layout()\n fig.savefig(plots_dir / f\"confusion_matrix_{split}.png\", dpi=150)\n plt.show()\n print(f\"Saved to {plots_dir / f'confusion_matrix_{split}.png'}\")"
|
| 196 |
+
}
|
| 197 |
+
],
|
| 198 |
+
"metadata": {
|
| 199 |
+
"kernelspec": {
|
| 200 |
+
"display_name": "Python 3",
|
| 201 |
+
"language": "python",
|
| 202 |
+
"name": "python3"
|
| 203 |
+
},
|
| 204 |
+
"language_info": {
|
| 205 |
+
"name": "python",
|
| 206 |
+
"version": "3.11.0"
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
"nbformat": 4,
|
| 210 |
+
"nbformat_minor": 5
|
| 211 |
+
}
|
src/notebooks/train_models.ipynb
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": "# Train & Evaluate DistilBERT / BERT / DeBERTa\n\nSentence-pair boundary classification on combined PubMed + Wikipedia + Gutenberg data.\n\n**Metrics:** per-class F1, macro F1, weighted F1, MCC (Matthews Correlation Coefficient)."
|
| 7 |
+
},
|
| 8 |
+
{
|
| 9 |
+
"cell_type": "code",
|
| 10 |
+
"execution_count": null,
|
| 11 |
+
"metadata": {},
|
| 12 |
+
"outputs": [],
|
| 13 |
+
"source": [
|
| 14 |
+
"import os, sys\n",
|
| 15 |
+
"os.chdir(os.path.join(os.path.dirname(os.getcwd()), \"..\"))\n",
|
| 16 |
+
"print(\"Working dir:\", os.getcwd())\n",
|
| 17 |
+
"import wandb\n",
|
| 18 |
+
"from dotenv import load_dotenv\n",
|
| 19 |
+
"\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"load_dotenv()\n",
|
| 22 |
+
"wandb.login(key=os.getenv(\"WB_TOKEN\"))"
|
| 23 |
+
]
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"cell_type": "code",
|
| 27 |
+
"execution_count": null,
|
| 28 |
+
"metadata": {},
|
| 29 |
+
"outputs": [],
|
| 30 |
+
"source": "import json\nimport logging\nfrom pathlib import Path\n\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom sklearn.metrics import (\n classification_report,\n confusion_matrix,\n f1_score,\n matthews_corrcoef,\n)\nfrom transformers import (\n AutoModelForSequenceClassification,\n AutoTokenizer,\n EarlyStoppingCallback,\n Trainer,\n TrainingArguments,\n)\n\nfrom src.datasets.combined_pairs_dataset import (\n CombinedPairsDataset,\n CombinedPairsConfig,\n NUM_LABELS,\n ID2LABEL,\n LABEL2ID,\n)\nfrom src.models.bert import load_bert, load_bert_tokenizer\nfrom src.models.deberta import load_deberta, load_deberta_tokenizer\nfrom src.models.distilbert import load_distilbert, load_distilbert_tokenizer\nfrom src.models.train import WeightedTrainer, compute_metrics\nfrom src.schemas.training_args import BertTrainingArgs, DebertaTrainingArgs, DistilBertTrainingArgs\n\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s %(levelname)s %(message)s\")\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"Device: {device}\")\nprint(f\"PyTorch: {torch.__version__}\")"
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"cell_type": "markdown",
|
| 34 |
+
"metadata": {},
|
| 35 |
+
"source": [
|
| 36 |
+
"## 1. Configuration"
|
| 37 |
+
]
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"cell_type": "code",
|
| 41 |
+
"execution_count": null,
|
| 42 |
+
"metadata": {},
|
| 43 |
+
"outputs": [],
|
| 44 |
+
"source": "# ── Training args from dataclasses (edit fields to override defaults) ──\ndistilbert_args = DistilBertTrainingArgs()\nbert_args = BertTrainingArgs()\ndeberta_args = DebertaTrainingArgs()\n\nfor name, args in [(\"DistilBERT\", distilbert_args), (\"BERT\", bert_args), (\"DeBERTa\", deberta_args)]:\n print(f\"{name} config:\")\n print(f\" output_dir: {args.output_dir}\")\n print(f\" epochs: {args.epochs}\")\n print(f\" batch_size: {args.batch_size}\")\n print(f\" lr: {args.lr}\")\n print(f\" max_length: {args.max_length}\")\n print(f\" gutenberg_cap: {args.gutenberg_cap}\")\n print()"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"cell_type": "markdown",
|
| 48 |
+
"metadata": {},
|
| 49 |
+
"source": [
|
| 50 |
+
"## 2. Build dataset splits"
|
| 51 |
+
]
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"cell_type": "code",
|
| 55 |
+
"execution_count": null,
|
| 56 |
+
"metadata": {},
|
| 57 |
+
"outputs": [],
|
| 58 |
+
"source": "cfg = CombinedPairsConfig(\n data_root=\"data\",\n gutenberg_train_cap=distilbert_args.gutenberg_cap,\n seed=distilbert_args.seed,\n max_length=distilbert_args.max_length,\n)\nbuilder = CombinedPairsDataset(cfg)\nraw_splits = builder.build_splits()\nclass_weights = builder.compute_class_weights(raw_splits[\"train\"])\nprint(f\"\\nClass weights: {class_weights.tolist()}\")"
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"cell_type": "markdown",
|
| 62 |
+
"metadata": {},
|
| 63 |
+
"source": [
|
| 64 |
+
"## 3. Helper functions"
|
| 65 |
+
]
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"cell_type": "code",
|
| 69 |
+
"execution_count": null,
|
| 70 |
+
"metadata": {},
|
| 71 |
+
"outputs": [],
|
| 72 |
+
"source": "import matplotlib.pyplot as plt\nfrom sklearn.metrics import ConfusionMatrixDisplay\n\n\ndef plot_loss_curves(trainer, model_name: str, output_dir: str):\n \"\"\"Plot training and validation loss curves from Trainer log history.\"\"\"\n plots_dir = Path(output_dir) / \"plots\"\n plots_dir.mkdir(parents=True, exist_ok=True)\n\n history = trainer.state.log_history\n\n train_steps, train_loss = [], []\n eval_steps, eval_loss = [], []\n eval_epochs, eval_macro_f1, eval_weighted_f1, eval_mcc = [], [], [], []\n\n for entry in history:\n if \"loss\" in entry and \"eval_loss\" not in entry:\n train_steps.append(entry[\"step\"])\n train_loss.append(entry[\"loss\"])\n if \"eval_loss\" in entry:\n eval_steps.append(entry[\"step\"])\n eval_loss.append(entry[\"eval_loss\"])\n eval_epochs.append(entry.get(\"epoch\", 0))\n eval_macro_f1.append(entry.get(\"eval_macro_f1\", 0))\n eval_weighted_f1.append(entry.get(\"eval_weighted_f1\", 0))\n eval_mcc.append(entry.get(\"eval_mcc\", 0))\n\n fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n\n # Loss curves\n ax = axes[0]\n ax.plot(train_steps, train_loss, label=\"Train loss\", alpha=0.7)\n ax.plot(eval_steps, eval_loss, \"o-\", label=\"Val loss\", markersize=6)\n ax.set_xlabel(\"Step\")\n ax.set_ylabel(\"Loss\")\n ax.set_title(f\"{model_name} — Loss\")\n ax.legend()\n ax.grid(True, alpha=0.3)\n\n # Metrics per epoch\n ax = axes[1]\n ax.plot(eval_epochs, eval_macro_f1, \"s-\", label=\"Macro F1\", markersize=6)\n ax.plot(eval_epochs, eval_weighted_f1, \"^-\", label=\"Weighted F1\", markersize=6)\n ax.plot(eval_epochs, eval_mcc, \"D-\", label=\"MCC\", markersize=6)\n ax.set_xlabel(\"Epoch\")\n ax.set_ylabel(\"Score\")\n ax.set_title(f\"{model_name} — Eval Metrics\")\n ax.set_ylim(0, 1)\n ax.legend()\n ax.grid(True, alpha=0.3)\n\n plt.tight_layout()\n fig.savefig(plots_dir / \"loss_curves.png\", dpi=150)\n plt.show()\n print(f\"Saved to {plots_dir / 'loss_curves.png'}\")\n\n\ndef save_best(trainer, model, tokenizer, train_args, model_type: str):\n best_dir = Path(train_args.output_dir) / \"best\"\n best_dir.mkdir(parents=True, exist_ok=True)\n\n trainer.save_model(str(best_dir))\n tokenizer.save_pretrained(str(best_dir))\n torch.save(class_weights, best_dir / \"class_weights.pt\")\n\n train_config = {\n \"model_type\": model_type,\n \"pretrained\": model.config._name_or_path,\n \"epochs\": train_args.epochs,\n \"batch_size\": train_args.batch_size,\n \"lr\": train_args.lr,\n \"max_length\": train_args.max_length,\n \"class_weights\": class_weights.tolist(),\n \"num_labels\": NUM_LABELS,\n \"id2label\": ID2LABEL,\n \"label2id\": LABEL2ID,\n }\n with open(best_dir / \"train_config.json\", \"w\") as f:\n json.dump(train_config, f, indent=2)\n\n print(f\"Best model saved to {best_dir}\")\n\n\ndef evaluate_model(model_dir: Path, dd, model_name: str, split=\"test\", batch_size=32, seed=42):\n \"\"\"Run Trainer.predict() and print full metrics report.\"\"\"\n plots_dir = model_dir.parent / \"plots\"\n plots_dir.mkdir(parents=True, exist_ok=True)\n\n model = AutoModelForSequenceClassification.from_pretrained(str(model_dir))\n ds = dd[split]\n\n eval_args = TrainingArguments(\n output_dir=\"/tmp/eval_output\",\n per_device_eval_batch_size=batch_size,\n report_to=\"none\",\n seed=seed,\n )\n trainer = Trainer(model=model, args=eval_args)\n predictions = trainer.predict(ds)\n\n preds = np.argmax(predictions.predictions, axis=-1)\n labels = predictions.label_ids\n target_names = [ID2LABEL[i] for i in range(3)]\n\n weighted_f1 = f1_score(labels, preds, average=\"weighted\")\n macro_f1 = f1_score(labels, preds, average=\"macro\")\n mcc = matthews_corrcoef(labels, preds)\n\n print(f\"\\n{'='*60}\")\n print(f\" {model_name} — {split} split ({len(ds):,} samples)\")\n print(f\"{'='*60}\\n\")\n print(f\" Weighted F1: {weighted_f1:.4f}\")\n print(f\" Macro F1: {macro_f1:.4f}\")\n print(f\" MCC: {mcc:.4f}\\n\")\n\n print(classification_report(labels, preds, target_names=target_names, digits=4))\n\n # Confusion matrix\n cm = confusion_matrix(labels, preds, labels=[0, 1, 2])\n fig, ax = plt.subplots(figsize=(6, 5))\n ConfusionMatrixDisplay(cm, display_labels=target_names).plot(ax=ax, cmap=\"Blues\", values_format=\",\")\n ax.set_title(f\"{model_name} — {split}\")\n plt.tight_layout()\n fig.savefig(plots_dir / f\"confusion_matrix_{split}.png\", dpi=150)\n plt.show()\n print(f\"Saved to {plots_dir / f'confusion_matrix_{split}.png'}\")\n\n # save metrics json\n report_dict = classification_report(labels, preds, target_names=target_names, output_dict=True)\n report_dict[\"weighted_f1\"] = weighted_f1\n report_dict[\"macro_f1\"] = macro_f1\n report_dict[\"mcc\"] = mcc\n\n out_path = model_dir / f\"{split}_metrics.json\"\n with open(out_path, \"w\") as f:\n json.dump(report_dict, f, indent=2)\n print(f\"Metrics saved to {out_path}\")\n\n return report_dict"
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"cell_type": "markdown",
|
| 76 |
+
"metadata": {},
|
| 77 |
+
"source": [
|
| 78 |
+
"---\n",
|
| 79 |
+
"## 4. Train DistilBERT"
|
| 80 |
+
]
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"cell_type": "code",
|
| 84 |
+
"execution_count": null,
|
| 85 |
+
"metadata": {},
|
| 86 |
+
"outputs": [],
|
| 87 |
+
"source": "distilbert_model = load_distilbert()\ndistilbert_tokenizer = load_distilbert_tokenizer()\n\ndd_distilbert = builder.build_hf_dataset_dict(distilbert_tokenizer, raw_splits=raw_splits)\n\nprint(f\"Params: {sum(p.numel() for p in distilbert_model.parameters()):,}\")\nprint(f\"Train: {len(dd_distilbert['train']):,} Val: {len(dd_distilbert['val']):,} Test: {len(dd_distilbert['test']):,}\")"
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"cell_type": "code",
|
| 91 |
+
"execution_count": null,
|
| 92 |
+
"metadata": {},
|
| 93 |
+
"outputs": [],
|
| 94 |
+
"source": "distilbert_trainer = WeightedTrainer(\n class_weights=class_weights,\n model=distilbert_model,\n args=distilbert_args.to_training_arguments(),\n train_dataset=dd_distilbert[\"train\"],\n eval_dataset=dd_distilbert[\"val\"],\n compute_metrics=compute_metrics,\n callbacks=[EarlyStoppingCallback(early_stopping_patience=distilbert_args.patience)]\n if distilbert_args.patience > 0 else [],\n)\n\ndistilbert_trainer.train()"
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"cell_type": "code",
|
| 98 |
+
"source": "plot_loss_curves(distilbert_trainer, \"DistilBERT\", distilbert_args.output_dir)",
|
| 99 |
+
"metadata": {},
|
| 100 |
+
"execution_count": null,
|
| 101 |
+
"outputs": []
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"cell_type": "code",
|
| 105 |
+
"execution_count": null,
|
| 106 |
+
"metadata": {},
|
| 107 |
+
"outputs": [],
|
| 108 |
+
"source": "save_best(distilbert_trainer, distilbert_model, distilbert_tokenizer, distilbert_args, \"distilbert\")"
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"cell_type": "markdown",
|
| 112 |
+
"metadata": {},
|
| 113 |
+
"source": [
|
| 114 |
+
"### 4.1 Evaluate DistilBERT"
|
| 115 |
+
]
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"cell_type": "code",
|
| 119 |
+
"execution_count": null,
|
| 120 |
+
"metadata": {},
|
| 121 |
+
"outputs": [],
|
| 122 |
+
"source": "distilbert_best = Path(distilbert_args.output_dir) / \"best\"\ndistilbert_val_metrics = evaluate_model(distilbert_best, dd_distilbert, \"DistilBERT\", split=\"val\",\n batch_size=distilbert_args.batch_size * 2,\n seed=distilbert_args.seed)"
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"cell_type": "code",
|
| 126 |
+
"execution_count": null,
|
| 127 |
+
"metadata": {},
|
| 128 |
+
"outputs": [],
|
| 129 |
+
"source": "distilbert_test_metrics = evaluate_model(distilbert_best, dd_distilbert, \"DistilBERT\", split=\"test\",\n batch_size=distilbert_args.batch_size * 2,\n seed=distilbert_args.seed)"
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
"cell_type": "markdown",
|
| 133 |
+
"metadata": {},
|
| 134 |
+
"source": [
|
| 135 |
+
"---\n",
|
| 136 |
+
"## 5. Train BERT"
|
| 137 |
+
]
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"cell_type": "code",
|
| 141 |
+
"execution_count": null,
|
| 142 |
+
"metadata": {},
|
| 143 |
+
"outputs": [],
|
| 144 |
+
"source": "bert_model = load_bert()\nbert_tokenizer = load_bert_tokenizer()\n\ndd_bert = builder.build_hf_dataset_dict(bert_tokenizer, raw_splits=raw_splits)\n\nprint(f\"Params: {sum(p.numel() for p in bert_model.parameters()):,}\")\nprint(f\"Train: {len(dd_bert['train']):,} Val: {len(dd_bert['val']):,} Test: {len(dd_bert['test']):,}\")"
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"cell_type": "code",
|
| 148 |
+
"execution_count": null,
|
| 149 |
+
"metadata": {},
|
| 150 |
+
"outputs": [],
|
| 151 |
+
"source": "bert_trainer = WeightedTrainer(\n class_weights=class_weights,\n model=bert_model,\n args=bert_args.to_training_arguments(),\n train_dataset=dd_bert[\"train\"],\n eval_dataset=dd_bert[\"val\"],\n compute_metrics=compute_metrics,\n callbacks=[EarlyStoppingCallback(early_stopping_patience=bert_args.patience)]\n if bert_args.patience > 0 else [],\n)\n\nbert_trainer.train()"
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"cell_type": "code",
|
| 155 |
+
"source": "plot_loss_curves(bert_trainer, \"BERT\", bert_args.output_dir)",
|
| 156 |
+
"metadata": {},
|
| 157 |
+
"execution_count": null,
|
| 158 |
+
"outputs": []
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"cell_type": "code",
|
| 162 |
+
"execution_count": null,
|
| 163 |
+
"metadata": {},
|
| 164 |
+
"outputs": [],
|
| 165 |
+
"source": "save_best(bert_trainer, bert_model, bert_tokenizer, bert_args, \"bert\")"
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"cell_type": "markdown",
|
| 169 |
+
"metadata": {},
|
| 170 |
+
"source": [
|
| 171 |
+
"### 5.1 Evaluate BERT"
|
| 172 |
+
]
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"cell_type": "code",
|
| 176 |
+
"execution_count": null,
|
| 177 |
+
"metadata": {},
|
| 178 |
+
"outputs": [],
|
| 179 |
+
"source": "bert_best = Path(bert_args.output_dir) / \"best\"\nbert_val_metrics = evaluate_model(bert_best, dd_bert, \"BERT\", split=\"val\",\n batch_size=bert_args.batch_size * 2,\n seed=bert_args.seed)"
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"cell_type": "code",
|
| 183 |
+
"execution_count": null,
|
| 184 |
+
"metadata": {},
|
| 185 |
+
"outputs": [],
|
| 186 |
+
"source": "bert_test_metrics = evaluate_model(bert_best, dd_bert, \"BERT\", split=\"test\",\n batch_size=bert_args.batch_size * 2,\n seed=bert_args.seed)"
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"cell_type": "markdown",
|
| 190 |
+
"source": "---\n## 6. Train DeBERTa",
|
| 191 |
+
"metadata": {}
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"cell_type": "code",
|
| 195 |
+
"source": "deberta_model = load_deberta()\ndeberta_tokenizer = load_deberta_tokenizer()\n\ndd_deberta = builder.build_hf_dataset_dict(deberta_tokenizer, raw_splits=raw_splits)\n\nprint(f\"Params: {sum(p.numel() for p in deberta_model.parameters()):,}\")\nprint(f\"Train: {len(dd_deberta['train']):,} Val: {len(dd_deberta['val']):,} Test: {len(dd_deberta['test']):,}\")",
|
| 196 |
+
"metadata": {},
|
| 197 |
+
"execution_count": null,
|
| 198 |
+
"outputs": []
|
| 199 |
+
},
|
| 200 |
+
{
|
| 201 |
+
"cell_type": "code",
|
| 202 |
+
"source": "deberta_trainer = WeightedTrainer(\n class_weights=class_weights,\n model=deberta_model,\n args=deberta_args.to_training_arguments(),\n train_dataset=dd_deberta[\"train\"],\n eval_dataset=dd_deberta[\"val\"],\n compute_metrics=compute_metrics,\n callbacks=[EarlyStoppingCallback(early_stopping_patience=deberta_args.patience)]\n if deberta_args.patience > 0 else [],\n)\n\ndeberta_trainer.train()",
|
| 203 |
+
"metadata": {},
|
| 204 |
+
"execution_count": null,
|
| 205 |
+
"outputs": []
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
"cell_type": "code",
|
| 209 |
+
"source": "plot_loss_curves(deberta_trainer, \"DeBERTa\", deberta_args.output_dir)",
|
| 210 |
+
"metadata": {},
|
| 211 |
+
"execution_count": null,
|
| 212 |
+
"outputs": []
|
| 213 |
+
},
|
| 214 |
+
{
|
| 215 |
+
"cell_type": "code",
|
| 216 |
+
"source": "save_best(deberta_trainer, deberta_model, deberta_tokenizer, deberta_args, \"deberta\")",
|
| 217 |
+
"metadata": {},
|
| 218 |
+
"execution_count": null,
|
| 219 |
+
"outputs": []
|
| 220 |
+
},
|
| 221 |
+
{
|
| 222 |
+
"cell_type": "markdown",
|
| 223 |
+
"source": "### 6.1 Evaluate DeBERTa",
|
| 224 |
+
"metadata": {}
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"cell_type": "code",
|
| 228 |
+
"source": "deberta_best = Path(deberta_args.output_dir) / \"best\"\ndeberta_val_metrics = evaluate_model(deberta_best, dd_deberta, \"DeBERTa\", split=\"val\",\n batch_size=deberta_args.batch_size * 2,\n seed=deberta_args.seed)",
|
| 229 |
+
"metadata": {},
|
| 230 |
+
"execution_count": null,
|
| 231 |
+
"outputs": []
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"cell_type": "code",
|
| 235 |
+
"source": "deberta_test_metrics = evaluate_model(deberta_best, dd_deberta, \"DeBERTa\", split=\"test\",\n batch_size=deberta_args.batch_size * 2,\n seed=deberta_args.seed)",
|
| 236 |
+
"metadata": {},
|
| 237 |
+
"execution_count": null,
|
| 238 |
+
"outputs": []
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"cell_type": "markdown",
|
| 242 |
+
"metadata": {},
|
| 243 |
+
"source": "---\n## 7. Compare models"
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"cell_type": "code",
|
| 247 |
+
"execution_count": null,
|
| 248 |
+
"metadata": {},
|
| 249 |
+
"outputs": [],
|
| 250 |
+
"source": "import pandas as pd\n\ncomparison = pd.DataFrame({\n \"Metric\": [\"Weighted F1\", \"Macro F1\", \"MCC\",\n \"F1 SAME_PARA\", \"F1 NEW_PARA\", \"F1 NEWLINE\"],\n \"DistilBERT\": [\n distilbert_test_metrics[\"weighted_f1\"],\n distilbert_test_metrics[\"macro_f1\"],\n distilbert_test_metrics[\"mcc\"],\n distilbert_test_metrics[\"SAME_PARAGRAPH\"][\"f1-score\"],\n distilbert_test_metrics[\"NEW_PARAGRAPH\"][\"f1-score\"],\n distilbert_test_metrics[\"NEWLINE\"][\"f1-score\"],\n ],\n \"BERT\": [\n bert_test_metrics[\"weighted_f1\"],\n bert_test_metrics[\"macro_f1\"],\n bert_test_metrics[\"mcc\"],\n bert_test_metrics[\"SAME_PARAGRAPH\"][\"f1-score\"],\n bert_test_metrics[\"NEW_PARAGRAPH\"][\"f1-score\"],\n bert_test_metrics[\"NEWLINE\"][\"f1-score\"],\n ],\n \"DeBERTa\": [\n deberta_test_metrics[\"weighted_f1\"],\n deberta_test_metrics[\"macro_f1\"],\n deberta_test_metrics[\"mcc\"],\n deberta_test_metrics[\"SAME_PARAGRAPH\"][\"f1-score\"],\n deberta_test_metrics[\"NEW_PARAGRAPH\"][\"f1-score\"],\n deberta_test_metrics[\"NEWLINE\"][\"f1-score\"],\n ],\n})\n\ncomparison = comparison.set_index(\"Metric\")\ncomparison = comparison.round(4)\ncomparison"
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"cell_type": "code",
|
| 254 |
+
"execution_count": null,
|
| 255 |
+
"metadata": {},
|
| 256 |
+
"outputs": [],
|
| 257 |
+
"source": "import matplotlib.pyplot as plt\n\ncomparison_dir = Path(\"checkpoints/plots\")\ncomparison_dir.mkdir(parents=True, exist_ok=True)\n\nmetrics_to_plot = [\"Weighted F1\", \"Macro F1\", \"MCC\"]\nplot_data = comparison.loc[metrics_to_plot]\n\nax = plot_data.plot.bar(rot=0, figsize=(10, 4))\nax.set_ylim(0, 1)\nax.set_ylabel(\"Score\")\nax.set_title(\"DistilBERT vs BERT vs DeBERTa — Test Set\")\nax.legend(loc=\"lower right\")\n\nfor container in ax.containers:\n ax.bar_label(container, fmt=\"%.3f\", fontsize=8, padding=2)\n\nplt.tight_layout()\nplt.savefig(comparison_dir / \"comparison_main_metrics.png\", dpi=150)\nplt.show()\nprint(f\"Saved to {comparison_dir / 'comparison_main_metrics.png'}\")"
|
| 258 |
+
},
|
| 259 |
+
{
|
| 260 |
+
"cell_type": "code",
|
| 261 |
+
"execution_count": null,
|
| 262 |
+
"metadata": {},
|
| 263 |
+
"outputs": [],
|
| 264 |
+
"source": "per_class = [\"F1 SAME_PARA\", \"F1 NEW_PARA\", \"F1 NEWLINE\"]\nplot_data = comparison.loc[per_class]\n\nax = plot_data.plot.bar(rot=0, figsize=(10, 4))\nax.set_ylim(0, 1)\nax.set_ylabel(\"F1 Score\")\nax.set_title(\"Per-class F1 — Test Set\")\nax.legend(loc=\"lower right\")\n\nfor container in ax.containers:\n ax.bar_label(container, fmt=\"%.3f\", fontsize=8, padding=2)\n\nplt.tight_layout()\nplt.savefig(comparison_dir / \"comparison_per_class_f1.png\", dpi=150)\nplt.show()\nprint(f\"Saved to {comparison_dir / 'comparison_per_class_f1.png'}\")"
|
| 265 |
+
}
|
| 266 |
+
],
|
| 267 |
+
"metadata": {
|
| 268 |
+
"kernelspec": {
|
| 269 |
+
"display_name": "Python 3",
|
| 270 |
+
"language": "python",
|
| 271 |
+
"name": "python3"
|
| 272 |
+
},
|
| 273 |
+
"language_info": {
|
| 274 |
+
"name": "python",
|
| 275 |
+
"version": "3.10.0"
|
| 276 |
+
}
|
| 277 |
+
},
|
| 278 |
+
"nbformat": 4,
|
| 279 |
+
"nbformat_minor": 4
|
| 280 |
+
}
|