{"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"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":3004,"databundleVersionId":861823},{"sourceType":"datasetVersion","sourceId":16480986,"datasetId":10558479,"databundleVersionId":17481815}],"dockerImageVersionId":31400,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false},"papermill":{"default_parameters":{},"duration":2437.299902,"end_time":"2024-05-22T06:26:14.628506","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2024-05-22T05:45:37.328604","version":"2.4.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"## IMPORT THE NECESSARY PYTHON LIBRARIES\n\n# Read & Explore Data\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom sklearn.preprocessing import MinMaxScaler\n\n# Principal Component Analysis (PCA)\nfrom sklearn.decomposition import PCA\nfrom sklearn.pipeline import Pipeline\nimport plotly.express as px\n\n# K-Nearest Neighbor Classifier (KNN)\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.model_selection import GridSearchCV\nfrom sklearn.metrics import classification_report, \\\n    confusion_matrix, ConfusionMatrixDisplay \n\n# VHT\nfrom scipy.ndimage import rotate","metadata":{"_cell_guid":"e169ec97-1f64-46cd-8099-1d09c757dccc","_uuid":"c382c9dc-2109-4ce6-b779-dc8c447da240","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:39:55.335804Z","iopub.execute_input":"2026-05-27T02:39:55.336145Z","iopub.status.idle":"2026-05-27T02:39:55.344848Z","shell.execute_reply.started":"2026-05-27T02:39:55.336121Z","shell.execute_reply":"2026-05-27T02:39:55.343943Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":3.3831,"end_time":"2024-05-22T05:45:44.613813","exception":false,"start_time":"2024-05-22T05:45:41.230713","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def func_array1d_rotate2d(Narray1d,rotate_angle,sizePixel):\n\n    N = Narray1d.shape[0]\n\n    Narray1d_rotate = np.zeros(Narray1d.shape)\n    \n    for i in range(N):\n        image = np.reshape(Narray1d[i,:],(sizePixel,sizePixel))\n        rotated_image = rotate(image,rotate_angle, reshape=False)\n        rotated_array1d = np.reshape(rotated_image,(1,-1))\n        Narray1d_rotate[i,:] = rotated_array1d\n        \n    return Narray1d_rotate","metadata":{"_cell_guid":"8ace7936-3ee9-4fe9-b71e-8d7e3b7eee15","_uuid":"b362968d-9828-4257-92ec-b9e4e49a9cb0","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:39:55.346233Z","iopub.execute_input":"2026-05-27T02:39:55.346489Z","iopub.status.idle":"2026-05-27T02:39:55.381737Z","shell.execute_reply.started":"2026-05-27T02:39:55.346469Z","shell.execute_reply":"2026-05-27T02:39:55.38073Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.031944,"end_time":"2024-05-22T05:45:44.664019","exception":false,"start_time":"2024-05-22T05:45:44.632075","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train = pd.read_csv('/kaggle/input/datasets/deimos0810/digit-recognizer/train.csv')\ndf_test  = pd.read_csv('/kaggle/input/datasets/deimos0810/digit-recognizer/test.csv')","metadata":{"_cell_guid":"8967621a-0c55-49ff-b6e2-05d9efdeedf2","_uuid":"4da57b46-cc50-4ae9-9495-d0dbeb6be5ee","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:39:55.382667Z","iopub.execute_input":"2026-05-27T02:39:55.382906Z","iopub.status.idle":"2026-05-27T02:39:58.140712Z","shell.execute_reply.started":"2026-05-27T02:39:55.382885Z","shell.execute_reply":"2026-05-27T02:39:58.139238Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":6.576341,"end_time":"2024-05-22T05:45:51.316853","exception":false,"start_time":"2024-05-22T05:45:44.740512","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Check the size of the train and test DataFrames\nprint(\"df_train shape:\", df_train.shape)  # 70%\nprint(\"df_test shape: \", df_test.shape)  # 30%","metadata":{"_cell_guid":"da0e04b7-5ce3-48ec-be64-cee70c71af07","_uuid":"5465a268-9ec4-4482-a0d7-e531d25f4917","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:39:58.143681Z","iopub.execute_input":"2026-05-27T02:39:58.144032Z","iopub.status.idle":"2026-05-27T02:39:58.151868Z","shell.execute_reply.started":"2026-05-27T02:39:58.144002Z","shell.execute_reply":"2026-05-27T02:39:58.150089Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.024133,"end_time":"2024-05-22T05:45:51.354029","exception":false,"start_time":"2024-05-22T05:45:51.329896","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fortunately there are no missing values in our data\nprint(\n    \"Missing values in df_train: \", \\\n    df_train.isna().sum().sum()\n)\nprint(\n    \"Missing values in df_test: \", \\\n    df_test.isna().sum().sum()\n)","metadata":{"_cell_guid":"758f94a2-69c7-4c9b-b1a3-22cb0f0b1a43","_uuid":"f8324800-d007-4d21-9eb6-39bba50ce548","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:39:58.15336Z","iopub.execute_input":"2026-05-27T02:39:58.153666Z","iopub.status.idle":"2026-05-27T02:39:58.20509Z","shell.execute_reply.started":"2026-05-27T02:39:58.153641Z","shell.execute_reply":"2026-05-27T02:39:58.203914Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.068677,"end_time":"2024-05-22T05:45:51.435642","exception":false,"start_time":"2024-05-22T05:45:51.366965","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Let's have a look at the first five training rows\ndf_train.head()","metadata":{"_cell_guid":"f1a8c252-31ba-46a1-b25c-c6ffff3fa2a9","_uuid":"b1a7f2dc-6cec-4610-ba22-6ad5a7fb06ac","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:39:58.206724Z","iopub.execute_input":"2026-05-27T02:39:58.207291Z","iopub.status.idle":"2026-05-27T02:39:58.223342Z","shell.execute_reply.started":"2026-05-27T02:39:58.20726Z","shell.execute_reply":"2026-05-27T02:39:58.221414Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.044489,"end_time":"2024-05-22T05:45:51.492995","exception":false,"start_time":"2024-05-22T05:45:51.448506","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# We can see that the first colum is the label,\n# and the rest of the columns are the 784 pixels\n# that represent 28x28 gray-scale images.\n# Let's separate X and Y\n\nX_train = df_train.drop(columns=['label']).values\ny_train = df_train['label'].values","metadata":{"_cell_guid":"939c1008-c85e-4ee0-bdab-bcccc4a252e4","_uuid":"48085649-c225-4e39-824f-82474ea004ff","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:39:58.224894Z","iopub.execute_input":"2026-05-27T02:39:58.225235Z","iopub.status.idle":"2026-05-27T02:39:58.288264Z","shell.execute_reply.started":"2026-05-27T02:39:58.22521Z","shell.execute_reply":"2026-05-27T02:39:58.286962Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.153936,"end_time":"2024-05-22T05:45:51.660395","exception":false,"start_time":"2024-05-22T05:45:51.506459","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"df_train shape:\", df_train.shape)\nprint(\"X_train shape:\", X_train.shape)\nprint(\"Giá trị min/max của X_train:\", X_train.min(), X_train.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T02:39:58.289849Z","iopub.execute_input":"2026-05-27T02:39:58.290099Z","iopub.status.idle":"2026-05-27T02:39:58.338351Z","shell.execute_reply.started":"2026-05-27T02:39:58.290078Z","shell.execute_reply":"2026-05-27T02:39:58.336777Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.hist(X_train.flatten(), bins=50)\nplt.title(\"Matplotlib hist test\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T02:39:58.339547Z","iopub.execute_input":"2026-05-27T02:39:58.339828Z","iopub.status.idle":"2026-05-27T02:39:58.929942Z","shell.execute_reply.started":"2026-05-27T02:39:58.339802Z","shell.execute_reply":"2026-05-27T02:39:58.929049Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import plotly.io as pio\npio.renderers.default = 'kaggle'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T02:39:58.932942Z","iopub.execute_input":"2026-05-27T02:39:58.933215Z","iopub.status.idle":"2026-05-27T02:39:58.939213Z","shell.execute_reply.started":"2026-05-27T02:39:58.933192Z","shell.execute_reply":"2026-05-27T02:39:58.937735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\nimport matplotlib.pyplot as plt\n\n# Code vẽ biểu đồ của bạn\nax = sns.histplot(x=X_train.flatten(), color='#4B95EE')\n_ = ax.set(title='Pixel Value Histogram', xlabel='Pixel Value')\n\n# Thêm duy nhất dòng này vào để chuyển trục Y sang hệ số logarit\nax.set_yscale('log') \n\nplt.show()","metadata":{"_cell_guid":"36ec9986-72eb-4f04-abf8-84e2df3e3bfa","_uuid":"f724afe4-d098-4f65-aaca-e97a096b8a32","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:39:58.941287Z","iopub.execute_input":"2026-05-27T02:39:58.941679Z","iopub.status.idle":"2026-05-27T02:40:14.889411Z","shell.execute_reply.started":"2026-05-27T02:39:58.941649Z","shell.execute_reply":"2026-05-27T02:40:14.888117Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":11.528807,"end_time":"2024-05-22T05:46:03.202675","exception":false,"start_time":"2024-05-22T05:45:51.673868","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Let's check that the target is balanced.\n# It means that the ten categories in the target\n# have roughly the same representation in the data.\n\nax = sns.countplot(x=y_train, color='#4895EE')\nax.set(\n    title='Target distribution is balanced? Yes!',\n    xlabel='Label'\n)\n# The expected number of samples for a balanced \n# class distribution is 42000/10 = 4200\n_ = ax.axhline(y=4200, color='#12086F', linestyle='--')\n\n# Since the target classes are balances, we don't have\n# worry about sampling methods and biases in our models.","metadata":{"_cell_guid":"d681064d-2dbe-4c2b-8075-9ce2c254d755","_uuid":"cd488e69-2d9d-4209-ac52-f11e13ab443b","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:14.890455Z","iopub.execute_input":"2026-05-27T02:40:14.890714Z","iopub.status.idle":"2026-05-27T02:40:15.080794Z","shell.execute_reply.started":"2026-05-27T02:40:14.890691Z","shell.execute_reply":"2026-05-27T02:40:15.078904Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.307505,"end_time":"2024-05-22T05:46:03.524115","exception":false,"start_time":"2024-05-22T05:46:03.21661","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Next, let's visualize one image from each class.\n# We will use the matplotlib 'imshow' function.\n\nfig, axs = plt.subplots(nrows=2, ncols=5, figsize=(10,5))\n\nfor i in range(10):\n    idx = np.where(y_train==i)[0][2]\n    img = axs[int(i >= 5), i%5].imshow(\n        X=X_train[idx].reshape(28, 28),\n        cmap='Blues'\n    )\n    img.axes.get_yaxis().set_visible(False)\n    img.axes.get_xaxis().set_visible(False)\n    img.axes.set_title(f'Label: {i}')","metadata":{"_cell_guid":"1fa8f38f-f989-4007-86c5-590612877157","_uuid":"9d6c909c-609f-4b5d-a907-2d75a3c17401","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:15.082807Z","iopub.execute_input":"2026-05-27T02:40:15.083162Z","iopub.status.idle":"2026-05-27T02:40:15.276886Z","shell.execute_reply.started":"2026-05-27T02:40:15.083131Z","shell.execute_reply":"2026-05-27T02:40:15.275895Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.689033,"end_time":"2024-05-22T05:46:04.228213","exception":false,"start_time":"2024-05-22T05:46:03.53918","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -------------------------------------------------- declare\nnumRotate_prior = 10\n\nangleOffset_prior = -20\nangleRange_prior = 40\n\nX_train_pick2rotate = {\n    'index': np.array([3700,3701,2,3,4,\n                      5,6,7,8,9]),\n    'offset': np.array([\n        [-5,35,-5,0,-15, \n         10,-35,0,20,25],\n        [0,0,0,0,0, \n         -5,-10,0,10,10],\n        [10,5,0,0,0, \n         0,10,0,20,0],\n        [45,10,-20,0,0, \n         0,-15,0,-35,0],\n        [0,-5,0,0,10, \n         10,-25,0,20,0],\n        \n        [0,12,0,0,5, \n         20,0,0,0,0],\n        [0,20,0,0,0, \n         0,-80,0,10,-10],\n        [0,0,0,0,0, \n         0,0,0,0,10],\n        [0,0,0,0,0, \n         0,0,0,0,0],\n        [0,0,0,0,0, \n         0,0,0,0,0],\n    ])\n}\n\nnumPickRotate = len(X_train_pick2rotate['index'])\n\nprint('numPickRotate = ', numPickRotate)\n\n# -------------------------------------------------- rotate\n\nX_sample_rotate = np.zeros((10,numRotate_prior,numPickRotate,X_train.shape[1]))\ny_sample_rotate = np.zeros((10,numRotate_prior,numPickRotate))\n\nfor k0 in range(10):\n    for k1 in range(numRotate_prior):\n        for k2 in range(numPickRotate):\n            \n                idxPick_X_train = X_train_pick2rotate['index'][k2]\n                \n                idx = np.where(y_train==k0)[0][idxPick_X_train]\n                \n                y_sample_rotate[k0,k1,k2] = k0 * numRotate_prior + k1\n                \n                angleRotate = (k1/numRotate_prior * angleRange_prior) + angleOffset_prior + X_train_pick2rotate['offset'][k2][k0]\n                \n                X_sample_rotate[k0,k1,k2,:] = func_array1d_rotate2d(X_train[idx][np.newaxis,:],angleRotate, 28)\n\n                \nX_sample_rotate[X_sample_rotate < 10] = 0\n#X_sample_rotate[X_sample_rotate > 300] = 0\n\n# Let's normalize the pixel values to the interval [0,1]\n# Formula: MinMaxscale(X) = (X - X_min) / (X_max - X_min)\n\nprint(\"Before applying MinMax Scaler:\")\nprint(\"  - Minimum value:\", X_sample_rotate.min())\nprint(\"  - Maximum value:\", X_sample_rotate.max())\n\nminmax_scaler = MinMaxScaler()\n\nfor k0 in range(10):\n    for k1 in range(numRotate_prior):\n        X_sample_rotate[k0,k1,:,:] = minmax_scaler.fit_transform(X_sample_rotate[k0,k1,:,:]) * 255\n\nprint(\"\\nAfter applying MinMax Scaler:\")\nprint(\"  - Minimum value:\", X_sample_rotate.min())\nprint(\"  - Maximum value:\", X_sample_rotate.max())\n\n\n\n# Next, let's visualize one image from each class.\n# We will use the matplotlib 'imshow' function.\n\n# for k2 in range(numPickRotate):\nfor k2 in range(5,10):\n    k1 = 0\n    # k1 = numRotate_prior - 1\n\n    fig, axs = plt.subplots(nrows=2, ncols=5, figsize=(10,5))\n\n    for k0 in range(10):\n        img = axs[int(k0 >= 5), k0%5].imshow(\n            X=X_sample_rotate[k0,k1,k2,:].reshape(28, 28),\n            cmap='Blues'\n        )\n        img.axes.get_yaxis().set_visible(False)\n        img.axes.get_xaxis().set_visible(False)\n        img.axes.set_title(f'Label: {k0}')\n        \nX_train_rotate = np.reshape(X_sample_rotate, (-1,784))\ny_train_rotate = np.reshape(y_sample_rotate, (-1,1)).flatten()","metadata":{"_cell_guid":"6b163e1f-7fee-43a6-b6f6-40c0d402a4fc","_uuid":"2fd8cd6b-8db8-45dc-b5f7-6721c80f11fd","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:15.278655Z","iopub.execute_input":"2026-05-27T02:40:15.279006Z","iopub.status.idle":"2026-05-27T02:40:16.403582Z","shell.execute_reply.started":"2026-05-27T02:40:15.278973Z","shell.execute_reply":"2026-05-27T02:40:16.402436Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":2.905339,"end_time":"2024-05-22T05:46:07.177592","exception":false,"start_time":"2024-05-22T05:46:04.272253","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Let's normalize the pixel values to the interval [0,1]\n# Formula: MinMaxscale(X) = (X - X_min) / (X_max - X_min)\n\nprint(\"Before applying MinMax Scaler:\")\nprint(\"  - Minimum value:\", X_train.min())\nprint(\"  - Maximum value:\", X_train.max())\n\nminmax_scaler = MinMaxScaler()\nX_train_rotate_scaled = minmax_scaler.fit_transform(X_train_rotate)\n\nprint(\"\\nAfter applying MinMax Scaler:\")\nprint(\"  - Minimum value:\", X_train_rotate_scaled.min())\nprint(\"  - Maximum value:\", X_train_rotate_scaled.max())","metadata":{"_cell_guid":"2f528db2-3b92-425b-a18c-2168dd8862d7","_uuid":"aad53bf6-9b1f-4451-844d-ae51f509a68f","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:16.404989Z","iopub.execute_input":"2026-05-27T02:40:16.405291Z","iopub.status.idle":"2026-05-27T02:40:16.464854Z","shell.execute_reply.started":"2026-05-27T02:40:16.405264Z","shell.execute_reply":"2026-05-27T02:40:16.463967Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.080595,"end_time":"2024-05-22T05:46:07.343749","exception":false,"start_time":"2024-05-22T05:46:07.263154","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Actually, we will embed the MinMaxScaler and the PCA\n# modules into a Sklearn Pipeline.\n\npca = Pipeline(steps=[\n    ('scaler', MinMaxScaler()),\n    ('pca', PCA(n_components=40))\n])\n\nX_train_rotate_pca = pca.fit_transform(X=X_train_rotate)\n\nprint('X_train_pca shape:', X_train_rotate_pca.shape)","metadata":{"_cell_guid":"e423b865-ef1d-4c48-9152-917f8b258060","_uuid":"6cae5170-3af3-4ed9-a9d8-372ef224e9ba","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:16.465892Z","iopub.execute_input":"2026-05-27T02:40:16.466162Z","iopub.status.idle":"2026-05-27T02:40:17.485254Z","shell.execute_reply.started":"2026-05-27T02:40:16.466132Z","shell.execute_reply":"2026-05-27T02:40:17.484778Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.441899,"end_time":"2024-05-22T05:46:07.80525","exception":false,"start_time":"2024-05-22T05:46:07.363351","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Let's extract the explained variance ratio\n# And let's compute their cumulative sum\n\npca_object = pca.named_steps['pca']\nvar_ratio = pd.Series(\n    data=pca_object.explained_variance_ratio_,\n    index=range(1, 41)\n)\n\ncumsum_var_ratio = var_ratio.cumsum()","metadata":{"_cell_guid":"c3cdf97b-6136-45e0-94c6-bdf23e750bf0","_uuid":"68b1ffab-f2de-43fe-a655-e04c6060ec60","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:17.485899Z","iopub.execute_input":"2026-05-27T02:40:17.486072Z","iopub.status.idle":"2026-05-27T02:40:17.492614Z","shell.execute_reply.started":"2026-05-27T02:40:17.486053Z","shell.execute_reply":"2026-05-27T02:40:17.492081Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.061747,"end_time":"2024-05-22T05:46:07.923017","exception":false,"start_time":"2024-05-22T05:46:07.86127","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot the explained variance ratio for each component (left)\n# And plot their cumulative sum (right)\n\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15,5))\n\nsns.barplot(\n    x=var_ratio.index, \n    y=var_ratio.values, \n    color='#4895EE', \n    ax=ax1\n)\nax1.set(\n    title='Explained Variance Ratio PCA',\n    xlabel='Component',\n    ylabel='Var Ratio',\n)\nax1.set_ylim(0, 0.5)\n\nsns.barplot(\n    x=cumsum_var_ratio.index, \n    y=cumsum_var_ratio.values, \n    color='#4895EE', \n    ax=ax2\n)\nax2.set(\n    title='Cumulative Explained Variance Ratio PCA',\n    xlabel='Component',\n    ylabel='Cumulative Var Ratio',\n)\n_ = ax2.set_ylim(0, 0.5)","metadata":{"_cell_guid":"6fe44c61-1ac9-4157-968d-022d42184ff6","_uuid":"9ea36679-af0c-40f1-bda2-641ef9c73a4e","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:17.493205Z","iopub.execute_input":"2026-05-27T02:40:17.493419Z","iopub.status.idle":"2026-05-27T02:40:18.118018Z","shell.execute_reply.started":"2026-05-27T02:40:17.493397Z","shell.execute_reply":"2026-05-27T02:40:18.116978Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":1.087502,"end_time":"2024-05-22T05:46:09.028302","exception":false,"start_time":"2024-05-22T05:46:07.9408","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Let's put the components in Pandas DataFrame format\n# This way we can use the seaborn and plotly libraries\n# to visualize the data.\n# (these packages work better with Pandas DataFrames)\n\ndf_train_pca = pd.DataFrame(\n    data=X_train_rotate_pca[:,:3], \n    columns=['x', 'y', 'z']\n)\n# Let's also add the target to set the colors\ndf_train_pca['target'] = y_train_rotate\n\nprint(df_train_pca.shape)\ndf_train_pca.head()","metadata":{"_cell_guid":"4fb22220-cc87-410b-a637-55bc57791987","_uuid":"9796e066-c6af-49c5-af8f-8ebde5e07430","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:18.119141Z","iopub.execute_input":"2026-05-27T02:40:18.119453Z","iopub.status.idle":"2026-05-27T02:40:18.133153Z","shell.execute_reply.started":"2026-05-27T02:40:18.119427Z","shell.execute_reply":"2026-05-27T02:40:18.131491Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.038334,"end_time":"2024-05-22T05:46:09.085096","exception":false,"start_time":"2024-05-22T05:46:09.046762","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Two-dimensional visualization of the data\n# Here the Top2 principal components are plotted\n\nsns.set_style(\"dark\")\nax = sns.scatterplot(\n    data=df_train_pca,\n    x='x', \n    y='y', \n    hue='target', \n    palette='Blues',\n)\nax.set(\n    title='PCA Two-Dimensional Projection',\n    xlabel='1st Comp.',\n    ylabel='2nd Comp.'\n)\nax.axhline(y=0, linestyle='--', color='#FFBF00', zorder=-1)\nax.axvline(x=0, linestyle='--', color='#FFBF00', zorder=-1)","metadata":{"_cell_guid":"147fd5ee-e632-4611-b6de-35d64c6946e9","_uuid":"30135dee-d7d0-4dc8-88f7-3a88a135ad5a","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:18.135434Z","iopub.execute_input":"2026-05-27T02:40:18.136069Z","iopub.status.idle":"2026-05-27T02:40:18.381585Z","shell.execute_reply.started":"2026-05-27T02:40:18.136024Z","shell.execute_reply":"2026-05-27T02:40:18.380449Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.717865,"end_time":"2024-05-22T05:46:09.82178","exception":false,"start_time":"2024-05-22T05:46:09.103915","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Now, let's apply the inverse_transform to compute\n# the approximation of images based on the 10 principal\n# compontents.\n# We can see that with just 10 components we convey a large\n# part of the information of each image.\n\npca_object = pca.named_steps['pca']\n\nfig, axs = plt.subplots(nrows=10, ncols=2, figsize=(6,20))\n\nfor i in range(10):\n    idx = np.where(y_train==i)[0][0]\n    original_img = axs[i, 0].imshow(\n        X=X_train_rotate[idx].reshape(28, 28),\n        cmap='Blues'\n    )\n    original_img.axes.get_yaxis().set_visible(False)\n    original_img.axes.get_xaxis().set_visible(False)\n    original_img.axes.set_title(f'Original [{i}]:')\n    \n    approx_img_ = pca_object.inverse_transform(X=X_train_rotate_pca[idx])\n    approx_img = axs[i, 1].imshow(\n        X=approx_img_.reshape(28, 28),\n        cmap='Blues'\n    )\n    approx_img.axes.get_yaxis().set_visible(False)\n    approx_img.axes.get_xaxis().set_visible(False)\n    approx_img.axes.set_title(f'Approximation [{i}]:')","metadata":{"_cell_guid":"329c95b3-2406-402c-b1f8-38487c4fa2f3","_uuid":"77146579-275e-4359-8ec9-7d175da30220","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:18.382719Z","iopub.execute_input":"2026-05-27T02:40:18.382995Z","iopub.status.idle":"2026-05-27T02:40:18.753189Z","shell.execute_reply.started":"2026-05-27T02:40:18.382965Z","shell.execute_reply":"2026-05-27T02:40:18.751716Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":1.101194,"end_time":"2024-05-22T05:46:10.944036","exception":false,"start_time":"2024-05-22T05:46:09.842842","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Show that the PCA components are uncorrelated\n# (This statement is true by definition and construction\n# of the Principal Component Analysis algorithm)\n\ncorr = np.corrcoef(x=X_train_rotate_pca, rowvar=False)\ncorr = np.around(corr, decimals=2)\n\nax = sns.heatmap(\n    data=corr, \n    vmax=1, \n    vmin=-1,\n    square=True,\n    linewidths=.5,\n    annot=True,\n    cmap='Blues',\n)\n\n_ = ax.set(\n    title='Correlation PCA Components [Uncorrelated]',\n    xlabel='Components',\n    ylabel='Components'\n)","metadata":{"_cell_guid":"ab321270-3628-423b-8fd6-a7ebaf6ccfe5","_uuid":"8c3fa8bb-61ab-43dc-bc7e-e5fecc13e6e2","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:18.754784Z","iopub.execute_input":"2026-05-27T02:40:18.755069Z","iopub.status.idle":"2026-05-27T02:40:20.585183Z","shell.execute_reply.started":"2026-05-27T02:40:18.755042Z","shell.execute_reply":"2026-05-27T02:40:20.583392Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":4.127858,"end_time":"2024-05-22T05:46:15.09463","exception":false,"start_time":"2024-05-22T05:46:10.966772","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"knn_clf = Pipeline(steps=[\n    ('scaler', MinMaxScaler()),\n    ('pca', PCA(n_components=10)),\n    ('knn', KNeighborsClassifier())\n])\n\nknn_clf_params = {\n    'knn__n_neighbors': [2, 4, 8, 16, 32]\n}\n\nknn_clf_gs = GridSearchCV(\n    estimator=knn_clf, \n    param_grid=knn_clf_params,\n    scoring='accuracy',\n    n_jobs=-1,\n    cv=5,\n    verbose=1,\n    return_train_score=True\n)\n\nknn_clf_gs.fit(X=X_train_rotate_pca, y=y_train_rotate)\n\nprint('best PCA+KNN params:', knn_clf_gs.best_params_)\nprint('best PCA+KNN cv accuracy:', knn_clf_gs.best_score_)","metadata":{"_cell_guid":"f8b23485-498b-446c-83a4-fdf1e50b8285","_uuid":"7154a244-1406-4e55-9062-2a876ffcfb42","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:20.586912Z","iopub.execute_input":"2026-05-27T02:40:20.587182Z","iopub.status.idle":"2026-05-27T02:40:22.694473Z","shell.execute_reply.started":"2026-05-27T02:40:20.587159Z","shell.execute_reply":"2026-05-27T02:40:22.693728Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":2.346727,"end_time":"2024-05-22T05:46:17.615699","exception":false,"start_time":"2024-05-22T05:46:15.268972","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot the Training and Validation Accuracies\n# for each Hyperparamter value tested during the\n# Grid Search.\n# This plot allows us to check for overfitting.\n\nfig, ax = plt.subplots()\n\nax.plot(\n    range(len(knn_clf_params['knn__n_neighbors'])), \n    knn_clf_gs.cv_results_['mean_train_score'],\n    label='train',\n    color='blue'\n)\nax.plot(\n    range(len(knn_clf_params['knn__n_neighbors'])),\n    knn_clf_gs.cv_results_['mean_test_score'],\n    label='test',\n    color='orange'\n)\nax.set_xticks(\n    ticks=range(len(knn_clf_params['knn__n_neighbors'])),\n    labels=knn_clf_params['knn__n_neighbors']\n)\n\nii = knn_clf_params['knn__n_neighbors'].index(\n    knn_clf_gs.best_params_['knn__n_neighbors']\n)\n\nplt.axvline(x=ii, linestyle='--', color='red', label='best', alpha=0.3)\nplt.legend()\nplt.xlabel(\"'n_neighbors' vals\")\nplt.ylabel('Accuracy')\nplt.title(\"PCA+KNN GridSearchCV Results\")\nplt.show()","metadata":{"_cell_guid":"b1316942-7e1c-4f06-8742-d3eb8f7918c3","_uuid":"43e1d701-a89a-463f-b860-3afbf14bbf36","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:22.695187Z","iopub.execute_input":"2026-05-27T02:40:22.695433Z","iopub.status.idle":"2026-05-27T02:40:22.851192Z","shell.execute_reply.started":"2026-05-27T02:40:22.69541Z","shell.execute_reply":"2026-05-27T02:40:22.849794Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.35757,"end_time":"2024-05-22T05:46:18.054689","exception":false,"start_time":"2024-05-22T05:46:17.697119","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score\n\n# Compute and display the Confusion Matrix\n# Note: the entire training dataset is used here\n\nX_val = X_train_rotate_pca\nY_val = y_train_rotate\n\nbest_cv_clf = knn_clf_gs.best_estimator_\ny_pred = best_cv_clf.predict(X=X_val)\n\ny_val_number = Y_val // numRotate_prior\ny_pred_number = y_pred // numRotate_prior\n\ny_val_angle = (Y_val % numRotate_prior)\ny_pred_angle = (y_pred % numRotate_prior)\n\nprint('KNN number + angle, Acc({:.2f}%)', accuracy_score(Y_val, y_pred))\nprint('KNN number, Acc({:.2f}%)', accuracy_score(y_val_number, y_pred_number))\nprint('KNN angle, Acc({:.2f}%)', accuracy_score(y_val_angle, y_pred_angle))\n\n# print('y_val_number =', y_val_number)\n# print('y_pred_number =', y_pred_number)\n\ny_val_angle_degree = y_val_angle/numRotate_prior * angleRange_prior + angleOffset_prior\ny_pred_angle_degree = y_pred_angle/numRotate_prior * angleRange_prior + angleOffset_prior\n\n# print('y_val_angle_degree =', y_val_angle_degree)\n# print('y_pred_angle_degree =', y_pred_angle_degree)\n\nconf_matrix = confusion_matrix(y_val_number, y_pred_number)\nconf_matrix_disp = ConfusionMatrixDisplay(conf_matrix)\nconf_matrix_disp.plot(cmap='Blues')\n\nconf_matrix = confusion_matrix(y_val_angle, y_pred_angle)\nconf_matrix_disp = ConfusionMatrixDisplay(conf_matrix)\nconf_matrix_disp.plot(cmap='Blues')","metadata":{"_cell_guid":"fdfdc4ce-7aa6-4c8a-b7c9-8a069cb98054","_uuid":"62d7493a-d5c0-42ca-ad56-df419c3367ee","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:22.853108Z","iopub.execute_input":"2026-05-27T02:40:22.853485Z","iopub.status.idle":"2026-05-27T02:40:23.384404Z","shell.execute_reply.started":"2026-05-27T02:40:22.853456Z","shell.execute_reply":"2026-05-27T02:40:23.38324Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":1.487097,"end_time":"2024-05-22T05:46:19.621525","exception":false,"start_time":"2024-05-22T05:46:18.134428","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_val = pca.transform(X_train)\nY_val = y_train\n\nbest_cv_clf = knn_clf_gs.best_estimator_\ny_pred = best_cv_clf.predict(X=X_val)\n\ny_pred_number = y_pred\ny_val_number = Y_val\n\nprint('KNN number, Acc({:.2f}%)', accuracy_score(y_val_number, y_pred_number))\n\nconf_matrix = confusion_matrix(y_val_number, y_pred_number)\nconf_matrix_disp = ConfusionMatrixDisplay(conf_matrix)\nconf_matrix_disp.plot(cmap='Blues')\n\ny_pred_angle = (y_pred % numRotate_prior)\n\ny_pred_angle_degree = y_pred_angle/numRotate_prior * angleRange_prior + angleOffset_prior\n\nprint('y_pred_angle_degree =', y_pred_angle_degree)","metadata":{"_cell_guid":"fa5652c2-d596-4880-8396-b6e77177f4ff","_uuid":"da419e38-d545-49ee-a8b6-32f3fa9760c3","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:23.385437Z","iopub.execute_input":"2026-05-27T02:40:23.385674Z","iopub.status.idle":"2026-05-27T02:40:33.778858Z","shell.execute_reply.started":"2026-05-27T02:40:23.385653Z","shell.execute_reply":"2026-05-27T02:40:33.777989Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":26.295867,"end_time":"2024-05-22T05:46:45.947426","exception":false,"start_time":"2024-05-22T05:46:19.651559","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Now, let's apply the inverse_transform to compute\n# the approximation of images based on the 10 principal\n# compontents.\n# We can see that with just 10 components we convey a large\n# part of the information of each image.\n\npca_object = pca.named_steps['pca']\n\nfig, axs = plt.subplots(nrows=10, ncols=2, figsize=(6,20))\n\nfor i in range(10):\n    idx = np.where(y_train == i )[0][0]\n    original_img = axs[i, 0].imshow(\n        X=X_train[idx].reshape(28, 28),\n        cmap='Blues'\n    )\n    original_img.axes.get_yaxis().set_visible(False)\n    original_img.axes.get_xaxis().set_visible(False)\n    original_img.axes.set_title(f'Original [{i}]:')\n    \n    approx_img_ = pca_object.inverse_transform(X=X_val[idx])\n    approx_img = axs[i, 1].imshow(\n        X=approx_img_.reshape(28, 28),\n        cmap='Blues'\n    )\n    approx_img.axes.get_yaxis().set_visible(False)\n    approx_img.axes.get_xaxis().set_visible(False)\n    approx_img.axes.set_title(f'Predicted angle [{y_pred_angle_degree[idx]}]:')","metadata":{"_cell_guid":"1393d26a-3a87-4a77-a642-c5b68c94fec3","_uuid":"bc4e4c18-00b8-4441-900b-83786685d798","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:33.779891Z","iopub.execute_input":"2026-05-27T02:40:33.780145Z","iopub.status.idle":"2026-05-27T02:40:34.132353Z","shell.execute_reply.started":"2026-05-27T02:40:33.780116Z","shell.execute_reply":"2026-05-27T02:40:34.131121Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":1.00165,"end_time":"2024-05-22T05:46:46.981876","exception":false,"start_time":"2024-05-22T05:46:45.980226","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<img src=\"https://taewanmerepo.github.io/2018/01/cnn/head.png\">","metadata":{"_cell_guid":"d606a8da-01ba-4650-b234-842037898d97","_uuid":"5a238d9f-7fbe-4d2a-85c7-b38f1793fe47","papermill":{"duration":0.032566,"end_time":"2024-05-22T05:46:47.114816","exception":false,"start_time":"2024-05-22T05:46:47.08225","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import plotly.express as px\nfrom plotly.offline import init_notebook_mode, iplot\nimport plotly.graph_objects as go\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"execution":{"iopub.status.busy":"2026-05-27T02:40:34.137528Z","iopub.execute_input":"2026-05-27T02:40:34.137854Z","iopub.status.idle":"2026-05-27T02:40:34.144125Z","shell.execute_reply.started":"2026-05-27T02:40:34.137828Z","shell.execute_reply":"2026-05-27T02:40:34.143006Z"},"papermill":{"duration":0.064014,"end_time":"2024-05-22T05:46:47.278407","exception":false,"start_time":"2024-05-22T05:46:47.214393","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_confusion_matrix(cm, labels, title):\n    # cm : confusion matrix list(list)\n    # labels : name of the data list(str)\n    # title : title for the heatmap\n    data = go.Heatmap(z=cm, y=labels, x=labels)\n    annotations = []\n    for i, row in enumerate(cm):\n        for j, value in enumerate(row):\n            annotations.append(\n                {\n                    \"x\": labels[i],\n                    \"y\": labels[j],\n                    \"font\": {\"color\": \"white\"},\n                    \"text\": str(value),\n                    \"xref\": \"x1\",\n                    \"yref\": \"y1\",\n                    \"showarrow\": False\n                }\n            )\n    layout = {\n        \"title\": title,\n        \"xaxis\": {\"title\": \"Predicted value\"},\n        \"yaxis\": {\"title\": \"Real value\"},\n        \"annotations\": annotations\n    }\n    fig = go.Figure(data=data, layout=layout)\n    return fig","metadata":{"execution":{"iopub.status.busy":"2026-05-27T02:40:34.145484Z","iopub.execute_input":"2026-05-27T02:40:34.145768Z","iopub.status.idle":"2026-05-27T02:40:34.16256Z","shell.execute_reply.started":"2026-05-27T02:40:34.145736Z","shell.execute_reply":"2026-05-27T02:40:34.160703Z"},"papermill":{"duration":0.044522,"end_time":"2024-05-22T05:46:47.356265","exception":false,"start_time":"2024-05-22T05:46:47.311743","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from tensorflow.keras.utils import np_utils\nfrom tensorflow.keras import utils\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\nfrom tensorflow.keras import datasets, layers, models","metadata":{"_cell_guid":"0d0efe69-4e03-4204-86d8-3ee2e5125830","_uuid":"17fc4707-d986-4950-b0fe-ac152932ac00","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:34.164267Z","iopub.execute_input":"2026-05-27T02:40:34.164692Z","iopub.status.idle":"2026-05-27T02:40:34.196852Z","shell.execute_reply.started":"2026-05-27T02:40:34.164653Z","shell.execute_reply":"2026-05-27T02:40:34.194391Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":12.71542,"end_time":"2024-05-22T05:47:00.10591","exception":false,"start_time":"2024-05-22T05:46:47.39049","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"papermill":{"duration":0.032826,"end_time":"2024-05-22T05:47:00.172971","exception":false,"start_time":"2024-05-22T05:47:00.140145","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train_cnn = (X_train_rotate / 255.0).reshape(-1, 28, 28, 1)\ny_train_cnn = utils.to_categorical(np.array(y_train_rotate // numRotate_prior))\n\nX_val = (X_train / 255.0).reshape(-1, 28, 28, 1)\nY_val = utils.to_categorical(np.array(y_train))\n\nX_train_cnn.shape","metadata":{"_cell_guid":"b20ca6f3-b288-42fe-8210-9e804c27268e","_uuid":"598e24f0-4665-4486-ba5d-fb550022bc96","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:34.198454Z","iopub.execute_input":"2026-05-27T02:40:34.1988Z","iopub.status.idle":"2026-05-27T02:40:34.288766Z","shell.execute_reply.started":"2026-05-27T02:40:34.198769Z","shell.execute_reply":"2026-05-27T02:40:34.287709Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.04601,"end_time":"2024-05-22T05:47:00.252423","exception":false,"start_time":"2024-05-22T05:47:00.206413","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train_cnn.shape","metadata":{"execution":{"iopub.status.busy":"2026-05-27T02:40:34.290009Z","iopub.execute_input":"2026-05-27T02:40:34.290276Z","iopub.status.idle":"2026-05-27T02:40:34.295226Z","shell.execute_reply.started":"2026-05-27T02:40:34.290249Z","shell.execute_reply":"2026-05-27T02:40:34.294603Z"},"papermill":{"duration":0.04305,"end_time":"2024-05-22T05:47:00.328899","exception":false,"start_time":"2024-05-22T05:47:00.285849","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# y_train_rotate// numRotate_prior","metadata":{"execution":{"iopub.status.busy":"2026-05-27T02:40:34.295976Z","iopub.execute_input":"2026-05-27T02:40:34.296188Z","iopub.status.idle":"2026-05-27T02:40:34.320562Z","shell.execute_reply.started":"2026-05-27T02:40:34.296166Z","shell.execute_reply":"2026-05-27T02:40:34.319528Z"},"papermill":{"duration":0.043049,"end_time":"2024-05-22T05:47:00.406235","exception":false,"start_time":"2024-05-22T05:47:00.363186","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# X_train_rotate = np.array(X_train_rotate).reshape(-1,28,28,1)\n# # X_val = np.array(pca.transform(X_train)).reshape(-1,28,28,1)\n# X_val = np.array(X_test_crop).reshape(-1,28,28,1)\n# y_train_rotate = utils.to_categorical(np.array(y_train_rotate))\n# Y_val = utils.to_categorical(np.array(y_test_crop))","metadata":{"_cell_guid":"9d27fd4b-f9a8-4c46-a856-7e8970246998","_uuid":"f978abbd-867f-46a5-b6ec-35f64e740f64","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:34.321663Z","iopub.execute_input":"2026-05-27T02:40:34.321934Z","iopub.status.idle":"2026-05-27T02:40:34.346778Z","shell.execute_reply.started":"2026-05-27T02:40:34.321906Z","shell.execute_reply":"2026-05-27T02:40:34.345459Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.041942,"end_time":"2024-05-22T05:47:00.481558","exception":false,"start_time":"2024-05-22T05:47:00.439616","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = models.Sequential()\nmodel.add(layers.Conv2D(filters = 32, kernel_size = (5,5),padding = 'Same', \n                 activation ='relu', input_shape = (28,28,1)))\nmodel.add(layers.Conv2D(filters = 32, kernel_size = (5,5),padding = 'Same', \n                 activation ='relu'))\nmodel.add(layers.MaxPool2D(pool_size=(2,2)))\nmodel.add(layers.BatchNormalization())\nmodel.add(layers.Dropout(0.25))\nmodel.add(layers.Conv2D(filters = 64, kernel_size = (3,3),padding = 'Same', \n                 activation ='relu'))\nmodel.add(layers.Conv2D(filters = 64, kernel_size = (3,3),padding = 'Same', \n                 activation ='relu'))\nmodel.add(layers.MaxPool2D(pool_size=(2,2), strides=(2,2)))\nmodel.add(layers.BatchNormalization())\nmodel.add(layers.Dropout(0.25))\nmodel.add(layers.Flatten())\nmodel.add(layers.Dense(256, activation = \"relu\"))\nmodel.add(layers.Dropout(0.5))\nmodel.add(layers.BatchNormalization())\nmodel.add(layers.Dense(10, activation = \"softmax\"))\n\nmodel.summary()","metadata":{"_cell_guid":"43ead369-05c5-4e2d-960c-d5773b8a7f7d","_uuid":"d30da279-3261-4e97-9b79-6e2a01d703d4","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:34.347861Z","iopub.execute_input":"2026-05-27T02:40:34.348123Z","iopub.status.idle":"2026-05-27T02:40:34.542843Z","shell.execute_reply.started":"2026-05-27T02:40:34.348098Z","shell.execute_reply":"2026-05-27T02:40:34.541674Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.403113,"end_time":"2024-05-22T05:47:00.918032","exception":false,"start_time":"2024-05-22T05:47:00.514919","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_path = './best_model.h5'\n\n# Callback lưu model có val_accuracy tốt nhất\ncheckpointer = ModelCheckpoint(\n    filepath=model_path,\n    monitor='val_accuracy',\n    verbose=1,\n    mode='max',\n    save_best_only=True\n)\n\n# Callback giảm learning rate khi val_loss không còn giảm\nlr_reducer = ReduceLROnPlateau(\n    monitor='val_loss',\n    factor=0.5,\n    patience=3,\n    min_lr=1e-5,\n    verbose=1\n)\n\n# Callback dừng sớm khi val_accuracy không cải thiện\nearlystop = EarlyStopping(\n    monitor='val_accuracy',\n    patience=7,\n    restore_best_weights=True,\n    verbose=1\n)\n\n# Compile mô hình\nmodel.compile(\n    loss='categorical_crossentropy',\n    optimizer='adam',\n    metrics=['accuracy']\n)","metadata":{"_cell_guid":"db889d42-b8bb-4667-8f07-b831a3fbcf38","_uuid":"d131aa49-918d-43dd-be19-62d3110ca17a","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:34.544371Z","iopub.execute_input":"2026-05-27T02:40:34.544707Z","iopub.status.idle":"2026-05-27T02:40:34.557516Z","shell.execute_reply.started":"2026-05-27T02:40:34.544679Z","shell.execute_reply":"2026-05-27T02:40:34.556267Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.070025,"end_time":"2024-05-22T05:47:01.024751","exception":false,"start_time":"2024-05-22T05:47:00.954726","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    X_train_cnn, y_train_cnn,\n    validation_data=(X_val, Y_val),\n    epochs=50,\n    batch_size=128,\n    callbacks=[checkpointer, lr_reducer, earlystop]\n)","metadata":{"_cell_guid":"cd8420f8-3c35-407f-93de-b1691d1b534f","_uuid":"620687bc-89d9-40dc-b133-006fef4e7c78","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:40:34.559304Z","iopub.execute_input":"2026-05-27T02:40:34.559732Z","iopub.status.idle":"2026-05-27T02:44:36.895861Z","shell.execute_reply.started":"2026-05-27T02:40:34.559698Z","shell.execute_reply":"2026-05-27T02:44:36.894648Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":1499.355943,"end_time":"2024-05-22T06:12:00.417221","exception":false,"start_time":"2024-05-22T05:47:01.061278","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Epochs:\", len(history.history['loss']))\nprint(\"Loss:\", history.history['loss'])\nprint(\"Accuracy:\", history.history['accuracy'])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\nimport numpy as np\nimport plotly.io as pio\npio.renderers.default = 'notebook'  # hoặc 'iframe', 'colab', 'jupyterlab' tùy môi trường\n\nfig = go.Figure()\n\nlabels = ['Loss', 'Accuracy']\ncolors = ['rgb(67,67,67)', 'rgb(49,130,189)']\n\nmode_size = [8, 8]\nline_size = [2, 2]\n\n# Số epoch thực tế\nepochs = len(history.history['loss'])\n\n# x từ 1 đến epochs\nx_data = np.vstack((np.arange(1, epochs + 1),) * 2)\n\n# y theo history\ny_data = np.array([\n    np.array(history.history['loss']),\n    np.array(history.history['accuracy'])\n])\n\nfor i in range(0, 2):\n    fig.add_trace(go.Scatter(\n        x=x_data[i],\n        y=y_data[i],\n        mode='lines',\n        name=labels[i],\n        line=dict(color=colors[i], width=line_size[i]),\n        connectgaps=True,\n    ))\n\n    # endpoints (điểm đầu & cuối)\n    fig.add_trace(go.Scatter(\n        x=[x_data[i][0], x_data[i][-1]],\n        y=[y_data[i][0], y_data[i][-1]],\n        mode='markers',\n        marker=dict(color=colors[i], size=mode_size[i])\n    ))\n\nfig.update_layout(\n    xaxis=dict(\n        showline=True,\n        showgrid=False,\n        showticklabels=True,\n        linecolor='rgb(204, 204, 204)',\n        linewidth=2,\n        ticks='outside',\n        tickfont=dict(\n            family='Arial',\n            size=12,\n            color='rgb(82, 82, 82)',\n        ),\n    ),\n    yaxis=dict(\n        showgrid=False,\n        zeroline=False,\n        showline=False,\n        showticklabels=False,\n    ),\n    autosize=False,\n    margin=dict(\n        autoexpand=False,\n        l=100,\n        r=20,\n        t=110,\n    ),\n    showlegend=False,\n    plot_bgcolor='white'\n)\n\nannotations = []\n\n# Adding labels\nfor y_trace, label, color in zip(y_data, labels, colors):\n    # label bên trái (epoch đầu)\n    annotations.append(dict(\n        xref='paper', x=0.05, y=y_trace[0],\n        xanchor='right', yanchor='middle',\n        text=label + ' {:.4f}'.format(y_trace[0]),\n        font=dict(family='Arial', size=16),\n        showarrow=False\n    ))\n    # label bên phải (epoch cuối) – dùng phần tử cuối, không hard-code 49\n    annotations.append(dict(\n        xref='paper', x=0.95, y=y_trace[-1],\n        xanchor='left', yanchor='middle',\n        text='{:.4f}'.format(y_trace[-1]),\n        font=dict(family='Arial', size=16),\n        showarrow=False\n    ))\n\n# Title\nannotations.append(dict(\n    xref='paper', yref='paper', x=0.0, y=1.05,\n    xanchor='left', yanchor='bottom',\n    text='Training Loss & Accuracy',\n    font=dict(family='Arial', size=30, color='rgb(37,37,37)'),\n    showarrow=False\n))\n\n# X label\nannotations.append(dict(\n    xref='paper', yref='paper', x=0.5, y=-0.1,\n    xanchor='center', yanchor='top',\n    text='Epochs',\n    font=dict(family='Arial', size=12, color='rgb(150,150,150)'),\n    showarrow=False\n))\n\nfig.update_layout(annotations=annotations, height=600, width=1000)\n\nfig.show()","metadata":{"_cell_guid":"7fcbf806-1db3-45b9-8bc0-3abf08dfabd9","_uuid":"b2a98a55-6032-40d1-8bbf-bf6b35840730","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:44:36.898125Z","iopub.execute_input":"2026-05-27T02:44:36.898457Z","iopub.status.idle":"2026-05-27T02:44:36.935915Z","shell.execute_reply.started":"2026-05-27T02:44:36.898429Z","shell.execute_reply":"2026-05-27T02:44:36.934763Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.516461,"end_time":"2024-05-22T06:12:01.010289","exception":false,"start_time":"2024-05-22T06:12:00.493828","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from keras.models import load_model\nmodel = load_model('./best_model.h5')\ny_pred = model.predict(X_val)\nM = confusion_matrix(np.argmax(Y_val,1), np.argmax(y_pred,1))\nplot_confusion_matrix(M, sorted(np.unique(np.argmax(Y_val,1))), 'Simple CNN Confusion Matrix, Acc({:.2f}%)'.format(accuracy_score(np.argmax(Y_val,1), np.argmax(y_pred,1))*100))","metadata":{"_cell_guid":"bdeca8dc-2a69-4598-b508-873212a964dd","_uuid":"57a8380a-11c2-45fb-8190-4e9da5bce1ed","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:44:36.937349Z","iopub.execute_input":"2026-05-27T02:44:36.937699Z","iopub.status.idle":"2026-05-27T02:45:05.740356Z","shell.execute_reply.started":"2026-05-27T02:44:36.937674Z","shell.execute_reply":"2026-05-27T02:45:05.739363Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":42.029637,"end_time":"2024-05-22T06:12:43.410834","exception":false,"start_time":"2024-05-22T06:12:01.381197","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"n = np.where(np.argmax(y_pred,1)!=np.argmax(Y_val,1))\nn = np.array(n).reshape(-1)\nn","metadata":{"_cell_guid":"3cea1159-64d0-4e09-88e9-7b2f3a4fc1fb","_uuid":"042a5e34-2598-420b-bb05-79a34d48eac3","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:45:05.742918Z","iopub.execute_input":"2026-05-27T02:45:05.7433Z","iopub.status.idle":"2026-05-27T02:45:05.753034Z","shell.execute_reply.started":"2026-05-27T02:45:05.743261Z","shell.execute_reply":"2026-05-27T02:45:05.751963Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.123006,"end_time":"2024-05-22T06:12:43.86138","exception":false,"start_time":"2024-05-22T06:12:43.738374","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# n: index các mẫu dự đoán sai\nn = np.where(np.argmax(y_pred, 1) != np.argmax(Y_val, 1))\nn = np.array(n).reshape(-1)\n\nprint(\"Tổng số mẫu sai:\", len(n))\n\nnum_show = min(32, len(n))  \nX_val_vis = np.array(X_val).reshape(X_val.shape[0], 28, 28)\n\nrows = int(np.ceil(num_show / 8))\ncols = 8\n\nfig, axis = plt.subplots(rows, cols, figsize=(35, 4 * rows))\n\n# Khi rows = 1, axis không phải mảng 2D, nên flatten cho an toàn\naxis = np.array(axis).reshape(-1)\n\nfor i in range(num_show):\n    ax = axis[i]\n    idx = n[i]\n    ax.imshow(X_val_vis[idx], cmap='binary')\n    digit  = np.argmax(y_pred, 1)[idx]\n    digit2 = np.argmax(Y_val, 1)[idx]\n    ax.set(title = f\"Pred: {digit}\\nReal: {digit2}\")\n    ax.axis('off')\n\n# Ẩn các ô thừa (nếu có)\nfor j in range(num_show, len(axis)):\n    axis[j].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"_cell_guid":"33ee870d-1eaa-4702-ac0b-7d41a9333040","_uuid":"470a60fd-078e-4a6b-99bf-206ffd71cfb8","collapsed":false,"execution":{"iopub.status.busy":"2026-05-27T02:45:05.75661Z","iopub.execute_input":"2026-05-27T02:45:05.757616Z","iopub.status.idle":"2026-05-27T02:45:07.333282Z","shell.execute_reply.started":"2026-05-27T02:45:05.757397Z","shell.execute_reply":"2026-05-27T02:45:07.331959Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":764.262633,"end_time":"2024-05-22T06:25:28.231677","exception":false,"start_time":"2024-05-22T06:12:43.969044","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_test = np.array(df_test).reshape(-1,28,28,1)","metadata":{"execution":{"iopub.status.busy":"2026-05-27T02:45:07.334524Z","iopub.execute_input":"2026-05-27T02:45:07.334844Z","iopub.status.idle":"2026-05-27T02:45:07.378131Z","shell.execute_reply.started":"2026-05-27T02:45:07.334798Z","shell.execute_reply":"2026-05-27T02:45:07.377074Z"},"papermill":{"duration":0.220793,"end_time":"2024-05-22T06:25:29.043007","exception":false,"start_time":"2024-05-22T06:25:28.822214","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = load_model('./best_model.h5')\ny_pred = model.predict(X_test)\ny_pred = np.argmax(y_pred,1)\npd_submit = pd.read_csv('/kaggle/input/datasets/deimos0810/digit-recognizer/sample_submission.csv')\npd_submit['Label'] = y_pred\npd_submit.to_csv('submission.csv', header=True, index=False)","metadata":{"execution":{"iopub.status.busy":"2026-05-27T02:45:07.37914Z","iopub.execute_input":"2026-05-27T02:45:07.379383Z","iopub.status.idle":"2026-05-27T02:45:27.180444Z","shell.execute_reply.started":"2026-05-27T02:45:07.379359Z","shell.execute_reply":"2026-05-27T02:45:27.179086Z"},"papermill":{"duration":41.938891,"end_time":"2024-05-22T06:26:11.099993","exception":false,"start_time":"2024-05-22T06:25:29.161102","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null}]}