{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.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":7327,"databundleVersionId":861871,"sourceType":"competition"}],"dockerImageVersionId":30635,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# [🐕 Dog Breed Classification](https://www.kaggle.com/competitions/dog-breed-identification/)\n\n# Objective\n\nDetermine the breed of a dog in an image\n\n# [Dataset](https://www.kaggle.com/competitions/dog-breed-identification/data)\n\nThe dataset comprises 120 breeds of dogs. Each image has a filename that is its unique `id`\n\n# [Evaluation](www.kaggle.com/competitions/dog-breed-identification/overview/evaluation)\n\nMetrics: [Multi Class Log Loss](https://stats.stackexchange.com/questions/113301/multi-class-logarithmic-loss-function-per-class)","metadata":{}},{"cell_type":"markdown","source":"# Data Loading","metadata":{}},{"cell_type":"code","source":"from pathlib import Path\n\nimport pandas as pd\n\npd.set_option('display.max_columns', None)\n\nKAGGLE_DATA_DIR = Path(\"/kaggle/input/dog-breed-identification\")\n\nlabels_df = pd.read_csv(KAGGLE_DATA_DIR / 'labels.csv')\n\nfilenames = [str(KAGGLE_DATA_DIR / f'train/{filename}.jpg') for filename in labels_df[\"id\"]]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T00:59:38.856438Z","iopub.execute_input":"2025-08-29T00:59:38.856695Z","iopub.status.idle":"2025-08-29T00:59:39.284286Z","shell.execute_reply.started":"2025-08-29T00:59:38.856672Z","shell.execute_reply":"2025-08-29T00:59:39.283299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = labels_df[\"breed\"].to_numpy()\nlen(labels) == len(filenames)","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:39.286173Z","iopub.execute_input":"2025-08-29T00:59:39.286536Z","iopub.status.idle":"2025-08-29T00:59:39.293416Z","shell.execute_reply.started":"2025-08-29T00:59:39.286511Z","shell.execute_reply":"2025-08-29T00:59:39.292428Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"filenames[:5]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T00:59:39.294638Z","iopub.execute_input":"2025-08-29T00:59:39.295026Z","iopub.status.idle":"2025-08-29T00:59:39.309212Z","shell.execute_reply.started":"2025-08-29T00:59:39.294996Z","shell.execute_reply":"2025-08-29T00:59:39.308426Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(filenames)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T00:59:39.310435Z","iopub.execute_input":"2025-08-29T00:59:39.310667Z","iopub.status.idle":"2025-08-29T00:59:39.326888Z","shell.execute_reply.started":"2025-08-29T00:59:39.310637Z","shell.execute_reply":"2025-08-29T00:59:39.326145Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# EDA (Explotary Data Analysis)","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nimport numpy as np\nfrom IPython.display import Image\n\n%matplotlib inline","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T00:59:39.329249Z","iopub.execute_input":"2025-08-29T00:59:39.329480Z","iopub.status.idle":"2025-08-29T00:59:39.336482Z","shell.execute_reply.started":"2025-08-29T00:59:39.329460Z","shell.execute_reply":"2025-08-29T00:59:39.335681Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"unique_breeds = np.unique(labels)\nunique_breeds[:10]","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:39.337421Z","iopub.execute_input":"2025-08-29T00:59:39.337741Z","iopub.status.idle":"2025-08-29T00:59:39.353501Z","shell.execute_reply.started":"2025-08-29T00:59:39.337711Z","shell.execute_reply":"2025-08-29T00:59:39.352685Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T00:59:39.354543Z","iopub.execute_input":"2025-08-29T00:59:39.354853Z","iopub.status.idle":"2025-08-29T00:59:39.369084Z","shell.execute_reply.started":"2025-08-29T00:59:39.354824Z","shell.execute_reply":"2025-08-29T00:59:39.368196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels_df.describe()","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:39.370065Z","iopub.execute_input":"2025-08-29T00:59:39.370584Z","iopub.status.idle":"2025-08-29T00:59:39.392282Z","shell.execute_reply.started":"2025-08-29T00:59:39.370553Z","shell.execute_reply":"2025-08-29T00:59:39.391491Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels_df[\"breed\"].value_counts(ascending=True).plot.barh(figsize=(20, 30))\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:39.393252Z","iopub.execute_input":"2025-08-29T00:59:39.393479Z","iopub.status.idle":"2025-08-29T00:59:40.857206Z","shell.execute_reply.started":"2025-08-29T00:59:39.393458Z","shell.execute_reply":"2025-08-29T00:59:40.856267Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"example_dog_breed_name = labels_df[labels_df[\"id\"] == '0021f9ceb3235effd7fcde7f7538ed62']['breed'].values[0]\n\nprint(f\"{example_dog_breed_name}\")\n\nexample_dog_breed = Image(KAGGLE_DATA_DIR /'train/0021f9ceb3235effd7fcde7f7538ed62.jpg')\nexample_dog_breed","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:40.858353Z","iopub.execute_input":"2025-08-29T00:59:40.858667Z","iopub.status.idle":"2025-08-29T00:59:40.878578Z","shell.execute_reply.started":"2025-08-29T00:59:40.858637Z","shell.execute_reply":"2025-08-29T00:59:40.877721Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image = plt.imread(filenames[0])\nimage.shape","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:40.879771Z","iopub.execute_input":"2025-08-29T00:59:40.880063Z","iopub.status.idle":"2025-08-29T00:59:40.905065Z","shell.execute_reply.started":"2025-08-29T00:59:40.880040Z","shell.execute_reply":"2025-08-29T00:59:40.904190Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image[:1]","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:40.906038Z","iopub.execute_input":"2025-08-29T00:59:40.906306Z","iopub.status.idle":"2025-08-29T00:59:40.912368Z","shell.execute_reply.started":"2025-08-29T00:59:40.906285Z","shell.execute_reply":"2025-08-29T00:59:40.911400Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# ETL (Extract, Transform and Load)","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import LabelBinarizer\n\nimport tensorflow as tf\n\nIMG_WIDTH = 224\nIMG_HEIGHT = IMG_WIDTH\nIMG_CHANNELS = 3\n\nBATCH_SIZE = 32\n\n@tf.autograph.experimental.do_not_convert\ndef process_image(image_path: str):\n    \"\"\"\n    Take an image file path and turns the image into a Tensor\n    \"\"\"\n    image = tf.io.read_file(image_path)\n    # 3 channels (Red, Green, Blue)\n    image = tf.image.decode_jpeg(image, channels=3)\n    # Convert the colour channel values from 0-255, to 0-1 values\n    # image = tf.image.convert_image_dtype(image, tf.float32)\n    image = tf.cast(image, tf.float32) / 255.0\n    # Resize image to IMG_SIZE\n    image = tf.image.resize(image, size=[IMG_WIDTH, IMG_HEIGHT])\n    return image\n\n@tf.autograph.experimental.do_not_convert\ndef get_image_label(image_path: str, label: str):\n    \"\"\"\n    Takes an image file path name and the associated label,\n    processes the image and returns a tuple (image, label)\n    \"\"\"\n    image = process_image(image_path)\n    return image, label\n\ndef create_data_batches(X, y=None, batch_size=BATCH_SIZE, valid_data=False, test_data=False):\n    \"\"\"\n    Creates batches of data out of image (X) and label (y) pairs.\n    Shuffles the data if it's training data but doesn't shuffle if it's validation data.\n    Also accepts test data as input (no labels).\n    \"\"\"\n    # If the data is a test dataset, we probably don't have have labels\n    if test_data:\n        print(\"Creating test data batches...\")\n        data = tf.data.Dataset.from_tensor_slices((tf.constant(X))) # only filepaths (no labels)\n        data_batch = data.map(process_image).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE).cache()\n        return data_batch\n\n    # If the data is a valid dataset, we don't need to shuffle it\n    if valid_data:\n        print(\"Creating validation data batches...\")\n        data = tf.data.Dataset.from_tensor_slices((\n                                                  tf.constant(X), # filepaths\n                                                  tf.constant(y)  # labels\n                                                  ))\n        data_batch = data.map(get_image_label).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE).cache()\n        return data_batch\n\n    print(\"Creating training data batches...\")\n    data = tf.data.Dataset.from_tensor_slices((\n                                             tf.constant(X),\n                                             tf.constant(y)\n                                            ))\n    data_batch = data.shuffle(buffer_size=len(X)) \\\n                   .map(get_image_label) \\\n                   .batch(BATCH_SIZE) \\\n                   .prefetch(tf.data.AUTOTUNE).cache()\n    return data_batch\n\nrandom_state = 42\nrandom_seed = random_state\n\ntf.random.set_seed(random_seed)\n\nprint(\"TF version:\", tf.__version__)\n\nif tf.config.list_physical_devices(\"GPU\"):\n    print(\"GPU enabled\")\nelse:\n    print(\"GPU is not available, switch to CPU\")\n\nlb = LabelBinarizer()\n\nencoded_labels = lb.fit_transform(labels)\n\nprint(f\"{encoded_labels[:1] = }\")\n\nX_train, X_test, y_train, y_test = train_test_split(filenames, \n                                                    encoded_labels, \n                                                    test_size=0.1, \n                                                    random_state=random_state, \n                                                    stratify=encoded_labels)\n\nX_train, X_valid, y_train, y_valid = train_test_split(X_train, \n                                                      y_train, \n                                                      test_size=0.1, \n                                                      random_state=7, \n                                                      stratify=y_train)\n\ntrain_data = create_data_batches(X_train, y_train)\nvalid_data = create_data_batches(X_valid, y_valid, valid_data=True)\ntest_data_ = create_data_batches(X_test, y_test, valid_data=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T00:59:40.913597Z","iopub.execute_input":"2025-08-29T00:59:40.913904Z","iopub.status.idle":"2025-08-29T00:59:54.947259Z","shell.execute_reply.started":"2025-08-29T00:59:40.913873Z","shell.execute_reply":"2025-08-29T00:59:54.946320Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.element_spec, valid_data.element_spec","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:54.950640Z","iopub.execute_input":"2025-08-29T00:59:54.951165Z","iopub.status.idle":"2025-08-29T00:59:54.956553Z","shell.execute_reply.started":"2025-08-29T00:59:54.951132Z","shell.execute_reply":"2025-08-29T00:59:54.955667Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_25_images(images, labels):\n    \"\"\"\n    Displays a plot of 25 images and their labels from a data batch.\n    \"\"\"\n    plt.figure(figsize=(15, 10))\n    for i in range(25):\n        # 5 rows, 5 columns\n        ax = plt.subplot(5, 5, i+1)\n        plt.imshow(images[i])\n        plt.title(unique_breeds[np.argmax(labels[i])])\n        # Turn the grid lines off\n        plt.axis(\"off\")\n    plt.tight_layout()","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:54.957722Z","iopub.execute_input":"2025-08-29T00:59:54.958081Z","iopub.status.idle":"2025-08-29T00:59:54.973547Z","shell.execute_reply.started":"2025-08-29T00:59:54.958044Z","shell.execute_reply":"2025-08-29T00:59:54.972717Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_images, train_labels = next(train_data.as_numpy_iterator())\nshow_25_images(train_images, train_labels)","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:54.974747Z","iopub.execute_input":"2025-08-29T00:59:54.975057Z","iopub.status.idle":"2025-08-29T00:59:57.610866Z","shell.execute_reply.started":"2025-08-29T00:59:54.975027Z","shell.execute_reply":"2025-08-29T00:59:57.609482Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"valid_images, valid_labels = next(valid_data.as_numpy_iterator())\nshow_25_images(valid_images, valid_labels)","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:57.612103Z","iopub.execute_input":"2025-08-29T00:59:57.612341Z","iopub.status.idle":"2025-08-29T00:59:59.896565Z","shell.execute_reply.started":"2025-08-29T00:59:57.612319Z","shell.execute_reply":"2025-08-29T00:59:59.895673Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_images, test_labels = next(test_data_.as_numpy_iterator())\nshow_25_images(test_images, test_labels)","metadata":{"execution":{"iopub.status.busy":"2025-08-29T00:59:59.897767Z","iopub.execute_input":"2025-08-29T00:59:59.898038Z","iopub.status.idle":"2025-08-29T01:00:02.185110Z","shell.execute_reply.started":"2025-08-29T00:59:59.898014Z","shell.execute_reply":"2025-08-29T01:00:02.184012Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Creation","metadata":{}},{"cell_type":"code","source":"import tensorflow_hub as hub\nfrom tensorflow.keras import layers\n\n# Helper functions\n\ndef save_model(model, file_name, path_folder='./', include_datetime=True):\n    \"\"\"\n    Saves a given model in a models directory and appends a suffix (string).\n    \"\"\"\n    # Create a model directory pathname with current time\n    suffix = None\n    \n    if include_datetime:\n        suffix = f'{datetime.datetime.now().strftime(\"%Y%m%d-%H%M%s\")}'\n    \n    if suffix:\n        full_file_name = f'{suffix}-{file_name}.keras'\n    else:\n        full_file_name = f'{file_name}.keras'\n    \n    model_path = Path(path_folder) / full_file_name\n    \n    print(f\"Saving model to: {model_path}...\")\n    \n    model.save(model_path)\n    \n    return model_path\n\ndef load_model(model_path):\n    \"\"\"\n    Loads a saved model from a specified path.\n    \"\"\"\n    \n    print(f\"Loading saved model from: {model_path}\")\n    \n    model = tf.keras.models.load_model(model_path)\n    \n    return model\n    \ndef MobileNetV2(input_shape=(IMG_WIDTH, IMG_HEIGHT, IMG_CHANNELS), \n                num_classes=len(unique_breeds), \n                base_model_trainable = False,\n                data_augmentation=None):\n    \n    base_model = tf.keras.applications.mobilenet_v2.MobileNetV2(input_shape=input_shape,\n                                                                include_top=False,\n                                                                weights=\"imagenet\",\n                                                                pooling=\"max\")\n    \n    base_model.trainable = base_model_trainable\n    \n    inputs = layers.Input(input_shape)\n    \n    if data_augmentation:\n        X = data_augmentation(inputs)\n    else:\n        X = inputs\n    \n    X = base_model(X, training=base_model_trainable) \n    \n    # X = layers.Dropout(.5)(X)\n    \n    outputs = layers.Dense(num_classes)(X)\n    \n    return tf.keras.Model(inputs = inputs, outputs=outputs)\n\n\ndef create_model(model=MobileNetV2(), learning_rate=1e-3):\n    model.compile(optimizer=tf.keras.optimizers.Adam(lr=learning_rate),\n                  loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True),\n                  metrics=[\"accuracy\"])\n    return model\n\ndef data_augmenter():\n    '''\n    Create a Sequential model composed of 2 layers\n    Returns:\n        tf.keras.Sequential\n    '''\n    data_augmentation = tf.keras.models.Sequential()\n    data_augmentation.add(layers.RandomFlip('horizontal'))\n    data_augmentation.add(layers.RandomZoom(.5))\n    # data_augmentation.add(layers.RandomRotation(0.2))\n    \n    return data_augmentation\n\nlr = 3e-1\n\nmodel = create_model(MobileNetV2(data_augmentation=data_augmenter()), learning_rate=lr)\n\n# model = create_model(learning_rate=lr)\n\nreduce_lr = tf.keras.callbacks.ReduceLROnPlateau(\n    monitor='val_loss', \n    factor=.3, \n    patience=3, \n    min_lr=1e-9\n)\n\nval_acc_stopping = tf.keras.callbacks.EarlyStopping(\n    monitor=\"val_accuracy\",\n    restore_best_weights=True, \n    patience=5,\n    start_from_epoch = 3,\n)\n\nmodel.summary(show_trainable=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T01:26:09.408324Z","iopub.execute_input":"2025-08-29T01:26:09.409008Z","iopub.status.idle":"2025-08-29T01:26:13.276659Z","shell.execute_reply.started":"2025-08-29T01:26:09.408975Z","shell.execute_reply":"2025-08-29T01:26:13.275762Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Training","metadata":{}},{"cell_type":"code","source":"epochs = 20\n\nhistory = model.fit(train_data, \n                    validation_data=valid_data, \n                    callbacks=[reduce_lr, val_acc_stopping],\n                    epochs=epochs)","metadata":{"execution":{"iopub.status.busy":"2025-08-29T01:26:18.090446Z","iopub.execute_input":"2025-08-29T01:26:18.091380Z","iopub.status.idle":"2025-08-29T01:28:25.924401Z","shell.execute_reply.started":"2025-08-29T01:26:18.091343Z","shell.execute_reply":"2025-08-29T01:28:25.923458Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Evaluation","metadata":{}},{"cell_type":"code","source":"acc = [0.] + history.history['accuracy']\nval_acc = [0.] + history.history['val_accuracy']\n\nloss = history.history['loss']\nval_loss = history.history['val_loss']\n\nplt.figure(figsize=(8, 8))\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([min(plt.ylim()),1])\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.legend(loc='upper right')\nplt.ylabel('Cross Entropy')\nplt.ylim([0,1.0])\nplt.title('Training and Validation Loss')\nplt.xlabel('epoch')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-08-29T01:25:51.865400Z","iopub.execute_input":"2025-08-29T01:25:51.866323Z","iopub.status.idle":"2025-08-29T01:25:52.278654Z","shell.execute_reply.started":"2025-08-29T01:25:51.866282Z","shell.execute_reply":"2025-08-29T01:25:52.277806Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_model = model.layers[-2]\nbase_model.trainable = True\n\nmodel.summary(show_trainable=True)","metadata":{"execution":{"iopub.status.busy":"2025-08-29T01:25:52.279868Z","iopub.execute_input":"2025-08-29T01:25:52.280567Z","iopub.status.idle":"2025-08-29T01:25:52.315674Z","shell.execute_reply.started":"2025-08-29T01:25:52.280529Z","shell.execute_reply":"2025-08-29T01:25:52.314889Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"loss_function=tf.keras.losses.CategoricalCrossentropy(from_logits=True)\noptimizer = tf.keras.optimizers.Adam(lr * 1e-3)\nmetrics=[tf.keras.metrics.CategoricalAccuracy(name='accuracy', dtype=np.float32)]","metadata":{"execution":{"iopub.status.busy":"2025-08-29T01:18:41.205144Z","iopub.execute_input":"2025-08-29T01:18:41.205940Z","iopub.status.idle":"2025-08-29T01:18:41.215375Z","shell.execute_reply.started":"2025-08-29T01:18:41.205908Z","shell.execute_reply":"2025-08-29T01:18:41.214487Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fine_tune_epochs = 50\ntotal_epochs = epochs + fine_tune_epochs\n\nhistory_fine = model.fit(train_data, \n                         validation_data=valid_data, \n                         epochs=total_epochs, \n                         callbacks=[reduce_lr, val_acc_stopping],\n                         initial_epoch=history.epoch[-1])","metadata":{"execution":{"iopub.status.busy":"2025-08-29T01:18:44.914573Z","iopub.execute_input":"2025-08-29T01:18:44.915527Z","iopub.status.idle":"2025-08-29T01:19:33.392033Z","shell.execute_reply.started":"2025-08-29T01:18:44.915493Z","shell.execute_reply":"2025-08-29T01:19:33.391142Z"},"trusted":true},"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":{"execution":{"iopub.status.busy":"2025-08-29T01:19:33.393869Z","iopub.execute_input":"2025-08-29T01:19:33.394562Z","iopub.status.idle":"2025-08-29T01:19:33.398967Z","shell.execute_reply.started":"2025-08-29T01:19:33.394524Z","shell.execute_reply":"2025-08-29T01:19:33.398018Z"},"trusted":true},"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([epochs-1,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([epochs-1,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":{"execution":{"iopub.status.busy":"2025-08-29T01:19:33.400208Z","iopub.execute_input":"2025-08-29T01:19:33.400464Z","iopub.status.idle":"2025-08-29T01:19:33.796083Z","shell.execute_reply.started":"2025-08-29T01:19:33.400441Z","shell.execute_reply":"2025-08-29T01:19:33.795192Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_pred_label(prediction_probabilities):\n  \"\"\"\n  Turns an array of prediction probabilities into a label.\n  \"\"\"\n  return unique_breeds[np.argmax(prediction_probabilities)]\n    \npreds = model.predict(test_data_, verbose=0)\n\n# First prediction\nindex = 0\nprint(f\"Max value (probability of prediction): {np.max(tf.nn.softmax(preds[index]))}\")\nprint(\"Sum:\", np.sum(tf.nn.softmax(preds[index])))\nprint(\"Max index:\", np.argmax(preds[index]))\nprint(\"Predicted label:\", unique_breeds[np.argmax(preds[index])])\nprint(\"Actual label:\", unique_breeds[np.argmax(y_test[index])])\n\npred_label = get_pred_label(preds[7])\nprint(f\"{pred_label = }\")","metadata":{"execution":{"iopub.status.busy":"2025-08-28T15:57:17.093945Z","iopub.execute_input":"2025-08-28T15:57:17.094324Z","iopub.status.idle":"2025-08-28T15:57:17.466635Z","shell.execute_reply.started":"2025-08-28T15:57:17.094298Z","shell.execute_reply":"2025-08-28T15:57:17.465843Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a function to unbatch a batch dataset\ndef unbatchify(data):\n    \"\"\"\n    Takes a batched dataset of (image, label) Tensors and reutrns separate arrays\n    of images and labels.\n    \"\"\"\n    images = []\n    labels = []\n    \n    # Loop through unbatched data \n    for image, label in data.unbatch().as_numpy_iterator():\n        images.append(image)\n        labels.append(unique_breeds[np.argmax(label)])\n        \n    return images, labels\n\n# Unbatchify the validation data\ntest_images, test_labels = unbatchify(test_data_)\ntest_images[0], test_labels[0]","metadata":{"execution":{"iopub.status.busy":"2025-08-28T15:57:43.375995Z","iopub.execute_input":"2025-08-28T15:57:43.376788Z","iopub.status.idle":"2025-08-28T15:57:43.934321Z","shell.execute_reply.started":"2025-08-28T15:57:43.376760Z","shell.execute_reply":"2025-08-28T15:57:43.933357Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_pred(prediction_probabilities, labels, images, n=1):\n    \"\"\"\n    View the prediction, ground truth and image for sample n\n    \"\"\"\n    pred_prob, true_label, image = prediction_probabilities[n], labels[n], images[n]\n    \n    pred_label = get_pred_label(pred_prob)\n    \n    plt.imshow(image)\n    plt.xticks([])\n    plt.yticks([])\n    \n    if pred_label == true_label:\n        color = \"green\"\n    else:\n        color = \"red\"\n    \n    plt.title(\"{} {:2.0f}% {}\".format(pred_label, np.max(pred_prob) * 100, true_label), color=color)\n\nplot_pred(prediction_probabilities=tf.nn.softmax(preds, axis=1),\n          labels=test_labels,\n          images=test_images,\n          n=10)","metadata":{"execution":{"iopub.status.busy":"2025-08-28T15:57:49.625821Z","iopub.execute_input":"2025-08-28T15:57:49.626620Z","iopub.status.idle":"2025-08-28T15:57:49.870938Z","shell.execute_reply.started":"2025-08-28T15:57:49.626590Z","shell.execute_reply":"2025-08-28T15:57:49.870001Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_pred_conf(prediction_probabilities, labels, n=1):\n    \"\"\"\n    Plus the top 10 highest prediction confidences along with the truth label for sample n.\n    \"\"\"\n    pred_prob, true_label = prediction_probabilities[n], labels[n]\n    \n    # Get the predicted label\n    pred_label = get_pred_label(pred_prob)\n    \n    # Find the top 10 prediction confidence indexes\n    top_10_pred_indexes = pred_prob.argsort()[-10:][::-1]\n    # Find the top 10 prediction confidence values\n    top_10_pred_values = pred_prob[top_10_pred_indexes]\n    # Find the top 10 prediction labels\n    top_10_pred_labels = unique_breeds[top_10_pred_indexes]\n    \n    top_10_plot = plt.barh(np.arange(len(top_10_pred_labels)), top_10_pred_values, color=\"grey\")\n    \n    plt.gca().invert_yaxis()\n    \n    plt.yticks(np.arange(len(top_10_pred_labels)), labels=top_10_pred_labels)\n    \n    if np.isin(true_label, top_10_pred_labels):\n        top_10_plot[np.argmax(top_10_pred_labels == true_label)].set_color(\"green\")  \n\nprediction_probabilities = tf.nn.softmax(preds, axis=1).numpy()\nplot_pred_conf(prediction_probabilities=prediction_probabilities,\n               labels=test_labels,\n               n=42)","metadata":{"execution":{"iopub.status.busy":"2025-08-28T15:58:45.606731Z","iopub.execute_input":"2025-08-28T15:58:45.607515Z","iopub.status.idle":"2025-08-28T15:58:45.838439Z","shell.execute_reply.started":"2025-08-28T15:58:45.607486Z","shell.execute_reply":"2025-08-28T15:58:45.837574Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"i_multiplier = 30\nnum_rows = 3\nnum_cols = 2\nnum_images = num_rows*num_cols\nplt.figure(figsize=(10*num_cols, 5*num_rows))\nfor i in range(num_images):\n    plt.subplot(num_rows, 2*num_cols, 2*i+1)\n    plot_pred(prediction_probabilities=prediction_probabilities,\n              labels=test_labels,\n              images=test_images,\n              n=i+i_multiplier)\n    plt.subplot(num_rows, 2*num_cols, 2*i+2)\n    plot_pred_conf(prediction_probabilities=prediction_probabilities,\n                   labels=test_labels,\n                   n=i+i_multiplier)\nplt.tight_layout(h_pad=1.0)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-08-28T15:59:04.636348Z","iopub.execute_input":"2025-08-28T15:59:04.636777Z","iopub.status.idle":"2025-08-28T15:59:06.181984Z","shell.execute_reply.started":"2025-08-28T15:59:04.636737Z","shell.execute_reply":"2025-08-28T15:59:06.180882Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\n\nfrom sklearn.metrics import confusion_matrix\n\ncf_matrix = confusion_matrix(test_labels, [get_pred_label(pred) for pred in prediction_probabilities])\n\nplt.figure(figsize=(30,15))\n\nax = sns.heatmap(cf_matrix, \n                 cmap=\"Reds\", \n                 linewidths=1, \n                 xticklabels=lb.classes_, \n                 yticklabels=lb.classes_)\n\nax.set_xlabel('Predicted')\nax.set_ylabel('Actual')\nax.xaxis.tick_top()\nplt.tight_layout()\nplt.xticks(rotation=90)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-08-28T16:01:35.180344Z","iopub.execute_input":"2025-08-28T16:01:35.180667Z","iopub.status.idle":"2025-08-28T16:01:38.001832Z","shell.execute_reply.started":"2025-08-28T16:01:35.180641Z","shell.execute_reply":"2025-08-28T16:01:38.000903Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Saving Model","metadata":{}},{"cell_type":"code","source":"save_model(model, file_name=\"mobilenetv2-Adam\", include_datetime=False);","metadata":{"execution":{"iopub.status.busy":"2025-08-28T16:04:18.799721Z","iopub.execute_input":"2025-08-28T16:04:18.800523Z","iopub.status.idle":"2025-08-28T16:04:18.965927Z","shell.execute_reply.started":"2025-08-28T16:04:18.800495Z","shell.execute_reply":"2025-08-28T16:04:18.965014Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Submission","metadata":{}},{"cell_type":"code","source":"submitted_data_dir = KAGGLE_DATA_DIR / 'test'\nsubmitted_img_names = [str(filename) for filename in submitted_data_dir.iterdir()]\nsubmitted_img_names[:2]","metadata":{"execution":{"iopub.status.busy":"2025-08-28T16:11:10.851490Z","iopub.execute_input":"2025-08-28T16:11:10.852254Z","iopub.status.idle":"2025-08-28T16:11:10.881946Z","shell.execute_reply.started":"2025-08-28T16:11:10.852223Z","shell.execute_reply":"2025-08-28T16:11:10.881228Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submitted_data = create_data_batches(submitted_img_names, test_data=True)\nnext(submitted_data.as_numpy_iterator())[0][0][0]","metadata":{"execution":{"iopub.status.busy":"2025-08-28T16:11:11.215080Z","iopub.execute_input":"2025-08-28T16:11:11.215857Z","iopub.status.idle":"2025-08-28T16:11:11.280487Z","shell.execute_reply.started":"2025-08-28T16:11:11.215828Z","shell.execute_reply":"2025-08-28T16:11:11.279536Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"next(submitted_data_dir.iterdir()).stem","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T16:15:58.986372Z","iopub.execute_input":"2025-08-28T16:15:58.987102Z","iopub.status.idle":"2025-08-28T16:15:58.997334Z","shell.execute_reply.started":"2025-08-28T16:15:58.987075Z","shell.execute_reply":"2025-08-28T16:15:58.996422Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submit_preds = tf.nn.softmax(model.predict(submitted_data, verbose=1), axis=1).numpy()\n\nout_df = pd.DataFrame(columns=[\"id\"]+list(unique_breeds))\nsubmit_ids = [entry.stem for entry in submitted_data_dir.iterdir()]\nout_df.loc[:,\"id\"] = submit_ids\nout_df[list(unique_breeds)] = submit_preds\nout_df.head()","metadata":{"execution":{"iopub.status.busy":"2025-08-28T16:16:41.605822Z","iopub.execute_input":"2025-08-28T16:16:41.606164Z","iopub.status.idle":"2025-08-28T16:16:45.339888Z","shell.execute_reply.started":"2025-08-28T16:16:41.606139Z","shell.execute_reply":"2025-08-28T16:16:45.339014Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"out_df.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2025-08-28T16:17:02.768822Z","iopub.execute_input":"2025-08-28T16:17:02.769652Z","iopub.status.idle":"2025-08-28T16:17:04.012236Z","shell.execute_reply.started":"2025-08-28T16:17:02.769621Z","shell.execute_reply":"2025-08-28T16:17:04.011287Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !rm -rf /kaggle/working/*","metadata":{"trusted":true,"execution":{"execution_failed":"2025-08-28T15:27:33.338Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Predicting with custom images","metadata":{"_kg_hide-input":true}},{"cell_type":"code","source":"# cust_imgs_path = Path(\"replace/custom/path/here\")\n# cust_imgs_filenames = [filename for filename in cust_imgs_path.iterdir()]\n# cust_imgs_filenames","metadata":{"_kg_hide-input":true,"trusted":true,"execution":{"execution_failed":"2025-08-28T15:27:33.338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# cust_data = create_data_batches(cust_imgs_filenames, test_data=True)\n# cust_data","metadata":{"_kg_hide-input":true,"trusted":true,"execution":{"execution_failed":"2025-08-28T15:27:33.338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# custom_preds = tf.nn.softmax(model.predict(cust_data), axis=1).numpy()\n# custom_pred_labels = [get_pred_label(pred) for pred in custom_preds]\n# custom_pred_labels","metadata":{"_kg_hide-input":true,"trusted":true,"execution":{"execution_failed":"2025-08-28T15:27:33.338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# custom_imgs = [img for img in cust_data.unbatch().as_numpy_iterator()]\n\n# plt.figure(figsize=(10, 10))\n# for i, image in enumerate(custom_imgs):\n#     plt.subplot(1, 4, i+1)\n#     plt.xticks([])\n#     plt.yticks([])\n#     plt.title(custom_pred_labels[i])\n#     plt.imshow(image)","metadata":{"_kg_hide-input":true,"trusted":true,"execution":{"execution_failed":"2025-08-28T15:27:33.338Z"}},"outputs":[],"execution_count":null}]}