【Python】画像認識で世界のタワーの識別

# はじめに
私は建築学科卒で学生時代に、タワー構造に関する研究をしていました。
いろいろなタワーを見ていく中で中身を見れば見るほど、それぞれのこだわりはあるものの、見た目も構造もすごく似ています。
画像認識をやるなら思い入れがあるタワー建築に関してがいいなと思い、このアプリケーションを作成しました。

# 実際のアプリケーション
先に実際のアプリケーションをお見せします。
https://aidemy-seika.onrender.com

# 目次
1.実行環境
2.画像収集
3.実行
4.HTMLの作成
5.試行錯誤
6.結果と考察
7.まとめ


# 実行環境
・Visual Studio Code
・Google Colaboratory

# 1.画像収集
必要な画像を用意します。
まず、GoogleDrive上に画像を保管する場合はドライブをマウントしなければなりません。
以下のコードを実行することで、マウントできます。

```python
from google.colab import drive
drive.mount('/content/drive')
```

次にいよいよ「scraiping」で画像収集をしていきます。

参考文献：
＞＞【だれでもできる】プログラミングが未経験でも大丈夫。Webから大量画像を収集する方法をわかりやすく解説します。（YouTube動画です）
　　https://www.youtube.com/watch?v=hRB104ik6pQ
＞＞画像データをキーワード検索で効率的に収集する方法（Python「icrawler」のBing検索）
　　https://www.atmarkit.co.jp/ait/articles/2010/28/news018.html

```python
#必要な機能のインストール
!pip install icrawler

#pythonライブラリの「icrawler」でBing用モジュールをインポート
from icrawler.builtin import BingImageCrawler

#ブルジュ・ハリファをキーワードとする
search_word = "ブルジュ・ハリファ"
#ダウンロードするキーワード
crawler = BingImageCrawler(storage={'root_dir': "/content/drive/MyDrive/Aidemy_seikabutu/production/Bruju"})
#ダウンロードする画像の最大枚数は130枚
crawler.crawl(keyword=search_word, max_num=100)

#上海タワーをキーワードとする
search_word = "上海タワー"
#ダウンロードするキーワード
crawler = BingImageCrawler(storage={'root_dir': "/content/drive/MyDrive/Aidemy_seikabutu/production/Shanghai"})
#ダウンロードする画像の最大枚数は100枚
crawler.crawl(keyword=search_word, max_num=100)

#台北101をキーワードとする
search_word = "台北101"
#ダウンロードするキーワード
crawler = BingImageCrawler(storage={'root_dir': "/content/drive/MyDrive/Aidemy_seikabutu/production/Taipei"})
#ダウンロードする画像の最大枚数は100枚
crawler.crawl(keyword=search_word, max_num=100)

#東京タワーをキーワードとする
search_word = "東京タワー"
#ダウンロードするキーワード
crawler = BingImageCrawler(storage={'root_dir': "/content/drive/MyDrive/Aidemy_seikabutu/production/TokyoTower"})
#ダウンロードする画像の最大枚数は100枚
crawler.crawl(keyword=search_word, max_num=100)

#東京スカイツリーをキーワードとする
search_word = "東京スカイツリー"
#ダウンロードするキーワード
crawler = BingImageCrawler(storage={'root_dir': "/content/drive/MyDrive/Aidemy_seikabutu/production/TokyoSkytree"})
#ダウンロードする画像の最大枚数は100枚
crawler.crawl(keyword=search_word, max_num=100)


#エッフェル塔をキーワードとする
search_word = "エッフェル塔"
#ダウンロードするキーワード
crawler = BingImageCrawler(storage={'root_dir': "/content/drive/MyDrive/Aidemy_seikabutu/production/EiffelTower"})
#ダウンロードする画像の最大枚数は100枚
crawler.crawl(keyword=search_word, max_num=100)

#ピサの斜塔をキーワードとする
search_word = "ピサの斜塔"
#ダウンロードするキーワード
crawler = BingImageCrawler(storage={'root_dir': "/content/drive/MyDrive/Aidemy_seikabutu/production/PisaLeaning"})
#ダウンロードする画像の最大枚数は100枚
crawler.crawl(keyword=search_word, max_num=100)

```
まずは100枚でやってみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/5cb7af42-0489-1a39-6c20-b8f14bc4c605.png)

