機械学習初学者のKerasでCNN

# はじめに
本記事は機械学習の知識が0だった人間が、１ヶ月間勉強した成果をまとめた内容になります。
具体的なコードを用いて、1行ずつ「何をやっているのか？」を自分の理解で綴ります。
厳密ではなかったり、間違っていることを書いているかもしれませんがご了承ください。

# CNNとは？
**Convolutional Neural Network**の略称です。
日本語だと**畳み込みニューラルネットワーク**と呼ばれています。
機械学習と一言で言っても種類は様々です。その中の一つがCNNです。
**画像分類**でよく用いられるニューラルネットワークです。

# Kerasとは？
機械学習には**scikit-learn**、**Chainer**、**TensorFlow**といった様々なライブラリが存在します。
**Keras**はGoogleが開発したTensorFlowをベースに利用することが可能なライブラリです。

# KerasでCNN
**Keras**を使って**CNN**で**0~9の手書き文字の画像分類**をやっていきます。
**MNIST**と呼ばれる手書き文字のデータセットを利用します。
<img width="758" alt="スクリーンショット 2019-04-09 14.52.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/4a684f18-da0f-47a1-0bef-95362882cc7f.png">
機械学習のHello worldですね。
早速ですが分類するコードを貼り付けます。

```python
import tensorflow as tf

# 0~9の手書き文字MNISTのデータセットを読み込む
(training_images, training_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()
# 画像データの形式を変更する
training_images = training_images.reshape(training_images.shape[0], 28, 28, 1)
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1)
# 画像データを正規化する
training_images = training_images / 255.0
test_images = test_images / 255.0
# ラベルデータを1-of-K表現にする
training_labels = tf.keras.utils.to_categorical(training_labels)
test_labels = tf.keras.utils.to_categorical(test_labels)
# CNNのモデルを作成する
model = tf.keras.models.Sequential([
  tf.keras.layers.Conv2D(64, (3,3), activation='relu', input_shape=(28, 28, 1)),
  tf.keras.layers.MaxPooling2D(2,2),
  tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
  tf.keras.layers.MaxPooling2D(2,2),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(10, activation='softmax')
])
# 任意のオプティマイザと損失関数を設定してモデルをコンパイルする
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# ネットワーク各層の出力内容を確認する
model.summary()
# モデルをトレーニングする
model.fit(training_images, training_labels, epochs=5)
# テストデータで精度を確認する
test_loss = model.evaluate(test_images, test_labels)
```

以上です。
これだけのコードで画像分類が可能です！
ここからは1行ずつ何をやっているのかを丁寧に見ていこうと思います。

## データの前処理

