{"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":"https://www.kaggle.com/code/turkiaalmarri/notebook4a0b6ced21","metadata":{}},{"cell_type":"code","source":"\"\"\"\nAssignment 3 - CIFAR-10 Kaggle competition\nhttps://www.kaggle.com/c/cifar-10\n\nModel: ResNet-9 (residual ConvNet), trained from scratch.\nAll weights are randomly initialised - no pre-trained weights are used.\n\nKaggle settings needed:\n  - Input: the CIFAR-10 competition data attached\n  - Accelerator: GPU (T4 or P100)\n  - Internet: ON (to download the official CIFAR-10 training set)\n\"\"\"\n\nimport os\nimport glob\nimport shutil\nimport subprocess\nimport time\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nimport keras\nfrom keras import layers\n\nkeras.utils.set_random_seed(42)\n\n# Mixed precision roughly doubles speed on T4 GPUs\nkeras.mixed_precision.set_global_policy(\"mixed_float16\")\n\nprint(\"TensorFlow\", tf.__version__, \"| Keras\", keras.__version__)\nprint(\"GPUs:\", tf.config.list_physical_devices(\"GPU\"))\n\nCLASSES = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\",\n           \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"]\nAUTOTUNE = tf.data.AUTOTUNE\n\n# ---------------------------------------------------------------\n# 1. Training data: official CIFAR-10 via Keras\n# ---------------------------------------------------------------\n# The Kaggle training set is identical to the official one, and the\n# official test set lets us estimate the Kaggle score before submitting.\n\ntry:\n    (x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()\nexcept Exception as e:\n    raise RuntimeError(\n        \"Could not download CIFAR-10. Turn Internet ON in the notebook's \"\n        \"Session options and run again.\"\n    ) from e\n\ny_train = y_train.ravel()\ny_test = y_test.ravel()\nprint(\"Train:\", x_train.shape, \"Test:\", x_test.shape)\n\n# ---------------------------------------------------------------\n# 2. Data pipeline with augmentation\n# ---------------------------------------------------------------\n# Standard CIFAR augmentation (Lecture 3, slides 43-47):\n#   - pad 4 pixels and take a random 32x32 crop (random shift)\n#   - random horizontal flip\n#   - cutout: blank a random 8x8 square, forcing the network to use\n#     the whole object rather than one tell-tale patch\n\nBATCH = 512\nEPOCHS = 30\n\n\ndef cutout(img, size=8):\n    cy = tf.random.uniform([], 0, 32, dtype=tf.int32)\n    cx = tf.random.uniform([], 0, 32, dtype=tf.int32)\n    yy = tf.range(32)[:, None]\n    xx = tf.range(32)[None, :]\n    inside = ((yy >= cy - size // 2) & (yy < cy + size // 2) &\n              (xx >= cx - size // 2) & (xx < cx + size // 2))\n    mask = tf.cast(tf.logical_not(inside), img.dtype)[..., None]\n    return img * mask\n\n\ndef augment(img, label):\n    img = tf.pad(img, [[4, 4], [4, 4], [0, 0]], mode=\"REFLECT\")\n    img = tf.image.random_crop(img, [32, 32, 3])\n    img = tf.image.random_flip_left_right(img)\n    img = cutout(img)\n    return tf.cast(img, tf.float32), tf.one_hot(label, 10)\n\n\ndef to_eval(img, label):\n    return tf.cast(img, tf.float32), tf.one_hot(label, 10)\n\n\ntrain_ds = (tf.data.Dataset.from_tensor_slices((x_train, y_train))\n            .shuffle(50000, reshuffle_each_iteration=True)\n            .map(augment, num_parallel_calls=AUTOTUNE)\n            .batch(BATCH, drop_remainder=True)\n            .prefetch(AUTOTUNE))\n\nval_ds = (tf.data.Dataset.from_tensor_slices((x_test, y_test))\n          .map(to_eval, num_parallel_calls=AUTOTUNE)\n          .batch(1000)\n          .prefetch(AUTOTUNE))\n\n# ---------------------------------------------------------------\n# 3. Model: ResNet-9\n# ---------------------------------------------------------------\n# VGG-style blocks of 3x3 conv + BatchNorm + ReLU, with max-pooling to\n# halve the image and double the channels (64 -> 128 -> 256 -> 512),\n# plus two residual blocks (Lecture 3, slides 77-80) so the extra\n# layers can learn \"x + 0\" easily. Global max-pooling replaces the big\n# fully connected layers of AlexNet/VGG.\n\nMEAN = np.array([0.4914, 0.4822, 0.4465])\nSTD = np.array([0.2470, 0.2435, 0.2616])\nREG = keras.regularizers.L2(2.5e-4)   # = weight decay 5e-4\n\n\ndef conv_bn(x, filters, pool=False):\n    x = layers.Conv2D(filters, 3, padding=\"same\", use_bias=False,\n                      kernel_regularizer=REG)(x)\n    x = layers.BatchNormalization(momentum=0.9, epsilon=1e-5)(x)\n    x = layers.ReLU()(x)\n    if pool:\n        x = layers.MaxPooling2D(2)(x)\n    return x\n\n\ndef residual(x, filters):\n    y = conv_bn(x, filters)\n    y = conv_bn(y, filters)\n    return layers.Add()([x, y])\n\n\ninputs = keras.Input(shape=(32, 32, 3))\nx = layers.Rescaling(1.0 / 255)(inputs)\nx = layers.Normalization(mean=MEAN, variance=STD ** 2)(x)\n\nx = conv_bn(x, 64)                    # 32x32\nx = conv_bn(x, 128, pool=True)        # 16x16\nx = residual(x, 128)\nx = conv_bn(x, 256, pool=True)        # 8x8\nx = conv_bn(x, 512, pool=True)        # 4x4\nx = residual(x, 512)\n\nx = layers.GlobalMaxPooling2D()(x)\nx = layers.Dense(10, use_bias=False, kernel_regularizer=REG)(x)\nx = layers.Rescaling(0.125)(x)        # tame the logits for stable training\noutputs = layers.Activation(\"softmax\", dtype=\"float32\")(x)\n\nmodel = keras.Model(inputs, outputs, name=\"resnet9\")\nmodel.summary()\n\n# ---------------------------------------------------------------\n# 4. Optimiser: SGD + one-cycle learning rate\n# ---------------------------------------------------------------\n# LR climbs linearly to its peak over the first 20% of training, then\n# falls linearly to zero. This lets us use a high LR for fast progress\n# and still finish with a fine-tuned model.\n\n\nclass OneCycle(keras.optimizers.schedules.LearningRateSchedule):\n    def __init__(self, peak, total_steps, warm_frac=0.2):\n        self.peak = peak\n        self.total_steps = total_steps\n        self.warm_frac = warm_frac\n\n    def __call__(self, step):\n        step = keras.ops.cast(step, \"float32\")\n        warm = self.total_steps * self.warm_frac\n        up = self.peak * step / warm\n        down = self.peak * keras.ops.maximum(\n            0.0, (self.total_steps - step) / (self.total_steps - warm))\n        return keras.ops.where(step < warm, up, down)\n\n    def get_config(self):\n        return {\"peak\": self.peak, \"total_steps\": self.total_steps,\n                \"warm_frac\": self.warm_frac}\n\n\nsteps_per_epoch = len(x_train) // BATCH\nschedule = OneCycle(peak=0.4, total_steps=EPOCHS * steps_per_epoch)\n\nmodel.compile(\n    optimizer=keras.optimizers.SGD(learning_rate=schedule,\n                                   momentum=0.9, nesterov=True),\n    loss=keras.losses.CategoricalCrossentropy(label_smoothing=0.2),\n    metrics=[\"accuracy\"],\n)\n\n# ---------------------------------------------------------------\n# 5. Train\n# ---------------------------------------------------------------\nt0 = time.time()\nhistory = model.fit(train_ds, validation_data=val_ds,\n                    epochs=EPOCHS, verbose=2)\ntrain_minutes = (time.time() - t0) / 60\nprint(f\"\\nTraining time: {train_minutes:.1f} minutes\")\n\n# ---------------------------------------------------------------\n# 6. Evaluate on the official test set (estimates the Kaggle score)\n# ---------------------------------------------------------------\n# Test-time augmentation: average the predictions for each image and\n# its mirror image. Usually worth a few tenths of a percent.\n\n\ndef tta_predict(img_ds):\n    p = model.predict(img_ds, verbose=0)\n    p += model.predict(img_ds.map(tf.image.flip_left_right), verbose=0)\n    return p / 2\n\n\ntest_img_ds = (tf.data.Dataset.from_tensor_slices(x_test)\n               .map(lambda im: tf.cast(im, tf.float32))\n               .batch(1000))\n\ntest_probs = tta_predict(test_img_ds)\ntest_acc = (test_probs.argmax(axis=1) == y_test).mean()\nprint(f\"Official CIFAR-10 test accuracy (with TTA): {test_acc * 100:.2f}%\")\nprint(\"-> expected Kaggle score is about\", round(test_acc, 4))\n\n# ---------------------------------------------------------------\n# 7. Extract the Kaggle test set (300,000 PNGs)\n# ---------------------------------------------------------------\n# Extract to /tmp, NOT /kaggle/working - anything in working gets\n# saved as notebook output, and 300k files would bloat the save.\n\narchives = glob.glob(\"/kaggle/input/**/test.7z\", recursive=True)\nif not archives:\n    raise FileNotFoundError(\n        \"test.7z not found. Attach the CIFAR-10 competition data via \"\n        \"Add Input -> Competitions (join the competition first).\")\nCOMP_DIR = os.path.dirname(archives[0])\nEXTRACT_DIR = \"/tmp/cifar_kaggle\"\n\nif not os.path.isdir(os.path.join(EXTRACT_DIR, \"test\")):\n    os.makedirs(EXTRACT_DIR, exist_ok=True)\n    t0 = time.time()\n    if shutil.which(\"7z\") is None:\n        subprocess.run(\"apt-get -qq update && apt-get -qq install -y p7zip-full\",\n                       shell=True, stdout=subprocess.DEVNULL)\n    if shutil.which(\"7z\"):\n        subprocess.run([\"7z\", \"x\", \"-y\", \"-bso0\", \"-bsp0\",\n                        archives[0], f\"-o{EXTRACT_DIR}\"], check=True)\n    else:\n        subprocess.run(\"pip install -q py7zr\", shell=True, check=True)\n        import py7zr\n        with py7zr.SevenZipFile(archives[0], mode=\"r\") as z:\n            z.extractall(EXTRACT_DIR)\n    print(f\"Extracted in {time.time() - t0:.0f}s\")\n\nIMG_DIR = os.path.join(EXTRACT_DIR, \"test\")\nprint(\"Kaggle test images:\", len(os.listdir(IMG_DIR)))\n\n# ---------------------------------------------------------------\n# 8. Predict on the Kaggle test set and write submission.csv\n# ---------------------------------------------------------------\nsub = pd.read_csv(os.path.join(COMP_DIR, \"sampleSubmission.csv\"))\npaths = [os.path.join(IMG_DIR, f\"{i}.png\") for i in sub[\"id\"]]\n\n\ndef load_png(path):\n    img = tf.io.decode_png(tf.io.read_file(path), channels=3)\n    img.set_shape([32, 32, 3])\n    return tf.cast(img, tf.float32)\n\n\nkaggle_ds = (tf.data.Dataset.from_tensor_slices(paths)\n             .map(load_png, num_parallel_calls=AUTOTUNE)\n             .batch(2000)\n             .prefetch(AUTOTUNE))\n\nt0 = time.time()\nkaggle_probs = tta_predict(kaggle_ds)\nprint(f\"Predicted {len(paths)} images in {time.time() - t0:.0f}s\")\n\nsub[\"label\"] = [CLASSES[i] for i in kaggle_probs.argmax(axis=1)]\nsub.to_csv(\"/kaggle/working/submission.csv\", index=False)\nprint(sub.head())\nprint(\"\\nsubmission.csv written to /kaggle/working/\")\n\n# ---------------------------------------------------------------\n# 9. Training curves\n# ---------------------------------------------------------------\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))\nax1.plot(history.history[\"accuracy\"], label=\"train\")\nax1.plot(history.history[\"val_accuracy\"], label=\"test\")\nax1.set_xlabel(\"epoch\")\nax1.set_ylabel(\"accuracy\")\nax1.set_title(\"Accuracy\")\nax1.legend()\nax2.plot(history.history[\"loss\"], label=\"train\")\nax2.plot(history.history[\"val_loss\"], label=\"test\")\nax2.set_xlabel(\"epoch\")\nax2.set_ylabel(\"loss\")\nax2.set_title(\"Loss\")\nax2.legend()\nplt.tight_layout()\nplt.show()\n\nprint(f\"\\nSummary: ResNet-9, {model.count_params():,} parameters, \"\n      f\"{EPOCHS} epochs, {train_minutes:.1f} min training, \"\n      f\"official test accuracy {test_acc * 100:.2f}%\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T06:18:53.275792Z","iopub.execute_input":"2026-09-21T06:18:53.275965Z"}},"outputs":[],"execution_count":null}]}