TensorFlowを使ったモデルの構築と転移学習

# ディープラーニングを使用した画像分類
ディープラーニングを用いて画像の分類を行います。
今回は画像分類のチュートリアルとしてよく用いられる猫と犬の画像の分類を実践してみます。


## ライブラリのインポート
Pythonにおけるディープラーニングのライブラリとしては以下が有名です。
- TensorFlow および Keras
- PyTorch

今回の例ではTensorFlowおよびKerasを使用します。


```python
import os
import numpy as np
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt
from PIL import Image

import tensorflow as tf
from tensorflow.keras import utils as np_utils
from tensorflow.keras.preprocessing import image_dataset_from_directory
from tensorflow.keras.layers import Input, Dense, Conv2D, Flatten, Dropout, MaxPooling2D
```

## データの取得
使用するデータを取得します。
今回使用するデータはTensorFlowのチュートリアルでも用いられている猫と犬の分類済み画像になります。


```python
_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'

path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)
PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')

train_dir = os.path.join(PATH, 'train')
validation_dir = os.path.join(PATH, 'validation')

train_cats_dir = os.path.join(train_dir, 'cats')  # 学習用の猫画像のディレクトリ
train_dogs_dir = os.path.join(train_dir, 'dogs')  # 学習用の犬画像のディレクトリ
validation_cats_dir = os.path.join(validation_dir, 'cats')  # 検証用の猫画像のディレクトリ
validation_dogs_dir = os.path.join(validation_dir, 'dogs')  # 検証用の犬画像のディレクトリ
```

    Downloading data from https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip
    68608000/68606236 [==============================] - 1s 0us/step
    68616192/68606236 [==============================] - 1s 0us/step
    

犬と猫の画像はそれぞれ`train`という学習用の画像セットと`validation`という検証用の画像のセットに分類されています。  
では、それぞれの画像の数を確認してみましょう。


```python
num_cats_tr = len(os.listdir(train_cats_dir))
num_dogs_tr = len(os.listdir(train_dogs_dir))

num_cats_val = len(os.listdir(validation_cats_dir))
num_dogs_val = len(os.listdir(validation_dogs_dir))

total_train = num_cats_tr + num_dogs_tr
total_val = num_cats_val + num_dogs_val

print('total training cat images:', num_cats_tr)
print('total training dog images:', num_dogs_tr)

print('total validation cat images:', num_cats_val)
print('total validation dog images:', num_dogs_val)
print("--")
print("Total training images:", total_train)
print("Total validation images:", total_val)
```

    total training cat images: 1000
    total training dog images: 1000
    total validation cat images: 500
    total validation dog images: 500
    --
    Total training images: 2000
    Total validation images: 1000
    

一旦ここで、この後に使用するネットワークの学習用の定数を設定しておきます。


```python
BATCH_SIZE = 128
epochs = 10
IMG_SIZE = (160, 160)
IMG_SHAPE = IMG_SIZE + (3,)
```

# データの準備

取得したデータを元に`image_dataset_from_directory`関数を使用してデータセットを作成します。
教師データ、検証データそれぞれで用意します。

また、学習後のモデルを評価するためにテストデータセットも用意します。
今回、テストデータセットは検証データセットの一部を使用します。


```python
# 教師データ
train_dataset = image_dataset_from_directory(train_dir,
                                             shuffle=True,
                                             batch_size=BATCH_SIZE,
                                             image_size=IMG_SIZE)
# 検証データ
validation_dataset = image_dataset_from_directory(validation_dir,
                                                  shuffle=True,
                                                  batch_size=BATCH_SIZE,
                                                  image_size=IMG_SIZE)

val_batches = tf.data.experimental.cardinality(validation_dataset)

# テストデータセット
test_dataset = validation_dataset.take(val_batches // 5)

validation_dataset = validation_dataset.skip(val_batches // 5)
```

    Found 2000 files belonging to 2 classes.
    Found 1000 files belonging to 2 classes.
    

試しに画像を表示してみます。


