JonnyBP commited on
Commit
d4353ad
·
1 Parent(s): 817a4d6

feat: add new structure for augmentation. #6

Browse files
notebooks/07_augmentation_clean_v2.ipynb CHANGED
@@ -4,8 +4,7 @@
4
  "cell_type": "markdown",
5
  "metadata": {},
6
  "source": [
7
- "# 🔁 Notebook 07 — Data Augmentation\n",
8
- "## YouTube Hate Speech Detection\n",
9
  "\n",
10
  "### ¿Qué hace este notebook?\n",
11
  "Evaluamos **3 estrategias de augmentation** sobre la clase tóxica del train set\n",
@@ -23,11 +22,6 @@
23
  "- EDA: mínima perturbación, preserva semántica\n",
24
  "- Back-translation: mejor calidad semántica, parafrasea naturalmente\n",
25
  "\n",
26
- "### Limitación conocida\n",
27
- "El augmentation se aplica **antes** del CV, no dentro del loop.\n",
28
- "Con 1000 muestras implementarlo dentro del loop es costoso.\n",
29
- "Se documenta como limitación metodológica.\n",
30
- "\n",
31
  "### Modelo de referencia\n",
32
  "LR tuned cargado desde `final_model.joblib` — F1 test ≈ 0.7579."
33
  ]
@@ -151,7 +145,7 @@
151
  },
152
  {
153
  "cell_type": "code",
154
- "execution_count": 10,
155
  "metadata": {},
156
  "outputs": [],
157
  "source": [
@@ -195,11 +189,13 @@
195
  " f1_tr = f1_score(y_tr, pred_train, average='weighted')\n",
196
  " roc = roc_auc_score(y_te, pipeline.predict_proba(X_te)[:,1])\n",
197
  " cv_mean = cv_std = cv_gap = None\n",
 
198
  " if cv_scores is not None:\n",
199
  " cv_mean = cv_scores['test_score'].mean()\n",
200
  " cv_std = cv_scores['test_score'].std()\n",
201
  " cv_gap = abs(cv_mean - f1_te) * 100\n",
202
  " return {\n",
 
203
  " 'name' : name,\n",
204
  " 'f1_test' : round(f1_te, 4),\n",
205
  " 'f1_train' : round(f1_tr, 4),\n",
@@ -312,11 +308,7 @@
312
  "source": [
313
  "## 5. Estrategia 1 — Synonym Replacement (WordNet)\n",
314
  "\n",
315
- "Reemplaza palabras aleatorias por sinónimos usando WordNet.\n",
316
- "\n",
317
- "**Limitación conocida:** WordNet tiene cobertura baja en\n",
318
- "lenguaje coloquial y jerga (palabras como 'thug', 'bullshit').\n",
319
- "Los sinónimos formales pueden cambiar el estilo y confundir al modelo."
320
  ]
321
  },
322
  {
@@ -370,7 +362,7 @@
370
  },
371
  {
372
  "cell_type": "code",
373
- "execution_count": 14,
374
  "metadata": {},
375
  "outputs": [
376
  {
@@ -401,6 +393,7 @@
401
  " X_test, y_test, 'LR + WordNet', cv_wn)\n",
402
  "\n",
403
  "delta = metrics_wn['f1_test'] - metrics_base['f1_test']\n",
 
404
  "print(f\"WordNet: F1={metrics_wn['f1_test']:.4f} ({delta*100:+.2f}pp) | \"\n",
405
  " f\"FN={metrics_wn['fn']} | FP={metrics_wn['fp']}\")"
406
  ]
@@ -537,8 +530,7 @@
537
  "- Los insultos raciales en inglés no siempre tienen equivalente directo\n",
538
  " en español → el modelo de traducción busca el contexto más cercano\n",
539
  "- Añade variedad sintáctica real, no solo léxica\n",
540
- "\n",
541
- "**Requiere:** `pip install deep-translator` e internet."
542
  ]
543
  },
544
  {
 
4
  "cell_type": "markdown",
5
  "metadata": {},
6
  "source": [
7
+ "# 🔁 Data Augmentation\n",
 
8
  "\n",
9
  "### ¿Qué hace este notebook?\n",
10
  "Evaluamos **3 estrategias de augmentation** sobre la clase tóxica del train set\n",
 
22
  "- EDA: mínima perturbación, preserva semántica\n",
23
  "- Back-translation: mejor calidad semántica, parafrasea naturalmente\n",
24
  "\n",
 
 
 
 
 
25
  "### Modelo de referencia\n",
26
  "LR tuned cargado desde `final_model.joblib` — F1 test ≈ 0.7579."
27
  ]
 
145
  },
146
  {
147
  "cell_type": "code",
148
+ "execution_count": null,
149
  "metadata": {},
150
  "outputs": [],
151
  "source": [
 
189
  " f1_tr = f1_score(y_tr, pred_train, average='weighted')\n",
190
  " roc = roc_auc_score(y_te, pipeline.predict_proba(X_te)[:,1])\n",
191
  " cv_mean = cv_std = cv_gap = None\n",
192
+ "\n",
193
  " if cv_scores is not None:\n",
194
  " cv_mean = cv_scores['test_score'].mean()\n",
195
  " cv_std = cv_scores['test_score'].std()\n",
196
  " cv_gap = abs(cv_mean - f1_te) * 100\n",
197
  " return {\n",
198
+ " \n",
199
  " 'name' : name,\n",
200
  " 'f1_test' : round(f1_te, 4),\n",
201
  " 'f1_train' : round(f1_tr, 4),\n",
 
308
  "source": [
309
  "## 5. Estrategia 1 — Synonym Replacement (WordNet)\n",
310
  "\n",
311
+ "Reemplaza palabras aleatorias por sinónimos usando WordNet."
 
 
 
 
312
  ]
313
  },
314
  {
 
362
  },
363
  {
364
  "cell_type": "code",
365
+ "execution_count": null,
366
  "metadata": {},
367
  "outputs": [
368
  {
 
393
  " X_test, y_test, 'LR + WordNet', cv_wn)\n",
394
  "\n",
395
  "delta = metrics_wn['f1_test'] - metrics_base['f1_test']\n",
396
+ "\n",
397
  "print(f\"WordNet: F1={metrics_wn['f1_test']:.4f} ({delta*100:+.2f}pp) | \"\n",
398
  " f\"FN={metrics_wn['fn']} | FP={metrics_wn['fp']}\")"
399
  ]
 
530
  "- Los insultos raciales en inglés no siempre tienen equivalente directo\n",
531
  " en español → el modelo de traducción busca el contexto más cercano\n",
532
  "- Añade variedad sintáctica real, no solo léxica\n",
533
+ "\n"
 
534
  ]
535
  },
536
  {
notebooks/08_transformers_v2.ipynb CHANGED
@@ -25,8 +25,7 @@
25
  "10. Evaluación RoBERTa Hate \n",
26
  "11. Comparación de modelos \n",
27
  "12. Error Analysis \n",
28
- "13. Guardado del mejor modelo \n",
29
- "14. Conclusiones\n"
30
  ]
31
  },
32
  {
@@ -39,7 +38,7 @@
39
  },
40
  {
41
  "cell_type": "code",
42
- "execution_count": 68,
43
  "id": "072caf60",
44
  "metadata": {},
45
  "outputs": [
@@ -57,7 +56,6 @@
57
  }
58
  ],
59
  "source": [
60
- "\n",
61
  "import os\n",
62
  "import sys\n",
63
  "import yaml\n",
@@ -117,7 +115,7 @@
117
  "if torch.cuda.is_available():\n",
118
  " print(\"GPU:\", torch.cuda.get_device_name(0))\n",
119
  "else:\n",
120
- " print(\"⚠️ GPU no detectada\")\n"
121
  ]
122
  },