しっかりと画像が保存されているのを確認しました。

# 2.画像の処理
流し見して不要な画像があまりにも多くなければ、スキップしてもいいかもしれません。
私はまず、実行してみて、不具合を起こしている画像があるフォルダをあぶりだしてから、削除しに戻りました。

私が行った手順はこちらです。
1.拡張子がすべてjpgであることを確認
2.明らかに傾向からずれている写真を削除
（例）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/c1e6c74c-5cbc-780e-6633-42e88adccc04.png)
このような画像を削除しました。

# 3.モデルの実行
```python
import os#osモジュール（os機能がpythonで扱えるようにする）
import cv2#画像や動画を処理するオープンライブラリ
import numpy as np#python拡張モジュール
import matplotlib.pyplot as plt#グラフ可視化
from tensorflow.keras.utils import to_categorical#正解ラベルをone-hotベクトルで求める
from tensorflow.keras.layers import Dense, Dropout, Flatten, Input#全結合層、過学習予防、平滑化、インプット
from tensorflow.keras.applications.vgg16 import VGG16#学習済モデル
from tensorflow.keras.models import Model, Sequential#線形モデル
from tensorflow.keras import optimizers#最適化関数

#世界のタワー画像の格納
drive_Bruju = "/content/drive/MyDrive/Aidemy_seikabutu/production/Bruju/"
drive_Shanghai = "/content/drive/MyDrive/Aidemy_seikabutu/production/Shanghai/"
drive_Taipei = "/content/drive/MyDrive/Aidemy_seikabutu/production/Taipei/"
drive_TokyoTower = "/content/drive/MyDrive/Aidemy_seikabutu/production/TokyoTower/"
drive_TokyoSkytree = "/content/drive/MyDrive/Aidemy_seikabutu/production/TokyoSkytree/"
drive_EiffelTower = "/content/drive/MyDrive/Aidemy_seikabutu/production/EiffelTower/"
drive_PisaLeaning = "/content/drive/MyDrive/Aidemy_seikabutu/production/PisaLeaning/"
image_size = 50#50×50のサイズに指定

#os.listdir() で指定したファイルを取得
path_Bruju = [filename for filename in os.listdir(drive_Bruju) if not filename.startswith('.')]
path_Shanghai = [filename for filename in os.listdir(drive_Shanghai) if not filename.startswith('.')]
path_Taipei = [filename for filename in os.listdir(drive_Taipei) if not filename.startswith('.')]
path_TokyoTower = [filename for filename in os.listdir(drive_TokyoTower) if not filename.startswith('.')]
path_TokyoSkytree = [filename for filename in os.listdir(drive_TokyoSkytree) if not filename.startswith('.')]
path_EiffelTower = [filename for filename in os.listdir(drive_EiffelTower) if not filename.startswith('.')]
path_PisaLeaning = [filename for filename in os.listdir(drive_PisaLeaning) if not filename.startswith('.')]

#各種タワーの画像を格納するリスト作成
img_Bruju = []
img_Shanghai = []
img_Taipei = []
img_TokyoTower = []
img_TokyoSkytree = []
img_EiffelTower = []
img_PisaLeaning = []
for i in range(len(path_Bruju)):
    img = cv2.imread(drive_Bruju+ path_Bruju[i])#画像を読み込む
    img = cv2.resize(img,(image_size,image_size))#画像をリサイズする
    img_Bruju.append(img)#画像配列に画像を加える

for i in range(len(path_Shanghai)):
    img = cv2.imread(drive_Shanghai+ path_Shanghai[i])
    img = cv2.resize(img,(image_size,image_size))
    img_Shanghai.append(img)

for i in range(len(path_Taipei)):
   img = cv2.imread(drive_Taipei+ path_Taipei[i])
   img = cv2.resize(img,(image_size,image_size))
   img_Taipei.append(img)
   
for i in range(len(path_TokyoTower)):
   img = cv2.imread(drive_TokyoTower+ path_TokyoTower[i])
   img = cv2.resize(img,(image_size,image_size))
   img_TokyoTower.append(img)

for i in range(len(path_TokyoSkytree)):
   img = cv2.imread(drive_TokyoSkytree+ path_TokyoSkytree[i])
   img = cv2.resize(img,(image_size,image_size))
   img_TokyoSkytree.append(img)

for i in range(len(path_EiffelTower)):
   img = cv2.imread(drive_EiffelTower+ path_EiffelTower[i])
   img = cv2.resize(img,(image_size,image_size))
   img_EiffelTower.append(img)

for i in range(len(path_PisaLeaning)):
   img = cv2.imread(drive_PisaLeaning+ path_PisaLeaning[i])
   img = cv2.resize(img,(image_size,image_size))
   img_PisaLeaning.append(img)

#np.arrayでXに学習画像、ｙに正解ラベルを代入
X = np.array(img_Bruju + img_Shanghai + img_Taipei + img_TokyoTower + img_TokyoSkytree + img_EiffelTower + img_PisaLeaning)
#正解ラベルの作成
y =  np.array([0]*len(img_Bruju) + [1]*len(img_Shanghai) + [2]*len(img_Taipei) + [3]*len(img_TokyoTower) + [4]*len(img_TokyoSkytree) + [5]*len(img_EiffelTower) + [6]*len(img_PisaLeaning))
label_num = list(set(y))
#配列のラベルをシャッフルする
rand_index = np.random.permutation(np.arange(len(X)))
X = X[rand_index]
y = y[rand_index]
#学習データと検証データを用意
X_train = X[:int(len(X)*0.8)]
y_train = y[:int(len(y)*0.8)]
X_test = X[int(len(X)*0.8):]
y_test = y[int(len(y)*0.8):]
print(X_train.shape)
print(y_train.shape)
print(X_test.shape)
print(y_test.shape)

#正解ラベルをone-hotベクトルで求める
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

#モデルの入力画像として用いるためのテンソールのオプション
input_tensor = Input(shape=(image_size,image_size, 3))

#転移学習のモデルとしてVGG16を使用
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

#モデルの定義～活性化関数シグモイド
#転移学習の自作モデルとして下記のコードを作成
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
# top_model.add(Dense(256, activation="sigmoid"))
# top_model.add(Dropout(0.5))
# top_model.add(Dense(64, activation='sigmoid'))
# top_model.add(Dropout(0.5))
top_model.add(Dense(32, activation='sigmoid'))
top_model.add(Dropout(0.5))
top_model.add(Dense(7, activation='softmax'))

#vggと自作のtop_modelを連結
model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

#vgg16による特徴抽出部分の重みを15層までに固定（以降に新しい層（top_model)が追加）
for layer in model.layers[:15]:
   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, batch_size=32, epochs=50, verbose=1, validation_data=(X_test, y_test))
score = model.evaluate(X_test, y_test, batch_size=32, verbose=0)
print('validation loss:{0[0]}\nvalidation accuracy:{0[1]}'.format(score))
#acc, val_accのプロット
plt.plot(history.history["accuracy"], label="accuracy", ls="-", marker="o")
plt.plot(history.history["val_accuracy"], label="val_accuracy", ls="-", marker="x")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.show()
#モデルを保存
model.save("my_model.h5")
```
以上のコードの中で実行するにあたって、考えるべきだと思う点を記述します。

