{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Transfer Learning: Dogs vs Cats Classification\n### Models: VGG16, InceptionV3, MobileNetV2","metadata":{}},{"cell_type":"markdown","source":"## Cell 1: Setup & GPU Check","metadata":{}},{"cell_type":"code","source":"import os, sys, numpy as np\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nfrom tensorflow import keras\n\nprint('TF version:', tf.__version__)\nprint('GPU:', tf.config.list_physical_devices('GPU'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:53:47.522032Z","iopub.execute_input":"2026-09-17T09:53:47.522860Z","iopub.status.idle":"2026-09-17T09:53:47.527507Z","shell.execute_reply.started":"2026-09-17T09:53:47.522830Z","shell.execute_reply":"2026-09-17T09:53:47.526719Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cell 2: Extract Dataset","metadata":{}},{"cell_type":"code","source":"import zipfile\n\nwith zipfile.ZipFile('/kaggle/input/competitions/dogs-vs-cats/test1.zip', 'r') as z:\n    z.extractall('.')\n\nwith zipfile.ZipFile('/kaggle/input/competitions/dogs-vs-cats/train.zip', 'r') as z:\n    z.extractall('.')\n\nprint('Extraction complete')\nprint(os.listdir('.'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:53:47.528725Z","iopub.execute_input":"2026-09-17T09:53:47.529073Z","iopub.status.idle":"2026-09-17T09:53:56.632914Z","shell.execute_reply.started":"2026-09-17T09:53:47.529051Z","shell.execute_reply":"2026-09-17T09:53:56.632313Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cell 3: Organize Dataset into train/test folders","metadata":{}},{"cell_type":"code","source":"from os import makedirs, listdir\nfrom shutil import copyfile\nfrom random import seed, random\n\n# create directories\ndataset_home = 'dataset_dogs_vs_cats/'\nfor subdir in ['train/', 'test/']:\n    for labeldir in ['dogs/', 'cats/']:\n        makedirs(dataset_home + subdir + labeldir, exist_ok=True)\n\n# split: 75% train, 25% test\nseed(1337)\nval_ratio = 0.25\nsrc_directory = 'train/'\n\nfor file in listdir(src_directory):\n    src = src_directory + '/' + file\n    dst_dir = 'train/'\n    if random() < val_ratio:\n        dst_dir = 'test/'\n    if file.startswith('cat'):\n        dst = dataset_home + dst_dir + 'cats/' + file\n        copyfile(src, dst)\n    elif file.startswith('dog'):\n        dst = dataset_home + dst_dir + 'dogs/' + file\n        copyfile(src, dst)\n\nprint('Train dogs:', len(listdir(dataset_home + 'train/dogs/')))\nprint('Train cats:', len(listdir(dataset_home + 'train/cats/')))\nprint('Test dogs: ', len(listdir(dataset_home + 'test/dogs/')))\nprint('Test cats: ', len(listdir(dataset_home + 'test/cats/')))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:53:56.633739Z","iopub.execute_input":"2026-09-17T09:53:56.634049Z","iopub.status.idle":"2026-09-17T09:53:59.870465Z","shell.execute_reply.started":"2026-09-17T09:53:56.634025Z","shell.execute_reply":"2026-09-17T09:53:59.869592Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cell 4: Plot Sample Images","metadata":{}},{"cell_type":"code","source":"from matplotlib.image import imread\n\nfig, axes = plt.subplots(2, 5, figsize=(15, 6))\nfolder = 'train/'\nfor i in range(5):\n    img = imread(folder + f'dog.{i}.jpg')\n    axes[0][i].imshow(img)\n    axes[0][i].set_title('dog')\n    axes[0][i].axis('off')\nfor i in range(5):\n    img = imread(folder + f'cat.{i}.jpg')\n    axes[1][i].imshow(img)\n    axes[1][i].set_title('cat')\n    axes[1][i].axis('off')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:53:59.872234Z","iopub.execute_input":"2026-09-17T09:53:59.873468Z","iopub.status.idle":"2026-09-17T09:54:01.005493Z","shell.execute_reply.started":"2026-09-17T09:53:59.873439Z","shell.execute_reply":"2026-09-17T09:54:01.004002Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cell 5: Install livelossplot","metadata":{}},{"cell_type":"code","source":"!pip -q install livelossplot==0.5.5\nfrom livelossplot import PlotLossesKeras\nprint('livelossplot installed')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:54:01.006476Z","iopub.execute_input":"2026-09-17T09:54:01.006803Z","iopub.status.idle":"2026-09-17T09:54:04.313715Z","shell.execute_reply.started":"2026-09-17T09:54:01.006781Z","shell.execute_reply":"2026-09-17T09:54:04.312971Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cell 6: Hyperparameters & Data Generators","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nIMAGE_SIZE = 224\nBATCH_SIZE = 64\nEPOCHS = 20\n\ntrain_path = 'dataset_dogs_vs_cats/train/'\ntest_path  = 'dataset_dogs_vs_cats/test/'\n\n# Data generators with augmentation for train\ntrain_datagen = ImageDataGenerator(\n    rescale=1.0/255.0,\n    horizontal_flip=True,\n    rotation_range=10,\n    zoom_range=0.1\n)\ntest_datagen = ImageDataGenerator(rescale=1.0/255.0)\n\ntrain_it = train_datagen.flow_from_directory(\n    train_path,\n    class_mode='binary',\n    batch_size=BATCH_SIZE,\n    target_size=(IMAGE_SIZE, IMAGE_SIZE)\n)\ntest_it = test_datagen.flow_from_directory(\n    test_path,\n    class_mode='binary',\n    batch_size=BATCH_SIZE,\n    target_size=(IMAGE_SIZE, IMAGE_SIZE),\n    shuffle=False\n)\n\nprint('Class indices:', train_it.class_indices)\nprint('Train samples:', train_it.samples)\nprint('Test samples: ', test_it.samples)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:54:04.315051Z","iopub.execute_input":"2026-09-17T09:54:04.315310Z","iopub.status.idle":"2026-09-17T09:54:04.901362Z","shell.execute_reply.started":"2026-09-17T09:54:04.315283Z","shell.execute_reply":"2026-09-17T09:54:04.900796Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cell 7: Helper Functions","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import classification_report\nimport numpy as np\n\ndef evaluate_model(model, test_it, model_name):\n    \"\"\"Evaluate model and print classification report\"\"\"\n    # Reset generator\n    test_it.reset()\n    \n    # Predict\n    steps = int(np.ceil(test_it.samples / test_it.batch_size))\n    probs = model.predict(test_it, steps=steps, verbose=1)\n    \n    # For binary output (sigmoid)\n    if probs.shape[1] == 1 if len(probs.shape) > 1 else True:\n        y_pred = (probs.flatten() > 0.5).astype(int)\n    else:\n        y_pred = np.argmax(probs, axis=1)\n    \n    y_true = test_it.classes[:len(y_pred)]\n    target_names = list(test_it.class_indices.keys())\n    \n    print(f'\\n===== {model_name} Classification Report =====')\n    print(classification_report(y_true, y_pred, target_names=target_names))\n    return y_true, y_pred\n\ndef plot_learning_curves(history, model_name):\n    \"\"\"Plot loss and accuracy curves\"\"\"\n    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))\n    ax1.plot(history.history['loss'],     label='Train Loss',  color='blue')\n    ax1.plot(history.history['val_loss'], label='Val Loss',    color='orange')\n    ax1.set_title(f'{model_name} - Cross Entropy Loss')\n    ax1.set_xlabel('Epoch')\n    ax1.legend()\n    ax2.plot(history.history['accuracy'],     label='Train Acc', color='blue')\n    ax2.plot(history.history['val_accuracy'], label='Val Acc',   color='orange')\n    ax2.set_title(f'{model_name} - Accuracy')\n    ax2.set_xlabel('Epoch')\n    ax2.legend()\n    plt.tight_layout()\n    plt.show()\n    print(f'Best Val Accuracy: {max(history.history[\"val_accuracy\"]):.4f}')\n\ndef predict_and_show(model, test_path, test_it, model_name, n=5):\n    \"\"\"Randomly sample n images per class and show predictions\"\"\"\n    import random\n    from tensorflow.keras.preprocessing.image import load_img, img_to_array\n    \n    results = []\n    for cls in ['cats', 'dogs']:\n        folder = os.path.join(test_path, cls)\n        files  = os.listdir(folder)\n        sampled = random.sample(files, n)\n        for fname in sampled:\n            fpath = os.path.join(folder, fname)\n            img   = load_img(fpath, target_size=(IMAGE_SIZE, IMAGE_SIZE))\n            arr   = img_to_array(img) / 255.0\n            arr   = np.expand_dims(arr, 0)\n            pred  = model.predict(arr, verbose=0)\n            prob  = pred[0][0] if pred.shape[-1] == 1 else pred[0][1]\n            pred_class = 'dogs' if prob >= 0.5 else 'cats'\n            results.append((img, prob, pred_class, cls))\n    \n    fig, axes = plt.subplots(2, n, figsize=(18, 8))\n    axes = axes.flatten()\n    for i, (img, prob, pred_class, true_class) in enumerate(results):\n        axes[i].imshow(img)\n        correct = pred_class == true_class\n        color   = 'green' if correct else 'red'\n        axes[i].set_title(\n            f'True: {true_class}\\nPred: {pred_class} ({prob:.2f})',\n            color=color, fontsize=9\n        )\n        axes[i].axis('off')\n    plt.suptitle(f'{model_name} - Predictions (Green=Correct, Red=Wrong)', fontsize=13)\n    plt.tight_layout()\n    plt.show()\n\nprint('Helper functions defined')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:54:04.902517Z","iopub.execute_input":"2026-09-17T09:54:04.902852Z","iopub.status.idle":"2026-09-17T09:54:04.917149Z","shell.execute_reply.started":"2026-09-17T09:54:04.902812Z","shell.execute_reply":"2026-09-17T09:54:04.916355Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---\n# Model 1: VGG16","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import VGG16\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, Flatten, Dropout\nfrom tensorflow.keras.optimizers import SGD\n\ndef define_model_vgg16():\n    base = VGG16(include_top=False, input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3))\n    for layer in base.layers:\n        layer.trainable = False\n    flat1  = Flatten()(base.layers[-1].output)\n    class1 = Dense(128, activation='relu', kernel_initializer='he_uniform')(flat1)\n    drop1  = Dropout(0.5)(class1)\n    output = Dense(1, activation='sigmoid')(drop1)\n    model  = Model(inputs=base.inputs, outputs=output)\n    opt    = SGD(learning_rate=0.001, momentum=0.9)\n    model.compile(optimizer=opt, loss='binary_crossentropy', metrics=['accuracy'])\n    return model\n\nmodel_vgg16 = define_model_vgg16()\nmodel_vgg16.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:54:04.918239Z","iopub.execute_input":"2026-09-17T09:54:04.919031Z","iopub.status.idle":"2026-09-17T09:54:05.244464Z","shell.execute_reply.started":"2026-09-17T09:54:04.918997Z","shell.execute_reply":"2026-09-17T09:54:05.243890Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for layer in model_vgg16.layers[-8:]:\n    layer.trainable = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:54:05.246708Z","iopub.execute_input":"2026-09-17T09:54:05.246972Z","iopub.status.idle":"2026-09-17T09:54:05.251147Z","shell.execute_reply.started":"2026-09-17T09:54:05.246949Z","shell.execute_reply":"2026-09-17T09:54:05.250463Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_vgg16.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=0.00001),  # Adam ดีกว่า SGD ตอน fine-tune\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)\n\nhistory_vgg16_ft2 = model_vgg16.fit(\n    train_it,\n    steps_per_epoch=len(train_it),\n    validation_data=test_it,\n    validation_steps=len(test_it),\n    epochs=10,\n    verbose=1,\n    callbacks=[PlotLossesKeras()]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T09:54:05.251977Z","iopub.execute_input":"2026-09-17T09:54:05.252360Z","iopub.status.idle":"2026-09-17T10:35:03.363086Z","shell.execute_reply.started":"2026-09-17T09:54:05.252331Z","shell.execute_reply":"2026-09-17T10:35:03.362204Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(model_vgg16)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T10:35:03.364303Z","iopub.execute_input":"2026-09-17T10:35:03.364643Z","iopub.status.idle":"2026-09-17T10:35:03.369477Z","shell.execute_reply.started":"2026-09-17T10:35:03.364619Z","shell.execute_reply":"2026-09-17T10:35:03.368797Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot VGG16 learning curves\nplot_learning_curves(history_vgg16_ft2, 'VGG16')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T14:15:46.412706Z","iopub.execute_input":"2026-09-17T14:15:46.413374Z","iopub.status.idle":"2026-09-17T14:15:46.668307Z","shell.execute_reply.started":"2026-09-17T14:15:46.413342Z","shell.execute_reply":"2026-09-17T14:15:46.667571Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true_vgg16, y_pred_vgg16 = evaluate_model(model_vgg16, test_it, 'VGG16 Fine-tuned v2')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T10:51:44.697389Z","iopub.execute_input":"2026-09-17T10:51:44.697844Z","iopub.status.idle":"2026-09-17T10:52:15.828964Z","shell.execute_reply.started":"2026-09-17T10:51:44.697815Z","shell.execute_reply":"2026-09-17T10:52:15.828318Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Predict 10 images (5 cats, 5 dogs) - VGG16\npredict_and_show(model_vgg16, test_path, test_it, 'VGG16', n=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T10:52:20.062567Z","iopub.execute_input":"2026-09-17T10:52:20.062911Z","iopub.status.idle":"2026-09-17T10:52:22.143744Z","shell.execute_reply.started":"2026-09-17T10:52:20.062867Z","shell.execute_reply":"2026-09-17T10:52:22.142787Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---\n# Model 2: InceptionV3","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import InceptionV3\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nIMAGE_SIZE_INCEPTION = 299\n\n# InceptionV3 needs 299x299\ntrain_datagen_inc = ImageDataGenerator(\n    rescale=1.0/255.0,\n    horizontal_flip=True,\n    rotation_range=10,\n    zoom_range=0.1\n)\ntest_datagen_inc = ImageDataGenerator(rescale=1.0/255.0)\n\ntrain_it_inc = train_datagen_inc.flow_from_directory(\n    train_path,\n    class_mode='binary',\n    batch_size=BATCH_SIZE,\n    target_size=(IMAGE_SIZE_INCEPTION, IMAGE_SIZE_INCEPTION)\n)\ntest_it_inc = test_datagen_inc.flow_from_directory(\n    test_path,\n    class_mode='binary',\n    batch_size=BATCH_SIZE,\n    target_size=(IMAGE_SIZE_INCEPTION, IMAGE_SIZE_INCEPTION),\n    shuffle=False\n)\n\ndef define_model_inceptionv3():\n    base = InceptionV3(include_top=False, input_shape=(IMAGE_SIZE_INCEPTION, IMAGE_SIZE_INCEPTION, 3))\n    for layer in base.layers:\n        layer.trainable = False\n    flat1  = Flatten()(base.layers[-1].output)\n    class1 = Dense(256, activation='relu', kernel_initializer='he_uniform')(flat1)\n    drop1  = Dropout(0.5)(class1)\n    output = Dense(1, activation='sigmoid')(drop1)\n    model  = Model(inputs=base.inputs, outputs=output)\n    opt    = SGD(learning_rate=0.001, momentum=0.9)\n    model.compile(optimizer=opt, loss='binary_crossentropy', metrics=['accuracy'])\n    return model\n\nmodel_inceptionv3 = define_model_inceptionv3()\nmodel_inceptionv3.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T10:52:22.145245Z","iopub.execute_input":"2026-09-17T10:52:22.145583Z","iopub.status.idle":"2026-09-17T10:52:29.269036Z","shell.execute_reply.started":"2026-09-17T10:52:22.145560Z","shell.execute_reply":"2026-09-17T10:52:29.268416Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train InceptionV3\nhistory_inceptionv3 = model_inceptionv3.fit(\n    train_it_inc,\n    steps_per_epoch=len(train_it_inc),\n    validation_data=test_it_inc,\n    validation_steps=len(test_it_inc),\n    epochs=EPOCHS,\n    verbose=1,\n    callbacks=[PlotLossesKeras()]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T10:52:29.269957Z","iopub.execute_input":"2026-09-17T10:52:29.270307Z","iopub.status.idle":"2026-09-17T12:52:19.175848Z","shell.execute_reply.started":"2026-09-17T10:52:29.270284Z","shell.execute_reply":"2026-09-17T12:52:19.175288Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot InceptionV3 learning curves\nplot_learning_curves(history_inceptionv3, 'InceptionV3')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T12:52:19.176862Z","iopub.execute_input":"2026-09-17T12:52:19.177698Z","iopub.status.idle":"2026-09-17T12:52:19.471300Z","shell.execute_reply.started":"2026-09-17T12:52:19.177666Z","shell.execute_reply":"2026-09-17T12:52:19.470658Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Evaluate InceptionV3\ny_true_inc, y_pred_inc = evaluate_model(model_inceptionv3, test_it_inc, 'InceptionV3')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T12:52:19.472235Z","iopub.execute_input":"2026-09-17T12:52:19.472459Z","iopub.status.idle":"2026-09-17T12:52:53.792122Z","shell.execute_reply.started":"2026-09-17T12:52:19.472438Z","shell.execute_reply":"2026-09-17T12:52:53.791460Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Predict 10 images - InceptionV3\nimport random\nfrom tensorflow.keras.preprocessing.image import load_img, img_to_array\n\nresults = []\nfor cls in ['cats', 'dogs']:\n    folder = os.path.join(test_path, cls)\n    files  = os.listdir(folder)\n    sampled = random.sample(files, 5)\n    for fname in sampled:\n        fpath = os.path.join(folder, fname)\n        img   = load_img(fpath, target_size=(IMAGE_SIZE_INCEPTION, IMAGE_SIZE_INCEPTION))\n        arr   = img_to_array(img) / 255.0\n        arr   = np.expand_dims(arr, 0)\n        prob  = model_inceptionv3.predict(arr, verbose=0)[0][0]\n        pred_class = 'dogs' if prob >= 0.5 else 'cats'\n        results.append((img, prob, pred_class, cls))\n\nfig, axes = plt.subplots(2, 5, figsize=(18, 8))\naxes = axes.flatten()\nfor i, (img, prob, pred_class, true_class) in enumerate(results):\n    axes[i].imshow(img)\n    correct = pred_class == true_class\n    color   = 'green' if correct else 'red'\n    axes[i].set_title(f'True: {true_class}\\nPred: {pred_class} ({prob:.2f})', color=color, fontsize=9)\n    axes[i].axis('off')\nplt.suptitle('InceptionV3 - Predictions (Green=Correct, Red=Wrong)', fontsize=13)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T12:52:53.793144Z","iopub.execute_input":"2026-09-17T12:52:53.793482Z","iopub.status.idle":"2026-09-17T12:53:03.532246Z","shell.execute_reply.started":"2026-09-17T12:52:53.793457Z","shell.execute_reply":"2026-09-17T12:53:03.531080Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---\n# Model 3: MobileNetV2","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import MobileNetV2\n\ndef define_model_mobilenetv2():\n    base = MobileNetV2(include_top=False, input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3))\n    for layer in base.layers:\n        layer.trainable = False\n    flat1  = Flatten()(base.layers[-1].output)\n    class1 = Dense(128, activation='relu', kernel_initializer='he_uniform')(flat1)\n    drop1  = Dropout(0.5)(class1)\n    output = Dense(1, activation='sigmoid')(drop1)\n    model  = Model(inputs=base.inputs, outputs=output)\n    opt    = SGD(learning_rate=0.001, momentum=0.9)\n    model.compile(optimizer=opt, loss='binary_crossentropy', metrics=['accuracy'])\n    return model\n\nmodel_mobilenet = define_model_mobilenetv2()\nmodel_mobilenet.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T12:53:03.533629Z","iopub.execute_input":"2026-09-17T12:53:03.534035Z","iopub.status.idle":"2026-09-17T12:53:06.230907Z","shell.execute_reply.started":"2026-09-17T12:53:03.533994Z","shell.execute_reply":"2026-09-17T12:53:06.230377Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train MobileNetV2\nhistory_mobilenet = model_mobilenet.fit(\n    train_it,\n    steps_per_epoch=len(train_it),\n    validation_data=test_it,\n    validation_steps=len(test_it),\n    epochs=EPOCHS,\n    verbose=1,\n    callbacks=[PlotLossesKeras()]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T14:16:32.706290Z","iopub.execute_input":"2026-09-17T14:16:32.706545Z","iopub.status.idle":"2026-09-17T15:25:18.567047Z","shell.execute_reply.started":"2026-09-17T14:16:32.706524Z","shell.execute_reply":"2026-09-17T15:25:18.566405Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot MobileNetV2 learning curves\nplot_learning_curves(history_mobilenet, 'MobileNetV2')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T15:25:18.568610Z","iopub.execute_input":"2026-09-17T15:25:18.568879Z","iopub.status.idle":"2026-09-17T15:25:18.877600Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Evaluate MobileNetV2\n\ny_true_mob, y_pred_mob = evaluate_model(model_mobilenet, test_it, 'MobileNetV2')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T15:25:18.884024Z","iopub.execute_input":"2026-09-17T15:25:18.884219Z","iopub.status.idle":"2026-09-17T15:25:32.743321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Predict 10 images - MobileNetV2\npredict_and_show(model_mobilenet, test_path, test_it, 'MobileNetV2', n=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T15:25:32.746722Z","iopub.execute_input":"2026-09-17T15:25:32.747001Z","iopub.status.idle":"2026-09-17T15:25:34.644485Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---\n# Summary: Compare All 3 Models","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score\n\nprint('='*50)\nprint('MODEL COMPARISON SUMMARY')\nprint('='*50)\nprint(f'VGG16       Best Val Acc: {max(history_vgg16_ft2.history[\"val_accuracy\"]):.4f}')\nprint(f'InceptionV3 Best Val Acc: {max(history_inceptionv3.history[\"val_accuracy\"]):.4f}')\nprint(f'MobileNetV2 Best Val Acc: {max(history_mobilenet.history[\"val_accuracy\"]):.4f}')\nprint()\nprint(f'VGG16       Test Acc: {accuracy_score(y_true_vgg16, y_pred_vgg16):.4f}')\nprint(f'InceptionV3 Test Acc: {accuracy_score(y_true_inc, y_pred_inc):.4f}')\nprint(f'MobileNetV2 Test Acc: {accuracy_score(y_true_mob, y_pred_mob):.4f}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-17T15:27:01.160721Z","iopub.execute_input":"2026-09-17T15:27:01.161126Z","iopub.status.idle":"2026-09-17T15:27:01.170018Z","shell.execute_reply.started":"2026-09-17T15:27:01.161099Z","shell.execute_reply":"2026-09-17T15:27:01.169235Z"}},"outputs":[],"execution_count":null}]}