123
  {
@@ -130,7 +128,7 @@
130
  },
131
  {
132
  "cell_type": "code",
133
- "execution_count": 69,
134
  "id": "0b9084dc",
135
  "metadata": {},
136
  "outputs": [
@@ -143,7 +141,6 @@
143
  }
144
  ],
145
  "source": [
146
- "\n",
147
  "def set_seed(seed=42):\n",
148
  "\n",
149
  " random.seed(seed)\n",
@@ -160,7 +157,7 @@
160
  "\n",
161
  "set_seed(RAND)\n",
162
  "\n",
163
- "print(\"Seed configurado:\", RAND)\n"
164
  ]
165
  },
166
  {
@@ -173,7 +170,7 @@
173
  },
174
  {
175
  "cell_type": "code",
176
- "execution_count": 70,
177
  "id": "0fb40c48",
178
  "metadata": {},
179
  "outputs": [
@@ -310,25 +307,13 @@
310
  }
311
  ],
312
  "source": [
313
- "\n",
314
- "DATA_PATH = (\n",
315
- " PROJECT_ROOT\n",
316
- " / \"data\"\n",
317
- " / \"processed\"\n",
318
- " / \"v2\"\n",
319
- " / \"comments_preprocessed.csv\"\n",
320
- ")\n",
321
  "\n",
322
  "df = pd.read_csv(DATA_PATH)\n",
323
  "\n",
324
  "TEXT_COL = \"Text\"\n",
325
  "\n",
326
- "df[TEXT_COL] = (\n",
327
- " df[TEXT_COL]\n",
328
- " .fillna(\"\")\n",
329
- " .astype(str)\n",
330
- " .str.strip()\n",
331
- ")\n",
332
  "\n",
333
  "df = df[df[TEXT_COL] != \"\"].copy()\n",
334
  "\n",
@@ -336,7 +321,7 @@
336
  "\n",
337
  "print(df.shape)\n",
338
  "\n",
339
- "df.head()\n"
340
  ]
341
  },
342
  {
@@ -349,7 +334,7 @@
349
  },
350
  {
351
  "cell_type": "code",
352
- "execution_count": 71,
353
  "id": "85c08f41",
354
  "metadata": {},
355
  "outputs": [
@@ -367,14 +352,10 @@
367
  }
368
  ],
369
  "source": [
370
- "\n",
371
  "X = df[TEXT_COL]\n",
372
  "y = df[TARGET]\n",
373
  "\n",
374
- "# -------------------------------------------------\n",
375
  "# TEST FINAL\n",
376
- "# -------------------------------------------------\n",
377
- "\n",
378
  "X_temp, X_test, y_temp, y_test = train_test_split(\n",
379
  " X,\n",
380
  " y,\n",
@@ -383,10 +364,7 @@
383
  " random_state=RAND,\n",
384
  ")\n",
385
  "\n",
386
- "# -------------------------------------------------\n",
387
  "# VALIDATION\n",
388
- "# -------------------------------------------------\n",
389
- "\n",
390
  "X_train, X_valid, y_train, y_valid = train_test_split(\n",
391
  " X_temp,\n",
392
  " y_temp,\n",
@@ -401,7 +379,7 @@
401
  "\n",
402
  "print(\"Train:\", len(X_train))\n",
403
  "print(\"Validation:\", len(X_valid))\n",
404
- "print(\"Test:\", len(X_test))\n"
405
  ]
406
  },
407
  {
@@ -414,7 +392,7 @@
414
  },
415
  {
416
  "cell_type": "code",
417
- "execution_count": 72,
418
  "id": "4372ea97",
419
  "metadata": {},
420
  "outputs": [
@@ -447,7 +425,6 @@
447
  }
448
  ],
449
  "source": [
450
- "\n",
451
  "print(\"=\" * 50)\n",
452
  "print(\"CLASS DISTRIBUTION\")\n",
453
  "print(\"=\" * 50)\n",
@@ -459,12 +436,12 @@
459
  "print(y_valid.value_counts(normalize=True))\n",
460
  "\n",
461
  "print(\"\\nTest\")\n",
462
- "print(y_test.value_counts(normalize=True))\n"
463
  ]
464
  },
465
  {
466
  "cell_type": "code",
467
- "execution_count": 73,
468
  "id": "49b2f924",
469
  "metadata": {},
470
  "outputs": [
@@ -495,7 +472,6 @@
495
  }
496
  ],
497
  "source": [
498
- "\n",
499
  "train_lengths = X_train.str.split().apply(len)\n",
500
  "\n",
501
  "print(train_lengths.describe())\n",
@@ -507,7 +483,7 @@
507
  "plt.title(\"Distribución de longitud de comentarios\")\n",
508
  "plt.xlabel(\"Número de palabras\")\n",
509
  "\n",
510
- "plt.show()\n"
511
  ]
512
  },
