iCrawlerとVGG16で画像分類やって遊んだ話

# はじめに
最近pythonを使ってめっちゃ簡単なAIを作ってみるのにハマってるので、その過程を記事にしてみます。
もっといいコードがあれば教えてください✨

# 実装
## 今回作ったもの
りんご、ぶどう、みかんの３つの分類を行うようにVGG16を再学習させました。
画像収集にはicrawlerを利用しました。また実行環境は以下のとおりです。
```:モデルの訓練
Google Colab
```
```:flaskアプリ
python 3.10
Apple M2チップ MacBook Air
macOS Venture13.1
```

## icrawlerで画像収集
icrawlerで画像を収集します。
```:colab
!pip install icrawler
```

```python:colab
# ライブラリのインポート
from icrawler.builtin import BingImageCrawler
```
pipでライブラリをインストールしその後importまで済ませます。
今回画像を収集するフォルダを作成します。今回はimagesフォルダを作成しその中に`りんご` `みかん` `ぶどう`のフォルダを作成しそこに画像を収集します。

```:colab
!mkdir images
!cd images
```
画像を収集します。
```python:colab
labels =["りんご", "みかん", "ぶどう"]
for label in labels:
  path = "/content/images/" + label
  crawler = BingImageCrawler(storage = {'root_dir' : path})
  crawler.crawl(keyword = i, max_num = 150)
```
labelsというリストに今回収集したいラベルを入れ、その後のfor文中でそれぞれ150枚ずつ画像を収集しようとしています。
画像を収集する過程で、`ERROR:downloader:Response status code 403, file`のようなエラーが出力される場合がありますが、これは上手くファイルを取れなかった時に出るものなので無視してOKです。

実行が終わると、google colabのランタイムの中に画像が収集されてい事がわかります。

## VGG
次にVGGに転移学習を施します。
まずはライブラリのインポートからです。
```python:colab
import os
import cv2
import numpy as np
from keras.utils import np_utils
import glob as glob
```

まずはimagesフォルダの画像を読み込む。
```python:colab
path = "/content/images"
n_classes = len(labels)
print(f"num of class = {n_classes} , classes = {labels}")

X = []
Y = []

for label,class_name in enumerate(classes):
  files = glob.glob(path + "/" +  class_name + "/*.jpg")
  for file in files:
    img = cv2.imread(file)
    img = cv2.resize(img,dsize=(224,224))
    X.append(img)
    Y.append(label)
```

次に精度を上げるために配列Xを正規化します。
```python:colab
X = np.array(X)
X = X.astype('float32')
X /= 255.0
```
次にラベルをone-hotエンコーディングして変換します。

```python:colab
Y = np.array(Y)
Y = np_utils.to_categorical(Y,n_classes）
```

次に訓練データとテストデータの分割を行います.
```python:colab
from sklearn.model_selection import train_test_split
X_train,X_test,Y_train,Y_test = train_test_split(X,Y,test_size=0.1)
```
次にVGG設定を行います。１６層すべてを再学習させていては、時間が足りないので、学習させない層を指定します。

```python:colab
from keras.applications.vgg16 import VGG16
from keras.models import Sequential
from keras.models import model_from_json
from keras.models import Model
from keras.layers import Input, Activation, Dense, Flatten, Dropout
from tensorflow.keras.optimizers import Adam

input_tensor = Input(shape=(224,224,3))
base_model = VGG16(weights='imagenet', input_tensor=input_tensor,include_top=False)


top_model = Sequential()
top_model.add(Flatten(input_shape=base_model.output_shape[1:]))
top_model.add(Dense(n_classes, activation='softmax'))

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

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

model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
```
モデルの各層の状態を確認してみましょう。
```python:colab
model.summary()
```
```
=================================================================
Total params: 14,789,955
Trainable params: 7,154,691
Non-trainable params: 7,635,264
_________________________________________________________________
```
のように表示されることから訓練可能な層の数を確認することができると思います。

いよいよ学習させていきたいと思います。
```python:colab
model.fit(X_train, Y_train, epochs=12, batch_size=5)
```
テストデータに対する精度を確認しましょう
```python:colab
score = model.evaluate(X_test, Y_test, batch_size=5)
```
これで学習は終了です。精度がイマイチだったりする方は再度データや学習パラーメータを見直したりしましょう。

実際に、任意の画像でテストをしてみましょう。
```python:colab
img = cv2.imread('ここには画像のパスを入れる')
img = cv2.resize(img,dsize=(224,224))
img = img.astype('float32')
img /= 255.0
img = img[None, ...]
result = model.predict(img)

np.set_printoptions(precision=3, suppress=True)
pred = result.argmax()
classes[pred]
```

## ローカル環境でのモデルのロード
ここまではGoogle colabでモデルの訓練をしてきましたが、実際にローカル環境(anacondak環境)で保存したモデルを使ってみたいと思います。


```python:colab
# モデルを保存する
import pickle
pickle.dump(classes, open('classes.sav', 'wb'))
model.save('model.h5')
```
モデルを保存したらcolabのランタイム、もしくは、driveと接続している方であればdriveの方からclasses.savとmodel.h5をダウンロードしましょう。

```python:ローカル
from keras.models import load_model
import pickle
#モデルとクラス名の読み込み
model = load_model('model.h5')
classes = pickle.load(open('classes.sav', 'rb'))
```
kerasとpickleを用いてモデルをロードします。ここまでできればコラボ環境で動作させたのと同じようにmodelをローカル実行することができます。

# 最後に
ここまでお読みいただきありがとうございました。
コードは一通り実行して動作確認はしておりますが、コピペミスやタイポなどでうまく動作しない箇所がありましたら、コメントなどで **優しく** ご指摘いただけると嬉しいです！
それから、もっと良い書き方や、機械学習のロジック的にイケてないコードも **優しく** 指摘してもらえると喜びます！

ありがとうございました。