転移学習を使用しての飛行機と船の写真の判別

# 1. はじめに
飛行機と船の画像をAIによって判別してもらうアプリを作成しました。
どのようなものなのかというと、

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801109/2c7a6043-c6f9-4198-ccd3-91fa7b456771.png)

ファイルの選択で下の船の画像を入れてみると

![fune1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801109/48dc7efe-a493-06e4-6ee9-e889069f14bf.jpeg)

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801109/f897dbdf-b4cd-7db5-d835-b9d7a898cee0.png)

このようにAIが自動でどちらか教えてくれます。

このようなアプリの開発を行った経緯としては、海上飛行機やオスプレイなどのように２つの役割をもつものはどちらに分類されるのか気になったからです。
~~本当はオスプレイは飛行機とヘリコプターでの分類をしたかったですが・・・~~

以下URLです。
[飛行機と船の判別アプリ](https://flask-mnist1-app.onrender.com/)

* ここからは私がAidemyさんの「AIアプリ開発コース」を受講しどのように作成したかを記述していきます。
アプリケーションのUI等は講座内容で実行するものを今回の課題用に少し変更を入れて作成したものです。

# 2. 実行環境
* Google Colaboratory
* Visual Studio Code
* コマンドプロンプト

# 3. 準備
* ### webアプリの開発
Flaskを用いてwebアプリを作成します。
アプリの見た目を作成するためにHTMLとCSSを作成します。

* #### HTML
```HTML:
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>airplane or ship Classifier</title>
    <link rel="stylesheet" href="./static/stylesheet.css">
</head>
<body>
    <header>   
        <img class="header_img" src="https://aidemyexstorage.blob.core.windows.net/aidemycontents/1621500180546399.png" alt="Aidemy">
        <a class="header-logo" href="#">airplane or ship Classifier</a>
    </header>

    <div class="main">    
        <h2> AIが送信された画像を飛行機か船かで識別します</h2>
        <p>画像を送信してください</p>
        <form method="POST" enctype="multipart/form-data">
            <input class="file_choose" type="file" name="file">
            <input class="btn" value="submit!" type="submit">
        </form>
        <div class="answer">{{answer}}</div>
    </div>

    <footer>
        <img class="footer_img" src="https://aidemyexstorage.blob.core.windows.net/aidemycontents/1621500180546399.png" alt="Aidemy">
        <small>&copy; 2019 Aidemy, inc.</small>   
    </footer>
</body>
</html>
```

* #### CSS
```CSS:
header {
    background-color: #76B55B;
    height: 60px;
    margin: -8px;
    display: flex;
    flex-direction: row-reverse;
    justify-content: space-between;
}

.header-logo {
    color: #fff;
    font-size: 25px;
    margin: 15px 25px;
}

.header_img {
    height: 25px;
    margin: 15px 25px;
}

.main {
    height: 370px;
}

h2 {
    color: #444444;
    margin: 90px 0px;
    text-align: center;
}

p {
    color: #444444;
    margin: 70px 0px 30px 0px;
    text-align: center;
}

.answer {
    color: #444444;
    margin: 70px 0px 30px 0px;
    text-align: center;
}

form {
    text-align: center;
}

footer {
    background-color: #F7F7F7;
    height: 110px;
    margin: -8px;
    position: relative;
}

.footer_img {
    height: 25px;
    margin: 15px 25px;
}

small {
    margin: 15px 25px;
    position: absolute;
    left: 0;
    bottom: 0;
}
```
# 4. モデルの作成
ここから精度を見ながらモデルの作成をしていきます。
コード内にて作業内容を記述しています。
* #### model
```python:model
#必要なライブラリのインポート
from tensorflow.keras.applications import VGG16
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.datasets import cifar10
from tensorflow.keras import optimizers
import tensorflow as tf
import numpy as np

#データの準備
# CIFAR-10データセットをロード
(X_train, y_train), (X_test, y_test) = cifar10.load_data()

# 飛行機（クラス0）と船（クラス8）のデータをフィルタリング
train_mask = np.isin(y_train, [0, 8]).flatten()
test_mask = np.isin(y_test, [0, 8]).flatten()

# 飛行機と船のデータのみを抽出
X_train, y_train = X_train[train_mask], y_train[train_mask]
X_test, y_test = X_test[test_mask], y_test[test_mask]

# ラベルを0と1に変換(船はCIFAR-10だと8のため1に変換)
y_train = np.where(y_train == 0, 0, 1)
y_test = np.where(y_test == 0, 0, 1)

# ラベルをカテゴリカルデータに変換
y_train = to_categorical(y_train, 2)
y_test = to_categorical(y_test, 2)

# VGG16ベースモデルのロード
base_model = VGG16(weights='imagenet', include_top=False, input_shape=(32, 32, 3))

# ベースモデルの出力をフラットに変更
x = base_model.output
x = Flatten()(x)

# 全結合層を追加
x = Dense(256, activation='relu')(x)
x = Dense(2, activation='softmax')(x)

# 新しいモデルを作成
model = Model(inputs=base_model.input, outputs=x)


# ベースモデルの層を固定
for layer in base_model.layers:
    layer.trainable = False


# モデルのコンパイル
model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
              metrics=['accuracy'])

# モデルの学習
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=32)


# 学習結果を表示
import matplotlib.pyplot as plt

# 訓練と検証の損失値のプロット
plt.plot(history.history['loss'], label='train loss')
plt.plot(history.history['val_loss'], label='validation loss')
plt.legend()
plt.show()

# 訓練と検証の精度のプロット
plt.plot(history.history['accuracy'], label='train accuracy')
plt.plot(history.history['val_accuracy'], label='validation accuracy')
plt.legend()
plt.show()
```
Epoch 1/50
313/313 [==============================] - 167s 529ms/step - loss: 1.3711 - accuracy: 0.7067 - val_loss: 0.7955 - val_accuracy: 0.7885
Epoch 2/50
313/313 [==============================] - 168s 536ms/step - loss: 0.5781 - accuracy: 0.7257 - val_loss: 0.5797 - val_accuracy: 0.7195
Epoch 3/50
313/313 [==============================] - 164s 524ms/step - loss: 0.6036 - accuracy: 0.6722 - val_loss: 0.6520 - val_accuracy: 0.7165
Epoch 4/50
313/313 [==============================] - 163s 522ms/step - loss: 0.5799 - accuracy: 0.6961 - val_loss: 0.6469 - val_accuracy: 0.6465
Epoch 5/50
313/313 [==============================] - 166s 530ms/step - loss: 0.5546 - accuracy: 0.7055 - val_loss: 0.5970 - val_accuracy: 0.6615

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801109/c80be2de-4e41-0d36-3f28-93522e6add10.png)

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801109/9b4d7d19-aa10-3fcf-3d74-b1de05effd7b.png)