参考文献：
＞＞numpy.random.permutation – 配列の要素をランダムに並べ替えた新しい配列を生成
　　https://www.headboost.jp/numpy-random-permutation/
＞＞Generator.permutation – 既存の配列の要素をランダムに並べ替えた新しい配列を生成
　　https://www.headboost.jp/generator-permutation/


# (i)画像の縮小サイズ
```python
image_size = 50#50×50のサイズに指定
```
これは画像を同一のサイズするときのサイズを定義しています。
はじめは特徴がつぶれる可能性があると思い、150×150で実行していました。
```python
image_size = 150#150×150のサイズに指定
```
ですが、出力してみると、50×50で十分だと感じましたので、50×50で本番も実行しました。
```python
from google.colab.patches import cv2_imshow
cv2_imshow(img_EiffelTower[50])
```
上記の実行結果がこちらです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/f4fb1dfb-a02a-93cd-a7c7-4e64dfca3408.png)

十分ですね。
実際にモデルの学習もできていました。

# (ii)学習データと検証データの割合
```python
#学習データと検証データを用意
X_train = X[:int(len(X)*0.8)]
y_train = y[:int(len(y)*0.8)]
X_test = X[int(len(X)*0.8):]
y_test = y[int(len(y)*0.8):]
```
これは8：2で悩まなかったです。
はじめ9：1でも結果はほとんど変わりませんでした。

