{"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":"gpu","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"sourceType":"competition"}],"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,"execution":{"iopub.status.busy":"2026-01-01T16:51:23.055486Z","iopub.execute_input":"2026-01-01T16:51:23.056058Z","iopub.status.idle":"2026-01-01T16:51:23.340008Z","shell.execute_reply.started":"2026-01-01T16:51:23.056031Z","shell.execute_reply":"2026-01-01T16:51:23.339327Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Know Data\n","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\nimport shutil","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:23.341574Z","iopub.execute_input":"2026-01-01T16:51:23.342084Z","iopub.status.idle":"2026-01-01T16:51:23.345537Z","shell.execute_reply.started":"2026-01-01T16:51:23.342057Z","shell.execute_reply":"2026-01-01T16:51:23.344851Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.makedirs(\"/kaggle/working/train\", exist_ok=True)\nos.makedirs(\"/kaggle/working/test\", exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:23.346378Z","iopub.execute_input":"2026-01-01T16:51:23.346642Z","iopub.status.idle":"2026-01-01T16:51:23.357661Z","shell.execute_reply.started":"2026-01-01T16:51:23.34662Z","shell.execute_reply":"2026-01-01T16:51:23.357102Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with zipfile.ZipFile(\"/kaggle/input/dogs-vs-cats/train.zip\", \"r\") as zip:\n    zip.extractall(\"/kaggle/working/train\")\n\nwith zipfile.ZipFile(\"/kaggle/input/dogs-vs-cats/test1.zip\", \"r\") as zip:\n    zip.extractall(\"/kaggle/working/test\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:23.359113Z","iopub.execute_input":"2026-01-01T16:51:23.359405Z","iopub.status.idle":"2026-01-01T16:51:30.908476Z","shell.execute_reply.started":"2026-01-01T16:51:23.359383Z","shell.execute_reply":"2026-01-01T16:51:30.907654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# /kaggle/working/train/train\n# train set will divide into train and validation\ntrain_files = os.listdir(\"/kaggle/working/train/train\")\nprint(train_files[0])\nprint(f\"no. of train images {len(train_files)}\")\n\ntest_files = os.listdir(\"/kaggle/working/test/test1\")\nprint(f\"no. of test images {len(test_files)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:30.909435Z","iopub.execute_input":"2026-01-01T16:51:30.909689Z","iopub.status.idle":"2026-01-01T16:51:30.935166Z","shell.execute_reply.started":"2026-01-01T16:51:30.909664Z","shell.execute_reply":"2026-01-01T16:51:30.934362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_dir = \"/kaggle/working/train/train\"\ncat_dir = \"/kaggle/working/train/cat\"\ndog_dir = \"/kaggle/working/train/dog\"\n\nos.makedirs(cat_dir, exist_ok=True)\nos.makedirs(dog_dir, exist_ok=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:30.936068Z","iopub.execute_input":"2026-01-01T16:51:30.936369Z","iopub.status.idle":"2026-01-01T16:51:30.940784Z","shell.execute_reply.started":"2026-01-01T16:51:30.936329Z","shell.execute_reply":"2026-01-01T16:51:30.940157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for file in os.listdir(base_dir):\n    if file.startswith(\"cat\"):\n        shutil.move(os.path.join(base_dir, file), os.path.join(cat_dir, file))\n    elif file.startswith(\"dog\"):\n        shutil.move(os.path.join(base_dir, file), os.path.join(dog_dir, file))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:30.941647Z","iopub.execute_input":"2026-01-01T16:51:30.941895Z","iopub.status.idle":"2026-01-01T16:51:32.690925Z","shell.execute_reply.started":"2026-01-01T16:51:30.941871Z","shell.execute_reply":"2026-01-01T16:51:32.69009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# \"/kaggle/working/train/\"  has \"cat\" & \"dog\"\nprint(\"Cats\", len(os.listdir(cat_dir)))\nprint(\"Dogs\", len(os.listdir(dog_dir)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:32.692603Z","iopub.execute_input":"2026-01-01T16:51:32.692857Z","iopub.status.idle":"2026-01-01T16:51:32.708721Z","shell.execute_reply.started":"2026-01-01T16:51:32.692836Z","shell.execute_reply":"2026-01-01T16:51:32.708005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls -d /kaggle/working/train/*/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:32.709519Z","iopub.execute_input":"2026-01-01T16:51:32.709744Z","iopub.status.idle":"2026-01-01T16:51:32.830568Z","shell.execute_reply.started":"2026-01-01T16:51:32.709712Z","shell.execute_reply":"2026-01-01T16:51:32.82976Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"empty_dir = \"/kaggle/working/train/train/\"\nif os.path.exists(empty_dir):\n    print(os.listdir(empty_dir)) # means it is empty","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:32.834286Z","iopub.execute_input":"2026-01-01T16:51:32.834596Z","iopub.status.idle":"2026-01-01T16:51:32.840449Z","shell.execute_reply.started":"2026-01-01T16:51:32.834569Z","shell.execute_reply":"2026-01-01T16:51:32.839669Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if os.path.exists(empty_dir):\n    shutil.rmtree(empty_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:32.841398Z","iopub.execute_input":"2026-01-01T16:51:32.84169Z","iopub.status.idle":"2026-01-01T16:51:32.852913Z","shell.execute_reply.started":"2026-01-01T16:51:32.841661Z","shell.execute_reply":"2026-01-01T16:51:32.852253Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport random\n\ncat_samples = random.sample(os.listdir(cat_dir), 4)\ndog_samples = random.sample(os.listdir(dog_dir), 4)\n\nfig, axes = plt.subplots(2, 4, figsize=(15, 8))\n\nfor i in range(4):\n    # plotting cats\n    cat_path = os.path.join(cat_dir, cat_samples[i])\n    cat_img = mpimg.imread(cat_path)\n    axes[0, i].imshow(cat_img)\n    axes[0, i].set_title(cat_samples[i])\n    axes[0, i].axis('off')\n\n    # plotting dogs\n    dog_path = os.path.join(dog_dir, dog_samples[i])\n    dog_img = mpimg.imread(dog_path)\n    axes[1, i].imshow(dog_img)\n    axes[1, i].set_title(dog_samples[i])\n    axes[1, i].axis('off')\n\nplt.tight_layout() # Prevents titles from overlapping\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:32.853695Z","iopub.execute_input":"2026-01-01T16:51:32.853878Z","iopub.status.idle":"2026-01-01T16:51:33.712738Z","shell.execute_reply.started":"2026-01-01T16:51:32.85386Z","shell.execute_reply":"2026-01-01T16:51:33.711698Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport random\n\nfolder = \"/kaggle/working/test/test1\"\nimages = os.listdir(folder) \nimages = random.sample(images, 4)\n\nfig, axes = plt.subplots(1, 4, figsize=(15, 5))\n\nfor i, img_name in enumerate(images):\n    img = mpimg.imread(os.path.join(folder, img_name))\n    axes[i].imshow(img)\n    axes[i].set_title(img_name)\n    axes[i].axis('off')\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:33.714121Z","iopub.execute_input":"2026-01-01T16:51:33.714987Z","iopub.status.idle":"2026-01-01T16:51:34.073439Z","shell.execute_reply.started":"2026-01-01T16:51:33.714954Z","shell.execute_reply":"2026-01-01T16:51:34.072665Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\n\nimg = Image.open(os.path.join(cat_dir, os.listdir(cat_dir)[4]))\nwidth, height = img.size\nprint(f\"Width: {width}, Height: {height}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:34.074925Z","iopub.execute_input":"2026-01-01T16:51:34.075687Z","iopub.status.idle":"2026-01-01T16:51:34.086365Z","shell.execute_reply.started":"2026-01-01T16:51:34.075658Z","shell.execute_reply":"2026-01-01T16:51:34.085623Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Load Data","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport tensorflow.keras.layers as tfl\n\nfrom tensorflow.keras.utils import image_dataset_from_directory # Highly recommanded\nfrom tensorflow.keras.layers import RandomFlip, RandomRotation, RandomZoom\n\nfrom tensorflow.keras.applications import MobileNetV3Large\nfrom tensorflow.keras.applications.mobilenet_v3 import preprocess_input\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout\nfrom tensorflow.keras.models import Model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:34.087154Z","iopub.execute_input":"2026-01-01T16:51:34.087382Z","iopub.status.idle":"2026-01-01T16:51:40.787288Z","shell.execute_reply.started":"2026-01-01T16:51:34.08736Z","shell.execute_reply":"2026-01-01T16:51:40.78647Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 32\nIMG_SIZE = (224, 224) # Standard for MobileNetV3\ndirectory = \"/kaggle/working/train/\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:40.788168Z","iopub.execute_input":"2026-01-01T16:51:40.788775Z","iopub.status.idle":"2026-01-01T16:51:40.79269Z","shell.execute_reply.started":"2026-01-01T16:51:40.788747Z","shell.execute_reply":"2026-01-01T16:51:40.792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load the training data\ntrain_ds = image_dataset_from_directory(\n    directory,\n    shuffle=True,\n    validation_split=0.2,\n    subset=\"training\",\n    seed=123,\n    image_size=IMG_SIZE, \n    batch_size=32\n)\n\n# Load the validation data\nvalidation_ds = image_dataset_from_directory(\n    directory,\n    shuffle=True,\n    validation_split=0.2,\n    subset=\"validation\",\n    seed=123,\n    image_size=IMG_SIZE,\n    batch_size=32\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:40.793617Z","iopub.execute_input":"2026-01-01T16:51:40.793835Z","iopub.status.idle":"2026-01-01T16:51:43.45988Z","shell.execute_reply.started":"2026-01-01T16:51:40.793815Z","shell.execute_reply":"2026-01-01T16:51:43.459306Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_names = train_ds.class_names\n\nplt.figure(figsize=(10, 10))\nfor images, labels in train_ds.take(1):\n    for i in range(9):\n        ax = plt.subplot(3, 3, i + 1)\n        plt.imshow(images[i].numpy().astype(\"uint8\"))\n        plt.title(class_names[labels[i]])\n        plt.axis(\"off\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:43.460774Z","iopub.execute_input":"2026-01-01T16:51:43.461632Z","iopub.status.idle":"2026-01-01T16:51:44.239808Z","shell.execute_reply.started":"2026-01-01T16:51:43.461603Z","shell.execute_reply":"2026-01-01T16:51:44.238979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(train_ds.class_names)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:44.24131Z","iopub.execute_input":"2026-01-01T16:51:44.241939Z","iopub.status.idle":"2026-01-01T16:51:44.246298Z","shell.execute_reply.started":"2026-01-01T16:51:44.2419Z","shell.execute_reply":"2026-01-01T16:51:44.245452Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Preprocess and Augment Training Data\n\nUsing `dataset.prefetch` which is a important extra step in data preprocessing. \n\nUsing `prefetch()` prevents a memory bottleneck that can occur when reading from disk. It sets aside some data and keeps it ready for when it's needed, by creating a source dataset from your input data, applying a transformation to preprocess it, then iterating over the dataset one element at a time. Because the iteration is streaming, the data doesn't need to fit into memory.\n\nBasically using CPU and GPU to there potential.","metadata":{}},{"cell_type":"code","source":"AUTOTUNE = tf.data.AUTOTUNE\ntrain_ds = train_ds.cache()            # 1. Fetch data from Ram directly instead of disk\ntrain_ds = train_ds.shuffle(1000)      # 2. Mix them up (1000 is the buffer size)\ntrain_ds = train_ds.prefetch(AUTOTUNE) # 3. Prepare next batch while GPU works","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:44.247219Z","iopub.execute_input":"2026-01-01T16:51:44.247522Z","iopub.status.idle":"2026-01-01T16:51:44.265643Z","shell.execute_reply.started":"2026-01-01T16:51:44.247489Z","shell.execute_reply":"2026-01-01T16:51:44.264902Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def data_augmenter():\n    data_augmentation = tf.keras.Sequential()\n    data_augmentation.add(RandomFlip(\"horizontal\"))\n    data_augmentation.add(RandomRotation(0.2))\n    data_augmentation.add(RandomZoom(0.1))\n    \n    return data_augmentation","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:44.26646Z","iopub.execute_input":"2026-01-01T16:51:44.266668Z","iopub.status.idle":"2026-01-01T16:51:44.272056Z","shell.execute_reply.started":"2026-01-01T16:51:44.266648Z","shell.execute_reply":"2026-01-01T16:51:44.271307Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_augmentation = data_augmenter()\n\nfor image, _ in train_ds.take(1):\n    plt.figure(figsize=(10, 10))\n    first_image = image[0]\n    for i in range(9):\n        ax = plt.subplot(3, 3, i + 1)\n        augmented_image = data_augmentation(tf.expand_dims(first_image, 0))\n        plt.imshow(augmented_image[0] / 255)\n        plt.axis('off')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:51:44.273143Z","iopub.execute_input":"2026-01-01T16:51:44.273837Z","iopub.status.idle":"2026-01-01T16:52:05.694612Z","shell.execute_reply.started":"2026-01-01T16:51:44.273806Z","shell.execute_reply":"2026-01-01T16:52:05.693734Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Using MobileNetV3 for Transfer Learning ","metadata":{}},{"cell_type":"code","source":"# The V3 version\npreprocess_input = tf.keras.applications.mobilenet_v3.preprocess_input","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:52:05.695731Z","iopub.execute_input":"2026-01-01T16:52:05.696026Z","iopub.status.idle":"2026-01-01T16:52:05.699682Z","shell.execute_reply.started":"2026-01-01T16:52:05.695993Z","shell.execute_reply":"2026-01-01T16:52:05.698932Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SHAPE = IMG_SIZE + (3,)\nbase_model = tf.keras.applications.MobileNetV3Large(\n    input_shape=IMG_SHAPE,\n    include_top=True,\n    weights='imagenet')\n\nbase_model.summary()\nprint(\"Number of layers in the base model: \", len(base_model.layers))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:52:05.700694Z","iopub.execute_input":"2026-01-01T16:52:05.701073Z","iopub.status.idle":"2026-01-01T16:52:07.00353Z","shell.execute_reply.started":"2026-01-01T16:52:05.701039Z","shell.execute_reply":"2026-01-01T16:52:07.002747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# The model is trained on on 1000 Labels we want 2\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:52:07.004609Z","iopub.execute_input":"2026-01-01T16:52:07.004875Z","iopub.status.idle":"2026-01-01T16:52:07.008276Z","shell.execute_reply.started":"2026-01-01T16:52:07.004852Z","shell.execute_reply":"2026-01-01T16:52:07.007531Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def catVsdogModel(image_shape=IMG_SIZE, data_augmentation=data_augmenter()):\n    image_shape = image_shape + (3,)\n\n    base_model = tf.keras.applications.MobileNetV3Large(\n        input_shape=image_shape,\n        include_top=False, # remove ImageNet classification head (1000 classes)\n        weights='imagenet')\n    \n    # Freeze the base model so we don't overwrite the pre-trained weights\n    base_model.trainable = False\n\n    # Build our model\n    inputs = tf.keras.Input(shape=image_shape) \n\n    # apply data augmentaion\n    x = data_augmentation(inputs)\n\n    # data preprocessing using the same weights the model was trained on\n    x = preprocess_input(x)\n\n    x = base_model(x, training=False) \n\n    # Convert the 7x7 spatial features into a single vector\n    x = tfl.GlobalAveragePooling2D()(x) \n\n    # Add Dropout to prevent overfitting \n    x = tf.keras.layers.Dropout(0.2)(x)\n\n    outputs = tf.keras.layers.Dense(1)(x)\n\n    model = tf.keras.Model(inputs, outputs)\n    \n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:52:07.009075Z","iopub.execute_input":"2026-01-01T16:52:07.009298Z","iopub.status.idle":"2026-01-01T16:52:07.030898Z","shell.execute_reply.started":"2026-01-01T16:52:07.009278Z","shell.execute_reply":"2026-01-01T16:52:07.030305Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = catVsdogModel(IMG_SIZE, data_augmenter())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:52:07.031708Z","iopub.execute_input":"2026-01-01T16:52:07.031915Z","iopub.status.idle":"2026-01-01T16:52:07.819474Z","shell.execute_reply.started":"2026-01-01T16:52:07.031895Z","shell.execute_reply":"2026-01-01T16:52:07.818591Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_learning_rate = 0.001\nmodel.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=base_learning_rate),\n              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n              metrics=['accuracy'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:52:07.822653Z","iopub.execute_input":"2026-01-01T16:52:07.822931Z","iopub.status.idle":"2026-01-01T16:52:07.837755Z","shell.execute_reply.started":"2026-01-01T16:52:07.822908Z","shell.execute_reply":"2026-01-01T16:52:07.837116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tf.config.list_physical_devices('GPU')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:52:07.838563Z","iopub.execute_input":"2026-01-01T16:52:07.838767Z","iopub.status.idle":"2026-01-01T16:52:07.84384Z","shell.execute_reply.started":"2026-01-01T16:52:07.838747Z","shell.execute_reply":"2026-01-01T16:52:07.843079Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"early_stopping = tf.keras.callbacks.EarlyStopping(\n    monitor='val_loss',     # Watch the validation error\n    patience=3,             # Wait 3 epochs for improvement before quitting\n    restore_best_weights=True # Keep the version of the model that performed best\n)\n\ninitial_epochs = 5\nhistory = model.fit(\n    train_ds, \n    validation_data=validation_ds, \n    epochs=initial_epochs,\n    callbacks=[early_stopping])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:52:07.844626Z","iopub.execute_input":"2026-01-01T16:52:07.844819Z","iopub.status.idle":"2026-01-01T16:54:31.006574Z","shell.execute_reply.started":"2026-01-01T16:52:07.8448Z","shell.execute_reply":"2026-01-01T16:54:31.005752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(history.history.keys())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:54:31.007597Z","iopub.execute_input":"2026-01-01T16:54:31.007839Z","iopub.status.idle":"2026-01-01T16:54:31.012015Z","shell.execute_reply.started":"2026-01-01T16:54:31.007812Z","shell.execute_reply":"2026-01-01T16:54:31.011252Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Optional: Add a starting point to both for consistency\nacc = [0.] + history.history['accuracy']\nval_acc = [0.] + history.history['val_accuracy']\n\n# For loss, the starting point is usually high, not 0\nloss = [0.7] + history.history['loss']\nval_loss = [0.7] + history.history['val_loss']\n\nimport matplotlib.pyplot as plt\n\nplt.figure(figsize=(8, 8))\n\n# Plot Training and Validation Accuracy\nplt.subplot(2, 1, 1)\nplt.plot(acc, label='Training Accuracy')\nplt.plot(val_acc, label='Validation Accuracy')\nplt.legend(loc='lower right')\nplt.ylabel('Accuracy')\nplt.ylim([0, 1])\nplt.title('Training and Validation Accuracy')\n\n# Plot Training and Validation Loss\nplt.subplot(2, 1, 2)\nplt.plot(loss, label='Training Loss')\nplt.plot(val_loss, label='Validation Loss')\nplt.legend(loc='upper right')\nplt.ylabel('Binary Crossentropy')\nplt.ylim([0, max(val_loss)])\nplt.title('Training and Validation Loss')\nplt.xlabel('Epoch')\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:54:31.012824Z","iopub.execute_input":"2026-01-01T16:54:31.013025Z","iopub.status.idle":"2026-01-01T16:54:31.26892Z","shell.execute_reply.started":"2026-01-01T16:54:31.013007Z","shell.execute_reply":"2026-01-01T16:54:31.268233Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Fine-tuning the Model\n\nYou could try fine-tuning the model by re-running the optimizer in the last layers to improve accuracy. When you use a smaller learning rate, you take smaller steps to adapt it a little more closely to the new data. In transfer learning, the way you achieve this is by unfreezing the layers at the end of the network, and then re-training your model on the final layers with a very low learning rate. Adapting your learning rate to go over these layers in smaller steps can yield more fine details - and higher accuracy.\n\nThe intuition for what's happening: when the network is in its earlier stages, it trains on low-level features, like edges. In the later layers, more complex, high-level features like wispy hair or pointy ears begin to emerge. For transfer learning, the low-level features can be kept the same, as they have common features for most images. When you add new data, you generally want the high-level features to adapt to it, which is rather like letting the network learn to detect features more related to your data, such as soft fur or big teeth. \n\nTo achieve this, just unfreeze the final layers and re-run the optimizer with a smaller learning rate, while keeping all the other layers frozen.\n\nWhere the final layers actually begin is a bit arbitrary, so feel free to play around with this number a bit. The important takeaway is that the later layers are the part of your network that contain the fine details (pointy ears, hairy tails) that are more specific to your problem.\n\nFirst, unfreeze the base model by setting `base_model.trainable=True`, set a layer to fine-tune from, then re-freeze all the layers before it. Run it again for another few epochs, and see if your accuracy improved!","metadata":{}},{"cell_type":"code","source":"print(\"Number of layers in the base model: \", len(base_model.layers))\n\n# for i, layer in enumerate(model.layers):\n#     print(i, layer.name)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:54:31.269787Z","iopub.execute_input":"2026-01-01T16:54:31.27007Z","iopub.status.idle":"2026-01-01T16:54:31.274391Z","shell.execute_reply.started":"2026-01-01T16:54:31.270035Z","shell.execute_reply":"2026-01-01T16:54:31.273609Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_model = model.get_layer('MobileNetV3Large')\nbase_model.trainable = True\n\n# Freeze everything except the last 30 layers\nfine_tune_at = 165\n\nfor layer in base_model.layers[:fine_tune_at]:\n    layer.trainable = False\n\n# Quick check to make sure it worked\ntrainable_count = len([l for l in base_model.layers if l.trainable])\nprint(f\"Base model layers: {len(base_model.layers)}\")\nprint(f\"Layers now trainable: {trainable_count}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:54:31.275292Z","iopub.execute_input":"2026-01-01T16:54:31.275588Z","iopub.status.idle":"2026-01-01T16:54:31.291993Z","shell.execute_reply.started":"2026-01-01T16:54:31.275566Z","shell.execute_reply":"2026-01-01T16:54:31.291308Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"loss_function= tf.keras.losses.BinaryCrossentropy(from_logits=True)\n\noptimizer = tf.keras.optimizers.Adam(learning_rate=1e-5) # 0.00001\nmetrics=['accuracy']\n\nmodel.compile(loss=loss_function,\n              optimizer = optimizer,\n              metrics=metrics)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:54:31.292812Z","iopub.execute_input":"2026-01-01T16:54:31.293072Z","iopub.status.idle":"2026-01-01T16:54:31.332302Z","shell.execute_reply.started":"2026-01-01T16:54:31.293044Z","shell.execute_reply":"2026-01-01T16:54:31.331668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:54:31.333681Z","iopub.execute_input":"2026-01-01T16:54:31.334045Z","iopub.status.idle":"2026-01-01T16:54:31.352223Z","shell.execute_reply.started":"2026-01-01T16:54:31.333987Z","shell.execute_reply":"2026-01-01T16:54:31.351531Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"early_stop = tf.keras.callbacks.EarlyStopping(\n    monitor='val_loss', \n    patience=3,             # Stop if val_loss doesn't improve for 3 epochs\n    restore_best_weights=True # Very important: rolls back to the best version\n)\n\nfine_tune_epochs = 10\ntotal_epochs =  initial_epochs + fine_tune_epochs\n\nhistory_fine = model.fit(\n    train_ds,\n    epochs=total_epochs,     # Set this to current_epochs + 10\n    initial_epoch=history.epoch[-1],\n    validation_data=validation_ds,\n    callbacks=[early_stop]   # Add the safety fuse here\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T16:55:57.95942Z","iopub.execute_input":"2026-01-01T16:55:57.959745Z","iopub.status.idle":"2026-01-01T17:01:46.265715Z","shell.execute_reply.started":"2026-01-01T16:55:57.959718Z","shell.execute_reply":"2026-01-01T17:01:46.264986Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"acc += history_fine.history['accuracy']\nval_acc += history_fine.history['val_accuracy']\n\nloss += history_fine.history['loss']\nval_loss += history_fine.history['val_loss']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T17:03:58.471645Z","iopub.execute_input":"2026-01-01T17:03:58.47195Z","iopub.status.idle":"2026-01-01T17:03:58.476215Z","shell.execute_reply.started":"2026-01-01T17:03:58.471927Z","shell.execute_reply":"2026-01-01T17:03:58.475538Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8, 8))\nplt.subplot(2, 1, 1)\nplt.plot(acc, label='Training Accuracy')\nplt.plot(val_acc, label='Validation Accuracy')\nplt.ylim([0, 1])\nplt.plot([initial_epochs-1,initial_epochs-1],\n          plt.ylim(), label='Start Fine Tuning')\nplt.legend(loc='lower right')\nplt.title('Training and Validation Accuracy')\n\nplt.subplot(2, 1, 2)\nplt.plot(loss, label='Training Loss')\nplt.plot(val_loss, label='Validation Loss')\nplt.ylim([0, 1.0])\nplt.plot([initial_epochs-1,initial_epochs-1],\n         plt.ylim(), label='Start Fine Tuning')\nplt.legend(loc='upper right')\nplt.title('Training and Validation Loss')\nplt.xlabel('epoch')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T17:08:52.629666Z","iopub.execute_input":"2026-01-01T17:08:52.629968Z","iopub.status.idle":"2026-01-01T17:08:52.896014Z","shell.execute_reply.started":"2026-01-01T17:08:52.629942Z","shell.execute_reply":"2026-01-01T17:08:52.895183Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.evaluate(validation_ds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T17:10:27.200335Z","iopub.execute_input":"2026-01-01T17:10:27.200899Z","iopub.status.idle":"2026-01-01T17:10:32.445415Z","shell.execute_reply.started":"2026-01-01T17:10:27.200868Z","shell.execute_reply":"2026-01-01T17:10:32.444745Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.image as mpimg\nimport numpy as np\nimport os\nimport random\nimport tensorflow as tf\n\nfolder = \"/kaggle/working/test/test1\"\nimages = os.listdir(folder) \nimages = random.sample(images, 4)\n\nfig, axes = plt.subplots(1, 4, figsize=(20, 5))\n\n# Get class names (usually ['cat', 'dog']) from your training dataset\n# If you don't have it, we assume 0=Cat, 1=Dog based on alphabetical order\nclass_names = ['Cat', 'Dog'] \n\nfor i, img_name in enumerate(images):\n    img_path = os.path.join(folder, img_name)\n    \n    # 1. Load and Preprocess the image for the model\n    # It must be the same size used during training (224x224)\n    img_load = tf.keras.utils.load_img(img_path, target_size=(224, 224))\n    img_array = tf.keras.utils.img_to_array(img_load)\n    img_array = tf.expand_dims(img_array, 0) # Create a batch of 1\n    \n    # 2. Make Prediction\n    prediction = model.predict(img_array, verbose=0)\n    score = prediction[0][0] # Since it's a sigmoid, it returns a single value\n    \n    # 3. Determine Label and Confidence\n    # If score > 0.5 it's a Dog (1), otherwise it's a Cat (0)\n    if score > 0.5:\n        label = \"Dog\"\n        confidence = score * 100\n    else:\n        label = \"Cat\"\n        confidence = (1 - score) * 100\n\n    # 4. Show the actual image\n    img_display = mpimg.imread(img_path)\n    axes[i].imshow(img_display)\n    axes[i].set_title(f\"Pred: {label}\\nConf: {confidence:.2f}%\")\n    axes[i].axis('off')\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T17:11:23.218938Z","iopub.execute_input":"2026-01-01T17:11:23.21972Z","iopub.status.idle":"2026-01-01T17:11:25.580642Z","shell.execute_reply.started":"2026-01-01T17:11:23.219685Z","shell.execute_reply":"2026-01-01T17:11:25.57982Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.save(\"/kaggle/working/cat_vs_dog_model.keras\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T17:19:54.594124Z","iopub.execute_input":"2026-01-01T17:19:54.594781Z","iopub.status.idle":"2026-01-01T17:19:55.131392Z","shell.execute_reply.started":"2026-01-01T17:19:54.594749Z","shell.execute_reply":"2026-01-01T17:19:55.130758Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Prediction","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport tensorflow as tf\n\ntest_dir = \"/kaggle/working/test/test1\"\nfilenames = os.listdir(test_dir)\n\nids = []\nprobs = [] # We'll store the raw decimals here\n\nprint(f\"Generating probabilities for {len(filenames)} images...\")\n\nfor fname in filenames:\n    img_id = fname.split('.')[0]\n    ids.append(img_id)\n    \n    # Preprocess\n    img_path = os.path.join(test_dir, fname)\n    img = tf.keras.utils.load_img(img_path, target_size=(224, 224))\n    img_array = tf.keras.utils.img_to_array(img)\n    img_array = tf.expand_dims(img_array, 0) \n    \n    # Predict raw probability\n    prediction = model.predict(img_array, verbose=0)\n    \n    # Extract the single float value\n    # prediction[0][0] is the probability of being a DOG\n    probs.append(prediction[0][0])\n\n# Create DataFrame\nsubmission_df = pd.DataFrame({\n    'id': ids,\n    'label': probs # Using 'label' as the column name, but filling with probabilities\n})\n\nsubmission_df['id'] = submission_df['id'].astype(int)\nsubmission_df = submission_df.sort_values(by='id')\n\nsubmission_df.to_csv('submission.csv', index=False)\nprint(\"Done! Preview of probabilities:\")\nprint(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T17:27:43.349801Z","iopub.execute_input":"2026-01-01T17:27:43.350638Z","iopub.status.idle":"2026-01-01T17:43:08.532334Z","shell.execute_reply.started":"2026-01-01T17:27:43.350604Z","shell.execute_reply":"2026-01-01T17:43:08.531646Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}