{"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":"# CIFAR-10 | ResNet-18 (from scratch) + high-score Kaggle submission\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.callbacks import EarlyStopping, ModelCheckpoint, LearningRateScheduler\nfrom tensorflow.keras.optimizers import SGD\n\nwarnings.filterwarnings(\"ignore\")\nos.environ[\"TF_CPP_MIN_LOG_LEVEL\"] = \"2\"\n\nprint(\"TF:\", tf.__version__)\nprint(\"GPU:\", tf.config.list_physical_devices(\"GPU\"))\n\nSEED = 1337\nrandom.seed(SEED); np.random.seed(SEED); tf.random.set_seed(SEED)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T13:15:00.823210Z","iopub.execute_input":"2025-09-21T13:15:00.823426Z","iopub.status.idle":"2025-09-21T13:15:16.609908Z","shell.execute_reply.started":"2025-09-21T13:15:00.823403Z","shell.execute_reply":"2025-09-21T13:15:16.609088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# One *single* checkpoint path used by BOTH training and inference\nCKPT_PATH = \"/kaggle/working/resnet18_best.keras\"\n\n# Dataset mount\nDATA_DIR = \"/kaggle/input/cifar10-object-recognition-in-images-zip-file\"\nSAMPLE_SUB_PATH = os.path.join(DATA_DIR, \"sampleSubmission.csv\")\n\n# Likely places for test images (dataset variants differ)\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    \"test\", \"/kaggle/working/test\"  # in case you extracted elsewhere\n]\n\nprint(\"SAMPLE_SUB exists:\", os.path.exists(SAMPLE_SUB_PATH))\nprint(\"Checkpoint target:\", CKPT_PATH)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T13:15:31.959826Z","iopub.execute_input":"2025-09-21T13:15:31.960446Z","iopub.status.idle":"2025-09-21T13:15:31.971493Z","shell.execute_reply.started":"2025-09-21T13:15:31.960403Z","shell.execute_reply":"2025-09-21T13:15:31.970611Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Official CIFAR-10 for training/validation (mirrors Kaggle labels)\n(x_train, y_train), (x_val, y_val) = cifar10.load_data()\nx_train = x_train.astype(\"float32\") / 255.0\nx_val   = x_val.astype(\"float32\")   / 255.0\ny_train = to_categorical(y_train, 10)\ny_val   = to_categorical(y_val, 10)\nprint(\"Train:\", x_train.shape, y_train.shape, \"| Val:\", x_val.shape, y_val.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T13:15:38.137651Z","iopub.execute_input":"2025-09-21T13:15:38.138331Z","iopub.status.idle":"2025-09-21T13:15:43.816603Z","shell.execute_reply.started":"2025-09-21T13:15:38.138305Z","shell.execute_reply":"2025-09-21T13:15:43.815865Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Strong baseline: pad+random-crop, flip, contrast, L2=5e-4, He init (random), Dropout 0.5\nreg = regularizers.l2(5e-4)\n\n# Channel-wise normalization (fit on training)\nnorm = layers.Normalization()\nnorm.adapt(x_train)  # x_train already in [0,1]\n\ndef conv3(x, f, s=1):\n    y = layers.Conv2D(f, 3, strides=s, padding=\"same\",\n                      kernel_initializer=\"he_normal\",\n                      use_bias=False, kernel_regularizer=reg)(x)\n    y = layers.BatchNormalization()(y)\n    return layers.ReLU()(y)\n\ndef resblock(x, f, down=False):\n    s = 2 if down else 1\n    y = conv3(x, f, s)\n    y = conv3(y, f, 1)\n    if down or x.shape[-1] != f:\n        skip = layers.Conv2D(f, 1, strides=s, padding=\"same\",\n                             kernel_initializer=\"he_normal\",\n                             use_bias=False, kernel_regularizer=reg)(x)\n        skip = layers.BatchNormalization()(skip)\n    else:\n        skip = x\n    z = layers.Add()([y, skip])\n    return layers.ReLU()(z)\n\ndef build_resnet18_strong():\n    inp = layers.Input((32, 32, 3))\n    x = layers.ZeroPadding2D(4)(inp)\n    x = layers.RandomCrop(32, 32)(x)\n    x = layers.RandomFlip(\"horizontal\")(x)\n    x = layers.RandomContrast(0.1)(x)\n    x = norm(x)\n\n    x = conv3(x, 64)\n    x = resblock(x, 64);  x = resblock(x, 64)\n    x = resblock(x, 128, down=True); x = resblock(x, 128)\n    x = resblock(x, 256, down=True); x = resblock(x, 256)\n    x = resblock(x, 512, down=True); x = resblock(x, 512)\n\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.5)(x)\n    out = layers.Dense(10, activation=\"softmax\")(x)\n    return models.Model(inp, out)\n\n_tmp = build_resnet18_strong()\nprint(\"✅ Built strong ResNet-18 | layers:\", len(_tmp.layers), \"| params:\", _tmp.count_params())\n_tmp.summary(); del _tmp\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T13:15:50.223038Z","iopub.execute_input":"2025-09-21T13:15:50.223718Z","iopub.status.idle":"2025-09-21T13:15:52.450812Z","shell.execute_reply.started":"2025-09-21T13:15:50.223688Z","shell.execute_reply":"2025-09-21T13:15:52.450068Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train settings that consistently reach ≥0.88 on CIFAR-10\nEPOCHS = 120\nBATCH  = 128\nINIT_LR = 0.1  # with SGD this is standard for CIFAR-10\n\ndef lr_cosine_warmup(epoch, total=EPOCHS, base=INIT_LR, warmup=5):\n    if epoch < warmup:\n        return base * (epoch + 1) / warmup\n    t = (epoch - warmup) / (total - warmup)\n    return 0.5 * base * (1 + math.cos(math.pi * t))\n\nmodel = build_resnet18_strong()\nmodel.compile(\n    optimizer=SGD(learning_rate=INIT_LR, momentum=0.9, nesterov=True),\n    loss=tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.1),\n    metrics=[\"accuracy\"]\n)\n\ncallbacks = [\n    LearningRateScheduler(lambda e: lr_cosine_warmup(e), verbose=1),\n    ModelCheckpoint(CKPT_PATH, monitor=\"val_loss\", save_best_only=True, verbose=1),\n    EarlyStopping(monitor=\"val_loss\", patience=20, restore_best_weights=True, verbose=1),\n]\nprint(\"✅ Compiled. Training for\", EPOCHS, \"epochs; best will be saved to:\", CKPT_PATH)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T13:16:11.108653Z","iopub.execute_input":"2025-09-21T13:16:11.109406Z","iopub.status.idle":"2025-09-21T13:16:11.392776Z","shell.execute_reply.started":"2025-09-21T13:16:11.109380Z","shell.execute_reply":"2025-09-21T13:16:11.391987Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Always train (fresh run). If you want to skip when a good checkpoint exists, guard with os.path.exists.\nhistory = model.fit(\n    x_train, y_train,                      # in-graph augmentation handles transforms\n    validation_data=(x_val, y_val),\n    epochs=EPOCHS,\n    batch_size=BATCH,\n    callbacks=callbacks,\n    verbose=1\n)\nprint(\"✅ Training done. Best saved to:\", CKPT_PATH)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T13:16:15.557557Z","iopub.execute_input":"2025-09-21T13:16:15.558267Z","iopub.status.idle":"2025-09-21T14:37:08.967203Z","shell.execute_reply.started":"2025-09-21T13:16:15.558238Z","shell.execute_reply":"2025-09-21T14:37:08.966399Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load exactly the saved file and evaluate on official CIFAR-10 test set\nassert os.path.exists(CKPT_PATH), f\"Missing: {CKPT_PATH}\"\nbest_model = tf.keras.models.load_model(CKPT_PATH)\n\n(_, _), (x_te, y_te) = cifar10.load_data()\nx_te = x_te.astype(\"float32\") / 255.0\ny_te = to_categorical(y_te, 10)\n\noffline_acc = best_model.evaluate(x_te, y_te, verbose=0)[1]\nprint(f\"✅ Offline CIFAR-10 test accuracy: {offline_acc:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T14:37:33.411965Z","iopub.execute_input":"2025-09-21T14:37:33.412225Z","iopub.status.idle":"2025-09-21T14:37:43.623000Z","shell.execute_reply.started":"2025-09-21T14:37:33.412206Z","shell.execute_reply":"2025-09-21T14:37:43.622417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Read Kaggle IDs and build a fast {id -> path} index by scanning folders once\nassert os.path.exists(SAMPLE_SUB_PATH), \"sampleSubmission.csv not found!\"\nsub_df  = pd.read_csv(SAMPLE_SUB_PATH)\nid_list = sub_df[\"id\"].tolist()\nprint(\"IDs to predict:\", len(id_list))\n\nindex = {}\nfor base in [p for p in TEST_DIR_CANDIDATES if os.path.isdir(p)]:\n    for ext in (\"png\",\"jpg\",\"jpeg\"):\n        for p in glob.glob(os.path.join(base, f\"*.{ext}\")):\n            name = os.path.splitext(os.path.basename(p))[0]\n            if name.isdigit():\n                i = int(name)\n                if i not in index:  # first hit wins\n                    index[i] = p\n\nmissing = [i for i in id_list if i not in index]\nassert not missing, f\"Missing {len(missing)} ids, e.g. {missing[:5]}\"\npaths = [index[i] for i in id_list]\nprint(\"✅ Paths resolved:\", len(paths), \"| example:\", paths[0])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T14:37:56.068995Z","iopub.execute_input":"2025-09-21T14:37:56.069267Z","iopub.status.idle":"2025-09-21T14:38:05.794638Z","shell.execute_reply.started":"2025-09-21T14:37:56.069248Z","shell.execute_reply":"2025-09-21T14:38:05.794005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# tf.data pipeline → predict class indices\ndef load_and_preprocess(path):\n    raw = tf.io.read_file(path)\n    img = tf.io.decode_image(raw, channels=3, expand_animations=False)\n    img = tf.image.convert_image_dtype(img, tf.float32)   # [0,1]\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_and_preprocess, num_parallel_calls=tf.data.AUTOTUNE)\nds = ds.batch(BATCH_PRED).prefetch(tf.data.AUTOTUNE)\n\nprint(\"Predicting with:\", CKPT_PATH)\nprobs = best_model.predict(ds, verbose=1)\npred_idx = np.argmax(probs, axis=1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T14:38:25.598896Z","iopub.execute_input":"2025-09-21T14:38:25.599169Z","iopub.status.idle":"2025-09-21T14:48:22.084680Z","shell.execute_reply.started":"2025-09-21T14:38:25.599149Z","shell.execute_reply":"2025-09-21T14:48:22.083858Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Map to label strings, keep original ID order, write CSV\nlabel_names = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\n               \"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)\n\nprint(\"📁 Saved submission.csv\")\nprint(\"Shape:\", submission.shape, \"| Unique IDs?:\", submission['id'].is_unique)\nprint(submission.head())\nprint(\"Label distribution:\")\nprint(submission[\"label\"].value_counts().head(10))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T14:49:35.386142Z","iopub.execute_input":"2025-09-21T14:49:35.386848Z","iopub.status.idle":"2025-09-21T14:49:35.746449Z","shell.execute_reply.started":"2025-09-21T14:49:35.386825Z","shell.execute_reply":"2025-09-21T14:49:35.745684Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}