機械学習によるギターとベースの画像認識

#はじめに
　機械学習に興味を持ったため、どうやって勉強しようかと考えていたところ、Aidemyなら公的補助を使いながら勉強できると知って、Aidemyで勉強することに決めました。
　
　そして、成果物として何を作ろうかな・・・と考えていたところ、ギターとベースを区別するアプリを作ることにしました。
　理由は、自分自身がベースを弾いていること・ベースとギターは似た楽器なので画像認識の勉強にちょうどいいかなと思ったからです。
　また、最終的には自動でベースの値段をつけるアプリを開発できたらと思っていて、その第一段階としてはちょうど良いと思ったからです。

今回のアプリはこちら↓
https://foraidemy.herokuapp.com/

ちなみに
![000000121160-02-l.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1308261/0cb587f2-7a8d-5ea3-6f84-0e1437f333f2.jpeg)
これがギターです。

![000000121219-02-l.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1308261/16f05493-5ead-700d-06d8-8bea95580e10.jpeg)
これがベースです。

大きく異なる点としては、弦の本数・太さが異なっています。
今回のアプリでは、このようなベースとギターの画像を多数集め、それぞれの楽器の特徴を人工知能に学習させて「ベースとはこういうものだ。ギターとはこういうものだ。だから今回アップされた画像はベース（ギター）だ！」と自分で作った人工知能に判別してもらえるようにしてみようといったものです。

#目次
・実行環境
・画像収集
・画像処理
・画像の学習及びモデルの構築
・結果
・おわりに

#実行環境
Python 3.81
Keras
Beautiful Soup4（以下BS4）
Selenium

Jupyter Notebook(主にスクレイピング時に使用)
Google colab(モデル構築時に使用)

※今回、Jupyter NotebookとGoogle colabの両方を使用したのは単純に両方を使ってみたかっただけですので、実際に作成する場合はどちらか片方のみを利用するだけでいいと思います。



#画像収集
　画像収集にはBS4・Seleniumを用いてスクレイピングをしました。
　もちろん、ギターとベースのデータセットはあるとは思いますが、スクレイピングからする方が勉強になると思ったので自分でスクレイピングをすることにしました。。
　ただ、AidemyだとBS4しかコースに含まれていなかったので、チューターの方に聞きながらSeleniumは勉強しました。

###スクレイピング手法
　スクレイピング元はいつもお世話になっているイケベ楽器の通販サイトからスクレイピングをしました。

　URL:https://www.ikebe-gakki.com/

　注意した点としてはサーバーに負荷をかけないようにテストで収集する際には5枚とかでやるように心がけました。
　そして、Seleniumを用いた理由としては、上記サイトだと画像のロード待ちが発生するので、BS4だとロード待ちをしているときの下記画像のようなものしか取得することができなかったからです。
![pngtree-load-the-png-image_2976163.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1308261/069f05dc-66cd-7d12-6192-4154e8f457f0.jpeg)
（おそらく、一度は皆さん目にしたことのあるロード待ちの画像。）

　なぜなら、BS4は静的な情報を取得するためのライブラリなので、今回の様に

「サイトにアクセス→ロード画像が表示→ロード完了した場合に商品画像を表示」

といった流れで推移するサイトだと、BS4は

「サイトにアクセス→ロード画像が表示→ロード画像が表示された状態の情報を取得」

といった流れで情報を取得してしまうので、ロード画像だけを取得してしまいます。

　しかし、Seleniumは実際に私たちが使うようにサイトをスクロールするなど操作してくれるライブラリなので、ロードが完了するまで待ってから画像を取得することが可能になります。


　今回、この手法を用いたことで、ベース約1,550枚・ギター約3,400枚の画像を収集することができたので画像の量としては十分な量を得ることができたかと思います。