513
  {
@@ -525,7 +501,6 @@
525
  "metadata": {},
526
  "outputs": [],
527
  "source": [
528
- "\n",
529
  "def build_hf_dataset(X, y):\n",
530
  "\n",
531
  " df_local = pd.DataFrame({\n",
@@ -639,21 +614,14 @@
639
  " plt.show()\n",
640
  "\n",
641
  " return {\n",
642
- "\n",
643
  " \"accuracy\": accuracy_score(y_test, preds),\n",
644
- "\n",
645
  " \"precision\": precision_score(y_test, preds),\n",
646
- "\n",
647
  " \"recall\": recall_score(y_test, preds),\n",
648
- "\n",
649
  " \"f1\": f1_score(y_test, preds),\n",
650
- "\n",
651
  " \"roc_auc\": roc_auc_score(y_test, probs),\n",
652
- "\n",
653
  " \"preds\": preds,\n",
654
- "\n",
655
  " \"probs\": probs,\n",
656
- " }\n"
657
  ]
658
  },
659
  {
@@ -666,7 +634,7 @@
666
  },
667
  {
668
  "cell_type": "code",
669
- "execution_count": 75,
670
  "id": "f6f76741",
671
  "metadata": {},
672
  "outputs": [
@@ -682,12 +650,11 @@
682
  }
683
  ],
684
  "source": [
685
- "\n",
686
  "hf_train_raw = build_hf_dataset(X_train, y_train)\n",
687
  "hf_valid_raw = build_hf_dataset(X_valid, y_valid)\n",
688
  "hf_test_raw = build_hf_dataset(X_test, y_test)\n",
689
  "\n",
690
- "print(hf_train_raw)\n"
691
  ]
692
  },
693
  {
@@ -709,7 +676,7 @@
709
  },
710
  {
711
  "cell_type": "code",
712
- "execution_count": 76,
713
  "id": "1e8f8d34",
714
  "metadata": {},
715
  "outputs": [
@@ -722,7 +689,6 @@
722
  }
723
  ],
724
  "source": [
725
- "\n",
726
  "DISTIL_MODEL = \"distilbert-base-uncased\"\n",
727
  "\n",
728
  "MAX_LEN = 128\n",
@@ -730,12 +696,12 @@
730
  "EPOCHS = 3\n",
731
  "LR = 2e-5\n",
732
  "\n",
733
- "print(DISTIL_MODEL)\n"
734
  ]
735
  },
736
  {
737
  "cell_type": "code",
738
- "execution_count": 77,
739
  "id": "b9ee6361",
740
  "metadata": {},
741
  "outputs": [
@@ -750,7 +716,6 @@
750
  }
751
  ],
752
  "source": [
753
- "\n",
754
  "distil_tokenizer = AutoTokenizer.from_pretrained(\n",
755
  " DISTIL_MODEL\n",
756
  ")\n",
@@ -771,25 +736,12 @@
771
  " hf_test_raw,\n",
772
  " distil_tokenizer,\n",
773
  " MAX_LEN,\n",
774
- ")\n"
775
- ]
776
- },
777
- {
778
- "cell_type": "code",
779
- "execution_count": 78,
780
- "id": "02d2d984",
781
- "metadata": {},
782
- "outputs": [],
783
- "source": [
784
- "\n",
785
- "distil_collator = DataCollatorWithPadding(\n",
786
- " tokenizer=distil_tokenizer\n",
787
- ")\n"
788
  ]
789
  },
790
  {
791
  "cell_type": "code",
792
- "execution_count": 79,
793
  "id": "8e867b40",
794
  "metadata": {},
795
  "outputs": [
@@ -825,6 +777,7 @@
825
  }
826
  ],
827
  "source": [
 
828
  "\n",
829
  "distil_model = AutoModelForSequenceClassification.from_pretrained(\n",
830
  " DISTIL_MODEL,\n",
@@ -833,12 +786,12 @@
833
  "\n",
834
  "distil_model.to(device)\n",
835
  "\n",
836
- "print(distil_model.__class__.__name__)\n"
837
  ]
838
  },
839
  {
840
  "cell_type": "code",
841
- "execution_count": 80,
842
  "id": "ed09755c",
843
  "metadata": {},
844
  "outputs": [
@@ -851,7 +804,6 @@
851
  }
852
  ],
853
  "source": [
854
- "\n",
855
  "distil_args = TrainingArguments(\n",
856
  "\n",
857
  " output_dir= PROJECT_ROOT / \"models\" / \"distilbert_results\",\n",
@@ -882,29 +834,17 @@
882
  " report_to=\"none\",\n",
883
  "\n",
884
  " seed=RAND,\n",
885
- ")\n"
886
- ]
887
- },
888
- {
889
- "cell_type": "code",
890
- "execution_count": 81,
891
- "id": "000c6a6b",
892
- "metadata": {},
893
- "outputs": [],
894
- "source": [
895
- "\n",
896
- "distil_early_stopping = EarlyStoppingCallback(\n",
897
- " early_stopping_patience=2\n",
898
- ")\n"
899
  ]
900
  },
901
  {
902
  "cell_type": "code",
903
- "execution_count": 82,
904
  "id": "827eed3d",
905
  "metadata": {},
906
  "outputs": [],
907
  "source": [
 
908
  "\n",
909
  "distil_trainer = Trainer(\n",
910
  "\n",
@@ -922,12 +862,12 @@
922
  " compute_metrics=compute_metrics,\n",
923
  "\n",
924
  " callbacks=[distil_early_stopping],\n",
925
- ")\n"
926
  ]
927
  },
928
  {
929
  "cell_type": "code",
930
- "execution_count": 83,
931
  "id": "eea8b514",
932
  "metadata": {},
933
  "outputs": [
@@ -1024,12 +964,11 @@
1024
  }
1025
  ],
1026
  "source": [
1027
- "\n",
1028
  "print(\"=\" * 50)\n",
1029
  "print(\"TRAINING DISTILBERT\")\n",
1030
  "print(\"=\" * 50)\n",
1031
  "\n",
1032
- "distil_trainer.train()\n"
1033
  ]
1034
  },
1035
  {
@@ -1042,7 +981,7 @@
1042
  },
1043
  {
1044
  "cell_type": "code",
1045
- "execution_count": 84,
1046
  "id": "9647c651",
1047
  "metadata": {},
1048
  "outputs": [
@@ -1086,13 +1025,12 @@
1086
  }
1087
  ],
1088
  "source": [
1089
- "\n",
1090
  "distil_results = evaluate_model(\n",
1091
  " distil_trainer,\n",
1092
  " distil_test,\n",
1093
  " y_test,\n",
1094
  " \"DistilBERT\",\n",
1095
- ")\n"
1096
  ]
1097
  },
1098
  {
@@ -1114,7 +1052,7 @@
1114
  },
1115
  {
1116
  "cell_type": "code",
1117
- "execution_count": 85,
1118
  "id": "40cbdf8d",
1119
  "metadata": {},
1120
  "outputs": [
@@ -1127,7 +1065,6 @@
1127
  }
1128
  ],
