{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Extract train images\nos.system(\"7z x /kaggle/input/competitions/cifar-10/train.7z -o/kaggle/working/\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision.transforms as transforms\nimport torchvision.models as models\nfrom torch.utils.data import Dataset, DataLoader","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nEPOCHS      = 15\nBATCH_SIZE  = 64\nRANDOM_SEED = 42\nDEVICE      = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nTRAIN_IMG_DIR   = \"/kaggle/working/train\"\nTRAIN_LABEL_CSV = \"/kaggle/input/competitions/cifar-10/trainLabels.csv\"\n\ntorch.manual_seed(RANDOM_SEED)\nnp.random.seed(RANDOM_SEED)\nprint(f\"Device : {DEVICE}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nclass KaggleCIFAR10(Dataset):\n    def __init__(self, image_dir, labels_df, transform=None):\n        self.image_dir    = image_dir\n        self.labels_df    = labels_df.reset_index(drop=True)\n        self.transform    = transform\n        self.class_to_idx = {\n            c: i for i, c in enumerate(sorted(labels_df['label'].unique()))\n        }\n        self.idx_to_class = {v: k for k, v in self.class_to_idx.items()}\n\n    def __len__(self):\n        return len(self.labels_df)\n\n    def __getitem__(self, idx):\n        img_id = self.labels_df.loc[idx, 'id']\n        label  = self.labels_df.loc[idx, 'label']\n        image  = Image.open(\n            os.path.join(self.image_dir, f\"{img_id}.png\")\n        ).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\n        return image, self.class_to_idx[label]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---- Transforms ----\n# CIFAR-10 images are 32x32 — pretrained models expect 224x224\n# So we resize + use ImageNet normalization stats\n\ntrain_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomCrop(224, padding=16),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],   # ImageNet stats\n                         std =[0.229, 0.224, 0.225])\n])\n\neval_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                         std =[0.229, 0.224, 0.225])\n])\n\n# ---- Load & Split ----\nlabels_df = pd.read_csv(TRAIN_LABEL_CSV)\n\ntrain_val_df, test_df = train_test_split(\n    labels_df, test_size=0.15,\n    random_state=RANDOM_SEED, stratify=labels_df['label']\n)\ntrain_df, val_df = train_test_split(\n    train_val_df, test_size=0.176,\n    random_state=RANDOM_SEED, stratify=train_val_df['label']\n)\n\ntrain_df = train_df.reset_index(drop=True)\nval_df   = val_df.reset_index(drop=True)\ntest_df  = test_df.reset_index(drop=True)\n\nprint(f\"Train: {len(train_df)} | Val: {len(val_df)} | Test: {len(test_df)}\")\n\ntrain_dataset = KaggleCIFAR10(TRAIN_IMG_DIR, train_df, transform=train_transform)\nval_dataset   = KaggleCIFAR10(TRAIN_IMG_DIR, val_df,   transform=eval_transform)\ntest_dataset  = KaggleCIFAR10(TRAIN_IMG_DIR, test_df,  transform=eval_transform)\n\nCLASS_NAMES = list(train_dataset.class_to_idx.keys())\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True,  num_workers=2, pin_memory=True)\nval_loader   = DataLoader(val_dataset,   batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\ntest_loader  = DataLoader(test_dataset,  batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 2. MODEL BUILDER\n# ============================================================\n\ndef build_model(model_name, strategy=\"finetune\", num_classes=10):\n    \"\"\"\n    strategy:\n      'finetune'  — pretrained backbone, unfreeze all, train everything\n      'feature'   — pretrained backbone, freeze all except final head\n    \"\"\"\n\n    if model_name == \"mobilenetv2\":\n        model = models.mobilenet_v2(weights=models.MobileNet_V2_Weights.IMAGENET1K_V1)\n        in_features = model.classifier[1].in_features\n        model.classifier = nn.Sequential(\n            nn.Dropout(0.3),\n            nn.Linear(in_features, num_classes)\n        )\n\n    elif model_name == \"efficientnet_b0\":\n        model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.IMAGENET1K_V1)\n        in_features = model.classifier[1].in_features\n        model.classifier = nn.Sequential(\n            nn.Dropout(0.3),\n            nn.Linear(in_features, num_classes)\n        )\n\n    else:\n        raise ValueError(f\"Unknown model: {model_name}\")\n\n    # Freeze backbone if feature extraction mode\n    if strategy == \"feature\":\n        for name, param in model.named_parameters():\n            if \"classifier\" not in name:\n                param.requires_grad = False\n\n    total  = sum(p.numel() for p in model.parameters())\n    trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)\n    print(f\"  [{model_name} | {strategy}] Total params: {total:,} | Trainable: {trainable:,}\")\n\n    return model.to(DEVICE)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 3. TRAIN / EVAL FUNCTIONS\n# ============================================================\n\ndef train_one_epoch(model, loader, optimizer, criterion):\n    model.train()\n    total_loss, correct, total = 0.0, 0, 0\n    for images, labels in loader:\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        total_loss += loss.item()\n        correct    += outputs.argmax(1).eq(labels).sum().item()\n        total      += labels.size(0)\n    return total_loss / len(loader), 100.0 * correct / total\n\n\ndef evaluate(model, loader, criterion):\n    model.eval()\n    total_loss, correct, total = 0.0, 0, 0\n    with torch.no_grad():\n        for images, labels in loader:\n            images, labels = images.to(DEVICE), labels.to(DEVICE)\n            outputs = model(images)\n            loss    = criterion(outputs, labels)\n            total_loss += loss.item()\n            correct    += outputs.argmax(1).eq(labels).sum().item()\n            total      += labels.size(0)\n    return total_loss / len(loader), 100.0 * correct / total\n\n\ndef get_preds(model, loader):\n    model.eval()\n    all_preds, all_labels = [], []\n    with torch.no_grad():\n        for images, labels in loader:\n            images = images.to(DEVICE)\n            preds  = model(images).argmax(1).cpu().numpy()\n            all_preds.extend(preds)\n            all_labels.extend(labels.numpy())\n    return np.array(all_labels), np.array(all_preds)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ============================================================\n# 4. EXPERIMENT DEFINITIONS\n# ============================================================\n\n# 4 experiments total:\n# MobileNetV2  — feature extraction (frozen backbone)\n# MobileNetV2  — full fine-tuning\n# EfficientNet-B0 — feature extraction\n# EfficientNet-B0 — full fine-tuning\n\nexperiments = [\n    {\n        \"name\"      : \"MobileNetV2 — Feature Extraction\",\n        \"model_name\": \"mobilenetv2\",\n        \"strategy\"  : \"feature\",\n    },\n    {\n        \"name\"      : \"MobileNetV2 — Fine-Tune\",\n        \"model_name\": \"mobilenetv2\",\n        \"strategy\"  : \"finetune\",\n    },\n    {\n        \"name\"      : \"EfficientNet-B0 — Feature Extraction\",\n        \"model_name\": \"efficientnet_b0\",\n        \"strategy\"  : \"feature\",\n    },\n    {\n        \"name\"      : \"EfficientNet-B0 — Fine-Tune\",\n        \"model_name\": \"efficientnet_b0\",\n        \"strategy\"  : \"finetune\",\n    },\n]\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 5. RUN ALL EXPERIMENTS\n# ============================================================\n\nall_results = {}\ncriterion   = nn.CrossEntropyLoss()\n\nfor exp in experiments:\n    print(f\"\\n{'='*60}\")\n    print(f\"  {exp['name']}\")\n    print(f\"{'='*60}\")\n\n    model = build_model(exp[\"model_name\"], exp[\"strategy\"])\n\n    # Use lower LR for finetune (backbone already trained)\n    # Higher LR okay for feature extraction (only head trains)\n    lr = 1e-3 if exp[\"strategy\"] == \"feature\" else 1e-4\n\n    optimizer = optim.AdamW(\n        filter(lambda p: p.requires_grad, model.parameters()),\n        lr=lr, weight_decay=1e-4\n    )\n    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n\n    train_losses, val_losses = [], []\n    train_accs,   val_accs   = [], []\n    lr_history               = []\n\n    best_val_loss  = float('inf')\n    best_model_wts = None\n    patience_count = 0\n    PATIENCE       = 4\n\n    for epoch in range(1, EPOCHS + 1):\n        tr_loss, tr_acc   = train_one_epoch(model, train_loader, optimizer, criterion)\n        val_loss, val_acc = evaluate(model, val_loader, criterion)\n        scheduler.step()\n\n        train_losses.append(tr_loss)\n        val_losses.append(val_loss)\n        train_accs.append(tr_acc)\n        val_accs.append(val_acc)\n        lr_history.append(optimizer.param_groups[0]['lr'])\n\n        if val_loss < best_val_loss:\n            best_val_loss  = val_loss\n            best_model_wts = {k: v.clone() for k, v in model.state_dict().items()}\n            patience_count = 0\n        else:\n            patience_count += 1\n\n        print(f\"  Epoch {epoch:02d}/{EPOCHS} | \"\n              f\"Train Loss: {tr_loss:.4f}  Acc: {tr_acc:.1f}% | \"\n              f\"Val Loss: {val_loss:.4f}  Acc: {val_acc:.1f}%\")\n\n        if patience_count >= PATIENCE:\n            print(f\"  Early stopping at epoch {epoch}\")\n            break\n\n    # Load best weights & evaluate on test\n    model.load_state_dict(best_model_wts)\n    true_labels, pred_labels = get_preds(model, test_loader)\n    test_acc = 100.0 * (true_labels == pred_labels).mean()\n\n    all_results[exp[\"name\"]] = {\n        \"train_losses\" : train_losses,\n        \"val_losses\"   : val_losses,\n        \"train_accs\"   : train_accs,\n        \"val_accs\"     : val_accs,\n        \"lr_history\"   : lr_history,\n        \"best_val_loss\": best_val_loss,\n        \"test_acc\"     : test_acc,\n        \"true_labels\"  : true_labels,\n        \"pred_labels\"  : pred_labels,\n    }\n\n    print(f\"\\n  Best Val Loss : {best_val_loss:.4f}\")\n    print(f\"  Test Accuracy : {test_acc:.2f}%\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 6. PLOTS\n# ============================================================\n\n# ---- 6a. Loss & Accuracy Curves ----\nn = len(all_results)\nfig, axes = plt.subplots(n, 2, figsize=(14, 4.5 * n))\nfig.suptitle(\"Transfer Learning — Loss & Accuracy Curves\", fontsize=14, fontweight='bold', y=1.01)\n\nfor i, (name, res) in enumerate(all_results.items()):\n    ep = range(1, len(res[\"train_losses\"]) + 1)\n\n    ax = axes[i][0]\n    ax.plot(ep, res[\"train_losses\"], label=\"Train Loss\", color=\"steelblue\", linewidth=2)\n    ax.plot(ep, res[\"val_losses\"],   label=\"Val Loss\",   color=\"tomato\",    linewidth=2, linestyle=\"--\")\n    ax.set_title(f\"{name}\\nLoss Curve\", fontsize=10)\n    ax.set_xlabel(\"Epoch\"); ax.set_ylabel(\"Loss\")\n    ax.legend(); ax.grid(True, alpha=0.3)\n\n    ax = axes[i][1]\n    ax.plot(ep, res[\"train_accs\"], label=\"Train Acc\", color=\"steelblue\", linewidth=2)\n    ax.plot(ep, res[\"val_accs\"],   label=\"Val Acc\",   color=\"tomato\",    linewidth=2, linestyle=\"--\")\n    ax.set_title(f\"{name}\\nAccuracy  (Test: {res['test_acc']:.1f}%)\", fontsize=10)\n    ax.set_xlabel(\"Epoch\"); ax.set_ylabel(\"Accuracy (%)\")\n    ax.legend(); ax.grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.savefig(\"tl_01_curves.png\", dpi=150, bbox_inches='tight')\nplt.show()\nprint(\"Saved: tl_01_curves.png\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---- 6b. Feature Extraction vs Fine-Tune side-by-side ----\nfig, axes = plt.subplots(2, 2, figsize=(14, 10))\nfig.suptitle(\"Feature Extraction vs Fine-Tuning\", fontsize=13, fontweight='bold')\n\nmodel_pairs = [\n    (\"MobileNetV2 — Feature Extraction\", \"MobileNetV2 — Fine-Tune\",     \"MobileNetV2\"),\n    (\"EfficientNet-B0 — Feature Extraction\", \"EfficientNet-B0 — Fine-Tune\", \"EfficientNet-B0\"),\n]\n\ncolors = {\"Feature Extraction\": (\"royalblue\", \"cornflowerblue\"),\n          \"Fine-Tune\"         : (\"tomato\",    \"lightsalmon\")}\n\nfor col, (feat_name, ft_name, title) in enumerate(model_pairs):\n    feat = all_results[feat_name]\n    ft   = all_results[ft_name]\n\n    # Val Loss comparison\n    ax = axes[0][col]\n    ep_f  = range(1, len(feat[\"val_losses\"]) + 1)\n    ep_ft = range(1, len(ft[\"val_losses\"])   + 1)\n    ax.plot(ep_f,  feat[\"val_losses\"], label=\"Feature Extraction\", color=\"royalblue\",  linewidth=2)\n    ax.plot(ep_ft, ft[\"val_losses\"],   label=\"Fine-Tune\",          color=\"tomato\",     linewidth=2)\n    ax.set_title(f\"{title} — Val Loss\")\n    ax.set_xlabel(\"Epoch\"); ax.set_ylabel(\"Val Loss\")\n    ax.legend(); ax.grid(True, alpha=0.3)\n\n    # Val Accuracy comparison\n    ax = axes[1][col]\n    ax.plot(ep_f,  feat[\"val_accs\"], label=f\"Feature Extraction ({feat['test_acc']:.1f}%)\", color=\"royalblue\", linewidth=2)\n    ax.plot(ep_ft, ft[\"val_accs\"],   label=f\"Fine-Tune ({ft['test_acc']:.1f}%)\",            color=\"tomato\",    linewidth=2)\n    ax.set_title(f\"{title} — Val Accuracy\")\n    ax.set_xlabel(\"Epoch\"); ax.set_ylabel(\"Val Accuracy (%)\")\n    ax.legend(); ax.grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.savefig(\"tl_02_feature_vs_finetune.png\", dpi=150)\nplt.show()\nprint(\"Saved: tl_02_feature_vs_finetune.png\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---- 6c. Summary Bar Chart ----\nnames     = list(all_results.keys())\ntest_accs = [all_results[n][\"test_acc\"] for n in names]\nshort_names = [\"MNV2\\nFeature\", \"MNV2\\nFinetune\", \"EffB0\\nFeature\", \"EffB0\\nFinetune\"]\n\nfig, ax = plt.subplots(figsize=(10, 5))\nbar_colors = [\"royalblue\", \"tomato\", \"royalblue\", \"tomato\"]\nbars = ax.bar(short_names, test_accs, color=bar_colors, alpha=0.85, width=0.5)\n\nax.set_ylabel(\"Test Accuracy (%)\")\nax.set_title(\"Final Test Accuracy — All Transfer Learning Experiments\")\nax.set_ylim(0, 100)\nax.grid(True, alpha=0.3, axis='y')\n\nfor bar, acc in zip(bars, test_accs):\n    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,\n            f'{acc:.1f}%', ha='center', va='bottom', fontweight='bold')\n\n# Legend\nfrom matplotlib.patches import Patch\nlegend_elements = [Patch(facecolor='royalblue', label='Feature Extraction'),\n                   Patch(facecolor='tomato',    label='Fine-Tune')]\nax.legend(handles=legend_elements)\n\nplt.tight_layout()\nplt.savefig(\"tl_03_summary_bar.png\", dpi=150)\nplt.show()\nprint(\"Saved: tl_03_summary_bar.png\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---- 6d. Confusion Matrix — Best Model ----\nbest_exp_name = max(all_results, key=lambda k: all_results[k][\"test_acc\"])\nres = all_results[best_exp_name]\n\ncm      = confusion_matrix(res[\"true_labels\"], res[\"pred_labels\"])\ncm_norm = cm.astype(float) / cm.sum(axis=1, keepdims=True)\n\nfig, axes = plt.subplots(1, 2, figsize=(16, 6))\nfig.suptitle(f\"Confusion Matrix — Best Model: {best_exp_name}\", fontsize=12, fontweight='bold')\n\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n            xticklabels=CLASS_NAMES, yticklabels=CLASS_NAMES, ax=axes[0])\naxes[0].set_title(\"Raw Counts\")\naxes[0].set_xlabel(\"Predicted\"); axes[0].set_ylabel(\"True\")\n\nsns.heatmap(cm_norm, annot=True, fmt='.2f', cmap='Blues',\n            xticklabels=CLASS_NAMES, yticklabels=CLASS_NAMES, ax=axes[1])\naxes[1].set_title(\"Normalized (Recall per class)\")\naxes[1].set_xlabel(\"Predicted\"); axes[1].set_ylabel(\"True\")\n\nplt.tight_layout()\nplt.savefig(\"tl_04_confusion_matrix.png\", dpi=150)\nplt.show()\nprint(\"Saved: tl_04_confusion_matrix.png\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---- 6e. Per-Class Accuracy ----\nper_class_acc = cm_norm.diagonal() * 100\n\nfig, ax = plt.subplots(figsize=(10, 4))\nbars = ax.bar(CLASS_NAMES, per_class_acc, color='steelblue', alpha=0.85)\nax.axhline(per_class_acc.mean(), color='tomato', linestyle='--', linewidth=2,\n           label=f'Mean: {per_class_acc.mean():.1f}%')\nax.set_title(f\"Per-Class Accuracy — {best_exp_name}\")\nax.set_ylabel(\"Accuracy (%)\"); ax.set_ylim(0, 100)\nax.legend(); ax.grid(True, alpha=0.3, axis='y')\n\nfor bar, acc in zip(bars, per_class_acc):\n    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,\n            f'{acc:.1f}', ha='center', va='bottom', fontsize=8)\n\nplt.tight_layout()\nplt.savefig(\"tl_05_per_class_acc.png\", dpi=150)\nplt.show()\nprint(\"Saved: tl_05_per_class_acc.png\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":" ============================================================\n# 7. METRICS — ALL EXPERIMENTS\n# ============================================================\n\nprint(f\"\\n{'='*60}\")\nprint(\"CLASSIFICATION REPORT — BEST MODEL\")\nprint(f\"{'='*60}\")\nprint(classification_report(res[\"true_labels\"], res[\"pred_labels\"], target_names=CLASS_NAMES))\n\nprint(f\"\\n{'='*65}\")\nprint(\"FINAL SUMMARY TABLE\")\nprint(f\"{'='*65}\")\nprint(f\"{'Experiment':<40} {'Best Val Loss':>13} {'Test Acc':>10}\")\nprint(\"-\" * 65)\nfor name, res in all_results.items():\n    print(f\"{name:<40} {res['best_val_loss']:>13.4f} {res['test_acc']:>9.2f}%\")\n\nbest = max(all_results, key=lambda k: all_results[k][\"test_acc\"])\nprint(f\"\\n Best Model : {best}\")\nprint(f\" Test Acc   : {all_results[best]['test_acc']:.2f}%\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}