```python
class_names = train_dataset.class_names

plt.figure(figsize=(10, 10))
for images, labels in train_dataset.take(1):
  for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(images[i].numpy().astype("uint8"))
    plt.title(class_names[labels[i]])
    plt.axis("off")
```


![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/829205b8-5dc9-9b25-54bd-cc6ed9fdf15b.png)

    


## モデルの作成
画像を分類するためのモデルを作成しましょう。


```python
def sample_model():
    # モデルの構成を定義します。
    inputs = Input(shape=IMG_SHAPE)
    x = tf.keras.layers.experimental.preprocessing.Rescaling(1./255, offset= -1)(inputs) # データを0-1の範囲に正規化
    x = Conv2D(16, 3, padding='same', activation='relu')(x)
    x = MaxPooling2D()(x)
    x = Conv2D(32, 3, padding='same', activation='relu')(x)
    x = MaxPooling2D()(x)
    x = Conv2D(64, 3, padding='same', activation='relu')(x)
    x = MaxPooling2D()(x)
    x = Flatten()(x)
    x = Dense(512, activation='relu')(x)
    outputs = Dense(1, activation='sigmoid')(x)
    model = tf.keras.Model(inputs=inputs, outputs=outputs)

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

# モデルを作成
model = sample_model()

# モデルの構造を確認します
model.summary()
```

    Model: "model"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    input_1 (InputLayer)         [(None, 160, 160, 3)]     0         
    _________________________________________________________________
    rescaling (Rescaling)        (None, 160, 160, 3)       0         
    _________________________________________________________________
    conv2d (Conv2D)              (None, 160, 160, 16)      448       
    _________________________________________________________________
    max_pooling2d (MaxPooling2D) (None, 80, 80, 16)        0         
    _________________________________________________________________
    conv2d_1 (Conv2D)            (None, 80, 80, 32)        4640      
    _________________________________________________________________
    max_pooling2d_1 (MaxPooling2 (None, 40, 40, 32)        0         
    _________________________________________________________________
    conv2d_2 (Conv2D)            (None, 40, 40, 64)        18496     
    _________________________________________________________________
    max_pooling2d_2 (MaxPooling2 (None, 20, 20, 64)        0         
    _________________________________________________________________
    flatten (Flatten)            (None, 25600)             0         
    _________________________________________________________________
    dense (Dense)                (None, 512)               13107712  
    _________________________________________________________________
    dense_1 (Dense)              (None, 1)                 513       
    =================================================================
    Total params: 13,131,809
    Trainable params: 13,131,809
    Non-trainable params: 0
    _________________________________________________________________
    

## モデルの学習

それでは作成したモデルを学習してみましょう。


```python
history = model.fit(train_dataset,
                    epochs=epochs,
                    validation_data=validation_dataset)
```

    Epoch 1/10
    16/16 [==============================] - 38s 335ms/step - loss: 1.0439 - accuracy: 0.5030 - val_loss: 0.6913 - val_accuracy: 0.5023
    Epoch 2/10
    16/16 [==============================] - 5s 265ms/step - loss: 0.6854 - accuracy: 0.5725 - val_loss: 0.6683 - val_accuracy: 0.6170
    Epoch 3/10
    16/16 [==============================] - 5s 257ms/step - loss: 0.6680 - accuracy: 0.5955 - val_loss: 0.6479 - val_accuracy: 0.6330
    Epoch 4/10
    16/16 [==============================] - 5s 256ms/step - loss: 0.6248 - accuracy: 0.6640 - val_loss: 0.6236 - val_accuracy: 0.6720
    Epoch 5/10
    16/16 [==============================] - 5s 258ms/step - loss: 0.5696 - accuracy: 0.7125 - val_loss: 0.5892 - val_accuracy: 0.7099
    Epoch 6/10
    16/16 [==============================] - 5s 259ms/step - loss: 0.5009 - accuracy: 0.7555 - val_loss: 0.6136 - val_accuracy: 0.6537
    Epoch 7/10
    16/16 [==============================] - 5s 261ms/step - loss: 0.4441 - accuracy: 0.8045 - val_loss: 0.6165 - val_accuracy: 0.7053
    Epoch 8/10
    16/16 [==============================] - 5s 261ms/step - loss: 0.3926 - accuracy: 0.8205 - val_loss: 0.6289 - val_accuracy: 0.6984
    Epoch 9/10
    16/16 [==============================] - 5s 259ms/step - loss: 0.3158 - accuracy: 0.8625 - val_loss: 0.6914 - val_accuracy: 0.6732
    Epoch 10/10
    16/16 [==============================] - 5s 260ms/step - loss: 0.2574 - accuracy: 0.8920 - val_loss: 0.7377 - val_accuracy: 0.6881
    

学習結果を可視化してみましょう。


```python
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

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

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
```


    
![output_17_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/1b7ed9ae-fba9-38d6-1600-c3f4258fe7da.png)

    


# モデルの転移学習

先程のように何もない状態からニューラルネットワークのモデルを構築することは実際の業務ではあまりなく、基本的には公開されている既に構築済み/事前学習済みのモデルをベースとすることが多いです。  

今回はMobileNetV2というモデルをImageNetというデータセットで事前学習済みのモデルをベースとし、転移学習しましょう。



```python
# ベースモデルを用意
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
                                               include_top=False,
                                               weights='imagenet')
base_model.trainable = False
```

    Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_160_no_top.h5
    9412608/9406464 [==============================] - 0s 0us/step
    9420800/9406464 [==============================] - 0s 0us/step
    

## 画像データの準備
先程のモデルでは画像のデータに対して1～0の範囲で正規化を行いました。


使用するモデルによって、データの範囲が異なります。  
使用するライブラリによっては前処理用のメソッドが用意されています。今回はその前処理用のメソッドを利用します。


```python
# モデル用の画像前処理のレイヤーを用意。
preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input
```

## モデルの構築


```python
def create_model():
    # モデルの構成を定義します。
    inputs = tf.keras.Input(shape=(160, 160, 3))
    x = preprocess_input(inputs)
    x = base_model(x, training=False)
    x = tf.keras.layers.GlobalAveragePooling2D()(x)
    x = tf.keras.layers.Dropout(0.2)(x)
    outputs = Dense(1, activation='sigmoid')(x)
    model = tf.keras.Model(inputs, outputs)

    # モデルのコンパイル
    base_learning_rate = 0.0001
    model.compile(optimizer=tf.keras.optimizers.Adam(lr=base_learning_rate),
                loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
                metrics=['accuracy'])
    return model

model = create_model()
model.summary()
```

    Model: "model_1"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    input_3 (InputLayer)         [(None, 160, 160, 3)]     0         
    _________________________________________________________________
    tf.math.truediv (TFOpLambda) (None, 160, 160, 3)       0         
    _________________________________________________________________
    tf.math.subtract (TFOpLambda (None, 160, 160, 3)       0         
    _________________________________________________________________
    mobilenetv2_1.00_160 (Functi (None, 5, 5, 1280)        2257984   
    _________________________________________________________________
    global_average_pooling2d (Gl (None, 1280)              0         
    _________________________________________________________________
    dropout (Dropout)            (None, 1280)              0         
    _________________________________________________________________
    dense_2 (Dense)              (None, 1)                 1281      
    =================================================================
    Total params: 2,259,265
    Trainable params: 1,281
    Non-trainable params: 2,257,984
    _________________________________________________________________
    

    /usr/local/lib/python3.7/dist-packages/keras/optimizer_v2/optimizer_v2.py:356: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
      "The `lr` argument is deprecated, use `learning_rate` instead.")
    

ではこのモデルを転移学習してみましょう。


```python
history = model.fit(train_dataset,
                    epochs=epochs,
                    validation_data=validation_dataset)
```

    Epoch 1/10
    

    /usr/local/lib/python3.7/dist-packages/keras/backend.py:4994: UserWarning: "`binary_crossentropy` received `from_logits=True`, but the `output` argument was produced by a sigmoid or softmax activation and thus does not represent logits. Was this intended?"
      '"`binary_crossentropy` received `from_logits=True`, but the `output`'
    

    16/16 [==============================] - 14s 528ms/step - loss: 0.7151 - accuracy: 0.5650 - val_loss: 0.6436 - val_accuracy: 0.6330
    Epoch 2/10
    16/16 [==============================] - 7s 369ms/step - loss: 0.6210 - accuracy: 0.6730 - val_loss: 0.5553 - val_accuracy: 0.7248
    Epoch 3/10
    16/16 [==============================] - 7s 372ms/step - loss: 0.5470 - accuracy: 0.7365 - val_loss: 0.4899 - val_accuracy: 0.7844
    Epoch 4/10
    16/16 [==============================] - 7s 369ms/step - loss: 0.4960 - accuracy: 0.7705 - val_loss: 0.4400 - val_accuracy: 0.8314
    Epoch 5/10
    16/16 [==============================] - 7s 374ms/step - loss: 0.4285 - accuracy: 0.8305 - val_loss: 0.3819 - val_accuracy: 0.8693
    Epoch 6/10
    16/16 [==============================] - 7s 373ms/step - loss: 0.3840 - accuracy: 0.8590 - val_loss: 0.3464 - val_accuracy: 0.8956
    Epoch 7/10
    16/16 [==============================] - 7s 376ms/step - loss: 0.3640 - accuracy: 0.8735 - val_loss: 0.3203 - val_accuracy: 0.9071
    Epoch 8/10
    16/16 [==============================] - 7s 381ms/step - loss: 0.3256 - accuracy: 0.8925 - val_loss: 0.2858 - val_accuracy: 0.9278
    Epoch 9/10
    16/16 [==============================] - 7s 373ms/step - loss: 0.3026 - accuracy: 0.9060 - val_loss: 0.2601 - val_accuracy: 0.9427
    Epoch 10/10
    16/16 [==============================] - 7s 374ms/step - loss: 0.2787 - accuracy: 0.9130 - val_loss: 0.2469 - val_accuracy: 0.9369
    

学習結果を表示してみます。  
先程のモデルを１から構築した場合よりも精度が向上している事がわかります。


```python
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

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

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
```


![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/006cf5ed-7501-f409-8215-10104e6b58e8.png)
   
![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/3463370c-28cb-cabb-05ee-8f67dc7c7a9b.png)

    


# モデルの評価

テストデータセットを使用してモデルの性能を検証しましょう。


```python
loss, accuracy = model.evaluate(test_dataset)
print('Test accuracy :', accuracy)
```

    1/1 [==============================] - 1s 1s/step - loss: 0.2511 - accuracy: 0.9219
    Test accuracy : 0.921875
    


```python
image_batch, label_batch = test_dataset.as_numpy_iterator().next()
predictions = model.predict_on_batch(image_batch).flatten()

predictions = tf.nn.sigmoid(predictions)
predictions = tf.where(predictions < 0.5, 0, 1)

print('予測結果:\n', predictions.numpy())
print('正解のラベル:\n', label_batch)

# 試しに9枚表示
plt.figure(figsize=(10, 10))
for i in range(9):
  ax = plt.subplot(3, 3, i + 1)
  plt.imshow(image_batch[i].astype("uint8"))
  plt.title(class_names[predictions[i]])
  plt.axis("off")
```

    予測結果:
     [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
     1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
     1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
     1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
    正解のラベル:
     [0 0 1 0 0 1 1 1 0 1 0 0 0 0 1 1 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1 0 1 1 0 0 0
     0 0 1 0 0 1 1 1 0 1 1 1 0 0 1 0 1 0 1 0 1 0 1 0 0 0 1 0 1 1 0 0 1 1 1 0 1
     1 0 1 1 0 1 0 0 0 0 0 0 1 0 1 0 0 1 1 1 1 1 1 0 0 0 1 0 1 0 1 0 1 0 0 1 1
     0 1 1 1 0 0 0 1 1 0 1 1 0 0 0 1 0]
    


    
# ディープラーニングを使用した画像分類の実例
ディープラーニングを用いて画像の分類を行います。  
今回は画像分類のチュートリアルにて猫と犬の画像の分類を実践してみます。


## ライブラリのインポート
Pythonにおけるディープラーニングのライブラリとしては以下が有名です。
- TensorFlow および Keras
- PyTorch

今回の例ではTensorFlowおよびKerasを使用してみましょう。


```python
import os
import numpy as np
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt
from PIL import Image

import tensorflow as tf
from tensorflow.keras import utils as np_utils
from tensorflow.keras.preprocessing import image_dataset_from_directory
from tensorflow.keras.layers import Input, Dense, Conv2D, Flatten, Dropout, MaxPooling2D
```

## データの取得
使用するデータを取得します。  
今回の例ではTensorFlowのチュートリアルでも使用されている犬と猫の分類済みのデータセットを使用します。


```python
_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'

path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)
PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')

train_dir = os.path.join(PATH, 'train')
validation_dir = os.path.join(PATH, 'validation')

train_cats_dir = os.path.join(train_dir, 'cats')  # 学習用の猫画像のディレクトリ
train_dogs_dir = os.path.join(train_dir, 'dogs')  # 学習用の犬画像のディレクトリ
validation_cats_dir = os.path.join(validation_dir, 'cats')  # 検証用の猫画像のディレクトリ
validation_dogs_dir = os.path.join(validation_dir, 'dogs')  # 検証用の犬画像のディレクトリ
```

    Downloading data from https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip
    68608000/68606236 [==============================] - 1s 0us/step
    68616192/68606236 [==============================] - 1s 0us/step
    

犬と猫の画像はそれぞれ`train`という学習用の画像セットと`validation`という検証用の画像のセットに分類されています。  
では、それぞれの画像の数を確認してみましょう。


```python
num_cats_tr = len(os.listdir(train_cats_dir))
num_dogs_tr = len(os.listdir(train_dogs_dir))

num_cats_val = len(os.listdir(validation_cats_dir))
num_dogs_val = len(os.listdir(validation_dogs_dir))

total_train = num_cats_tr + num_dogs_tr
total_val = num_cats_val + num_dogs_val

print('total training cat images:', num_cats_tr)
print('total training dog images:', num_dogs_tr)

print('total validation cat images:', num_cats_val)
print('total validation dog images:', num_dogs_val)
print("--")
print("Total training images:", total_train)
print("Total validation images:", total_val)
```

    total training cat images: 1000
    total training dog images: 1000
    total validation cat images: 500
    total validation dog images: 500
    --
    Total training images: 2000
    Total validation images: 1000
    

一旦ここで、この後に使用するネットワークの学習用の定数を設定しておきます。


```python
BATCH_SIZE = 128
epochs = 10
IMG_SIZE = (160, 160)
IMG_SHAPE = IMG_SIZE + (3,)
```

# データの準備

画像データを準備します。

先程取得した画像データを元に`image_dataset_from_directory`という関数を使用してデータセットを作成します。


また、テスト用のデータセットを用意しましょう。
今回は検証用のデータセットの一部をテストデータセットとして利用します。
学習後にデータの性能を確認するためテスト用のデータセットを使用して推測を行います。


```python
# 教師データ
train_dataset = image_dataset_from_directory(train_dir,
                                             shuffle=True,
                                             batch_size=BATCH_SIZE,
                                             image_size=IMG_SIZE)
# 検証データ
validation_dataset = image_dataset_from_directory(validation_dir,
                                                  shuffle=True,
                                                  batch_size=BATCH_SIZE,
                                                  image_size=IMG_SIZE)

val_batches = tf.data.experimental.cardinality(validation_dataset)
test_dataset = validation_dataset.take(val_batches // 5)

# テストデータセット
validation_dataset = validation_dataset.skip(val_batches // 5)
```

    Found 2000 files belonging to 2 classes.
    Found 1000 files belonging to 2 classes.
    

試しに画像を表示してみます。


```python
class_names = train_dataset.class_names

plt.figure(figsize=(10, 10))
for images, labels in train_dataset.take(1):
  for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(images[i].numpy().astype("uint8"))
    plt.title(class_names[labels[i]])
    plt.axis("off")
```


    
![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/6b6518ab-3a16-5e63-9eba-f8f6d60e642a.png)

    


## モデルの作成
画像を分類するためのモデルを作成します。
今回は3層の畳み込み層を持つシンプルなモデルを作成します。

```python
def sample_model():
    # モデルの構成を定義します。
    inputs = Input(shape=IMG_SHAPE)
    x = tf.keras.layers.experimental.preprocessing.Rescaling(1./255, offset= -1)(inputs) # データを0-1の範囲に正規化
    x = Conv2D(16, 3, padding='same', activation='relu')(x) # 1つ目の畳み込み層
    x = MaxPooling2D()(x)
    x = Conv2D(32, 3, padding='same', activation='relu')(x) # 2つ目の畳み込み層
    x = MaxPooling2D()(x)
    x = Conv2D(64, 3, padding='same', activation='relu')(x) # 3つ目の畳み込み層
    x = MaxPooling2D()(x)
    x = Flatten()(x) 
    x = Dense(512, activation='relu')(x)
    outputs = Dense(1, activation='sigmoid')(x)
    model = tf.keras.Model(inputs=inputs, outputs=outputs)

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

# モデルを作成
model = sample_model()

# モデルの構造を確認します
model.summary()
```

    Model: "model"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    input_1 (InputLayer)         [(None, 160, 160, 3)]     0         
    _________________________________________________________________
    rescaling (Rescaling)        (None, 160, 160, 3)       0         
    _________________________________________________________________
    conv2d (Conv2D)              (None, 160, 160, 16)      448       
    _________________________________________________________________
    max_pooling2d (MaxPooling2D) (None, 80, 80, 16)        0         
    _________________________________________________________________
    conv2d_1 (Conv2D)            (None, 80, 80, 32)        4640      
    _________________________________________________________________
    max_pooling2d_1 (MaxPooling2 (None, 40, 40, 32)        0         
    _________________________________________________________________
    conv2d_2 (Conv2D)            (None, 40, 40, 64)        18496     
    _________________________________________________________________
    max_pooling2d_2 (MaxPooling2 (None, 20, 20, 64)        0         
    _________________________________________________________________
    flatten (Flatten)            (None, 25600)             0         
    _________________________________________________________________
    dense (Dense)                (None, 512)               13107712  
    _________________________________________________________________
    dense_1 (Dense)              (None, 1)                 513       
    =================================================================
    Total params: 13,131,809
    Trainable params: 13,131,809
    Non-trainable params: 0
    _________________________________________________________________
    

## モデルの学習

それでは作成したモデルを学習してみましょう。


```python
history = model.fit(train_dataset,
                    epochs=epochs,
                    validation_data=validation_dataset)
```

    Epoch 1/10
    16/16 [==============================] - 38s 335ms/step - loss: 1.0439 - accuracy: 0.5030 - val_loss: 0.6913 - val_accuracy: 0.5023
    Epoch 2/10
    16/16 [==============================] - 5s 265ms/step - loss: 0.6854 - accuracy: 0.5725 - val_loss: 0.6683 - val_accuracy: 0.6170
    Epoch 3/10
    16/16 [==============================] - 5s 257ms/step - loss: 0.6680 - accuracy: 0.5955 - val_loss: 0.6479 - val_accuracy: 0.6330
    Epoch 4/10
    16/16 [==============================] - 5s 256ms/step - loss: 0.6248 - accuracy: 0.6640 - val_loss: 0.6236 - val_accuracy: 0.6720
    Epoch 5/10
    16/16 [==============================] - 5s 258ms/step - loss: 0.5696 - accuracy: 0.7125 - val_loss: 0.5892 - val_accuracy: 0.7099
    Epoch 6/10
    16/16 [==============================] - 5s 259ms/step - loss: 0.5009 - accuracy: 0.7555 - val_loss: 0.6136 - val_accuracy: 0.6537
    Epoch 7/10
    16/16 [==============================] - 5s 261ms/step - loss: 0.4441 - accuracy: 0.8045 - val_loss: 0.6165 - val_accuracy: 0.7053
    Epoch 8/10
    16/16 [==============================] - 5s 261ms/step - loss: 0.3926 - accuracy: 0.8205 - val_loss: 0.6289 - val_accuracy: 0.6984
    Epoch 9/10
    16/16 [==============================] - 5s 259ms/step - loss: 0.3158 - accuracy: 0.8625 - val_loss: 0.6914 - val_accuracy: 0.6732
    Epoch 10/10
    16/16 [==============================] - 5s 260ms/step - loss: 0.2574 - accuracy: 0.8920 - val_loss: 0.7377 - val_accuracy: 0.6881
    

学習結果を可視化してみましょう。


```python
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

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

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
```


![output_17_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/b8b5c2eb-90d7-9c56-e1d8-9d20d27506d2.png)

    


# モデルの転移学習

先程のように何もない状態からニューラルネットワークのモデルを構築することは実際の業務ではあまりなく、基本的には公開されている既に構築済み/事前学習済みのモデルをベースとすることが多いです。  

今回はMobileNetV2というモデルをImageNetというデータセットで事前学習済みのモデルをベースとし、転移学習しましょう。



```python
# ベースモデルを用意
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
                                               include_top=False,
                                               weights='imagenet')
base_model.trainable = False
```

    Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_160_no_top.h5
    9412608/9406464 [==============================] - 0s 0us/step
    9420800/9406464 [==============================] - 0s 0us/step
    

## 画像データの準備
先程のモデルでは画像のデータに対して1～0の範囲で正規化を行いました。


使用するモデルによって、データの範囲が異なります。  
使用するライブラリによっては前処理用のメソッドが用意されています。今回はその前処理用のメソッドを利用します。


```python
# モデル用の画像前処理のレイヤーを用意。
preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input
```

## モデルの構築


```python
def create_model():
    # モデルの構成を定義します。
    inputs = tf.keras.Input(shape=(160, 160, 3))
    x = preprocess_input(inputs)
    x = base_model(x, training=False)
    x = tf.keras.layers.GlobalAveragePooling2D()(x)
    x = tf.keras.layers.Dropout(0.2)(x)
    outputs = Dense(1, activation='sigmoid')(x)
    model = tf.keras.Model(inputs, outputs)

    # モデルのコンパイル
    base_learning_rate = 0.0001
    model.compile(optimizer=tf.keras.optimizers.Adam(lr=base_learning_rate),
                loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
                metrics=['accuracy'])
    return model

model = create_model()
model.summary()
```

    Model: "model_1"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    input_3 (InputLayer)         [(None, 160, 160, 3)]     0         
    _________________________________________________________________
    tf.math.truediv (TFOpLambda) (None, 160, 160, 3)       0         
    _________________________________________________________________
    tf.math.subtract (TFOpLambda (None, 160, 160, 3)       0         
    _________________________________________________________________
    mobilenetv2_1.00_160 (Functi (None, 5, 5, 1280)        2257984   
    _________________________________________________________________
    global_average_pooling2d (Gl (None, 1280)              0         
    _________________________________________________________________
    dropout (Dropout)            (None, 1280)              0         
    _________________________________________________________________
    dense_2 (Dense)              (None, 1)                 1281      
    =================================================================
    Total params: 2,259,265
    Trainable params: 1,281
    Non-trainable params: 2,257,984
    _________________________________________________________________
    

    /usr/local/lib/python3.7/dist-packages/keras/optimizer_v2/optimizer_v2.py:356: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
      "The `lr` argument is deprecated, use `learning_rate` instead.")
    

ではこのモデルを転移学習してみましょう。


```python
history = model.fit(train_dataset,
                    epochs=epochs,
                    validation_data=validation_dataset)
```

    Epoch 1/10
    

    /usr/local/lib/python3.7/dist-packages/keras/backend.py:4994: UserWarning: "`binary_crossentropy` received `from_logits=True`, but the `output` argument was produced by a sigmoid or softmax activation and thus does not represent logits. Was this intended?"
      '"`binary_crossentropy` received `from_logits=True`, but the `output`'
    

    16/16 [==============================] - 14s 528ms/step - loss: 0.7151 - accuracy: 0.5650 - val_loss: 0.6436 - val_accuracy: 0.6330
    Epoch 2/10
    16/16 [==============================] - 7s 369ms/step - loss: 0.6210 - accuracy: 0.6730 - val_loss: 0.5553 - val_accuracy: 0.7248
    Epoch 3/10
    16/16 [==============================] - 7s 372ms/step - loss: 0.5470 - accuracy: 0.7365 - val_loss: 0.4899 - val_accuracy: 0.7844
    Epoch 4/10
    16/16 [==============================] - 7s 369ms/step - loss: 0.4960 - accuracy: 0.7705 - val_loss: 0.4400 - val_accuracy: 0.8314
    Epoch 5/10
    16/16 [==============================] - 7s 374ms/step - loss: 0.4285 - accuracy: 0.8305 - val_loss: 0.3819 - val_accuracy: 0.8693
    Epoch 6/10
    16/16 [==============================] - 7s 373ms/step - loss: 0.3840 - accuracy: 0.8590 - val_loss: 0.3464 - val_accuracy: 0.8956
    Epoch 7/10
    16/16 [==============================] - 7s 376ms/step - loss: 0.3640 - accuracy: 0.8735 - val_loss: 0.3203 - val_accuracy: 0.9071
    Epoch 8/10
    16/16 [==============================] - 7s 381ms/step - loss: 0.3256 - accuracy: 0.8925 - val_loss: 0.2858 - val_accuracy: 0.9278
    Epoch 9/10
    16/16 [==============================] - 7s 373ms/step - loss: 0.3026 - accuracy: 0.9060 - val_loss: 0.2601 - val_accuracy: 0.9427
    Epoch 10/10
    16/16 [==============================] - 7s 374ms/step - loss: 0.2787 - accuracy: 0.9130 - val_loss: 0.2469 - val_accuracy: 0.9369
    

学習結果を表示してみます。  
先程のモデルを１から構築した場合よりも精度が向上している事がわかります。


```python
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

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

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
```


    
![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/a8899af7-fdff-8be5-2cdd-2a2aa719578f.png)

    


# モデルの評価

テストデータセットを使用してモデルの性能を検証しましょう。


```python
loss, accuracy = model.evaluate(test_dataset)
print('Test accuracy :', accuracy)
```

    1/1 [==============================] - 1s 1s/step - loss: 0.2511 - accuracy: 0.9219
    Test accuracy : 0.921875
    


```python
image_batch, label_batch = test_dataset.as_numpy_iterator().next()
predictions = model.predict_on_batch(image_batch).flatten()

predictions = tf.nn.sigmoid(predictions)
predictions = tf.where(predictions < 0.5, 0, 1)

print('予測結果:\n', predictions.numpy())
print('正解のラベル:\n', label_batch)

# 試しに9枚表示
plt.figure(figsize=(10, 10))
for i in range(9):
  ax = plt.subplot(3, 3, i + 1)
  plt.imshow(image_batch[i].astype("uint8"))
  plt.title(class_names[predictions[i]])
  plt.axis("off")
```

    予測結果:
     [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
     1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
     1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
     1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
    正解のラベル:
     [0 0 1 0 0 1 1 1 0 1 0 0 0 0 1 1 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1 0 1 1 0 0 0
     0 0 1 0 0 1 1 1 0 1 1 1 0 0 1 0 1 0 1 0 1 0 1 0 0 0 1 0 1 1 0 0 1 1 1 0 1
     1 0 1 1 0 1 0 0 0 0 0 0 1 0 1 0 0 1 1 1 1 1 1 0 0 0 1 0 1 0 1 0 1 0 0 1 1
     0 1 1 1 0 0 0 1 1 0 1 1 0 0 0 1 0]
    

    
![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/36237c67-6764-3a7c-9c25-c5d4ab2b2473.png)

    

# 参考資料

TensorFlow チュートリアル
- [画像分類](https://www.tensorflow.org/tutorials/images/classification?hl=ja)
- [転移学習とファインチューニング](https://www.tensorflow.org/tutorials/images/transfer_learning?hl=ja)