{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.18","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"tpu1vmV38","dataSources":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"},{"sourceId":12789931,"sourceType":"datasetVersion","datasetId":8086337}],"dockerImageVersionId":31090,"isInternetEnabled":true,"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-08-18T20:18:39.596307Z","iopub.execute_input":"2025-08-18T20:18:39.598005Z","iopub.status.idle":"2025-08-18T20:18:40.100349Z","shell.execute_reply.started":"2025-08-18T20:18:39.597972Z","shell.execute_reply":"2025-08-18T20:18:40.095813Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import math, re, os\nimport numpy as np\nimport tensorflow as tf\n\nprint(\"Tensorflow version \" + tf.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:40.102617Z","iopub.execute_input":"2025-08-18T20:18:40.102944Z","iopub.status.idle":"2025-08-18T20:18:43.663817Z","shell.execute_reply.started":"2025-08-18T20:18:40.102918Z","shell.execute_reply":"2025-08-18T20:18:43.659398Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## TPU Setup","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras import mixed_precision\n\n# Detect and init the TPU (must pass 'local' on Kaggle)\ntpu = tf.distribute.cluster_resolver.TPUClusterResolver(tpu='local')\nprint(\"Running on TPU:\", tpu.master())\n\ntf.config.experimental_connect_to_cluster(tpu)\ntf.tpu.experimental.initialize_tpu_system(tpu)\n\n# Create strategy\ntpu_strategy = tf.distribute.TPUStrategy(tpu)\nprint(\"REPLICAS:\", tpu_strategy.num_replicas_in_sync)\n\n# Set mixed precision for TPU\nmixed_precision.set_global_policy('mixed_bfloat16')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:43.666114Z","iopub.execute_input":"2025-08-18T20:18:43.666874Z","iopub.status.idle":"2025-08-18T20:18:50.832431Z","shell.execute_reply.started":"2025-08-18T20:18:43.666849Z","shell.execute_reply":"2025-08-18T20:18:50.827530Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tf.config.set_soft_device_placement(True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:50.833871Z","iopub.execute_input":"2025-08-18T20:18:50.834101Z","iopub.status.idle":"2025-08-18T20:18:50.843018Z","shell.execute_reply.started":"2025-08-18T20:18:50.834077Z","shell.execute_reply":"2025-08-18T20:18:50.838379Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Configuration","metadata":{}},{"cell_type":"code","source":"image_size = [512,512]\nauto = tf.data.AUTOTUNE\nbatch_size = 16 * tpu_strategy.num_replicas_in_sync\nepochs = 25\nnum_classes = 104","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:50.846036Z","iopub.execute_input":"2025-08-18T20:18:50.846258Z","iopub.status.idle":"2025-08-18T20:18:50.857376Z","shell.execute_reply.started":"2025-08-18T20:18:50.846236Z","shell.execute_reply":"2025-08-18T20:18:50.852890Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Loading","metadata":{}},{"cell_type":"code","source":"train_files = tf.io.gfile.glob(\"/kaggle/input/petals-to-metals-512-x-512/tfrecords-jpeg-512x512/train/*.tfrec\")\nval_files = tf.io.gfile.glob(\"/kaggle/input/petals-to-metals-512-x-512/tfrecords-jpeg-512x512/val/*.tfrec\")\ntest_files = tf.io.gfile.glob(\"/kaggle/input/petals-to-metals-512-x-512/tfrecords-jpeg-512x512/test/*.tfrec\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:50.860060Z","iopub.execute_input":"2025-08-18T20:18:50.860267Z","iopub.status.idle":"2025-08-18T20:18:50.894777Z","shell.execute_reply.started":"2025-08-18T20:18:50.860248Z","shell.execute_reply":"2025-08-18T20:18:50.889159Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## TFRecord parsing","metadata":{}},{"cell_type":"code","source":"def decode_image(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.image.resize(image, image_size)   # safer than reshape\n    image = tf.cast(image, tf.float32) / 255.0\n    return image\n\ndef read_tfrecord(example, labeled=True):\n    tfrec_format = {\n        \"id\": tf.io.FixedLenFeature([], tf.string),\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n    }\n    if labeled:\n        tfrec_format[\"class\"] = tf.io.FixedLenFeature([], tf.int64)\n    \n    example = tf.io.parse_single_example(example, tfrec_format)\n    image = decode_image(example[\"image\"])\n    \n    if labeled:\n        label = tf.cast(example[\"class\"], tf.int32)\n        return image, label\n    else:\n        return image, example[\"id\"]\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(lambda x: read_tfrecord(x, labeled), num_parallel_calls=auto)\n    return dataset\n\ndef get_dataset(filenames, labeled=True, shuffle=False):\n    dataset = load_dataset(filenames, labeled=labeled)\n    if shuffle:\n        dataset = dataset.shuffle(2048)\n    dataset = dataset.prefetch(auto)\n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:50.896947Z","iopub.execute_input":"2025-08-18T20:18:50.897175Z","iopub.status.idle":"2025-08-18T20:18:50.910374Z","shell.execute_reply.started":"2025-08-18T20:18:50.897152Z","shell.execute_reply":"2025-08-18T20:18:50.905168Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- CPU-side augmentation to avoid TPU XLA errors ---\ndata_augmentation = tf.keras.Sequential([\n    tf.keras.layers.RandomFlip(\"horizontal\"),\n    tf.keras.layers.RandomRotation(0.1),\n    tf.keras.layers.RandomZoom(0.2),\n    tf.keras.layers.RandomContrast(0.2),\n], name=\"cpu_aug\")\n\ndef preprocess(image, label):\n    return image, tf.cast(label, tf.int32)\n\ndef augment_on_cpu(image, label):\n    # Ensure augmentation happens on CPU (not TPU graph)\n    with tf.device(\"/CPU:0\"):\n        image = data_augmentation(image)\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:50.912389Z","iopub.execute_input":"2025-08-18T20:18:50.912605Z","iopub.status.idle":"2025-08-18T20:18:50.936893Z","shell.execute_reply.started":"2025-08-18T20:18:50.912585Z","shell.execute_reply":"2025-08-18T20:18:50.932398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_ds = get_dataset(train_files, labeled=True, shuffle=True).map(preprocess, num_parallel_calls=auto)\nval_ds   = get_dataset(val_files,   labeled=True, shuffle=False).map(preprocess, num_parallel_calls=auto)\ntest_ds  = get_dataset(test_files,  labeled=False, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:50.938336Z","iopub.execute_input":"2025-08-18T20:18:50.938524Z","iopub.status.idle":"2025-08-18T20:18:51.179411Z","shell.execute_reply.started":"2025-08-18T20:18:50.938505Z","shell.execute_reply":"2025-08-18T20:18:51.174065Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train → augmentation on CPU, repeat for TPU training\ntrain_ds = (train_ds\n            .map(augment_on_cpu, num_parallel_calls=auto)\n            .shuffle(2048)\n            .batch(batch_size, drop_remainder=True)   # important for TPU\n            .repeat()                                # TPU likes infinite dataset\n            .prefetch(auto))\n\n# Validation → cache + batch evenly\nval_ds = (val_ds\n          .batch(batch_size, drop_remainder=True)\n          .cache()\n          .prefetch(auto))\n\n# Test → no drop_remainder so we don’t lose samples\ntest_ds_batched = (test_ds\n                   .batch(batch_size, drop_remainder=False)\n                   .cache()\n                   .prefetch(auto))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:51.181179Z","iopub.execute_input":"2025-08-18T20:18:51.181381Z","iopub.status.idle":"2025-08-18T20:18:51.388607Z","shell.execute_reply.started":"2025-08-18T20:18:51.181360Z","shell.execute_reply":"2025-08-18T20:18:51.383954Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for images, labels in train_ds.take(1):\n    print(\"Batch shape:\", images.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:51.389298Z","iopub.execute_input":"2025-08-18T20:18:51.389493Z","iopub.status.idle":"2025-08-18T20:18:56.834504Z","shell.execute_reply.started":"2025-08-18T20:18:51.389473Z","shell.execute_reply":"2025-08-18T20:18:56.830114Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import math","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:56.835900Z","iopub.execute_input":"2025-08-18T20:18:56.836150Z","iopub.status.idle":"2025-08-18T20:18:56.845774Z","shell.execute_reply.started":"2025-08-18T20:18:56.836125Z","shell.execute_reply":"2025-08-18T20:18:56.841472Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def count_records(filenames):\n    # counts raw examples in tfrecords\n    c = 0\n    for _ in tf.data.TFRecordDataset(filenames):\n        c += 1\n    return c\n\ntrain_count = count_records(train_files)\nval_count   = count_records(val_files)\ntest_count  = count_records(test_files)\nprint(f\"Train: {train_count}  Val: {val_count}  Test: {test_count}\")\n\nsteps_per_epoch    = max(1, train_count // batch_size)\nvalidation_steps   = max(1, math.ceil(val_count / batch_size))\ntest_steps         = max(1, math.ceil(test_count / batch_size))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:56.848577Z","iopub.execute_input":"2025-08-18T20:18:56.848843Z","iopub.status.idle":"2025-08-18T20:18:58.626208Z","shell.execute_reply.started":"2025-08-18T20:18:56.848821Z","shell.execute_reply":"2025-08-18T20:18:58.620094Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for img, label in train_ds.take(1):\n    print(\"Train label:\", label.numpy())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:18:58.628521Z","iopub.execute_input":"2025-08-18T20:18:58.628797Z","iopub.status.idle":"2025-08-18T20:19:03.821276Z","shell.execute_reply.started":"2025-08-18T20:18:58.628772Z","shell.execute_reply":"2025-08-18T20:19:03.815024Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for img, label in val_ds.take(1):\n    print(\"Val label:\", label.numpy())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:19:03.823469Z","iopub.execute_input":"2025-08-18T20:19:03.823752Z","iopub.status.idle":"2025-08-18T20:19:04.178793Z","shell.execute_reply.started":"2025-08-18T20:19:03.823691Z","shell.execute_reply":"2025-08-18T20:19:04.174049Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for img, img_id in test_ds.take(1):\n    print(\"Test id:\", img_id.numpy())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:19:04.181203Z","iopub.execute_input":"2025-08-18T20:19:04.181446Z","iopub.status.idle":"2025-08-18T20:19:04.253988Z","shell.execute_reply.started":"2025-08-18T20:19:04.181423Z","shell.execute_reply":"2025-08-18T20:19:04.247712Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:19:04.256446Z","iopub.execute_input":"2025-08-18T20:19:04.256811Z","iopub.status.idle":"2025-08-18T20:19:04.266224Z","shell.execute_reply.started":"2025-08-18T20:19:04.256678Z","shell.execute_reply":"2025-08-18T20:19:04.261581Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"raw_ds = tf.data.TFRecordDataset(train_files[0])\nfor raw_record in raw_ds.take(1):\n    example = tf.train.Example()\n    example.ParseFromString(raw_record.numpy())\n    print(example)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:19:04.268528Z","iopub.execute_input":"2025-08-18T20:19:04.268827Z","iopub.status.idle":"2025-08-18T20:19:04.356665Z","shell.execute_reply.started":"2025-08-18T20:19:04.268806Z","shell.execute_reply":"2025-08-18T20:19:04.351758Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Config","metadata":{}},{"cell_type":"code","source":"with tpu_strategy.scope():\n    base_model = tf.keras.applications.efficientnet_v2.EfficientNetV2S(\n        include_top=False,\n        input_shape=(*image_size, 3),\n        weights=\"imagenet\"\n    )\n    base_model.trainable = True\n\n    inputs = tf.keras.Input(shape=(*image_size, 3))\n    x = tf.keras.applications.efficientnet_v2.preprocess_input(inputs)\n    x = base_model(x, training=True)\n    x = tf.keras.layers.GlobalAveragePooling2D()(x)\n    x = tf.keras.layers.Dropout(0.4)(x)\n    outputs = tf.keras.layers.Dense(num_classes, activation=\"softmax\")(x)\n\n    model = tf.keras.Model(inputs, outputs)\n\n    total_steps = epochs * steps_per_epoch\n    lr_schedule = tf.keras.optimizers.schedules.CosineDecay(\n        initial_learning_rate=1e-3,\n        decay_steps=total_steps\n    )\n\n    optimizer = tf.keras.optimizers.AdamW(\n        learning_rate=lr_schedule,\n        weight_decay=1e-5\n    )\n\n    model.compile(\n        optimizer=optimizer,\n        loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),\n        metrics=[\"accuracy\"]\n    )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:19:04.358361Z","iopub.execute_input":"2025-08-18T20:19:04.358606Z","iopub.status.idle":"2025-08-18T20:19:30.692594Z","shell.execute_reply.started":"2025-08-18T20:19:04.358582Z","shell.execute_reply":"2025-08-18T20:19:30.686865Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training","metadata":{}},{"cell_type":"code","source":"model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:19:30.694616Z","iopub.execute_input":"2025-08-18T20:19:30.695017Z","iopub.status.idle":"2025-08-18T20:19:30.741165Z","shell.execute_reply.started":"2025-08-18T20:19:30.694993Z","shell.execute_reply":"2025-08-18T20:19:30.736663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"checkpoint = tf.keras.callbacks.ModelCheckpoint(\n    \"best_model.h5\",monitor=\"val_accuracy\",save_best_only=True,mode=\"max\"\n)\nearlystop = tf.keras.callbacks.EarlyStopping(\n    monitor=\"val_accuracy\",patience=5,restore_best_weights=True\n)\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=epochs,\n    steps_per_epoch=steps_per_epoch,\n    validation_steps=validation_steps,\n    callbacks=[checkpoint, earlystop],\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T20:19:30.742635Z","iopub.execute_input":"2025-08-18T20:19:30.742843Z","iopub.status.idle":"2025-08-18T20:46:01.585630Z","shell.execute_reply.started":"2025-08-18T20:19:30.742823Z","shell.execute_reply":"2025-08-18T20:46:01.580274Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm import tqdm\nimport numpy as np","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T21:11:06.070841Z","iopub.execute_input":"2025-08-18T21:11:06.071200Z","iopub.status.idle":"2025-08-18T21:11:06.082653Z","shell.execute_reply.started":"2025-08-18T21:11:06.071170Z","shell.execute_reply":"2025-08-18T21:11:06.077006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ids = []\nfor _, id_ in test_ds:   # no unbatch() needed\n    ids.append(id_.numpy().decode(\"utf-8\"))\nids = np.array(ids)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T21:36:40.901419Z","iopub.execute_input":"2025-08-18T21:36:40.901819Z","iopub.status.idle":"2025-08-18T21:36:42.778316Z","shell.execute_reply.started":"2025-08-18T21:36:40.901787Z","shell.execute_reply":"2025-08-18T21:36:42.771527Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------------------------\n# 1. Load test dataset\n# ---------------------------\ntest_raw = load_dataset(test_files, labeled=False, ordered=True)\n\n# Extract IDs\nids = []\nfor _, id_ in test_raw:\n    ids.append(id_.numpy().decode(\"utf-8\"))\nids = np.array(ids)\n\n# Only keep images for prediction\ntest_images = test_raw.map(lambda img, id_: img)\n\n# ---------------------------\n# 2. Prepare batching\n# ---------------------------\nbatch_size = 16 * tpu_strategy.num_replicas_in_sync\nnum_test = len(ids)\n\ntest_ds_batched = (\n    test_images\n    .batch(batch_size, drop_remainder=False)\n    .prefetch(tf.data.AUTOTUNE)\n)\n\nprint(f\"Number of test samples: {num_test}, batch_size: {batch_size}\")\n\n# ---------------------------\n# 3. Manual Prediction Loop\n# ---------------------------\nall_preds = []\n\nfor batch in tqdm(test_ds_batched, total=num_test // batch_size + 1, desc=\"Predicting\"):\n    batch_preds = model(batch, training=False)   # forward pass\n    all_preds.append(batch_preds.numpy())\n\ntest_preds = np.vstack(all_preds)   # stack into single array\npred_labels = np.argmax(test_preds, axis=-1)\n\n# ---------------------------\n# 4. Create Submission\n# ---------------------------\nsubmission = pd.DataFrame({\"id\": ids, \"label\": pred_labels})\nsubmission.to_csv(\"/kaggle/working/submission.csv\", index=False)\n\nprint(submission.head(), \"\\nSaved submission.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T21:40:37.224100Z","iopub.execute_input":"2025-08-18T21:40:37.224522Z","iopub.status.idle":"2025-08-18T22:20:52.478196Z","shell.execute_reply.started":"2025-08-18T21:40:37.224423Z","shell.execute_reply":"2025-08-18T22:20:52.472262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}