{ "cells": [ { "cell_type": "markdown", "id": "08615cee", "metadata": {}, "source": [ "# 03 — ResNet50 Transfer Learning\n", "\n", "Fine-tune a pretrained ResNet50 (ImageNet) for cattle breed classification.\n", "\n", "### Two-Phase Training\n", "1. **Phase 1** (10 epochs): Freeze backbone, train only the classifier head\n", "2. **Phase 2** (20 epochs): Unfreeze `layer3` + `layer4`, fine-tune with lower LR\n", "\n", "### Expected Output\n", "- Best checkpoint → `ml/artifacts/checkpoints/resnet_best.pth`\n", "- Training curves, confusion matrix, per-class F1\n", "- Classification report and training summary JSON" ] }, { "cell_type": "code", "execution_count": 1, "id": "457f79bb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Project root: /Users/ajitkumarsingh/Desktop/desktop/cattle-breed-classifier-webapp\n" ] } ], "source": [ "import sys, os\n", "from pathlib import Path\n", "\n", "PROJECT_ROOT = Path(os.getcwd()).resolve()\n", "if 'notebooks' in str(PROJECT_ROOT):\n", " PROJECT_ROOT = PROJECT_ROOT.parent.parent\n", "sys.path.insert(0, str(PROJECT_ROOT))\n", "print(f'Project root: {PROJECT_ROOT}')" ] }, { "cell_type": "code", "execution_count": 2, "id": "ae58ea25", "metadata": {}, "outputs": [ { "ename": "InterruptedError", "evalue": "[Errno 4] Interrupted system call", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mInterruptedError\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 4\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m ml.src.utils.seed \u001b[38;5;28;01mimport\u001b[39;00m set_seed\n\u001b[32m 2\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m ml.src.utils.device \u001b[38;5;28;01mimport\u001b[39;00m get_device\n\u001b[32m 3\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m ml.src.utils.io \u001b[38;5;28;01mimport\u001b[39;00m load_config\n\u001b[32m----> \u001b[39m\u001b[32m4\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m ml.src.utils.manifests \u001b[38;5;28;01mimport\u001b[39;00m create_dataloaders\n\u001b[32m 5\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m ml.src.data.transforms \u001b[38;5;28;01mimport\u001b[39;00m get_train_transforms, get_eval_transforms\n\u001b[32m 6\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m ml.src.models.resnet \u001b[38;5;28;01mimport\u001b[39;00m CattleResNet\n\u001b[32m 7\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m ml.src.training.trainer \u001b[38;5;28;01mimport\u001b[39;00m Trainer\n", "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/desktop/cattle-breed-classifier-webapp/ml/src/utils/manifests.py:10\u001b[39m\n\u001b[32m 7\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mtyping\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Optional\n\u001b[32m 9\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mpandas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mpd\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m10\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mPIL\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Image\n\u001b[32m 11\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mtorch\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mutils\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mdata\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Dataset, DataLoader\n\u001b[32m 12\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mtorchvision\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m transforms\n", "\u001b[36mFile \u001b[39m\u001b[32m:1360\u001b[39m, in \u001b[36m_find_and_load\u001b[39m\u001b[34m(name, import_)\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32m:1322\u001b[39m, in \u001b[36m_find_and_load_unlocked\u001b[39m\u001b[34m(name, import_)\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32m:1262\u001b[39m, in \u001b[36m_find_spec\u001b[39m\u001b[34m(name, path, target)\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32m:1532\u001b[39m, in \u001b[36mPathFinder.find_spec\u001b[39m\u001b[34m(cls, fullname, path, target)\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32m:1504\u001b[39m, in \u001b[36mPathFinder._get_spec\u001b[39m\u001b[34m(cls, fullname, path, target)\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32m:1484\u001b[39m, in \u001b[36mPathFinder._path_importer_cache\u001b[39m\u001b[34m(cls, path)\u001b[39m\n", "\u001b[31mInterruptedError\u001b[39m: [Errno 4] Interrupted system call" ] } ], "source": [ "from ml.src.utils.seed import set_seed\n", "from ml.src.utils.device import get_device\n", "from ml.src.utils.io import load_config\n", "from ml.src.utils.manifests import create_dataloaders\n", "from ml.src.data.transforms import get_train_transforms, get_eval_transforms\n", "from ml.src.models.resnet import CattleResNet\n", "from ml.src.training.trainer import Trainer\n", "\n", "set_seed(42)\n", "device = get_device()" ] }, { "cell_type": "markdown", "id": "c4d85602", "metadata": {}, "source": [ "## 1. Load Config & Data" ] }, { "cell_type": "code", "execution_count": null, "id": "44de86e3", "metadata": {}, "outputs": [], "source": [ "config = load_config('resnet', PROJECT_ROOT / 'ml' / 'configs')\n", "config['num_classes'] = 26\n", "\n", "img_size = config['image']['size']\n", "batch_size = config['training']['batch_size']\n", "arch = config['model']['architecture']\n", "\n", "print(f'Backbone: {arch[\"backbone\"]}')\n", "print(f'Pretrained: {arch[\"pretrained\"]}')\n", "print(f'Freeze backbone: {arch[\"freeze_backbone\"]}')\n", "print(f'Unfreeze after: {arch[\"unfreeze_after_epochs\"]} epochs')\n", "print(f'Unfreeze layers: {arch[\"unfreeze_layers\"]}')\n", "print(f'Epochs: {config[\"training\"][\"num_epochs\"]}')\n", "print(f'LR: {config[\"training\"][\"learning_rate\"]} Fine-tune LR: {config[\"training\"][\"fine_tune_lr\"]}')" ] }, { "cell_type": "code", "execution_count": null, "id": "a2807dbd", "metadata": {}, "outputs": [], "source": [ "train_transforms = get_train_transforms(img_size=img_size)\n", "eval_transforms = get_eval_transforms(img_size=img_size)\n", "\n", "dataloaders = create_dataloaders(\n", " manifests_dir=PROJECT_ROOT / 'ml' / 'artifacts' / 'manifests',\n", " data_root=PROJECT_ROOT / 'Cattle_Resized',\n", " train_transform=train_transforms,\n", " eval_transform=eval_transforms,\n", " batch_size=batch_size,\n", " num_workers=config['data'].get('num_workers', 4),\n", ")\n", "\n", "class_names = dataloaders['train'].dataset.classes\n", "print(f'Classes: {len(class_names)}')\n", "print(f'Train: {len(dataloaders[\"train\"].dataset)} | Val: {len(dataloaders[\"val\"].dataset)} | Test: {len(dataloaders[\"test\"].dataset)}')" ] }, { "cell_type": "markdown", "id": "62801587", "metadata": {}, "source": [ "## 2. Create Model" ] }, { "cell_type": "code", "execution_count": null, "id": "60fd0584", "metadata": {}, "outputs": [], "source": [ "model = CattleResNet.from_config(config)\n", "\n", "# Count trainable vs frozen params\n", "trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)\n", "total = sum(p.numel() for p in model.parameters())\n", "print(f'Total parameters: {total:,}')\n", "print(f'Trainable (Phase 1): {trainable:,} ({100*trainable/total:.1f}%)')\n", "print(f'Frozen: {total - trainable:,}')" ] }, { "cell_type": "markdown", "id": "3a783655", "metadata": {}, "source": [ "## 3. Two-Phase Training" ] }, { "cell_type": "code", "execution_count": null, "id": "077006b5", "metadata": {}, "outputs": [], "source": [ "trainer = Trainer(\n", " model=model,\n", " config=config,\n", " dataloaders=dataloaders,\n", " class_names=class_names,\n", " device=device,\n", " model_name='resnet',\n", ")\n", "\n", "# Two-phase training: frozen backbone → partial fine-tuning\n", "training_summary = trainer.train_with_phase_switch(\n", " phase1_epochs=arch['unfreeze_after_epochs'],\n", " phase2_lr=config['training']['fine_tune_lr'],\n", " unfreeze_layers=arch['unfreeze_layers'],\n", ")" ] }, { "cell_type": "markdown", "id": "c62c99fb", "metadata": {}, "source": [ "## 4. Evaluate on Test Set" ] }, { "cell_type": "code", "execution_count": null, "id": "b3c413c6", "metadata": {}, "outputs": [], "source": [ "test_results = trainer.evaluate(split='test')" ] }, { "cell_type": "markdown", "id": "98c3ee02", "metadata": {}, "source": [ "## 5. Save Artifacts" ] }, { "cell_type": "code", "execution_count": null, "id": "97f96e44", "metadata": {}, "outputs": [], "source": [ "trainer.save_artifacts()\n", "\n", "print('\\n=== ResNet50 Transfer Learning Summary ===')\n", "print(f'Best Val Loss: {training_summary[\"best_val_loss\"]:.4f}')\n", "print(f'Best Val Acc: {training_summary[\"best_val_accuracy\"]:.4f}')\n", "print(f'Test Accuracy: {test_results[\"metrics\"][\"accuracy\"]:.4f}')\n", "print(f'Test Macro F1: {test_results[\"metrics\"][\"macro_f1\"]:.4f}')\n", "print(f'Latency: {test_results[\"latency\"][\"avg_ms\"]:.2f} ms')\n", "print(f'Model Size: {test_results[\"model_size_mb\"]:.2f} MB')" ] }, { "cell_type": "markdown", "id": "f5362b63", "metadata": {}, "source": [ "## 6. Classification Report" ] }, { "cell_type": "code", "execution_count": null, "id": "2f3951b2", "metadata": {}, "outputs": [], "source": [ "print(test_results['metrics']['classification_report'])" ] }, { "cell_type": "markdown", "id": "764286aa", "metadata": {}, "source": [ "---\n", "**✅ ResNet50 Transfer Learning complete.** Proceed to `04_vit_transfer_learning.ipynb`." ] } ], "metadata": { "kernelspec": { "display_name": "Cattle Classifier (Python 3.12)", "language": "python", "name": "cattle-classifier" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.13" } }, "nbformat": 4, "nbformat_minor": 5 }