{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, glob, numpy as np, pandas as pd\nfrom PIL import Image\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers, Model\nfrom tensorflow.keras.callbacks import Callback\n\n# ========= Repro & GPU info =========\nnp.random.seed(42)\ntf.random.set_seed(42)\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"GPU:\", tf.config.list_physical_devices('GPU'))\n\nclass_names = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-16T18:05:59.906181Z","iopub.execute_input":"2025-10-16T18:05:59.906856Z","iopub.status.idle":"2025-10-16T18:06:00.235692Z","shell.execute_reply.started":"2025-10-16T18:05:59.906831Z","shell.execute_reply":"2025-10-16T18:06:00.234987Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# LOAD & PREPARE DATA — simple /255 normalization (no standard deviation)\n# ============================================================================\n(x_train, y_train), (x_val, y_val) = tf.keras.datasets.cifar10.load_data()\n\n# Normalize pixel values to [0,1]\nx_train = x_train.astype(\"float32\") / 255.0\nx_val   = x_val.astype(\"float32\") / 255.0\n\n# One-hot encode labels for categorical crossentropy\ny_train = tf.keras.utils.to_categorical(y_train, 10)\ny_val   = tf.keras.utils.to_categorical(y_val, 10)\n\nprint(\"x_train:\", x_train.shape, \"y_train:\", y_train.shape)\nprint(\"x_val:\", x_val.shape, \"y_val:\", y_val.shape)\n\n# ============================================================================\n# MIXUP PIPELINE (dtype-safe)\n# ============================================================================\nAUTO = tf.data.AUTOTUNE\nBATCH = 128\n\ndef mixup_batch(x, y, alpha=0.2):\n    x = tf.cast(x, tf.float32)\n    y = tf.cast(y, tf.float32)\n\n    lam = tf.random.gamma(shape=[1], alpha=alpha, dtype=x.dtype)\n    lam = tf.squeeze(tf.minimum(lam, 1.0 - lam))\n    idx = tf.random.shuffle(tf.range(tf.shape(x)[0]))\n\n    x2 = tf.gather(x, idx)\n    y2 = tf.gather(y, idx)\n\n    x_m = lam * x + (1.0 - lam) * x2\n    y_m = lam * y + (1.0 - lam) * y2\n    return x_m, y_m\n\ntrain_ds = (\n    tf.data.Dataset.from_tensor_slices((x_train, y_train))\n    .shuffle(50_000, seed=42, reshuffle_each_iteration=True)\n    .batch(BATCH, drop_remainder=True)\n    .map(lambda xb, yb: mixup_batch(xb, yb, alpha=0.2), num_parallel_calls=AUTO)\n    .prefetch(AUTO)\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T18:06:00.238586Z","iopub.execute_input":"2025-10-16T18:06:00.238820Z","iopub.status.idle":"2025-10-16T18:06:04.962620Z","shell.execute_reply.started":"2025-10-16T18:06:00.238804Z","shell.execute_reply":"2025-10-16T18:06:04.961951Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AdvancedAugmentation(layers.Layer):\n    '''\n    Applying various different augmentation techniques\n    '''\n    \n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.flip = layers.RandomFlip(\"horizontal\")\n        self.rotation = layers.RandomRotation(0.10)\n        self.zoom = layers.RandomZoom(0.10)\n        self.translation = layers.RandomTranslation(0.10, 0.10)\n        self.contrast = layers.RandomContrast(0.15)\n\n    def cutout_one(self, image, mask_size=8):\n        h = tf.random.uniform([], 0, 32 - mask_size, dtype=tf.int32)\n        w = tf.random.uniform([], 0, 32 - mask_size, dtype=tf.int32)\n        hole = tf.zeros([mask_size, mask_size, 3], dtype=image.dtype)\n        mask = tf.pad(\n            hole, [[h, 32 - h - mask_size], [w, 32 - w - mask_size], [0, 0]], constant_values=1.0\n        )\n        return image * mask\n\n    def call(self, images, training=None):\n        if training:\n            images = self.flip(images)\n            images = self.rotation(images)\n            images = self.zoom(images)\n            images = self.translation(images)\n            images = self.contrast(images)\n            def apply_cutout():\n                return tf.map_fn(self.cutout_one, images, dtype=images.dtype)\n            images = tf.cond(tf.random.uniform([]) < 0.5, apply_cutout, lambda: images)\n        return images\n\nprint(\"✅ AdvancedAugmentation ready\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T18:06:04.963517Z","iopub.execute_input":"2025-10-16T18:06:04.963770Z","iopub.status.idle":"2025-10-16T18:06:04.972029Z","shell.execute_reply.started":"2025-10-16T18:06:04.963751Z","shell.execute_reply":"2025-10-16T18:06:04.971142Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def channel_attention(x, reduction=8):\n    c = x.shape[-1]\n    gap = layers.GlobalAveragePooling2D()(x)\n    gmp = layers.GlobalMaxPooling2D()(x)\n    d1 = layers.Dense(c // reduction, activation=\"relu\")\n    d2 = layers.Dense(c, activation=\"sigmoid\")\n    a1 = d2(d1(gap))\n    a2 = d2(d1(gmp))\n    attn = layers.Add()([a1, a2])\n    attn = layers.Reshape((1,1,c))(attn)\n    return layers.Multiply()([x, attn])\n\ndef spatial_attention(x):\n    avg = layers.Lambda(lambda t: tf.reduce_mean(t, axis=-1, keepdims=True))(x)\n    mx  = layers.Lambda(lambda t: tf.reduce_max(t, axis=-1, keepdims=True))(x)\n    concat = layers.Concatenate()([avg, mx])\n    attn = layers.Conv2D(1, 7, padding=\"same\", activation=\"sigmoid\")(concat)\n    return layers.Multiply()([x, attn])\n\nclass DropPath(layers.Layer):\n    def __init__(self, survival_prob=0.9):\n        super().__init__()\n        self.survival_prob = survival_prob\n    def call(self, x, training=None):\n        if (not training) or self.survival_prob == 1.0:\n            return x\n        keep = tf.random.uniform([tf.shape(x)[0],1,1,1]) < self.survival_prob\n        return tf.where(keep, x / self.survival_prob, tf.zeros_like(x))\n\ndef hybrid_residual_block(x, filters, stride=1, use_proj=False, use_attention=True, dp_survival=1.0):\n    shortcut = x\n    bneck = max(filters // 4, 16)\n\n    # 1x1\n    x = layers.Conv2D(bneck, 1, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x); x = layers.Activation(\"relu\")(x)\n    # 3x3\n    x = layers.Conv2D(bneck, 3, strides=stride, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x); x = layers.Activation(\"relu\")(x)\n    # 1x1\n    x = layers.Conv2D(filters, 1, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x)\n\n    if use_attention:\n        x = channel_attention(x)\n        x = spatial_attention(x)\n\n    if use_proj or stride != 1:\n        shortcut = layers.Conv2D(filters, 1, strides=stride, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(shortcut)\n        shortcut = layers.BatchNormalization()(shortcut)\n\n    x = DropPath(dp_survival)(x)\n    x = layers.Add()([x, shortcut])\n    x = layers.Activation(\"relu\")(x)\n    return x\n\ndef se_block(x, ratio=16):\n    channels = x.shape[-1]\n    se = layers.GlobalAveragePooling2D()(x)\n    se = layers.Dense(channels // ratio, activation='relu')(se)\n    se = layers.Dense(channels, activation='sigmoid')(se)\n    se = layers.Reshape((1, 1, channels))(se)\n    return layers.Multiply()([x, se])\n    \nprint(\"residual blocks and other model addition\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T18:06:04.973109Z","iopub.execute_input":"2025-10-16T18:06:04.973554Z","iopub.status.idle":"2025-10-16T18:06:05.642893Z","shell.execute_reply.started":"2025-10-16T18:06:04.973534Z","shell.execute_reply":"2025-10-16T18:06:05.642189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_model(input_shape=(32, 32, 3), num_classes=10):\n    inputs = layers.Input(shape=input_shape)\n    x = AdvancedAugmentation()(inputs)\n\n    # ---- Stem ----\n    \n    x = layers.BatchNormalization()(x)\n    x = layers.Activation(\"relu\")(x)\n    x = layers.Conv2D(64, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n\n    # ---- Stage 1: two residual blocks, 32×32 ----\n    for _ in range(2):\n        shortcut = x\n        x = layers.Conv2D(64, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.Activation(\"relu\")(x)\n        x = layers.Conv2D(64, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.Add()([x, shortcut])\n        x = layers.Activation(\"relu\")(x)\n        x = se_block(x)\n\n    # ---- Stage 2: two blocks, 16×16 ----\n    # Downsample first\n    shortcut = layers.Conv2D(128, 1, strides=2, padding=\"same\", use_bias=False)(x)\n    shortcut = layers.BatchNormalization()(shortcut)\n    x = layers.Conv2D(128, 3, strides=2, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation(\"relu\")(x)\n    x = layers.Conv2D(128, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Add()([x, shortcut])\n    x = layers.Activation(\"relu\")(x)\n    x = se_block(x)\n\n    # One more residual block (same size)\n    shortcut = x\n    x = layers.Conv2D(128, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation(\"relu\")(x)\n    x = layers.Conv2D(128, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Add()([x, shortcut])\n    x = layers.Activation(\"relu\")(x)\n    x = se_block(x)\n\n    # ---- Stage 3: **Added block here → makes it \"ResNet-10\"**, 8×8 ----\n    shortcut = layers.Conv2D(256, 1, strides=2, padding=\"same\", use_bias=False)(x)\n    shortcut = layers.BatchNormalization()(shortcut)\n    x = layers.Conv2D(256, 3, strides=2, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation(\"relu\")(x)\n    x = layers.Conv2D(256, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Add()([x, shortcut])\n    x = layers.Activation(\"relu\")(x)\n    x = se_block(x)\n\n    # ✅ Extra residual block (the “+1” you’re adding)\n    for _ in range(2):\n        shortcut = x\n        x = layers.Conv2D(256, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.Activation(\"relu\")(x)\n        x = layers.Conv2D(256, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.Add()([x, shortcut])\n        x = layers.Activation(\"relu\")(x)\n        x = se_block(x)\n\n    # ---- Stage 4: 512 filters, 4×4 ----\n    shortcut = layers.Conv2D(512, 1, strides=2, padding=\"same\", use_bias=False)(x)\n    shortcut = layers.BatchNormalization()(shortcut)\n    x = layers.Conv2D(512, 3, strides=2, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation(\"relu\")(x)\n    x = layers.Conv2D(512, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Add()([x, shortcut])\n    x = layers.Activation(\"relu\")(x)\n    x = se_block(x)\n\n    # After the first Stage 4 block, add:\n    for _ in range(2):\n        shortcut = x\n        x = layers.Conv2D(512, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.Activation(\"relu\")(x)\n        x = layers.Conv2D(512, 3, padding=\"same\", use_bias=False, kernel_initializer=\"he_normal\")(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.Add()([x, shortcut])\n        x = layers.Activation(\"relu\")(x)\n        x = se_block(x)\n\n\n    # ---- Head ----\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.4)(x)\n    outputs = layers.Dense(num_classes, activation=\"softmax\", kernel_initializer=\"he_normal\")(x)\n\n    model = Model(inputs, outputs, name=\"ResNet10_Simple\")\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T18:06:05.644656Z","iopub.execute_input":"2025-10-16T18:06:05.644981Z","iopub.status.idle":"2025-10-16T18:06:05.668739Z","shell.execute_reply.started":"2025-10-16T18:06:05.644964Z","shell.execute_reply":"2025-10-16T18:06:05.668146Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CosineAnnealingWithWarmup(Callback):\n    def __init__(self, max_lr=1e-3, min_lr=1e-6, warmup_epochs=5, total_epochs=100):\n        super().__init__()\n        self.max_lr = max_lr\n        self.min_lr = min_lr\n        self.warmup_epochs = warmup_epochs\n        self.total_epochs = total_epochs\n\n    def on_epoch_begin(self, epoch, logs=None):\n        if epoch < self.warmup_epochs:\n            lr = self.max_lr * (epoch + 1) / self.warmup_epochs\n        else:\n            progress = (epoch - self.warmup_epochs) / max(1, (self.total_epochs - self.warmup_epochs))\n            lr = self.min_lr + (self.max_lr - self.min_lr) * 0.5 * (1 + np.cos(np.pi * progress))\n        self.model.optimizer.learning_rate.assign(lr)\n\n    def on_epoch_end(self, epoch, logs=None):\n        logs = logs or {}\n        logs[\"lr\"] = float(self.model.optimizer.learning_rate.numpy())\nprint(\"cosine annealing\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T18:06:05.669357Z","iopub.execute_input":"2025-10-16T18:06:05.669526Z","iopub.status.idle":"2025-10-16T18:06:05.693946Z","shell.execute_reply.started":"2025-10-16T18:06:05.669513Z","shell.execute_reply":"2025-10-16T18:06:05.693379Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = build_model()   # <-- safe to call outside any strategy\nmodel.summary()\n\noptimizer = tf.keras.optimizers.AdamW(learning_rate=1e-3, weight_decay=1e-4)\n\n# labels are one-hot, so use CategoricalCrossentropy with smoothing\nloss = tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.05)\n\nmodel.compile(optimizer=optimizer, loss=loss, metrics=[\"accuracy\"])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T18:06:05.694769Z","iopub.execute_input":"2025-10-16T18:06:05.695332Z","iopub.status.idle":"2025-10-16T18:06:06.345004Z","shell.execute_reply.started":"2025-10-16T18:06:05.695314Z","shell.execute_reply":"2025-10-16T18:06:06.344175Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 100\ncosine = CosineAnnealingWithWarmup(max_lr=1e-3, min_lr=1e-6, warmup_epochs=5, total_epochs=EPOCHS)\n\ncallbacks = [\n    cosine,\n    tf.keras.callbacks.EarlyStopping(monitor=\"val_accuracy\", patience=20, restore_best_weights=True, verbose=1),\n    tf.keras.callbacks.ModelCheckpoint(\"best_model.h5\", monitor=\"val_accuracy\", save_best_only=True, verbose=1),\n]\n\nhistory = model.fit(\n    x_train, y_train,\n    validation_data=(x_val, y_val),\n    epochs=EPOCHS,\n    batch_size=128,\n    callbacks=callbacks,\n    verbose=1,\n)\n\n# ============================================\n# ✅ Save best model weights for Kaggle\n# ============================================\n\n# Save the model checkpoint (weights only)\nmodel.save_weights(\"best_model.weights.h5\")\n\n\nprint(\"✅ Saved best_model.h5 — ready for Kaggle submission\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T18:06:06.345852Z","iopub.execute_input":"2025-10-16T18:06:06.346171Z","iopub.status.idle":"2025-10-16T20:44:51.064125Z","shell.execute_reply.started":"2025-10-16T18:06:06.346153Z","shell.execute_reply":"2025-10-16T20:44:51.063252Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================================================================\n# ✅ Extract CIFAR-10 test images (from test.7z)\n# ================================================================\n!apt install -y libarchive-dev\n!pip install -q libarchive-c py7zr   # ✅ fixed typo: \"libarchiv\" → \"libarchive-c\"\n\nimport os, glob, py7zr\n\ntest_7z_path = \"/kaggle/input/cifar-10/test.7z\"   # adjust if dataset path differs\n\n# Verify file existence\nif not os.path.exists(test_7z_path):\n    raise FileNotFoundError(f\"⚠️ File not found: {test_7z_path}\")\n\n# Extraction directory\nextract_dir = \"./test\"\nos.makedirs(extract_dir, exist_ok=True)\n\nprint(\"📦 Extracting test.7z with py7zr...\")\n\nwith py7zr.SevenZipFile(test_7z_path, mode='r') as archive:\n    archive.extractall(path=extract_dir)\n\nprint(\"✅ Extraction completed.\")\n\n# Quick check\npng_files = glob.glob(os.path.join(extract_dir, \"*.png\"))\nprint(f\"🖼️ Found {len(png_files)} PNG test images in '{extract_dir}'\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T20:44:51.065247Z","iopub.execute_input":"2025-10-16T20:44:51.065557Z","iopub.status.idle":"2025-10-16T20:47:16.237306Z","shell.execute_reply.started":"2025-10-16T20:44:51.065539Z","shell.execute_reply":"2025-10-16T20:47:16.236397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, shutil, glob\n\nsrc = \"./test/test\"\ndst = \"./test\"\n\nif os.path.exists(src):\n    files = glob.glob(os.path.join(src, \"*.png\"))\n    print(f\"📦 Moving {len(files)} files from nested folder to top-level test/ ...\")\n\n    os.makedirs(dst, exist_ok=True)\n    moved = 0\n    for f in files:\n        shutil.move(f, dst)\n        moved += 1\n        if moved % 5000 == 0:\n            print(f\"  Moved {moved} files...\")\n\n    print(f\"✅ Done! Total moved: {moved}\")\n\n    # Try removing now-empty subfolder\n    try:\n        os.rmdir(src)\n        print(\"🧹 Removed empty ./test/test/ folder.\")\n    except Exception as e:\n        print(f\"(info) Could not remove ./test/test/: {e}\")\nelse:\n    print(\"No nested folder found.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T20:47:16.238529Z","iopub.execute_input":"2025-10-16T20:47:16.239133Z","iopub.status.idle":"2025-10-16T20:47:32.337129Z","shell.execute_reply.started":"2025-10-16T20:47:16.239110Z","shell.execute_reply":"2025-10-16T20:47:32.336485Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import glob\nprint(\"✅ Files now in ./test:\", len(glob.glob(\"./test/*.png\")))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T20:47:32.338017Z","iopub.execute_input":"2025-10-16T20:47:32.338374Z","iopub.status.idle":"2025-10-16T20:47:32.895316Z","shell.execute_reply.started":"2025-10-16T20:47:32.338343Z","shell.execute_reply":"2025-10-16T20:47:32.894648Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# FINAL KAGGLE SUBMISSION CELL — /255 normalization only (no mean/std)\n# ============================================================================\nimport os, glob, numpy as np, pandas as pd\nfrom PIL import Image\nimport tensorflow as tf\n\n# Load best weights (Keras 3 naming convention)\nif os.path.exists(\"best_model.h5\"):\n    model.load_weights(\"best_model.weights.h5\")\n    print(\"✅ Loaded best_model.weights.h5\")\nelse:\n    raise FileNotFoundError(\"❌ best_model.weights.h5 not found!\")\n\nmodel.trainable = False\n\n# ============================================================================\n# Load test images and preprocess (normalize only)\n# ============================================================================\ntest_dir = \"test\"\ntest_files = sorted(\n    glob.glob(os.path.join(test_dir, \"*.png\")),\n    key=lambda x: int(os.path.splitext(os.path.basename(x))[0])\n)\nprint(f\"✅ Found {len(test_files)} test images\")\n\ndef load_batch(paths):\n    \"\"\"Load and normalize test images\"\"\"\n    arr = np.zeros((len(paths), 32, 32, 3), dtype=\"float32\")\n    ids = np.zeros((len(paths),), dtype=\"int32\")\n    for i, p in enumerate(paths):\n        img = Image.open(p).convert(\"RGB\")\n        arr[i] = np.asarray(img, dtype=\"float32\") / 255.0   # ✅ Normalize only\n        ids[i] = int(os.path.splitext(os.path.basename(p))[0])\n    return arr, ids\n\n# ============================================================================\n# Predict in batches for efficiency\n# ============================================================================\nall_preds, all_ids = [], []\nB = 5000  # process 5000 images per batch\n\nfor s in range(0, len(test_files), B):\n    batch_files = test_files[s:s+B]\n    xb, ib = load_batch(batch_files)\n    pb = model.predict(xb, batch_size=256, verbose=1)\n    all_preds.append(pb)\n    all_ids.append(ib)\n\npreds = np.concatenate(all_preds, axis=0)\ntest_ids = np.concatenate(all_ids, axis=0)\npred_classes = np.argmax(preds, axis=1)\n\n# ============================================================================\n# Sanity check: prediction distribution\n# ============================================================================\nvals, cnts = np.unique(pred_classes, return_counts=True)\nclass_names = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']\n\nprint(\"\\n📊 Prediction class histogram:\")\nfor v, c in zip(vals, cnts):\n    print(f\"  {class_names[v]:12s}: {c:6d} ({c/len(pred_classes)*100:.1f}%)\")\n\nif len(vals) == 1:\n    print(\"\\n⚠️ WARNING: All predictions collapsed to ONE class!\")\nelif max(cnts) > len(pred_classes) * 0.4:\n    print(f\"\\n⚠️ WARNING: Class '{class_names[vals[np.argmax(cnts)]]}' \"\n          f\"has {max(cnts)/len(pred_classes)*100:.1f}% of predictions\")\n\n# ============================================================================\n# Create submission.csv\n# ============================================================================\n# ============================================================================\n# ✅ Create submission.csv with numeric labels\n# ============================================================================\nsubmission = pd.DataFrame({\n    \"id\": test_ids,\n    \"label\": [class_names[i] for i in pred_classes]\n}).sort_values(\"id\").reset_index(drop=True)\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"\\n✅ submission.csv created successfully!\")\nprint(submission.head(10))\nprint(f\"\\nTotal predictions: {len(submission)}\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T21:01:56.906810Z","iopub.execute_input":"2025-10-16T21:01:56.907577Z","iopub.status.idle":"2025-10-16T21:05:00.889968Z","shell.execute_reply.started":"2025-10-16T21:01:56.907551Z","shell.execute_reply":"2025-10-16T21:05:00.889180Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}