MNISTデータセットを使って、深層学習モデル作って、画像分類正解率99%まで~Tensorflow~

# MNISTのデータセットとは

MNIST(Mixed National Institute of Standards and Technology database)とは「0〜9」のいずれかの数字が書かれた手書き数字画像のデータセットです。(NISTとはアメリカ国立標準技術研究所のことを指します)
こんな感じのデータが学習用データ6万個ほど、テストデータが1万個ほどあります。
    
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/aaeede8f-4b14-6eb8-de50-e6ce84d6836a.png)


# まずはデータのダウンロード

今回は、GPUが無料で使えるgoogle colabを使って行いました。
tensorflowのデータセットから読み込みます。
```python
import tensorflow as tf
from tensorflow.keras import datasets, layers, models

# MNISTデータセットの読み込み
(train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data()
```

画像（画素）の値を0~1の範囲のfloat型にします。

```python
# データの前処理
train_images = train_images.reshape((60000, 28, 28, 1)).astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1)).astype('float32') / 255
```
サンプルとしてどのようなデータが入っているか見てみましょう。
```python
import numpy as np
import matplotlib.pyplot as plt
# 画像のサンプルを表示
def display_sample_images(images, labels, num_samples=5):
    indices = np.random.choice(images.shape[0], num_samples, replace=False)
    sample_images = images[indices]
    sample_labels = labels[indices]

    plt.figure(figsize=(10, 2))
    for i in range(num_samples):
        plt.subplot(1, num_samples, i + 1)
        plt.imshow(sample_images[i].reshape(28, 28), cmap='gray')
        plt.title(f"Label: {sample_labels[i]}")
        plt.axis('off')
    plt.show()

# 訓練データのサンプル画像を表示
display_sample_images(train_images, train_labels)
```
結果はこのようになります。
ランダムに選んで表示しているのでは、実行すると毎回画像は変わります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/67589fca-7bb7-baf2-1528-835d0629dc49.png)


画像とラベルのshape(形)を確認します。
```python
print(train_images.shape)
print(train_labels.shape)
print(test_images.shape)
print(test_labels.shape)
```
出力はこうなります。

    (60000, 28, 28, 1)
    (60000,)
    (10000, 28, 28, 1)
    (10000,)


学習用データが28*28ピクセルのグレースケール画像が6万枚、テストデータが1万枚あることがわかります。


# 学習させる

結論からいうと以下のように、すべての学習用データを用いて、畳み込み層とプーリング層を使うことでAccuracy(正解率)が一番高くなりますが、今回はモデルに与える学習用データの数とモデルの内部の構造を変えてみて、どのように精度が変わるか見ていきます。
※エポック数は100にして途中で、lossがあまり変わらなければ学習を途中で打ち切るようにします。

## 学習データの数 100個

### モデル1
入力画像の形状を28x28ピクセルの2次元配列から784次元の1次元ベクトルに変換
10個のニューロンを持つ全結合層で、活性化関数にReLU（Rectified Linear Unit）を使用　
10個のニューロンを持つ全結合層で、ソフトマックス（softmax）活性化関数を使用し、各クラス（0-9の数字）に対する確率を出力


コードは以下のようになります。本当に簡単にモデルを作成して学習までできます。またエポック数ごとの学習データのAccuracy,lossとテストデータのAccuracy、lossを可視化します。
```python
from tensorflow import keras
# モデルの構築
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=(28,28)))
model.add(keras.layers.Dense(10, activation="relu"))
model.add(keras.layers.Dense(10,activation="softmax"))


# モデルのコンパイル
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# EarlyStoppingコールバックの設定
early_stopping = keras.callbacks.EarlyStopping(
    monitor='val_loss',  # 監視する値
    patience=5,          # 許容するエポック数
    verbose=1,           # 停止時のメッセージ表示
    restore_best_weights=True  # 最良の重みを復元
)

# モデルの訓練
#100個の情報のみで学習してみる
#train_images[:100]このように表記することで、100個のみを使っている
history = model.fit(train_images[:100], train_labels[:100], epochs=100,
                    validation_data=(test_images, test_labels),
                    callbacks = [early_stopping])


# モデルの評価
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print(f"\nTest accuracy: {test_acc}")

# 訓練と検証の精度と損失をプロット
plt.figure(figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Accuracy')
plt.plot(history.history['val_accuracy'], label = 'Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0, 1])
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Loss')
plt.plot(history.history['val_loss'], label = 'Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.ylim([0, 3])
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')

plt.show()
```