# (iii)学習モデルのニューロン数の定義
```python
#モデルの定義～活性化関数シグモイド
#転移学習の自作モデルとして下記のコードを作成
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
# top_model.add(Dense(256, activation="sigmoid"))
# top_model.add(Dropout(0.5))
# top_model.add(Dense(64, activation='sigmoid'))
# top_model.add(Dropout(0.5))
top_model.add(Dense(32, activation='sigmoid'))
top_model.add(Dropout(0.5))
top_model.add(Dense(7, activation='softmax'))
```
はじめは256,64も定義し、学習させていましたが、あまり意味ないかなと思いました。
それは、outputが7定義だけだったからです。
100を超える定義数であれば、必要だと思いますが、今回はモデルをできるだけ軽くしたかったので、32，7のみとしました。

こちらを参考にすれば、無駄のない定義ができるかと思います。
https://www.youtube.com/watch?v=OwccN7rj4Qg&t=327s

他にも考えた点はたくさんありますが、以下に記載しているので、割愛します。

# 4.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>Tower 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="#">Tower Classifier</a>
    </header>

    <div class="main">    
        <h2> AIが送信された3種類のタワーを識別します（台北101、東京スカイツリー、ピサの斜塔）</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; 2023 OGASAWARA, inc.</small>   
    </footer>
</body>
</html>
```
AidemyのFlask入門の中にあるコードを参考にしました。


# 5.試行錯誤
上記のコードは進んではいたものの、実行時間長すぎて断念しました。
（1時間くらいたっても画像処理をしている）

以下に試行錯誤した内容を記述します。

# モデルの定義をsigmoidからReLUに変更
```python
#モデルの定義～活性化関数ReLU
#転移学習の自作モデルとして下記のコードを作成
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(32, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(7, activation='softmax'))
```
epoch数を3にして18分くらいで実行が完了しました。
```
#実行結果
(1732, 50, 50, 3)
(1732,)
(434, 50, 50, 3)
(434,)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg16/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5
58889256/58889256 [==============================] - 0s 0us/step
/usr/local/lib/python3.8/dist-packages/keras/optimizers/optimizer_v2/gradient_descent.py:108: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
  super(SGD, self).__init__(name, **kwargs)
Epoch 1/3
55/55 [==============================] - 81s 1s/step - loss: 1.9528 - accuracy: 0.1669 - val_loss: 1.9058 - val_accuracy: 0.2028
Epoch 2/3
55/55 [==============================] - 80s 1s/step - loss: 1.9246 - accuracy: 0.1801 - val_loss: 1.8867 - val_accuracy: 0.2235
Epoch 3/3
55/55 [==============================] - 79s 1s/step - loss: 1.9049 - accuracy: 0.1761 - val_loss: 1.8800 - val_accuracy: 0.2396
validation loss:1.8800395727157593
validation accuracy:0.23963133990764618
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/b64c90b8-60b8-c2f9-7a29-db2e11ab2261.png)

ReLUにして大幅に実行時間が短くなりました。
ただ、accuracyは0.18ほどですし、上昇の傾向が見られないので、これも失敗です。


# 画像を増やす