1129
  "source": [
1130
- "\n",
1131
  "HATE_MODEL = \"cardiffnlp/twitter-roberta-base-hate\"\n",
1132
  "\n",
1133
  "MAX_LEN = 128\n",
@@ -1135,12 +1072,12 @@
1135
  "EPOCHS = 3\n",
1136
  "LR = 2e-5\n",
1137
  "\n",
1138
- "print(HATE_MODEL)\n"
1139
  ]
1140
  },
1141
  {
1142
  "cell_type": "code",
1143
- "execution_count": 86,
1144
  "id": "6d0d9245",
1145
  "metadata": {},
1146
  "outputs": [
@@ -1155,7 +1092,6 @@
1155
  }
1156
  ],
1157
  "source": [
1158
- "\n",
1159
  "hate_tokenizer = AutoTokenizer.from_pretrained(\n",
1160
  " HATE_MODEL\n",
1161
  ")\n",
@@ -1176,25 +1112,12 @@
1176
  " hf_test_raw,\n",
1177
  " hate_tokenizer,\n",
1178
  " MAX_LEN,\n",
1179
- ")\n"
1180
- ]
1181
- },
1182
- {
1183
- "cell_type": "code",
1184
- "execution_count": 87,
1185
- "id": "1052b914",
1186
- "metadata": {},
1187
- "outputs": [],
1188
- "source": [
1189
- "\n",
1190
- "hate_collator = DataCollatorWithPadding(\n",
1191
- " tokenizer=hate_tokenizer\n",
1192
- ")\n"
1193
  ]
1194
  },
1195
  {
1196
  "cell_type": "code",
1197
- "execution_count": 88,
1198
  "id": "5114bb65",
1199
  "metadata": {},
1200
  "outputs": [
@@ -1214,6 +1137,7 @@
1214
  }
1215
  ],
1216
  "source": [
 
1217
  "\n",
1218
  "hate_model = AutoModelForSequenceClassification.from_pretrained(\n",
1219
  " HATE_MODEL,\n",
@@ -1223,7 +1147,7 @@
1223
  "\n",
1224
  "hate_model.to(device)\n",
1225
  "\n",
1226
- "print(hate_model.__class__.__name__)\n"
1227
  ]
1228
  },
1229
  {
@@ -1236,7 +1160,7 @@
1236
  },
1237
  {
1238
  "cell_type": "code",
1239
- "execution_count": 89,
1240
  "id": "c3729fb1",
1241
  "metadata": {},
1242
  "outputs": [
@@ -1253,18 +1177,12 @@
1253
  }
1254
  ],
1255
  "source": [
1256
- "\n",
1257
- "# -----------------------------------------------------\n",
1258
  "# Congelar backbone\n",
1259
- "# -----------------------------------------------------\n",
1260
  "\n",
1261
  "for param in hate_model.base_model.parameters():\n",
1262
  " param.requires_grad = False\n",
1263
  "\n",
1264
- "# -----------------------------------------------------\n",
1265
  "# Classification head entrenable\n",
1266
- "# -----------------------------------------------------\n",
1267
- "\n",
1268
  "classifier_found = False\n",
1269
  "\n",
1270
  "for name, param in hate_model.named_parameters():\n",
@@ -1279,10 +1197,7 @@
1279
  "else:\n",
1280
  " print(\"⚠️ No se encontró classifier head\")\n",
1281
  "\n",
1282
- "# -----------------------------------------------------\n",
1283
  "# Verificación\n",
1284
- "# -----------------------------------------------------\n",
1285
- "\n",
1286
  "total_params = 0\n",
1287
  "trainable_params = 0\n",
1288
  "\n",
@@ -1298,12 +1213,12 @@
1298
  "print()\n",
1299
  "print(f\"Trainable params: {trainable_params:,}\")\n",
1300
  "print(f\"Total params: {total_params:,}\")\n",
1301
- "print(f\"Trainable %: {pct:.2f}%\")\n"
1302
  ]
1303
  },
1304
  {
1305
  "cell_type": "code",
1306
- "execution_count": 90,
1307
  "id": "7a0a4821",
1308
  "metadata": {},
1309
  "outputs": [
@@ -1316,7 +1231,6 @@
1316
  }
1317
  ],
1318
  "source": [
1319
- "\n",
1320
  "hate_args = TrainingArguments(\n",
1321
  "\n",
1322
  " output_dir= PROJECT_ROOT / \"models\" / \"roberta_hate_results\",\n",
@@ -1347,29 +1261,17 @@
1347
  " report_to=\"none\",\n",
1348
  "\n",
1349
  " seed=RAND,\n",
1350
- ")\n"
1351
- ]
1352
- },
1353
- {
1354
- "cell_type": "code",
1355
- "execution_count": 91,
1356
- "id": "13ffac9e",
1357
- "metadata": {},
1358
- "outputs": [],
1359
- "source": [
1360
- "\n",
1361
- "hate_early_stopping = EarlyStoppingCallback(\n",
1362
- " early_stopping_patience=2\n",
1363
- ")\n"
1364
  ]
1365
  },
1366
  {
1367
  "cell_type": "code",
1368
- "execution_count": 92,
1369
  "id": "6a3acd3d",
1370
  "metadata": {},
1371
  "outputs": [],
1372
  "source": [
 
1373
  "\n",
1374
  "hate_trainer = Trainer(\n",
1375
  "\n",
@@ -1387,12 +1289,12 @@
1387
  " compute_metrics=compute_metrics,\n",
1388
  "\n",
1389
  " callbacks=[hate_early_stopping],\n",
1390
- ")\n"
1391
  ]
1392
  },
1393
  {
1394
  "cell_type": "code",
1395
- "execution_count": 93,
1396
  "id": "a5752cbb",
1397
  "metadata": {},
1398
  "outputs": [
@@ -1489,12 +1391,11 @@
1489
  }
1490
  ],
1491
  "source": [
1492
- "\n",
1493
  "print(\"=\" * 50)\n",
1494
  "print(\"TRAINING ROBERTA HATE\")\n",
1495
  "print(\"=\" * 50)\n",
1496
  "\n",
1497
- "hate_trainer.train()\n"
1498
  ]
1499
  },
1500
  {
@@ -1507,7 +1408,7 @@
1507
  },
1508
  {
1509
  "cell_type": "code",
1510
- "execution_count": 94,
1511
  "id": "d58a64a5",
1512
  "metadata": {},
1513
  "outputs": [
@@ -1551,13 +1452,12 @@
1551
  }
1552
  ],