```python
import tensorflow as tf
```
今回は[tf.keras](https://www.tensorflow.org/api_docs/python/tf/keras)を利用していくのでtensorflowをインポートします。
* * *
```python
(training_images, training_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()
```
手書き文字の画像データが存在しないと始まりません。
この１行で**トレーニング用画像60000枚**と**テスト用画像10000枚**が手に入ります。
今回は画像に対して答え（**ラベル**）が存在する**教師あり学習**になります。
1と書かれている画像にはラベル1、9と書かれている画像にはラベル9、即ち**予めその画像が何を意味するかの答えを持っているデータセット**になります。
<img width="759" alt="スクリーンショット 2019-04-09 15.44.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/588c8f04-46b4-1403-f947-508553285f2f.png">
* * *
```python
training_images = training_images.reshape(training_images.shape[0], 28, 28, 1)
```
training_images.shape[0]はトレーニング用画像の数を意味します。
後述する画像データ入力の形状に変形するため**reshape**を行なっています。
* * *
```python
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1)
```
テスト用画像データに対しても同じ処理を行います。
* * *
```python
training_images = training_images / 255.0
```
トレーニング用画像データを**正規化（Normalization）**します。
ここで言うところの正規化とは**ピクセル値を0〜1の範囲に収める**作業になります。
今回の手書き文字画像は**グレースケール（黒から白までの色の変化を0~255の値で表現）**です。
<img width="397" alt="gray_scale.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/4428926a-20ca-f330-0689-dea12a65f2c7.png">
なので、各ピクセル値を255で割ることによって0~1の範囲に収めることができますね。
<img width="340" alt="normalization_before.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/64bbc4a3-454e-597f-1062-f7cf9bc7fc63.png"><img width="340" alt="normalization_after.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/753b269b-c199-c86c-b815-ca27256d14c4.png">
**左：正規化前（0~255）****右：正規化後（0~1）**
### 正規化（特徴スケーリング）がなぜ必要か？
例えば、**年齢と身長**を**特徴量データ**とした時に、**26歳と168センチ**というデータを扱うことになると思います。年齢は0~100歳、身長は30センチ〜200センチといったように**データの取る範囲と値が異なる**場合があります。なので、データのスケールを揃えてあげる作業が必要になってくるわけです。
画像分類の場合も0~1の間でスケーリング（今回は正規化）することによって精度が良くなるらしいです。
* * *
### 補足
<img width="340" alt="スクリーンショット 2019-04-09 18.43.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/ccecff2a-2616-0c59-ba26-8bc60011a6da.png">
因みにグレースケールではなく**RGBの場合**はこんな感じでRとGとBの次元を持っています。
* * *
```python
test_images = test_images / 255.0
```
テスト用画像データに対しても同じ処理を行います。
* * *
```python
training_labels = tf.keras.utils.to_categorical(training_labels)
```
次はトレーニング用ラベルデータを入力する形状に変形します。
取得した直後のラベルデータの中身を見てみましょう。
<img width="348" alt="label_1.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/28f5ec60-e61f-187f-eb08-a42971b71185.png">
1番目の画像が5、2番目の画像が0、最後の画像が8ということが一目で分かります。
今回はこれを**to_categorical()**を使って**1-of-K表現**に変換します。
<img width="496" alt="label_2.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/a2f92646-ae63-b5a1-d7af-0f7a355f6433.png">
変換後の1番目のラベルデータを見てみましょう。
5だったはずですが、**0と1の長さ10の配列**になっていると思います。
配列の長さは**分類するラベル数（クラス数）**を意味します。
注目すべきは**1の場所**で、**0から数えて5番目だけが1**になっています。
**対象の位置だけを1にして他を0にする**ことで表現するのが1-of-k表現です。
* * *
```python
test_labels = tf.keras.utils.to_categorical(test_labels)
```
テスト用ラベルに対しても同じ処理を行います。
* * *
## モデルの定義

```python
model = tf.keras.models.Sequential
```
モデルを定義しています。
* * *
```python
tf.keras.layers.Conv2D(64, (3,3), activation='relu', input_shape=(28, 28, 1)),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
```
ニューラルネットワークの詳細を見ていきましょう。
1行ずつ見て行く前に大雑把に何をやっているのかを記述します。
入力→畳み込み→プーリング→畳み込み→プーリング→1次元配列化→全結合→出力。
以上です。
基本は**畳み込んでプーリングして全結合**するだけです。
* * *
### 畳み込み層

```python
tf.keras.layers.Conv2D(64, (3,3), activation='relu', input_shape=(28, 28, 1))
```
**Conv2D**は**畳み込み**を行うために利用するニューラルネットワークです。
色々引数に取っていますが焦らず1つずつ見ていきましょう。
#### 入力の形状
```python
input_shape=(28, 28, 1)
```
inputなので入力される画像データの形状が指定されています。
先ほど前処理を行なった画像データの一番最初（確か5でしたよね）の形状を見てみましょう。
<img width="206" alt="label_3.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/60ffffb3-3eec-a724-1bd0-97af05091090.png">
input_shapeと一致しますね！
**縦が28ピクセル**、**横が28ピクセル**、**グレースケール**の画像データであることが分かります。
3次元目の1は**チャンネル数**を意味するので、色の情報を持っていれば**(28, 28, 3)**となります。
#### フィルタサイズ
```python
(3,3)
```
・・・これだけじゃ引数に何を取るのか分からないので[ドキュメント](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Conv2D)を見ていきます。

```python
__init__(
    filters,
    kernel_size,
    strides=(1, 1),
    padding='valid',
    data_format=None,
    dilation_rate=(1, 1),
    activation=None,
    use_bias=True,
    kernel_initializer='glorot_uniform',
    bias_initializer='zeros',
    kernel_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    kernel_constraint=None,
    bias_constraint=None,
    **kwargs
)
```
どうやら**kernel_size**を取るようです。
今回は**3×3のカーネルサイズ**を引数に取っているということです。
<img width="168" alt="kernel.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/ebf4037c-28ff-a886-06ea-56805514501f.png">
#### 畳み込みとは？
畳み込みを説明するためには**strides**と**padding**も重要です。
デフォルト値として**strides=(1, 1)**で**padding='valid'**であることを把握しておきましょう。
また、カーネルとは**フィルタ**とも呼ばれています。
これで、畳み込みを説明するための材料が揃いました。
ずばり、畳み込みとは
**入力画像のピクセル範囲に対してフィルタを適用して行く作業**になります。
<img width="1338" alt="28ピクセルの3×3フィルタ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/2ac4c94f-0bbd-016a-457a-4522070f5b1b.png">
上記図を見ながら説明していきます。
黄色い枠が3×3のカーネル、すなわちフィルタになります。
それを画像ピクセルの左上から適用し、画像ピクセル値と対応するフィルタのピクセル値を掛け合わせ算出された値を足し合わせます。図の例だと画像ピクセル値が全て0なので結果も0となります。適用後のピクセルサイズを見てみましょう。
**3×3が1×1のピクセルサイズ**に**畳み込まれ**ていますね。
最後までフィルタを適用していくと**全体として28×28の画像が26×26に畳み込まれ**ます。
大きするので、**5×5の画像に3×3のフィルタを適用する動き**を可視化してみましょう。
![convolution.gif](https://qiita-image-store.s3.amazonaws.com/0/115823/ab40d210-2772-84c7-18ca-5de4681ccd77.gif)
サイズを変えないで畳み込むことも可能です。
これを指定するのが先ほど確認した**padding**になります。
デフォルト値は**padding='valid'**が指定されており、これは画像そのものにフィルタを適用していくことになります。
**padding='same'**を指定すると、**画像の周りを0パディング**することによってサイズを変えず**端の特徴**もより捉えることができるようになります。
![convolution_padding.gif](https://qiita-image-store.s3.amazonaws.com/0/115823/2f870a13-7293-837d-c6ad-3c47d4691012.gif)
フィルタを間引くことも可能です。
今はstrides=(1, 1)によって横に1ピクセル縦に1ピクセルずつフィルタを適用していました。
これを**strides=(2, 2)**にするとどうでしょう？可視化すれば一目瞭然だと思います。![convolution_stride.gif](https://qiita-image-store.s3.amazonaws.com/0/115823/35fe0c39-012c-4b79-2649-4b33d179509c.gif)
フィルタ適用開始位置が2ピクセル分飛んでフィルタを適用している感じになります。
#### フィルタ数
```python
64
```
・・・これだけじゃ引数に何を取るのか分からないので再び[ドキュメント](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Conv2D)を見ていきます。
どうやらを**filters**を取るようです。
今回は**64枚のフィルタ**を使っているということです。
今までの例だと1枚のフィルタを適用して1枚の畳み込まれた画像が出力されていることを確認しましたが、実際には64枚のフィルタを使い、64パターンの**特徴マップ**が出力されることになります。
<img width="551" alt="filters.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/547c0827-8ad8-451b-466e-670561921fc7.png">
#### 活性化関数
もう一踏ん張りです。最後の引数を見ていきましょう。

```python
activation='relu'
```
活性化関数に使う関数名を指定しています。
今回は**ReLU関数（ランプ関数）**を利用しています。
![relu.png](https://qiita-image-store.s3.amazonaws.com/0/115823/3e84d79f-189d-f00d-d74c-3eaa6340c310.png)
横軸を入力、縦軸を出力と見ると、**正の値の場合はそのまま出力**し、**負の値の場合は0を出力**するという関数になります。先ほどのフィルタの例だと全て正の値になっていたのでそのまま出力していましたが、実際には負の値も取ります。
**最後にはReLUという活性化関数が適用され、出力される値（特徴マップ）が決まる**というわけです。

#### 畳み込み層のまとめ
- 入力画像に対して**カーネル（フィルタ）**を適用する
- フィルタを適用していく**ストライド（移動幅）**を設定する
- フィルタ数を決める
- パディングが必要かどうかを考える
- 最後に活性化関数**ReLU**を適用する

フィルタサイズやストライド、フィルタ数に関しては**ハイパーパラメータ**なので、値を色々変えて学習の変化を試してみると面白いかもしれません。

### プーリング層

```python
tf.keras.layers.MaxPooling2D(2,2)
```
畳み込んだ画像データに対して次は**プーリング**という作業を行います。
平均値を取るプーリングもありますが、今回は**最大値を取るプーリング**を行なっていきます。

```python
(2,2)
```
・・・・[ドキュメント](https://www.tensorflow.org/api_docs/python/tf/layers/MaxPooling2D)を見てきましょう。

```python
__init__(
    pool_size,
    strides,
    padding='valid',
    data_format='channels_last',
    name=None,
    **kwargs
)
```
どうやら**pool_size**を取るようです。
今回は**2×2のプールサイズ**を引数に取っているということです。
2×2に含まれるピクセルの中で**最大の数値**を採用するのが**MaxPooling2D**です。
![pooling.gif](https://qiita-image-store.s3.amazonaws.com/0/115823/b099ad53-ca72-cc08-ebdd-f04ccb104bac.gif)
プーリングも畳み込み同様**strides**と**padding**を取ることができます。
今回はデフォルト値を利用しているので、動きとしては図のようになります。
プーリングはこれだけです。

### 層を深くする
1度だけの畳み込み・プーリングだけでも精度は出ると思います。
層を深くすることによって精度が上がるかもしれません。

```python
tf.keras.layers.Conv2D(32, (3,3), activation='relu')
```
プーリングしたデータに対して今度は**32個のフィルタ**を使って**3×3のフィルタ**を適用します。

```python
tf.keras.layers.MaxPooling2D(2,2)
```
先ほどと同様に**2×2のプーリング**を行います。
#### 2回の畳み込みとプーリング後
<img width="1270" alt="last_shape.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/d62de338-e598-4044-c7c7-b394cee2b77c.png">
2回目のプーリングを終えた時点で、28×28あった画像データが**5×5に圧縮**されたことがわかると思います。
漠然としたオリジナルの画像情報からその画像の特徴だけを振るい落として圧縮した、そんなイメージの理解です。

### 全結合層

```python
tf.keras.layers.Flatten()
```
各フィルタによって細かく特徴が分割されていたと思いますが、これを一つにまとめます。
5×5の32枚の特徴マップを1次元に変換します。
**(5, 5, 32) → (800)**

```python
tf.keras.layers.Dense(128, activation='relu')
```
```python
128
```
・・・[ドキュメント](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense)を見ていきましょう。
どうやら**units**を取るようです。

いきなりですが、**ニューロン**の話をします。
ニューロンというのは人間の脳で情報を伝播させるために利用される神経細胞です。
もともと**ニューラルネットワーク**はこのニューロンをヒントに考案されているので、ここでの理解もその概念が役に立ちます。
<img width="588" alt="image_neuron.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/c0e16fe1-d6b2-8a21-9a3e-19cfa34b1790.png">
[ここ](https://pixabay.com/ja/vectors/%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%AD%E3%83%B3-%E7%A5%9E%E7%B5%8C-%E3%82%BB%E3%83%AB-29147/)の画像を使わせていただきました。
**Unit**というのはまさにニューロンを意味します。
今回はこの**ニューロンが128個用意**されているというわけです。
1次元に変換された値が全て1個のニューロンに入力され、内部で**値を全て結合**し、一つの値を出力します。
ここでも活性化関数にReLUを使い、出力するかしないかを決定します。
<img width="435" alt="multilayer_perceptron.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/1a76ebe5-af0a-21cb-75c8-5101d358595e.png">

### 出力層
```python
tf.keras.layers.Dense(10, activation='softmax')
```
呼んでいる関数は先ほどと同じ**Dense**になります。
つまり、最後は10個のニューロンを利用して出力を得るということになります。
活性化関数には**softmax**を利用します。
### Softmax関数
総和が1となるように**予測確率を算出してくれる関数**です。
今回の画像分類だと、次のような出力が得られます（イメージです）。
<img width="214" alt="スクリーンショット 2019-04-10 18.53.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/34cffb29-5b8d-0f60-1b2d-08881b0c87dc.png">
<img width="824" alt="nn.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/ca23531b-7722-5681-6515-612956d9cbb0.png">
* * *
### モデルのコンパイル
**オプティマイザ**と**損失関数**を設定してモデルをコンパイルします。

```python
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
```
- **オプティマイザ**（optimizer）
    - トレーニングを最適化する手法を設定する
        - 今回は**Adam**を採用している
        - 他にもMomentum SGD、AdaGrad等が存在する
        - ここを実装することはないのであるものを使えば良いと思います
- **損失関数**（loss）
    - ラベルデータと実際の出力がどれだけ誤差があるかを計算する関数
        - 今回は**categorical_crossentropy**を採用している
        - 実はラベルを1-of-K表現に変形せずとも**sparse_categorical_crossentropy**を使えばそのままの形状で渡すことが可能です

初学者のうちは、ここら辺はあるものを使っていく感じで大丈夫だと思います。
ひとまずは**「loss使って誤差出してoptimizerでいい感じに重みの更新とか計算してくれてんだろうな〜」**程度の理解しかしてません。間違ってるかもしれません。でも最初のうちはそれでいいんだと思います。（投げやり）
* * *
```python
model.summary()
```
Kerasのモデルは**summary()**が実装されています。
これはネットワークの各層の出力が見れるので大変便利です。
<img width="680" alt="スクリーンショット 2019-04-09 16.31.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/1a15926e-1278-f268-a5ff-16f2b69a552a.png">
今まで確認してきた各層の内容が一目瞭然ですね。
* * *
### モデルのトレーニング
いよいよ学習していきます。

```
model.fit(training_images, training_labels, epochs=5)
```
modelの**fit()**を呼ぶだけです。

```python
epochs=5
```
学習を行う回数になります。
今回は60000枚の画像データそれぞれに対して5回学習を行います。
学習し過ぎると**Overfitting（過学習）**という問題も発生してくるので注意です。
<img width="710" alt="1 2.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/da223039-c18f-4399-c621-0636d7fce843.png">
1回目の学習でも**95%の精度**を出してますね！
5回目の学習が終わった時点で**99%の精度**です！
#### 補足
ニューラルネットワークの世界は複雑で、**トレーニングデータに対しては精度を出せるけど、テストデータといった未知のデータに対しては極端に精度が出なくなってしまう**過学習という現象が度々見られます。
過学習を抑えるのに**Dropout（ドロップアウト）**という手法があります。
いくつかのノードを無効化しながら学習を繰り返します。
今回は利用していません。
* * *
```python
test_loss = model.evaluate(test_images, test_labels)
```
最後は学習に利用していない10000枚のテストデータを使って精度を確認していきます。
<img width="715" alt="1.png" src="https://qiita-image-store.s3.amazonaws.com/0/115823/7cb16337-90b8-af33-e3ee-d0e0703b067b.png">
**99%の正答率**ですね！すごい！
お疲れ様でした！
* * *
# おわりに
今回は手書き文字の画像データを扱いましたが、同じ手法で別の画像データ（犬の画像、猫の画像、アイドルの画像、etc...）も学習させることが可能です！
今回のMNISTから無限の可能性を切り開けたと思います！（言い過ぎですね）

**「よし！今日からゲームの画像分類をCNNで書くぞ！」**

**「・・・画像データどうする？そもそもデータセットの作り方は？」**

そうなんです。
MNISTから始めた初学者が次にぶち当たる壁として**データ収集**と**データセット作成**が待ち構えています。
結局は**データがないと何もできない**んですよね・・・。
次回は少ない画像データでも精度が出せる（かもしれない）優秀なモデルをベースとした**転移学習**についてまとめていきたいです。

# 参考にさせていただいた記事
- [定番のConvolutional Neural Networkをゼロから理解する](https://deepage.net/deep_learning/2016/11/07/convolutional_neural_network.html)
- [Feature Scalingはなぜ必要？](https://qiita.com/ttskng/items/2a33c1ca925e4501e609)