最大500枚にして大幅に画像数を増やしました。
全てのフォルダが250-350枚ほどになりました。
```
#実行結果
(1732, 50, 50, 3)
(1732,)
(434, 50, 50, 3)
(434,)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg16/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5
58889256/58889256 [==============================] - 2s 0us/step
Epoch 1/3
/usr/local/lib/python3.8/dist-packages/keras/optimizers/optimizer_v2/gradient_descent.py:108: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
  super(SGD, self).__init__(name, **kwargs)
55/55 [==============================] - 11s 39ms/step - loss: 3.2285 - accuracy: 0.1801 - val_loss: 1.9457 - val_accuracy: 0.1382
Epoch 2/3
55/55 [==============================] - 1s 24ms/step - loss: 1.9455 - accuracy: 0.1461 - val_loss: 1.9456 - val_accuracy: 0.1382
Epoch 3/3
55/55 [==============================] - 1s 24ms/step - loss: 1.9451 - accuracy: 0.1484 - val_loss: 1.9454 - val_accuracy: 0.1429
validation loss:1.9453628063201904
validation accuracy:0.1428571492433548
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/3c1cd1ff-9a23-df3e-3a9e-8b5f22a0da00.png)

結果変わらずです。。

# 分類数7→3
分類数を3に減らし、その中でもわかりやすい「台北101」「東京スカイツリー」「ピサの斜塔」にしました。
```
# 実行結果
(668, 50, 50, 3)
(668,)
(167, 50, 50, 3)
(167,)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg16/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5
58889256/58889256 [==============================] - 5s 0us/step
Epoch 1/50
/usr/local/lib/python3.8/dist-packages/keras/optimizers/optimizer_v2/gradient_descent.py:108: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
  super(SGD, self).__init__(name, **kwargs)
21/21 [==============================] - 10s 68ms/step - loss: 3.3761 - accuracy: 0.5314 - val_loss: 0.8457 - val_accuracy: 0.6168
Epoch 2/50
21/21 [==============================] - 1s 26ms/step - loss: 0.9064 - accuracy: 0.5569 - val_loss: 0.7222 - val_accuracy: 0.6766
Epoch 3/50
21/21 [==============================] - 1s 29ms/step - loss: 0.8450 - accuracy: 0.6138 - val_loss: 0.7792 - val_accuracy: 0.5090
Epoch 4/50
21/21 [==============================] - 1s 28ms/step - loss: 0.8535 - accuracy: 0.5554 - val_loss: 0.7712 - val_accuracy: 0.6946
Epoch 5/50
21/21 [==============================] - 1s 25ms/step - loss: 0.7171 - accuracy: 0.6437 - val_loss: 0.6562 - val_accuracy: 0.7126
＝＝＝＝＝＝＝＝＝＝＝＝＝＝

中間層省略

＝＝＝＝＝＝＝＝＝＝＝＝＝＝
Epoch 45/50
21/21 [==============================] - 1s 28ms/step - loss: 0.1339 - accuracy: 0.9162 - val_loss: 0.5575 - val_accuracy: 0.8563
Epoch 46/50
21/21 [==============================] - 1s 25ms/step - loss: 0.1446 - accuracy: 0.9207 - val_loss: 0.7276 - val_accuracy: 0.8623
Epoch 47/50
21/21 [==============================] - 1s 25ms/step - loss: 0.1138 - accuracy: 0.9326 - val_loss: 0.6174 - val_accuracy: 0.8623
Epoch 48/50
21/21 [==============================] - 1s 25ms/step - loss: 0.1198 - accuracy: 0.9266 - val_loss: 0.5279 - val_accuracy: 0.8862
Epoch 49/50
21/21 [==============================] - 1s 25ms/step - loss: 0.1218 - accuracy: 0.9341 - val_loss: 0.7009 - val_accuracy: 0.8683
Epoch 50/50
21/21 [==============================] - 1s 25ms/step - loss: 0.1454 - accuracy: 0.9296 - val_loss: 0.8367 - val_accuracy: 0.8503
validation loss:0.836748480796814
validation accuracy:0.8502994179725647
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/d0302e35-6b40-7350-f4b8-db07291a808a.png)

3分類ではうまくいきました。
ギリギリのラインを狙っていきたいと思います。

# 分類数3→4