1553
  "source": [
1554
- "\n",
1555
  "hate_results = evaluate_model(\n",
1556
  " hate_trainer,\n",
1557
  " hate_test,\n",
1558
  " y_test,\n",
1559
  " \"RoBERTa Hate\",\n",
1560
- ")\n"
1561
  ]
1562
  },
1563
  {
@@ -1570,7 +1470,7 @@
1570
  },
1571
  {
1572
  "cell_type": "code",
1573
- "execution_count": 95,
1574
  "id": "d7f2b05f",
1575
  "metadata": {},
1576
  "outputs": [
@@ -1638,7 +1538,6 @@
1638
  }
1639
  ],
1640
  "source": [
1641
- "\n",
1642
  "comparison_df = pd.DataFrame({\n",
1643
  "\n",
1644
  " \"Model\": [\n",
@@ -1672,12 +1571,12 @@
1672
  " ],\n",
1673
  "})\n",
1674
  "\n",
1675
- "comparison_df\n"
1676
  ]
1677
  },
1678
  {
1679
  "cell_type": "code",
1680
- "execution_count": 96,
1681
  "id": "2c757b76",
1682
  "metadata": {},
1683
  "outputs": [
@@ -1693,7 +1592,6 @@
1693
  }
1694
  ],
1695
  "source": [
1696
- "\n",
1697
  "plt.figure(figsize=(8, 5))\n",
1698
  "\n",
1699
  "sns.barplot(\n",
@@ -1703,8 +1601,7 @@
1703
  ")\n",
1704
  "\n",
1705
  "plt.title(\"Comparación F1 Score\")\n",
1706
- "\n",
1707
- "plt.show()\n"
1708
  ]
1709
  },
1710
  {
@@ -1717,7 +1614,7 @@
1717
  },
1718
  {
1719
  "cell_type": "code",
1720
- "execution_count": 97,
1721
  "id": "0f32e457",
1722
  "metadata": {},
1723
  "outputs": [
@@ -1730,29 +1627,24 @@
1730
  }
1731
  ],
1732
  "source": [
1733
- "\n",
1734
  "if hate_results[\"f1\"] >= distil_results[\"f1\"]:\n",
1735
  "\n",
1736
  " best_name = \"RoBERTa Hate\"\n",
1737
- "\n",
1738
  " best_preds = hate_results[\"preds\"]\n",
1739
- "\n",
1740
  " best_probs = hate_results[\"probs\"]\n",
1741
  "\n",
1742
  "else:\n",
1743
  "\n",
1744
  " best_name = \"DistilBERT\"\n",
1745
- "\n",
1746
  " best_preds = distil_results[\"preds\"]\n",
1747
- "\n",
1748
  " best_probs = distil_results[\"probs\"]\n",
1749
  "\n",
1750
- "print(\"Best model:\", best_name)\n"
1751
  ]
1752
  },
1753
  {
1754
  "cell_type": "code",
1755
- "execution_count": 98,
1756
  "id": "1d7148ce",
1757
  "metadata": {},
1758
  "outputs": [
@@ -1858,15 +1750,11 @@
1858
  }
1859
  ],
1860
  "source": [
1861
- "\n",
1862
  "error_df = pd.DataFrame({\n",
1863
  "\n",
1864
  " \"text\": X_test.values,\n",
1865
- "\n",
1866
  " \"real\": y_test.values,\n",
1867
- "\n",
1868
  " \"pred\": best_preds,\n",
1869
- "\n",
1870
  " \"prob_toxic\": best_probs,\n",
1871
  "})\n",
1872
  "\n",
@@ -1875,13 +1763,12 @@
1875
  ")\n",
1876
  "\n",
1877
  "print(\"Errores:\", error_df[\"is_error\"].sum())\n",
1878
- "\n",
1879
- "error_df.head()\n"
1880
  ]
1881
  },
1882
  {
1883
  "cell_type": "code",
1884
- "execution_count": 99,
1885
  "id": "64457e22",
1886
  "metadata": {},
1887
  "outputs": [
@@ -1940,12 +1827,7 @@
1940
  }
1941
  ],
1942
  "source": [
1943
- "\n",
1944
- "false_negatives = error_df[\n",
1945
- " (error_df[\"real\"] == 1)\n",
1946
- " &\n",
1947
- " (error_df[\"pred\"] == 0)\n",
1948
- "]\n",
1949
  "\n",
1950
  "print(\"=\" * 80)\n",
1951
  "print(\"FALSE NEGATIVES\")\n",
@@ -1954,9 +1836,7 @@
1954
  "for idx, row in false_negatives.head(10).iterrows():\n",
1955
  "\n",
1956
  " print(\"\\nProb toxicidad:\", round(row[\"prob_toxic\"], 4))\n",
1957
- "\n",
1958
  " print(\"-\" * 60)\n",
1959
- "\n",
1960
  " print(row[\"text\"])\n"
1961
  ]
1962
  },
@@ -1970,7 +1850,7 @@
1970
  },
1971
  {
1972
  "cell_type": "code",
1973
- "execution_count": 100,
1974
  "id": "8cc4001e",
1975
  "metadata": {},
1976
  "outputs": [
@@ -1997,7 +1877,6 @@
1997
  }
1998
  ],
1999
  "source": [
2000
- "\n",
2001
  "SAVE_DIR = PROJECT_ROOT / \"models\"\n",
2002
  "\n",
2003
  "if best_name == \"RoBERTa Hate\":\n",
@@ -2015,10 +1894,8 @@
2015
  " save_path = SAVE_DIR / \"best_distilbert\"\n",
2016
  "\n",
2017
  "final_model.save_model(save_path)\n",
2018
- "\n",
2019
  "final_tokenizer.save_pretrained(save_path)\n",
2020
- "\n",
2021
- "print(\"Modelo guardado en:\", save_path)\n"
2022
  ]
2023
  }
2024
  ],
 
25
  "10. Evaluación RoBERTa Hate \n",
26
  "11. Comparación de modelos \n",
27
  "12. Error Analysis \n",
28
+ "13. Guardado del mejor modelo \n"
 
29
  ]
30
  },
31
  {
 
38
  },
39
  {
40
  "cell_type": "code",
41
+ "execution_count": null,
42
  "id": "072caf60",
43
  "metadata": {},
44
  "outputs": [
 
56
  }
57
  ],
