{"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"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q py7zr\nimport py7zr\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-21T15:04:08.865924Z","iopub.execute_input":"2025-09-21T15:04:08.866082Z","iopub.status.idle":"2025-09-21T15:04:15.229402Z","shell.execute_reply.started":"2025-09-21T15:04:08.866066Z","shell.execute_reply":"2025-09-21T15:04:15.228703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# CIFAR-10 — ResNet18 (from scratch) + Kaggle Submission\n# - Uses py7zr to extract /kaggle/input/cifar-10/*.7z to /kaggle/working/*\n# - Trains on official Keras CIFAR-10 (allowed; no pretrained weights)\n# - Rotation augmentation included (±15 deg)\n# - Predicts on extracted Kaggle test images and saves submission.csv\n# =========================================================\nimport os, glob, math, random, numpy as np, pandas as pd, 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\nprint(\"TensorFlow:\", tf.__version__)\n\n# --------------------------\n# Reproducibility\n# --------------------------\nSEED = 1337\nrandom.seed(SEED); np.random.seed(SEED); tf.random.set_seed(SEED)\n\n# =========================================================\n# Cell 1 — Paths & competition inputs\n# =========================================================\n# Main competition mount commonly named 'cifar-10'\nINPUT_DIR = \"/kaggle/input/cifar-10\"\nassert os.path.exists(INPUT_DIR), f\"Expected {INPUT_DIR} to exist as an input on Kaggle.\"\n\nTRAIN_7Z = os.path.join(INPUT_DIR, \"train.7z\")\nTEST_7Z  = os.path.join(INPUT_DIR, \"test.7z\")\nSAMPLE_SUB = os.path.join(INPUT_DIR, \"sampleSubmission.csv\")\nassert os.path.exists(SAMPLE_SUB), \"sampleSubmission.csv not found in the competition input.\"\n\n# Extraction targets\nWK_TRAIN_DIR = \"/kaggle/working/train_images\"\nWK_TEST_DIR  = \"/kaggle/working/test_images\"\nos.makedirs(WK_TRAIN_DIR, exist_ok=True)\nos.makedirs(WK_TEST_DIR,  exist_ok=True)\n\n# =========================================================\n# Cell 2 — Extract .7z archives with py7zr\n# =========================================================\nimport py7zr\n\ndef extract_7z(file_path, out_dir):\n    try:\n        with py7zr.SevenZipFile(file_path, mode='r') as z:\n            z.extractall(out_dir)\n        print(f\" Extracted: {file_path} -> {out_dir}\")\n        return True\n    except Exception as e:\n        print(f\" Failed to extract {file_path}: {e}\")\n        return False\n\nprint(\"=== EXTRACTING COMPETITION DATA ===\")\nif os.path.exists(TRAIN_7Z):\n    extract_7z(TRAIN_7Z, WK_TRAIN_DIR)\nif os.path.exists(TEST_7Z):\n    extract_7z(TEST_7Z, WK_TEST_DIR)\n\n# After extraction the structure is typically:\n# /kaggle/working/train_images/train/*.png   (50,000 files)\n# /kaggle/working/test_images/test/*.png     (300,000 files)\n# Resolve the inner folders robustly:\ndef find_inner_dir(root, expected_leaf):\n    # Try common layouts: root/<expected_leaf> or root/*/<expected_leaf>\n    d1 = os.path.join(root, expected_leaf)\n    if os.path.isdir(d1):\n        return d1\n    # look one level deep\n    for d in glob.glob(os.path.join(root, \"*\")):\n        cand = os.path.join(d, expected_leaf)\n        if os.path.isdir(cand):\n            return cand\n    # fall back to root if files are directly inside\n    return root\n\nTRAIN_IMG_DIR = find_inner_dir(WK_TRAIN_DIR, \"train\")\nTEST_IMG_DIR  = find_inner_dir(WK_TEST_DIR, \"test\")\nprint(\"Resolved TRAIN_IMG_DIR:\", TRAIN_IMG_DIR)\nprint(\"Resolved TEST_IMG_DIR :\", TEST_IMG_DIR)\n\n# (Optional) quick count\nn_train_found = len(glob.glob(os.path.join(TRAIN_IMG_DIR, \"*.png\"))) + len(glob.glob(os.path.join(TRAIN_IMG_DIR, \"*.jpg\")))\nn_test_found  = len(glob.glob(os.path.join(TEST_IMG_DIR, \"*.png\")))  + len(glob.glob(os.path.join(TEST_IMG_DIR, \"*.jpg\")))\nprint(\"Found train imgs:\", n_train_found, \"| test imgs:\", n_test_found)\n\n# =========================================================\n# Cell 3 — Load official CIFAR-10 for training/validation\n#        (Allowed; same data distribution as competition)\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(\"CIFAR-10 train:\", x_train.shape, y_train.shape, \"| test:\", x_test.shape, y_test.shape)\n\n# =========================================================\n# Cell 4 — Model: ResNet18-like (from scratch, no pretrained)\n# =========================================================\ndef conv_block(x, filters, stride=1, wd=1e-4):\n    y = layers.Conv2D(filters, 3, strides=stride, padding='same',\n                      kernel_regularizer=regularizers.l2(wd),\n                      use_bias=False, kernel_initializer=\"he_normal\")(x)\n    y = layers.BatchNormalization()(y)\n    y = layers.ReLU()(y)\n    return y\n\ndef residual_block(x, filters, downsample=False, wd=1e-4):\n    stride = 2 if downsample else 1\n    y = conv_block(x, filters, stride=stride, wd=wd)\n    y = layers.Conv2D(filters, 3, padding='same',\n                      kernel_regularizer=regularizers.l2(wd),\n                      use_bias=False, kernel_initializer=\"he_normal\")(y)\n    y = layers.BatchNormalization()(y)\n    if downsample or x.shape[-1] != filters:\n        x = layers.Conv2D(filters, 1, strides=stride, padding='same',\n                          kernel_regularizer=regularizers.l2(wd),\n                          use_bias=False, kernel_initializer=\"he_normal\")(x)\n        x = layers.BatchNormalization()(x)\n    out = layers.Add()([x, y])\n    out = layers.ReLU()(out)\n    return out\n\ndef build_resnet18(input_shape=(32,32,3), num_classes=10, wd=1e-4, drop=0.5):\n    inputs = layers.Input(shape=input_shape)\n    x = conv_block(inputs, 64, wd=wd)\n    x = residual_block(x, 64, wd=wd)\n    x = residual_block(x, 64, wd=wd)\n\n    x = residual_block(x, 128, downsample=True, wd=wd)\n    x = residual_block(x, 128, wd=wd)\n\n    x = residual_block(x, 256, downsample=True, wd=wd)\n    x = residual_block(x, 256, wd=wd)\n\n    x = residual_block(x, 512, downsample=True, wd=wd)\n    x = residual_block(x, 512, wd=wd)\n\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(drop)(x)\n    outputs = layers.Dense(num_classes, activation='softmax',\n                           kernel_initializer=\"he_normal\")(x)\n    return models.Model(inputs, outputs, name=\"ResNet18_CIFAR10\")\n\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\"])\nmodel.summary()\n\n# =========================================================\n# Cell 5 — Augmentation + training callbacks\n# (Rotation added here: ±15 degrees)\n# =========================================================\ndatagen = ImageDataGenerator(\n    rotation_range=15,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    horizontal_flip=True\n)\ndatagen.fit(x_train)\n\ncheckpoint = ModelCheckpoint(\"best_model_loss.h5\", monitor=\"val_loss\",\n                             save_best_only=True, verbose=1)\nearly_stop = EarlyStopping(monitor=\"val_loss\", patience=12,\n                           restore_best_weights=True, verbose=1)\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 = LearningRateScheduler(cosine_annealing)\n\n# =========================================================\n# Cell 6 — Train\n# (Let Keras infer steps_per_epoch from generator length)\n# =========================================================\nBATCH_SIZE = 128\nEPOCHS = 60\n\nhistory = model.fit(\n    datagen.flow(x_train, y_train, batch_size=BATCH_SIZE),\n    validation_data=(x_test, y_test),\n    epochs=EPOCHS,\n    callbacks=[checkpoint, early_stop, lr_scheduler],\n    verbose=2\n)\n\nloss, acc = model.evaluate(x_test, y_test, verbose=0)\nprint(f\" Local CIFAR-10 test accuracy: {acc:.4f}\")\nprint(f\" Local CIFAR-10 test loss    : {loss:.4f}\")\n\n# =========================================================\n# Cell 7 — Load best checkpoint\n# =========================================================\nbest_model = tf.keras.models.load_model(\"best_model_loss.h5\")\nprint(\" Loaded best_model_loss.h5\")\n\n# =========================================================\n# Cell 8 — Build Kaggle submission from extracted test images\n# =========================================================\n# Load IDs from sampleSubmission.csv (1..300000)\nsample = pd.read_csv(SAMPLE_SUB)\nid_list = sample[\"id\"].tolist()\nprint(\"Total test IDs:\", len(id_list))\n\n# Resolve test directory (png/jpg)\ndef resolve_img_path(base_dir, img_id):\n    p_png = os.path.join(base_dir, f\"{img_id}.png\")\n    if os.path.exists(p_png): return p_png\n    p_jpg = os.path.join(base_dir, f\"{img_id}.jpg\")\n    if os.path.exists(p_jpg): return p_jpg\n    raise FileNotFoundError(f\"Missing image id={img_id} in {base_dir}\")\n\ntest_paths = [resolve_img_path(TEST_IMG_DIR, i) for i in id_list]\n\n# tf.data pipeline for fast batched inference\ndef load_img_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])  # safety\n    return img\n\nbatch_size = 1024\nds = tf.data.Dataset.from_tensor_slices(test_paths)\nds = ds.map(load_img_tf, num_parallel_calls=tf.data.AUTOTUNE)\nds = ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)\n\n# Predict\nprobs = best_model.predict(ds, verbose=1)\npred_idx = np.argmax(probs, axis=1)\nlabel_names = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\"dog\",\"frog\",\"horse\",\"ship\",\"truck\"]\npred_labels = [label_names[i] for i in pred_idx]\n\n# Save submission\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# Quick checks\nprint(\"Shape:\", submission.shape)\nprint(\"Unique IDs:\", submission['id'].is_unique)\nprint(\"ID range:\", submission['id'].min(), \"to\", submission['id'].max())\nprint(submission['label'].value_counts().head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T15:04:15.231162Z","iopub.execute_input":"2025-09-21T15:04:15.231634Z","iopub.status.idle":"2025-09-21T15:34:57.408113Z","shell.execute_reply.started":"2025-09-21T15:04:15.231611Z","shell.execute_reply":"2025-09-21T15:34:57.407470Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(\"CWD:\", os.getcwd())         # should be /kaggle/working\nassert os.path.exists(\"submission.csv\")\nfrom IPython.display import FileLink\nFileLink(\"submission.csv\")\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-21T15:03:45.685Z"}},"outputs":[],"execution_count":null}]}