###コード
```python
import time
import requests
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

def scraping(url):
    driver.get(url)
    # 日本語のページはencodeを推奨
    images =[]
    
    #先に下までゆっくりスクロールする
    for x in range(6):
        driver.execute_script("window.scrollBy(0, window.innerHeight);")
        time.sleep(10)
        html = driver.page_source.encode('utf-8')
        soup = BeautifulSoup(html , "html.parser")
        img_list =soup.find_all('img', class_="fs-c-productListItem__image__image fs-c-productImage__image")
        # 画像リストの作成を繰り返しする
        for i in img_list:
            images.append(i.get("src"))
        # 画像の保存を繰り返しする
    images = [ src for src in images if "https://ikebeweb.itembox.design/item/src/loading.svg" not in src]
    images = list(set(images))
    print(images)
    print(len(images))
    for target in images:
        re = requests.get(target)
        target = target.split("?")[0]
        print(target)
        with open('./img_for_bass/' + target.split('/')[-1], 'wb') as f: # imgフォルダに格納
            # .contentで画像データとして書き込む
            f.write(re.content)
    # 次へのリンクを取得する
    href = soup.find("a" , class_ ="fs-c-pagination__item fs-c-pagination__item--next")
    return href["href"]

base_url = "https://www.ikebe-gakki.com/p/search?keyword=&detailRadio=&tag=%E3%83%99%E3%83%BC%E3%82%B9&tag=&tag=&cat1=%E3%83%99%E3%83%BC%E3%82%B9&value2=&cat2=&value3=&cat3=&tag=&detailShop=&minprice=&maxprice="
url =base_url 
driver = webdriver.Chrome(executable_path ="C:\chromedriver.exe")
while True:
    href = scraping(url)
    if href == None:
        break
    url = base_url +  href 
```

　また、今回のコードにロード待ちだけで無く、スクロールの処理もさせている理由としては、イケベ楽器のサイトだとスクロールをしないと画面外に隠れている画像はロードしない様にされており、スクロールしなければいつまでもロードをしてくれず、ロード待ち画像を取得してしまうからです。
　よって、ご自身で作られる場合は利用するサイトに応じて変えていただくと良いと思います。

#画像処理

　下記コードでは、自分のフォルダに保存したベースとギターの画像をリサイズ→画像データにラベル付け→画像データをシャッフル→グレースケールで画像を数枚確認のために出力、といった流れで処理を進めています。
　こうすることで、そのままの画像だとPCはベースかギターかわからない状態の画像が、PCでもベースかギターかがわかる画像にできます。
###コード

```python
import matplotlib.pyplot as plt
import os
import cv2
import random
import numpy as np
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from tensorflow.keras.utils import to_categorical
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
import os

from google.colab import files

DATADIR = "drive/MyDrive/Aidemy"  #Google drive内のどこに画像フォルダがあるか
CATEGORIES = ["Guitar", "Bass"]
IMG_SIZE = 50
training_data = []
def create_training_data():
    for class_num, category in enumerate(CATEGORIES):
        path = os.path.join(DATADIR, category)
        for image_name in os.listdir(path):
            try:
                img_array = cv2.imread(os.path.join(path, image_name))  # 画像読み込み
                img_resize_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))  # 画像のリサイズ
                training_data.append([img_resize_array, class_num])  # 画像データ、ラベル情報を追加
            except Exception as e:
                pass
create_training_data()
random.shuffle(training_data)  # データをシャッフル
X = []  # 画像データ
y = []  # ラベル情報
# データセット作成
for feature, label in training_data:
    X.append(feature)
    y.append(label)
# numpy配列に変換
X = np.array(X)
y = np.array(y)
# データセットの確認
for i in range(0, 4):
    print("学習データのラベル：", y[i])
    plt.subplot(2, 2, i+1)
    plt.axis('off')
    plt.title(label = 'Guitar' if y[i] == 0 else 'Bass')
    plt.imshow(X[i], cmap='gray')
plt.show()
```
　なお、今回のコードではラベル付けが間違えていないかを確認するために、処理した画像から4つをピックアップして画像とその画像のラベルを表示することで確認ができる様にしています。

#画像の学習及びモデルの構築

　今回は、VGG16を用いて転移学習を行うことで作成するモデルの質を担保しつつ、自分自身でもモデルを定義することでさらに精度を向上させることに挑戦してみました。
　ちなみに、VGG16とは、100万枚を超える画像を学習させてすでに作られているニューラルネットワークのことで、これを利用して自分のモデルを作ることを転移学習と言います。
###コード

