{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"datasetVersion","sourceId":14383702,"datasetId":9186038,"databundleVersionId":15196429}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torchvision.transforms as transforms\nimport torchvision.datasets as datasets\nfrom torch.utils.data import random_split, DataLoader\n\n# ===============================\n# Train Transform (With Augmentation)\n# ===============================\ntrain_transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(20),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\n# ===============================\n# Test Transform (No Augmentation)\n# ===============================\ntest_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\n# ===============================\n# Load Dataset\n# ===============================\ndataset_path = \"/kaggle/input/datasets/razaulibnabari/wheatdisease/Original Dataset\"\n\nfull_dataset = datasets.ImageFolder(root=dataset_path)\n\ntrain_size = int(0.8 * len(full_dataset))\ntest_size = len(full_dataset) - train_size\n\ntrain_dataset, test_dataset = random_split(full_dataset, [train_size, test_size])\n\n# Apply transforms separately\ntrain_dataset.dataset.transform = train_transform\ntest_dataset.dataset.transform = test_transform\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)\n\nprint(\"Train size:\", len(train_dataset))\nprint(\"Test size:\", len(test_dataset))\nprint(\"Total Images:\", len(full_dataset))\nprint(\"Total Classes:\", len(full_dataset.classes))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-19T16:54:37.204072Z","iopub.execute_input":"2026-03-19T16:54:37.204656Z","iopub.status.idle":"2026-03-19T16:54:50.939118Z","shell.execute_reply.started":"2026-03-19T16:54:37.204626Z","shell.execute_reply":"2026-03-19T16:54:50.938480Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# RESNET18","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport torchvision.transforms as transforms\nimport torchvision.datasets as datasets\nfrom torch.utils.data import DataLoader, Subset\nfrom sklearn.model_selection import train_test_split\n\n# ===============================\n# CONFIG\n# ===============================\ndataset_path = \"/kaggle/input/datasets/razaulibnabari/wheatdisease/Original Dataset\"\nbatch_size = 32\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# ===============================\n# TRANSFORMS\n# ===============================\ntrain_transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(20),\n    transforms.RandomAffine(degrees=15, translate=(0.1, 0.1)),\n    transforms.ColorJitter(0.2, 0.2, 0.2, 0.1),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\ntest_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\n# ===============================\n# LOAD DATASET (NO TRANSFORM YET)\n# ===============================\nbase_dataset = datasets.ImageFolder(root=dataset_path)\ntargets = base_dataset.targets\nnum_classes = len(base_dataset.classes)\n\n# ===============================\n# STRATIFIED SPLIT (TRAIN/VAL/TEST)\n# ===============================\ntrain_idx, temp_idx = train_test_split(\n    list(range(len(targets))),\n    test_size=0.3,\n    stratify=targets,\n    random_state=42\n)\n\nval_idx, test_idx = train_test_split(\n    temp_idx,\n    test_size=0.5,\n    stratify=[targets[i] for i in temp_idx],\n    random_state=42\n)\n\n# ===============================\n# CREATE SEPARATE DATASETS (IMPORTANT)\n# ===============================\ntrain_dataset = datasets.ImageFolder(root=dataset_path, transform=train_transform)\nval_dataset   = datasets.ImageFolder(root=dataset_path, transform=test_transform)\ntest_dataset  = datasets.ImageFolder(root=dataset_path, transform=test_transform)\n\ntrain_dataset = Subset(train_dataset, train_idx)\nval_dataset   = Subset(val_dataset, val_idx)\ntest_dataset  = Subset(test_dataset, test_idx)\n\n# ===============================\n# DATALOADERS\n# ===============================\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_loader   = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\ntest_loader  = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\nprint(\"Train:\", len(train_dataset))\nprint(\"Val:\", len(val_dataset))\nprint(\"Test:\", len(test_dataset))\nprint(\"Classes:\", num_classes)\n\n# ===============================\n# MODEL (Example: ResNet18 with Regularization)\n# ===============================\nimport torchvision.models as models\n\nmodel = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\nmodel.fc = nn.Sequential(\n    nn.Dropout(0.5),\n    nn.Linear(model.fc.in_features, num_classes)\n)\nmodel = model.to(device)\n\n# ===============================\n# LOSS + OPTIMIZER\n# ===============================\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)\n\n# ===============================\n# TRAINING WITH VALIDATION + EARLY STOPPING\n# ===============================\nepochs = 20\nbest_val_acc = 0\npatience = 5\ncounter = 0\n\nfor epoch in range(epochs):\n    # ---- TRAIN ----\n    model.train()\n    train_correct, train_total = 0, 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        _, preds = torch.max(outputs, 1)\n        train_total += labels.size(0)\n        train_correct += (preds == labels).sum().item()\n\n    train_acc = 100 * train_correct / train_total\n\n    # ---- VALIDATION ----\n    model.eval()\n    val_correct, val_total = 0, 0\n\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            _, preds = torch.max(outputs, 1)\n\n            val_total += labels.size(0)\n            val_correct += (preds == labels).sum().item()\n\n    val_acc = 100 * val_correct / val_total\n\n    print(f\"Epoch {epoch+1}: Train={train_acc:.2f}% | Val={val_acc:.2f}%\")\n\n    # ---- EARLY STOPPING ----\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        counter = 0\n        torch.save(model.state_dict(), \"best_model.pth\")\n    else:\n        counter += 1\n        if counter >= patience:\n            print(\"Early stopping triggered\")\n            break\n\n# ===============================\n# FINAL TEST EVALUATION\n# ===============================\nmodel.load_state_dict(torch.load(\"best_model.pth\"))\nmodel.eval()\n\ncorrect, total = 0, 0\n\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images, labels = images.to(device), labels.to(device)\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        total += labels.size(0)\n        correct += (preds == labels).sum().item()\n\ntest_acc = 100 * correct / total\nprint(f\"\\nFinal Test Accuracy: {test_acc:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-19T17:51:30.714345Z","iopub.execute_input":"2026-03-19T17:51:30.715105Z","iopub.status.idle":"2026-03-19T17:59:16.820288Z","shell.execute_reply.started":"2026-03-19T17:51:30.715077Z","shell.execute_reply":"2026-03-19T17:59:16.819570Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# RESNET50","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport torchvision.transforms as transforms\nimport torchvision.datasets as datasets\nfrom torch.utils.data import DataLoader, Subset\nfrom sklearn.model_selection import train_test_split\n\n# ===============================\n# CONFIG\n# ===============================\ndataset_path = \"/kaggle/input/datasets/razaulibnabari/wheatdisease/Original Dataset\"\nbatch_size = 32\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# ===============================\n# TRANSFORMS\n# ===============================\ntrain_transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(20),\n    transforms.RandomAffine(degrees=15, translate=(0.1, 0.1)),\n    transforms.ColorJitter(0.2, 0.2, 0.2, 0.1),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\ntest_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\n# ===============================\n# LOAD DATASET (NO TRANSFORM YET)\n# ===============================\nbase_dataset = datasets.ImageFolder(root=dataset_path)\ntargets = base_dataset.targets\nnum_classes = len(base_dataset.classes)\n\n# ===============================\n# STRATIFIED SPLIT (TRAIN/VAL/TEST)\n# ===============================\ntrain_idx, temp_idx = train_test_split(\n    list(range(len(targets))),\n    test_size=0.3,\n    stratify=targets,\n    random_state=42\n)\n\nval_idx, test_idx = train_test_split(\n    temp_idx,\n    test_size=0.5,\n    stratify=[targets[i] for i in temp_idx],\n    random_state=42\n)\n\n# ===============================\n# CREATE SEPARATE DATASETS (IMPORTANT)\n# ===============================\ntrain_dataset = datasets.ImageFolder(root=dataset_path, transform=train_transform)\nval_dataset   = datasets.ImageFolder(root=dataset_path, transform=test_transform)\ntest_dataset  = datasets.ImageFolder(root=dataset_path, transform=test_transform)\n\ntrain_dataset = Subset(train_dataset, train_idx)\nval_dataset   = Subset(val_dataset, val_idx)\ntest_dataset  = Subset(test_dataset, test_idx)\n\n# ===============================\n# DATALOADERS\n# ===============================\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_loader   = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\ntest_loader  = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\nprint(\"Train:\", len(train_dataset))\nprint(\"Val:\", len(val_dataset))\nprint(\"Test:\", len(test_dataset))\nprint(\"Classes:\", num_classes)\n\n# ===============================\n# MODEL (Example: ResNet18 with Regularization)\n# ===============================\nimport torchvision.models as models\n\nmodel =  models.resnet50(weights=models.ResNet50_Weights.DEFAULT)\nmodel.fc = nn.Sequential(\n    nn.Dropout(0.5),\n    nn.Linear(model.fc.in_features, num_classes)\n)\nmodel = model.to(device)\n\n# ===============================\n# LOSS + OPTIMIZER\n# ===============================\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)\n\n# ===============================\n# TRAINING WITH VALIDATION + EARLY STOPPING\n# ===============================\nepochs = 20\nbest_val_acc = 0\npatience = 5\ncounter = 0\n\nfor epoch in range(epochs):\n    # ---- TRAIN ----\n    model.train()\n    train_correct, train_total = 0, 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        _, preds = torch.max(outputs, 1)\n        train_total += labels.size(0)\n        train_correct += (preds == labels).sum().item()\n\n    train_acc = 100 * train_correct / train_total\n\n    # ---- VALIDATION ----\n    model.eval()\n    val_correct, val_total = 0, 0\n\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            _, preds = torch.max(outputs, 1)\n\n            val_total += labels.size(0)\n            val_correct += (preds == labels).sum().item()\n\n    val_acc = 100 * val_correct / val_total\n\n    print(f\"Epoch {epoch+1}: Train={train_acc:.2f}% | Val={val_acc:.2f}%\")\n\n    # ---- EARLY STOPPING ----\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        counter = 0\n        torch.save(model.state_dict(), \"best_model.pth\")\n    else:\n        counter += 1\n        if counter >= patience:\n            print(\"Early stopping triggered\")\n            break\n\n# ===============================\n# FINAL TEST EVALUATION\n# ===============================\nmodel.load_state_dict(torch.load(\"best_model.pth\"))\nmodel.eval()\n\ncorrect, total = 0, 0\n\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images, labels = images.to(device), labels.to(device)\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        total += labels.size(0)\n        correct += (preds == labels).sum().item()\n\ntest_acc = 100 * correct / total\nprint(f\"\\nFinal Test Accuracy: {test_acc:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T16:50:24.006137Z","iopub.execute_input":"2026-03-20T16:50:24.006392Z","iopub.status.idle":"2026-03-20T17:04:48.161520Z","shell.execute_reply.started":"2026-03-20T16:50:24.006370Z","shell.execute_reply":"2026-03-20T17:04:48.160776Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# MOBILENETV2","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport torchvision.transforms as transforms\nimport torchvision.datasets as datasets\nfrom torch.utils.data import DataLoader, Subset\nfrom sklearn.model_selection import train_test_split\n\n# ===============================\n# CONFIG\n# ===============================\ndataset_path = \"/kaggle/input/datasets/razaulibnabari/wheatdisease/Original Dataset\"\nbatch_size = 32\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# ===============================\n# TRANSFORMS\n# ===============================\ntrain_transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(20),\n    transforms.RandomAffine(degrees=15, translate=(0.1, 0.1)),\n    transforms.ColorJitter(0.2, 0.2, 0.2, 0.1),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\ntest_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\n# ===============================\n# LOAD DATASET (NO TRANSFORM YET)\n# ===============================\nbase_dataset = datasets.ImageFolder(root=dataset_path)\ntargets = base_dataset.targets\nnum_classes = len(base_dataset.classes)\n\n# ===============================\n# STRATIFIED SPLIT (TRAIN/VAL/TEST)\n# ===============================\ntrain_idx, temp_idx = train_test_split(\n    list(range(len(targets))),\n    test_size=0.3,\n    stratify=targets,\n    random_state=42\n)\n\nval_idx, test_idx = train_test_split(\n    temp_idx,\n    test_size=0.5,\n    stratify=[targets[i] for i in temp_idx],\n    random_state=42\n)\n\n# ===============================\n# CREATE SEPARATE DATASETS (IMPORTANT)\n# ===============================\ntrain_dataset = datasets.ImageFolder(root=dataset_path, transform=train_transform)\nval_dataset   = datasets.ImageFolder(root=dataset_path, transform=test_transform)\ntest_dataset  = datasets.ImageFolder(root=dataset_path, transform=test_transform)\n\ntrain_dataset = Subset(train_dataset, train_idx)\nval_dataset   = Subset(val_dataset, val_idx)\ntest_dataset  = Subset(test_dataset, test_idx)\n\n# ===============================\n# DATALOADERS\n# ===============================\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_loader   = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\ntest_loader  = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\nprint(\"Train:\", len(train_dataset))\nprint(\"Val:\", len(val_dataset))\nprint(\"Test:\", len(test_dataset))\nprint(\"Classes:\", num_classes)\n\n# ===============================\n# MODEL (Example: ResNet18 with Regularization)\n# ===============================\nimport torchvision.models as models\n\nmodel =  models.mobilenet_v2(weights=models.MobileNet_V2_Weights.DEFAULT)\nmodel.classifier[1] = nn.Sequential(nn.Dropout(0.5),\n                                    nn.Linear(model.classifier[1].in_features, num_classes))\nmodel = model.to(device)\n\n# ===============================\n# LOSS + OPTIMIZER\n# ===============================\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)\n\n# ===============================\n# TRAINING WITH VALIDATION + EARLY STOPPING\n# ===============================\nepochs = 20\nbest_val_acc = 0\npatience = 5\ncounter = 0\n\nfor epoch in range(epochs):\n    # ---- TRAIN ----\n    model.train()\n    train_correct, train_total = 0, 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        _, preds = torch.max(outputs, 1)\n        train_total += labels.size(0)\n        train_correct += (preds == labels).sum().item()\n\n    train_acc = 100 * train_correct / train_total\n\n    # ---- VALIDATION ----\n    model.eval()\n    val_correct, val_total = 0, 0\n\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            _, preds = torch.max(outputs, 1)\n\n            val_total += labels.size(0)\n            val_correct += (preds == labels).sum().item()\n\n    val_acc = 100 * val_correct / val_total\n\n    print(f\"Epoch {epoch+1}: Train={train_acc:.2f}% | Val={val_acc:.2f}%\")\n\n    # ---- EARLY STOPPING ----\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        counter = 0\n        torch.save(model.state_dict(), \"best_model.pth\")\n    else:\n        counter += 1\n        if counter >= patience:\n            print(\"Early stopping triggered\")\n            break\n\n# ===============================\n# FINAL TEST EVALUATION\n# ===============================\nmodel.load_state_dict(torch.load(\"best_model.pth\"))\nmodel.eval()\n\ncorrect, total = 0, 0\n\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images, labels = images.to(device), labels.to(device)\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        total += labels.size(0)\n        correct += (preds == labels).sum().item()\n\ntest_acc = 100 * correct / total\nprint(f\"\\nFinal Test Accuracy: {test_acc:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T17:10:12.359273Z","iopub.execute_input":"2026-03-20T17:10:12.359881Z","iopub.status.idle":"2026-03-20T17:22:38.618198Z","shell.execute_reply.started":"2026-03-20T17:10:12.359849Z","shell.execute_reply":"2026-03-20T17:22:38.617501Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# VGG16","metadata":{}},{"cell_type":"markdown","source":"# VGG19","metadata":{}},{"cell_type":"markdown","source":"# CONVNEXT","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport torchvision.transforms as transforms\nimport torchvision.datasets as datasets\nfrom torch.utils.data import DataLoader, Subset\nfrom sklearn.model_selection import train_test_split\n\n# ===============================\n# CONFIG\n# ===============================\ndataset_path = \"/kaggle/input/datasets/razaulibnabari/wheatdisease/Original Dataset\"\nbatch_size = 32\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# ===============================\n# TRANSFORMS\n# ===============================\ntrain_transform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(20),\n    transforms.RandomAffine(degrees=15, translate=(0.1, 0.1)),\n    transforms.ColorJitter(0.2, 0.2, 0.2, 0.1),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\ntest_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\n# ===============================\n# LOAD DATASET (NO TRANSFORM YET)\n# ===============================\nbase_dataset = datasets.ImageFolder(root=dataset_path)\ntargets = base_dataset.targets\nnum_classes = len(base_dataset.classes)\n\n# ===============================\n# STRATIFIED SPLIT (TRAIN/VAL/TEST)\n# ===============================\ntrain_idx, temp_idx = train_test_split(\n    list(range(len(targets))),\n    test_size=0.3,\n    stratify=targets,\n    random_state=42\n)\n\nval_idx, test_idx = train_test_split(\n    temp_idx,\n    test_size=0.5,\n    stratify=[targets[i] for i in temp_idx],\n    random_state=42\n)\n\n# ===============================\n# CREATE SEPARATE DATASETS (IMPORTANT)\n# ===============================\ntrain_dataset = datasets.ImageFolder(root=dataset_path, transform=train_transform)\nval_dataset   = datasets.ImageFolder(root=dataset_path, transform=test_transform)\ntest_dataset  = datasets.ImageFolder(root=dataset_path, transform=test_transform)\n\ntrain_dataset = Subset(train_dataset, train_idx)\nval_dataset   = Subset(val_dataset, val_idx)\ntest_dataset  = Subset(test_dataset, test_idx)\n\n# ===============================\n# DATALOADERS\n# ===============================\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_loader   = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\ntest_loader  = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\nprint(\"Train:\", len(train_dataset))\nprint(\"Val:\", len(val_dataset))\nprint(\"Test:\", len(test_dataset))\nprint(\"Classes:\", num_classes)\n\n# ===============================\n# MODEL \n# ===============================\nimport torchvision.models as models\n\nmodel =  models.convnext_tiny(weights=models.ConvNeXt_Tiny_Weights.DEFAULT)\nmodel.classifier[2] = nn.Sequential(\n            nn.Dropout(0.5),\n            nn.Linear(model.classifier[2].in_features, num_classes))\nmodel = model.to(device)\n\n# ===============================\n# LOSS + OPTIMIZER\n# ===============================\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)\n\n# ===============================\n# TRAINING WITH VALIDATION + EARLY STOPPING\n# ===============================\nepochs = 20\nbest_val_acc = 0\npatience = 5\ncounter = 0\n\nfor epoch in range(epochs):\n    # ---- TRAIN ----\n    model.train()\n    train_correct, train_total = 0, 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        _, preds = torch.max(outputs, 1)\n        train_total += labels.size(0)\n        train_correct += (preds == labels).sum().item()\n\n    train_acc = 100 * train_correct / train_total\n\n    # ---- VALIDATION ----\n    model.eval()\n    val_correct, val_total = 0, 0\n\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            _, preds = torch.max(outputs, 1)\n\n            val_total += labels.size(0)\n            val_correct += (preds == labels).sum().item()\n\n    val_acc = 100 * val_correct / val_total\n\n    print(f\"Epoch {epoch+1}: Train={train_acc:.2f}% | Val={val_acc:.2f}%\")\n\n    # ---- EARLY STOPPING ----\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        counter = 0\n        torch.save(model.state_dict(), \"best_model.pth\")\n    else:\n        counter += 1\n        if counter >= patience:\n            print(\"Early stopping triggered\")\n            break\n\n# ===============================\n# FINAL TEST EVALUATION\n# ===============================\nmodel.load_state_dict(torch.load(\"best_model.pth\"))\nmodel.eval()\n\ncorrect, total = 0, 0\n\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images, labels = images.to(device), labels.to(device)\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        total += labels.size(0)\n        correct += (preds == labels).sum().item()\n\ntest_acc = 100 * correct / total\nprint(f\"\\nFinal Test Accuracy: {test_acc:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T17:36:45.119759Z","iopub.execute_input":"2026-03-20T17:36:45.120346Z","iopub.status.idle":"2026-03-20T17:49:56.588238Z","shell.execute_reply.started":"2026-03-20T17:36:45.120317Z","shell.execute_reply":"2026-03-20T17:49:56.587452Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, classification_report\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# ===============================\n# GET PREDICTIONS\n# ===============================\nall_preds = []\nall_labels = []\n\nmodel.eval()\n\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images = images.to(device)\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.numpy())\n\nall_preds = np.array(all_preds)\nall_labels = np.array(all_labels)\n\n# ===============================\n# CONFUSION MATRIX\n# ===============================\ncm = confusion_matrix(all_labels, all_preds)\n\nprint(\"\\nConfusion Matrix:\\n\")\nprint(cm)\n\n# ===============================\n# PLOT CONFUSION MATRIX\n# ===============================\nplt.figure(figsize=(6,6))\nplt.imshow(cm)\nplt.title(\"Confusion Matrix\")\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"True\")\n\nplt.colorbar()\n\nclasses = base_dataset.classes\nplt.xticks(range(len(classes)), classes, rotation=45)\nplt.yticks(range(len(classes)), classes)\n\nfor i in range(len(classes)):\n    for j in range(len(classes)):\n        plt.text(j, i, cm[i, j], ha=\"center\", va=\"center\")\n\nplt.tight_layout()\nplt.show()\n\n# ===============================\n# CLASSIFICATION REPORT\n# ===============================\nprint(\"\\nClassification Report:\\n\")\nprint(classification_report(all_labels, all_preds, target_names=classes))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T17:57:29.177245Z","iopub.execute_input":"2026-03-20T17:57:29.177870Z","iopub.status.idle":"2026-03-20T17:57:35.147343Z","shell.execute_reply.started":"2026-03-20T17:57:29.177840Z","shell.execute_reply":"2026-03-20T17:57:35.146667Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# ENSAMBLE","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torchvision.models as models\n\n# ===============================\n# SETUP\n# ===============================\nnum_classes = len(base_dataset.classes)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nepochs = 10\n\nmodel_names = [\"resnet18\", \"resnet50\", \"mobilenet\", \"convnext\", \"vit\"]\n\n# ===============================\n# MODEL FUNCTION\n# ===============================\ndef get_models():\n    resnet18 = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\n    resnet18.fc = nn.Sequential(nn.Dropout(0.5),\n                                nn.Linear(resnet18.fc.in_features, num_classes))\n\n    resnet50 = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)\n    resnet50.fc = nn.Sequential(nn.Dropout(0.5),\n                                nn.Linear(resnet50.fc.in_features, num_classes))\n\n    mobilenet = models.mobilenet_v2(weights=models.MobileNet_V2_Weights.DEFAULT)\n    mobilenet.classifier[1] = nn.Sequential(nn.Dropout(0.5),\n                                            nn.Linear(mobilenet.classifier[1].in_features, num_classes))\n\n    convnext = models.convnext_tiny(weights=models.ConvNeXt_Tiny_Weights.DEFAULT)\n    convnext.classifier[2] = nn.Sequential(nn.Dropout(0.5),\n                                           nn.Linear(convnext.classifier[2].in_features, num_classes))\n\n    vit = models.vit_b_16(weights=models.ViT_B_16_Weights.DEFAULT)\n    vit.heads.head = nn.Sequential(nn.Dropout(0.5),\n                                   nn.Linear(vit.heads.head.in_features, num_classes))\n\n    return [resnet18, resnet50, mobilenet, convnext, vit]\n\nmodels_list = get_models()\n\nfor model in models_list:\n    model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\n\n# ===============================\n# TRAIN + SAVE BEST MODELS\n# ===============================\nval_scores = []\n\nfor model, name in zip(models_list, model_names):\n\n    optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)\n    best_val_acc = 0\n\n    for epoch in range(epochs):\n\n        # ---- TRAIN ----\n        model.train()\n        train_correct, train_total = 0, 0\n\n        for images, labels in train_loader:\n            images, labels = images.to(device), labels.to(device)\n\n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n\n            _, preds = torch.max(outputs, 1)\n            train_total += labels.size(0)\n            train_correct += (preds == labels).sum().item()\n\n        train_acc = 100 * train_correct / train_total\n\n        # ---- VALIDATION ----\n        model.eval()\n        val_correct, val_total = 0, 0\n\n        with torch.no_grad():\n            for images, labels in val_loader:\n                images, labels = images.to(device), labels.to(device)\n                outputs = model(images)\n                _, preds = torch.max(outputs, 1)\n\n                val_total += labels.size(0)\n                val_correct += (preds == labels).sum().item()\n\n        val_acc = 100 * val_correct / val_total\n\n        print(f\"{name} Epoch {epoch+1}: Train={train_acc:.2f}% | Val={val_acc:.2f}%\")\n\n        # ---- SAVE BEST ----\n        if val_acc > best_val_acc:\n            best_val_acc = val_acc\n            torch.save(model.state_dict(), f\"{name}_best.pth\")\n\n    val_scores.append(best_val_acc)\n\n# ===============================\n# LOAD BEST MODELS\n# ===============================\nmodels_list = get_models()\n\nfor model, name in zip(models_list, model_names):\n    model.load_state_dict(torch.load(f\"{name}_best.pth\"))\n    model.to(device)\n    model.eval()\n\n# ===============================\n# WEIGHTED ENSEMBLE\n# ===============================\nweights = torch.tensor(val_scores)\nweights = weights / weights.sum()\n\ndef ensemble_predict(models, images, weights):\n    outputs = []\n    for model, w in zip(models, weights):\n        with torch.no_grad():\n            out = model(images)\n            outputs.append(torch.softmax(out, dim=1) * w)\n    return torch.sum(torch.stack(outputs), dim=0)\n\n# ===============================\n# TEST ENSEMBLE\n# ===============================\ncorrect, total = 0, 0\n\nfor images, labels in test_loader:\n    images, labels = images.to(device), labels.to(device)\n\n    outputs = ensemble_predict(models_list, images, weights)\n    _, preds = torch.max(outputs, 1)\n\n    total += labels.size(0)\n    correct += (preds == labels).sum().item()\n\nensemble_acc = 100 * correct / total\nprint(f\"\\nFinal Ensemble Test Accuracy: {ensemble_acc:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-19T18:07:32.152791Z","iopub.execute_input":"2026-03-19T18:07:32.153423Z","iopub.status.idle":"2026-03-19T18:50:09.196549Z","shell.execute_reply.started":"2026-03-19T18:07:32.153395Z","shell.execute_reply":"2026-03-19T18:50:09.195873Z"}},"outputs":[],"execution_count":null}]}