{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# CELL 1 - Imports, configuration, and random seeds.\n\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\n\nprint(\"TensorFlow version:\", tf.__version__)\nprint(\"GPU available:\", tf.config.list_physical_devices('GPU'))\n\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nIMG_ROWS, IMG_COLS = 28, 28\nNUM_CLASSES = 10\n\n# Slightly smaller batch size helps generalization\nBATCH_SIZE = 128\n\n# Upper bound; EarlyStopping will stop earlier\nEPOCHS = 60","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 2 - Load training and test data from Kaggle input.\n\nDATA_DIR = \"/kaggle/input/digit-recognizer\"\n\ntrain_df = pd.read_csv(os.path.join(DATA_DIR, \"train.csv\"))\ntest_df  = pd.read_csv(os.path.join(DATA_DIR, \"test.csv\"))\n\nprint(\"Train shape:\", train_df.shape)\nprint(\"Test shape :\", test_df.shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 3 - Preprocess data: labels, reshape to 28x28x1, normalize.\n\ny_train = train_df[\"label\"].values\nX_train = train_df.drop(columns=[\"label\"]).values\nX_test  = test_df.values\n\nX_train = X_train.reshape(-1, IMG_ROWS, IMG_COLS, 1).astype(\"float32\")\nX_test  = X_test.reshape(-1, IMG_ROWS, IMG_COLS, 1).astype(\"float32\")\n\nX_train /= 255.0\nX_test  /= 255.0\n\nprint(\"X_train:\", X_train.shape, \"y_train:\", y_train.shape)\nprint(\"X_test :\", X_test.shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 4 - Light data augmentation for digits.\n\ndata_augmentation = keras.Sequential(\n    [\n        layers.RandomRotation(0.08),\n        layers.RandomTranslation(0.08, 0.08),\n        layers.RandomZoom(0.08),\n    ],\n    name=\"data_augmentation\",\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 5 - Stronger CNN architecture with L2 regularization.\n\nfrom tensorflow.keras import regularizers\n\nweight_decay = 1e-4  # small L2 to help generalization\n\ndef conv_block(x, filters):\n    x = layers.Conv2D(filters, (3, 3), padding=\"same\",\n                      use_bias=False,\n                      kernel_regularizer=regularizers.l2(weight_decay))(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation(\"relu\")(x)\n    return x\n\ndef build_model():\n    inputs = keras.Input(shape=(IMG_ROWS, IMG_COLS, 1))\n\n    # 1) Augmentation\n    x = data_augmentation(inputs)\n\n    # 2) Convolutional blocks\n    x = conv_block(x, 64)\n    x = conv_block(x, 64)\n    x = layers.MaxPooling2D((2, 2))(x)\n    x = layers.SpatialDropout2D(0.1)(x)\n\n    x = conv_block(x, 128)\n    x = conv_block(x, 128)\n    x = layers.MaxPooling2D((2, 2))(x)\n    x = layers.SpatialDropout2D(0.1)(x)\n\n    x = conv_block(x, 256)\n    x = layers.MaxPooling2D((2, 2))(x)\n\n    # 3) Global Average Pooling + dense head\n    x = layers.GlobalAveragePooling2D()(x)\n\n    x = layers.Dense(\n        256,\n        use_bias=False,\n        kernel_regularizer=regularizers.l2(weight_decay),\n    )(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation(\"relu\")(x)\n    x = layers.Dropout(0.5)(x)\n\n    outputs = layers.Dense(NUM_CLASSES, activation=\"softmax\")(x)\n\n    model = keras.Model(inputs, outputs, name=\"mnist_cnn_ensemble\")\n    return model\n\n# Just to verify the model builds\ntmp_model = build_model()\ntmp_model.summary()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 6 - Compile function and shared callbacks.\n\ndef compile_model(model, lr=1e-3):\n    loss_fn = keras.losses.SparseCategoricalCrossentropy()\n    model.compile(\n        optimizer=keras.optimizers.Adam(learning_rate=lr),\n        loss=loss_fn,\n        metrics=[\"accuracy\"],\n    )\n    return model\n\nearly_stopping = keras.callbacks.EarlyStopping(\n    monitor=\"val_accuracy\",\n    patience=7,\n    mode=\"max\",\n    restore_best_weights=True,\n    verbose=1,\n)\n\nlr_reducer = keras.callbacks.ReduceLROnPlateau(\n    monitor=\"val_accuracy\",\n    factor=0.5,\n    patience=3,\n    mode=\"max\",\n    min_lr=1e-5,\n    verbose=1,\n)\n\ncallbacks = [early_stopping, lr_reducer]\n\nprint(\"Callbacks ready.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 7 - Train an ensemble of several models (e.g. 3).\n\nN_MODELS = 3\nmodels_list = []\nhistories = []\n\nfor m in range(N_MODELS):\n    print(\"\\n\" + \"=\" * 40)\n    print(f\"Training model {m+1}/{N_MODELS}\")\n    print(\"=\" * 40)\n\n    # Different seed for each model so weights/augmentations differ\n    tf.random.set_seed(SEED + m)\n\n    model = build_model()\n    model = compile_model(model, lr=1e-3)\n\n    history = model.fit(\n        X_train,\n        y_train,\n        batch_size=BATCH_SIZE,\n        epochs=60,              # upper bound; EarlyStopping will cut it\n        validation_split=0.1,\n        callbacks=callbacks,\n        verbose=2,\n    )\n\n    models_list.append(model)\n    histories.append(history)\n\n# Check the best validation accuracy achieved across all models\nbest_vals = [max(h.history[\"val_accuracy\"]) for h in histories]\nprint(\"\\nBest val_accuracy per model:\", best_vals)\nprint(\"Max val_accuracy among ensemble:\", max(best_vals))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 8 - Ensemble predictions and create submission.csv.\n\nimport numpy as np\n\n# Average probabilities from all models\ntest_probs = np.zeros((X_test.shape[0], NUM_CLASSES), dtype=\"float32\")\n\nfor i, model in enumerate(models_list):\n    print(f\"Predicting with model {i+1}/{len(models_list)} ...\")\n    probs = model.predict(X_test, batch_size=BATCH_SIZE, verbose=0)\n    test_probs += probs\n\ntest_probs /= len(models_list)\n\ntest_preds = np.argmax(test_probs, axis=1)\n\nsubmission = pd.DataFrame(\n    {\n        \"ImageId\": np.arange(1, len(test_preds) + 1),\n        \"Label\": test_preds,\n    }\n)\n\nsubmission.to_csv(\"submission.csv\", index=False)\nsubmission.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}