Spaces:
Sleeping
Sleeping
Created using Colab
Browse files
notebooks/01_model_training_and_fine_tuning.ipynb
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
"colab": {
|
| 6 |
"provenance": [],
|
| 7 |
"gpuType": "T4",
|
| 8 |
-
"authorship_tag": "
|
| 9 |
"include_colab_link": true
|
| 10 |
},
|
| 11 |
"kernelspec": {
|
|
@@ -56,13 +56,13 @@
|
|
| 56 |
},
|
| 57 |
{
|
| 58 |
"cell_type": "code",
|
| 59 |
-
"execution_count":
|
| 60 |
"metadata": {
|
| 61 |
"colab": {
|
| 62 |
"base_uri": "https://localhost:8080/"
|
| 63 |
},
|
| 64 |
"id": "IGGkAlCRNlro",
|
| 65 |
-
"outputId": "
|
| 66 |
},
|
| 67 |
"outputs": [
|
| 68 |
{
|
|
@@ -85,6 +85,10 @@
|
|
| 85 |
"import time\n",
|
| 86 |
"import copy\n",
|
| 87 |
"import numpy as np\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
"\n",
|
| 89 |
"# Configure the device\n",
|
| 90 |
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
|
|
@@ -190,10 +194,10 @@
|
|
| 190 |
],
|
| 191 |
"metadata": {
|
| 192 |
"id": "I6obJOa1QYMy",
|
| 193 |
-
"outputId": "3a3b35dc-01e5-4980-bdfc-0958467a5f24",
|
| 194 |
"colab": {
|
| 195 |
"base_uri": "https://localhost:8080/"
|
| 196 |
-
}
|
|
|
|
| 197 |
},
|
| 198 |
"execution_count": 3,
|
| 199 |
"outputs": [
|
|
@@ -264,11 +268,11 @@
|
|
| 264 |
"print(\"Model architecture, optimizer, and weighted loss function configured successfully!\")"
|
| 265 |
],
|
| 266 |
"metadata": {
|
| 267 |
-
"id": "3cTqFqcvnVG2",
|
| 268 |
-
"outputId": "19533754-f990-4485-b848-d9c2be3f782b",
|
| 269 |
"colab": {
|
| 270 |
"base_uri": "https://localhost:8080/"
|
| 271 |
-
}
|
|
|
|
|
|
|
| 272 |
},
|
| 273 |
"execution_count": 7,
|
| 274 |
"outputs": [
|
|
@@ -284,11 +288,324 @@
|
|
| 284 |
}
|
| 285 |
]
|
| 286 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
{
|
| 288 |
"cell_type": "code",
|
| 289 |
"source": [],
|
| 290 |
"metadata": {
|
| 291 |
-
"id": "
|
| 292 |
},
|
| 293 |
"execution_count": null,
|
| 294 |
"outputs": []
|
|
|
|
| 5 |
"colab": {
|
| 6 |
"provenance": [],
|
| 7 |
"gpuType": "T4",
|
| 8 |
+
"authorship_tag": "ABX9TyPSiHqZC3XJwRUnsyWT6QPo",
|
| 9 |
"include_colab_link": true
|
| 10 |
},
|
| 11 |
"kernelspec": {
|
|
|
|
| 56 |
},
|
| 57 |
{
|
| 58 |
"cell_type": "code",
|
| 59 |
+
"execution_count": 9,
|
| 60 |
"metadata": {
|
| 61 |
"colab": {
|
| 62 |
"base_uri": "https://localhost:8080/"
|
| 63 |
},
|
| 64 |
"id": "IGGkAlCRNlro",
|
| 65 |
+
"outputId": "69681eec-4dca-43fc-f0c3-489d8d514d71"
|
| 66 |
},
|
| 67 |
"outputs": [
|
| 68 |
{
|
|
|
|
| 85 |
"import time\n",
|
| 86 |
"import copy\n",
|
| 87 |
"import numpy as np\n",
|
| 88 |
+
"import torch.nn.functional as F\n",
|
| 89 |
+
"from sklearn.metrics import classification_report, confusion_matrix\n",
|
| 90 |
+
"import matplotlib.pyplot as plt\n",
|
| 91 |
+
"import seaborn as sns\n",
|
| 92 |
"\n",
|
| 93 |
"# Configure the device\n",
|
| 94 |
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
|
|
|
|
| 194 |
],
|
| 195 |
"metadata": {
|
| 196 |
"id": "I6obJOa1QYMy",
|
|
|
|
| 197 |
"colab": {
|
| 198 |
"base_uri": "https://localhost:8080/"
|
| 199 |
+
},
|
| 200 |
+
"outputId": "3a3b35dc-01e5-4980-bdfc-0958467a5f24"
|
| 201 |
},
|
| 202 |
"execution_count": 3,
|
| 203 |
"outputs": [
|
|
|
|
| 268 |
"print(\"Model architecture, optimizer, and weighted loss function configured successfully!\")"
|
| 269 |
],
|
| 270 |
"metadata": {
|
|
|
|
|
|
|
| 271 |
"colab": {
|
| 272 |
"base_uri": "https://localhost:8080/"
|
| 273 |
+
},
|
| 274 |
+
"id": "3cTqFqcvnVG2",
|
| 275 |
+
"outputId": "19533754-f990-4485-b848-d9c2be3f782b"
|
| 276 |
},
|
| 277 |
"execution_count": 7,
|
| 278 |
"outputs": [
|
|
|
|
| 288 |
}
|
| 289 |
]
|
| 290 |
},
|
| 291 |
+
{
|
| 292 |
+
"cell_type": "markdown",
|
| 293 |
+
"source": [
|
| 294 |
+
"## **Hyperparameter Tuning with Optuna and Model Training**\n",
|
| 295 |
+
"\n",
|
| 296 |
+
"Instead of manually guessing the best hyperparameters, we use **Optuna** to perform an automated Bayesian search.\n",
|
| 297 |
+
"\n",
|
| 298 |
+
"We will wrap our standard PyTorch training loop inside an Optuna `objective` function. Optuna will test different combinations of:\n",
|
| 299 |
+
"1. **Learning Rates** (Logarithmic scale)\n",
|
| 300 |
+
"2. **Dropout Rates** (To find the sweet spot for preventing overfitting)\n",
|
| 301 |
+
"3. **Optimizers** (Adam vs. SGD)\n",
|
| 302 |
+
"\n",
|
| 303 |
+
"*Note: To manage Google Colab compute limits, we restrict the search to a small number of epochs and trials. Optuna's built-in \"Pruning\" will automatically kill unpromising trials early to save GPU time.*"
|
| 304 |
+
],
|
| 305 |
+
"metadata": {
|
| 306 |
+
"id": "31zYMNWrqGYj"
|
| 307 |
+
}
|
| 308 |
+
},
|
| 309 |
+
{
|
| 310 |
+
"cell_type": "code",
|
| 311 |
+
"source": [
|
| 312 |
+
"# We will store the best model weights globally so we can save them after the study\n",
|
| 313 |
+
"best_model_wts = copy.deepcopy(model.state_dict())\n",
|
| 314 |
+
"highest_accuracy = 0.0\n",
|
| 315 |
+
"\n",
|
| 316 |
+
"def objective(trial):\n",
|
| 317 |
+
" global best_model_wts, highest_accuracy\n",
|
| 318 |
+
"\n",
|
| 319 |
+
" # 1. Let Optuna suggest hyperparameters for this specific trial\n",
|
| 320 |
+
" dropout_rate = trial.suggest_float(\"dropout_rate\", 0.2, 0.6)\n",
|
| 321 |
+
" lr = trial.suggest_float(\"lr\", 1e-5, 1e-2, log=True)\n",
|
| 322 |
+
" optimizer_name = trial.suggest_categorical(\"optimizer\", [\"Adam\", \"SGD\"])\n",
|
| 323 |
+
"\n",
|
| 324 |
+
" # 2. Re-build the custom classifier with the suggested dropout rate\n",
|
| 325 |
+
" model.classifier = nn.Sequential(\n",
|
| 326 |
+
" nn.Linear(num_ftrs, 256),\n",
|
| 327 |
+
" nn.ReLU(),\n",
|
| 328 |
+
" nn.Dropout(dropout_rate),\n",
|
| 329 |
+
" nn.Linear(256, 2)\n",
|
| 330 |
+
" ).to(device)\n",
|
| 331 |
+
"\n",
|
| 332 |
+
" # 3. Assign the suggested optimizer\n",
|
| 333 |
+
" if optimizer_name == \"Adam\":\n",
|
| 334 |
+
" optimizer = optim.Adam(model.classifier.parameters(), lr=lr)\n",
|
| 335 |
+
" else:\n",
|
| 336 |
+
" optimizer = optim.SGD(model.classifier.parameters(), lr=lr, momentum=0.9)\n",
|
| 337 |
+
"\n",
|
| 338 |
+
" EPOCHS = 3 # Keep epochs low per trial for time constraints\n",
|
| 339 |
+
"\n",
|
| 340 |
+
" for epoch in range(EPOCHS):\n",
|
| 341 |
+
" # --- TRAINING PHASE ---\n",
|
| 342 |
+
" model.train()\n",
|
| 343 |
+
" running_loss = 0.0\n",
|
| 344 |
+
"\n",
|
| 345 |
+
" for inputs, labels in train_loader:\n",
|
| 346 |
+
" inputs, labels = inputs.to(device), labels.to(device)\n",
|
| 347 |
+
"\n",
|
| 348 |
+
" optimizer.zero_grad()\n",
|
| 349 |
+
" outputs = model(inputs)\n",
|
| 350 |
+
" loss = criterion(outputs, labels) # Uses the class_weights we defined earlier!\n",
|
| 351 |
+
" loss.backward()\n",
|
| 352 |
+
" optimizer.step()\n",
|
| 353 |
+
"\n",
|
| 354 |
+
" running_loss += loss.item() * inputs.size(0)\n",
|
| 355 |
+
"\n",
|
| 356 |
+
" # --- VALIDATION PHASE ---\n",
|
| 357 |
+
" model.eval()\n",
|
| 358 |
+
" correct = 0\n",
|
| 359 |
+
" total = 0\n",
|
| 360 |
+
"\n",
|
| 361 |
+
" with torch.no_grad():\n",
|
| 362 |
+
" for inputs, labels in val_loader:\n",
|
| 363 |
+
" inputs, labels = inputs.to(device), labels.to(device)\n",
|
| 364 |
+
" outputs = model(inputs)\n",
|
| 365 |
+
" _, predicted = torch.max(outputs, 1)\n",
|
| 366 |
+
" total += labels.size(0)\n",
|
| 367 |
+
" correct += (predicted == labels).sum().item()\n",
|
| 368 |
+
"\n",
|
| 369 |
+
" val_acc = correct / total\n",
|
| 370 |
+
"\n",
|
| 371 |
+
" # --- OPTUNA PRUNING & SAVING ---\n",
|
| 372 |
+
" # Report accuracy to Optuna. If it's performing terribly, Optuna kills the trial.\n",
|
| 373 |
+
" trial.report(val_acc, epoch)\n",
|
| 374 |
+
" if trial.should_prune():\n",
|
| 375 |
+
" raise optuna.exceptions.TrialPruned()\n",
|
| 376 |
+
"\n",
|
| 377 |
+
" # Save the best weights if this trial beats our global high score\n",
|
| 378 |
+
" if val_acc > highest_accuracy:\n",
|
| 379 |
+
" highest_accuracy = val_acc\n",
|
| 380 |
+
" best_model_wts = copy.deepcopy(model.state_dict())\n",
|
| 381 |
+
"\n",
|
| 382 |
+
" return val_acc # Optuna uses this final returned value to judge the trial\n",
|
| 383 |
+
"\n",
|
| 384 |
+
"# --- RUN THE OPTUNA STUDY ---\n",
|
| 385 |
+
"print(\"Starting Optuna Hyperparameter Search...\")\n",
|
| 386 |
+
"start_time = time.time()\n",
|
| 387 |
+
"\n",
|
| 388 |
+
"study = optuna.create_study(direction=\"maximize\")\n",
|
| 389 |
+
"# We run 5 trials. (Increase this if you have Colab Pro and more time)\n",
|
| 390 |
+
"study.optimize(objective, n_trials=5)\n",
|
| 391 |
+
"\n",
|
| 392 |
+
"time_elapsed = time.time() - start_time\n",
|
| 393 |
+
"print(f\"\\nOptimization complete in {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s\")\n",
|
| 394 |
+
"print(f\"Best Trial Accuracy: {study.best_trial.value:.4f}\")\n",
|
| 395 |
+
"print(\"Best Hyperparameters:\", study.best_trial.params)\n",
|
| 396 |
+
"\n",
|
| 397 |
+
"# Load the absolute best weights back into the model for final export\n",
|
| 398 |
+
"model.load_state_dict(best_model_wts)\n",
|
| 399 |
+
"print(\"Best model weights restored and ready for testing.\")"
|
| 400 |
+
],
|
| 401 |
+
"metadata": {
|
| 402 |
+
"colab": {
|
| 403 |
+
"base_uri": "https://localhost:8080/"
|
| 404 |
+
},
|
| 405 |
+
"id": "HrV6nUsjodCM",
|
| 406 |
+
"outputId": "ff77d837-8183-4f16-ea06-4979fd6e540a"
|
| 407 |
+
},
|
| 408 |
+
"execution_count": 8,
|
| 409 |
+
"outputs": [
|
| 410 |
+
{
|
| 411 |
+
"output_type": "stream",
|
| 412 |
+
"name": "stderr",
|
| 413 |
+
"text": [
|
| 414 |
+
"[I 2026-05-28 22:18:23,269] A new study created in memory with name: no-name-045ce5dc-ed4f-4a58-8341-754b15b1d97b\n"
|
| 415 |
+
]
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"output_type": "stream",
|
| 419 |
+
"name": "stdout",
|
| 420 |
+
"text": [
|
| 421 |
+
"Starting Optuna Hyperparameter Search...\n"
|
| 422 |
+
]
|
| 423 |
+
},
|
| 424 |
+
{
|
| 425 |
+
"output_type": "stream",
|
| 426 |
+
"name": "stderr",
|
| 427 |
+
"text": [
|
| 428 |
+
"[I 2026-05-28 22:22:44,758] Trial 0 finished with value: 0.625 and parameters: {'dropout_rate': 0.3761532058720902, 'lr': 1.417924247896451e-05, 'optimizer': 'SGD'}. Best is trial 0 with value: 0.625.\n",
|
| 429 |
+
"[I 2026-05-28 22:27:02,814] Trial 1 finished with value: 0.9375 and parameters: {'dropout_rate': 0.4806277944716644, 'lr': 0.007419660171950955, 'optimizer': 'SGD'}. Best is trial 1 with value: 0.9375.\n",
|
| 430 |
+
"[I 2026-05-28 22:31:19,752] Trial 2 finished with value: 0.875 and parameters: {'dropout_rate': 0.4749246311849121, 'lr': 0.002914222867247111, 'optimizer': 'SGD'}. Best is trial 1 with value: 0.9375.\n",
|
| 431 |
+
"[I 2026-05-28 22:35:36,219] Trial 3 finished with value: 0.9375 and parameters: {'dropout_rate': 0.5587573900041348, 'lr': 0.000938365213167857, 'optimizer': 'Adam'}. Best is trial 1 with value: 0.9375.\n",
|
| 432 |
+
"[I 2026-05-28 22:39:51,671] Trial 4 finished with value: 0.8125 and parameters: {'dropout_rate': 0.5357695028670346, 'lr': 8.813243604021709e-05, 'optimizer': 'SGD'}. Best is trial 1 with value: 0.9375.\n"
|
| 433 |
+
]
|
| 434 |
+
},
|
| 435 |
+
{
|
| 436 |
+
"output_type": "stream",
|
| 437 |
+
"name": "stdout",
|
| 438 |
+
"text": [
|
| 439 |
+
"\n",
|
| 440 |
+
"Optimization complete in 21m 28s\n",
|
| 441 |
+
"Best Trial Accuracy: 0.9375\n",
|
| 442 |
+
"Best Hyperparameters: {'dropout_rate': 0.4806277944716644, 'lr': 0.007419660171950955, 'optimizer': 'SGD'}\n",
|
| 443 |
+
"Best model weights restored and ready for testing.\n"
|
| 444 |
+
]
|
| 445 |
+
}
|
| 446 |
+
]
|
| 447 |
+
},
|
| 448 |
+
{
|
| 449 |
+
"cell_type": "markdown",
|
| 450 |
+
"source": [
|
| 451 |
+
"## **Final Evaluation and Model Export**\n",
|
| 452 |
+
"\n",
|
| 453 |
+
"With our Optuna-optimized weights loaded, we must evaluate the model against the **Test Dataset**. This dataset contains images the model has never seen during training or hyperparameter tuning, providing an honest assessment of how it will perform in the real world.\n",
|
| 454 |
+
"\n",
|
| 455 |
+
"Finally, we export the tuned architecture weights to a `.pth` file. This is the \"brain\" that our FastAPI backend will load to generate real-time Grad-CAM heatmaps."
|
| 456 |
+
],
|
| 457 |
+
"metadata": {
|
| 458 |
+
"id": "HgGk_69gybfq"
|
| 459 |
+
}
|
| 460 |
+
},
|
| 461 |
+
{
|
| 462 |
+
"cell_type": "code",
|
| 463 |
+
"source": [
|
| 464 |
+
"# --- 1. Evaluate on the Test Set ---\n",
|
| 465 |
+
"model.eval()\n",
|
| 466 |
+
"test_loss = 0.0\n",
|
| 467 |
+
"correct = 0\n",
|
| 468 |
+
"total = 0\n",
|
| 469 |
+
"all_preds = []\n",
|
| 470 |
+
"all_labels = []\n",
|
| 471 |
+
"\n",
|
| 472 |
+
"print(\"Evaluating on Test Dataset...\")\n",
|
| 473 |
+
"\n",
|
| 474 |
+
"with torch.no_grad():\n",
|
| 475 |
+
" for inputs, labels in test_loader:\n",
|
| 476 |
+
" inputs, labels = inputs.to(device), labels.to(device)\n",
|
| 477 |
+
"\n",
|
| 478 |
+
" outputs = model(inputs)\n",
|
| 479 |
+
" loss = criterion(outputs, labels)\n",
|
| 480 |
+
" test_loss += loss.item() * inputs.size(0)\n",
|
| 481 |
+
"\n",
|
| 482 |
+
" # Get predictions\n",
|
| 483 |
+
" probabilities = F.softmax(outputs, dim=1)\n",
|
| 484 |
+
" _, predicted = torch.max(probabilities, 1)\n",
|
| 485 |
+
"\n",
|
| 486 |
+
" total += labels.size(0)\n",
|
| 487 |
+
" correct += (predicted == labels).sum().item()\n",
|
| 488 |
+
"\n",
|
| 489 |
+
" # Store for metrics\n",
|
| 490 |
+
" all_preds.extend(predicted.cpu().numpy())\n",
|
| 491 |
+
" all_labels.extend(labels.cpu().numpy())\n",
|
| 492 |
+
"\n",
|
| 493 |
+
"test_accuracy = correct / total\n",
|
| 494 |
+
"test_loss = test_loss / total\n",
|
| 495 |
+
"\n",
|
| 496 |
+
"print(f\"Test Accuracy: {test_accuracy * 100:.2f}%\")\n",
|
| 497 |
+
"print(f\"Test Loss: {test_loss:.4f}\\n\")\n",
|
| 498 |
+
"\n",
|
| 499 |
+
"# --- 2. Classification Report & Confusion Matrix ---\n",
|
| 500 |
+
"target_names = train_dataset.classes\n",
|
| 501 |
+
"print(\"Classification Report:\")\n",
|
| 502 |
+
"print(classification_report(all_labels, all_preds, target_names=target_names))\n",
|
| 503 |
+
"\n",
|
| 504 |
+
"# Plotting the Confusion Matrix\n",
|
| 505 |
+
"cm = confusion_matrix(all_labels, all_preds)\n",
|
| 506 |
+
"plt.figure(figsize=(6, 5))\n",
|
| 507 |
+
"sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=target_names, yticklabels=target_names)\n",
|
| 508 |
+
"plt.title('Test Dataset Confusion Matrix')\n",
|
| 509 |
+
"plt.ylabel('Actual Diagnosis')\n",
|
| 510 |
+
"plt.xlabel('Predicted Diagnosis')\n",
|
| 511 |
+
"plt.show()\n",
|
| 512 |
+
"\n",
|
| 513 |
+
"# --- 3. Export the Model Weights ---\n",
|
| 514 |
+
"export_path = \"densenet_pneumonia_finetuned.pth\"\n",
|
| 515 |
+
"torch.save(model.state_dict(), export_path)\n",
|
| 516 |
+
"print(f\"\\n✅ Model weights successfully exported to: {export_path}\")"
|
| 517 |
+
],
|
| 518 |
+
"metadata": {
|
| 519 |
+
"id": "Wr-7CrqRqs4w",
|
| 520 |
+
"outputId": "486eaf48-3dd2-452c-a486-da7f2b8317ef",
|
| 521 |
+
"colab": {
|
| 522 |
+
"base_uri": "https://localhost:8080/",
|
| 523 |
+
"height": 773
|
| 524 |
+
}
|
| 525 |
+
},
|
| 526 |
+
"execution_count": 10,
|
| 527 |
+
"outputs": [
|
| 528 |
+
{
|
| 529 |
+
"output_type": "stream",
|
| 530 |
+
"name": "stdout",
|
| 531 |
+
"text": [
|
| 532 |
+
"Evaluating on Test Dataset...\n",
|
| 533 |
+
"Test Accuracy: 86.38%\n",
|
| 534 |
+
"Test Loss: 0.3476\n",
|
| 535 |
+
"\n",
|
| 536 |
+
"Classification Report:\n",
|
| 537 |
+
" precision recall f1-score support\n",
|
| 538 |
+
"\n",
|
| 539 |
+
" NORMAL 0.89 0.73 0.80 234\n",
|
| 540 |
+
" PNEUMONIA 0.85 0.95 0.90 390\n",
|
| 541 |
+
"\n",
|
| 542 |
+
" accuracy 0.86 624\n",
|
| 543 |
+
" macro avg 0.87 0.84 0.85 624\n",
|
| 544 |
+
"weighted avg 0.87 0.86 0.86 624\n",
|
| 545 |
+
"\n"
|
| 546 |
+
]
|
| 547 |
+
},
|
| 548 |
+
{
|
| 549 |
+
"output_type": "display_data",
|
| 550 |
+
"data": {
|
| 551 |
+
"text/plain": [
|
| 552 |
+
"<Figure size 600x500 with 2 Axes>"
|
| 553 |
+
],
|
| 554 |
+
"image/png": "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\n"
|
| 555 |
+
},
|
| 556 |
+
"metadata": {}
|
| 557 |
+
},
|
| 558 |
+
{
|
| 559 |
+
"output_type": "stream",
|
| 560 |
+
"name": "stdout",
|
| 561 |
+
"text": [
|
| 562 |
+
"\n",
|
| 563 |
+
"✅ Model weights successfully exported to: densenet_pneumonia_finetuned.pth\n"
|
| 564 |
+
]
|
| 565 |
+
}
|
| 566 |
+
]
|
| 567 |
+
},
|
| 568 |
+
{
|
| 569 |
+
"cell_type": "markdown",
|
| 570 |
+
"source": [
|
| 571 |
+
"## **Clinical Assessment and Conclusion**\n",
|
| 572 |
+
"\n",
|
| 573 |
+
"Our fine-tuned DenseNet121 architecture has been evaluated on the completely unseen Test Dataset. To understand its true clinical viability, we must look past raw accuracy and analyze the Confusion Matrix.\n",
|
| 574 |
+
"\n",
|
| 575 |
+
"### **Clinical Metrics Breakdown**\n",
|
| 576 |
+
"* **True Negatives (170):** Healthy lungs correctly identified.\n",
|
| 577 |
+
"* **True Positives (369):** Pneumonia cases successfully detected.\n",
|
| 578 |
+
"* **False Positives (64):** Healthy lungs flagged as potential pneumonia (Type I Error).\n",
|
| 579 |
+
"* **False Negatives (21):** Actual pneumonia cases missed by the model (Type II Error).\n",
|
| 580 |
+
"\n",
|
| 581 |
+
"### **The \"Recall\" Victory**\n",
|
| 582 |
+
"In precision diagnostics, minimizing False Negatives is the absolute highest priority. Sending a sick patient home without treatment is a severe failure.\n",
|
| 583 |
+
"\n",
|
| 584 |
+
"Our model achieved a **Pneumonia Recall (Sensitivity) of 94.6%** ($369 / 390$). Out of 390 sick patients, the model only missed 21. The higher number of False Positives (64) is an acceptable clinical trade-off, acting as a cautious safety net that simply prompts a human physician for a secondary review. This proves that our strategy of applying **Class Weights** during training successfully forced the model to prioritize pathological detection.\n",
|
| 585 |
+
"\n",
|
| 586 |
+
"---\n",
|
| 587 |
+
"**Next Phase: Building the Inference Engine**\n",
|
| 588 |
+
"With the `densenet_pneumonia_finetuned.pth` weights successfully exported, the core machine learning phase is complete. We will now transition out of the Jupyter environment and begin building the production MLOps pipeline. The next step is to construct a **FastAPI backend** that will load this \"brain,\" process incoming X-ray uploads in real-time, and generate **Grad-CAM explainability heatmaps** to show the doctors exactly *where* the model is looking."
|
| 589 |
+
],
|
| 590 |
+
"metadata": {
|
| 591 |
+
"id": "DWEkHU0AzaZJ"
|
| 592 |
+
}
|
| 593 |
+
},
|
| 594 |
+
{
|
| 595 |
+
"cell_type": "markdown",
|
| 596 |
+
"source": [
|
| 597 |
+
"___\n",
|
| 598 |
+
"Muyiwa J. Obadara"
|
| 599 |
+
],
|
| 600 |
+
"metadata": {
|
| 601 |
+
"id": "-6LkhH6WzlIF"
|
| 602 |
+
}
|
| 603 |
+
},
|
| 604 |
{
|
| 605 |
"cell_type": "code",
|
| 606 |
"source": [],
|
| 607 |
"metadata": {
|
| 608 |
+
"id": "D5I3Ujm6yyOh"
|
| 609 |
},
|
| 610 |
"execution_count": null,
|
| 611 |
"outputs": []
|