Kaggle / MNIST で予測精度向上を目指す (1. チュートリアル通りにCNN を作る)

# 要約
- [TensorFlow の CNN のチュートリアル](https://www.tensorflow.org/tutorials/images/cnn?hl=ja) 通りに CNN を作成
- 予測精度は 0.98792 となり，[Kaggle / MNIST をサポートベクターマシンで頑張る](https://qiita.com/satomshr/items/cf806934b057ab631ae4) の結果の 0.98375 をあっさり越えた

# はじめに
[Kaggle の Digit Recognizer](https://www.kaggle.com/c/digit-recognizer) というコンペティションは，[MNIST](https://en.wikipedia.org/wiki/MNIST_database) という有名な手書き数字の画像を使って学習・予測するタスクである。ここでは学習に畳み込みニューラルネットワーク (Convolutional Neural Network, CNN) を使って，予測精度向上を目指す。

MNIST 向けの CNN の事例はネットなどで数多く見つけることができるが，ネットワーク構造やパラメータの選択理由がよく分からなかった。そこで，私自身がシンプルな CNN からスタートし，予測精度向上のために何を考えてネットワーク構造やパラメータを変えていったのかを書き残していく。「おっ!」という凄いことは書いていないけど，私自身の考えの振り返りと，他の方にとって多少の参考になれば幸いである。

もし誤りや勘違いなどあれば，指摘してもらえるとありがたいです。

# 対象読者
- CNN の基本事項 (CNN の畳み込み計算や，max pooling，batch normalization など) を一応知っている方

# 基本の CNN

## データの準備
Kaggle で用意されたデータを読み込んで，学習用に整形していく。ポイントは…

1. CSV ファイルを `pandas.DataFrame` で読み込み，TensorFlow で処理するためにそれを `numpy.ndarray` に変換する
2. 数字の範囲を 0 ～ 1 に変換するために，255.0 で割る

```python:digit-recognition_CNN1a.py
# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

# Input data files are available in the read-only "../input/" directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using "Save & Run All" 
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session


# データ読み込み
train_data = pd.read_csv("/kaggle/input/digit-recognizer/train.csv")
test_data = pd.read_csv("/kaggle/input/digit-recognizer/test.csv")

# データ数を確認
train_data_len = len(train_data)
test_data_len = len(test_data)
print("Length of train_data ; {}".format(train_data_len))
print("Length of test_data ; {}".format(test_data_len))

# Length of train_data ; 42000
# Length of test_data ; 28000

# ラベルとデータを分離
train_data_y = train_data["label"]
train_data_x = train_data.drop(columns="label")

# TensorFlow で処理するため，panda.DataFrame を numpy.ndarray に変換する
# 意図的にデータ型を float64 に変換する
train_data_x = train_data_x.astype('float64').values.reshape((train_data_len, 28, 28, 1))
test_data = test_data.astype('float64').values.reshape((test_data_len, 28, 28, 1))

# データを 0 ～ 1 の範囲にする
train_data_x /= 255.0
test_data /= 255.0
```

## CNN を作る
CNN は [TensorFlow の CNN のチュートリアル](https://www.tensorflow.org/tutorials/images/cnn?hl=ja) をそのまま活用する。

```python:digit-recognition_CNN1a.py
import tensorflow as tf
from tensorflow.keras import layers, models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))

model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.summary()
```

できあがった CNN は下記の通り。

```
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 26, 26, 32)        320       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 11, 11, 64)        18496     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 64)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 3, 3, 64)          36928     
_________________________________________________________________
flatten (Flatten)            (None, 576)               0         
_________________________________________________________________
dense (Dense)                (None, 64)                36928     
_________________________________________________________________
dense_1 (Dense)              (None, 10)                650       
=================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
_________________________________________________________________
```

## コンパイルして学習を実行する
引き続き [TensorFlow のチュートリアル](https://www.tensorflow.org/tutorials/images/cnn?hl=ja) の通りに，できたモデルをコンパイルして，学習を実行する。

```python:digit-recognition_CNN1a.py
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(train_data_x, train_data_y, epochs=5)
```

ちなみに，ここではラベルの one-hot encoding は行っていない。この場合，コンパイル時のオプションは `loss='sparse_categorical_crossentropy'` を指定する。

もし one-hot encoding する場合は，`loss='categorical_crossentropy'` を指定する。(参考 ; [目的関数の利用方法](https://keras.io/ja/objectives/))

## 予測と結果保存
学習したモデルを使って，テストデータの予測をして，結果を保存する。`tensorflow.keras.models.predict_classes` を使って，予測結果のラベルを取得する。

もし各ラベルの確率を知りたいなら，`tensorflow.keras.models.predict_proba` を使う。

得られた結果から ```pandas.DataFrame``` を作って，CSV ファイルで保存する。

```python:digit-recognition_CNN1a.py
prediction = model.predict_classes(test_data, verbose=0)
output = pd.DataFrame({"ImageId" : np.arange(1, 28000+1), "Label":prediction})

output.to_csv('digit_recognizer_CNN1a.csv', index=False)
print("Your submission was successfully saved!")
```
## 結果

| No | 説明 | スコア |
|:-- |:-- | :-: |
| Ref | SVM | 0.98375 |
| 01 | チュートリアル通り | ***0.98792*** |

前回，[Kaggle / MNIST をサポートベクターマシンで頑張る](https://qiita.com/satomshr/items/cf806934b057ab631ae4) の結果は 0.98375 だったが，それをあっさり越えてきた。さすが CNN。

今後，このスクリプトをベースにして，予測精度向上を目指す。

# 参考
## web site
- [Kaggle / Digit Recognizer](https://www.kaggle.com/c/digit-recognizer)
- [TensorFlow の CNN のチュートリアル](https://www.tensorflow.org/tutorials/images/cnn?hl=ja)
- [Kaggle / MNIST で予測精度向上を目指す (2. フィルタサイズを変える)](https://qiita.com/satomshr/items/322133a7f9b88bf62256) ; 次の記事です

## サンプルスクリプト
- [digit-recognition_CNN1a.py](https://github.com/satomshr/kaggle_mnist_cnn/blob/main/qiita/00/digit-recognition_CNN1a.py) ; この記事で紹介したスクリプトです