分類数を1つだけ増やしてみました。
その中でもあえて、「東京スカイツリー」と似ている「エッフェル塔」を追加しました。
```
# 実行結果
(944, 50, 50, 3)
(944,)
(236, 50, 50, 3)
(236,)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg16/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5
58889256/58889256 [==============================] - 2s 0us/step
Epoch 1/50
/usr/local/lib/python3.8/dist-packages/keras/optimizers/optimizer_v2/gradient_descent.py:108: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
  super(SGD, self).__init__(name, **kwargs)
30/30 [==============================] - 10s 52ms/step - loss: 3.7518 - accuracy: 0.3676 - val_loss: 1.3861 - val_accuracy: 0.2542
Epoch 2/50
30/30 [==============================] - 1s 25ms/step - loss: 1.3860 - accuracy: 0.2733 - val_loss: 1.3858 - val_accuracy: 0.2542
Epoch 3/50
30/30 [==============================] - 1s 24ms/step - loss: 1.3857 - accuracy: 0.2733 - val_loss: 1.3853 - val_accuracy: 0.2542
Epoch 4/50
30/30 [==============================] - 1s 24ms/step - loss: 1.3853 - accuracy: 0.2733 - val_loss: 1.3848 - val_accuracy: 0.2542
Epoch 5/50
30/30 [==============================] - 1s 25ms/step - loss: 1.3849 - accuracy: 0.2733 - val_loss: 1.3844 - val_accuracy: 0.2542
＝＝＝＝＝＝＝＝＝＝＝＝＝＝

中間層省略

＝＝＝＝＝＝＝＝＝＝＝＝＝＝
Epoch 45/50
30/30 [==============================] - 1s 25ms/step - loss: 1.3762 - accuracy: 0.2860 - val_loss: 1.3720 - val_accuracy: 0.3178
Epoch 46/50
30/30 [==============================] - 1s 25ms/step - loss: 1.3762 - accuracy: 0.2860 - val_loss: 1.3718 - val_accuracy: 0.3178
Epoch 47/50
30/30 [==============================] - 1s 23ms/step - loss: 1.3762 - accuracy: 0.2860 - val_loss: 1.3716 - val_accuracy: 0.3178
Epoch 48/50
30/30 [==============================] - 1s 25ms/step - loss: 1.3761 - accuracy: 0.2860 - val_loss: 1.3714 - val_accuracy: 0.3178
Epoch 49/50
30/30 [==============================] - 1s 24ms/step - loss: 1.3750 - accuracy: 0.2860 - val_loss: 1.3712 - val_accuracy: 0.3178
Epoch 50/50
30/30 [==============================] - 1s 24ms/step - loss: 1.3746 - accuracy: 0.2860 - val_loss: 1.3709 - val_accuracy: 0.3178
validation loss:1.370943546295166
validation accuracy:0.31779661774635315
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/b52932a7-46bd-32b0-bbe5-c74c1511d55e.png)

またaccuracyが低くなってしまいました。
これは「東京スカイツリー」と「エッフェル塔」が似ているから判断ミスをしているのではないか、と考えました。

# 「東京スカイツリー」と「エッフェル塔」の2分類
「東京スカイツリー」と「エッフェル塔」が見分けられているのか、確認します。
```
# 実行結果
(532, 50, 50, 3)
(532,)
(134, 50, 50, 3)
(134,)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg16/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5
58889256/58889256 [==============================] - 2s 0us/step
Epoch 1/50
/usr/local/lib/python3.8/dist-packages/keras/optimizers/optimizer_v2/gradient_descent.py:108: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
  super(SGD, self).__init__(name, **kwargs)
17/17 [==============================] - 11s 82ms/step - loss: 3.0961 - accuracy: 0.6090 - val_loss: 0.5786 - val_accuracy: 0.6866
Epoch 2/50
17/17 [==============================] - 0s 26ms/step - loss: 0.6771 - accuracy: 0.6053 - val_loss: 0.6441 - val_accuracy: 0.5522
Epoch 3/50
17/17 [==============================] - 0s 26ms/step - loss: 0.6170 - accuracy: 0.6767 - val_loss: 0.5800 - val_accuracy: 0.6045
Epoch 4/50
17/17 [==============================] - 0s 27ms/step - loss: 0.5112 - accuracy: 0.6541 - val_loss: 0.4739 - val_accuracy: 0.7910
Epoch 5/50
17/17 [==============================] - 0s 27ms/step - loss: 0.4902 - accuracy: 0.7857 - val_loss: 0.4860 - val_accuracy: 0.8433
＝＝＝＝＝＝＝＝＝＝＝＝＝＝

中間層省略

