{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# MSBA 6420 – Homework 4 (Q2): Image Classification with ResNet50V2  \n**Author:** Utkarsh Joshi - 5982808  \n**Kaggle Score:** `0.06588`  \n**Code in Kaggle Repo:** https://www.kaggle.com/code/utkarsh1611/hw4-q2/edit \n\n---\n\n## Process Flow\n\n### 1) Imports & Constants\n- Loaded essential libraries: `tensorflow`, `numpy`, `pandas`, `matplotlib`, `os`, etc.\n- Defined constants:  \n  - `IMG_SIZE = 224`  \n  - `BATCH_SIZE = 32`  \n  - `BASE_LR = 1e-4`  \n  - `EPOCHS = 5`  \n\n---\n\n### 2) Unzipping Files\n- Extracted `train.zip` and `test.zip` from Kaggle input directory into a working directory.\n\n---\n\n### 3) Set Paths & Validate Image Counts\n- Defined `TRAIN_DIR` and `TEST_DIR`.\n- Counted `.jpg` files to verify successful extraction.\n\n---\n\n### 4) Create DataFrame\n- Parsed filenames to assign labels (`dog` or `cat`).\n- Visualized class distribution using a bar plot.\n- Displayed 8 random sample images from training data.\n\n---\n\n### 5) Train-Validation Split\n- Split the data using `train_test_split` (80/20) with stratification on labels to ensure balanced class representation.\n\n---\n\n### 6) Data Augmentation\n- **Training Generator:** Included real-time augmentations like rotation, shift, zoom, flip, etc.\n- **Validation Generator:** Rescaling only.\n\n---\n\n### 7) Model Architecture (Phase 1: Head Only)\n- Used `ResNet50V2` pretrained on ImageNet with `include_top=False`.\n- Appended:\n  - `GlobalAveragePooling2D`\n  - `Dropout(0.5)`\n  - `Dense(1, activation='sigmoid')`\n- **Base model frozen** during this phase.\n\n---\n\n### 8) Callbacks\n- Added:\n  - `ReduceLROnPlateau` (reduce LR on plateauing val loss)\n  - `EarlyStopping` (patience of 7 with best weights restoration)\n\n---\n\n### 9) Phase 1 Training (Head Only)\n- Trained for **5 epochs** on frozen base model.\n\n---\n\n### 10) Fine-Tuning the Base Model\n- Unfroze last 30 layers of the base model.\n- Recompiled with a **lower learning rate** (`1e-5`).\n- Fine-tuned for **2 more epochs**.\n\n---\n\n### 11) Prepare Test Data\n- Created a test DataFrame from filenames.\n- Used an ImageDataGenerator to rescale images.\n- Created a test generator (no shuffling, no labels).\n\n---\n\n### 12) Make Predictions & Submit\n- Predicted using the fine-tuned model on test data.\n- Formatted results to match Kaggle submission requirements:\n  - `id`: numeric part of filename\n  - `label`: predicted probability of being a dog\n- Saved as `submission.csv`.\n\n---\n\n## Final Notes\n- ResNet50V2 transfer learning approach yielded a public **Kaggle score of 0.06588**, indicating strong performance for binary image classification.\n- Minimal overfitting observed due to early stopping and dropout.\n- Could further improve via: more epochs, image resizing, or trying EfficientNet.\n\n---\n","metadata":{}},{"cell_type":"code","source":"# 1) IMPORTS & CONSTANTS\nimport os\nimport zipfile\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, optimizers, callbacks\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom sklearn.model_selection import train_test_split\n\nIMG_SIZE    = 224\nBATCH_SIZE  = 32\nBASE_LR     = 1e-4\nEPOCHS      = 5\n\nINPUT_DIR   = '/kaggle/input/dogs-vs-cats-redux-kernels-edition'\nWORK_DIR    = '/kaggle/working/data'\n\n# 2) UNZIP (only if you haven’t already)\n!unzip -q /kaggle/input/dogs-vs-cats-redux-kernels-edition/train.zip  -d {WORK_DIR}\n!unzip -q /kaggle/input/dogs-vs-cats-redux-kernels-edition/test.zip   -d {WORK_DIR}\n\n# 3) SET PATHS\nTRAIN_DIR = f'{WORK_DIR}/train'\nTEST_DIR  = f'{WORK_DIR}/test'\n\nprint(\"Train JPG count:\", len([f for f in os.listdir(TRAIN_DIR) if f.endswith('.jpg')]))\nprint(\"Test  JPG count:\", len([f for f in os.listdir(TEST_DIR)  if f.endswith('.jpg')]))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-22T21:08:32.877455Z","iopub.execute_input":"2025-04-22T21:08:32.877704Z","iopub.status.idle":"2025-04-22T21:09:08.435396Z","shell.execute_reply.started":"2025-04-22T21:08:32.877677Z","shell.execute_reply":"2025-04-22T21:09:08.433843Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Display sample images\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\n\n# 4) CREATE DF\nfilenames = [f for f in os.listdir(TRAIN_DIR) if f.endswith('.jpg')]\nlabels    = [fn.split('.')[0] for fn in filenames]   # 'dog' or 'cat'\ndf = pd.DataFrame({'filename': filenames, 'label': labels})\n\n# Plot label distribution\ndf['label'].value_counts().plot(kind='bar', color=['skyblue', 'salmon'])\nplt.title(\"Label Distribution\")\nplt.xlabel(\"Class\")\nplt.ylabel(\"Count\")\nplt.grid(False)\nplt.show()\n\nsample_files = np.random.choice(filenames, size=8, replace=False)\n\nplt.figure(figsize=(14, 6))\nfor i, file in enumerate(sample_files):\n    img_path = os.path.join(TRAIN_DIR, file)\n    img = mpimg.imread(img_path)\n    label = file.split('.')[0]\n    \n    plt.subplot(2, 4, i+1)\n    plt.imshow(img)\n    plt.title(label)\n    plt.axis('off')\n    \nplt.tight_layout()\nplt.suptitle(\"Sample Images from Training Set\", fontsize=16, y=1.05)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T21:13:21.461146Z","iopub.execute_input":"2025-04-22T21:13:21.461528Z","iopub.status.idle":"2025-04-22T21:13:23.005508Z","shell.execute_reply.started":"2025-04-22T21:13:21.461499Z","shell.execute_reply":"2025-04-22T21:13:23.004441Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4) BUILD DF\nfilenames = [f for f in os.listdir(TRAIN_DIR) if f.endswith('.jpg')]\nlabels    = [fn.split('.')[0] for fn in filenames]\n\ndf = pd.DataFrame({'filename': filenames, 'label': labels})\ntrain_df, val_df = train_test_split(df, test_size=0.2, stratify=df['label'], random_state=42)\n\n# 5) DATA GENERATORS\ntrain_aug = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=20,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    shear_range=0.1,\n    zoom_range=0.1,\n    horizontal_flip=True,\n    fill_mode='reflect'\n)\nval_aug = ImageDataGenerator(rescale=1./255)\n\ntrain_gen = train_aug.flow_from_dataframe(\n    train_df, TRAIN_DIR,\n    x_col='filename', y_col='label',\n    target_size=(IMG_SIZE, IMG_SIZE),\n    class_mode='binary',\n    batch_size=BATCH_SIZE,\n    seed=42\n)\nval_gen = val_aug.flow_from_dataframe(\n    val_df, TRAIN_DIR,\n    x_col='filename', y_col='label',\n    target_size=(IMG_SIZE, IMG_SIZE),\n    class_mode='binary',\n    batch_size=BATCH_SIZE,\n    seed=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T21:13:27.827645Z","iopub.execute_input":"2025-04-22T21:13:27.827975Z","iopub.status.idle":"2025-04-22T21:13:28.121553Z","shell.execute_reply.started":"2025-04-22T21:13:27.827951Z","shell.execute_reply":"2025-04-22T21:13:28.120526Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 6) BUILD & COMPILE MODEL (PHASE 1: HEAD ONLY)\nfrom tensorflow.keras.applications import ResNet50V2\n\nbase = ResNet50V2(\n    weights='imagenet',\n    include_top=False,\n    input_shape=(IMG_SIZE, IMG_SIZE, 3)\n)\nbase.trainable = False\n\ninp = layers.Input((IMG_SIZE, IMG_SIZE, 3))\nx   = base(inp, training=False)\nx   = layers.GlobalAveragePooling2D()(x)\nx   = layers.Dropout(0.5)(x)\nout = layers.Dense(1, activation='sigmoid')(x)\n\nmodel = models.Model(inp, out)\nmodel.compile(\n    optimizer=optimizers.Adam(learning_rate=1e-4),\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T21:13:32.893155Z","iopub.execute_input":"2025-04-22T21:13:32.893520Z","iopub.status.idle":"2025-04-22T21:13:37.718063Z","shell.execute_reply.started":"2025-04-22T21:13:32.893494Z","shell.execute_reply":"2025-04-22T21:13:37.717145Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 7) CALLBACKS\ncb = [\n    callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, verbose=1),\n    callbacks.EarlyStopping(monitor='val_loss', patience=7, restore_best_weights=True, verbose=1)\n]\n\n# 8) TRAIN HEAD ONLY\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=5,\n    callbacks=cb\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T21:14:12.241953Z","iopub.execute_input":"2025-04-22T21:14:12.242326Z","iopub.status.idle":"2025-04-22T23:56:49.924184Z","shell.execute_reply.started":"2025-04-22T21:14:12.242299Z","shell.execute_reply":"2025-04-22T23:56:49.923121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 9) FINE-TUNE BASE MODEL\nbase.trainable = True\nfor layer in base.layers[:-30]:\n    layer.trainable = False\n\nmodel.compile(\n    optimizer=optimizers.Adam(learning_rate=1e-5),\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)\n\nfine_tune_history = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=2,\n    callbacks=cb\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-22T23:57:49.190063Z","iopub.execute_input":"2025-04-22T23:57:49.190432Z","iopub.status.idle":"2025-04-23T01:26:31.845582Z","shell.execute_reply.started":"2025-04-22T23:57:49.190371Z","shell.execute_reply":"2025-04-23T01:26:31.844500Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 10) PREPARE TEST DATA\ntest_fns = sorted([f for f in os.listdir(TEST_DIR) if f.endswith('.jpg')])\ntest_df  = pd.DataFrame({'filename': test_fns})\ntest_aug = ImageDataGenerator(rescale=1./255)\n\ntest_gen = test_aug.flow_from_dataframe(\n    test_df, TEST_DIR,\n    x_col='filename', y_col=None,\n    target_size=(IMG_SIZE, IMG_SIZE),\n    class_mode=None,\n    batch_size=BATCH_SIZE,\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-23T01:26:49.955355Z","iopub.execute_input":"2025-04-23T01:26:49.955727Z","iopub.status.idle":"2025-04-23T01:26:50.191589Z","shell.execute_reply.started":"2025-04-23T01:26:49.955702Z","shell.execute_reply":"2025-04-23T01:26:50.190193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 11) PREDICT & SUBMIT\npreds = model.predict(test_gen, verbose=1)\n\nsubmission = pd.DataFrame({\n    'id':    [int(fn.split('.')[0]) for fn in test_fns],\n    'label': preds.ravel()\n})\nsubmission.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-23T01:26:53.793169Z","iopub.execute_input":"2025-04-23T01:26:53.793560Z","iopub.status.idle":"2025-04-23T01:43:04.873804Z","shell.execute_reply.started":"2025-04-23T01:26:53.793523Z","shell.execute_reply":"2025-04-23T01:43:04.872494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls -lh /kaggle/working","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-23T01:43:20.632503Z","iopub.execute_input":"2025-04-23T01:43:20.632800Z","iopub.status.idle":"2025-04-23T01:43:20.884015Z","shell.execute_reply.started":"2025-04-23T01:43:20.632781Z","shell.execute_reply":"2025-04-23T01:43:20.882479Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}