{"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"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Аналіз датасету","metadata":{}},{"cell_type":"markdown","source":"Почнемо зі стандартних імпортів для завантаження та перегляду даних:","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n        \nrandom_state = 42\nnp.random.seed(random_state)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-05-22T05:37:11.296292Z","iopub.execute_input":"2021-05-22T05:37:11.296687Z","iopub.status.idle":"2021-05-22T05:37:11.308419Z","shell.execute_reply.started":"2021-05-22T05:37:11.296607Z","shell.execute_reply":"2021-05-22T05:37:11.307404Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/digit-recognizer/train.csv')\ntest = pd.read_csv('/kaggle/input/digit-recognizer/test.csv')\ntrain_labels = train['label']\ntrain = train.drop(columns=['label'])","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:11.334886Z","iopub.execute_input":"2021-05-22T05:37:11.335140Z","iopub.status.idle":"2021-05-22T05:37:15.961415Z","shell.execute_reply.started":"2021-05-22T05:37:11.335116Z","shell.execute_reply":"2021-05-22T05:37:15.960515Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"В `train` та `test` кожне зображення представлено рядком пікселів, кожний з яких приймає значення від 0 до 255, що можна інтерпретувати як яскравість: 0 для чорного пікселя та 255 для білого:","metadata":{}},{"cell_type":"code","source":"display(train)\ntrain.max(axis=1)","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:15.965037Z","iopub.execute_input":"2021-05-22T05:37:15.965304Z","iopub.status.idle":"2021-05-22T05:37:16.031664Z","shell.execute_reply.started":"2021-05-22T05:37:15.965277Z","shell.execute_reply":"2021-05-22T05:37:16.030760Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Для подальшої роботи буде зручно розділити всі значення на 255, щоб вони були в діапазоні [0;1].","metadata":{}},{"cell_type":"code","source":"train, test = train/255, test/255","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:16.033456Z","iopub.execute_input":"2021-05-22T05:37:16.033791Z","iopub.status.idle":"2021-05-22T05:37:16.231956Z","shell.execute_reply.started":"2021-05-22T05:37:16.033756Z","shell.execute_reply":"2021-05-22T05:37:16.231125Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Типові зображеня в датасеті, розподіл класів","metadata":{}},{"cell_type":"markdown","source":"Подивимося на випадкові зображення з `train`:","metadata":{}},{"cell_type":"code","source":"pic_num = 16\nfig, axes = plt.subplots(pic_num, pic_num, figsize=(8, 8))\nrand_ind = np.random.choice(len(train), size=pic_num**2)\nselected_pics = train.iloc[rand_ind].to_numpy().reshape((-1, 28, 28))\nfor num, pic in enumerate(selected_pics):\n    axes[num//pic_num][num%pic_num].imshow(pic, cmap='gray')\n    axes[num//pic_num][num%pic_num].set_xticks([])\n    axes[num//pic_num][num%pic_num].set_yticks([])\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:16.234450Z","iopub.execute_input":"2021-05-22T05:37:16.234977Z","iopub.status.idle":"2021-05-22T05:37:25.821165Z","shell.execute_reply.started":"2021-05-22T05:37:16.234936Z","shell.execute_reply":"2021-05-22T05:37:25.820352Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Також подивимося на розподіл класів у `train`:","metadata":{}},{"cell_type":"code","source":"plt.hist(train_labels, edgecolor='black')\nplt.hlines(len(train_labels)/10, 0, 9, color='tab:orange')\nplt.xticks(np.arange(0,10))\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:25.822215Z","iopub.execute_input":"2021-05-22T05:37:25.822667Z","iopub.status.idle":"2021-05-22T05:37:25.982201Z","shell.execute_reply.started":"2021-05-22T05:37:25.822626Z","shell.execute_reply":"2021-05-22T05:37:25.981458Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Як видно з гістограми,розподіл класів майже рівномірний,хоча зображень одиниць трохи більше, ніж інших цифр. Це може бути пов'язано з тим, що при збиранні даних одиниці в рукописних текстах траплялися трохи частіше.","metadata":{}},{"cell_type":"markdown","source":"Цікаво подивитися на усереднені зображення кожної цифри з `train` та на усереднене зображення у всьому датасеті - як в `train`, так і в `test`.","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(2, 5, figsize=(8, 3))\nfor i in range(10):\n    ind = train_labels[train_labels == i].index.to_numpy()\n    axes[i//5][i%5].imshow(train.iloc[ind].mean().to_numpy().reshape((28, 28)), cmap='plasma')\n    axes[i//5][i%5].set_xticks([])\n    axes[i//5][i%5].set_yticks([])\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:25.983337Z","iopub.execute_input":"2021-05-22T05:37:25.983692Z","iopub.status.idle":"2021-05-22T05:37:26.762203Z","shell.execute_reply.started":"2021-05-22T05:37:25.983652Z","shell.execute_reply":"2021-05-22T05:37:26.761172Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Отже, можна побачити середнє написання кожної цифри у `train`. Деякі цифри всі люди пишуть більш-менш однаково, як-от 0, 2, 3, 5, 7, 8, а у деяких видно відмінності.\nНаприклад, схоже, що 1 пишуть з різним нахилом, 4 - двома способами, а в 9 нижню риску загинають по-різному. Щоб перевірити це припущення, знову подивимося на зображення випадкових цифр, але вже впорядковані за значенням цифр.","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(10, 30, figsize=(24, 8))\nfor i in range(10):\n    ind = train_labels[train_labels == i].index.to_numpy()\n    rand_ind = np.random.choice(ind, size=30)\n    for j in range(30):\n        axes[i][j].imshow(train.iloc[rand_ind[j]].to_numpy().reshape((28, 28)), cmap='gray')\n        axes[i][j].set_xticks([])\n        axes[i][j].set_yticks([])\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:26.763694Z","iopub.execute_input":"2021-05-22T05:37:26.764025Z","iopub.status.idle":"2021-05-22T05:37:37.928507Z","shell.execute_reply.started":"2021-05-22T05:37:26.763988Z","shell.execute_reply":"2021-05-22T05:37:37.927718Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Дійсно, з випадкових 30 зображеннях кожної цифри видно, що різних написань кожної цифри багато, але, наприклад, багато варіантів 0, 1 (за винятком кута нахилу), 3, 5, 6, 8 (теж за винятком кута нахилу) та 9 є схожими. Більш різноманітними є 2, 4 та 7.","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 7))\n\navg_sample_train = train.mean().to_numpy().reshape((28, 28))\naxes[0].imshow(avg_sample_train, cmap='plasma')\naxes[0].set_title('Average train picture')\n\navg_sample_test = test.mean().to_numpy().reshape((28, 28))\naxes[1].imshow(avg_sample_test, cmap='plasma')\naxes[1].set_title('Average test picture')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:37.931084Z","iopub.execute_input":"2021-05-22T05:37:37.931398Z","iopub.status.idle":"2021-05-22T05:37:38.492837Z","shell.execute_reply.started":"2021-05-22T05:37:37.931371Z","shell.execute_reply":"2021-05-22T05:37:38.491882Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"З цих усереднених зображень можна зробити висновок, що на багатьох зображеннях є досить багато пустого місця: в середньому, по 5 пікселів зліва і справа.","metadata":{}},{"cell_type":"markdown","source":"## Зниження розмірності","metadata":{}},{"cell_type":"code","source":"from sklearn.manifold import TSNE\nfrom sklearn.cluster import KMeans\nfrom sklearn.decomposition import PCA","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:38.494807Z","iopub.execute_input":"2021-05-22T05:37:38.495160Z","iopub.status.idle":"2021-05-22T05:37:39.471078Z","shell.execute_reply.started":"2021-05-22T05:37:38.495121Z","shell.execute_reply":"2021-05-22T05:37:39.470286Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Почнемо з застосування TSNE:","metadata":{}},{"cell_type":"code","source":"train_embedded = TSNE(n_components=2, random_state=random_state).fit_transform(train)\nfig, axes = plt.subplots(figsize=(12, 8))\nscatter = axes.scatter(train_embedded[:, 0], train_embedded[:, 1], c=train_labels, cmap='tab10', s=0.3)\nhandles, _ = scatter.legend_elements(prop='colors')\nplt.legend(handles, np.arange(0, 10))\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:37:39.472437Z","iopub.execute_input":"2021-05-22T05:37:39.472764Z","iopub.status.idle":"2021-05-22T05:45:28.360014Z","shell.execute_reply.started":"2021-05-22T05:37:39.472729Z","shell.execute_reply":"2021-05-22T05:45:28.359174Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"TSNE намагається зберігати взаємозв'язки між точками у датасеті при проектуванні на площину, але тим не менш цікаво, що навіть при такому проектуванні з 784-вимірного простору на 2-вимірну площину, різні класи знаходяться на не дуже малій відстані один від одного. Звісно, деякі точки не потрапили до \"своїх\" областей, що можна пояснити якимось винятковим написанням. Можна також помітити, що деяка частина зображень 8 потрапила поруч з 5 - це може означати, що деяка частина зображень 8  схожа на зображення 5.","metadata":{}},{"cell_type":"markdown","source":"Після такого результату роботи T-SNE цікаво спробувати запустити кластеризацію за допомогою K-means.","metadata":{}},{"cell_type":"code","source":"km = KMeans(n_clusters=10, random_state=random_state).fit(train_embedded)\nkm_labels = km.labels_\nfig, axes = plt.subplots(figsize=(12, 8))\nscatter = axes.scatter(train_embedded[:, 0], train_embedded[:, 1], c=km_labels, cmap='tab10', s=0.3)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:45:28.361419Z","iopub.execute_input":"2021-05-22T05:45:28.361766Z","iopub.status.idle":"2021-05-22T05:45:30.376161Z","shell.execute_reply.started":"2021-05-22T05:45:28.361729Z","shell.execute_reply":"2021-05-22T05:45:30.370352Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Але K-means, як видно, не вдалося точно відділити кластери, що відповідають різним цифрам. Це можна пояснити тим, що деякі з них знаходяться поруч.","metadata":{}},{"cell_type":"markdown","source":"Оскільки, як зазначалося раніше, на зображеннях багато чорних пікселів, можна перевірити, що дасть застосування PCA. Доречно перевіряти пониження розмірності лише до \"квадратних\", щоб отримані зменшені версії можна було переглянути, як квадратні зображення.\nДля кожної розмірності дивитимемося на суму `explained_variance_ratio_` - сумарну частку дисперсії, що пояснюється вибраними головними компонентами.","metadata":{}},{"cell_type":"code","source":"var_ratios = []\nfor dim in range(2, 28):\n    temp_pca = PCA(n_components=dim**2).fit(train)\n    var_ratios.append(sum(temp_pca.explained_variance_ratio_))","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:45:30.377463Z","iopub.execute_input":"2021-05-22T05:45:30.377969Z","iopub.status.idle":"2021-05-22T05:48:54.603221Z","shell.execute_reply.started":"2021-05-22T05:45:30.377931Z","shell.execute_reply":"2021-05-22T05:48:54.602325Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20,8))\nplt.plot(np.arange(2, 28)**2, var_ratios)\nplt.scatter(np.arange(2, 28)**2, var_ratios)\nplt.hlines(0.99, 4, 729, color='tab:green', label='0.99')\nplt.hlines(0.97, 4, 729, color='tab:orange', label='0.97')\nplt.hlines(0.95, 4, 729, color='tab:red', label='0.95')\nplt.xticks(np.arange(2, 28)**2)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:48:54.604503Z","iopub.execute_input":"2021-05-22T05:48:54.604849Z","iopub.status.idle":"2021-05-22T05:48:54.858722Z","shell.execute_reply.started":"2021-05-22T05:48:54.604815Z","shell.execute_reply":"2021-05-22T05:48:54.857789Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Отже, видно, що зниження розмірності до 18\\*18 збегірає приблизно 99% інформативності даних, до 15\\*15 - приблизно 97%, а до 12\\*12 - приблизно 95%.\nДо зображень, які будуть отримані після зменшення розмірності до 18\\*18 (тобто, приблизно 41% від оригінальної розмірності), ще повернемося.","metadata":{}},{"cell_type":"code","source":"train_pca = PCA(n_components=18**2).fit_transform(train)\nfig, axes = plt.subplots(10, 30, figsize=(24, 8))\nfor i in range(10):\n    ind = train_labels[train_labels == i].index.to_numpy()\n    rand_ind = np.random.choice(ind, size=30)\n    for j in range(30):\n        axes[i][j].imshow(train_pca[rand_ind[j]].reshape((18, 18)), cmap='gray')\n        axes[i][j].set_xticks([])\n        axes[i][j].set_yticks([])\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:48:54.860088Z","iopub.execute_input":"2021-05-22T05:48:54.860455Z","iopub.status.idle":"2021-05-22T05:49:16.844080Z","shell.execute_reply.started":"2021-05-22T05:48:54.860417Z","shell.execute_reply":"2021-05-22T05:49:16.843139Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Після застосування PCA у перетворених даних цифри вже побачити не можна, оскільки тепер кожна координата нового 324-вектору вже описує якусь комбінацію пікселів з оригінальних даних.","metadata":{}},{"cell_type":"markdown","source":"# Логістична регресія та Random Forest","metadata":{}},{"cell_type":"markdown","source":"В першу чергу перевіримо, які результати покажуть ці дві моделі.","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split, GridSearchCV\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.ensemble import RandomForestClassifier","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:49:16.845594Z","iopub.execute_input":"2021-05-22T05:49:16.845938Z","iopub.status.idle":"2021-05-22T05:49:16.872708Z","shell.execute_reply.started":"2021-05-22T05:49:16.845900Z","shell.execute_reply":"2021-05-22T05:49:16.871989Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"10% з `train` піде у validation-датасет. Також, порівняємо результати і з використанням датасету зі зниженою до 18\\*18 розмірністю.","metadata":{}},{"cell_type":"code","source":"x_train, x_val, y_train, y_val = train_test_split(train, train_labels, test_size=0.1, random_state=random_state)\nx_train_pca, x_val_pca, y_train_pca, y_val_pca = train_test_split(train_pca, train_labels, test_size=0.1, random_state=random_state)","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:49:16.873902Z","iopub.execute_input":"2021-05-22T05:49:16.874239Z","iopub.status.idle":"2021-05-22T05:49:17.103891Z","shell.execute_reply.started":"2021-05-22T05:49:16.874203Z","shell.execute_reply":"2021-05-22T05:49:17.103039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Всі подальші результати будемо зберігати для порівняння.","metadata":{}},{"cell_type":"code","source":"results = dict()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:49:17.105173Z","iopub.execute_input":"2021-05-22T05:49:17.105536Z","iopub.status.idle":"2021-05-22T05:49:17.109630Z","shell.execute_reply.started":"2021-05-22T05:49:17.105498Z","shell.execute_reply":"2021-05-22T05:49:17.108688Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Логістична регресія","metadata":{}},{"cell_type":"markdown","source":"Спочатку на звичайному датасеті:","metadata":{}},{"cell_type":"code","source":"log_reg_clf = LogisticRegression(max_iter=1000)\nlog_reg_clf.fit(x_train, y_train)\nprint('train score', log_reg_clf.score(x_train, y_train))\nprint('val score', log_reg_clf.score(x_val, y_val))\nresults['log_reg_orig'] = {'train': log_reg_clf.score(x_train, y_train), 'val': log_reg_clf.score(x_val, y_val)}","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:49:17.110806Z","iopub.execute_input":"2021-05-22T05:49:17.111143Z","iopub.status.idle":"2021-05-22T05:51:20.569045Z","shell.execute_reply.started":"2021-05-22T05:49:17.111107Z","shell.execute_reply":"2021-05-22T05:51:20.568072Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"І на \"зменшеному\":","metadata":{}},{"cell_type":"code","source":"log_reg_clf_pca = LogisticRegression(max_iter=1000)\nlog_reg_clf_pca.fit(x_train_pca, y_train)\nprint('train score', log_reg_clf_pca.score(x_train_pca, y_train))\nprint('val score', log_reg_clf_pca.score(x_val_pca, y_val))\nresults['log_reg_pca'] = {'train': log_reg_clf_pca.score(x_train_pca, y_train), 'val': log_reg_clf_pca.score(x_val_pca, y_val)}","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:51:20.573303Z","iopub.execute_input":"2021-05-22T05:51:20.573671Z","iopub.status.idle":"2021-05-22T05:51:45.074740Z","shell.execute_reply.started":"2021-05-22T05:51:20.573634Z","shell.execute_reply":"2021-05-22T05:51:45.073864Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"На \"зменшеному\" датасеті результати майже не змінились, що виглядає логічно, адже він зберіг приблизно 99% інформативності оригінальних даних. Але все ж таки, для такої нескладної (з погляду людини) задачі, як класифікація цифр з зображень, точність у 91% не виглядає надто переконливо. Подивимося, які помилки зробив класифікатор:","metadata":{}},{"cell_type":"code","source":"def show_mistakes(pic_num, val_pred):\n    val_pred_temp = pd.Series(val_pred, index=y_val.index)\n    mistakes = y_val[y_val != val_pred_temp].index\n    fig, axes = plt.subplots(pic_num, pic_num, figsize=(pic_num, pic_num*1.25))\n    rand_ind = np.random.choice(mistakes, size=pic_num**2)\n    selected_pics = x_val.loc[rand_ind].to_numpy().reshape((-1, 28, 28))\n    selected_pred = list(val_pred_temp[rand_ind])\n    for num, pic in enumerate(selected_pics):\n        axes[num//pic_num][num%pic_num].imshow(pic, cmap='gray')\n        axes[num//pic_num][num%pic_num].set_xticks([])\n        axes[num//pic_num][num%pic_num].set_yticks([])\n        axes[num//pic_num][num%pic_num].set_title(f'Pred: {selected_pred[num]}')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:51:45.076196Z","iopub.execute_input":"2021-05-22T05:51:45.076773Z","iopub.status.idle":"2021-05-22T05:51:45.086941Z","shell.execute_reply.started":"2021-05-22T05:51:45.076734Z","shell.execute_reply":"2021-05-22T05:51:45.085824Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_mistakes(8, log_reg_clf.predict(x_val))","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:51:45.088396Z","iopub.execute_input":"2021-05-22T05:51:45.089047Z","iopub.status.idle":"2021-05-22T05:51:47.400423Z","shell.execute_reply.started":"2021-05-22T05:51:45.089009Z","shell.execute_reply":"2021-05-22T05:51:47.399598Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Деякі з помилок можна пояснити схожим написанням передбаченої та реальної цифри, але більшість помилкових цифр все ж таки візуально добре відрізняються від передбачень класифікатора.","metadata":{}},{"cell_type":"markdown","source":"## Random Forest","metadata":{}},{"cell_type":"markdown","source":"Наступною моделлю буде Random Forest з використанням Grid Search для параметру `n_estimators`.","metadata":{}},{"cell_type":"markdown","source":"На звичайному датасеті:","metadata":{}},{"cell_type":"code","source":"#rand_for_clf = GridSearchCV(RandomForestClassifier(random_state=random_state), {'n_estimators': [100, 150, 200, 250]})\nrand_for_clf = RandomForestClassifier(n_estimators=200, random_state=random_state)\nrand_for_clf.fit(x_train, y_train)\n#print(rand_for_clf.best_params_)\nprint('train', rand_for_clf.score(x_train, y_train))\nprint('val', rand_for_clf.score(x_val, y_val))\nresults['rand_for_orig'] = {'train': rand_for_clf.score(x_train, y_train), 'val': rand_for_clf.score(x_val, y_val)}","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:51:47.401663Z","iopub.execute_input":"2021-05-22T05:51:47.402007Z","iopub.status.idle":"2021-05-22T05:52:36.588381Z","shell.execute_reply.started":"2021-05-22T05:51:47.401971Z","shell.execute_reply":"2021-05-22T05:52:36.587543Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"І на \"зменшеному\":","metadata":{}},{"cell_type":"code","source":"#rand_for_clf_pca = GridSearchCV(RandomForestClassifier(random_state=random_state), {'n_estimators': [100, 150, 200, 250]})\nrand_for_clf_pca = RandomForestClassifier(n_estimators=200, random_state=random_state)\nrand_for_clf_pca.fit(x_train_pca, y_train_pca)\n#print(rand_for_clf_pca.best_params_)\nprint('train', rand_for_clf_pca.score(x_train_pca, y_train_pca))\nprint('val', rand_for_clf_pca.score(x_val_pca, y_val_pca))\nresults['rand_for_pca'] = {'train': rand_for_clf_pca.score(x_train_pca, y_train_pca), 'val': rand_for_clf_pca.score(x_val_pca, y_val_pca)}","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:52:36.592321Z","iopub.execute_input":"2021-05-22T05:52:36.592576Z","iopub.status.idle":"2021-05-22T05:55:18.306513Z","shell.execute_reply.started":"2021-05-22T05:52:36.592549Z","shell.execute_reply":"2021-05-22T05:55:18.305655Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Оверфіт нікуди не дівся, але тепер точність на валідаційних даних гірша, ніж у випадки оригінальних даних. Можливо, причиною є те, що у \"зменшених\" даних самі зображення вже не є такими репрезентативними, як оригінальні. Також подивимося на помилки класифікатора:","metadata":{}},{"cell_type":"code","source":"show_mistakes(8, rand_for_clf.predict(x_val))","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:55:18.308443Z","iopub.execute_input":"2021-05-22T05:55:18.308788Z","iopub.status.idle":"2021-05-22T05:55:21.084790Z","shell.execute_reply.started":"2021-05-22T05:55:18.308752Z","shell.execute_reply":"2021-05-22T05:55:21.083815Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"У порівнянні з логістичною регресією, більшість помилок Random Forest - саме на зображеннях з дивним написанням.","metadata":{}},{"cell_type":"markdown","source":"Цікаво подивитися на feature importance у Random Forest, натренованому на оригінальних даних, та порівняти її з усередненим зображенням з `train`, яке будувалося раніше:","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 7))\n\naxes[0].imshow(avg_sample_train, cmap='plasma')\naxes[0].set_title('Average train picture')\n\nfeature_imp = rand_for_clf.feature_importances_.reshape((28, 28))\naxes[1].imshow(feature_imp, cmap='plasma')\naxes[1].set_title('RandomForest pixel importance')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:55:21.086125Z","iopub.execute_input":"2021-05-22T05:55:21.086489Z","iopub.status.idle":"2021-05-22T05:55:21.339591Z","shell.execute_reply.started":"2021-05-22T05:55:21.086450Z","shell.execute_reply":"2021-05-22T05:55:21.338651Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Видно, що всі хоч якось важливі пікселі знаходяться приблизно там само, де і пікселі на усередненому зображенні, а найважливіші - в центрі зображення.","metadata":{}},{"cell_type":"markdown","source":"# Моделі Deep Learning","metadata":{}},{"cell_type":"markdown","source":"Спробуємо звичайну нейронну мережу та згорткову мережу з архітектурою LeNet.","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import Model\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense\nfrom tensorflow.keras.callbacks import EarlyStopping\nfrom sklearn.metrics import accuracy_score","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:55:21.340968Z","iopub.execute_input":"2021-05-22T05:55:21.341327Z","iopub.status.idle":"2021-05-22T05:55:26.303975Z","shell.execute_reply.started":"2021-05-22T05:55:21.341291Z","shell.execute_reply":"2021-05-22T05:55:26.303163Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"При тренуванні застосовуватимемо early stopping для уникнення оверфіту","metadata":{}},{"cell_type":"code","source":"es = EarlyStopping(monitor='val_loss', patience=3)","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:55:26.305315Z","iopub.execute_input":"2021-05-22T05:55:26.305643Z","iopub.status.idle":"2021-05-22T05:55:26.310433Z","shell.execute_reply.started":"2021-05-22T05:55:26.305607Z","shell.execute_reply":"2021-05-22T05:55:26.309532Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def make_plots(history):\n    fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 7))\n    axes[0].plot(history.history['loss'], label='train loss')\n    axes[0].plot(history.history['val_loss'], label='val loss')\n    axes[0].set_title('loss value')\n    axes[0].legend()\n    axes[1].plot(history.history['accuracy'], label='train accuracy')\n    axes[1].plot(history.history['val_accuracy'], label='val accuracy')\n    axes[1].set_title('accuracy')\n    axes[1].legend()\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:55:26.311930Z","iopub.execute_input":"2021-05-22T05:55:26.312283Z","iopub.status.idle":"2021-05-22T05:55:26.321476Z","shell.execute_reply.started":"2021-05-22T05:55:26.312233Z","shell.execute_reply":"2021-05-22T05:55:26.320654Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Звичайна нейронна мережа","metadata":{}},{"cell_type":"markdown","source":"Перша модель - нейронна мережа з двома шарами: 784 на вході та 10 на виході.","metadata":{}},{"cell_type":"code","source":"model_1 = Sequential([\n    Dense(units=784, activation='relu'),\n    Dense(units=10, activation='softmax')\n])\n\nmodel_1.compile(optimizer='adam',\n                loss='sparse_categorical_crossentropy',\n                metrics=['accuracy'])\n\nhistory_1 = model_1.fit(x_train, y_train, epochs=50,\n                        validation_data=(x_val, y_val),\n                        use_multiprocessing=True,\n                        callbacks=[es], verbose=1)\n\nprint(model_1.summary())\n\nmake_plots(history_1)\n\ntrain_pred = np.argmax(model_1.predict(x_train), axis=1)\nval_pred = np.argmax(model_1.predict(x_val), axis=1)\nresults['nn_784'] = {'train': accuracy_score(y_train, train_pred), 'val': accuracy_score(y_val, val_pred)}","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:55:26.322654Z","iopub.execute_input":"2021-05-22T05:55:26.323130Z","iopub.status.idle":"2021-05-22T05:55:53.643191Z","shell.execute_reply.started":"2021-05-22T05:55:26.323092Z","shell.execute_reply":"2021-05-22T05:55:53.642342Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Спробуємо ще на \"зменшеному\" датасеті:","metadata":{}},{"cell_type":"code","source":"model_1_reduce = Sequential([\n    Dense(units=324, activation='relu'),\n    Dense(units=10, activation='softmax')\n])\n\nmodel_1_reduce.compile(optimizer='adam',\n                loss='sparse_categorical_crossentropy',\n                metrics=['accuracy'])\n\nhistory_1_reduce = model_1_reduce.fit(x_train_pca, y_train_pca, epochs=50,\n                        validation_data=(x_val_pca, y_val_pca),\n                        use_multiprocessing=True,\n                        callbacks=[es], verbose=1)\n\nprint(model_1_reduce.summary())\n\nmake_plots(history_1_reduce)\n\ntrain_pred = np.argmax(model_1_reduce.predict(x_train_pca), axis=1)\nval_pred = np.argmax(model_1_reduce.predict(x_val_pca), axis=1)\nresults['nn_324'] = {'train': accuracy_score(y_train, train_pred), 'val': accuracy_score(y_val, val_pred)}","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:55:53.644511Z","iopub.execute_input":"2021-05-22T05:55:53.644844Z","iopub.status.idle":"2021-05-22T05:56:13.743464Z","shell.execute_reply.started":"2021-05-22T05:55:53.644810Z","shell.execute_reply":"2021-05-22T05:56:13.742467Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"І знову точність краща, ніж у Random Forest. А також така модель навчалася швидше за першу через меншу кількість параметрів - 108,550 проти 623,290. Знову подивимося на помилки (але лише першої моделі):","metadata":{}},{"cell_type":"code","source":"show_mistakes(8, np.argmax(model_1.predict(x_val), axis=1))","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:56:13.744774Z","iopub.execute_input":"2021-05-22T05:56:13.745124Z","iopub.status.idle":"2021-05-22T05:56:16.277772Z","shell.execute_reply.started":"2021-05-22T05:56:13.745087Z","shell.execute_reply":"2021-05-22T05:56:16.276978Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Як і у випадку Random Forest, мережа припускається помилок на цифрах з ну дуже дивним написанням.","metadata":{}},{"cell_type":"markdown","source":"Спробуємо зробити мережу глибшою, додавши ще шарів:","metadata":{}},{"cell_type":"code","source":"model_2 = Sequential([\n    Dense(units=784, activation='relu'),\n    Dense(units=512, activation='relu'),\n    Dense(units=256, activation='relu'),\n    Dense(units=128, activation='relu'),\n    Dense(units=64, activation='relu'),\n    Dense(units=32, activation='relu'),\n    Dense(units=10, activation='softmax')\n])\n\nmodel_2.compile(optimizer='adam',\n                loss='sparse_categorical_crossentropy',\n                metrics=['accuracy'])\nhistory_2 = model_2.fit(x_train, y_train, epochs=50,\n                        validation_data=(x_val, y_val),\n                        use_multiprocessing=True,\n                        callbacks=[es], verbose=1)\n\nprint(model_2.summary())\n\nmake_plots(history_2)\n\ntrain_pred = np.argmax(model_2.predict(x_train), axis=1)\nval_pred = np.argmax(model_2.predict(x_val), axis=1)\nresults['nn_784_large'] = {'train': accuracy_score(y_train, train_pred), 'val': accuracy_score(y_val, val_pred)}","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:56:16.279028Z","iopub.execute_input":"2021-05-22T05:56:16.279496Z","iopub.status.idle":"2021-05-22T05:56:55.934235Z","shell.execute_reply.started":"2021-05-22T05:56:16.279457Z","shell.execute_reply":"2021-05-22T05:56:55.933376Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Цікаво, що в цьому випадку, хоча мережа стала складнішою (аж 1,192,250 параметрів), точність розпізнавання не підвищалася.","metadata":{}},{"cell_type":"markdown","source":"## LeNet","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.layers import Conv2D, AveragePooling2D, MaxPooling2D, Flatten","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:56:55.935574Z","iopub.execute_input":"2021-05-22T05:56:55.935918Z","iopub.status.idle":"2021-05-22T05:56:55.942399Z","shell.execute_reply.started":"2021-05-22T05:56:55.935869Z","shell.execute_reply":"2021-05-22T05:56:55.941565Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"В цій мережі на вхід подаються вже не 784-вимірні вектори, а саме зображення у вигляді массивів 28\\*28\\*1 (1 відповідає за кількість каналів). ","metadata":{}},{"cell_type":"code","source":"x_train_lenet = x_train.to_numpy().reshape((-1, 28, 28, 1))\nx_val_lenet = x_val.to_numpy().reshape((-1, 28, 28, 1))","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:56:55.945321Z","iopub.execute_input":"2021-05-22T05:56:55.945564Z","iopub.status.idle":"2021-05-22T05:56:55.953561Z","shell.execute_reply.started":"2021-05-22T05:56:55.945540Z","shell.execute_reply":"2021-05-22T05:56:55.952741Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"![](https://cdn-images-1.medium.com/max/1024/1*DMcPgeekUftwk0GTMcNawg.png)","metadata":{}},{"cell_type":"code","source":"lenet = Sequential([\n    Conv2D(filters=6, kernel_size=(5,5), padding='same',\n           input_shape=(28,28,1), activation='sigmoid'),\n    AveragePooling2D(pool_size=(2,2), strides=2),\n    Conv2D(filters=16, kernel_size=(5,5), padding='valid',\n           activation='sigmoid'),\n    AveragePooling2D(pool_size=(2,2), strides=2),\n    Flatten(),\n    Dense(units=120, activation='sigmoid'),\n    Dense(units=84, activation='sigmoid'),\n    Dense(units=10, activation='softmax')\n])\n\nlenet.compile(optimizer='adam',\n              loss='sparse_categorical_crossentropy',\n              metrics=['accuracy'])\nhistory_lenet = lenet.fit(x_train_lenet, y_train, epochs=50,\n                          validation_data=(x_val_lenet, y_val),\n                          use_multiprocessing=True,\n                          callbacks=[es], verbose=1)\n\nprint(lenet.summary())\n\nmake_plots(history_lenet)\n\ntrain_pred = np.argmax(lenet.predict(x_train_lenet), axis=1)\nval_pred = np.argmax(lenet.predict(x_val_lenet), axis=1)\nresults['lenet'] = {'train': accuracy_score(y_train, train_pred), 'val': accuracy_score(y_val, val_pred)}","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:56:55.954821Z","iopub.execute_input":"2021-05-22T05:56:55.955334Z","iopub.status.idle":"2021-05-22T05:58:22.955214Z","shell.execute_reply.started":"2021-05-22T05:56:55.955278Z","shell.execute_reply":"2021-05-22T05:58:22.954347Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"LeNet показала поки що найкращий результат точності у порівнянні з іншими моделями. До того ж, у цієї мережі значно менше параметрів, ніж у інших мереж вище - всього лише 61,706. Подивимося ще на зображення, що були класифіковані непраильно:","metadata":{}},{"cell_type":"code","source":"show_mistakes(8, np.argmax(lenet.predict(x_val_lenet), axis=1))","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:58:22.956660Z","iopub.execute_input":"2021-05-22T05:58:22.957008Z","iopub.status.idle":"2021-05-22T05:58:25.653952Z","shell.execute_reply.started":"2021-05-22T05:58:22.956970Z","shell.execute_reply":"2021-05-22T05:58:25.653145Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Знову ж, помилки тут на дуже дивних зображеннях (причому, помилок на 4200 зображень у `validation` лише 79).","metadata":{}},{"cell_type":"markdown","source":"## LeNet з аугментацією зображень","metadata":{}},{"cell_type":"markdown","source":"Можна спробувати додати до вибірки нових зображень, повернувши на деякі кути оригінальні. Це може дати LeNet ще кращу точність.","metadata":{}},{"cell_type":"code","source":"import cv2\n\ndef rotate_image(image, angle, scale=1.0):\n    h, w = image.shape\n    M = cv2.getRotationMatrix2D((w/2, h/2), angle, scale)\n    return cv2.warpAffine(image.astype(np.float32), M, (w, h))","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:58:25.655071Z","iopub.execute_input":"2021-05-22T05:58:25.655518Z","iopub.status.idle":"2021-05-22T05:58:25.783102Z","shell.execute_reply.started":"2021-05-22T05:58:25.655484Z","shell.execute_reply":"2021-05-22T05:58:25.782339Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"x_train_lenet = x_train_lenet.reshape((-1, 28, 28))\nx_train_aug = x_train_lenet.copy()\ny_train_aug = y_train.copy()\nfor angle in [-10, -5, 5, 10]:\n    rotate_image(x_train_lenet[0], angle)\n    x_train_aug = np.concatenate((\n        x_train_aug,\n        np.array([rotate_image(image, angle) for image in x_train_lenet])\n    ))\nfor _ in range(x_train_aug.shape[0]//y_train.shape[0] - 1):\n    y_train_aug = np.concatenate((\n        y_train_aug,\n        y_train\n    ))\nx_train_lenet = x_train_lenet.reshape((-1, 28, 28, 1))\nx_train_aug = x_train_aug.reshape((-1, 28, 28, 1))\nprint(f'initial train shape: {x_train_lenet.shape}')\nprint(f'new train shape: {x_train_aug.shape}')","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:58:25.785860Z","iopub.execute_input":"2021-05-22T05:58:25.786111Z","iopub.status.idle":"2021-05-22T05:58:29.922756Z","shell.execute_reply.started":"2021-05-22T05:58:25.786086Z","shell.execute_reply":"2021-05-22T05:58:29.921841Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Таким чином отримали в 5 разів більше зображень.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.models import clone_model","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:58:29.924021Z","iopub.execute_input":"2021-05-22T05:58:29.924526Z","iopub.status.idle":"2021-05-22T05:58:29.928891Z","shell.execute_reply.started":"2021-05-22T05:58:29.924487Z","shell.execute_reply":"2021-05-22T05:58:29.928134Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"lenet_aug = clone_model(lenet)\nlenet_aug.compile(optimizer='adam',\n                  loss='sparse_categorical_crossentropy',\n                  metrics=['accuracy'])\nhistory_lenet_aug = lenet_aug.fit(x_train_aug, y_train_aug, epochs=50,\n                                  validation_data=(x_val_lenet, y_val),\n                                  use_multiprocessing=True,\n                                  callbacks=[es], verbose=1)\n\nmake_plots(history_lenet)\n\ntrain_pred = np.argmax(lenet_aug.predict(x_train_aug), axis=1)\nval_pred = np.argmax(lenet_aug.predict(x_val_lenet), axis=1)\nresults['lenet_aug'] = {'train': accuracy_score(y_train_aug, train_pred), 'val': accuracy_score(y_val, val_pred)}","metadata":{"execution":{"iopub.status.busy":"2021-05-22T05:58:29.930087Z","iopub.execute_input":"2021-05-22T05:58:29.930467Z","iopub.status.idle":"2021-05-22T06:00:23.975699Z","shell.execute_reply.started":"2021-05-22T05:58:29.930429Z","shell.execute_reply":"2021-05-22T06:00:23.974844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Дійсно, точність на `validation` трохи підвищалсь: 0.9869 проти 0.9826 без додавання зображень.","metadata":{}},{"cell_type":"markdown","source":"# Аналіз результатів","metadata":{}},{"cell_type":"markdown","source":"Порівняємо точність усії моделей на `train` та `validataion`:","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(20, 7))\naxes[0].barh(np.arange(len(results)), [results[model]['train'] for model in results])\naxes[0].set_yticks(np.arange(len(results)))\naxes[0].set_yticklabels(list(results.keys()))\naxes[0].invert_yaxis()\n\naxes[1].barh(np.arange(len(results)), [results[model]['val'] for model in results])\naxes[1].set_yticks(np.arange(len(results)))\naxes[1].set_yticklabels(list(results.keys()))\naxes[1].invert_yaxis()\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-05-22T06:00:23.977050Z","iopub.execute_input":"2021-05-22T06:00:23.977391Z","iopub.status.idle":"2021-05-22T06:00:24.227833Z","shell.execute_reply.started":"2021-05-22T06:00:23.977355Z","shell.execute_reply":"2021-05-22T06:00:24.226887Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Не дивно, що абсолютним переможцем за точністю на `validation` стає саме **LeNet з аугментацією**: ця модель, власне, і розроблялся на прикладі цього датасету, а додавання \"нових\" зображень дало змогу трохи підвищити точність.","metadata":{}},{"cell_type":"markdown","source":"# Сабміт","metadata":{}},{"cell_type":"code","source":"sub = pd.read_csv('/kaggle/input/digit-recognizer/sample_submission.csv', index_col=['ImageId'])\ntest_lenet = test.to_numpy().reshape((-1, 28, 28, 1))\nsub['Label'] = np.argmax(lenet_aug.predict(test_lenet), axis=1)\nsub.to_csv('my_sub.csv')","metadata":{"execution":{"iopub.status.busy":"2021-05-22T06:00:24.229127Z","iopub.execute_input":"2021-05-22T06:00:24.229614Z","iopub.status.idle":"2021-05-22T06:00:25.702407Z","shell.execute_reply.started":"2021-05-22T06:00:24.229573Z","shell.execute_reply":"2021-05-22T06:00:25.701545Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Результат - 0.98646**","metadata":{}}]}