Test accuracy: 0.6177999973297119

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/d9ece81a-23d9-4c06-5927-92520a88390f.png)

過学習が起きています。


### モデル2
入力画像の形状を28x28ピクセルの2次元配列から784次元の1次元ベクトルに変換
10個のニューロンを持つ全結合層で、活性化関数にReLU（Rectified Linear Unit）を使用　
10個のニューロンを持つ全結合層で、ソフトマックス（softmax）活性化関数を使用し、各クラス（0-9の数字）に対する確率を出力

```python
from tensorflow import keras
# モデルの構築
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=(28,28)))
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.Dense(10,activation="softmax"))


# モデルのコンパイル
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# EarlyStoppingコールバックの設定
early_stopping = keras.callbacks.EarlyStopping(
    monitor='val_loss',  # 監視する値
    patience=5,          # 許容するエポック数
    verbose=1,           # 停止時のメッセージ表示
    restore_best_weights=True  # 最良の重みを復元
)

# モデルの訓練
#100個の情報のみで学習してみる
history = model.fit(train_images[:100], train_labels[:100], epochs=100,
                    validation_data=(test_images, test_labels),
                    callbacks = [early_stopping])


# モデルの評価
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print(f"\nTest accuracy: {test_acc}")

# 訓練と検証の精度と損失をプロット
plt.figure(figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Accuracy')
plt.plot(history.history['val_accuracy'], label = 'Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0, 1])
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Loss')
plt.plot(history.history['val_loss'], label = 'Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.ylim([0, 3])
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')

plt.show()
```


Test accuracy: 0.6657999753952026
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/04a5eba0-3f16-5b5d-f5c5-e363aa6dedb1.png)


### モデル3
先ほど[モデル2](#モデル2)に中間層を追加


```python
from tensorflow import keras
# モデルの構築
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=(28,28)))
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.Dense(10,activation="softmax"))


# モデルのコンパイル
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# EarlyStoppingコールバックの設定
early_stopping = keras.callbacks.EarlyStopping(
    monitor='val_loss',  # 監視する値
    patience=5,          # 許容するエポック数
    verbose=1,           # 停止時のメッセージ表示
    restore_best_weights=True  # 最良の重みを復元
)

# モデルの訓練
#100個の情報のみで学習してみる
history = model.fit(train_images[:100], train_labels[:100], epochs=100,
                    validation_data=(test_images, test_labels),
                    callbacks = [early_stopping])


# モデルの評価
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print(f"\nTest accuracy: {test_acc}")

# 訓練と検証の精度と損失をプロット
plt.figure(figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Accuracy')
plt.plot(history.history['val_accuracy'], label = 'Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0, 1])
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Loss')
plt.plot(history.history['val_loss'], label = 'Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.ylim([0, 3])
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')

plt.show()
```
Test accuracy: 0.671500027179718

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/ddadeff1-92c1-97ed-4c1a-a6754bf8efc9.png)



### モデル4
Conv2D層1:32個のフィルタを持つ畳み込み層で、フィルタのサイズは3x3ピクセル 活性化関数にReLU（Rectified Linear Unit）
MaxPooling2D層1:プーリング層で、2x2のウィンドウを使って空間情報をダウンサンプリング
Conv2D層2:4個のフィルタを持つ畳み込み層で、フィルタのサイズは3x3ピクセル 活性化関数にReLU
MaxPooling2D層2:再び2x2のウィンドウを使ってダウンサンプリング
Conv2D層3:64個のフィルタを持つ畳み込み層で、フィルタのサイズは3x3ピクセル 活性化関数にReLU
Flatten層: 畳み込み層の出力を1次元ベクトルに変換
Dense層1:64個のニューロンを持つ全結合層で、活性化関数にReLU
Dense層2:10個のニューロンを持つ全結合層で、ソフトマックス（softmax）活性化関数を使用し、各クラス（0-9の数字）に対する確率を出力



