只今勉強中！学習済みモデルを用いた機械学習 画像分類

## 犬と猫の画像の分類
犬と猫の画像の分類はKaggleのコンペで提供された問題で犬と猫の25,000枚の画像から構成されているデータセット。

## 準備
まずは[Kaggleのサイト](https://www.kaggle.com/c/dogs-vs-cats/data)からデータをダウンロードします。
初めて利用する場合はユーザー登録をしておいてください。
データをダウンロードしたら、ダウンロードしたデータを扱い易いようにディレクトリごとにざっくり下記のようなディレクトリ構成にします。
画像パスは自分の環境に置き換えてください。

| ディレクトリ名（論理名）|      説明 |    パス|
|:-----------------|-----------------:|:------------------:|
| original_dataset_dir |ダウンロード画像 |    'C:\\Users\\minarai\\Downloads\\dogs-vs-cats\\train'       |
| base_dir|           学習用データのベースディレクトリ | 'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir'      |
| train_dir          |  訓練データディレクトリ |        'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir\\train'        |
| validation_dir           |検証データディレクトリ | 'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir\\validation'|
| test_dir            |テストデータディレクトリ |'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir\\test' |
| train_dogs_dir         | 訓練用犬画像のディレクトリ |'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir\\train\\dogs'|
| train_cats_dir         | 訓練用猫画像のディレクトリ |'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir\\train\\cats'|
| validation_dogs_dir         | 検証用犬画像のディレクトリ |'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir\\validation\\dogs'|
| validation_cats_dir         | 検証用猫画像のディレクトリ |'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir\\validation\\cats'|
| test_dogs_dir         |テスト用犬画像のディレクトリ |'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir\\test\\dogs'|
| test_dogs_dir         | テスト用犬画像のディレクトリ |'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir\\test\\cats'|

では早速、始めましょう。

``` sample.py
# 訓練画像を用意
import os, shutil
"""
データの準備
"""

# オリジナルデータ
original_dataset_dir = 'C:\\Users\\minarai\\Downloads\\dogs-vs-cats\\train'

# 機械学習用の画像を格納するディレクトリ
base_dir = 'C:\\Users\\minarai\\Pictures\\dogs-vs-cats\\base_dir'
# ディレクトリがなければ作成します
if os.path.isdir(base_dir) == False:
    os.mkdir(base_dir)
    
# 訓練データ、検証データ、テストデータ用のディレクトリ
train_dir      = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir       = os.path.join(base_dir, 'test')
# ディレクトリがなければ作成します
if os.path.isdir(train_dir) == False:
    os.mkdir(train_dir)
if os.path.isdir(validation_dir) == False:
    os.mkdir(validation_dir)
if os.path.isdir(test_dir) == False:
    os.mkdir(test_dir)

# 訓練用　犬、猫のディレクトリ
train_dogs_dir = os.path.join(train_dir, 'dogs')
train_cats_dir = os.path.join(train_dir, 'cats')
# ディレクトリがなければ作成します
if os.path.isdir(train_dogs_dir) == False:
    os.mkdir(train_dogs_dir)
if os.path.isdir(train_cats_dir) == False:
    os.mkdir(train_cats_dir)

# 検証用　犬、猫のディレクトリ
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
validation_cats_dir = os.path.join(validation_dir, 'cats')
# ディレクトリがなければ作成します
if os.path.isdir(validation_dogs_dir) == False:
    os.mkdir(validation_dogs_dir)
if os.path.isdir(validation_cats_dir) == False:
    os.mkdir(validation_cats_dir)

# テスト用　犬、猫のディレクトリ
test_dogs_dir = os.path.join(test_dir, 'dogs')
test_cats_dir = os.path.join(test_dir, 'cats')
# ディレクトリがなければ作成します
if os.path.isdir(test_dogs_dir) == False:
    os.mkdir(test_dogs_dir)
if os.path.isdir(test_cats_dir) == False:
    os.mkdir(test_cats_dir)

# 訓練用データをコピー ※画像名は cat.i.jpg/dog.i.jpg 格納されている
for i in range(2000):
    cat_file = 'cat.{}.jpg'.format(i)
    dog_file = 'dog.{}.jpg'.format(i)
    
    cat_src = os.path.join(original_dataset_dir, cat_file)
    dog_src = os.path.join(original_dataset_dir, dog_file)
    if i < 1000:
        cat_dst = os.path.join(train_cats_dir, cat_file)
        shutil.copyfile(cat_src, cat_dst)
        
        dog_dst = os.path.join(train_dogs_dir, dog_file)
        shutil.copyfile(dog_src, dog_dst)
    elif i < 1500:
        cat_dst = os.path.join(validation_cats_dir, cat_file)
        shutil.copyfile(cat_src, cat_dst)
        
        dog_dst = os.path.join(validation_dogs_dir, dog_file)
        shutil.copyfile(dog_src, dog_dst)
    else:
        cat_dst = os.path.join(test_cats_dir, cat_file)
        shutil.copyfile(cat_src, cat_dst)
        
        dog_dst = os.path.join(test_dogs_dir, dog_file)
        shutil.copyfile(dog_src, dog_dst)

# 画像数を確認     
>>> print('total training cat images:', len(os.listdir(train_cats_dir)))
>>> print('total training dog images:', len(os.listdir(train_dogs_dir)))

>>> print('total validation cat images:', len(os.listdir(validation_cats_dir)))
>>> print('total validation dog images:', len(os.listdir(validation_dogs_dir)))

>>> print('total test cat images:', len(os.listdir(test_cats_dir)))
>>> print('total test dog  images:', len(os.listdir(test_dogs_dir)))

total training cat images: 1000
total training dog  images: 1000
total validation cat images: 500
total validation dog images: 500
total test cat images: 500
total test dog images: 500
```
## モデルの設計と実装
``` sample.py
from keras import layers
from keras import models
# モデルアーキテクチャ設計
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
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(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

# モデルを確認
model.summary()

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 148, 148, 32)      896       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 74, 74, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 72, 72, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 36, 36, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 34, 34, 128)       73856     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 17, 17, 128)       0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 36992)             0         
_________________________________________________________________
dense_1 (Dense)              (None, 512)               18940416  
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 513       
=================================================================
Total params: 19,034,177
Trainable params: 19,034,177
Non-trainable params: 0
_________________________________________________________________


# モデルのコンパイル
from keras import optimizers

# 2値問題なので損失関数はbinaly_crossentropy
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])
```

## データの前処理
CNNへデータを突っ込むために画像データを4次元テンソルへ変換します。
※詳しくは「[只今勉強中！　機械学習ライブラリがやっていること](https://qiita.com/minarai/items/c5fbbfe4c545e475e375)」を見てね。

・下記のような流れで4次元テンソルへ変換します。
1 画像ファイルを読み込む
2 JEPEG から RGBのピクセルグリッドへ変換
3 浮動小数点型のテンソルへ変換
4 ピクセル値（0-255)を0~1の範囲へ変換

``` sample.py
# データの前処理
"""
1. 画像ファイルを読み込む
2. JPEGファイルをRGBピクセルグリッドにデコード
3. 浮動小数点型のテンソルに変換
4. ピクセル値(0-255)を0-1の範囲に変換する
"""
from keras.preprocessing.image import ImageDataGenerator

# 画像を 1/255でスケーリング
train_datagen = ImageDataGenerator(rescale = 1./255)
test_datagen = ImageDataGenerator(rescale = 1./255)

train_generator = train_datagen.flow_from_directory(
    train_dir, # スケーリング対象が入っているディレクトリ
    target_size=(150, 150), # 画像サイズ
    batch_size = 20, # バッチサイズ
    class_mode='binary'
)

validation_generator = test_datagen.flow_from_directory(
    train_dir, # スケーリング対象が入っているディレクトリ
    target_size=(150, 150), # 画像サイズ
    batch_size = 20, # バッチサイズ
    class_mode='binary'
)

for data_batch, labels_batch in train_generator:
    print('data batch shape(バッチサイズ, 幅, 高さ, チャンネル数):', data_batch.shape)
    print('labels batch shape(バッチサイズ):', labels_batch.shape)
    break

Found 2000 images belonging to 2 classes.
Found 2000 images belonging to 2 classes.
data batch shape(バッチサイズ, 幅, 高さ, チャンネル数): (20, 150, 150, 3)
labels batch shape(バッチサイズ): (20,)
```

# モデルの訓練
```
# バッチジェネレータでモデルを適用
history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=30,
    validation_data=validation_generator,
    validation_steps=50
)


Epoch 1/30
100/100 [==============================] - 69s 688ms/step - loss: 0.6951 - acc: 0.5405 - val_loss: 0.6421 - val_acc: 0.6360
Epoch 2/30
100/100 [==============================] - 69s 688ms/step - loss: 0.6136 - acc: 0.6720 - val_loss: 0.5649 - val_acc: 0.7090
Epoch 3/30
100/100 [==============================] - 68s 683ms/step - loss: 0.5539 - acc: 0.7210 - val_loss: 0.4832 - val_acc: 0.8030
Epoch 4/30
100/100 [==============================] - 69s 691ms/step - loss: 0.5015 - acc: 0.7625 - val_loss: 0.4932 - val_acc: 0.7510
Epoch 5/30
100/100 [==============================] - 68s 680ms/step - loss: 0.4655 - acc: 0.7720 - val_loss: 0.3995 - val_acc: 0.8380
Epoch 6/30
100/100 [==============================] - 68s 679ms/step - loss: 0.4334 - acc: 0.8015 - val_loss: 0.3574 - val_acc: 0.8610
Epoch 7/30
100/100 [==============================] - 68s 679ms/step - loss: 0.3901 - acc: 0.8260 - val_loss: 0.3804 - val_acc: 0.8130
Epoch 8/30
100/100 [==============================] - 68s 680ms/step - loss: 0.3549 - acc: 0.8430 - val_loss: 0.3071 - val_acc: 0.8810
Epoch 9/30
100/100 [==============================] - 68s 681ms/step - loss: 0.3169 - acc: 0.8650 - val_loss: 0.2486 - val_acc: 0.9200
Epoch 10/30
100/100 [==============================] - 68s 681ms/step - loss: 0.2810 - acc: 0.8940 - val_loss: 0.1991 - val_acc: 0.9490
Epoch 11/30
100/100 [==============================] - 68s 685ms/step - loss: 0.2448 - acc: 0.9075 - val_loss: 0.1786 - val_acc: 0.9640
Epoch 12/30
100/100 [==============================] - 68s 682ms/step - loss: 0.2079 - acc: 0.9235 - val_loss: 0.1873 - val_acc: 0.9400
Epoch 13/30
100/100 [==============================] - 69s 690ms/step - loss: 0.1833 - acc: 0.9390 - val_loss: 0.1128 - val_acc: 0.9810
Epoch 14/30
100/100 [==============================] - 68s 683ms/step - loss: 0.1512 - acc: 0.9500 - val_loss: 0.1133 - val_acc: 0.9720
Epoch 15/30
100/100 [==============================] - 68s 682ms/step - loss: 0.1273 - acc: 0.9610 - val_loss: 0.0767 - val_acc: 0.9900
Epoch 16/30
100/100 [==============================] - 68s 681ms/step - loss: 0.1037 - acc: 0.9715 - val_loss: 0.0666 - val_acc: 0.9930
Epoch 17/30
100/100 [==============================] - 68s 683ms/step - loss: 0.0866 - acc: 0.9750 - val_loss: 0.0832 - val_acc: 0.9810
Epoch 18/30
100/100 [==============================] - 68s 681ms/step - loss: 0.0669 - acc: 0.9850 - val_loss: 0.0306 - val_acc: 0.9980
Epoch 19/30
100/100 [==============================] - 68s 682ms/step - loss: 0.0516 - acc: 0.9890 - val_loss: 0.0224 - val_acc: 1.0000
Epoch 20/30
100/100 [==============================] - 68s 680ms/step - loss: 0.0399 - acc: 0.9925 - val_loss: 0.0302 - val_acc: 0.9970
Epoch 21/30
100/100 [==============================] - 68s 683ms/step - loss: 0.0359 - acc: 0.9905 - val_loss: 0.0167 - val_acc: 1.0000
Epoch 22/30
100/100 [==============================] - 68s 682ms/step - loss: 0.0306 - acc: 0.9925 - val_loss: 0.0246 - val_acc: 0.9960
Epoch 23/30
100/100 [==============================] - 68s 683ms/step - loss: 0.0180 - acc: 0.9965 - val_loss: 0.0080 - val_acc: 1.0000
Epoch 24/30
100/100 [==============================] - 68s 682ms/step - loss: 0.0264 - acc: 0.9930 - val_loss: 0.0043 - val_acc: 1.0000
Epoch 25/30
100/100 [==============================] - 68s 683ms/step - loss: 0.0173 - acc: 0.9960 - val_loss: 0.0043 - val_acc: 1.0000
Epoch 26/30
100/100 [==============================] - 68s 683ms/step - loss: 0.0176 - acc: 0.9945 - val_loss: 0.0033 - val_acc: 1.0000
Epoch 27/30
100/100 [==============================] - 68s 682ms/step - loss: 0.0160 - acc: 0.9950 - val_loss: 0.0062 - val_acc: 1.0000
Epoch 28/30
100/100 [==============================] - 68s 681ms/step - loss: 0.0216 - acc: 0.9960 - val_loss: 0.0014 - val_acc: 1.0000
Epoch 29/30
100/100 [==============================] - 68s 683ms/step - loss: 0.0151 - acc: 0.9950 - val_loss: 0.0017 - val_acc: 1.0000
Epoch 30/30
100/100 [==============================] - 68s 683ms/step - loss: 0.0097 - acc: 0.9975 - val_loss: 0.0013 - val_acc: 1.0000
```

## まとめ
以上、CNNモデルの作成から訓練の実施まででした。
次は学習済モデルを使ってより効率的に学習させていく方法を紹介します。