58
  "source": [
 
59
  "import os\n",
60
  "import sys\n",
61
  "import yaml\n",
 
115
  "if torch.cuda.is_available():\n",
116
  " print(\"GPU:\", torch.cuda.get_device_name(0))\n",
117
  "else:\n",
118
+ " print(\"⚠️ GPU no detectada\")"
119
  ]
120
  },
121
  {
 
128
  },
129
  {
130
  "cell_type": "code",
131
+ "execution_count": null,
132
  "id": "0b9084dc",
133
  "metadata": {},
134
  "outputs": [
 
141
  }
142
  ],
143
  "source": [
 
144
  "def set_seed(seed=42):\n",
145
  "\n",
146
  " random.seed(seed)\n",
 
157
  "\n",
158
  "set_seed(RAND)\n",
159
  "\n",
160
+ "print(\"Seed configurado:\", RAND)"
161
  ]
162
  },
163
  {
 
170
  },
171
  {
172
  "cell_type": "code",
173
+ "execution_count": null,
174
  "id": "0fb40c48",
175
  "metadata": {},
176
  "outputs": [
 
307
  }
308
  ],
309
  "source": [
310
+ "DATA_PATH = (PROJECT_ROOT / \"data\" / \"processed\" / \"v2\" / \"comments_preprocessed.csv\")\n",
 
 
 
 
 
 
 
311
  "\n",
312
  "df = pd.read_csv(DATA_PATH)\n",
313
  "\n",
314
  "TEXT_COL = \"Text\"\n",
315
  "\n",
316
+ "df[TEXT_COL] = (df[TEXT_COL].fillna(\"\").astype(str).str.strip())\n",
 
 
 
 
 
317
  "\n",
318
  "df = df[df[TEXT_COL] != \"\"].copy()\n",
319
  "\n",
 
321
  "\n",
322
  "print(df.shape)\n",
323
  "\n",
324
+ "df.head()"
325
  ]
326
  },
327
  {
 
334
  },
335
  {
336
  "cell_type": "code",
337
+ "execution_count": null,
338
  "id": "85c08f41",
339
  "metadata": {},
340
  "outputs": [
 
352
  }
353
  ],
354
  "source": [
 
355
  "X = df[TEXT_COL]\n",
356
  "y = df[TARGET]\n",
357
  "\n",
 
358
  "# TEST FINAL\n",
 
 
359
  "X_temp, X_test, y_temp, y_test = train_test_split(\n",
360
  " X,\n",
361
  " y,\n",
 
364
  " random_state=RAND,\n",
365
  ")\n",
366
  "\n",
 
367
  "# VALIDATION\n",
 
 
368
  "X_train, X_valid, y_train, y_valid = train_test_split(\n",
369
  " X_temp,\n",
370
  " y_temp,\n",
 
379
  "\n",
380
  "print(\"Train:\", len(X_train))\n",
381
  "print(\"Validation:\", len(X_valid))\n",
382
+ "print(\"Test:\", len(X_test))"
383
  ]
384
  },
385
  {
 
392
  },
393
  {
394
  "cell_type": "code",
395
+ "execution_count": null,
396
  "id": "4372ea97",
397
  "metadata": {},
398
  "outputs": [
 
425
  }
426
  ],
427
  "source": [
 
428
  "print(\"=\" * 50)\n",
429
  "print(\"CLASS DISTRIBUTION\")\n",
430
  "print(\"=\" * 50)\n",
 
436
  "print(y_valid.value_counts(normalize=True))\n",
437
  "\n",
438
  "print(\"\\nTest\")\n",
439
+ "print(y_test.value_counts(normalize=True))"
440
  ]
441
  },
442
  {
443
  "cell_type": "code",
444
+ "execution_count": null,
445
  "id": "49b2f924",
446
  "metadata": {},
447
  "outputs": [
 
472
  }
473
  ],
474
  "source": [
 
475
  "train_lengths = X_train.str.split().apply(len)\n",
476
  "\n",
477
  "print(train_lengths.describe())\n",
 
483
  "plt.title(\"Distribución de longitud de comentarios\")\n",
484
  "plt.xlabel(\"Número de palabras\")\n",
485
  "\n",
486
+ "plt.show()"
487
  ]
488
  },
489
  {
 
501
  "metadata": {},
502
  "outputs": [],
503
  "source": [
 
504
  "def build_hf_dataset(X, y):\n",
505
  "\n",
506
  " df_local = pd.DataFrame({\n",
 
614
  " plt.show()\n",
615
  "\n",
616
  " return {\n",
 
617
  " \"accuracy\": accuracy_score(y_test, preds),\n",
 
618
  " \"precision\": precision_score(y_test, preds),\n",
 
619
  " \"recall\": recall_score(y_test, preds),\n",
 
620
  " \"f1\": f1_score(y_test, preds),\n",
 
621
  " \"roc_auc\": roc_auc_score(y_test, probs),\n",
 
622
  " \"preds\": preds,\n",
 
623
  " \"probs\": probs,\n",
624
+ " }"
625
  ]
626
  },
627
  {
 
634
  },
635
  {
636
  "cell_type": "code",
637
+ "execution_count": null,
638
  "id": "f6f76741",
639
  "metadata": {},
640
  "outputs": [
 
650
  }
651
  ],
652
  "source": [
 
653
  "hf_train_raw = build_hf_dataset(X_train, y_train)\n",
654
  "hf_valid_raw = build_hf_dataset(X_valid, y_valid)\n",
655
  "hf_test_raw = build_hf_dataset(X_test, y_test)\n",
656
  "\n",
657
+ "print(hf_train_raw)"
658
  ]
659
  },
660
  {
 
676
  },
677
  {
678
  "cell_type": "code",
679
+ "execution_count": null,
680
  "id": "1e8f8d34",
681
  "metadata": {},
682
  "outputs": [
 
689
  }
690
  ],
691
  "source": [
 
692
  "DISTIL_MODEL = \"distilbert-base-uncased\"\n",
693
  "\n",
694
  "MAX_LEN = 128\n",
 
696
  "EPOCHS = 3\n",
697
  "LR = 2e-5\n",
698
  "\n",
699
+ "print(DISTIL_MODEL)"
700
  ]
701
  },
702
  {
703
  "cell_type": "code",
704
+ "execution_count": null,
705
  "id": "b9ee6361",
706
  "metadata": {},
707
  "outputs": [
 
716
  }
717
  ],
718
  "source": [
 
719
  "distil_tokenizer = AutoTokenizer.from_pretrained(\n",
720
  " DISTIL_MODEL\n",
721
  ")\n",
 
736
  " hf_test_raw,\n",
737
  " distil_tokenizer,\n",
738
  " MAX_LEN,\n",
739
+ ")"
 
 
 
 
 
 
 
 
 
 
 
 
 
740
  ]