```python
# モデルの構築
model = models.Sequential() #convを行う
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.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
# EarlyStoppingコールバックの設定
early_stopping = keras.callbacks.EarlyStopping(
    monitor='val_loss',  # 監視する値
    patience=5,          # 許容するエポック数
    verbose=1,           # 停止時のメッセージ表示
    restore_best_weights=True  # 最良の重みを復元
)

# モデルの訓練
history = model.fit(train_images[:100], train_labels[:100], epochs=100, 
                    validation_data=(test_images, test_labels),
                    callbacks=[early_stopping])

# モデルの評価
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print(f"\nTest accuracy: {test_acc}")

# 訓練と検証の精度と損失をプロット
plt.figure(figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Accuracy')
plt.plot(history.history['val_accuracy'], label = 'Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0, 1])
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Loss')
plt.plot(history.history['val_loss'], label = 'Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.ylim([0, 3])
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')

plt.show()
```

Test accuracy: 0.7261000275611877

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/8d27927a-8a2e-c14f-cd8f-003bdc449b18.png)


学習データ100個でも、テストデータに対する正解率が72％まで上がったのはすごいと思いました。しかし以前として、データの数が少ないため過学習していることがわかります。

## 学習データ1000個

モデルは上記のと同じものを使いました。
```python
# モデルの訓練
history = model.fit(train_images[:100], train_labels[:100], epochs=100, 
                    validation_data=(test_images, test_labels),
                    callbacks=[early_stopping])
```
プログラムの変更は、上の部分をこのように変えるだけです。
```python
# モデルの訓練
history = model.fit(train_images[:1000], train_labels[:1000], epochs=100, 
                    validation_data=(test_images, test_labels),
                    callbacks=[early_stopping])
```
結果を見ていきます。

[モデル1](#モデル1)
Test accuracy: 0.8560000061988831
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/9ee0b8f7-1c56-2772-183e-a89b398fbf2b.png)

[モデル2](#モデル2)
Test accuracy: 0.8860999941825867
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/49646cee-7e86-282c-6f37-3950f1566bd0.png)

[モデル3](#モデル3)
Test accuracy: 0.880299985408783
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/ebe90c88-a4fc-3d4e-68fb-911bbb84d39f.png)

[モデル4](#モデル4)
Test accuracy: 0.9376999735832214
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/cc881127-77cb-7305-0425-cfaaebea2f8b.png)

過学習は起こっていますが、学習データが増えるとやはり精度が上がっています。
ここで驚きなのは、畳み込み層を用いているモデル4では、正解率が93%まで上がっています。


## 学習データ10000個

次は学習データを10,000個にしてみます。


[モデル1](#モデル1)
Test accuracy: 0.9165999889373779
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/f56ba17a-8325-34df-22b5-27f72d70f99f.png)

[モデル2](#モデル2)
Test accuracy: 0.9510999917984009
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/b1eb29cc-1ebc-bf11-c761-26cddfd85c1f.png)

[モデル3](#モデル3)
Test accuracy: 0.953000009059906
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/122165ef-dd0a-f803-97d4-ebc7d9f1efa4.png)


[モデル4](#モデル4)
Test accuracy: 0.9847000241279602

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/afc3319c-5e86-b9aa-862a-00d1af14f4d2.png)


学習データを1万個にするとかなり精度があがりました。


## 学習データ6万個（全データ)

次は学習データを6万個にしてみます。

[モデル1](#モデル1)
Test accuracy: 0.944599986076355
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/3461b80a-6985-0d23-a3be-43247d7b7825.png)

[モデル2](#モデル2)
Test accuracy: 0.9782000184059143
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/5fd284c6-c981-0a06-4fec-b634905b555f.png)


[モデル3](#モデル3)
Test accuracy: 0.975600004196167
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/277aa6c3-e6d2-6103-cf73-7180b73c6c0f.png)

[モデル4](#モデル4)
Test accuracy: 0.9922000169754028
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794915/67b11bc8-e078-1167-1f84-a5f42a9d93ee.png)


# 考察、感想

この自分で深層学習モデルをつくってみて、ライブラリを使えばこんなにも簡単に深層学習モデルをつくって学習させることに感動しました。特に、活性化関数や、中間層の種類や、学習の際のアルゴリズムの仕組み（誤差逆伝播法）もある程度理解していたので、このライブラリが以下にすごいかが実感できました。

また、実際にプログラムを動すことで、過学習についても実感できました。モデルの種類によって、精度が変わり、学習データが大量にあればかなりの精度のモデルを作成することがわかりました。

今回は、MNISTのデータセットを使ってモデルを作成してみましたが、違う自分オリジナルのAIモデルを作りたいなと思いました。