{"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":"gpu","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"},{"sourceId":312126,"sourceType":"datasetVersion","datasetId":130727}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ====================================================\n# CIFAR-10 | ResNet18 (safe) + Robust Kaggle Submission\n# Works with dataset: /kaggle/input/cifar10-object-recognition-in-images-zip-file\n# ====================================================\nimport os, math, random, glob, warnings\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, regularizers\nfrom tensorflow.keras.datasets import cifar10\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, LearningRateScheduler\nfrom tensorflow.keras.optimizers import AdamW\n\nwarnings.filterwarnings(\"ignore\")\n\nprint(\"TF version:\", tf.__version__)\nprint(\"GPU available:\", tf.config.list_physical_devices('GPU'))\n\n# --------------------------\n# Reproducibility\n# --------------------------\nSEED = 1337\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\n# --------------------------\n# Paths (Kaggle)\n# --------------------------\nDATA_DIR = \"/kaggle/input/cifar10-object-recognition-in-images-zip-file\"\nSAMPLE_SUB_PATH = os.path.join(DATA_DIR, \"sampleSubmission.csv\")\n\n# The zip-file dataset sometimes nests test images differently.\n# We'll resolve robustly later, but these are the *candidate* folders:\nTEST_DIR_CANDIDATES = [\n    os.path.join(DATA_DIR, \"train_test\"),\n    os.path.join(DATA_DIR, \"train_test\", \"test\"),\n    os.path.join(DATA_DIR, \"train_test\", \"test\", \"test\"),\n]\n\nCKPT_PATH = \"/kaggle/working/best_model_loss.h5\"   # where we save/load the trained model\n\n# ====================================================\n# 1) Load CIFAR-10 (official Keras) for TRAIN/VAL\n# ====================================================\n(x_train, y_train), (x_test, y_test) = cifar10.load_data()\nx_train = x_train.astype(\"float32\") / 255.0\nx_test  = x_test.astype(\"float32\") / 255.0\ny_train = to_categorical(y_train, 10)\ny_test  = to_categorical(y_test, 10)\nprint(\"Train:\", x_train.shape, y_train.shape, \"  Val/Test:\", x_test.shape, y_test.shape)\n\n# ====================================================\n# 2) Model: ResNet18-like (from scratch)\n# ====================================================\ndef conv_block(x, filters, stride=1):\n    y = layers.Conv2D(filters, 3, strides=stride, padding='same',\n                      kernel_regularizer=regularizers.l2(1e-4),\n                      use_bias=False)(x)\n    y = layers.BatchNormalization()(y)\n    y = layers.Activation('relu')(y)\n    return y\n\ndef residual_block(x, filters, downsample=False):\n    stride = 2 if downsample else 1\n    y = conv_block(x, filters, stride)\n    y = conv_block(y, filters)\n    if downsample or x.shape[-1] != filters:\n        x = layers.Conv2D(filters, 1, strides=stride, padding='same',\n                          kernel_regularizer=regularizers.l2(1e-4),\n                          use_bias=False)(x)\n        x = layers.BatchNormalization()(x)\n    out = layers.add([x, y])\n    out = layers.Activation('relu')(out)\n    return out\n\ndef build_resnet18():\n    inputs = layers.Input(shape=(32, 32, 3))\n    x = conv_block(inputs, 64)\n    x = residual_block(x, 64)\n    x = residual_block(x, 64)\n\n    x = residual_block(x, 128, downsample=True)\n    x = residual_block(x, 128)\n\n    x = residual_block(x, 256, downsample=True)\n    x = residual_block(x, 256)\n\n    x = residual_block(x, 512, downsample=True)\n    x = residual_block(x, 512)\n\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.5)(x)\n    outputs = layers.Dense(10, activation='softmax')(x)\n    return models.Model(inputs, outputs)\n\n# Build/compile model\nmodel = build_resnet18()\noptimizer = AdamW(learning_rate=3e-4, weight_decay=1e-5, clipnorm=1.0)\nmodel.compile(optimizer=optimizer, loss=\"categorical_crossentropy\", metrics=[\"accuracy\"])\n\n# ====================================================\n# 3) Data Augmentation (gentle, no rotation)\n# ====================================================\ndatagen = ImageDataGenerator(\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    horizontal_flip=True\n)\ndatagen.fit(x_train)\n\ncheckpoint_cb = ModelCheckpoint(\n    CKPT_PATH, monitor=\"val_loss\", save_best_only=True, verbose=1\n)\nearly_stop_cb = EarlyStopping(\n    monitor=\"val_loss\", patience=12, restore_best_weights=True, verbose=1\n)\n\ndef cosine_annealing(epoch):\n    max_epochs = 60\n    initial_lr = 3e-4\n    return 0.5 * initial_lr * (1 + math.cos(math.pi * epoch / max_epochs))\nlr_scheduler_cb = LearningRateScheduler(cosine_annealing)\n\n# ====================================================\n# 4) TRAIN (only if checkpoint does not exist)\n# ====================================================\nBATCH_SIZE = 128\nEPOCHS = 60\n\nif not os.path.exists(CKPT_PATH):\n    print(\"No checkpoint found -> training model...\")\n    history = model.fit(\n        datagen.flow(x_train, y_train, batch_size=BATCH_SIZE),\n        validation_data=(x_test, y_test),\n        steps_per_epoch=x_train.shape[0] // BATCH_SIZE,\n        epochs=EPOCHS,\n        callbacks=[checkpoint_cb, early_stop_cb, lr_scheduler_cb],\n        verbose=1\n    )\nelse:\n    print(\"Checkpoint already exists -> skipping training.\")\n\n# ====================================================\n# 5) Load BEST model\n# ====================================================\ndef robust_load_model():\n    # try common candidates and also SavedModel directories if needed\n    import glob\n    candidates = [CKPT_PATH, \"best_model_loss.h5\", \"/kaggle/working/best_model.h5\"]\n    for p in candidates:\n        if os.path.exists(p):\n            print(\"Loading model from:\", p)\n            return tf.keras.models.load_model(p)\n\n    savedmodel = glob.glob(\"/kaggle/working/**/saved_model.pb\", recursive=True)\n    if savedmodel:\n        mdir = os.path.dirname(savedmodel[0])\n        print(\"Loading SavedModel from:\", mdir)\n        return tf.keras.models.load_model(mdir)\n\n    raise FileNotFoundError(\n        \"No checkpoint found. Re-run the training cell so best_model_loss.h5 is created.\"\n    )\n\nbest_model = robust_load_model()\nprint(\"✅ Model ready.\")\n\n# ====================================================\n# 6) Build Submission\n# ====================================================\nassert os.path.exists(SAMPLE_SUB_PATH), \"sampleSubmission.csv not found in dataset!\"\nsample = pd.read_csv(SAMPLE_SUB_PATH)\nid_list = sample[\"id\"].tolist()\nprint(\"IDs to predict:\", len(id_list))\n\n# Resolve a valid test directory and extension per id\ndef resolve_img_path(img_id):\n    # Try each candidate folder and .png/.jpg\n    for base in TEST_DIR_CANDIDATES:\n        p_png = os.path.join(base, f\"{img_id}.png\")\n        if os.path.exists(p_png):\n            return p_png\n        p_jpg = os.path.join(base, f\"{img_id}.jpg\")\n        if os.path.exists(p_jpg):\n            return p_jpg\n    return None\n\n# Try to locate the first few to assert structure is correct early\nprobes = [resolve_img_path(i) for i in id_list[:10]]\nif not all(probes):\n    print(\"⚠️  Some probe paths were not found. Candidate folders tried:\")\n    for c in TEST_DIR_CANDIDATES:\n        print(\"   -\", c)\n    # don't crash; maybe deeper IDs exist—full pass below will assert\n\npaths = []\nmissing = 0\nfor i in id_list:\n    p = resolve_img_path(i)\n    if p is None:\n        missing += 1\n    else:\n        paths.append(p)\n\nif missing > 0:\n    raise FileNotFoundError(\n        f\"{missing} test images could not be resolved. \"\n        \"Please open the dataset in the right panel and confirm the test folder structure. \"\n        \"We look for: train_test/, train_test/test/, or train_test/test/test/ with .png/.jpg.\"\n    )\n\nprint(\"Resolved test paths:\", len(paths))\n\n# tf.data input pipeline: decode -> resize -> [0,1]\ndef load_tf(path):\n    img = tf.io.read_file(path)\n    img = tf.io.decode_image(img, channels=3, expand_animations=False)\n    img = tf.image.convert_image_dtype(img, tf.float32)\n    img = tf.image.resize(img, [32, 32])\n    return img\n\nBATCH_PRED = 1024\nds = tf.data.Dataset.from_tensor_slices(paths)\nds = ds.map(load_tf, num_parallel_calls=tf.data.AUTOTUNE)\nds = ds.batch(BATCH_PRED).prefetch(tf.data.AUTOTUNE)\n\n# Predict (shows progress)\nprobs = best_model.predict(ds, verbose=1)\npred_idx = np.argmax(probs, axis=1)\n\n# Map idx -> class string that Kaggle expects\nlabel_names = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\"dog\",\"frog\",\"horse\",\"ship\",\"truck\"]\npred_labels = [label_names[i] for i in pred_idx]\n\nsubmission = pd.DataFrame({\"id\": id_list, \"label\": pred_labels})\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"📁 Saved submission.csv\")\nprint(submission.head())\n\n# Basic sanity\nprint(\"Shape:\", submission.shape)\nprint(\"Columns:\", list(submission.columns))\nprint(\"IDs unique?:\", submission['id'].is_unique)\nprint(\"NaNs per column:\", submission.isna().sum().to_dict())\nprint(\"Label distribution (top 10):\")\nprint(submission['label'].value_counts().head(10))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T16:34:23.507754Z","iopub.execute_input":"2025-09-19T16:34:23.508263Z","iopub.status.idle":"2025-09-19T17:22:12.858615Z","shell.execute_reply.started":"2025-09-19T16:34:23.508239Z","shell.execute_reply":"2025-09-19T17:22:12.857876Z"}},"outputs":[],"execution_count":null}]}