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": {
|
|
@@ -44,13 +44,25 @@
|
|
| 44 |
},
|
| 45 |
{
|
| 46 |
"cell_type": "code",
|
| 47 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
"metadata": {
|
| 49 |
"colab": {
|
| 50 |
"base_uri": "https://localhost:8080/"
|
| 51 |
},
|
| 52 |
"id": "IGGkAlCRNlro",
|
| 53 |
-
"outputId": "
|
| 54 |
},
|
| 55 |
"outputs": [
|
| 56 |
{
|
|
@@ -69,6 +81,9 @@
|
|
| 69 |
"from torchvision import datasets, transforms, models\n",
|
| 70 |
"from torch.utils.data import DataLoader\n",
|
| 71 |
"import os\n",
|
|
|
|
|
|
|
|
|
|
| 72 |
"import numpy as np\n",
|
| 73 |
"\n",
|
| 74 |
"# Configure the device\n",
|
|
@@ -122,9 +137,9 @@
|
|
| 122 |
"base_uri": "https://localhost:8080/"
|
| 123 |
},
|
| 124 |
"id": "ikceEotXOU_1",
|
| 125 |
-
"outputId": "
|
| 126 |
},
|
| 127 |
-
"execution_count":
|
| 128 |
"outputs": [
|
| 129 |
{
|
| 130 |
"output_type": "stream",
|
|
@@ -135,11 +150,145 @@
|
|
| 135 |
}
|
| 136 |
]
|
| 137 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
{
|
| 139 |
"cell_type": "code",
|
| 140 |
"source": [],
|
| 141 |
"metadata": {
|
| 142 |
-
"id": "
|
| 143 |
},
|
| 144 |
"execution_count": null,
|
| 145 |
"outputs": []
|
|
|
|
| 5 |
"colab": {
|
| 6 |
"provenance": [],
|
| 7 |
"gpuType": "T4",
|
| 8 |
+
"authorship_tag": "ABX9TyMscbUSeB2qw1Lue5xsmWqV",
|
| 9 |
"include_colab_link": true
|
| 10 |
},
|
| 11 |
"kernelspec": {
|
|
|
|
| 44 |
},
|
| 45 |
{
|
| 46 |
"cell_type": "code",
|
| 47 |
+
"source": [
|
| 48 |
+
"%%capture install_output\n",
|
| 49 |
+
"!pip install optuna"
|
| 50 |
+
],
|
| 51 |
+
"metadata": {
|
| 52 |
+
"id": "OwBert5NpQwF"
|
| 53 |
+
},
|
| 54 |
+
"execution_count": 5,
|
| 55 |
+
"outputs": []
|
| 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 |
{
|
|
|
|
| 81 |
"from torchvision import datasets, transforms, models\n",
|
| 82 |
"from torch.utils.data import DataLoader\n",
|
| 83 |
"import os\n",
|
| 84 |
+
"import optuna\n",
|
| 85 |
+
"import time\n",
|
| 86 |
+
"import copy\n",
|
| 87 |
"import numpy as np\n",
|
| 88 |
"\n",
|
| 89 |
"# Configure the device\n",
|
|
|
|
| 137 |
"base_uri": "https://localhost:8080/"
|
| 138 |
},
|
| 139 |
"id": "ikceEotXOU_1",
|
| 140 |
+
"outputId": "077395cc-6cf0-42f5-e0b3-65a1e322b5a8"
|
| 141 |
},
|
| 142 |
+
"execution_count": 2,
|
| 143 |
"outputs": [
|
| 144 |
{
|
| 145 |
"output_type": "stream",
|
|
|
|
| 150 |
}
|
| 151 |
]
|
| 152 |
},
|
| 153 |
+
{
|
| 154 |
+
"cell_type": "markdown",
|
| 155 |
+
"source": [
|
| 156 |
+
"## **Initializing Datasets and DataLoaders**\n",
|
| 157 |
+
"\n",
|
| 158 |
+
"With our transforms defined, we will use PyTorch's `ImageFolder` to automatically read our directory structure and assign the correct labels (0 for Normal, 1 for Pneumonia).\n",
|
| 159 |
+
"\n",
|
| 160 |
+
"We will then wrap these datasets in `DataLoaders`. DataLoaders handle the heavy lifting of batching the images, shuffling them, and pushing them to the GPU. We are setting the `batch_size` to 64 to fully utilize the Google Colab GPU memory."
|
| 161 |
+
],
|
| 162 |
+
"metadata": {
|
| 163 |
+
"id": "odyLrQARmmn-"
|
| 164 |
+
}
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"cell_type": "code",
|
| 168 |
+
"source": [
|
| 169 |
+
"# 1. Create the Dataset objects\n",
|
| 170 |
+
"train_dataset = datasets.ImageFolder(train_dir, transform=train_transforms)\n",
|
| 171 |
+
"val_dataset = datasets.ImageFolder(val_dir, transform=eval_transforms)\n",
|
| 172 |
+
"test_dataset = datasets.ImageFolder(test_dir, transform=eval_transforms)\n",
|
| 173 |
+
"\n",
|
| 174 |
+
"print(f\"Training images: {len(train_dataset)}\")\n",
|
| 175 |
+
"print(f\"Validation images: {len(val_dataset)}\")\n",
|
| 176 |
+
"print(f\"Testing images: {len(test_dataset)}\")\n",
|
| 177 |
+
"print(f\"Detected Classes: {train_dataset.classes}\")\n",
|
| 178 |
+
"\n",
|
| 179 |
+
"# 2. Create the DataLoaders\n",
|
| 180 |
+
"BATCH_SIZE = 64\n",
|
| 181 |
+
"\n",
|
| 182 |
+
"# We shuffle the training data so the model doesn't learn sequence patterns\n",
|
| 183 |
+
"train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, pin_memory=True)\n",
|
| 184 |
+
"\n",
|
| 185 |
+
"# We do not need to shuffle validation/testing data\n",
|
| 186 |
+
"val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\n",
|
| 187 |
+
"test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\n",
|
| 188 |
+
"\n",
|
| 189 |
+
"print(\"DataLoaders are ready!\")"
|
| 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": [
|
| 200 |
+
{
|
| 201 |
+
"output_type": "stream",
|
| 202 |
+
"name": "stdout",
|
| 203 |
+
"text": [
|
| 204 |
+
"Training images: 5216\n",
|
| 205 |
+
"Validation images: 16\n",
|
| 206 |
+
"Testing images: 624\n",
|
| 207 |
+
"Detected Classes: ['NORMAL', 'PNEUMONIA']\n",
|
| 208 |
+
"DataLoaders are ready!\n"
|
| 209 |
+
]
|
| 210 |
+
}
|
| 211 |
+
]
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"cell_type": "markdown",
|
| 215 |
+
"source": [
|
| 216 |
+
"## **Model Architecture and Class Weighting**\n",
|
| 217 |
+
"\n",
|
| 218 |
+
"Instead of training a model from scratch, we use **Transfer Learning**. We will download DenseNet121 (which already knows how to detect edges, textures, and shapes from millions of standard images) and replace its final classification layer with a custom one designed specifically for our two classes (Normal vs. Pneumonia).\n",
|
| 219 |
+
"\n",
|
| 220 |
+
"Simultaneously, we will calculate the inverse frequency of our classes to generate **Class Weights**, injecting them directly into the PyTorch loss function to penalize the model more heavily if it misclassifies a minority \"Normal\" scan."
|
| 221 |
+
],
|
| 222 |
+
"metadata": {
|
| 223 |
+
"id": "MPPAHhh2nlVz"
|
| 224 |
+
}
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"cell_type": "code",
|
| 228 |
+
"source": [
|
| 229 |
+
"# --- 1. Handling the Imbalance (Class Weights) ---\n",
|
| 230 |
+
"# ImageFolder automatically stores the label for every image in the .targets attribute\n",
|
| 231 |
+
"labels = train_dataset.targets\n",
|
| 232 |
+
"class_counts = np.bincount(labels)\n",
|
| 233 |
+
"total_samples = len(labels)\n",
|
| 234 |
+
"num_classes = len(train_dataset.classes)\n",
|
| 235 |
+
"\n",
|
| 236 |
+
"# Inverse frequency formula: Weight = Total / (NumClasses * ClassCount)\n",
|
| 237 |
+
"weights = total_samples / (num_classes * class_counts)\n",
|
| 238 |
+
"class_weights = torch.FloatTensor(weights).to(device)\n",
|
| 239 |
+
"\n",
|
| 240 |
+
"print(f\"Class Counts: NORMAL={class_counts[0]}, PNEUMONIA={class_counts[1]}\")\n",
|
| 241 |
+
"print(f\"Applied Weights: NORMAL={weights[0]:.4f}, PNEUMONIA={weights[1]:.4f}\\n\")\n",
|
| 242 |
+
"\n",
|
| 243 |
+
"# --- 2. Model Architecture (DenseNet121) ---\n",
|
| 244 |
+
"# Download the pre-trained neural network\n",
|
| 245 |
+
"model = models.densenet121(weights=models.DenseNet121_Weights.IMAGENET1K_V1)\n",
|
| 246 |
+
"\n",
|
| 247 |
+
"# Freeze the core convolutional layers so we don't destroy the pre-trained visual features\n",
|
| 248 |
+
"for param in model.parameters():\n",
|
| 249 |
+
" param.requires_grad = False\n",
|
| 250 |
+
"\n",
|
| 251 |
+
"# Replace the final classification layer (originally 1000 classes) with our custom 2-class head\n",
|
| 252 |
+
"num_ftrs = model.classifier.in_features\n",
|
| 253 |
+
"\n",
|
| 254 |
+
"# Push the entire model architecture to the GPU\n",
|
| 255 |
+
"model = model.to(device)\n",
|
| 256 |
+
"\n",
|
| 257 |
+
"# --- 3. Loss Function and Optimizer ---\n",
|
| 258 |
+
"# Inject our calculated class weights into the loss function\n",
|
| 259 |
+
"criterion = nn.CrossEntropyLoss(weight=class_weights)\n",
|
| 260 |
+
"\n",
|
| 261 |
+
"# We only want the optimizer to update our newly added classifier layers\n",
|
| 262 |
+
"optimizer = optim.Adam(model.classifier.parameters(), lr=0.001)\n",
|
| 263 |
+
"\n",
|
| 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": [
|
| 275 |
+
{
|
| 276 |
+
"output_type": "stream",
|
| 277 |
+
"name": "stdout",
|
| 278 |
+
"text": [
|
| 279 |
+
"Class Counts: NORMAL=1341, PNEUMONIA=3875\n",
|
| 280 |
+
"Applied Weights: NORMAL=1.9448, PNEUMONIA=0.6730\n",
|
| 281 |
+
"\n",
|
| 282 |
+
"Model architecture, optimizer, and weighted loss function configured successfully!\n"
|
| 283 |
+
]
|
| 284 |
+
}
|
| 285 |
+
]
|
| 286 |
+
},
|
| 287 |
{
|
| 288 |
"cell_type": "code",
|
| 289 |
"source": [],
|
| 290 |
"metadata": {
|
| 291 |
+
"id": "HrV6nUsjodCM"
|
| 292 |
},
|
| 293 |
"execution_count": null,
|
| 294 |
"outputs": []
|