741
  },
742
  {
743
  "cell_type": "code",
744
+ "execution_count": null,
745
  "id": "8e867b40",
746
  "metadata": {},
747
  "outputs": [
 
777
  }
778
  ],
779
  "source": [
780
+ "distil_collator = DataCollatorWithPadding(tokenizer=distil_tokenizer)\n",
781
  "\n",
782
  "distil_model = AutoModelForSequenceClassification.from_pretrained(\n",
783
  " DISTIL_MODEL,\n",
 
786
  "\n",
787
  "distil_model.to(device)\n",
788
  "\n",
789
+ "print(distil_model.__class__.__name__)"
790
  ]
791
  },
792
  {
793
  "cell_type": "code",
794
+ "execution_count": null,
795
  "id": "ed09755c",
796
  "metadata": {},
797
  "outputs": [
 
804
  }
805
  ],
806
  "source": [
 
807
  "distil_args = TrainingArguments(\n",
808
  "\n",
809
  " output_dir= PROJECT_ROOT / \"models\" / \"distilbert_results\",\n",
 
834
  " report_to=\"none\",\n",
835
  "\n",
836
  " seed=RAND,\n",
837
+ ")"
 
 
 
 
 
 
 
 
 
 
 
 
 
838
  ]
839
  },
840
  {
841
  "cell_type": "code",
842
+ "execution_count": null,
843
  "id": "827eed3d",
844
  "metadata": {},
845
  "outputs": [],
846
  "source": [
847
+ "distil_early_stopping = EarlyStoppingCallback(early_stopping_patience=2)\n",
848
  "\n",
849
  "distil_trainer = Trainer(\n",
850
  "\n",
 
862
  " compute_metrics=compute_metrics,\n",
863
  "\n",
864
  " callbacks=[distil_early_stopping],\n",
865
+ ")"
866
  ]
867
  },
868
  {
869
  "cell_type": "code",
870
+ "execution_count": null,
871
  "id": "eea8b514",
872
  "metadata": {},
873
  "outputs": [
 
964
  }
965
  ],
966
  "source": [
 
967
  "print(\"=\" * 50)\n",
968
  "print(\"TRAINING DISTILBERT\")\n",
969
  "print(\"=\" * 50)\n",
970
  "\n",
971
+ "distil_trainer.train()"
972
  ]
973
  },
974
  {
 
981
  },
982
  {
983
  "cell_type": "code",
984
+ "execution_count": null,
985
  "id": "9647c651",
986
  "metadata": {},
987
  "outputs": [
 
1025
  }
1026
  ],
1027
  "source": [
 
1028
  "distil_results = evaluate_model(\n",
1029
  " distil_trainer,\n",
1030
  " distil_test,\n",
1031
  " y_test,\n",
1032
  " \"DistilBERT\",\n",
1033
+ ")"
1034
  ]
1035
  },
1036
  {
 
1052
  },
1053
  {
1054
  "cell_type": "code",
1055
+ "execution_count": null,
1056
  "id": "40cbdf8d",
1057
  "metadata": {},
1058
  "outputs": [
 
1065
  }
1066
  ],
1067
  "source": [
 
1068
  "HATE_MODEL = \"cardiffnlp/twitter-roberta-base-hate\"\n",
1069
  "\n",
1070
  "MAX_LEN = 128\n",
 
1072
  "EPOCHS = 3\n",
1073
  "LR = 2e-5\n",
1074
  "\n",
1075
+ "print(HATE_MODEL)"
1076
  ]
1077
  },
1078
  {
1079
  "cell_type": "code",
1080
+ "execution_count": null,
1081
  "id": "6d0d9245",
1082
  "metadata": {},
1083
  "outputs": [
 
1092
  }
1093
  ],
1094
  "source": [
 
1095
  "hate_tokenizer = AutoTokenizer.from_pretrained(\n",
1096
  " HATE_MODEL\n",
1097
  ")\n",
 
1112
  " hf_test_raw,\n",
1113
  " hate_tokenizer,\n",
1114
  " MAX_LEN,\n",
1115
+ ")"
 
 
 
 
 
 
 
 
 
 
 
 
 
1116
  ]
1117
  },
1118
  {
1119
  "cell_type": "code",
1120
+ "execution_count": null,
1121
  "id": "5114bb65",
1122
  "metadata": {},
1123
  "outputs": [
 
1137
  }
1138
  ],
1139
  "source": [
1140
+ "hate_collator = DataCollatorWithPadding(tokenizer=hate_tokenizer)\n",
1141
  "\n",
1142
  "hate_model = AutoModelForSequenceClassification.from_pretrained(\n",
1143
  " HATE_MODEL,\n",
 
1147
  "\n",
1148
  "hate_model.to(device)\n",
1149
  "\n",
1150
+ "print(hate_model.__class__.__name__)"
1151
  ]
1152
  },
1153
  {
 
1160
  },
1161
  {
1162
  "cell_type": "code",
1163
+ "execution_count": null,
1164
  "id": "c3729fb1",
1165
  "metadata": {},
1166
  "outputs": [
 
1177
  }
1178
  ],
1179
  "source": [
 
 
1180
  "# Congelar backbone\n",
 
1181
  "\n",
1182
  "for param in hate_model.base_model.parameters():\n",
1183
  " param.requires_grad = False\n",
1184
  "\n",
 
1185
  "# Classification head entrenable\n",
 
 
1186
  "classifier_found = False\n",
1187
  "\n",
1188
  "for name, param in hate_model.named_parameters():\n",
 
1197
  "else:\n",
1198
  " print(\"⚠️ No se encontró classifier head\")\n",
1199
  "\n",
 
1200
  "# Verificación\n",
 
 
1201
  "total_params = 0\n",
1202
  "trainable_params = 0\n",
1203
  "\n",
 
1213
  "print()\n",
1214
  "print(f\"Trainable params: {trainable_params:,}\")\n",
1215
  "print(f\"Total params: {total_params:,}\")\n",
1216
+ "print(f\"Trainable %: {pct:.2f}%\")"
1217
  ]
1218
  },
1219
  {
1220
  "cell_type": "code",
1221
+ "execution_count": null,
1222
  "id": "7a0a4821",
1223
  "metadata": {},
1224
  "outputs": [
 
1231
  }
1232
  ],
