mobadara commited on
Commit
e446f59
·
1 Parent(s): 1efb6ba

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": "ABX9TyMscbUSeB2qw1Lue5xsmWqV",
9
  "include_colab_link": true
10
  },
11
  "kernelspec": {
@@ -56,13 +56,13 @@
56
  },
57
  {
58
  "cell_type": "code",
59
- "execution_count": 6,
60
  "metadata": {
61
  "colab": {
62
  "base_uri": "https://localhost:8080/"
63
  },
64
  "id": "IGGkAlCRNlro",
65
- "outputId": "778acab3-e0b5-4c40-a995-ded91103b0a4"
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": "HrV6nUsjodCM"
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": []