＝＝＝＝＝＝＝＝＝＝＝＝＝＝
Epoch 45/50
17/17 [==============================] - 0s 27ms/step - loss: 0.0115 - accuracy: 0.9925 - val_loss: 0.3542 - val_accuracy: 0.9179
Epoch 46/50
17/17 [==============================] - 0s 27ms/step - loss: 0.0106 - accuracy: 0.9925 - val_loss: 0.3912 - val_accuracy: 0.9104
Epoch 47/50
17/17 [==============================] - 0s 26ms/step - loss: 0.0107 - accuracy: 0.9925 - val_loss: 0.3739 - val_accuracy: 0.9179
Epoch 48/50
17/17 [==============================] - 0s 27ms/step - loss: 0.0165 - accuracy: 0.9906 - val_loss: 0.3515 - val_accuracy: 0.9254
Epoch 49/50
17/17 [==============================] - 0s 27ms/step - loss: 0.0116 - accuracy: 0.9962 - val_loss: 0.3967 - val_accuracy: 0.9104
Epoch 50/50
17/17 [==============================] - 0s 27ms/step - loss: 0.0107 - accuracy: 0.9944 - val_loss: 0.3727 - val_accuracy: 0.9254
validation loss:0.3727129101753235
validation accuracy:0.9253731369972229
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/df7086c4-03b2-1be1-2a7e-12bc02a27d80.png)

しっかり見分けられているようです。
難しい。。。

私はここで、断念しました。

# 6.結果と考察
タワーの分類は3分類までが私の実力では限界でした。
4分類以上はどこでモデルが間違いを起こしているのか、追求しきれませんでした。
他に考えられる問題としては以下だと考えます。

1.　4分類で他の画像同士が識別できていない。
2.　モデルの定義でReLUやsigmoidが今回の画像識別に合っていない。 
3.　学習させる画像の枚数がまだ足りない。

これらを解決すれば、うまくいくかもしれません。


# 7.まとめ
実際、受講してみて、ディープラーニングやAIの体系的な知識も学べましたし、基本的なモデルの構築もできるようになったので、非常にいい勉強になりました。
質問できる環境があるというのは非常に大きいことだと感じました。
独学でしたら確実に挫折していたと思います。

# 追記
このブログを書き終えた後に4分類でaccuracyが一定になってしまう、ということを
カウンセリングで聞いてみると、
1.ニューロン数を増やしてみたらどうか
2.画像サイズを上げてみたらどうか
とアドバイスいただき、やってみましたので、結果を記載します。

