{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"},{"sourceId":12481101,"sourceType":"datasetVersion","datasetId":7875270}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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":"2025-07-22T03:01:04.019722Z","iopub.execute_input":"2025-07-22T03:01:04.020532Z","iopub.status.idle":"2025-07-22T03:01:04.507323Z","shell.execute_reply.started":"2025-07-22T03:01:04.020502Z","shell.execute_reply":"2025-07-22T03:01:04.506254Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Import the libraries\n%reset -f\nfrom __future__ import print_function\n\nimport math\nimport seaborn as sns\nimport numpy as np\nimport numpy.linalg as nla\nimport pandas as pd\nimport re\nimport six\nfrom os.path import join\nimport tensorflow as tf\nfrom matplotlib import pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom keras_tuner import HyperParameters\nfrom sklearn.metrics import accuracy_score\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense\nfrom tensorflow.keras.optimizers import SGD\nimport os\nimport glob\nfrom tensorflow.data.experimental import load\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:15.063458Z","iopub.execute_input":"2025-07-22T03:01:15.064532Z","iopub.status.idle":"2025-07-22T03:01:21.227330Z","shell.execute_reply.started":"2025-07-22T03:01:15.064501Z","shell.execute_reply":"2025-07-22T03:01:21.226363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\n\nstart_time = time.time()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:21.228625Z","iopub.execute_input":"2025-07-22T03:01:21.229248Z","iopub.status.idle":"2025-07-22T03:01:21.234141Z","shell.execute_reply.started":"2025-07-22T03:01:21.229213Z","shell.execute_reply":"2025-07-22T03:01:21.233071Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Function to decode and normalize the images and standardize to RGB\nimage_size = [224,224]\n\ndef decode_image(image_data):\n    \"\"\"Function to decode the image from the .tfrec\"\"\"\n    ## Converts the raw JPEG file bytes into a 3D tensor, channels=3  indicates RGB, create the shape (height, width, 3), the output is a uint tensor with values 0-255\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    \n    ## Resize all images to the image size specified above [512,512], using bilinear method to smooth the images for efficient processing\n    image = tf.image.resize(image, image_size, method = \"bilinear\")\n    \n    ## Converts the uint to float32, then normalizes the inputs by dividing by the number of pixel values 255\n    ## Removed /255 because EfficientNet expects input [0-255] not [0-1]\n    image = tf.cast(image, tf.float32) \n    \n    ## Takes the image size defined above this function and reshapes it to be the image size [height, width, 3]\n    image = tf.reshape(image, [*image_size,3])\n    return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:23.531456Z","iopub.execute_input":"2025-07-22T03:01:23.531799Z","iopub.status.idle":"2025-07-22T03:01:23.537819Z","shell.execute_reply.started":"2025-07-22T03:01:23.531772Z","shell.execute_reply":"2025-07-22T03:01:23.536805Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_dataset(filenames, labeled=True):\n    \"\"\"Load TFRecord dataset from filenames\"\"\"\n    ## Creates a dataset that reads the files, AUTOTUNE processes them simultneously and TF optimizes the number of readers\n    ## Creates a dataset of the raw binary .tfrec examples\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=tf.data.AUTOTUNE)\n\n    ## dataset.map applies the read_labeled_tfrec function to each example, AUTOTUNE processes them simultneously and TF optimizes the number of readers\n    ## Transforms the raw data to (image_tensor, label_int) pairs\n    if labeled:\n        dataset = dataset.map(read_labeled_tfrec, num_parallel_calls=tf.data.AUTOTUNE)\n    else:\n        dataset = dataset.map(read_unlabeled_tfrec, num_parallel_calls=tf.data.AUTOTUNE)       \n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:25.571319Z","iopub.execute_input":"2025-07-22T03:01:25.571663Z","iopub.status.idle":"2025-07-22T03:01:25.577921Z","shell.execute_reply.started":"2025-07-22T03:01:25.571637Z","shell.execute_reply":"2025-07-22T03:01:25.576488Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Function to return an image, label pair for the training and validation sets\ndef read_labeled_tfrec(input_example):\n    \"\"\"Read and parse the labeled .tfrec\"\"\"\n    ## Tells Tensorflow how to interpret the binary .tfrec data, \"image\" tells TF to expect binary jppeg bytes, \"class\" tells TF to expect integer labels (the flower labels)\n    labeled_tfrec_format = { \n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64)\n    }\n    ## Parses the input_example using the format specified above\n    ## Takes raw binary data (input_example) and uses the labeled_tfrec_format to return a dictionary {image bytes, flower label}\n    input_example = tf.io.parse_single_example(input_example, labeled_tfrec_format)\n    \n    ## Process the image - Takes the JPEG bytes from the example image, and normalizes them to a [512,512,3] tensor using the decode_image function\n    image = decode_image(input_example[\"image\"])\n    \n    ## Process the label - input_example['class'] is the flower class ID\n    label = tf.cast(input_example['class'], tf.int32)\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:27.276208Z","iopub.execute_input":"2025-07-22T03:01:27.276948Z","iopub.status.idle":"2025-07-22T03:01:27.282433Z","shell.execute_reply.started":"2025-07-22T03:01:27.276912Z","shell.execute_reply":"2025-07-22T03:01:27.281323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Function to return an image without labels for the test set\ndef read_unlabeled_tfrec(input_example):\n    \"\"\"Read and parse the unlabeled .tfrec\"\"\"\n    unlabeled_tfrec_format = { \n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\": tf.io.FixedLenFeature([], tf.string)\n    }    \n    input_example = tf.io.parse_single_example(input_example, unlabeled_tfrec_format)\n    \n    ## Process the image - Takes the JPEG bytes from the example image, and normalizes them to a [512,512,3] tensor using the decode_image function\n    image = decode_image(input_example[\"image\"])\n    \n    ## Process the label - input_example['id'] is the image ID\n    image_id = input_example['id']\n    return image, image_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:29.475995Z","iopub.execute_input":"2025-07-22T03:01:29.476337Z","iopub.status.idle":"2025-07-22T03:01:29.481990Z","shell.execute_reply.started":"2025-07-22T03:01:29.476312Z","shell.execute_reply":"2025-07-22T03:01:29.480869Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Get filenames for 224x224 images only\nfolder = 'tfrecords-jpeg-224x224'\ntrain_files = tf.io.gfile.glob(f\"/kaggle/input/tpu-getting-started/{folder}/train/*.tfrec\")\nval_files = tf.io.gfile.glob(f\"/kaggle/input/tpu-getting-started/{folder}/val/*.tfrec\")\ntest_files = tf.io.gfile.glob(f\"/kaggle/input/tpu-getting-started/{folder}/test/*.tfrec\")\n\n## Create train, validation and test data set\ntrain_dataset = load_dataset(train_files, labeled=True)\nvalidation_dataset = load_dataset(val_files, labeled=True)\ntest_dataset = load_dataset(test_files, labeled=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:31.943651Z","iopub.execute_input":"2025-07-22T03:01:31.944083Z","iopub.status.idle":"2025-07-22T03:01:32.258042Z","shell.execute_reply.started":"2025-07-22T03:01:31.944054Z","shell.execute_reply":"2025-07-22T03:01:32.257160Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Shuffling the training and validation sets\n\n## Sets random seed for reproducability\ntf.random.set_seed(42)\n\n## Set shuffle buffer to set how many are shuffled at once\nshuffle_buffer = 500\n\n## Shuffle the training set\ntrain_dataset = train_dataset.shuffle(shuffle_buffer, seed=42, reshuffle_each_iteration=False)\n\n## Shuffle the validation set\nvalidation_dataset = validation_dataset.shuffle(shuffle_buffer, seed=42, reshuffle_each_iteration=False)\n\n## No shuffling of test data as it's not needed","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:35.507105Z","iopub.execute_input":"2025-07-22T03:01:35.507444Z","iopub.status.idle":"2025-07-22T03:01:35.522230Z","shell.execute_reply.started":"2025-07-22T03:01:35.507423Z","shell.execute_reply":"2025-07-22T03:01:35.521355Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Set hyperparameters\n## Sets the batch to 32 as the number of images to be processed at a time durin one forward/backward pass through the model, 32 is a default\n## Larger batches take more memory and are more stable but if they batch is too large the model may generalize poorly\nBATCH_SIZE = 32\n## Tensorflow optimization that automatically determines the most optimal number of parallel processes\nAUTO = tf.data.AUTOTUNE\n## Sets the number of classes to the number of flower categories\nNUM_CLASSES = 104\n\n## Prepare the datasets for training by grouping a batch based on the size set above, and pre-fetches the next batch while the current batch is being processed\ntrain_dataset = train_dataset.batch(BATCH_SIZE).prefetch(AUTO)\nvalidation_dataset = validation_dataset.batch(BATCH_SIZE).prefetch(AUTO)\ntest_dataset = test_dataset.batch(BATCH_SIZE).prefetch(AUTO)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:37.903164Z","iopub.execute_input":"2025-07-22T03:01:37.903501Z","iopub.status.idle":"2025-07-22T03:01:37.919593Z","shell.execute_reply.started":"2025-07-22T03:01:37.903481Z","shell.execute_reply":"2025-07-22T03:01:37.918521Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_model():\n    WEIGHTS_PATH = '/kaggle/input/imagenet/efficientnetb0_notop.h5'\n    ## Load the pretrained EfficientNetB0 model\n    ## Set include_top to false to remove the 1000 pre-set image net classification layers because we want to use the 104 flower classification labels\n    ## Set weights to None in kaggle notebook since it can't import the imagenet weights. Jupyter - Set weights to use pretrained ImageNet weights through ImageNet transfer learning\n    ## Set the model to expect inputs with the shape (224, 224, 3)\n    ## Sets the pooling to average to help reduce the number of parameters\n    base_model = tf.keras.applications.EfficientNetB0(\n        include_top=False,\n        weights=WEIGHTS_PATH, \n        input_shape=(224, 224, 3),\n        pooling='avg'\n    )\n    \n    ## Freeze the pretrained weights - sets all layers in the base model not to be trainable\n    ## This preserves the useful features learned by ImageNet, prevents changing the pre-trained imagenet weights, reduces training time and combats overfitting\n    base_model.trainable = False\n    \n    ## Create the model\n    ## Set .Dropout() to help regualrize the model and prevent overfitting, essentially say to leave out 20% of the neurons in training\n    ## Pass NUM_CLASSES to specify the number of output to be 104 flower classes, activation softmax indicates all outputs should sum to 1, output represents probability it is that flower\n    model = tf.keras.Sequential([\n        base_model,\n        tf.keras.layers.Dropout(0.3),\n        ## Add dense layer\n        tf.keras.layers.Dense(256, activation='relu'),\n        ## Add another dropour layer with .2 dropout\n        tf.keras.layers.Dropout(0.2),\n        ## This is the output layer\n        tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')\n    ])\n    \n    return model\n\n## Create and compile the model\n## Use the adam optimizer for efficiency and ability to adapt learning rates, sets the learning rate of the optimizer\n## Loss is sparse (meaning not one-hot encoded but numerical 0-103, categorical_crossentropy is used for multiclass clasification\n## Metrics track the accuracy\nmodel = create_model()\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=5e-4),\n    loss='sparse_categorical_crossentropy', \n    metrics=['accuracy']\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:45.701712Z","iopub.execute_input":"2025-07-22T03:01:45.702115Z","iopub.status.idle":"2025-07-22T03:01:47.820277Z","shell.execute_reply.started":"2025-07-22T03:01:45.702089Z","shell.execute_reply":"2025-07-22T03:01:47.819152Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Set training parameters\n## Sets epochs\nEPOCHS = 10\n\n## EarlyStopping - Add callback to monitor the validation loss and stop training after 3 epochs if the loss stops improving and reverts the weights back to where the model was still improving and had converged, this helps prevent overfitting\n##ReduceLROnPlateau - Add callback to monitor the validation loss and reduce the learning rate once performance starts to palteau, factor says multiply the learning rate by this value, patience say wait two epochs before reducing again, min_lr says don't go below this learning rate\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor='val_loss',\n        patience=3,\n        restore_best_weights=True\n    ),\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor='val_loss',\n        factor=0.2,\n        patience=2,\n        min_lr=1e-6\n    )\n]\n\n## Train the model using the train_dataset, number of epochs, specifying the validation set as validation_dataset, and point to the callback defined above\nhistory = model.fit(\n    train_dataset,\n    epochs=EPOCHS,\n    validation_data=validation_dataset,\n    callbacks=callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T03:01:51.179791Z","iopub.execute_input":"2025-07-22T03:01:51.180180Z","iopub.status.idle":"2025-07-22T04:42:17.311643Z","shell.execute_reply.started":"2025-07-22T03:01:51.180147Z","shell.execute_reply":"2025-07-22T04:42:17.307314Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Plot the training history\ndef plot_training_history(history):\n    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))\n    \n    ## Plot the accuracy\n    ax1.plot(history.history['accuracy'], label='Training')\n    ax1.plot(history.history['val_accuracy'], label='Validation')\n    ax1.set_title('Model Accuracy')\n    ax1.set_xlabel('Epoch')\n    ax1.set_ylabel('Accuracy')\n    ax1.legend()\n    \n    ## Plot the loss\n    ax2.plot(history.history['loss'], label='Training')\n    ax2.plot(history.history['val_loss'], label='Validation')\n    ax2.set_title('Model Loss')\n    ax2.set_xlabel('Epoch')\n    ax2.set_ylabel('Loss')\n    ax2.legend()\n    \n    plt.tight_layout()\n    plt.show()\n\n## Show the training results\nplot_training_history(history)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T04:42:17.991931Z","iopub.execute_input":"2025-07-22T04:42:17.992193Z","iopub.status.idle":"2025-07-22T04:42:18.406686Z","shell.execute_reply.started":"2025-07-22T04:42:17.992173Z","shell.execute_reply":"2025-07-22T04:42:18.405796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Function to assemble the models class predictions\ndef get_predictions():\n    image_ids = []\n    predictions = []\n    \n    print(\"Getting predictions for test set...\")\n    batch_count = 0\n    \n    # Get predictions for test set (now properly batched)\n    for batch_images, batch_image_names in test_dataset:\n        batch_count += 1\n        print(f\"Processing batch {batch_count}, batch shape: {batch_images.shape}\")\n        \n        # Get predictions for this batch\n        pred = model.predict(batch_images, verbose=0)\n        pred_labels = tf.argmax(pred, axis=1)\n        \n        # Convert tensor to numpy for easier handling\n        batch_image_names = batch_image_names.numpy()\n        pred_labels = pred_labels.numpy()\n        \n        # Extend our lists\n        image_ids.extend([name.decode('utf-8') for name in batch_image_names])\n        predictions.extend(pred_labels)\n    \n    print(f\"Total predictions made: {len(predictions)}\")\n    return image_ids, predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T04:42:27.439917Z","iopub.execute_input":"2025-07-22T04:42:27.440244Z","iopub.status.idle":"2025-07-22T04:42:27.447342Z","shell.execute_reply.started":"2025-07-22T04:42:27.440221Z","shell.execute_reply":"2025-07-22T04:42:27.446370Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_submission():\n    # Get predictions\n    image_ids, predictions = get_predictions()\n    \n    # Create submission DataFrame\n    submission_df = pd.DataFrame({\n        'id': image_ids,\n        'label': predictions\n    })\n    \n    # Save submission file\n    submission_df.to_csv('submission.csv', index=False)\n    print(\"Submission file created!\")\n    \n# Create submission after training\ncreate_submission()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T04:43:06.380537Z","iopub.execute_input":"2025-07-22T04:43:06.380898Z","iopub.status.idle":"2025-07-22T04:48:04.148332Z","shell.execute_reply.started":"2025-07-22T04:43:06.380842Z","shell.execute_reply":"2025-07-22T04:48:04.146971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"end_time = time.time()\nprint(\"Execution time: \", end_time - start_time, \"secs\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T04:49:03.160437Z","iopub.execute_input":"2025-07-22T04:49:03.160773Z","iopub.status.idle":"2025-07-22T04:49:03.166090Z","shell.execute_reply.started":"2025-07-22T04:49:03.160747Z","shell.execute_reply":"2025-07-22T04:49:03.165110Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}