{"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":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":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T17:22:52.120409Z","iopub.execute_input":"2025-10-16T17:22:52.120738Z","iopub.status.idle":"2025-10-16T17:23:06.862217Z","shell.execute_reply.started":"2025-10-16T17:22:52.120713Z","shell.execute_reply":"2025-10-16T17:23:06.861407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# LOAD & PREPARE DATA — per-channel standardization (better than /255 alone)\n# ============================================================================\n(x_train, y_train), (x_val, y_val) = tf.keras.datasets.cifar10.load_data()\n\n# To float in [0,1]\nx_train = x_train.astype(\"float32\") / 255.0\nx_val   = x_val.astype(\"float32\") / 255.0\n\n# CIFAR-10 train set stats\nMEAN = tf.constant([0.4914, 0.4822, 0.4465], dtype=tf.float32)\nSTD  = tf.constant([0.2470, 0.2435, 0.2616], dtype=tf.float32)\n\ndef standardize(imgs):\n    return (imgs - MEAN) / STD\n\n# Standardize\nx_train = standardize(x_train)\nx_val   = standardize(x_val)\n\n# One-hot labels (so we can use label smoothing)\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T17:23:06.864018Z","iopub.execute_input":"2025-10-16T17:23:06.864527Z","iopub.status.idle":"2025-10-16T17:23:36.701996Z","shell.execute_reply.started":"2025-10-16T17:23:06.864508Z","shell.execute_reply":"2025-10-16T17:23:36.701171Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================== MixUp pipeline (dtype-safe) ==================\nAUTO = tf.data.AUTOTUNE\nBATCH = 128\n\ndef mixup_batch(x, y, alpha=0.2):\n    # Ensure x,y are float32 inside the graph\n    x = tf.cast(x, tf.float32)\n    y = tf.cast(y, tf.float32)\n\n    # lam ~ Beta(alpha, alpha) via Gamma; match dtype of x\n    lam = tf.random.gamma(shape=[1], alpha=alpha, dtype=x.dtype)\n    lam = tf.squeeze(tf.minimum(lam, 1.0 - lam))  # symmetric Beta\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 = (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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T17:23:36.704865Z","iopub.execute_input":"2025-10-16T17:23:36.705171Z","iopub.status.idle":"2025-10-16T17:23:38.896208Z","shell.execute_reply.started":"2025-10-16T17:23:36.705146Z","shell.execute_reply":"2025-10-16T17:23:38.895624Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AdvancedAugmentation(layers.Layer):\n    '''\n    Applying various different augementation techniques\n    \n    '''\n    \n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.flip = layers.RandomFlip(\"horizontal\")\n        self.rotation = layers.RandomRotation(0.10)          # was 0.15\n        self.zoom = layers.RandomZoom(0.10)                  # was 0.15\n        self.translation = layers.RandomTranslation(0.10, 0.10)  # was 0.15,0.15\n        self.contrast = layers.RandomContrast(0.15)          # was 0.2\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(hole, [[h, 32-h-mask_size],[w, 32-w-mask_size],[0,0]], constant_values=1.0)\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\nprint(\"Aug done\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T17:23:38.896983Z","iopub.execute_input":"2025-10-16T17:23:38.897199Z","iopub.status.idle":"2025-10-16T17:23:38.904707Z","shell.execute_reply.started":"2025-10-16T17:23:38.897183Z","shell.execute_reply":"2025-10-16T17:23:38.903924Z"}},"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-16T17:23:38.905676Z","iopub.execute_input":"2025-10-16T17:23:38.906056Z","iopub.status.idle":"2025-10-16T17:23:39.602996Z","shell.execute_reply.started":"2025-10-16T17:23:38.906035Z","shell.execute_reply":"2025-10-16T17:23:39.602319Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Try 2","metadata":{}},{"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    x = layers.Conv2D(32, 3, strides=1, padding=\"same\", use_bias=False)(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation(\"relu\")(x)\n    x = layers.Conv2D(64, 3, strides=1, padding=\"same\", use_bias=False)(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation(\"relu\")(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    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\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: 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 blocks\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    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    # ---- Head ----\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.5)(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\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T17:23:39.604993Z","iopub.execute_input":"2025-10-16T17:23:39.605182Z","iopub.status.idle":"2025-10-16T17:23:39.623197Z","shell.execute_reply.started":"2025-10-16T17:23:39.605167Z","shell.execute_reply":"2025-10-16T17:23:39.622589Z"}},"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-16T17:23:39.623784Z","iopub.execute_input":"2025-10-16T17:23:39.623991Z","iopub.status.idle":"2025-10-16T17:23:39.642046Z","shell.execute_reply.started":"2025-10-16T17:23:39.623974Z","shell.execute_reply":"2025-10-16T17:23:39.641335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = 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-16T17:23:39.642804Z","iopub.execute_input":"2025-10-16T17:23:39.643144Z","iopub.status.idle":"2025-10-16T17:23:39.890902Z","shell.execute_reply.started":"2025-10-16T17:23:39.643122Z","shell.execute_reply":"2025-10-16T17:23:39.889845Z"}},"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-16T17:23:39.891356Z","iopub.status.idle":"2025-10-16T17:23:39.891566Z","shell.execute_reply.started":"2025-10-16T17:23:39.891465Z","shell.execute_reply":"2025-10-16T17:23:39.891474Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!apt install libarchive-dev\n!pip install libarchiv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T17:23:39.892300Z","iopub.status.idle":"2025-10-16T17:23:39.892580Z","shell.execute_reply.started":"2025-10-16T17:23:39.892412Z","shell.execute_reply":"2025-10-16T17:23:39.892426Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install py7zr\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T17:23:39.893662Z","iopub.status.idle":"2025-10-16T17:23:39.893992Z","shell.execute_reply.started":"2025-10-16T17:23:39.893816Z","shell.execute_reply":"2025-10-16T17:23:39.893830Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob, py7zr\n\ntest_7z_path = \"/kaggle/input/cifar-10/test.7z\"   # ✅ adjust path if needed\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T17:23:39.894725Z","iopub.status.idle":"2025-10-16T17:23:39.895050Z","shell.execute_reply.started":"2025-10-16T17:23:39.894892Z","shell.execute_reply":"2025-10-16T17:23:39.894908Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- FIXED VERSION: Apply same standardization as training ---\nimport os, glob, numpy as np, pandas as pd\nfrom PIL import Image\nimport tensorflow as tf\n\n# Load best weights\nif os.path.exists(\"best_model.h5\"):\n    model.load_weights(\"best_model.h5\")\n    print(\"✅ Loaded best_model.h5\")\n\nmodel.trainable = False\n\n# ⚠️ CRITICAL: Use SAME preprocessing as training!\nMEAN = tf.constant([0.4914, 0.4822, 0.4465], dtype=tf.float32)\nSTD = tf.constant([0.2470, 0.2435, 0.2616], dtype=tf.float32)\n\ndef standardize(imgs):\n    \"\"\"Apply EXACT same standardization as training data\"\"\"\n    return (imgs - MEAN) / STD\n\n# Collect test files\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\n# Load and preprocess correctly\ndef load_batch(paths):\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  # First normalize to [0,1]\n    \n    # ✅ NOW apply standardization (CRITICAL!)\n    arr = standardize(tf.convert_to_tensor(arr)).numpy()\n\n    \n    for i, p in enumerate(paths):\n        ids[i] = int(os.path.splitext(os.path.basename(p))[0])\n    \n    return arr, ids\n\n# Process in batches\nall_preds = []\nall_ids = []\nB = 5000  # batch of filenames\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# Sanity check\nvals, cnts = np.unique(pred_classes, return_counts=True)\nprint(\"\\n📊 Prediction class histogram:\")\nclass_names = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']\nfor v, c in zip(vals, cnts):\n    print(f\"  {class_names[v]:12s}: {c:6d} ({c/len(pred_classes)*100:.1f}%)\")\n\n# Warning for suspicious distributions\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)]]}' has {max(cnts)/len(pred_classes)*100:.1f}% of predictions\")\n\n# ============================================================================\n# ✅ Create submission.csv with numeric labels (NOT names)\n# ============================================================================\nsubmission = pd.DataFrame({\n    \"id\": test_ids,\n    \"label\": pred_classes.astype(int)   # ✅ numeric classes 0–9\n}).sort_values(\"id\").reset_index(drop=True)\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"✅ submission.csv created successfully!\")\nprint(submission.head(10))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-16T17:23:39.895920Z","iopub.status.idle":"2025-10-16T17:23:39.896138Z","shell.execute_reply.started":"2025-10-16T17:23:39.896040Z","shell.execute_reply":"2025-10-16T17:23:39.896050Z"}},"outputs":[],"execution_count":null}]}