{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"12d50ae2-41f7-4388-8ce9-db0b9c2c3b06","cell_type":"markdown","source":"# Digit Recognizer - MNIST with a Simple MLP\n### A Beginner-Friendly Notebook\n\nThis notebook builds a fast, simple model to classify handwritten digits (0-9) from the classic MNIST dataset, using the Digit Recognizer competition data.\n\n**Approach:**\n- Load and explore the data\n- Normalize pixel values\n- Train a simple `MLPClassifier` from scikit-learn (fast and beginner-friendly)\n- Evaluate on a validation split\n- Predict on the test set and generate `submission.csv`\n\nWe'll use `gc.collect()` at key points to keep memory usage under control, since the dataset (42,000 training images, 784 pixels each) can use a fair bit of RAM.","metadata":{}},{"id":"078eecd4-24b3-4270-927a-c2993f0c353b","cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport gc\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.neural_network import MLPClassifier\nfrom sklearn.metrics import accuracy_score\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(\"Libraries imported successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-13T03:38:59.878592Z","iopub.execute_input":"2026-09-13T03:38:59.878822Z","iopub.status.idle":"2026-09-13T03:39:00.606250Z","shell.execute_reply.started":"2026-09-13T03:38:59.878802Z","shell.execute_reply":"2026-09-13T03:39:00.605586Z"}},"outputs":[],"execution_count":null},{"id":"316e81bc-7f06-4bbf-ba91-64a0198ff22b","cell_type":"markdown","source":"## Step 1: Load the Data\n\nThe competition provides:\n- `train.csv`: 42,000 rows, each with a `label` column (the true digit) and 784 pixel columns (`pixel0` to `pixel783`, representing a 28x28 image).\n- `test.csv`: 28,000 rows with only the 784 pixel columns (no labels) — this is what we need to predict.","metadata":{}},{"id":"8ff72e3c-d8e2-4f07-8784-a0a505e30cd6","cell_type":"code","source":"import os\nimport kagglehub\nimport pandas as pd\n\n# Download the latest version of the competition files\npath = kagglehub.competition_download('digit-recognizer')\nprint(\"Path to competition files:\", path)\n\n# Dynamically construct the file paths using the downloaded directory\ntrain_df = pd.read_csv(os.path.join(path, 'train.csv'))\n# For some competitions, the test file might be named 'test.csv'\ntest_df = pd.read_csv(os.path.join(path, 'test.csv'))\n\n# Print shapes and preview the data\nprint(\"Train shape:\", train_df.shape)\nprint(\"Test shape:\", test_df.shape)\n\nprint(\"\\nFirst 5 rows of training data:\")\nprint(train_df.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-13T03:39:00.607248Z","iopub.execute_input":"2026-09-13T03:39:00.607519Z","iopub.status.idle":"2026-09-13T03:39:03.633170Z","shell.execute_reply.started":"2026-09-13T03:39:00.607502Z","shell.execute_reply":"2026-09-13T03:39:03.632542Z"}},"outputs":[],"execution_count":null},{"id":"62a3f6e3-c59f-4062-846d-e2e5955dd417","cell_type":"markdown","source":"## Step 2: Quick Look at the Data\n\nLet's check the distribution of digit labels and visualize a few sample images to make sure everything loaded correctly.","metadata":{}},{"id":"4ec9263d-eae7-455d-a7d1-45058a24766b","cell_type":"code","source":"import matplotlib.pyplot as plt\n\nprint(\"Label distribution:\")\nprint(train_df['label'].value_counts().sort_index())\n\n# Plot a few sample digits\nfig, axes = plt.subplots(1, 5, figsize=(10, 2))\nfor i in range(5):\n    img = train_df.iloc[i, 1:].values.reshape(28, 28)\n    axes[i].imshow(img, cmap='gray')\n    axes[i].set_title(f\"Label: {train_df.iloc[i, 0]}\")\n    axes[i].axis('off')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-13T03:39:03.633910Z","iopub.execute_input":"2026-09-13T03:39:03.634125Z","iopub.status.idle":"2026-09-13T03:39:03.849785Z","shell.execute_reply.started":"2026-09-13T03:39:03.634105Z","shell.execute_reply":"2026-09-13T03:39:03.849201Z"}},"outputs":[],"execution_count":null},{"id":"1e04263f-aec8-4d1b-84a1-6984bce7ff5a","cell_type":"markdown","source":"## Step 3: Prepare the Data\n\nWe separate the labels (`y`) from the pixel features (`X`), then normalize pixel values from the 0-255 range to 0-1. This helps the MLP model train faster and more reliably.\n\nWe also split off a small validation set so we can check our model's accuracy before submitting.","metadata":{}},{"id":"eab29833-0af8-4b58-ac91-a92fc9cef2a6","cell_type":"code","source":"# Separate features and labels\nX = train_df.drop('label', axis=1).values\ny = train_df['label'].values\n\n# Normalize pixel values to range [0, 1]\nX = X / 255.0\nX_test_final = test_df.values / 255.0\n\n# Split into training and validation sets\nX_train, X_val, y_train, y_val = train_test_split(\n    X, y, test_size=0.1, random_state=42, stratify=y\n)\n\nprint(\"X_train shape:\", X_train.shape)\nprint(\"X_val shape:\", X_val.shape)\n\n# Free up memory from objects we no longer need\ndel X, y, train_df\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-13T03:39:03.851602Z","iopub.execute_input":"2026-09-13T03:39:03.851880Z","iopub.status.idle":"2026-09-13T03:39:04.307718Z","shell.execute_reply.started":"2026-09-13T03:39:03.851860Z","shell.execute_reply":"2026-09-13T03:39:04.307121Z"}},"outputs":[],"execution_count":null},{"id":"b1ecbf23-415d-4278-a4c1-99485e9f91ea","cell_type":"markdown","source":"## Step 4: Train a Simple MLP Model\n\nWe use scikit-learn's `MLPClassifier` — a basic neural network with one hidden layer. This is fast to train on CPU and gives solid accuracy (typically 96-97%) on MNIST without needing GPUs or deep learning frameworks.","metadata":{}},{"id":"6cbf95f9-cdf3-44b3-9184-8770ba4fd80f","cell_type":"code","source":"model = MLPClassifier(\n    hidden_layer_sizes=(128,),   # one hidden layer with 128 neurons\n    activation='relu',\n    solver='adam',\n    alpha=1e-4,\n    max_iter=20,\n    random_state=42,\n    verbose=True\n)\n\nmodel.fit(X_train, y_train)\n\n# Clean up memory after training\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-13T03:39:04.308469Z","iopub.execute_input":"2026-09-13T03:39:04.308663Z","iopub.status.idle":"2026-09-13T03:39:17.504233Z","shell.execute_reply.started":"2026-09-13T03:39:04.308643Z","shell.execute_reply":"2026-09-13T03:39:17.503739Z"}},"outputs":[],"execution_count":null},{"id":"bdd8034a-12bc-4a12-976f-7f7472426385","cell_type":"markdown","source":"## Step 5: Evaluate on the Validation Set\n\nBefore predicting on the real test set, let's check how well our model performs on data it hasn't seen yet.","metadata":{}},{"id":"db1acb82-35e0-4cfe-8963-aa6cadb053b0","cell_type":"code","source":"val_predictions = model.predict(X_val)\nval_accuracy = accuracy_score(y_val, val_predictions)\n\nprint(f\"Validation Accuracy: {val_accuracy:.4f}\")\n\n# Free memory used by validation predictions\ndel val_predictions\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-13T03:39:17.505055Z","iopub.execute_input":"2026-09-13T03:39:17.505240Z","iopub.status.idle":"2026-09-13T03:39:17.625845Z","shell.execute_reply.started":"2026-09-13T03:39:17.505222Z","shell.execute_reply":"2026-09-13T03:39:17.625056Z"}},"outputs":[],"execution_count":null},{"id":"af498d5a-23ef-4cd7-89d7-cb62f6170cc8","cell_type":"markdown","source":"## Step 6: Predict on the Test Set\n\nNow we use our trained model to predict labels for the 28,000 test images provided by the competition.","metadata":{}},{"id":"c475aae0-d244-46c8-9a0b-23e874a6fca1","cell_type":"code","source":"test_predictions = model.predict(X_test_final)\n\nprint(\"Predictions generated for\", len(test_predictions), \"test images.\")\nprint(\"Sample predictions:\", test_predictions[:10])\n\n# Clean up memory\ndel X_test_final\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-13T03:39:17.627886Z","iopub.execute_input":"2026-09-13T03:39:17.628463Z","iopub.status.idle":"2026-09-13T03:39:17.864990Z","shell.execute_reply.started":"2026-09-13T03:39:17.628434Z","shell.execute_reply":"2026-09-13T03:39:17.864289Z"}},"outputs":[],"execution_count":null},{"id":"2cd5fbe7-f4e5-450d-8486-763c95163df0","cell_type":"markdown","source":"## Step 7: Create the Submission File\n\nThe competition requires a CSV file with exactly two columns:\n- `ImageId`: the 1-indexed row number of the test image\n- `Label`: the predicted digit (0-9)\n\nWe build this DataFrame and save it as `submission.csv`.","metadata":{}},{"id":"b1566681-bb9f-4ae0-834a-7126f9b076b3","cell_type":"code","source":"submission = pd.DataFrame({\n    'ImageId': np.arange(1, len(test_predictions) + 1),\n    'Label': test_predictions\n})\n\nsubmission.to_csv('submission.csv', index=False)\n\nprint(\"submission.csv created successfully!\")\nsubmission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-13T03:39:17.866713Z","iopub.execute_input":"2026-09-13T03:39:17.866902Z","iopub.status.idle":"2026-09-13T03:39:17.895278Z","shell.execute_reply.started":"2026-09-13T03:39:17.866884Z","shell.execute_reply":"2026-09-13T03:39:17.894730Z"}},"outputs":[],"execution_count":null},{"id":"3cd37340-47aa-4b96-b699-30439422cf95","cell_type":"markdown","source":"## Done!\n\nYou now have a `submission.csv` file ready to submit to the competition.\n\n**Ideas to improve accuracy later:**\n- Try `hidden_layer_sizes=(256, 128)` for a deeper network\n- Increase `max_iter` for more training epochs\n- Try a Convolutional Neural Network (CNN) with Keras/TensorFlow for higher accuracy\n- Use data augmentation (rotations, shifts) to improve generalization","metadata":{}}]}