{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## CIFAR-10 Challenge Assignment\n\n### Custom residual ConvNet trained from scratch\nThis notebook uses a custom-designed residual convolutional neural network for CIFAR-10. The architecture is built manually rather than using an off-the-shelf model. All network weights are randomly initialized; no pretrained weights are used.\n\nChallenge requirements implemented:\n- Custom neural network architecture\n- AdamW optimizer\n- Cosine annealing learning-rate schedule with linear warmup\n- Advanced CutOut data augmentation\n- Test-time augmentation for Kaggle prediction","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\n\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\n\nprint(\"TensorFlow version:\", tf.__version__)\nprint(\"GPU available:\", tf.config.list_physical_devices('GPU'))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-10-10T09:18:22.964782Z","iopub.execute_input":"2026-10-10T09:18:22.965516Z","iopub.status.idle":"2026-10-10T09:18:44.20283Z","shell.execute_reply.started":"2026-10-10T09:18:22.965439Z","shell.execute_reply":"2026-10-10T09:18:44.202025Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load the official CIFAR-10 dataset\n(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()\n\nprint(\"Training images:\", x_train.shape)\nprint(\"Training labels:\", y_train.shape)\nprint(\"Test images:\", x_test.shape)\nprint(\"Test labels:\", y_test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T09:18:44.204153Z","iopub.execute_input":"2026-10-10T09:18:44.204872Z","iopub.status.idle":"2026-10-10T09:45:12.836325Z","shell.execute_reply.started":"2026-10-10T09:18:44.204843Z","shell.execute_reply":"2026-10-10T09:45:12.835549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Normalize pixel values from [0, 255] to [0, 1]\nx_train = x_train.astype(\"float32\") / 255.0\nx_test = x_test.astype(\"float32\") / 255.0\n\n\n# Advanced data augmentation: random crop, horizontal flip, and CutOutclass RandomCutout(layers.Layer):\nclass RandomCutout(layers.Layer):\n    def __init__(self, mask_size=8, **kwargs):\n        super().__init__(**kwargs)\n        self.mask_size = mask_size\n\n    def cutout_one(self, image):\n        h = tf.shape(image)[0]\n        w = tf.shape(image)[1]\n\n        cy = tf.random.uniform([], 0, h, dtype=tf.int32)\n        cx = tf.random.uniform([], 0, w, dtype=tf.int32)\n\n        half = self.mask_size // 2\n\n        y1 = tf.maximum(0, cy - half)\n        y2 = tf.minimum(h, cy + half)\n        x1 = tf.maximum(0, cx - half)\n        x2 = tf.minimum(w, cx + half)\n\n        ys = tf.range(h)[:, None]\n        xs = tf.range(w)[None, :]\n\n        inside_y = tf.logical_and(ys >= y1, ys < y2)\n        inside_x = tf.logical_and(xs >= x1, xs < x2)\n\n        mask = tf.logical_and(inside_y, inside_x)\n        mask = 1.0 - tf.cast(mask, image.dtype)\n        mask = mask[..., None]\n\n        return image * mask\n\n    def call(self, images, training=None):\n        if training:\n            return tf.map_fn(\n                self.cutout_one,\n                images,\n                fn_output_signature=images.dtype\n            )\n        return images\n\n\n# CIFAR-10 augmentation:\n# pad -> random crop -> horizontal flip -> CutOut\ndata_augmentation = tf.keras.Sequential([\n    layers.ZeroPadding2D(padding=4),\n    layers.RandomCrop(32, 32),\n    layers.RandomFlip(\"horizontal\"),\n    RandomCutout(mask_size=8)\n], name=\"data_augmentation\")\n\n\nprint(\"Training pixel range:\", x_train.min(), \"to\", x_train.max())\nprint(\"Test pixel range:\", x_test.min(), \"to\", x_test.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T09:45:12.837385Z","iopub.execute_input":"2026-10-10T09:45:12.837799Z","iopub.status.idle":"2026-10-10T09:45:13.952851Z","shell.execute_reply.started":"2026-10-10T09:45:12.837771Z","shell.execute_reply":"2026-10-10T09:45:13.952028Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# Custom residual block designed for this CIFAR-10 model\ndef residual_block(x, filters, stride=1):\n    shortcut = x\n\n    x = layers.Conv2D(\n        filters,\n        3,\n        strides=stride,\n        padding=\"same\",\n        use_bias=False,\n        kernel_initializer=\"he_normal\"\n    )(x)\n\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n\n    x = layers.Conv2D(\n        filters,\n        3,\n        strides=1,\n        padding=\"same\",\n        use_bias=False,\n        kernel_initializer=\"he_normal\"\n    )(x)\n\n    x = layers.BatchNormalization()(x)\n\n    # Match dimensions if needed\n    if stride != 1 or shortcut.shape[-1] != filters:\n        shortcut = layers.Conv2D(\n            filters,\n            1,\n            strides=stride,\n            padding=\"same\",\n            use_bias=False,\n            kernel_initializer=\"he_normal\"\n        )(shortcut)\n\n        shortcut = layers.BatchNormalization()(shortcut)\n\n    x = layers.Add()([x, shortcut])\n    x = layers.ReLU()(x)\n\n    return x\n\n\n# Build custom wider CIFAR-10 residual ConvNet from scratch\ninputs = keras.Input(shape=(32, 32, 3))\n\nx = data_augmentation(inputs)\n\n# Initial convolution\nx = layers.Conv2D(\n    96,\n    3,\n    padding=\"same\",\n    use_bias=False,\n    kernel_initializer=\"he_normal\"\n)(x)\n\nx = layers.BatchNormalization()(x)\nx = layers.ReLU()(x)\n\n\n# Stage 1\nx = residual_block(x, 96)\nx = residual_block(x, 96)\nx = residual_block(x, 96)\n\n\n# Stage 2\nx = residual_block(x, 192, stride=2)\nx = residual_block(x, 192)\nx = residual_block(x, 192)\n\n\n# Stage 3\nx = residual_block(x, 384, stride=2)\nx = residual_block(x, 384)\nx = residual_block(x, 384)\n\n\n# Classification head\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.Dropout(0.1)(x)\n\noutputs = layers.Dense(\n    10,\n    activation=\"softmax\",\n    kernel_initializer=\"he_normal\"\n)(x)\n\n\nmodel = keras.Model(\n    inputs=inputs,\n    outputs=outputs\n)\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T09:45:13.954878Z","iopub.execute_input":"2026-10-10T09:45:13.955124Z","iopub.status.idle":"2026-10-10T09:45:16.308726Z","shell.execute_reply.started":"2026-10-10T09:45:13.9551Z","shell.execute_reply":"2026-10-10T09:45:16.308027Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# AdamW + cosine annealing learning rate + warmup\n\nbatch_size = 128\nepochs = 50\n\ntrain_size = int(len(x_train) * 0.9)\nsteps_per_epoch = int(np.ceil(train_size / batch_size))\n\ntotal_steps = steps_per_epoch * epochs\n\nwarmup_epochs = 5\nwarmup_steps = steps_per_epoch * warmup_epochs\n\n\nclass WarmUpCosine(tf.keras.optimizers.schedules.LearningRateSchedule):\n    def __init__(\n        self,\n        base_lr,\n        total_steps,\n        warmup_steps,\n        min_lr=1e-6,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n\n        self.base_lr = base_lr\n        self.total_steps = total_steps\n        self.warmup_steps = warmup_steps\n        self.min_lr = min_lr\n\n    def __call__(self, step):\n        step = tf.cast(step, tf.float32)\n\n        warmup_steps = tf.cast(self.warmup_steps, tf.float32)\n        total_steps = tf.cast(self.total_steps, tf.float32)\n\n        # Linear warmup\n        warmup_lr = (\n            self.base_lr *\n            step /\n            tf.maximum(1.0, warmup_steps)\n        )\n\n        # Cosine annealing after warmup\n        progress = (\n            (step - warmup_steps) /\n            tf.maximum(1.0, total_steps - warmup_steps)\n        )\n\n        progress = tf.clip_by_value(progress, 0.0, 1.0)\n\n        cosine_decay = 0.5 * (\n            1.0 + tf.cos(np.pi * progress)\n        )\n\n        cosine_lr = (\n            self.min_lr +\n            (self.base_lr - self.min_lr) * cosine_decay\n        )\n\n        return tf.where(\n            step < warmup_steps,\n            warmup_lr,\n            cosine_lr\n        )\n\n    def get_config(self):\n        return {\n            \"base_lr\": self.base_lr,\n            \"total_steps\": self.total_steps,\n            \"warmup_steps\": self.warmup_steps,\n            \"min_lr\": self.min_lr\n        }\n\n\nlr_schedule = WarmUpCosine(\n    base_lr=1e-3,\n    total_steps=total_steps,\n    warmup_steps=warmup_steps,\n    min_lr=1e-6\n)\n\n\noptimizer = tf.keras.optimizers.AdamW(\n    learning_rate=lr_schedule,\n    weight_decay=5e-4,\n    use_ema=True,\n    ema_momentum=0.999\n)\n\n\nmodel.compile(\n    optimizer=optimizer,\n    loss=\"sparse_categorical_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\nprint(\"Model compiled with AdamW + warmup + cosine annealing + EMA.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T09:45:16.309859Z","iopub.execute_input":"2026-10-10T09:45:16.310241Z","iopub.status.idle":"2026-10-10T09:45:16.32993Z","shell.execute_reply.started":"2026-10-10T09:45:16.310216Z","shell.execute_reply":"2026-10-10T09:45:16.329136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training callbacks\n\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_accuracy\",\n        patience=15,\n        restore_best_weights=True,\n        verbose=1\n    ),\n\n    tf.keras.callbacks.ModelCheckpoint(\n        \"best_cifar10_challenge.weights.h5\",\n        monitor=\"val_accuracy\",\n        save_best_only=True,\n        save_weights_only=True,\n        verbose=1\n    )\n]\n\nprint(\"Callbacks ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T09:45:16.331053Z","iopub.execute_input":"2026-10-10T09:45:16.331556Z","iopub.status.idle":"2026-10-10T09:45:16.350005Z","shell.execute_reply.started":"2026-10-10T09:45:16.33152Z","shell.execute_reply":"2026-10-10T09:45:16.34933Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    x_train,\n    y_train,\n    batch_size=batch_size,\n    epochs=epochs,\n    validation_split=0.1,\n    callbacks=callbacks,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T09:45:16.350834Z","iopub.execute_input":"2026-10-10T09:45:16.351184Z","iopub.status.idle":"2026-10-10T11:52:45.101623Z","shell.execute_reply.started":"2026-10-10T09:45:16.351139Z","shell.execute_reply":"2026-10-10T11:52:45.100875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loss, test_accuracy = model.evaluate(\n    x_test,\n    y_test,\n    verbose=1\n)\n\nprint(f\"Test loss: {test_loss:.4f}\")\nprint(f\"Test accuracy: {test_accuracy:.4f}\")\nprint(f\"Test accuracy (%): {test_accuracy * 100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T11:52:45.103006Z","iopub.execute_input":"2026-10-10T11:52:45.103405Z","iopub.status.idle":"2026-10-10T11:52:58.713306Z","shell.execute_reply.started":"2026-10-10T11:52:45.103378Z","shell.execute_reply":"2026-10-10T11:52:58.71256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor root, dirs, files in os.walk(\"/kaggle/input\"):\n    for file in files:\n        if \"test.7z\" in file or \"sampleSubmission.csv\" in file:\n            print(os.path.join(root, file))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T11:52:58.714276Z","iopub.execute_input":"2026-10-10T11:52:58.714649Z","iopub.status.idle":"2026-10-10T11:52:58.744415Z","shell.execute_reply.started":"2026-10-10T11:52:58.714621Z","shell.execute_reply":"2026-10-10T11:52:58.743793Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_path = \"/kaggle/input/competitions/cifar-10/sampleSubmission.csv\"\n\nsample_submission = pd.read_csv(sample_path)\n\nprint(sample_submission.head())\nprint(\"Rows:\", len(sample_submission))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T11:52:58.747189Z","iopub.execute_input":"2026-10-10T11:52:58.747736Z","iopub.status.idle":"2026-10-10T11:52:58.997513Z","shell.execute_reply.started":"2026-10-10T11:52:58.747712Z","shell.execute_reply":"2026-10-10T11:52:58.996396Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"try:\n    import libarchive\n    print(\"libarchive is ready.\")\nexcept ImportError:\n    print(\"libarchive is not installed.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T11:52:58.998706Z","iopub.execute_input":"2026-10-10T11:52:58.999066Z","iopub.status.idle":"2026-10-10T11:52:59.004866Z","shell.execute_reply.started":"2026-10-10T11:52:58.999018Z","shell.execute_reply":"2026-10-10T11:52:59.00389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q libarchive-c","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T11:52:59.005933Z","iopub.execute_input":"2026-10-10T11:52:59.006281Z","iopub.status.idle":"2026-10-10T11:53:04.758233Z","shell.execute_reply.started":"2026-10-10T11:52:59.006243Z","shell.execute_reply":"2026-10-10T11:53:04.757251Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import libarchive\nprint(\"libarchive is ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T11:53:04.759974Z","iopub.execute_input":"2026-10-10T11:53:04.760847Z","iopub.status.idle":"2026-10-10T11:53:04.81562Z","shell.execute_reply.started":"2026-10-10T11:53:04.76081Z","shell.execute_reply":"2026-10-10T11:53:04.814659Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import io\nfrom PIL import Image\n\narchive_path = \"/kaggle/input/competitions/cifar-10/test.7z\"\n\nclass_names = [\n    \"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\",\n    \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"\n]\n\nbatch_size = 512\n\nbatch_images = []\nbatch_ids = []\n\nall_ids = []\nall_predictions = []\n\nprocessed = 0\n\n\nwith libarchive.file_reader(archive_path) as archive:\n    for entry in archive:\n\n        # Only process PNG images\n        if not entry.pathname.lower().endswith(\".png\"):\n            continue\n\n        # Read image directly from the .7z archive\n        image_bytes = b\"\".join(entry.get_blocks())\n\n        image = Image.open(io.BytesIO(image_bytes)).convert(\"RGB\")\n        image_array = np.asarray(\n            image,\n            dtype=np.float32\n        ) / 255.0\n\n        # Example: test/123.png -> ID = 123\n        image_id = int(\n            os.path.splitext(\n                os.path.basename(entry.pathname)\n            )[0]\n        )\n\n        batch_images.append(image_array)\n        batch_ids.append(image_id)\n\n        # Predict when batch is full\n        if len(batch_images) == batch_size:\n\n            batch_array = np.stack(batch_images)\n\n            # Prediction on original images\n            pred_original = model(\n                batch_array,\n                training=False\n            ).numpy()\n\n            # Prediction on horizontally flipped images\n            flipped_batch = tf.image.flip_left_right(\n                batch_array\n            )\n\n            pred_flipped = model(\n                flipped_batch,\n                training=False\n            ).numpy()\n\n            # Test-time augmentation (TTA):\n            # average predictions from the original and horizontally flipped images\n            probabilities = (\n                pred_original + pred_flipped\n            ) / 2.0\n\n            predictions = np.argmax(\n                probabilities,\n                axis=1\n            )\n\n            all_ids.extend(batch_ids)\n            all_predictions.extend(predictions)\n\n            processed += len(batch_images)\n\n            if processed % 10000 == 0:\n                print(\n                    f\"Processed {processed:,} images\"\n                )\n\n            batch_images = []\n            batch_ids = []\n\n\n# Process final incomplete batch\nif len(batch_images) > 0:\n\n    batch_array = np.stack(batch_images)\n\n    pred_original = model(\n        batch_array,\n        training=False\n    ).numpy()\n\n    flipped_batch = tf.image.flip_left_right(\n        batch_array\n    )\n\n    pred_flipped = model(\n        flipped_batch,\n        training=False\n    ).numpy()\n\n    probabilities = (\n        pred_original + pred_flipped\n    ) / 2.0\n\n    predictions = np.argmax(\n        probabilities,\n        axis=1\n    )\n\n    all_ids.extend(batch_ids)\n    all_predictions.extend(predictions)\n\n    processed += len(batch_images)\n\n\nprint(\"Total images processed:\", processed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T11:59:10.265852Z","iopub.execute_input":"2026-10-10T11:59:10.26631Z","iopub.status.idle":"2026-10-10T12:13:16.568793Z","shell.execute_reply.started":"2026-10-10T11:59:10.266279Z","shell.execute_reply":"2026-10-10T12:13:16.567789Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predicted_labels = [class_names[i] for i in all_predictions]\n\npredictions_df = pd.DataFrame({\n    \"id\": all_ids,\n    \"label\": predicted_labels\n})\n\nprint(predictions_df.head())\nprint(\"Predictions:\", len(predictions_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T12:44:40.978664Z","iopub.execute_input":"2026-10-10T12:44:40.979122Z","iopub.status.idle":"2026-10-10T12:44:41.1373Z","shell.execute_reply.started":"2026-10-10T12:44:40.97909Z","shell.execute_reply":"2026-10-10T12:44:41.136341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = sample_submission[[\"id\"]].merge(\n    predictions_df,\n    on=\"id\",\n    how=\"left\"\n)\n\nprint(submission.head())\nprint(\"Rows:\", len(submission))\nprint(\"Missing predictions:\", submission[\"label\"].isna().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T12:44:43.794143Z","iopub.execute_input":"2026-10-10T12:44:43.794609Z","iopub.status.idle":"2026-10-10T12:44:43.893656Z","shell.execute_reply.started":"2026-10-10T12:44:43.794579Z","shell.execute_reply":"2026-10-10T12:44:43.892768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_path = \"/kaggle/working/submission.csv\"\n\nsubmission.to_csv(submission_path, index=False)\n\nprint(\"Saved:\", submission_path)\nprint(\"Shape:\", submission.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-10T12:44:46.59309Z","iopub.execute_input":"2026-10-10T12:44:46.593822Z","iopub.status.idle":"2026-10-10T12:44:46.860561Z","shell.execute_reply.started":"2026-10-10T12:44:46.593791Z","shell.execute_reply":"2026-10-10T12:44:46.859639Z"}},"outputs":[],"execution_count":null}]}