結果としては精度は６割程度でしょうか。
今回はepochs=5としたので精度もあまり高くはありませんし安定していません。
そして回数を増やそうと思うと学習時間が結構かかります。

# 5. modelの改良
* #### コンパイル
まずはコンパイル文ですが今回は"categorical_crossentropy"を使用しましたがこちらは３クラス以上の分類で使用するのに適しているものでした。その結果、余計な計算が発生してしまい効率が悪くなってしまい精度が上がらなかったと考えました。

なのでコンパイル文を下記のように変更します。
```python:コンパイル変更箇所
model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])
```
今回は飛行機と船の２クラスでの分類なので、２クラス分類に適している"binary_crossentropy"を使用してみます。

* #### EarlyStoppingの使用
epochs数を増やして学習させたいが精度が上がらないまま学習を続ける可能性があるのでそれを回避するために"EarlyStopping"を追加します。
```python:EarlyStopping
# EarlyStoppingのコールバックを設定
early_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss', 
                                                  patience=3, 
                                                  restore_best_weights=True)

# モデルの学習
history = model.fit(X_train, y_train, epochs=50,
                    validation_data=(X_test, y_test),
                    callbacks=[early_stopping])
```
こちらを追加することで"val_loss"の数値を監視し、指定回数改善しなかった場合に学習を止めることで過学習を抑えることができます。


# 6. 改善結果

Epoch 1/50
313/313 [==============================] - 165s 523ms/step - loss: 1.2587 - accuracy: 0.8127 - val_loss: 0.6199 - val_accuracy: 0.8475
Epoch 2/50
313/313 [==============================] - 165s 529ms/step - loss: 0.3922 - accuracy: 0.8923 - val_loss: 0.5461 - val_accuracy: 0.8510
Epoch 3/50
313/313 [==============================] - 165s 528ms/step - loss: 0.2189 - accuracy: 0.9252 - val_loss: 0.5025 - val_accuracy: 0.8555
Epoch 4/50
313/313 [==============================] - 164s 525ms/step - loss: 0.1387 - accuracy: 0.9527 - val_loss: 0.5361 - val_accuracy: 0.8595
Epoch 5/50
313/313 [==============================] - 164s 525ms/step - loss: 0.0979 - accuracy: 0.9678 - val_loss: 0.5647 - val_accuracy: 0.8575
Epoch 6/50
313/313 [==============================] - 164s 526ms/step - loss: 0.0734 - accuracy: 0.9769 - val_loss: 0.5467 - val_accuracy: 0.8575

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801109/cf53be09-8627-7377-637b-10d6932e6f50.png)

![結果2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801109/9e9aed72-20a1-8c52-b10e-488a1f0b5c95.png)

結果として精度が約８５％と高くなり改善は成功しました。

# 7. やりたかったことの実践
では、私が気になっていた海上飛行機の画像を入れてみてどのように判別されるか見てみましょう。

![kauzyou_hikouki.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801109/827d87db-7da2-2b37-f774-fbddb03d0756.jpeg)

こちらのとある劇中に出てきている海上飛行機で試してみます。
すると、

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801109/f7d820f7-0ac5-57b5-5f0f-de2709bd95be.png)

飛行機と判別されました。
周りが海なので惑わされるかと思いましたが、しっかりと画像に写っているもので判別して飛行機と検知してくれました。

# 8. 考察
* 作成するモデルに最適なコンパイル文を使用することで精度が向上する。
* "EarlyStopping"を使用したが結局epochs数は６で止まってしまったので、元のmodelの中間層などを変更することで精度の改善が見込めそうである。
* 周りのものや背景には惑わされることなく判別ができているのは、元の学習で周りの背景には重みが少なく学習されているため結果には大きく関係はしない。

# 9. 終わりに
最後まで読んでいただいてありがとうございます。
なんとか目標とすることの飛行機と船の識別アプリを完成させることができました。課題は精度をもっと上げたり、２クラス分類でなく３クラス、４クラスと範囲を広くして対応できるものを増やしたりとまだまだありますが、初めて作るAIアプリに私は満足をしています。
作成途中では何度もエラーがありましたが、そのたびに調べるという作業を繰り返すことで乗り越えられました。自分で調べる能力がかなり必要なことと気軽に質問ができる環境が大事であること、そして何より自分自身の向上心が必要だと痛感しました。この経験は今後とも活かされるものとなるのでこの経験ができてよかったと思います。

最後までお付き合いいただきありがとうございました。