{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Transfer Learning with MobileNetV2","metadata":{}},{"cell_type":"code","source":"import torchvision\nimport torchvision.transforms as transforms\nfrom torchvision.datasets import ImageFolder\nfrom torch.utils.data import DataLoader\nimport torch\nimport torch.nn as nn\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom torchvision.models import mobilenet_v2, MobileNet_V2_Weights\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nfrom torchvision.datasets import CIFAR10","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T18:52:47.469049Z","iopub.execute_input":"2025-10-27T18:52:47.469294Z","iopub.status.idle":"2025-10-27T18:52:55.996717Z","shell.execute_reply.started":"2025-10-27T18:52:47.469272Z","shell.execute_reply":"2025-10-27T18:52:55.996122Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Transforms\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize((0.4914, 0.4822, 0.4465),\n                         (0.2023, 0.1994, 0.2010))\n])\n\ntest_transform = transforms.Compose([\n    transforms.Resize(224),\n    transforms.ToTensor(),\n    transforms.Normalize((0.4914, 0.4822, 0.4465),\n                         (0.2023, 0.1994, 0.2010))\n])\n\n# Load train and test datasets\ntrainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=train_transform)\ntestset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=test_transform)\n\n# DataLoaders\nfrom torch.utils.data import DataLoader\ntrainloader = DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2)\ntestloader = DataLoader(testset, batch_size=256, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T18:53:03.964199Z","iopub.execute_input":"2025-10-27T18:53:03.964459Z","iopub.status.idle":"2025-10-27T18:53:13.684513Z","shell.execute_reply.started":"2025-10-27T18:53:03.964440Z","shell.execute_reply":"2025-10-27T18:53:13.683832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = mobilenet_v2(weights=MobileNet_V2_Weights.DEFAULT)\n\n# Replace classifier\nmodel.classifier[1] = nn.Linear(model.last_channel, 10)\n\n# Freeze features except classifier\nfor param in model.features.parameters():\n    param.requires_grad = False\n\n# Unfreeze last 3 blocks\nfor param in model.features[-3:].parameters():\n    param.requires_grad = True\n\n# Unfreeze classifier (just in case)\nfor param in model.classifier.parameters():\n    param.requires_grad = True\n\n# Move to device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = model.to(device)\n\n# Set up optimizer and loss\noptimizer = torch.optim.Adam(\n    filter(lambda p: p.requires_grad, model.parameters()), lr=0.001\n)\ncriterion = nn.CrossEntropyLoss()\n\nscheduler = ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T18:53:29.343061Z","iopub.execute_input":"2025-10-27T18:53:29.343367Z","iopub.status.idle":"2025-10-27T18:53:29.450644Z","shell.execute_reply.started":"2025-10-27T18:53:29.343346Z","shell.execute_reply":"2025-10-27T18:53:29.449861Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"n_epochs = 15\ntrain_losses, val_losses = [], []\ntrain_accuracies, val_accuracies = [], []\n\nfor epoch in range(n_epochs):\n    # Training\n    model.train()\n    running_loss, running_correct, running_total = 0.0, 0, 0\n    for images, labels in trainloader:\n        images, labels = images.to(device), labels.to(device)\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        running_loss += loss.item() * images.size(0)\n        _, preds = torch.max(outputs, 1)\n        running_correct += (preds == labels).sum().item()\n        running_total += labels.size(0)\n    train_loss = running_loss / running_total\n    train_acc = running_correct / running_total\n    scheduler.step(train_acc) # LR decay\n    train_losses.append(train_loss)\n    train_accuracies.append(train_acc)\n\n    # Validation\n    model.eval()\n    val_running_loss, val_running_correct, val_running_total = 0.0, 0, 0\n    with torch.no_grad():\n        for images, labels in testloader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            val_running_loss += loss.item() * images.size(0)\n            _, preds = torch.max(outputs, 1)\n            val_running_correct += (preds == labels).sum().item()\n            val_running_total += labels.size(0)\n    val_loss = val_running_loss / val_running_total\n    val_acc = val_running_correct / val_running_total\n    val_losses.append(val_loss)\n    val_accuracies.append(val_acc)\n\n    print(f\"Epoch {epoch+1}/{n_epochs} | Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f} - Current LR: {optimizer.param_groups[0]['lr']}\")\n\n# --- Plotting ---\nplt.figure(figsize=(12,5))\nplt.subplot(1,2,1)\nplt.plot(train_losses, label=\"Train Loss\")\nplt.plot(val_losses, label=\"Val Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.title(\"Loss Curves\")\n\nplt.subplot(1,2,2)\nplt.plot(train_accuracies, label=\"Train Acc\")\nplt.plot(val_accuracies, label=\"Val Acc\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.title(\"Accuracy Curves\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T18:53:33.123787Z","iopub.execute_input":"2025-10-27T18:53:33.124059Z","iopub.status.idle":"2025-10-27T19:12:12.207874Z","shell.execute_reply.started":"2025-10-27T18:53:33.124038Z","shell.execute_reply":"2025-10-27T19:12:12.207040Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save({\n    'epoch': epoch+1,\n    'model_state_dict': model.state_dict(),\n    'optimizer_state_dict': optimizer.state_dict(),\n    'train_losses': train_losses,\n    'val_losses': val_losses,\n    'train_accuracies': train_accuracies,\n    'val_accuracies': val_accuracies\n}, 'checkpoint.pth')\n\ncheckpoint = torch.load('checkpoint.pth')\nmodel.load_state_dict(checkpoint['model_state_dict'])\n\n# Create a new optimizer with the new learning rate\nnew_lr = 0.002  \noptimizer = torch.optim.Adam(\n    filter(lambda p: p.requires_grad, model.parameters()),\n    lr=new_lr\n)\n# Load previous optimizer state\noptimizer.load_state_dict(checkpoint['optimizer_state_dict'])\nfor param_group in optimizer.param_groups:\n    param_group['lr'] = new_lr\n\n# Set the epoch to start from where it was left off\nstart_epoch = checkpoint['epoch'] ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T19:16:12.308337Z","iopub.execute_input":"2025-10-27T19:16:12.308668Z","iopub.status.idle":"2025-10-27T19:16:12.459224Z","shell.execute_reply.started":"2025-10-27T19:16:12.308645Z","shell.execute_reply":"2025-10-27T19:16:12.458660Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"extra_epochs = 15\n\nfor epoch in range(start_epoch, start_epoch + extra_epochs):\n    # Training\n    model.train()\n    running_loss, running_correct, running_total = 0.0, 0, 0\n    for images, labels in trainloader:\n        images, labels = images.to(device), labels.to(device)\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        running_loss += loss.item() * images.size(0)\n        _, preds = torch.max(outputs, 1)\n        running_correct += (preds == labels).sum().item()\n        running_total += labels.size(0)\n    train_loss = running_loss / running_total\n    train_acc = running_correct / running_total\n    scheduler.step(train_acc) # LR decay\n    train_losses.append(train_loss)\n    train_accuracies.append(train_acc)\n\n    # Validation\n    model.eval()\n    val_running_loss, val_running_correct, val_running_total = 0.0, 0, 0\n    with torch.no_grad():\n        for images, labels in testloader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            val_running_loss += loss.item() * images.size(0)\n            _, preds = torch.max(outputs, 1)\n            val_running_correct += (preds == labels).sum().item()\n            val_running_total += labels.size(0)\n    val_loss = val_running_loss / val_running_total\n    val_acc = val_running_correct / val_running_total\n    val_losses.append(val_loss)\n    val_accuracies.append(val_acc)\n\n    print(f\"Epoch {epoch}/{start_epoch + extra_epochs - 1} | Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f} - Current LR: {optimizer.param_groups[0]['lr']}\")\n\n# --- Plotting ---\nplt.figure(figsize=(12,5))\nplt.subplot(1,2,1)\nplt.plot(train_losses, label=\"Train Loss\")\nplt.plot(val_losses, label=\"Val Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.title(\"Loss Curves\")\n\nplt.subplot(1,2,2)\nplt.plot(train_accuracies, label=\"Train Acc\")\nplt.plot(val_accuracies, label=\"Val Acc\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.title(\"Accuracy Curves\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T19:16:15.800424Z","iopub.execute_input":"2025-10-27T19:16:15.800982Z","iopub.status.idle":"2025-10-27T19:35:07.340981Z","shell.execute_reply.started":"2025-10-27T19:16:15.800961Z","shell.execute_reply":"2025-10-27T19:35:07.340158Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Took approx. 35 min to run 30 epochs in Kaggle's servers utilizing GPU P100.","metadata":{}}]}