犬猫の種類の識別 

## 目次
[はじめに](#はじめに)
[本記事の概要](#本記事の概要)
[作成したプログラム](#作成したプログラム)
[検証結果](#検証結果)
[おわりに](#おわりに)
[補足](#補足)


## はじめに

あまり時間がない社会人です．
本来はもっと時間をかけてアプリ制作してみたかったのですが，
期限がなくて1日で作らざるを得ませんでした😢
それでも修了要件は満たしたいので頑張ったつもりです

まあ，今後はこの経験を生かして自分でじっくりコツコツ作れればいいかなと思います...



## 本記事の概要

特に「このアプリを作りたい！」と思って始めたわけではないので
「何か良い課題ないかな～」と思いながらデータセットを探していたところ，
可愛いペットの画像が目につきました．


「動物好きだし，いい感じに識別プログラム書いたれ！」と思い付きで作り始める今日この頃
 ”全部の動物を種類ごとに分類♪”みたいな大それたことはできません．犬猫が限界

本来はWebアプリで作りたかったのですが，先述のとおり時間がない(泣)
特に指定は無い(?)ので，最低限オフラインで動くアプリが作れればいいかと．

オフラインでもアプリはアプリ．やるからにはちゃんと作りたいと思います！

開発環境はGoogle Colaboratory
執筆時のバージョンは
Python 3.10.12
Tensorflow 2.13.0


本記事で学習したデータセット
The Oxford-IIIT Pet Dataset
https://www.robots.ox.ac.uk/~vgg/data/pets/



## 作成したプログラム

まず，データはGoogle Drive上で扱うので，Google Driveが使えるようにします．

~~~python:pet_classification.py
from google.colab import drive
drive.mount('/content/drive')
~~~

データをGoogle Driveに入れた後，使いやすいようにファイルの構成を変えます．

~~~python:pet_classification.py
import os

data_path = "/content/drive/MyDrive/images/"
file_list = os.listdir(data_path)

files = []
for i in  range(len(file_list)):
  files.extend([data_path + file_list[i]])

pets_list = ["Abyssinian",
             "american_bulldog",
             "american_pit_bull_terrier",
             "basset_hound",
             "beagle",
             "Bengal",
             "Birman",
             "Bombay",
             "boxer",
             "British_Shorthair",
             "chihuahua",
             "Egyptian_Mau",
             "english_cocker_spaniel",
             "english_setter",
             "german_shorthaired",
             "great_pyrenees",
             "havanese",
             "japanese_chin",
             "keeshond",
             "leonberger",
             "Maine_Coon",
             "miniature_pinscher",
             "newfoundland",
             "Persian",
             "pomeranian",
             "pug",
             "Ragdoll",
             "Russian_Blue",
             "saint_bernard",
             "samoyed",
             "scottish_terrier",
             "shiba_inu",
             "Siamese",
             "Sphynx",
             "staffordshire_bull_terrier",
             "wheaten_terrier",
             "yorkshire_terrier"]
             
for j in range(len(pets_list)):
  os.makedirs(data_path + pets_list[j], exist_ok=True)
  for k in range(len(files)):
    if pets_list[j] in files[k] and "jpg" in files[k]:
      os.replace(files[k],data_path + pets_list[j] + "/" + file_list[k])
~~~
実際の学習コードです．
今回はモデルのネットワーク構造として[ResNet50](#補足)を採用しました．

~~~python:pet_classification.py
import os
import glob
import keras.models
from keras.preprocessing.image import ImageDataGenerator
from keras.applications.resnet50 import ResNet50
from keras.models import Sequential, Model
from keras.layers import Input, Flatten, Dense
from keras import optimizers

if __name__ == "__main__":

    # 学習データのパスと学習モデルの保存先を指定
    train_path = "/content/drive/MyDrive/images/"
    model_path = "/content/drive/MyDrive/"

    # モデルのクラスを指定
    classes = pets_list
    classes_number = len(classes)
    train_samples = glob.glob(train_path + "/*/*.jpg")

    # ResNetに入力する画像のサイズの指定
    height = 224
    width = 224

    # データをTensorFlow用に前処理
    train_datagen = ImageDataGenerator(
        rescale = 1./255,
        shear_range = 0.2,
        zoom_range = 0.2,
        horizontal_flip = True)

    train_generator = train_datagen.flow_from_directory(
        train_path,
        target_size = (height, width),
        color_mode = "rgb",
        classes = classes,
        class_mode = "categorical",
        batch_size = 3
        )

    # 入力するデータの情報
    input_tensor = Input(shape = (height, width, 3))
    ResNet50 = ResNet50(include_top = False, weights = None, input_tensor = input_tensor)

    # モデルの構造を定義
    top_model = Sequential()
    top_model.add(Flatten(input_shape = ResNet50.output_shape[1:]))
    top_model.add(Dense(classes_number, activation = "softmax"))
    model = Model(inputs = ResNet50.input, outputs = top_model(ResNet50.output))

    #ハイパーパラメーターの定義
    model.compile(loss = "categorical_crossentropy",
                  optimizer = keras.optimizers.Adam(learning_rate = 1e-3),
                  metrics = ["accuracy"])

    print("============== MODEL TRAINING ==============")

    #学習フェーズ
    epoch = 10
    history = model.fit(
        train_generator,
        epochs = epoch
        )

    print("============== END TRAINING ==============")

    model.save(model_path)
~~~

エポックごとの学習のLoss(損失)とAccuracy(精度)はこんな感じ
![無題1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3563995/6358a593-1d92-b2a3-a833-bebe4d1b5e50.png)

損失は大体2回目で頭打ちですね．
精度は9回目で一回下がってますが，10回目でまた上がっているので，もう少し回数を増やしても良さそうです:confused:...結局は時間やマシンスペックとの兼ね合いだと思いますが

## 検証結果

上記のプログラムで学習したあと，さっそく一枚の画像を入れてテストしてみました！
<img width="200" alt = "begle.jpg" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3563995/6cc10da2-1abe-cc59-4d7b-040757186416.jpeg">

テスト用のプログラムは以下の通り．"test"というフォルダの中に画像を入れてます．
~~~python:pet_classification_test.py
from keras.models import load_model
from keras.models import Model
from keras.preprocessing.image import load_img, img_to_array
import numpy as np
import glob
import matplotlib.pyplot as plt


# テストフォルダとモデルファイルの場所
test_dir = "/content/drive/MyDrive/test/"
model_dir = "/content/drive/MyDrive/"

test_samples = glob.glob(test_dir + "*.jpg")

# 入力する画像の大きさ
height = 224
width = 224

# モデルの読み込み
model = load_model(model_dir)

for i in enumerate(test_samples):
    x = load_img(i[1], color_mode = "rgb", target_size = (height, width))
    x = img_to_array(x)/255
    x = x[None,...]
    predict = model.predict(x, batch_size = 1, verbose = 1)
    print(pets_list[np.argmax(predict)])
~~~

出力結果は以下の通りです:grin:
~~~
boxer
~~~

(⑉˙ᗜ˙⑉)．．．???

ボクサー...
<img height="300" alt = "ダウンロード.jfif" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3563995/1d2cc546-8914-4904-db34-f1ddb1d17ec5.jpeg">
まあ，似てなくはない...のかな？

動物の区別は難しいというか，このような類の画像は背景部分に影響されることが大きいと聞きます．同じ画像でも区別ができないものが何枚かありました．
なので，あらかじめパーツを特徴づけするのがセオリー．特に，最近だとVision Transformer が使うのが主流なので，このような単純な手法であまりうまくいくはずないですよね...

特に，撮影条件は揃えないと厳しそうです:frowning2:


## おわりに

結果は芳しくなかったですが，汎用性の高いプログラムになったと思います．
これを機に他のプログラムにも挑戦したいと思います．

参考サイト
Keras | ResNet50つかって自前画像をtrain・testする
https://qiita.com/kiii142/items/eadfaf5cf305981353e8

## 補足
### ResNet50とは
ResNetとはResidual Networkの略で，学習時に残差接続を用いています．
NNでパラメーターの更新にはだいたい勾配降下法が使われるのですが，従来のCNNでは層を深くすればするほど勾配が0になってしまうことが多く，0になればパラメーターが更新できなくなるため精度が下がります:frowning2:

基本的に層が深くなればなるほど性能が上がりやすいので(過学習は除く...)，勾配が0になってしまう問題(勾配消失問題)を解決し，より層を深くするために考えられたのがResNetらしいです:open_mouth:

基になった論文がこちら↓

https://arxiv.org/abs/1512.03385

ResNet〇の〇の部分は層の数で様々な構造が提案されています．
基の論文にも載っていますが，ResNet50のネットワークの基本構造は以下の通りです．
今回ResNet50を採用した理由は特にありません．他の構造もいずれチャレンジしてみたいと思います！

![無題.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3563995/af3c4c49-90dc-8385-3066-7ad82fbcb6bd.jpeg)