```python
#モデルの作成
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

X_train.shape

#VGG16を使う
input_tensor = Input(shape=(50,50,3))
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(128, activation="relu"))
top_model.add(Dense(64, activation="relu"))
top_model.add(Dense(2, activation = "softmax"))


model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

for layer in model.layers[:15]:
    layer.trainable = False

model.summary()

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

def pred_inst(img):
    img = cv2.resize(img, (50,50))
    pred = np.argmax(model.predict(np.array[img]))
    if pred == 0:
        return 'guitar'
    else:
        return 'bass'

    
#モデルの学習
model.fit(X_train, y_train, validation_data=(X_test, y_test), verbose = 1, batch_size=100, epochs=11)

    
#モデルの評価
scores = model.evaluate(X_test,y_test, verbose=1)
print("Test loss: ", scores[0])
print("Test accuracy: ",scores[1])
```

#結果

Test loss:  0.13838611543178558
Test accuracy:  0.9546998739242554

![グラフ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1308261/4cb28bdf-7e39-1d5f-f179-96b312acbc6f.png)


　正確性については95%程度と結構高くできました。
　そもそも転移学習をしているのである程度までは正確性が高くなっている状態で学習を始めることができたことが理由として挙げられると考えています。
 　試しに、VGG16を使わずに学習をさせた場合、正確性は90%程度まで落ちるので転移学習の効果というのはかなり高いと感じています。

　実際、完成までにいろいろとモデルの構成を変えながら試してみたものの、なかなか95%は超えることができませんでした。
　ちなみに、今回のコードの改良点としては画像の量を増やすことが考えられます。なぜなら、今回は合計4,000枚程度とそこまで多くなく、単純に画像量を増やすことで特徴をより多く抽出することができるため学習精度が向上すると考えられるからです。


#おわりに
　今後は、正確性を向上させると共に、ベースの種類を判別するアプリを作ってみたいと思っています。
　これで値段をつける一歩手前にこれると考えています。


##参考文献
・[すぐに使える！業務で実践できる！　PythonによるAI・機械学習・深層学習アプリのつくり方　TensorFlow2対応 [ クジラ飛行机 ]](https://hb.afl.rakuten.co.jp/ichiba/21886af9.28d0f9fd.21886afa.7d354fc2/?pc=https%3A%2F%2Fitem.rakuten.co.jp%2Fbook%2F16453281%2F&link_type=hybrid_url&ut=eyJwYWdlIjoiaXRlbSIsInR5cGUiOiJoeWJyaWRfdXJsIiwic2l6ZSI6IjI0MHgyNDAiLCJuYW0iOjEsIm5hbXAiOiJyaWdodCIsImNvbSI6MSwiY29tcCI6ImRvd24iLCJwcmljZSI6MSwiYm9yIjoxLCJjb2wiOjEsImJidG4iOjEsInByb2QiOjAsImFtcCI6ZmFsc2V9)
・[PythonとKerasによるディープラーニング [ Francois Chollet ]](https://hb.afl.rakuten.co.jp/ichiba/21886af9.28d0f9fd.21886afa.7d354fc2/?pc=https%3A%2F%2Fitem.rakuten.co.jp%2Fbook%2F15447911%2F&link_type=hybrid_url&ut=eyJwYWdlIjoiaXRlbSIsInR5cGUiOiJoeWJyaWRfdXJsIiwic2l6ZSI6IjI0MHgyNDAiLCJuYW0iOjEsIm5hbXAiOiJyaWdodCIsImNvbSI6MSwiY29tcCI6ImRvd24iLCJwcmljZSI6MSwiYm9yIjoxLCJjb2wiOjEsImJidG4iOjEsInByb2QiOjAsImFtcCI6ZmFsc2V9)
・[みんなのPython 第4版 [ 柴田 淳 ]](https://hb.afl.rakuten.co.jp/ichiba/21886af9.28d0f9fd.21886afa.7d354fc2/?pc=https%3A%2F%2Fitem.rakuten.co.jp%2Fbook%2F14583659%2F&link_type=hybrid_url&ut=eyJwYWdlIjoiaXRlbSIsInR5cGUiOiJoeWJyaWRfdXJsIiwic2l6ZSI6IjI0MHgyNDAiLCJuYW0iOjEsIm5hbXAiOiJyaWdodCIsImNvbSI6MSwiY29tcCI6ImRvd24iLCJwcmljZSI6MSwiYm9yIjoxLCJjb2wiOjEsImJidG4iOjEsInByb2QiOjAsImFtcCI6ZmFsc2V9)