{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":10338,"databundleVersionId":862042,"isSourceIdPinned":false},{"sourceType":"datasetVersion","sourceId":2047221,"datasetId":1226038,"databundleVersionId":2087216},{"sourceType":"datasetVersion","sourceId":1432479,"datasetId":839140,"databundleVersionId":1465871},{"sourceType":"datasetVersion","sourceId":1494905,"datasetId":724418,"databundleVersionId":1528912},{"sourceType":"datasetVersion","sourceId":7079240,"datasetId":4077867,"databundleVersionId":7166905},{"sourceType":"datasetVersion","sourceId":1426603,"datasetId":835414,"databundleVersionId":1459942},{"sourceType":"datasetVersion","sourceId":23812,"datasetId":17810,"databundleVersionId":23851},{"sourceType":"datasetVersion","sourceId":951996,"datasetId":516716,"databundleVersionId":979875},{"sourceType":"datasetVersion","sourceId":1022626,"datasetId":562468,"databundleVersionId":1051543},{"sourceType":"datasetVersion","sourceId":1157383,"datasetId":548681,"databundleVersionId":1188252},{"sourceType":"datasetVersion","sourceId":477177,"datasetId":216167,"databundleVersionId":493143},{"sourceType":"datasetVersion","sourceId":18613,"datasetId":5839,"databundleVersionId":18613},{"sourceType":"datasetVersion","sourceId":1019494,"datasetId":560711,"databundleVersionId":1048360},{"sourceType":"datasetVersion","sourceId":482,"datasetId":228,"databundleVersionId":482},{"sourceType":"datasetVersion","sourceId":1166777,"datasetId":661308,"databundleVersionId":1197728},{"sourceType":"datasetVersion","sourceId":20797,"datasetId":15700,"databundleVersionId":20797},{"sourceType":"datasetVersion","sourceId":7773,"datasetId":4667,"databundleVersionId":7773},{"sourceType":"datasetVersion","sourceId":519715,"datasetId":246422,"databundleVersionId":535956},{"sourceType":"datasetVersion","sourceId":2332307,"datasetId":891819,"databundleVersionId":2373865}],"dockerImageVersionId":30698,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Import Libraries & Reading Dataset","metadata":{}},{"cell_type":"code","source":"import os, shutil\nimport random\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport skimage\nimport matplotlib.pyplot as plt\nimport skimage.segmentation\nimport seaborn as sns\n%matplotlib inline\nplt.style.use('ggplot')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = ['PNEUMONIA','NORMAL']\nimg_size = 128\ndef get_data(data_dir):\n    data=[]\n    for label in labels:\n#         train/PNEUMONIA\n        path = os.path.join(data_dir, label)\n        class_num = labels.index(label)\n        for img in os.listdir(path):\n            try:\n                img_arr = cv2.imread(os.path.join(path, img), cv2.IMREAD_GRAYSCALE)\n                resized_arr = cv2.resize(img_arr, (img_size, img_size))\n                data.append([resized_arr, class_num])\n            except Exception as e:\n                print(e)\n    return np.array(data)","metadata":{"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = get_data(\"chest_xray/chest_xray/train\")\ntest = get_data(\"chest_xray/chest_xray/test\")\nval = get_data(\"chest_xray/chest_xray/val\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pneumonia = os.listdir(\"chest_xray/train/PNEUMONIA\")\npenomina_dir = \"chest_xray/train/PNEUMONIA\"","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(20,10))\n\nfor i in range(9):\n    plt.subplot(3,3, i+1)\n    img = plt.imread(os.path.join(penomina_dir, pneumonia[i]))\n    plt.imshow(img, cmap='gray')\n    plt.axis(\"off\")\n    plt.title(\"Pneumonia X-ray\")\nplt.tight_layout()","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"normal = os.listdir(\"chest_xray/train/NORMAL\")\nnormal_dir = \"chest_xray/train/NORMAL\"","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(20,10))\n\nfor i in range(9):\n    plt.subplot(3,3, i+1)\n    img = plt.imread(os.path.join(normal_dir, normal[i]))\n    plt.imshow(img, cmap='gray')\n    plt.axis(\"off\")\n    plt.title(\"Normal X-ray\")\nplt.tight_layout()","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"listx = []\nfor i in train:\n    if(i[1] == 0):\n        listx.append(\"Pneumonia\")\n    else:\n        listx.append(\"Normal\")\nsns.countplot(listx)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Augmentation & Resizing","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader\nfrom torchvision import datasets, transforms, models","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_transforms = transforms.Compose([\n    transforms.Resize((128, 128)),\n    transforms.RandomHorizontalFlip(p=0.4),\n    transforms.RandomVerticalFlip(p=0.4),\n    transforms.RandomRotation(40),\n    transforms.RandomAffine(degrees=0, shear=11.5, translate=(0.4, 0.4)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\nvalid_transforms = transforms.Compose([\n    transforms.Resize((128, 128)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\ntest_transforms = transforms.Compose([\n    transforms.Resize((128, 128)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = datasets.ImageFolder(\"chest_xray/chest_xray/train\", transform=train_transforms)\nvalid_dataset = datasets.ImageFolder(\"chest_xray/chest_xray/val\",   transform=valid_transforms)\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True,  num_workers=2, pin_memory=True)\nvalid_loader = DataLoader(valid_dataset, batch_size=32, shuffle=True,  num_workers=2, pin_memory=True)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_labels = train_dataset.class_to_idx","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_labels","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_name = {v: k for k, v in class_labels.items()}","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_name","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# VGG19 CNN Architecture","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\n# Load VGG19 with ImageNet weights and freeze all feature layers\nbase_model = models.vgg19(weights=models.VGG19_Weights.IMAGENET1K_V1)\nfor param in base_model.parameters():\n    param.requires_grad = False\n\n# Replace the classifier head to match the original architecture\n# VGG19 features output shape with 128x128 input: 512 * 4 * 4 = 8192\nbase_model.avgpool = nn.AdaptiveAvgPool2d((4, 4))\nbase_model.classifier = nn.Sequential(\n    nn.Flatten(),\n    nn.Linear(512 * 4 * 4, 4608),\n    nn.ReLU(inplace=True),\n    nn.Dropout(0.2),\n    nn.Linear(4608, 1152),\n    nn.ReLU(inplace=True),\n    nn.Linear(1152, 2)\n)\n\nmodel_01 = base_model.to(device)\nprint(model_01)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── Callback utilities ─────────────────────────────────────────────────────────\n\nclass EarlyStopping:\n    \"\"\"Stop training when monitored metric stops improving.\"\"\"\n    def __init__(self, patience=4, mode='min', verbose=1):\n        self.patience = patience\n        self.mode     = mode\n        self.verbose  = verbose\n        self.best     = float('inf') if mode == 'min' else -float('inf')\n        self.counter  = 0\n        self.stop     = False\n\n    def __call__(self, value):\n        improved = (value < self.best) if self.mode == 'min' else (value > self.best)\n        if improved:\n            self.best    = value\n            self.counter = 0\n        else:\n            self.counter += 1\n            if self.verbose:\n                print(f\"EarlyStopping: no improvement ({self.counter}/{self.patience})\")\n            if self.counter >= self.patience:\n                if self.verbose:\n                    print(\"EarlyStopping triggered.\")\n                self.stop = True\n\n\nclass ModelCheckpoint:\n    \"\"\"Save model weights whenever the monitored metric improves.\"\"\"\n    def __init__(self, filepath, mode='min', verbose=1):\n        self.filepath = filepath\n        self.mode     = mode\n        self.verbose  = verbose\n        self.best     = float('inf') if mode == 'min' else -float('inf')\n\n    def __call__(self, value, model):\n        improved = (value < self.best) if self.mode == 'min' else (value > self.best)\n        if improved:\n            self.best = value\n            torch.save(model.state_dict(), self.filepath)\n            if self.verbose:\n                print(f\"ModelCheckpoint: saved best model to {self.filepath}\")\n\n\n# ── Per-epoch helpers ──────────────────────────────────────────────────────────\n\ndef train_epoch(model, loader, criterion, optimizer, device, steps_per_epoch=None):\n    model.train()\n    running_loss, correct, total = 0.0, 0, 0\n    for i, (inputs, targets) in enumerate(loader):\n        if steps_per_epoch and i >= steps_per_epoch:\n            break\n        inputs, targets = inputs.to(device), targets.to(device)\n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss    = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n        running_loss += loss.item()\n        _, predicted  = outputs.max(1)\n        total        += targets.size(0)\n        correct      += predicted.eq(targets).sum().item()\n    n = min(steps_per_epoch, len(loader)) if steps_per_epoch else len(loader)\n    return running_loss / n, correct / total\n\n\ndef eval_epoch(model, loader, criterion, device):\n    model.eval()\n    running_loss, correct, total = 0.0, 0, 0\n    with torch.no_grad():\n        for inputs, targets in loader:\n            inputs, targets = inputs.to(device), targets.to(device)\n            outputs         = model(inputs)\n            loss            = criterion(outputs, targets)\n            running_loss   += loss.item()\n            _, predicted    = outputs.max(1)\n            total          += targets.size(0)\n            correct        += predicted.eq(targets).sum().item()\n    return running_loss / len(loader), correct / total\n\n\n# ── Training setup ─────────────────────────────────────────────────────────────\nfilepath   = \"model.pt\"\ncriterion  = nn.CrossEntropyLoss()\noptimizer_01 = optim.SGD(model_01.parameters(), lr=0.0001, momentum=0,\n                          weight_decay=1e-6, nesterov=False)\n\nes  = EarlyStopping(patience=4, mode='min', verbose=1)\ncp  = ModelCheckpoint(filepath, mode='min', verbose=1)\nlrr = optim.lr_scheduler.ReduceLROnPlateau(optimizer_01, mode='max',\n                                            patience=3, factor=0.5,\n                                            min_lr=0.0001, verbose=True)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history_01 = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}\n\nfor epoch in range(1):   # epochs=1 (same as original)\n    train_loss, train_acc = train_epoch(model_01, train_loader, criterion,\n                                        optimizer_01, device, steps_per_epoch=50)\n    val_loss,   val_acc   = eval_epoch(model_01, valid_loader, criterion, device)\n\n    history_01['train_loss'].append(train_loss)\n    history_01['train_acc'].append(train_acc)\n    history_01['val_loss'].append(val_loss)\n    history_01['val_acc'].append(val_acc)\n\n    print(f\"Epoch {epoch+1} | train_loss: {train_loss:.4f}  train_acc: {train_acc:.4f}\"\n          f\"  val_loss: {val_loss:.4f}  val_acc: {val_acc:.4f}\")\n\n    lrr.step(val_acc)\n    cp(val_loss, model_01)\n    es(val_loss)\n    if es.stop:\n        break","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if not os.path.isdir('model_weights/'):\n    os.mkdir(\"model_weights/\")\ntorch.save(model_01.state_dict(), \"model_weights/vgg19_model_01.pt\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset = datasets.ImageFolder(\"chest_xray/chest_xray/test\", transform=test_transforms)\ntest_loader  = DataLoader(test_dataset, batch_size=32, shuffle=True,\n                          num_workers=2, pin_memory=True)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_01.load_state_dict(torch.load(\"model_weights/vgg19_model_01.pt\", map_location=device))\n\nvgg_val_loss_01, vgg_val_acc_01   = eval_epoch(model_01, valid_loader, criterion, device)\nvgg_test_loss_01, vgg_test_acc_01 = eval_epoch(model_01, test_loader,  criterion, device)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Validation Loss:     {vgg_val_loss_01}\")\nprint(f\"Validation Accuracy: {vgg_val_acc_01}\")\nprint(f\"Test Loss:           {vgg_test_loss_01}\")\nprint(f\"Test Accuracy:       {vgg_test_acc_01}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Incremental unfreezing & fine tuning","metadata":{}},{"cell_type":"code","source":"# Build model_02 with the same architecture and load model_01 weights\nbase_model_02 = models.vgg19(weights=None)\nbase_model_02.avgpool = nn.AdaptiveAvgPool2d((4, 4))\nbase_model_02.classifier = nn.Sequential(\n    nn.Flatten(),\n    nn.Linear(512 * 4 * 4, 4608),\n    nn.ReLU(inplace=True),\n    nn.Dropout(0.2),\n    nn.Linear(4608, 1152),\n    nn.ReLU(inplace=True),\n    nn.Linear(1152, 2)\n)\n\nmodel_02 = base_model_02.to(device)\nmodel_02.load_state_dict(torch.load(\"model_weights/vgg19_model_01.pt\", map_location=device))\n\n# Freeze everything first\nfor param in model_02.parameters():\n    param.requires_grad = False\n\n# Unfreeze from block5_conv3 onwards (features[32] and later in torchvision VGG19)\n# VGG19 features layer indices: block5_conv3 -> index 32, block5_conv4 -> index 34\nset_trainable = False\nbase_feature_names = [name for name, _ in model_02.features.named_children()]\nfor name, module in model_02.features.named_children():\n    if name in ['32', '34']:   # block5_conv3, block5_conv4\n        set_trainable = True\n    if set_trainable:\n        for param in module.parameters():\n            param.requires_grad = True\n\n# Always unfreeze the custom classifier head\nfor param in model_02.classifier.parameters():\n    param.requires_grad = True\n\nprint(model_02)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_feature_names","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer_02 = optim.SGD(\n    filter(lambda p: p.requires_grad, model_02.parameters()),\n    lr=0.0001, momentum=0, weight_decay=1e-6, nesterov=False\n)\nlrr_02 = optim.lr_scheduler.ReduceLROnPlateau(optimizer_02, mode='max',\n                                               patience=3, factor=0.5,\n                                               min_lr=0.0001, verbose=True)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history_02 = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}\nes_02 = EarlyStopping(patience=4, mode='min', verbose=1)\ncp_02 = ModelCheckpoint(filepath, mode='min', verbose=1)\n\nfor epoch in range(1):   # epochs=1 (same as original)\n    train_loss, train_acc = train_epoch(model_02, train_loader, criterion,\n                                        optimizer_02, device, steps_per_epoch=10)\n    val_loss,   val_acc   = eval_epoch(model_02, valid_loader, criterion, device)\n\n    history_02['train_loss'].append(train_loss)\n    history_02['train_acc'].append(train_acc)\n    history_02['val_loss'].append(val_loss)\n    history_02['val_acc'].append(val_acc)\n\n    print(f\"Epoch {epoch+1} | train_loss: {train_loss:.4f}  train_acc: {train_acc:.4f}\"\n          f\"  val_loss: {val_loss:.4f}  val_acc: {val_acc:.4f}\")\n\n    lrr_02.step(val_acc)\n    cp_02(val_loss, model_02)\n    es_02(val_loss)\n    if es_02.stop:\n        break","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if not os.path.isdir('model_weights/'):\n    os.mkdir(\"model_weights/\")\ntorch.save(model_02.state_dict(), \"model_weights/vgg19_model_02.pt\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_02.load_state_dict(torch.load(\"model_weights/vgg19_model_02.pt\", map_location=device))\n\nvgg_val_loss_02, vgg_val_acc_02   = eval_epoch(model_02, valid_loader, criterion, device)\nvgg_test_loss_02, vgg_test_acc_02 = eval_epoch(model_02, test_loader,  criterion, device)\n\nprint(f\"Validation Loss:     {vgg_val_loss_02}\")\nprint(f\"Validation Accuracy: {vgg_val_acc_02}\")\nprint(f\"Test Loss:           {vgg_test_loss_02}\")\nprint(f\"Test Accuracy:       {vgg_test_acc_02}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Unfreezing and fine tuning the entire network","metadata":{}},{"cell_type":"code","source":"# Build model_03 with all layers unfrozen for full fine-tuning\nbase_model_03 = models.vgg19(weights=None)\nbase_model_03.avgpool = nn.AdaptiveAvgPool2d((4, 4))\nbase_model_03.classifier = nn.Sequential(\n    nn.Flatten(),\n    nn.Linear(512 * 4 * 4, 4608),\n    nn.ReLU(inplace=True),\n    nn.Dropout(0.2),\n    nn.Linear(4608, 1152),\n    nn.ReLU(inplace=True),\n    nn.Linear(1152, 2)\n)\n\nmodel_03 = base_model_03.to(device)\nmodel_03.load_state_dict(torch.load(\"model_weights/vgg19_model_01.pt\", map_location=device))\n\n# Unfreeze all parameters\nfor param in model_03.parameters():\n    param.requires_grad = True\n\nprint(model_03)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer_03 = optim.SGD(model_03.parameters(), lr=0.0001, momentum=0,\n                          weight_decay=1e-6, nesterov=False)\nlrr_03 = optim.lr_scheduler.ReduceLROnPlateau(optimizer_03, mode='max',\n                                               patience=3, factor=0.5,\n                                               min_lr=0.0001, verbose=True)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history_03 = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}\nes_03 = EarlyStopping(patience=4, mode='min', verbose=1)\ncp_03 = ModelCheckpoint(filepath, mode='min', verbose=1)\n\nfor epoch in range(1):   # epochs=1 (same as original)\n    train_loss, train_acc = train_epoch(model_03, train_loader, criterion,\n                                        optimizer_03, device, steps_per_epoch=100)\n    val_loss,   val_acc   = eval_epoch(model_03, valid_loader, criterion, device)\n\n    history_03['train_loss'].append(train_loss)\n    history_03['train_acc'].append(train_acc)\n    history_03['val_loss'].append(val_loss)\n    history_03['val_acc'].append(val_acc)\n\n    print(f\"Epoch {epoch+1} | train_loss: {train_loss:.4f}  train_acc: {train_acc:.4f}\"\n          f\"  val_loss: {val_loss:.4f}  val_acc: {val_acc:.4f}\")\n\n    lrr_03.step(val_acc)\n    cp_03(val_loss, model_03)\n    es_03(val_loss)\n    if es_03.stop:\n        break","metadata":{},"outputs":[],"execution_count":null}]}