{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"},{"sourceId":12481101,"sourceType":"datasetVersion","datasetId":7875270}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"## Import the libraries\n%reset -f\nfrom __future__ import print_function\n\n## Import the libraries\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T20:07:22.520252Z","iopub.execute_input":"2025-07-25T20:07:22.521088Z","iopub.status.idle":"2025-07-25T20:07:23.507614Z","shell.execute_reply.started":"2025-07-25T20:07:22.521063Z","shell.execute_reply":"2025-07-25T20:07:23.506715Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Function to decode and normalize the images and standardize to RGB\nimage_size = [240, 240]\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-25T20:07:24.596590Z","iopub.execute_input":"2025-07-25T20:07:24.596849Z","iopub.status.idle":"2025-07-25T20:07:24.601960Z","shell.execute_reply.started":"2025-07-25T20:07:24.596833Z","shell.execute_reply":"2025-07-25T20:07:24.601111Z"}},"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-25T20:07:27.733825Z","iopub.execute_input":"2025-07-25T20:07:27.734591Z","iopub.status.idle":"2025-07-25T20:07:27.739053Z","shell.execute_reply.started":"2025-07-25T20:07:27.734569Z","shell.execute_reply":"2025-07-25T20:07:27.738121Z"}},"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-25T20:07:32.100570Z","iopub.execute_input":"2025-07-25T20:07:32.101251Z","iopub.status.idle":"2025-07-25T20:07:32.105796Z","shell.execute_reply.started":"2025-07-25T20:07:32.101221Z","shell.execute_reply":"2025-07-25T20:07:32.105089Z"}},"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-25T20:07:35.291935Z","iopub.execute_input":"2025-07-25T20:07:35.292479Z","iopub.status.idle":"2025-07-25T20:07:35.296743Z","shell.execute_reply.started":"2025-07-25T20:07:35.292456Z","shell.execute_reply":"2025-07-25T20:07:35.296000Z"}},"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-25T20:07:37.132209Z","iopub.execute_input":"2025-07-25T20:07:37.132854Z","iopub.status.idle":"2025-07-25T20:07:37.320485Z","shell.execute_reply.started":"2025-07-25T20:07:37.132820Z","shell.execute_reply":"2025-07-25T20:07:37.319697Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Balance the dataset by creating augmented data only for the classes that \ndef quick_balance(dataset, min_samples=50):\n    ## Count the classes\n    class_counts = {}\n    for _, label in dataset:\n        lbl = int(label.numpy())\n        class_counts[lbl] = class_counts.get(lbl, 0) + 1\n    \n    ## Find minority classes\n    minority_classes = [cls for cls, count in class_counts.items() if count < min_samples]\n    print(f\"Boosting {len(minority_classes)} minority classes\")\n    \n    \n    augmentation = tf.keras.Sequential([\n        tf.keras.layers.RandomFlip(\"horizontal\"),\n        tf.keras.layers.RandomRotation(0.15),\n        tf.keras.layers.RandomBrightness(0.15),\n        tf.keras.layers.RandomContrast(0.15),\n        tf.keras.layers.RandomZoom(0.15),\n        tf.keras.layers.RandomTranslation(0.1, 0.1)\n    ])\n\n    # augmented = dataset.map(lambda img, lbl: (augmentation(img, training=True), lbl))\n    minority_data = dataset.filter(lambda img, lbl: tf.reduce_any(tf.equal(lbl, minority_classes)))\n    boosted_minorities = minority_data.map(lambda img, lbl: (augmentation(img, training=True), lbl)).repeat(4)\n    \n    ## Concatenates the training data with the augmented data\n    # return dataset.concatenate(augmented).concatenate(boosted_minorities).shuffle(10000)\n\n    ## Concatenates the training data only with the minority classes augmented data\n    return dataset.concatenate(boosted_minorities)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T20:07:42.890197Z","iopub.execute_input":"2025-07-25T20:07:42.891106Z","iopub.status.idle":"2025-07-25T20:07:42.897070Z","shell.execute_reply.started":"2025-07-25T20:07:42.891077Z","shell.execute_reply":"2025-07-25T20:07:42.896455Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = quick_balance(train_dataset, min_samples=50) ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T20:07:45.480055Z","iopub.execute_input":"2025-07-25T20:07:45.480788Z","iopub.status.idle":"2025-07-25T20:07:52.633238Z","shell.execute_reply.started":"2025-07-25T20:07:45.480751Z","shell.execute_reply":"2025-07-25T20:07:52.632649Z"}},"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 = 250\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-25T20:07:56.792440Z","iopub.execute_input":"2025-07-25T20:07:56.792718Z","iopub.status.idle":"2025-07-25T20:07:56.841474Z","shell.execute_reply.started":"2025-07-25T20:07:56.792698Z","shell.execute_reply":"2025-07-25T20:07:56.840643Z"}},"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\n## Increase the batch size to hopefully improve runtime\nBATCH_SIZE = 48\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-25T20:07:58.884386Z","iopub.execute_input":"2025-07-25T20:07:58.884726Z","iopub.status.idle":"2025-07-25T20:07:58.897775Z","shell.execute_reply.started":"2025-07-25T20:07:58.884704Z","shell.execute_reply":"2025-07-25T20:07:58.897096Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_b1_model():\n    ## Load the pretrained EfficientNetB4 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 use pretrained ImageNet weights through ImageNet transfer learning\n    ## Set the model to expect inputs with the shape (380, 380, 3) - EfficientNetB4 input size\n    ## Sets the pooling to average to help reduce the number of parameters\n    b1_model = tf.keras.applications.EfficientNetB1(\n        include_top=False,\n        weights='imagenet', \n        input_shape=(240, 240, 3),\n        pooling='avg'\n    )\n    \n    ## Unfreeze the top layers for hyperparameter tuning\n    b1_model.trainable = True\n\n    ## Freeze only the bottom layers of the model\n    tuned = int(len(b1_model.layers) *.9)\n    for layer in b1_model.layers[:tuned]:\n        layer.trainable = False\n    \n    ## Create the model with slightly higher dropout for the larger model\n    b1_model = tf.keras.Sequential([\n        b1_model,\n        tf.keras.layers.Dropout(0.5),  \n        tf.keras.layers.Dense(512, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.0005)),\n        ## Add another dropout layer with .3 dropout\n        tf.keras.layers.Dropout(0.4),\n        tf.keras.layers.Dense(256, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.0005)),\n        tf.keras.layers.Dropout(0.3),\n        ## This is the output layer\n        tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')\n    ])\n    \n    return b1_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_b1_model()\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=2e-3),\n    loss='sparse_categorical_crossentropy', \n    metrics=['accuracy']\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T20:08:06.793505Z","iopub.execute_input":"2025-07-25T20:08:06.793783Z","iopub.status.idle":"2025-07-25T20:08:08.623530Z","shell.execute_reply.started":"2025-07-25T20:08:06.793762Z","shell.execute_reply":"2025-07-25T20:08:08.622776Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Set training parameters\n## Sets epochs\nEPOCHS = 35\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\n## Updated monitor from val_loss to val_accuracy\n## Setting mode='max' to maximize accuracy\n## Increased the factor for reducing the learning rate\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor='val_accuracy',\n        patience=3,\n        restore_best_weights=True,\n        mode='max'\n    ),\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor='val_accuracy',\n        factor=0.5,\n        patience=2,\n        min_lr=1e-6,\n        mode='max'\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-25T20:08:15.697117Z","iopub.execute_input":"2025-07-25T20:08:15.697495Z","iopub.status.idle":"2025-07-25T20:52:16.481647Z","shell.execute_reply.started":"2025-07-25T20:08:15.697472Z","shell.execute_reply":"2025-07-25T20:52:16.480819Z"}},"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},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}