{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","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"},{"sourceId":2947278,"sourceType":"datasetVersion","datasetId":1237036},{"sourceId":10061466,"sourceType":"datasetVersion","datasetId":6185906},{"sourceId":10077226,"sourceType":"datasetVersion","datasetId":6200689},{"sourceId":10107320,"sourceType":"datasetVersion","datasetId":6234915}],"dockerImageVersionId":30786,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Mô hình nhận dạng chữ số, 4 phép tính cơ bản\n\nCác bước chính:\n* Xử lí dữ liệu số\n* Xây dựng mô hình\n* Xử lí dữ liệu phép tính\n* Hoàn thiện mô hình\n","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd\nimport os","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2024-12-09T01:44:02.789091Z","iopub.execute_input":"2024-12-09T01:44:02.789565Z","iopub.status.idle":"2024-12-09T01:44:02.795007Z","shell.execute_reply.started":"2024-12-09T01:44:02.789526Z","shell.execute_reply":"2024-12-09T01:44:02.793903Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mnist_train = pd.read_csv(\"/kaggle/input/mnistdata/mnist_train.csv\")\nmnist_test = pd.read_csv(\"/kaggle/input/mnistdata/mnist_test.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T01:44:02.797123Z","iopub.execute_input":"2024-12-09T01:44:02.797500Z","iopub.status.idle":"2024-12-09T01:44:07.443602Z","shell.execute_reply.started":"2024-12-09T01:44:02.797456Z","shell.execute_reply":"2024-12-09T01:44:07.442339Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Bước 1: Xử lí dữ liệu số\n\nDữ liệu gốc bao gồm:\n* Dữ liệu train: 60000 chữ số, file .csv\n* Dữ liệu test: 10000 chữ số, file .csv\n* Dữ liệu dấu: 4 phép tính +, -, x, :, mỗi phép tính bao gồm ~600 ảnh, định dạng jpg hoặc png","metadata":{}},{"cell_type":"markdown","source":"File mnist chứa 1 cột label là nhãn của dữ liệu, 784 cột còn lại là các pixel từ ô 1 cột 1, ô 2 cột 1, ..., ô 28 cột 28 được định dạng thành mảng 1 chiều để có thể dễ dàng truy cập\n","metadata":{}},{"cell_type":"code","source":"mnist_train","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T01:44:07.445728Z","iopub.execute_input":"2024-12-09T01:44:07.446229Z","iopub.status.idle":"2024-12-09T01:44:07.470243Z","shell.execute_reply.started":"2024-12-09T01:44:07.446182Z","shell.execute_reply":"2024-12-09T01:44:07.469148Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Đầu tiên, ta tách dữ liệu train thành 2 phần `X_train` chữ dữ liệu ảnh và `y_train` chứa dữ liệu nhãn.\n","metadata":{}},{"cell_type":"code","source":"y_train = mnist_train[\"label\"].copy().to_numpy()\ny_train = y_train.ravel() \nX_train = mnist_train.drop(columns=[\"label\"]).to_numpy()\n\nprint(\"Training data:\\n\", X_train)\nprint(\"Digit labels: \", y_train)\n\ny_test = mnist_test[\"label\"].copy().to_numpy()\nX_test = mnist_test.drop(columns=[\"label\"]).to_numpy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T01:44:07.471511Z","iopub.execute_input":"2024-12-09T01:44:07.471821Z","iopub.status.idle":"2024-12-09T01:44:07.574897Z","shell.execute_reply.started":"2024-12-09T01:44:07.471792Z","shell.execute_reply":"2024-12-09T01:44:07.573807Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%matplotlib inline\nimport matplotlib\nimport matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T01:44:07.577103Z","iopub.execute_input":"2024-12-09T01:44:07.577520Z","iopub.status.idle":"2024-12-09T01:44:07.583422Z","shell.execute_reply.started":"2024-12-09T01:44:07.577488Z","shell.execute_reply":"2024-12-09T01:44:07.582419Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Hiển thị `X_train[2]` bằng `Matplotlib`","metadata":{}},{"cell_type":"code","source":"digitData = X_train[2]\ndigitImage = digitData.reshape(28, 28)\nprint(y_train[2])\nprint(type(digitImage))\nplt.imshow(digitImage)\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T01:44:07.584680Z","iopub.execute_input":"2024-12-09T01:44:07.584999Z","iopub.status.idle":"2024-12-09T01:44:07.668014Z","shell.execute_reply.started":"2024-12-09T01:44:07.584968Z","shell.execute_reply":"2024-12-09T01:44:07.666960Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_digits(data, row_size=5):\n    for i in range(len(data)):\n        ax = plt.subplot(row_size, row_size, i+1)\n        \n        digit = data[i]\n        digit_image = digit.reshape(28, 28)\n        \n        plt.imshow(digit_image)\n        plt.axis(\"off\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T01:44:07.669340Z","iopub.execute_input":"2024-12-09T01:44:07.669808Z","iopub.status.idle":"2024-12-09T01:44:07.677057Z","shell.execute_reply.started":"2024-12-09T01:44:07.669723Z","shell.execute_reply":"2024-12-09T01:44:07.676028Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Lần lượt hiển thị các dữ liệu ảnh khác, ta có thể thấy chúng là dữ liệu chữ viết thô, chưa bo viền, gây khó nhận dạng nếu viết tay ở các vị trí khác nhau\n","metadata":{}},{"cell_type":"code","source":"plot_digits(X_train[:36], row_size=6)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T01:44:07.678238Z","iopub.execute_input":"2024-12-09T01:44:07.679641Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Hàm dùng để bo viền kí tự:","metadata":{}},{"cell_type":"code","source":"from PIL import Image, ImageChops, ImageOps, ImageFilter\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\ndef trim_borders(image):\n    bg = Image.new(image.mode, image.size, image.getpixel((0,0)))\n    diff = ImageChops.difference(image, bg)\n    diff = ImageChops.add(diff, diff, 2.0, -100)\n    bbox = diff.getbbox()\n    if bbox:\n        return image.crop(bbox)\n    return image\n\ndigitImage = Image.fromarray(np.uint8(digitImage))\ndigitImage = trim_borders(digitImage)\nplt.imshow(digitImage)\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Hàm dùng để làm vuông ảnh:\n","metadata":{}},{"cell_type":"code","source":"def make_square(image, min_size=28, fill_color=(0, 0, 0, 0)):\n    x, y = image.size\n    size = max(x, y)\n    newImage = Image.new('RGBA', (size, size), fill_color)\n\n    newImage.paste(image, (int((size - x) / 2), int((size - y) / 2)))\n    return newImage\n\ndigitImage = make_square(digitImage)\nplt.imshow(digitImage)\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Hàm dùng để làm loang ảnh:","metadata":{}},{"cell_type":"code","source":"def average_filter(image):\n    kernel = [1/9] * 9 \n    size = (3, 3)     \n    \n    filtered_image = image.filter(ImageFilter.Kernel(size, kernel, scale=None))\n    return filtered_image\n\ndigitImage = average_filter(digitImage)\nplt.imshow(digitImage)\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Hàm chuyển ảnh thành mảng 1 chiều dựa trên các kênh màu:","metadata":{}},{"cell_type":"code","source":"\ndef imgToArray(img, channel):\n  img_array = np.array(img)\n  channel_index = {'R': 0, 'G': 1, 'B': 2, 'A': 3}[channel]\n  channel_array = img_array[:, :, channel_index]\n  return channel_array.flatten().reshape(1, -1)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Xử lí trên từng dòng của tập dữ liệu cần chuẩn hóa:","metadata":{}},{"cell_type":"code","source":"def solveLine(i, arr):\n    alpha = arr[i]\n    alpha = alpha.astype(np.uint8)\n    alpha = alpha.reshape(28, 28)\n    rgba = np.dstack((np.zeros((28, 28, 3), dtype = np.uint8), alpha))\n    image = Image.fromarray(rgba, mode = \"RGBA\") \n    image = trim_borders(image)\n    image = make_square(image)\n    image = average_filter(image)\n    image = image.resize((28, 28))\n    line = imgToArray(image, 'A')\n    line[0] = line[0].astype(np.int64)\n    arr[i] = line[0]\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Nơi các hàm xử lí dữ liệu được khởi chạy:\nCác dòng trong dữ liệu được chuyển thành ảnh, bo viền, làm vuông, làm loang và được chuyển lại thành 1 dòng đè lên dòng cũ.","metadata":{}},{"cell_type":"code","source":"    \nfor i in range (np.shape(X_train)[0]):\n    solveLine(i, X_train)\n    \nfor i in range (np.shape(X_test)[0]):\n    solveLine(i, X_test)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Bước 2: Huấn luyện mô hình\nKiểm tra mô hình phù hợp bằng cách fit dữ liệu `X_train` và `y_train` và test lại tập dữ liệu số.","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.model_selection import cross_val_predict\nfrom sklearn.model_selection import GridSearchCV\n\nknn = KNeighborsClassifier()\nknn.fit(X_train, y_train)\nknn_preds = cross_val_predict(knn, X_train, y_train, cv=3)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"knn_preds","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"`Confusion matrix` của mô hình được huấn luyện:","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\ncf_mat = confusion_matrix(y_train, knn_preds)\ncm_fig, cm_ax = plt.subplots()\ncf_mat_disp = ConfusionMatrixDisplay(cf_mat)\ncf_mat_disp.plot(ax=cm_ax, cmap=plt.cm.Blues)\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Độ chính xác của mô hình cũng khá khả quan, đạt 97%:","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import classification_report\nprint(classification_report(y_train, knn_preds, digits=6))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Kiểm tra trên các mô hình khác, đầu tiên là Hồi quy Logicstics","metadata":{}},{"cell_type":"code","source":"from sklearn.svm import SVC\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.decomposition import PCA\nfrom sklearn.discriminant_analysis import LinearDiscriminantAnalysis\nfrom sklearn.metrics import accuracy_score\n\nmodel_lr = LogisticRegression(max_iter=1000, random_state=42)\nmodel_lr.fit(X_train, y_train)\ny_pred_lr = model_lr.predict(X_test)\naccuracy_lr = accuracy_score(y_test, y_pred_lr)\nprint(f\"Accuracy of Logistic Regression: {accuracy_lr:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"SVM","metadata":{}},{"cell_type":"code","source":"model_svm = SVC(random_state=42)\nmodel_svm.fit(X_train, y_train)\ny_pred_svm = model_svm.predict(X_test)\naccuracy_svm = accuracy_score(y_test, y_pred_svm)\nprint(f\"Accuracy of SVM: {accuracy_svm:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"LR bằng cách kết hợp PCA","metadata":{}},{"cell_type":"code","source":"pca = PCA(n_components=50)\nX_train_pca = pca.fit_transform(X_train)\nX_test_pca = pca.transform(X_test)\nmodel_lr_pca = LogisticRegression(max_iter=1000, random_state=42)\nmodel_lr_pca.fit(X_train_pca, y_train)\ny_pred_lr_pca = model_lr_pca.predict(X_test_pca)\naccuracy_lr_pca = accuracy_score(y_test, y_pred_lr_pca)\nprint(f\"Accuracy of Logistic Regression with PCA: {accuracy_lr_pca:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"LR bằng cách kết hợp LDA","metadata":{}},{"cell_type":"code","source":"lda = LinearDiscriminantAnalysis(n_components=9) \nX_train_lda = lda.fit_transform(X_train, y_train)\nX_test_lda = lda.transform(X_test)\nmodel_lr_lda = LogisticRegression(max_iter=1000, random_state=42)\nmodel_lr_lda.fit(X_train_lda, y_train)\ny_pred_lr_lda = model_lr_lda.predict(X_test_lda)\naccuracy_lr_lda = accuracy_score(y_test, y_pred_lr_lda)\nprint(f\"Accuracy of Logistic Regression with LDA: {accuracy_lr_lda:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Kết luận: SVM có tỉ lệ chính xác cao nhất","metadata":{}},{"cell_type":"markdown","source":"## Bước 3: Xử lí dữ liệu phép tính\n\nFile dữ liệu phép toán được lưu trữ dưới dạng .jpn và .png, nên chúng ta sẽ đọc chúng và truyền vào các list tương ứng `addImages`, `diviImages`, `mulImages` và `subImages`","metadata":{}},{"cell_type":"code","source":"def loadImageIntoArray(folder_path):\n  images = []\n  for filename in os.listdir(folder_path):\n    if filename.endswith('.jpg') or filename.endswith('.png'): \n      img_path = os.path.join(folder_path, filename)\n      try:\n        img = Image.open(img_path)\n        images.append(img)\n      except IOError:\n        print(f\"Error opening image: {img_path}\")\n\n  return images","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def loadFolder(s):\n    folder_path = '/kaggle/input/handwritten-math-symbol/' + s\n    image_array = loadImageIntoArray(folder_path)\n    print(\"Tổng số ảnh \" + s + \" đã load:\", len(image_array))\n    return image_array\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"addImages = loadFolder(\"add\")\ndiviImages = loadFolder(\"divi\")\nmulImages = loadFolder(\"mul\")\nsubImages = loadFolder(\"sub\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Mở một file bất kì để kiểm tra xem nó đã được đọc đúng chưa","metadata":{}},{"cell_type":"code","source":"plt.imshow(mulImages[0])\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Có vẻ dữ liệu của chúng ta có nền trắng, trước hết ta tiến hành xóa phông để bức ảnh trở thành dữ liệu trong suốt.","metadata":{}},{"cell_type":"code","source":"def remove_white_background(img):\n    new_img = Image.new('RGBA', img.size, (0, 0, 0, 0))\n    pixels = img.getdata()\n    for i, pixel in enumerate(pixels):\n        if pixel[0] != 255 or pixel[1] != 255 or pixel[2] != 255:\n            new_img.putpixel((i % img.width, i // img.width), pixel)\n    return new_img\n    \nimgg = remove_white_background(mulImages[0])\nplt.imshow(imgg)\nplt.axis(\"off\")\nplt.show()\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Sau khi xử lí dữ liệu, chúng ta tiếp tục xử lí như xử lí dữ liệu số và gộp lại thành DataFrame symbol","metadata":{}},{"cell_type":"code","source":"symbol = []\ndef addSymbol(img, i):\n    img = remove_white_background(img)\n    img = trim_borders(img)\n    img = make_square(img)\n    img = average_filter(img)\n    img = img.resize((28, 28))\n    line = imgToArray(img, 'A')\n    alpha = line\n    return np.insert(alpha, 0, i)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i in addImages:\n    symbol.append(addSymbol(i, 11))\nfor i in subImages:\n    symbol.append(addSymbol(i, 12))\nfor i in mulImages:\n    symbol.append(addSymbol(i, 13))\nfor i in diviImages:\n    symbol.append(addSymbol(i, 14))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"symbol = pd.DataFrame(symbol)\nsymbol.columns = mnist_train.columns\nprint(symbol)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Thử nghiệm cho thấy tham số tốt nhất là C=0.1, gamma=1, kernel=rbf ","metadata":{}},{"cell_type":"code","source":"# cvres = pd.DataFrame.from_dict(grid.cv_results_)\n# cvres","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Khớp dữ liệu với mô hình và kiểm tra độ chính xác.","metadata":{}},{"cell_type":"code","source":"from sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.model_selection import GridSearchCV\n\ny_sym = symbol[\"label\"].copy().to_numpy()\nX_sym = symbol.drop(columns=[\"label\"]).to_numpy()\n\nspace = [\n    {\"weights\": [\"uniform\", \"distance\"],\n     \"n_neighbors\": [2, 3, 4, 5, 6, 7, 9, 11, 15, 20, 35]\n    }\n]\nknn = KNeighborsClassifier()\n\nknn.fit(X_sym, y_sym)\nknn_grid_search = GridSearchCV(knn, param_grid=space, cv=3, scoring=\"accuracy\", n_jobs=2)\nknn_grid_search.fit(X_sym, y_sym)\nprint(knn_grid_search.best_estimator_)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Dữ liệu cho ta thấy mô hình đạt độ chính xác khả quan với tỉ lệ chính xác là `99,3%` với `k = 2`","metadata":{}},{"cell_type":"code","source":"cvres = pd.DataFrame.from_dict(knn_grid_search.cv_results_)\ncvres","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Bước 4: Hoàn thiện mô hình.\n\nGộp dữ liệu phép toán và dữ liệu số, tiến hành kiểm tra để tìm ra tham số tốt nhất.","metadata":{}},{"cell_type":"code","source":"X_train = pd.DataFrame(X_train)\nX_sym = pd.DataFrame(X_sym)\ny_train = pd.DataFrame(y_train)\ny_sym = pd.DataFrame(y_sym)\n\nX_train = pd.concat((X_train, X_sym), ignore_index=True)\ny_train = pd.concat((y_train, y_sym), ignore_index=True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Sử dụng GridSearchCV để tìm ra tham số tốt nhất","metadata":{}},{"cell_type":"code","source":"# param_grid = {'C': [0.1, 1, 10, 100, 1000],  \n#               'gamma': [1, 0.1, 0.01, 0.001, 0.0001], \n#               'kernel': ['rbf']}  \n  \n# grid = GridSearchCV(SVC(), param_grid, refit = True, verbose = 3) \n# grid.fit(X_train, y_train)\n\n# print(grid.best_params_) \n# print(grid.best_estimator_) ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_train = y_train.values.ravel() \nmodel_svm = SVC(random_state=42)\nmodel_svm.fit(X_train, y_train)\ny_pred_svm = model_svm.predict(X_test)\naccuracy_svm = accuracy_score(y_test, y_pred_svm)\nprint(f\"Accuracy of SVM: {accuracy_svm:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Kiểm tra dựa trên một dữ liệu bất kì","metadata":{}},{"cell_type":"code","source":"# testData = symbol.iloc[1000]\n# testT = testData[1:]\n# # print(testT.values.reshape(784))\n# # print(testData.values.reshape(785))\n\n# digitImage = model_svm.values.reshape(28, 28)\n# plt.imshow(digitImage)\n# plt.axis(\"off\")\n# plt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Dự đoán chính xác dữ liệu dấu `-`","metadata":{}},{"cell_type":"code","source":"# print(model_svm.predict([testT])[0])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Xuất ra file pickle để có thể sử dụng trực tiếp mô hình mà không cần huấn luyện.","metadata":{}},{"cell_type":"code","source":"import pickle \nknnPickle = open('knnpickle_file', 'wb') \npickle.dump(model_svm, knnPickle)  \nknnPickle.close()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}