{"metadata":{"kernelspec":{"display_name":"kaggle","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.13.5"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"df4ea6ff","cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{},"outputs":[],"execution_count":null},{"id":"49170dbb","cell_type":"code","source":"train_data = pd.read_csv(\"/kaggle/input/digit-recognizer/train.csv\")\ntest_data = pd.read_csv(\"/kaggle/input/digit-recognizer/test.csv\")","metadata":{},"outputs":[],"execution_count":null},{"id":"9fd751bd","cell_type":"markdown","source":"## Data preparation\n\nThe dataset is a simple csv file, each column contains a pixel value from 0 to 255, while the first column contains the label.\nThis section will cover the dataset creation, which is performed simply by reshaping the row into a 28x28 tensor and casting everything to tf data types.","metadata":{}},{"id":"0bf8d856","cell_type":"code","source":"import tensorflow as tf\nfrom sklearn.model_selection import train_test_split\n\ndef preprocess_row(row, train: bool = True):\n    if train:\n        image = tf.reshape(row[1:], (28, 28, 1))\n        image = tf.cast(image, tf.float32)\n        label = row[0]\n        label = tf.cast(label, tf.int8)\n        return image, label\n    else:\n        image = tf.reshape(row, (28, 28, 1))\n        image = tf.cast(image, tf.float32)\n        return image\n\ndef dataframe_to_dataset(df, train: bool = True):\n    data = df.to_numpy()\n    if train:\n        train_data_np, test_data_np = train_test_split(data, test_size=0.2, random_state=42)\n        train_dataset = tf.data.Dataset.from_tensor_slices(train_data_np)\n        train_dataset = train_dataset.map(lambda row: preprocess_row(row))\n        test_dataset = tf.data.Dataset.from_tensor_slices(test_data_np)\n        test_dataset = test_dataset.map(lambda row: preprocess_row(row))\n        return train_dataset, test_dataset\n    else:\n        test_dataset = tf.data.Dataset.from_tensor_slices(data)\n        test_dataset = test_dataset.map(lambda row: preprocess_row(row, train=train))\n        return test_dataset\n\nbatch_size = 32\ntrain_dataset, validation_dataset = dataframe_to_dataset(train_data)\ntrain_dataset = train_dataset.shuffle(buffer_size=10000).batch(batch_size).prefetch(tf.data.AUTOTUNE)\nvalidation_dataset = validation_dataset.shuffle(buffer_size=10000).batch(batch_size).prefetch(tf.data.AUTOTUNE)","metadata":{},"outputs":[],"execution_count":null},{"id":"2ce27d8f","cell_type":"markdown","source":"## Model building\n\nThe model is pretty simple as well, I'll focus first on the preprocessing part, which contains:\n\n- rescaling layer: to bring everything in 0 to 1 scale\n- optionally data augmentation made by flipping and rotating","metadata":{}},{"id":"9a89e741","cell_type":"code","source":"class DataAugmentation(tf.keras.layers.Layer):\n    def __init__(self):\n        super().__init__()\n        self.augment = tf.keras.Sequential([\n            tf.keras.layers.RandomFlip(\"horizontal\"),\n            tf.keras.layers.RandomRotation(0.1),\n        ])\n\n    def build(self, input_shape):\n        self.augment.build(input_shape)\n        super().build(input_shape)\n\n    def call(self, x, training=False):\n        if training:\n            return self.augment(x, training=training)\n        return x\n\npreprocessing_with_augmentation = tf.keras.Sequential([\n    tf.keras.layers.Rescaling(1./255),\n    DataAugmentation()\n])\n\npreprocessing_without_augmentation = tf.keras.Sequential([\n    tf.keras.layers.Rescaling(1./255)\n])","metadata":{},"outputs":[],"execution_count":null},{"id":"ce617b88","cell_type":"markdown","source":"The model itself is made of two convolution laters that are pooled, a flattening layer and a final dense layer.\nI will compare the training curves obtained by using data augmentation and not using it.\nUsing a small number of epochs (10) it is clear that the model without data augmentation reaches the best accuracy in a few iterations and then does not improve anymore. On the contrary, using data augmentation the model starts at a lower accuracy but seems to keep improving.\nHowever, the performance for the model without data augmentation is still better, and 10 epochs already obtains satisfying performance.","metadata":{}},{"id":"13e38f16","cell_type":"code","source":"model = tf.keras.Sequential([\n    tf.keras.layers.Input(shape=(28, 28, 1)),  # grayscale input\n    preprocessing_without_augmentation,\n    tf.keras.layers.Conv2D(32, kernel_size=3, activation='relu'),\n    tf.keras.layers.MaxPooling2D(),\n    tf.keras.layers.Conv2D(64, kernel_size=3, activation='relu'),\n    tf.keras.layers.MaxPooling2D(),\n    tf.keras.layers.Flatten(),\n    tf.keras.layers.Dense(10, activation='softmax')\n])\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(0.001),\n    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),\n    metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],\n)\n\nhistory = model.fit(\n    train_dataset,\n    epochs=10,\n    validation_data=validation_dataset,\n)","metadata":{},"outputs":[],"execution_count":null},{"id":"2d681784","cell_type":"code","source":"def plot_metrics(history):\n    import matplotlib.pyplot as plt\n\n    acc = history.history['sparse_categorical_accuracy']\n    val_acc = history.history['val_sparse_categorical_accuracy']\n    loss = history.history['loss']\n    val_loss = history.history['val_loss']\n    epochs_range = range(1, len(acc) + 1)\n\n    plt.figure(figsize=(12, 5))\n\n    plt.subplot(1, 2, 1)\n    plt.plot(epochs_range, acc, label='Training Accuracy')\n    plt.plot(epochs_range, val_acc, label='Validation Accuracy')\n    plt.title('Accuracy')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy')\n    plt.ylim(0.8, 1)\n    plt.legend()\n\n    plt.subplot(1, 2, 2)\n    plt.plot(epochs_range, loss, label='Training Loss')\n    plt.plot(epochs_range, val_loss, label='Validation Loss')\n    plt.title('Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.ylim(0, 0.5)\n    plt.legend()\n\n    plt.tight_layout()\n    plt.show()","metadata":{},"outputs":[],"execution_count":null},{"id":"488a035e","cell_type":"code","source":"plot_metrics(history)","metadata":{},"outputs":[],"execution_count":null},{"id":"5ad45090","cell_type":"code","source":"test_dataset = dataframe_to_dataset(test_data, train=False)\ntest_dataset = test_dataset.batch(batch_size).prefetch(tf.data.AUTOTUNE)\npredicted_classes = np.argmax(model.predict(test_dataset), axis = 1)","metadata":{},"outputs":[],"execution_count":null},{"id":"37081662","cell_type":"code","source":"import matplotlib.pyplot as plt\n\nnum_samples = 9\nplt.figure(figsize=(10, 10))\n\nfor idx in range(num_samples):\n    plt.subplot(3, 3, idx+1)\n    image = test_data.iloc[idx].to_numpy().reshape(28,28)\n    label = predicted_classes[idx]\n    \n    plt.imshow(image, cmap=\"gray\")\n    plt.title(f\"Predicted: {label}\")\n    plt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{},"outputs":[],"execution_count":null},{"id":"958c828f","cell_type":"code","source":"submission_df = pd.DataFrame({\n    \"ImageId\": list(range(1, len(predicted_classes) + 1)),\n    \"Label\": predicted_classes\n})\n\nsubmission_df.to_csv(\"submission.csv\", index=False)","metadata":{},"outputs":[],"execution_count":null}]}