# 1.ニューロン数を増やしてみた結果
変更後のコード
```python
#モデルの定義～活性化関数ReLU
#転移学習の自作モデルとして下記のコードを作成
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation="relu"))
top_model.add(Dropout(0.5))
top_model.add(Dense(64, activation='relu'))
top_model.add(Dropout(0.5))
# top_model.add(Dense(32, activation='relu'))
# top_model.add(Dropout(0.5))
top_model.add(Dense(4, activation='softmax'))
```
実行結果
```
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg16/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5
58889256/58889256 [==============================] - 5s 0us/step
Epoch 1/50
/usr/local/lib/python3.8/dist-packages/keras/optimizers/optimizer_v2/gradient_descent.py:108: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
  super(SGD, self).__init__(name, **kwargs)
30/30 [==============================] - 10s 54ms/step - loss: 3.6176 - accuracy: 0.2966 - val_loss: 1.3850 - val_accuracy: 0.3136
Epoch 2/50
30/30 [==============================] - 1s 25ms/step - loss: 1.3856 - accuracy: 0.2903 - val_loss: 1.3855 - val_accuracy: 0.3051
Epoch 3/50
30/30 [==============================] - 1s 24ms/step - loss: 1.3854 - accuracy: 0.2892 - val_loss: 1.3850 - val_accuracy: 0.3051
Epoch 4/50
30/30 [==============================] - 1s 24ms/step - loss: 1.3849 - accuracy: 0.2892 - val_loss: 1.3845 - val_accuracy: 0.3051
Epoch 5/50
30/30 [==============================] - 1s 24ms/step - loss: 1.3845 - accuracy: 0.2892 - val_loss: 1.3841 - val_accuracy: 0.3051

＝＝＝＝＝＝＝＝＝＝＝＝＝＝

中間層省略

＝＝＝＝＝＝＝＝＝＝＝＝＝＝

Epoch 45/50
30/30 [==============================] - 1s 24ms/step - loss: 1.3716 - accuracy: 0.2998 - val_loss: 1.3718 - val_accuracy: 0.3051
Epoch 46/50
30/30 [==============================] - 1s 25ms/step - loss: 1.3723 - accuracy: 0.2733 - val_loss: 1.3716 - val_accuracy: 0.3051
Epoch 47/50
30/30 [==============================] - 1s 26ms/step - loss: 1.3717 - accuracy: 0.2956 - val_loss: 1.3715 - val_accuracy: 0.3051
Epoch 48/50
30/30 [==============================] - 1s 24ms/step - loss: 1.3725 - accuracy: 0.2934 - val_loss: 1.3713 - val_accuracy: 0.3051
Epoch 49/50
30/30 [==============================] - 1s 25ms/step - loss: 1.3726 - accuracy: 0.2924 - val_loss: 1.3712 - val_accuracy: 0.3051
Epoch 50/50
30/30 [==============================] - 1s 25ms/step - loss: 1.3718 - accuracy: 0.3019 - val_loss: 1.3710 - val_accuracy: 0.3051
validation loss:1.371010184288025
validation accuracy:0.3050847351551056
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/99028133-152d-7229-8d7a-5098b7d3753e.png)

変わりませんでした。

# 2.画像サイズを上げた結果
具体的には50×50から150×150に変更しました。

以下が実行結果になります。
```
Epoch 1/50
30/30 [==============================] - 8s 181ms/step - loss: 4.7264 - accuracy: 0.2903 - val_loss: 1.3747 - val_accuracy: 0.3814
Epoch 2/50
30/30 [==============================] - 3s 106ms/step - loss: 1.3904 - accuracy: 0.3114 - val_loss: 1.3770 - val_accuracy: 0.3347
Epoch 3/50
30/30 [==============================] - 3s 107ms/step - loss: 1.3740 - accuracy: 0.3167 - val_loss: 1.3640 - val_accuracy: 0.3390
Epoch 4/50
30/30 [==============================] - 3s 107ms/step - loss: 1.3699 - accuracy: 0.3263 - val_loss: 1.3537 - val_accuracy: 0.3686
Epoch 5/50
30/30 [==============================] - 3s 107ms/step - loss: 1.3624 - accuracy: 0.3326 - val_loss: 1.3417 - val_accuracy: 0.4025
＝＝＝＝＝＝＝＝＝＝＝＝＝＝

中間層省略

＝＝＝＝＝＝＝＝＝＝＝＝＝＝
Epoch 45/50
30/30 [==============================] - 3s 110ms/step - loss: 0.9123 - accuracy: 0.6854 - val_loss: 1.1464 - val_accuracy: 0.6102
Epoch 46/50
30/30 [==============================] - 3s 111ms/step - loss: 0.9176 - accuracy: 0.6780 - val_loss: 1.1241 - val_accuracy: 0.6229
Epoch 47/50
30/30 [==============================] - 3s 111ms/step - loss: 0.9237 - accuracy: 0.6854 - val_loss: 1.1286 - val_accuracy: 0.6144
Epoch 48/50
30/30 [==============================] - 3s 111ms/step - loss: 0.8865 - accuracy: 0.6970 - val_loss: 1.1179 - val_accuracy: 0.6144
Epoch 49/50
30/30 [==============================] - 3s 111ms/step - loss: 0.8298 - accuracy: 0.7246 - val_loss: 1.1322 - val_accuracy: 0.6441
Epoch 50/50
30/30 [==============================] - 3s 111ms/step - loss: 0.8130 - accuracy: 0.7585 - val_loss: 1.3233 - val_accuracy: 0.5593
validation loss:1.3232680559158325
validation accuracy:0.5593220591545105
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090401/2fabda9e-2d9f-2ee3-62de-3fde588c2b46.png)

75％まで向上しました！
はじめからこうすればよかった。。
しかし、次の問題が。

「Github、Renderの無料の容量に収まらない」

とのことでした。

ですので、今回は上記のまま、3分類で成果物を提出させていただきます。