Kerasの使い方~簡単なモデル生成からCNNまで~

#この記事でやること
ディープラーニングでは、使用するバックエンドによって実装手順がことなる。そのため、公式のドキュメントを読み込んだり、解説している参考書などを参照しながら使い方を学んでいく。この記事ではその使い方の流れを説明している。
#kerasを使った場合の実装手順

##1.訓練データを定義する

##2.入力値を目的値にマッピングする複数の層からなるネットワークを定義する。
##3.損失関数、オプティマイザ、監視する指標を選択することで学習プロセスを設定する。

##4.モデルのfitメソッドを呼び出すことで、訓練データを繰り返し学習する。

#kerasのpython環境の構築
anaconda上に新しい仮想環境を作り、tensorflow及びkerasをインストールする。今後は作った環境の上で実行していく。anaconda prompt上で以下を実行する。

```bash
conda create -n keras_env python=3.6 #仮想環境の作成
conda activate keras_env #環境の切り替え
conda install tensorflow==1.12.0
conda isntall keras==2.2.4
```


#具体的な手順
##データセットの読み込み
```python
from keras.datasets import imdb

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
```
##データの前処理
###データのベクトル化
ニューラルネットワークの入力値と目的値はすべて**浮動小数点のデータのテンソルでなければならない。**音声、画像、テキストなど、処理しなければならないデータがどのようなものであったとしても、まずそれらをテンソルに変換する必要がある。
クラス分類のラベルをベクトル化するには***one-hotエンコーディング***を行う。

```python
from keras.utils.np_utils import to_categorical

one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)
```
###データの正規化
画像データは0~255の範囲のグレースケール値を表す整数としてエンコードされている。このデータをニューラルネットワークに供給するためには、float32型でキャストしてから255で割ることで0~1の範囲の浮動小数点に変換する。
ネットワークに範囲の異なる特徴量を供給するのはどう考えても問題である。そこで範囲を同じにするために正規化を行う。特徴量の平均値を引き、標準偏差で割るという処理を行う。そうすると、特徴量の中心が0になり、標準偏差が1になる。

##モデルの定義
各層ごとに活性化関数とニューロン数を設定する。順伝播させたい順に`add()`で追加していけば良い。

```python
from keras import models
from keras import layers

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.add(Dense(200))
model.add(Activation("relu"))


```
##モデルのコンパイル
**オプティマイザと損失関数を選択する。**以下の場合は文字列で指定しているが、これが可能なのはkerasの一部としてパッケージ化されているためである。

```python
model.compile(optimizer='rmsprop',#文字指定
              loss='binary_crossentropy',
              metrics=['accuracy'])
```

**オプティマイザのパラメータ引数を指定したい**場合は以下のようにオプティマイザクラスのインスタンスを指定して、メソッドを呼び出す。

```python
from keras import optimizers

model.compile(optimizer=optimizers.RMSprop(lr=0.001),#メソッド指定
              loss='binary_crossentropy',
              metrics=['accuracy'])
```

**独自の損失関数や指標関数を使用したい**場合は、lossパラメータか、metricsパラメータに引数として関数オブジェクトを指定する。

```python
from keras import losses
from keras import metrics

model.compile(optimizer=optimizers.RMSprop(lr=0.001),
              loss=losses.binary_crossentropy,
              metrics=[metrics.binary_accuracy])
```

##検証データセット(validating data set)の設定
全く新しいデータでモデルを訓練するときに正解率を監視するには、もとの訓練データセットから取り分けておいたサンプルを使って検証データセットを作成する。以下では10000個のサンプルを取り出す場合。

```python
x_val = x_train[:10000] #検証データの取り出し
partial_x_train = x_train[10000:] #

y_val = y_train[:10000] #正解検証データの取り出し
partial_y_train = y_train[10000:]
```
###k分割交差検証
データの数が少ないと検証データはかなり小さなものになってしまう。結果として、検証と訓練にどのデータ点を選択したかによって検証スコアが大きく変化することになるかもしれない。つまり、検証データセットの分割方法によっては、検証スコアの分散が高くなり、過学習に陥ってしまう。それを防ぐ最適な方法が**k分割交差検証**である。詳しくは説明しないので、調べてみてほしい。

##モデルの訓練
8のミニバッチで20エポックの訓練を行う。xは訓練データyは正解データであることが多い。
取り分けておいた10000サンプルでの損失値と正解率を監視する。検証データはvalidation_dataパラメータに引数として渡す。