1233
  "source": [
 
1234
  "hate_args = TrainingArguments(\n",
1235
  "\n",
1236
  " output_dir= PROJECT_ROOT / \"models\" / \"roberta_hate_results\",\n",
 
1261
  " report_to=\"none\",\n",
1262
  "\n",
1263
  " seed=RAND,\n",
1264
+ ")"
 
 
 
 
 
 
 
 
 
 
 
 
 
1265
  ]
1266
  },
1267
  {
1268
  "cell_type": "code",
1269
+ "execution_count": null,
1270
  "id": "6a3acd3d",
1271
  "metadata": {},
1272
  "outputs": [],
1273
  "source": [
1274
+ "hate_early_stopping = EarlyStoppingCallback(early_stopping_patience=2)\n",
1275
  "\n",
1276
  "hate_trainer = Trainer(\n",
1277
  "\n",
 
1289
  " compute_metrics=compute_metrics,\n",
1290
  "\n",
1291
  " callbacks=[hate_early_stopping],\n",
1292
+ ")"
1293
  ]
1294
  },
1295
  {
1296
  "cell_type": "code",
1297
+ "execution_count": null,
1298
  "id": "a5752cbb",
1299
  "metadata": {},
1300
  "outputs": [
 
1391
  }
1392
  ],
1393
  "source": [
 
1394
  "print(\"=\" * 50)\n",
1395
  "print(\"TRAINING ROBERTA HATE\")\n",
1396
  "print(\"=\" * 50)\n",
1397
  "\n",
1398
+ "hate_trainer.train()"
1399
  ]
1400
  },
1401
  {
 
1408
  },
1409
  {
1410
  "cell_type": "code",
1411
+ "execution_count": null,
1412
  "id": "d58a64a5",
1413
  "metadata": {},
1414
  "outputs": [
 
1452
  }
1453
  ],
1454
  "source": [
 
1455
  "hate_results = evaluate_model(\n",
1456
  " hate_trainer,\n",
1457
  " hate_test,\n",
1458
  " y_test,\n",
1459
  " \"RoBERTa Hate\",\n",
1460
+ ")"
1461
  ]
1462
  },
1463
  {
 
1470
  },
1471
  {
1472
  "cell_type": "code",
1473
+ "execution_count": null,
1474
  "id": "d7f2b05f",
1475
  "metadata": {},
1476
  "outputs": [
 
1538
  }
1539
  ],
1540
  "source": [
 
1541
  "comparison_df = pd.DataFrame({\n",
1542
  "\n",
1543
  " \"Model\": [\n",
 
1571
  " ],\n",
1572
  "})\n",
1573
  "\n",
1574
+ "comparison_df"
1575
  ]
1576
  },
1577
  {
1578
  "cell_type": "code",
1579
+ "execution_count": null,
1580
  "id": "2c757b76",
1581
  "metadata": {},
1582
  "outputs": [
 
1592
  }
1593
  ],
1594
  "source": [
 
1595
  "plt.figure(figsize=(8, 5))\n",
1596
  "\n",
1597
  "sns.barplot(\n",
 
1601
  ")\n",
1602
  "\n",
1603
  "plt.title(\"Comparación F1 Score\")\n",
1604
+ "plt.show()"
 
1605
  ]
1606
  },
1607
  {
 
1614
  },
1615
  {
1616
  "cell_type": "code",
1617
+ "execution_count": null,
1618
  "id": "0f32e457",
1619
  "metadata": {},
1620
  "outputs": [
 
1627
  }
1628
  ],
1629
  "source": [
 
1630
  "if hate_results[\"f1\"] >= distil_results[\"f1\"]:\n",
1631
  "\n",
1632
  " best_name = \"RoBERTa Hate\"\n",
 
1633
  " best_preds = hate_results[\"preds\"]\n",
 
1634
  " best_probs = hate_results[\"probs\"]\n",
1635
  "\n",
1636
  "else:\n",
1637
  "\n",
1638
  " best_name = \"DistilBERT\"\n",
 
1639
  " best_preds = distil_results[\"preds\"]\n",
 
1640
  " best_probs = distil_results[\"probs\"]\n",
1641
  "\n",
1642
+ "print(\"Best model:\", best_name)"
1643
  ]
1644
  },
1645
  {
1646
  "cell_type": "code",
1647
+ "execution_count": null,
1648
  "id": "1d7148ce",
1649
  "metadata": {},
1650
  "outputs": [
 
1750
  }
1751
  ],
1752
  "source": [
 
1753
  "error_df = pd.DataFrame({\n",
1754
  "\n",
1755
  " \"text\": X_test.values,\n",
 
1756
  " \"real\": y_test.values,\n",
 
1757
  " \"pred\": best_preds,\n",
 
1758
  " \"prob_toxic\": best_probs,\n",
1759
  "})\n",
1760
  "\n",
 
1763
  ")\n",
1764
  "\n",
1765
  "print(\"Errores:\", error_df[\"is_error\"].sum())\n",
1766
+ "error_df.head()"
 
1767
  ]
1768
  },
1769
  {
1770
  "cell_type": "code",
1771
+ "execution_count": null,
1772
  "id": "64457e22",
1773
  "metadata": {},
1774
  "outputs": [
 
1827
  }
1828
  ],
1829
  "source": [
1830
+ "false_negatives = error_df[(error_df[\"real\"] == 1) & (error_df[\"pred\"] == 0)]\n",
 
 
 
 
 
1831
  "\n",
1832
  "print(\"=\" * 80)\n",
1833
  "print(\"FALSE NEGATIVES\")\n",
 
1836
  "for idx, row in false_negatives.head(10).iterrows():\n",
1837
  "\n",
1838
  " print(\"\\nProb toxicidad:\", round(row[\"prob_toxic\"], 4))\n",
 
1839
  " print(\"-\" * 60)\n",
 
1840
  " print(row[\"text\"])\n"
1841
  ]
1842
  },
 
1850
  },
1851
  {
1852
  "cell_type": "code",
1853
+ "execution_count": null,
1854
  "id": "8cc4001e",
1855
  "metadata": {},
1856
  "outputs": [
 
1877
  }
1878
  ],
1879
  "source": [
 
1880
  "SAVE_DIR = PROJECT_ROOT / \"models\"\n",
1881
  "\n",
1882
  "if best_name == \"RoBERTa Hate\":\n",
 
1894
  " save_path = SAVE_DIR / \"best_distilbert\"\n",
1895
  "\n",
1896
  "final_model.save_model(save_path)\n",
 
1897
  "final_tokenizer.save_pretrained(save_path)\n",
1898
+ "print(\"Modelo guardado en:\", save_path)"
 
1899
  ]
1900
  }
1901
  ],