{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":30646,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"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":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2025-11-02T18:23:19.877924Z","iopub.execute_input":"2025-11-02T18:23:19.880304Z","iopub.status.idle":"2025-11-02T18:23:21.627860Z","shell.execute_reply.started":"2025-11-02T18:23:19.880241Z","shell.execute_reply":"2025-11-02T18:23:21.626233Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#load datasets\ntrain_data = pd.read_csv('/kaggle/input/digit-recognizer/train.csv')\ntest_data = pd.read_csv('/kaggle/input/digit-recognizer/test.csv')","metadata":{"execution":{"iopub.status.busy":"2025-11-02T18:23:51.076723Z","iopub.execute_input":"2025-11-02T18:23:51.077127Z","iopub.status.idle":"2025-11-02T18:23:57.595266Z","shell.execute_reply.started":"2025-11-02T18:23:51.077095Z","shell.execute_reply":"2025-11-02T18:23:57.593949Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, models, utils, callbacks\nfrom sklearn.model_selection import StratifiedShuffleSplit\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nX = train_data.drop(columns='label').values.astype('float32') / 255.0\nX = X.reshape(-1,28,28,1)\ny = utils.to_categorical(train_data['label'], 10)\n\nX_test = test_data.values.astype('float32') / 255.0\nX_test = X_test.reshape(-1,28,28,1)\n\n# Stratified split\nsss = StratifiedShuffleSplit(n_splits=1, test_size=0.1, random_state=42)\ntrain_idx, val_idx = next(sss.split(X, np.argmax(y, axis=1)))\nX_train, X_val = X[train_idx], X[val_idx]\ny_train, y_val = y[train_idx], y[val_idx]\n\n# Data augmentation for main model\ndata_gen_main = ImageDataGenerator(\n    rotation_range=15,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    zoom_range=0.1,\n    shear_range=0.1\n)\ndata_gen_main.fit(X_train)\n\n# SE Block\ndef se_block(inputs, ratio=8):\n    channel = inputs.shape[-1]\n    se = layers.GlobalAveragePooling2D()(inputs)\n    se = layers.Dense(channel // ratio, activation='relu')(se)\n    se = layers.Dense(channel, activation='sigmoid')(se)\n    se = layers.Reshape((1,1,channel))(se)\n    return layers.multiply([inputs, se])\n\n# CNN\ndef build_main_cnn():\n    inputs = layers.Input(shape=(28,28,1))\n    x = layers.Conv2D(32,3,padding='same',activation='relu')(inputs)\n    x = layers.BatchNormalization()(x)\n    x = layers.Conv2D(32,3,padding='same',activation='relu')(x)\n    x = layers.BatchNormalization()(x)\n    x = se_block(x)\n    x = layers.MaxPooling2D(2)(x)\n    x = layers.Dropout(0.25)(x)\n\n    x = layers.Conv2D(64,3,padding='same',activation='relu')(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Conv2D(64,3,padding='same',activation='relu')(x)\n    x = layers.BatchNormalization()(x)\n    x = se_block(x)\n    x = layers.MaxPooling2D(2)(x)\n    x = layers.Dropout(0.25)(x)\n\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dense(128,activation='relu')(x)\n    x = layers.Dropout(0.5)(x)\n\n    outputs = layers.Dense(10,activation='softmax')(x)\n    return models.Model(inputs, outputs)\n\nmodel = build_main_cnn()\nmodel.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])\n\n# Callbacks\nearly_stop = callbacks.EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)\ncheckpoint = callbacks.ModelCheckpoint('best_model.h5', save_best_only=True, monitor='val_loss')\n\n# Train CNN\nmodel.fit(\n    data_gen_main.flow(X_train, y_train, batch_size=64),\n    validation_data=(X_val, y_val),\n    steps_per_epoch=int(np.ceil(len(X_train)/64)),\n    epochs=30,\n    callbacks=[early_stop, checkpoint],\n    verbose=2\n)\n\n# Confusion resolver for 1,3,5,6,7,9\nconfused_digits = [1,3,5,6,7,9]\nmask_train_conf = np.isin(np.argmax(y_train, axis=1), confused_digits)\nX_train_conf = X_train[mask_train_conf]\ny_train_conf = y_train[mask_train_conf][:, confused_digits]\n\nmask_val_conf = np.isin(np.argmax(y_val, axis=1), confused_digits)\nX_val_conf = X_val[mask_val_conf]\ny_val_conf = y_val[mask_val_conf][:, confused_digits]\n\n# Augmentation for confusion resolver\ndata_gen_conf = ImageDataGenerator(\n    rotation_range=15,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    zoom_range=0.1,\n    shear_range=0.1\n)\ndata_gen_conf.fit(X_train_conf)\n\ndef build_confusion_resolver():\n    inputs = layers.Input(shape=(28,28,1))\n    x = layers.Conv2D(32,3,activation='relu',padding='same')(inputs)\n    x = layers.MaxPooling2D(2)(x)\n    x = layers.Flatten()(x)\n    x = layers.Dense(64,activation='relu')(x)\n    outputs = layers.Dense(len(confused_digits),activation='softmax')(x)\n    return models.Model(inputs, outputs)\n\nconf_resolver = build_confusion_resolver()\nconf_resolver.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])\n\n# Train confusion resolver\nconf_resolver.fit(\n    data_gen_conf.flow(X_train_conf, y_train_conf, batch_size=32),\n    validation_data=(X_val_conf, y_val_conf),\n    steps_per_epoch=int(np.ceil(len(X_train_conf)/32)),\n    epochs=20,\n    verbose=2\n)\n\n# Test-time augmentation and ensemble\ntta_gen = ImageDataGenerator(\n    rotation_range=10,\n    width_shift_range=0.05,\n    height_shift_range=0.05,\n    zoom_range=0.05\n)\n\ntta_steps = 5\npred_probs_ensemble = np.zeros((len(X_test), 10))\n\nfor _ in range(tta_steps):\n    augmented_test = tta_gen.flow(X_test, batch_size=128, shuffle=False)\n    pred_probs_ensemble += model.predict(augmented_test, verbose=0)\npred_probs_ensemble /= tta_steps\n\n# Apply confusion resolver for low-confidence confused digits\nmask_conf = np.isin(np.argmax(pred_probs_ensemble, axis=1), confused_digits) & (np.max(pred_probs_ensemble, axis=1)<0.85)\nif mask_conf.any():\n    conf_probs = conf_resolver.predict(X_test[mask_conf], batch_size=128, verbose=0)\n    main_probs_conf = pred_probs_ensemble[mask_conf][:, confused_digits]\n    merged_probs = 0.4*main_probs_conf + 0.6*conf_probs\n    pred_probs_ensemble[mask_conf][:, confused_digits] = merged_probs\n\n# Final predictions\nfinal_preds = np.argmax(pred_probs_ensemble, axis=1)\n\n# Submission\nsubmission = pd.DataFrame({\n    'ImageId': np.arange(1, len(final_preds)+1),\n    'Label': final_preds\n})\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}