{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":264318247,"sourceType":"kernelVersion"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ==== Cell 1: setup ====\nimport os, math, random, json, pathlib, sys\nimport numpy as np\nimport tensorflow as tf\nfrom tensorflow import keras\n\nprint(\"TensorFlow:\", tf.__version__)\nassert tf.__version__.startswith(\"2.\"), \"TF 2.x required\"\n\n# Seed (note: we do not force deterministic ops; Kaggle speed matters)\nSEED       = 42\nIMG_SIZE   = 32\nNUM_CLASSES= 10\nBATCH_SIZE = 128\nAUTO       = tf.data.AUTOTUNE\n\ntf.keras.utils.set_random_seed(SEED)\nnp.random.seed(SEED)\nrandom.seed(SEED)\n\n# CIFAR-10 class names (label order used by Keras)\nCLASS_NAMES = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']\n\n# Small helpers\ndef to_one_hot(y, num_classes=NUM_CLASSES):\n    return tf.one_hot(tf.cast(y, tf.int32), num_classes)\n\nprint(\"Setup OK.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:37:36.959635Z","iopub.execute_input":"2025-10-05T13:37:36.960270Z","iopub.status.idle":"2025-10-05T13:37:36.966592Z","shell.execute_reply.started":"2025-10-05T13:37:36.960241Z","shell.execute_reply":"2025-10-05T13:37:36.965988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== Cell 2: data arrays (no external weights) ====\n(x_train_full, y_train_full), (x_test, y_test) = keras.datasets.cifar10.load_data()\ny_train_full = y_train_full.squeeze().astype(\"int32\")\ny_test       = y_test.squeeze().astype(\"int32\")\n\n# scale to [0,1]\nx_train_full = x_train_full.astype(\"float32\")/255.0\nx_test       = x_test.astype(\"float32\")/255.0\n\n# 45k train / 5k val\nx_train, y_train = x_train_full[:45000], y_train_full[:45000]\nx_val,   y_val   = x_train_full[45000:], y_train_full[45000:]\n\nprint(x_train.shape, y_train.shape, x_val.shape, y_val.shape, x_test.shape, y_test.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:37:50.488016Z","iopub.execute_input":"2025-10-05T13:37:50.488683Z","iopub.status.idle":"2025-10-05T13:37:55.396388Z","shell.execute_reply.started":"2025-10-05T13:37:50.488659Z","shell.execute_reply":"2025-10-05T13:37:55.395586Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== Cell 3: augmentations ====\n\n@tf.function\ndef random_crop_pad_flip(img):\n    # classic CIFAR trick: pad 4 -> random crop -> flip\n    img = tf.image.resize_with_crop_or_pad(img, IMG_SIZE + 4, IMG_SIZE + 4)\n    img = tf.image.random_crop(img, size=[IMG_SIZE, IMG_SIZE, 3])\n    img = tf.image.random_flip_left_right(img)\n    return img\n\n@tf.function\ndef cutout_image(img, size=8):\n    # per-image CutOut (no control-flow pitfalls)\n    h = tf.shape(img)[0]\n    w = tf.shape(img)[1]\n    cy = tf.random.uniform([], 0, h, dtype=tf.int32)\n    cx = tf.random.uniform([], 0, w, dtype=tf.int32)\n    half = size // 2\n    y1 = tf.clip_by_value(cy - half, 0, h)\n    y2 = tf.clip_by_value(cy + half, 0, h)\n    x1 = tf.clip_by_value(cx - half, 0, w)\n    x2 = tf.clip_by_value(cx + half, 0, w)\n\n    # build rectangular mask (0 in the box, 1 elsewhere)\n    rect = tf.pad(\n        tf.zeros([y2 - y1, x2 - x1, 3], dtype=img.dtype),\n        paddings=[[y1, h - y2], [x1, w - x2], [0, 0]],\n        constant_values=0.0\n    )\n    mask = 1.0 - rect\n    return img * mask\n\n@tf.function\ndef apply_cutout_batch(images, p=0.5, size=8):\n    # map per-image cutout, then randomly choose to keep/appply per sample\n    cut_imgs = tf.map_fn(lambda im: cutout_image(im, size=size),\n                         images, fn_output_signature=tf.float32)\n    b = tf.less(tf.random.uniform([tf.shape(images)[0], 1, 1, 1]), p)\n    return tf.where(b, cut_imgs, images)\n\n@tf.function\ndef cutmix_batch(images, labels, alpha=1.0, p=0.5):\n    # CutMix with one-hot labels; kept unchanged, robust path\n    if tf.random.uniform(()) > p:\n        return images, labels\n    b = tf.shape(images)[0]\n    idx = tf.random.shuffle(tf.range(b))\n    imgs2 = tf.gather(images, idx)\n    labs2 = tf.gather(labels, idx)\n\n    # sample area via lambda -> box size\n    lam = tf.random.uniform([], 0.3, 0.7)  # stable range\n    H = tf.shape(images)[1]; W = tf.shape(images)[2]\n    r  = tf.sqrt(1.0 - lam)\n    rw = tf.cast(r * tf.cast(W, tf.float32), tf.int32)\n    rh = tf.cast(r * tf.cast(H, tf.float32), tf.int32)\n    rx = tf.random.uniform([], 0, W, dtype=tf.int32)\n    ry = tf.random.uniform([], 0, H, dtype=tf.int32)\n\n    x1 = tf.clip_by_value(rx - rw // 2, 0, W)\n    y1 = tf.clip_by_value(ry - rh // 2, 0, H)\n    x2 = tf.clip_by_value(rx + rw // 2, 0, W)\n    y2 = tf.clip_by_value(ry + rh // 2, 0, H)\n\n    rect = tf.pad(\n        tf.ones([y2 - y1, x2 - x1, 3], dtype=images.dtype),\n        paddings=[[y1, H - y2], [x1, W - x2], [0, 0]],\n        constant_values=0.0\n    )\n    mixed = images * (1.0 - rect) + imgs2 * rect\n\n    area = tf.cast((x2 - x1) * (y2 - y1), tf.float32)\n    lam_adj = 1.0 - area / tf.cast(H * W, tf.float32)\n    labs = lam_adj * labels + (1.0 - lam_adj) * labs2\n    return mixed, labs\n\nprint(\"Augmentations ready (per-image CutOut; robust CutMix).\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:46:19.063657Z","iopub.execute_input":"2025-10-05T13:46:19.063996Z","iopub.status.idle":"2025-10-05T13:46:19.077423Z","shell.execute_reply.started":"2025-10-05T13:46:19.063972Z","shell.execute_reply":"2025-10-05T13:46:19.076742Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== Cell 4: tf.data pipelines ====\n# One-hot labels up front (required for CutMix / categorical loss)\n\ny_train_oh = tf.one_hot(y_train, NUM_CLASSES)\ny_val_oh   = tf.one_hot(y_val,   NUM_CLASSES)\ny_test_oh  = tf.one_hot(y_test,  NUM_CLASSES)\n\nUSE_CUTMIX  = True   # set False if you want simpler run\nCUTMIX_P    = 0.5\nUSE_CUTOUT  = True\nCUTOUT_P    = 0.5\nCUTOUT_SIZE = 8\n\ndef aug_batch(images, labels):\n    # per-image crop+flip\n    images = tf.map_fn(random_crop_pad_flip, images, fn_output_signature=tf.float32)\n    # optional CutOut\n    if USE_CUTOUT:\n        images = apply_cutout_batch(images, p=CUTOUT_P, size=CUTOUT_SIZE)\n    # optional CutMix\n    if USE_CUTMIX:\n        images, labels = cutmix_batch(images, labels, alpha=1.0, p=CUTMIX_P)\n    return images, labels\n\ntrain_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train_oh))\ntrain_ds = train_ds.shuffle(50000, seed=SEED, reshuffle_each_iteration=True)\ntrain_ds = train_ds.batch(BATCH_SIZE, drop_remainder=True)\ntrain_ds = train_ds.map(aug_batch, num_parallel_calls=AUTO).prefetch(AUTO)\n\nval_ds   = tf.data.Dataset.from_tensor_slices((x_val, y_val_oh))  \\\n           .batch(BATCH_SIZE).prefetch(AUTO)\ntest_ds_official = tf.data.Dataset.from_tensor_slices((x_test, y_test_oh)) \\\n           .batch(BATCH_SIZE).prefetch(AUTO)\n\ntrain_steps = int(tf.data.experimental.cardinality(train_ds).numpy())\nval_steps   = int(tf.data.experimental.cardinality(val_ds).numpy())\nprint(\"train_steps:\", train_steps, \"| val_steps:\", val_steps)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:47:00.954998Z","iopub.execute_input":"2025-10-05T13:47:00.955285Z","iopub.status.idle":"2025-10-05T13:47:03.794333Z","shell.execute_reply.started":"2025-10-05T13:47:00.955263Z","shell.execute_reply":"2025-10-05T13:47:03.793687Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== Cell 5: model (custom residual CNN, no pretrained weights) ====\nfrom tensorflow.keras import layers, regularizers, Model\n\nWEIGHT_DECAY = 5e-4  # classic CIFAR-10 L2\n\ndef conv3x3(x, filters, stride=1):\n    return layers.Conv2D(\n        filters, 3, strides=stride, padding=\"same\",\n        use_bias=False, kernel_initializer=\"he_normal\",\n        kernel_regularizer=regularizers.l2(WEIGHT_DECAY)\n    )(x)\n\ndef basic_block(x, filters, stride):\n    # Pre-activation basic block\n    shortcut = x\n    y = layers.BatchNormalization()(x)\n    y = layers.ReLU()(y)\n    y = conv3x3(y, filters, stride=stride)\n\n    y = layers.BatchNormalization()(y)\n    y = layers.ReLU()(y)\n    y = conv3x3(y, filters, stride=1)\n\n    if (shortcut.shape[-1] != filters) or (stride != 1):\n        shortcut = layers.Conv2D(\n            filters, 1, strides=stride, padding=\"same\", use_bias=False,\n            kernel_initializer=\"he_normal\",\n            kernel_regularizer=regularizers.l2(WEIGHT_DECAY)\n        )(shortcut)\n    out = layers.Add()([shortcut, y])\n    return out\n\ndef make_stage(x, filters, blocks, first_stride):\n    x = basic_block(x, filters, first_stride)\n    for _ in range(blocks - 1):\n        x = basic_block(x, filters, 1)\n    return x\n\ndef build_model(input_shape=(IMG_SIZE, IMG_SIZE, 3), num_classes=NUM_CLASSES):\n    inp = layers.Input(shape=input_shape)\n    x = layers.Conv2D(\n        64, 3, padding=\"same\", use_bias=False,\n        kernel_initializer=\"he_normal\",\n        kernel_regularizer=regularizers.l2(WEIGHT_DECAY)\n    )(inp)\n\n    # Stages (compact but strong)\n    x = make_stage(x,  72, blocks=2, first_stride=1)  # 32x32\n    x = make_stage(x, 112, blocks=2, first_stride=2)  # 16x16\n    x = make_stage(x, 160, blocks=3, first_stride=2)  # 8x8\n    x = make_stage(x, 256, blocks=2, first_stride=2)  # 4x4\n\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n    x = layers.GlobalAveragePooling2D()(x)\n    out = layers.Dense(num_classes, activation=\"softmax\",\n                       kernel_regularizer=regularizers.l2(WEIGHT_DECAY))(x)\n    return Model(inp, out, name=\"MiniResNet_A6\")\n\nmodel = build_model()\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:52:49.168383Z","iopub.execute_input":"2025-10-05T13:52:49.169111Z","iopub.status.idle":"2025-10-05T13:52:50.358960Z","shell.execute_reply.started":"2025-10-05T13:52:49.169086Z","shell.execute_reply":"2025-10-05T13:52:50.358266Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== Cell 6: optimizer & compile (AdamW + warmup-cosine, serializable) ====\n\nimport numpy as np\nfrom tensorflow import keras\n\n# steps/epoch from your tf.data pipelines\ntrain_steps = int(tf.data.experimental.cardinality(train_ds).numpy())\n\nEPOCHS       = 200\nTOTAL_STEPS  = train_steps * EPOCHS\nWARMUP_STEPS = int(0.10 * TOTAL_STEPS)   # 10% warmup\n\nclass WarmupCosine(keras.optimizers.schedules.LearningRateSchedule):\n    def __init__(self, base_lr, warmup_steps, total_steps, min_lr=1e-5):\n        super().__init__()\n        self.base_lr = float(base_lr)\n        self.warmup_steps = float(warmup_steps)\n        self.total_steps = float(total_steps)\n        self.min_lr = float(min_lr)\n\n    def __call__(self, step):\n        step = tf.cast(step, tf.float32)\n        # linear warmup\n        warm = self.base_lr * (step / tf.maximum(1.0, self.warmup_steps))\n        # cosine decay to min_lr\n        progress = (step - self.warmup_steps) / tf.maximum(1.0, self.total_steps - self.warmup_steps)\n        progress = tf.clip_by_value(progress, 0.0, 1.0)\n        cos = self.min_lr + 0.5 * (self.base_lr - self.min_lr) * (1.0 + tf.cos(np.pi * progress))\n        return tf.where(step < self.warmup_steps, warm, cos)\n\n    def get_config(self):\n        return {\n            \"base_lr\": self.base_lr,\n            \"warmup_steps\": self.warmup_steps,\n            \"total_steps\": self.total_steps,\n            \"min_lr\": self.min_lr,\n        }\n\n# rebuild the schedule & optimizer\nlr_schedule = WarmupCosine(\n    base_lr=3e-4,\n    warmup_steps=WARMUP_STEPS,\n    total_steps=TOTAL_STEPS,\n    min_lr=1e-5,\n)\noptimizer = keras.optimizers.AdamW(learning_rate=lr_schedule, weight_decay=5e-4)\n\n# one-hot labels → categorical loss with smoothing (stable with CutMix)\nloss = keras.losses.CategoricalCrossentropy(label_smoothing=0.1)\n\nmodel.compile(optimizer=optimizer, loss=loss, metrics=[\"accuracy\"])\nprint(\"Recompiled with serializable WarmupCosine. Ready to train.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:57:41.317250Z","iopub.execute_input":"2025-10-05T13:57:41.317505Z","iopub.status.idle":"2025-10-05T13:57:41.331516Z","shell.execute_reply.started":"2025-10-05T13:57:41.317484Z","shell.execute_reply":"2025-10-05T13:57:41.330746Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== Cell 7: train & evaluate ====\nckpt_path = \"/kaggle/working/mini_resnet_best.keras\"\n\ncallbacks = [\n    keras.callbacks.ModelCheckpoint(\n        ckpt_path, monitor=\"val_accuracy\", save_best_only=True, verbose=1\n    ),\n    keras.callbacks.EarlyStopping(\n        monitor=\"val_accuracy\", patience=20, restore_best_weights=True, verbose=1\n    ),\n    keras.callbacks.CSVLogger(\"/kaggle/working/train_log.csv\", append=False),\n]\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS,\n    callbacks=callbacks,\n    verbose=1\n)\n\n# quick sanity on official CIFAR-10 test split\ntest_loss, test_acc = model.evaluate(test_ds_official, verbose=0)\nprint(f\"[Official CIFAR-10 test] accuracy={test_acc:.4f}  loss={test_loss:.4f}\")\nprint(\"Best model saved at:\", ckpt_path)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:58:05.926118Z","iopub.execute_input":"2025-10-05T13:58:05.926387Z","iopub.status.idle":"2025-10-05T14:58:55.945071Z","shell.execute_reply.started":"2025-10-05T13:58:05.926368Z","shell.execute_reply":"2025-10-05T14:58:55.944299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== STEP 8: Build submission.csv for CIFAR-10 Challenge ====\nimport os, numpy as np, pandas as pd, tensorflow as tf\n\n# Paths\nDATA_DIR = \"/kaggle/input/cifar-10\"\nWORK_DIR = \"/kaggle/working\"\nMODEL_PATH = \"/kaggle/input/rashid/mini_resnet_best.keras\"\nSAMPLE_SUB = os.path.join(DATA_DIR, \"sampleSubmission.csv\")\nTEST_7Z = os.path.join(DATA_DIR, \"test.7z\")\nTEST_DIR = os.path.join(WORK_DIR, \"test\")\n\n# --- Check if model exists\nif not os.path.exists(MODEL_PATH):\n    raise FileNotFoundError(f\"❌ Model not found at {MODEL_PATH}. Please attach it under 'Add input'.\")\nprint(f\"✅ Model found at: {MODEL_PATH}\")\n\n# --- Load trained model\nmodel = tf.keras.models.load_model(MODEL_PATH, compile=False)\nprint(\"✅ Model loaded successfully.\")\n\n# --- Extract test.7z only if not extracted\nif not os.path.exists(TEST_DIR) or len(os.listdir(TEST_DIR)) == 0:\n    print(\"📦 Extracting test.7z ...\")\n    os.system(f\"7z x {TEST_7Z} -o{WORK_DIR}/test -y > /dev/null\")\nelse:\n    print(\"✅ Test images already extracted.\")\n\n# --- Gather all .png files\nimport glob\npngs = sorted(glob.glob(os.path.join(TEST_DIR, \"**\", \"*.png\"), recursive=True))\nprint(f\"📸 Found {len(pngs)} test images.\")\n\nif len(pngs) == 0:\n    raise FileNotFoundError(\"No test images found. Please verify test.7z extraction.\")\n\n# --- Define loading and preprocessing\ndef load_image(path):\n    img = tf.io.read_file(path)\n    img = tf.io.decode_image(img, channels=3, expand_animations=False)\n    img = tf.image.resize(img, [32, 32])\n    img = tf.image.convert_image_dtype(img, tf.float32)\n    img = (img - 0.5) * 2.0  # normalize [-1,1]\n    return img\n\nBATCH = 512\ntest_ds = (tf.data.Dataset.from_tensor_slices(pngs)\n            .map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\n            .batch(BATCH)\n            .prefetch(tf.data.AUTOTUNE))\n\n# --- Predict\nprobs = model.predict(test_ds, verbose=1)\npred_idx = np.argmax(probs, axis=1)\n\n# --- Create submission\nclass_names = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\"dog\",\"frog\",\"horse\",\"ship\",\"truck\"]\npred_labels = [class_names[i] for i in pred_idx]\n\nids = [int(os.path.basename(p).split(\".\")[0]) for p in pngs]\nsubmission = pd.DataFrame({\"id\": ids, \"label\": pred_labels})\nsubmission = submission.sort_values(\"id\")\nsave_path = os.path.join(WORK_DIR, \"submission.csv\")\nsubmission.to_csv(save_path, index=False)\n\nprint(f\"\\n✅ Submission file saved to: {save_path}\")\nprint(submission.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:30:13.503262Z","iopub.execute_input":"2025-10-06T07:30:13.503900Z","iopub.status.idle":"2025-10-06T07:30:29.782505Z","shell.execute_reply.started":"2025-10-06T07:30:13.503864Z","shell.execute_reply":"2025-10-06T07:30:29.781898Z"}},"outputs":[],"execution_count":null}]}