{"cells": [{"cell_type": "markdown", "metadata": {}, "source": "# Flower Classification (tpu-getting-started) \u2014 CPU/GPU Edition\n\nA **CPU/GPU** image classifier for the Petals to the Metal flower classification competition.\nKaggle's TPU provisioning for this 2020 competition is broken, so we run on GPU instead \u2014\nthe competition scores on accuracy regardless of compute.\n\n- **104 flower classes** (0-103)\n- ~12753 training images, ~3712 validation images, ~7382 test images\n- TFRecords (192x192 JPEG) with data augmentation\n- DenseNet121 pretrained on ImageNet (transfer learning)\n- Writes submission.csv with id,label (integer class index 0-103)\n"}, {"cell_type": "code", "metadata": {}, "source": "import os, math, re, glob\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nprint(\"TF version:\", tf.__version__)\n\n# Check for GPU\ngpus = tf.config.list_physical_devices('GPU')\nif gpus:\n    print(f\"GPU available: {len(gpus)} device(s)\")\n    for g in gpus:\n        print(f\"  {g}\")\nelse:\n    print(\"No GPU detected, running on CPU\")\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "# Configuration\nIMAGE_SIZE = [192, 192]\nBATCH_SIZE = 32\nEPOCHS = 8\nNUM_CLASSES = 104  # 104 flower types\n\n# Find competition data directory (nested layout)\nbase = None\nfor p in [\"/kaggle/input/competitions/tpu-getting-started\",\n          \"/kaggle/input/tpu-getting-started\"]:\n    if os.path.exists(p):\n        base = p\n        break\n\nif base is None:\n    raise FileNotFoundError(\"Could not find tpu-getting-started data directory\")\n\nprint(\"Data directory:\", base)\n\ntrain_fns = sorted(glob.glob(os.path.join(base, \"tfrecords-jpeg-192x192\", \"train\", \"*.tfrec\")))\nval_fns = sorted(glob.glob(os.path.join(base, \"tfrecords-jpeg-192x192\", \"val\", \"*.tfrec\")))\ntest_fns = sorted(glob.glob(os.path.join(base, \"tfrecords-jpeg-192x192\", \"test\", \"*.tfrec\")))\n\n# If no val split, use last 2 train shards as validation\nif not val_fns:\n    val_fns = train_fns[-2:]\n    train_fns = train_fns[:-2]\n\nTRAIN_FILENAMES = train_fns\nVAL_FILENAMES = val_fns\nTEST_FILENAMES = test_fns\n\nprint(f\"Train TFRecords: {len(TRAIN_FILENAMES)}\")\nprint(f\"Val TFRecords: {len(VAL_FILENAMES)}\")\nprint(f\"Test TFRecords: {len(TEST_FILENAMES)}\")\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "def count_data_items(filenames):\n    n = 0\n    for f in filenames:\n        match = re.search(r\"-(\\d+)\\.\", f)\n        if match:\n            n += int(match.group(1))\n        else:\n            # Fallback: count records in the TFRecord file directly\n            n += sum(1 for _ in tf.data.TFRecordDataset(f))\n    return n\n\nNUM_TRAIN_IMAGES = count_data_items(TRAIN_FILENAMES)\nNUM_VAL_IMAGES = count_data_items(VAL_FILENAMES) if VAL_FILENAMES else 0\nNUM_TEST_IMAGES = count_data_items(TEST_FILENAMES)\nprint(f\"Train: {NUM_TRAIN_IMAGES}, Val: {NUM_VAL_IMAGES}, Test: {NUM_TEST_IMAGES}\")\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "AUTO = tf.data.experimental.AUTOTUNE\n\ndef decode_image(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    return image\n\ndef read_labeled_tfrecord(example):\n    LABELED_TFREC_FORMAT = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64),  # 0-103, the flower class label\n    }\n    example = tf.io.parse_single_example(example, LABELED_TFREC_FORMAT)\n    image = decode_image(example[\"image\"])\n    label = tf.cast(example[\"class\"], tf.int32)  # 0-103 class index\n    return image, label\n\ndef read_unlabeled_tfrecord(example):\n    UNLABELED_TFREC_FORMAT = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\": tf.io.FixedLenFeature([], tf.string),\n    }\n    example = tf.io.parse_single_example(example, UNLABELED_TFREC_FORMAT)\n    image = decode_image(example[\"image\"])\n    idnum = example[\"id\"]\n    return image, idnum\n\ndef load_dataset(filenames, labeled=True, ordered=False):\n    ignore_order = tf.data.Options()\n    if not ordered:\n        ignore_order.experimental_deterministic = False\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTO)\n    dataset = dataset.with_options(ignore_order)\n    dataset = dataset.map(read_labeled_tfrecord if labeled else read_unlabeled_tfrecord, num_parallel_calls=AUTO)\n    return dataset\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "def data_augment(image, label):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_saturation(image, 0.8, 1.2)\n    image = tf.image.random_brightness(image, 0.1)\n    image = tf.image.random_contrast(image, 0.8, 1.2)\n    image = tf.image.random_hue(image, 0.05)\n    return image, label\n\ndef get_training_dataset():\n    dataset = load_dataset(TRAIN_FILENAMES, labeled=True)\n    dataset = dataset.map(data_augment, num_parallel_calls=AUTO)\n    dataset = dataset.repeat()\n    dataset = dataset.shuffle(2048)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\ndef get_validation_dataset():\n    dataset = load_dataset(VAL_FILENAMES, labeled=True)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.cache()\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\ndef get_test_dataset(ordered=False):\n    dataset = load_dataset(TEST_FILENAMES, labeled=False, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "def lrfn(epoch):\n    LR_START = 0.00001\n    LR_MAX = 0.0001\n    LR_MIN = 0.00001\n    LR_RAMPUP_EPOCHS = 3\n    LR_SUSTAIN_EPOCHS = 0\n    LR_EXP_DECAY = .8\n\n    if epoch < LR_RAMPUP_EPOCHS:\n        lr = (LR_MAX - LR_START) / LR_RAMPUP_EPOCHS * epoch + LR_START\n    elif epoch < LR_RAMPUP_EPOCHS + LR_SUSTAIN_EPOCHS:\n        lr = LR_MAX\n    else:\n        lr = (LR_MAX - LR_MIN) * LR_EXP_DECAY**(epoch - LR_RAMPUP_EPOCHS - LR_SUSTAIN_EPOCHS) + LR_MIN\n    return lr\n\nlr_callback = tf.keras.callbacks.LearningRateScheduler(lrfn, verbose=True)\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "# Build model \u2014 DenseNet121 pretrained on ImageNet, no TPU/strategy.scope needed\npretrained_model = tf.keras.applications.DenseNet121(\n    weights=\"imagenet\",\n    include_top=False,\n    input_shape=[*IMAGE_SIZE, 3]\n)\npretrained_model.trainable = True\n\nmodel = tf.keras.Sequential([\n    pretrained_model,\n    tf.keras.layers.GlobalAveragePooling2D(),\n    tf.keras.layers.Dense(NUM_CLASSES, activation=\"softmax\")\n])\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),\n    loss=\"sparse_categorical_crossentropy\",\n    metrics=[\"sparse_categorical_accuracy\"]\n)\n\nmodel.summary()\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "STEPS_PER_EPOCH = NUM_TRAIN_IMAGES // BATCH_SIZE\nprint(f\"Steps per epoch: {STEPS_PER_EPOCH}\")\n\nhistory = model.fit(\n    get_training_dataset(),\n    epochs=EPOCHS,\n    steps_per_epoch=STEPS_PER_EPOCH,\n    callbacks=[lr_callback]\n)\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "if VAL_FILENAMES:\n    val_dataset = get_validation_dataset()\n    val_loss, val_acc = model.evaluate(val_dataset)\n    print(f\"Validation accuracy: {val_acc:.4f}\")\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "print(\"Predicting on test set...\")\ntest_dataset = get_test_dataset(ordered=True)\ntest_images = test_dataset.map(lambda image, idnum: image)\nprobabilities = model.predict(test_images, verbose=1)\npredictions = np.argmax(probabilities, axis=-1)\nprint(f\"Predictions shape: {predictions.shape}\")\nprint(f\"Prediction range: {predictions.min()} - {predictions.max()}\")\nprint(f\"Unique predictions: {len(np.unique(predictions))}\")\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "print(\"Getting test IDs...\")\ntest_ids = [id.numpy().decode('utf-8') for image, idnum in test_dataset for id in idnum]\nprint(f\"Number of test IDs: {len(test_ids)}\")\n\n# Read sample submission to verify format\nsample_sub_path = None\nfor p in [\"/kaggle/input/competitions/tpu-getting-started/sample_submission.csv\",\n          \"/kaggle/input/tpu-getting-started/sample_submission.csv\"]:\n    if os.path.exists(p):\n        sample_sub_path = p\n        break\n\nif sample_sub_path:\n    sample_df = pd.read_csv(sample_sub_path)\n    print(f\"Sample submission columns: {sample_df.columns.tolist()}\")\n    print(f\"Sample submission rows: {len(sample_df)}\")\n    print(sample_df.head())\nelse:\n    print(\"WARNING: sample_submission.csv not found, using generated IDs\")\n\n# Build submission\nsubmission = pd.DataFrame({\n    \"id\": test_ids,\n    \"label\": predictions\n})\n\n# Sort by id to match sample submission order\nsubmission = submission.sort_values(\"id\").reset_index(drop=True)\n\nprint(f\"\\nSubmission shape: {submission.shape}\")\nprint(submission.head())\nprint(f\"\\nLabel distribution (first 20):\\n{submission['label'].value_counts().sort_index().head(20)}\")\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"\\nsubmission.csv written!\")\nprint(f\"File size: {os.path.getsize('submission.csv')} bytes\")\n", "outputs": [], "execution_count": null}, {"cell_type": "code", "metadata": {}, "source": "sub_check = pd.read_csv('submission.csv')\nprint(f\"Final submission: {sub_check.shape[0]} rows, {sub_check.shape[1]} columns\")\nprint(f\"Columns: {sub_check.columns.tolist()}\")\nprint(f\"Label range: {sub_check['label'].min()} - {sub_check['label'].max()}\")\nprint(f\"Any NaN: {sub_check.isna().any().any()}\")\nprint(sub_check.head(10))\n", "outputs": [], "execution_count": null}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.10"}}, "nbformat": 4, "nbformat_minor": 4}