{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":3004,"databundleVersionId":861823}],"dockerImageVersionId":31286,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"6cda994b-dc0c-48c9-9065-8c80ddc1f4a7","cell_type":"markdown","source":"# MNIST Classification Competition - TensorFlow/Keras\n\nPerceptron, MLP, and LeNet-style CNN in Kaggle-ready format.","metadata":{}},{"id":"64dd1969-b39b-4c62-8b59-d0a76a9ecade","cell_type":"code","source":"import os\nimport random\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, precision_recall_fscore_support, confusion_matrix\n\nSEED = 42\nos.makedirs(\"mnist_outputs\", exist_ok=True)\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\nprint(tf.__version__)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T16:52:37.586523Z","iopub.execute_input":"2026-03-17T16:52:37.586774Z","iopub.status.idle":"2026-03-17T16:53:14.731851Z","shell.execute_reply.started":"2026-03-17T16:52:37.586748Z","shell.execute_reply":"2026-03-17T16:53:14.730614Z"}},"outputs":[],"execution_count":null},{"id":"7a92c817-0785-4996-94e8-f0f56a632846","cell_type":"code","source":"(x_train_full, y_train_full), (x_test, y_test) = tf.keras.datasets.mnist.load_data()\n\nx_train_full = x_train_full.astype(\"float32\") / 255.0\nx_test = x_test.astype(\"float32\") / 255.0\n\nx_train, x_val, y_train, y_val = train_test_split(\n    x_train_full,\n    y_train_full,\n    test_size=0.1,\n    random_state=SEED,\n    stratify=y_train_full\n)\n\nprint(\"Train:\", x_train.shape, y_train.shape)\nprint(\"Validation:\", x_val.shape, y_val.shape)\nprint(\"Test:\", x_test.shape, y_test.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T16:59:33.527375Z","iopub.execute_input":"2026-03-17T16:59:33.527812Z"}},"outputs":[],"execution_count":null},{"id":"5afb1f40-686b-4c30-a608-dc2a8cb9e485","cell_type":"code","source":"plt.figure(figsize=(10, 4))\nfor i in range(10):\n    plt.subplot(2, 5, i + 1)\n    plt.imshow(x_train[i], cmap=\"gray\")\n    plt.title(f\"Label: {y_train[i]}\")\n    plt.axis(\"off\")\nplt.tight_layout()\nplt.savefig(\"mnist_outputs/sample_digits.png\", dpi=200, bbox_inches=\"tight\")\nplt.show()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"114d9007-b54b-4adf-ae66-09b67801f98b","cell_type":"code","source":"def build_perceptron():\n    model = tf.keras.Sequential([\n        tf.keras.layers.Input(shape=(28, 28)),\n        tf.keras.layers.Flatten(),\n        tf.keras.layers.Dense(10, activation=\"softmax\")\n    ])\n    return model\n\n\ndef build_mlp():\n    model = tf.keras.Sequential([\n        tf.keras.layers.Input(shape=(28, 28)),\n        tf.keras.layers.Flatten(),\n        tf.keras.layers.Dense(256, activation=\"relu\"),\n        tf.keras.layers.Dropout(0.2),\n        tf.keras.layers.Dense(128, activation=\"relu\"),\n        tf.keras.layers.Dropout(0.2),\n        tf.keras.layers.Dense(10, activation=\"softmax\")\n    ])\n    return model\n\n\ndef build_lenet():\n    model = tf.keras.Sequential([\n        tf.keras.layers.Input(shape=(28, 28, 1)),\n        tf.keras.layers.Conv2D(6, kernel_size=5, activation=\"tanh\", padding=\"same\"),\n        tf.keras.layers.AveragePooling2D(pool_size=2),\n        tf.keras.layers.Conv2D(16, kernel_size=5, activation=\"tanh\"),\n        tf.keras.layers.AveragePooling2D(pool_size=2),\n        tf.keras.layers.Flatten(),\n        tf.keras.layers.Dense(120, activation=\"tanh\"),\n        tf.keras.layers.Dense(84, activation=\"tanh\"),\n        tf.keras.layers.Dense(10, activation=\"softmax\")\n    ])\n    return model\n","metadata":{},"outputs":[],"execution_count":null},{"id":"dcf05e1b-5eb1-4594-8910-2c7026830cf6","cell_type":"code","source":"def compile_model(model, lr=0.001):\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=lr),\n        loss=\"sparse_categorical_crossentropy\",\n        metrics=[\"accuracy\"]\n    )\n    return model\n\n\ndef plot_history(history, model_name):\n    epochs = range(1, len(history.history[\"loss\"]) + 1)\n\n    plt.figure(figsize=(8, 5))\n    plt.plot(epochs, history.history[\"loss\"], label=\"Train Loss\")\n    plt.plot(epochs, history.history[\"val_loss\"], label=\"Validation Loss\")\n    plt.xlabel(\"Epoch\")\n    plt.ylabel(\"Loss\")\n    plt.title(f\"{model_name} Loss Curve\")\n    plt.legend()\n    plt.tight_layout()\n    plt.savefig(f\"mnist_outputs/{model_name.lower()}_loss.png\", dpi=200, bbox_inches=\"tight\")\n    plt.show()\n\n    plt.figure(figsize=(8, 5))\n    plt.plot(epochs, history.history[\"accuracy\"], label=\"Train Accuracy\")\n    plt.plot(epochs, history.history[\"val_accuracy\"], label=\"Validation Accuracy\")\n    plt.xlabel(\"Epoch\")\n    plt.ylabel(\"Accuracy\")\n    plt.title(f\"{model_name} Accuracy Curve\")\n    plt.legend()\n    plt.tight_layout()\n    plt.savefig(f\"mnist_outputs/{model_name.lower()}_accuracy.png\", dpi=200, bbox_inches=\"tight\")\n    plt.show()\n\n\ndef evaluate_model(model, x_test_input, y_test_input, model_name):\n    y_prob = model.predict(x_test_input, verbose=0)\n    y_pred = np.argmax(y_prob, axis=1)\n    acc = accuracy_score(y_test_input, y_pred)\n    precision, recall, f1, _ = precision_recall_fscore_support(\n        y_test_input,\n        y_pred,\n        average=\"weighted\",\n        zero_division=0\n    )\n    cm = confusion_matrix(y_test_input, y_pred)\n    return {\n        \"Model\": model_name,\n        \"Accuracy\": acc,\n        \"Precision\": precision,\n        \"Recall\": recall,\n        \"F1 Score\": f1,\n        \"Confusion Matrix\": cm\n    }\n","metadata":{},"outputs":[],"execution_count":null},{"id":"4d11107f-ecb3-4ec9-bd7a-c1a0317a9f10","cell_type":"code","source":"early_stop = tf.keras.callbacks.EarlyStopping(\n    monitor=\"val_loss\",\n    patience=3,\n    restore_best_weights=True\n)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"308269a9-7782-4f77-902d-bfbc6dbb03c7","cell_type":"code","source":"perceptron = compile_model(build_perceptron(), lr=0.001)\nperceptron.summary()\n\nhistory_perceptron = perceptron.fit(\n    x_train,\n    y_train,\n    validation_data=(x_val, y_val),\n    epochs=15,\n    batch_size=128,\n    callbacks=[early_stop],\n    verbose=1\n)\n\nplot_history(history_perceptron, \"Perceptron\")\nresult_perceptron = evaluate_model(perceptron, x_test, y_test, \"Perceptron\")\nprint(result_perceptron)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"66a5ec73-51fc-4afa-ae12-8e6dcdfcb2be","cell_type":"code","source":"mlp = compile_model(build_mlp(), lr=0.001)\nmlp.summary()\n\nhistory_mlp = mlp.fit(\n    x_train,\n    y_train,\n    validation_data=(x_val, y_val),\n    epochs=20,\n    batch_size=128,\n    callbacks=[early_stop],\n    verbose=1\n)\n\nplot_history(history_mlp, \"MLP\")\nresult_mlp = evaluate_model(mlp, x_test, y_test, \"MLP\")\nprint(result_mlp)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"4a24b4a2-27c0-4088-bd58-66b33aa39b87","cell_type":"code","source":"x_train_cnn = np.expand_dims(x_train, axis=-1)\nx_val_cnn = np.expand_dims(x_val, axis=-1)\nx_test_cnn = np.expand_dims(x_test, axis=-1)\n\nlenet = compile_model(build_lenet(), lr=0.001)\nlenet.summary()\n\nhistory_lenet = lenet.fit(\n    x_train_cnn,\n    y_train,\n    validation_data=(x_val_cnn, y_val),\n    epochs=20,\n    batch_size=128,\n    callbacks=[early_stop],\n    verbose=1\n)\n\nplot_history(history_lenet, \"LeNet\")\nresult_lenet = evaluate_model(lenet, x_test_cnn, y_test, \"LeNet\")\nprint(result_lenet)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"5d1908b2-3e8f-4653-b734-df998d921428","cell_type":"code","source":"results_df = pd.DataFrame([\n    {k: v for k, v in result_perceptron.items() if k != \"Confusion Matrix\"},\n    {k: v for k, v in result_mlp.items() if k != \"Confusion Matrix\"},\n    {k: v for k, v in result_lenet.items() if k != \"Confusion Matrix\"}\n])\n\nresults_df.to_csv(\"mnist_outputs/model_comparison.csv\", index=False)\nresults_df\n","metadata":{},"outputs":[],"execution_count":null},{"id":"6e70b8b8-a5c1-4bbc-9fea-15fbe3fbdece","cell_type":"code","source":"plt.figure(figsize=(8, 5))\nplt.bar(results_df[\"Model\"], results_df[\"Accuracy\"])\nplt.ylim(0.85, 1.0)\nplt.xlabel(\"Model\")\nplt.ylabel(\"Test Accuracy\")\nplt.title(\"MNIST Model Accuracy Comparison\")\nplt.tight_layout()\nplt.savefig(\"mnist_outputs/model_accuracy_comparison.png\", dpi=200, bbox_inches=\"tight\")\nplt.show()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"534279c7-a809-4584-a43c-0bfff281b113","cell_type":"code","source":"for name, cm in [\n    (\"Perceptron\", result_perceptron[\"Confusion Matrix\"]),\n    (\"MLP\", result_mlp[\"Confusion Matrix\"]),\n    (\"LeNet\", result_lenet[\"Confusion Matrix\"])\n]:\n    print(name)\n    print(cm)\n    print()\n","metadata":{},"outputs":[],"execution_count":null}]}