```python
history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=20,
                    batch_size=8,
                    validation_data=(x_val, y_val))
```

fitメソッドは1エポックごとに訓練した出力と、検証データの出力を辞書型で返す。今回は`history`オブジェクトに保存している。以下のコードを実行すると

```python
history_dict = history.history
history_dict.keys()
```
dict_keys(['val_acc', 'acc', 'val_loss', 'loss'])
となる。

##訓練データと検証データでの損失値をプロット
損失値をmatplotlibを使ってプロットする。`history`オブジェクトに訓練の記録がされているのでここから呼び出す。この結果を元にしてハイパーパラメータを調整する。

```python
import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

# "bo" is for "blue dot"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b is for "solid blue line"
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()
```

#CNNで画像認識する場合の手順
##画像のコピー
訓練画像(train)を保存するフォルダと検証画像(validation)を保存するフォルダを作り、学習に使うために集めた画像をコピーして振り分ける。検証データの画像枚数はかんで決めるが、だいたい全画像数のの20%~40%で調整するといいだろう。後の前処理の工程で出てくる`flow_from_directory()`のフォルダパスにここで作ったフォルダのパスを指定する。
##CNNのインスタンス化
```python
from keras import layers
from keras import 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(Dense(2))
model.add(Activation("softmax"))
```

CNNの入力テンソルの形状は(image_height,image_width,image_channels)だ。第３引数は画像のチャンネル数である。RGB画像の場合はチャンネル数が3になる。
`Conv2D（出力特徴の深さ,フィルタサイズ)` `padding='same' `の引数を指定すると出力の幅と高さが入力と同じになるようにパディングする。
ちなみに`model.summary()`で作成したモデルを確認できる。
画像分類の場合、最終層は`Dense`(全結合層)にし、引数には分類するクラス数を指定する。出力は判定の確率になるので損失関数はsoftmaxを選択する。
#モデルのコンパイル
損失関数とオプティマイザの設定。

```python
from keras import optimizers

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])
```

##データの前処理
データをCNNに供給するには、その前に浮動小数点型のテンソルとして適切に処理しておく必要がある。手順は以下の通り、
１．画像ファイルを読み込む
２．JPEGファイルの内容をRGBのピクセルグリッドにデコードする。
３．これらのピクセルを浮動小数点型のテンソルに変換する。
４．ピクセル(0~255)の尺度に取り直し、[0,1]の範囲の値にする。
kerasには、上記の手順を自動的に処理するユーティリティが用意されている。`ImageDataGenerator`クラスを利用すれば、ディスク上の画像ファイルを前処理されたテンソルのバッチに自動的に変換できるpythonジェネレータをセットできる。

ImageDataGeneratorを使ってディレクトリから画像を読み込む

```python
from keras.preprocessing.image import ImageDataGenerator

# All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=20,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')
```
##モデルの訓練
モデルを訓練させるには`fit_generator()`関数を利用する。
`steps_per_epoch`は1エポックで進行するステップ数。`validation_steps`は1エポックの中で何枚ずつ画像を検証するかを表す。

```python
history = model.fit_generator(
      train_generator,
      steps_per_epoch=10,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=5)
```
##ネットワークの保存
訓練を終えたネットワークのパラメータを保存するには`save()`関数を利用する。kerasでは`.h5`という拡張子のファイルで保存される。h5ファイルを呼び出せば次回以降はこのパラメータで画像の予測をさせることができる。

```python
model.save('cats_and_dogs_small_1.h5')
```

##正解率のプロット

```python
import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()
```
##保存したネットワークでの推定
`model = models.Sequential()`にしていた部分を以下のように変えるだけ。`load_model`の引数には保存したネットワークファイルのパスを指定する。
学習済みネットワークを使うので、学習のときにあった`add()`系は不要。学習したモデルがそのまま読み込まれる。

```python
model=keras.models.load_model('cats_and_dogs_small_1.h5')
```



#参考
https://qiita.com/GushiSnow/items/8c946208de0d6a4e31e7#%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E5%AE%9F%E8%A3%85%E4%BE%8B
https://qiita.com/tsunaki/items/608ff3cd941d82cd656b
https://qiita.com/tomo_20180402/items/e8c55bdca648f4877188
https://ymgsapo.com/2019/02/28/keras-dog-and-cat/