Python で犬種の識別をしてみた！

 # はじめに
　こんにちは。この記事を見ていただき、ありがとうございます。
今回は、実家にいる保護犬のマロンがどの犬種に最も近いのか気になったので、Pythonを用いて判別してみたいと思います。


# 目次
- 準備
- モデルについて
- 反省と考察
- まとめ







 # 準備
 - Google Colaboratory
 - 画像の準備
　
   犬のデータセット
   　http://vision.stanford.edu/aditya86/ImageNetDogs/

    こちらを使用しました。犬種については120種、各100枚のデータとなっています。
    ※一部抜粋
    
    ![スクリーンショット 2024-06-17 182828.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817925/9054222a-e717-599c-6168-cc435c91f2e6.png)


 
   



# モデルについて

　まず、GoogleDrive上に画像を保管する場合はドライブをマウントします。
以下のコードを実行することで、マウントできます。

```python
 from google.colab import drive

# Google Driveをマウント
drive.mount('/content/drive')
```

### 画像の収集と分割
　下記の**data_dir**については、マウントした犬のデータセット**DogsDataset**を入れます。

```python
import tensorflow as tf
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.layers import Dense, Dropout, Flatten, Input
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras import optimizers
from keras.layers import Dropout
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.datasets import cifar10
import matplotlib.pyplot as plt
import cv2
import numpy as np
import h5py
import os
from google.colab import files

#犬のデータセットを用意
data_dir = "/content/drive/MyDrive/DogsDataset"
```
　画像の種類と数が多いので、**Tensor Flowサイト**（https://www.tensorflow.org/tutorials/load_data/images?hl=ja）
にある　**tf.keras.utils.image_dataset_from_directory** ユーティリティを使用してデータ
の読み込みをしました。

```python
#訓練データと検証データに分割する
train_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,#訓練データを分割
  subset="training",
  seed=123,#シード値
  image_size=(200,200),#画像サイズ
  batch_size= 32 )#バッチサイズ

val_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,
  subset="validation",
  seed=123,
  image_size=(200,200),
  batch_size= 32 )
```
　上記を実行したところこのようになりました。　

```
Found 12000 files belonging to 120 classes.　#120種、12000枚の画像
Using 9600 files for training.　#9600枚を訓練データに使用
Found 12000 files belonging to 120 classes.
Using 2400 files for validation.
```
　
**tf.keras.utils.image_dataset_from_directory** は直接ファイル名からラベル名を
  取得できるので、下記のコードで確認します。

 
```python
class_names = train_ds.class_names
print(class_names)
```
```
[['001-Chihuahua', '002-Japanese_spaniel', '003-Maltese_dog', '004-Pekinese', '005-Shih-Tzu', '006-Blenheim_spaniel', '007-papillon', '008-toy_terrier', '009-Rhodesian_ridgeback', '010-Afghan_hound', '011-basset', '012-beagle', '013-bloodhound', '014-bluetick', '015-black-and-tan_coonhound', '016-Walker_hound', '017-English_foxhound', '018-redbone', '019-borzoi', '020-Irish_wolfhound', '021-Italian_greyhound', '022-whippet', '023-Ibizan_hound', '024-Norwegian_elkhound', '025-otterhound', '026-Saluki', '027-Scottish_deerhound', '028-Weimaraner', '029-Staffordshire_bullterrier', '030-American_Staffordshire_terrier', '031-Bedlington_terrier', '032-Border_terrier', '033-Kerry_blue_terrier', '034-Irish_terrier', '035-Norfolk_terrier', '036-Norwich_terrier', '037-Yorkshire_terrier', '038-haired_fox_terrier', '039-Lakeland_terrier', '040-Sealyham_terrier', '041-Airedale', '042-cairn', '043-Australian_terrier', '044-Dandie_Dinmont', '045-Boston_bull', '046-miniature_schnauzer', '047-giant_schnauzer', '048-standard_schnauzer', '049-Scotch_terrier', '050-Tibetan_terrier', '051-silky_terrier', '052-soft-coated_wheaten_terrier', '053-West_Highland_white_terrier', '054-Lhasa', '055-flat-coated_retriever', '056-curly-coated_retriever', '057-golden_retriever', '058-Labrador_retriever', '059-Chesapeake_Bay_retriever', '060-German_short-haired_pointer', '061-vizsla', '062-English_setter', '063-Irish_setter', '064-Gordon_setter', '065-Brittany_spaniel', '066-clumber', '067-English_springer', '068-Welsh_springer_spaniel', '069-cocker_spaniel', '070-Sussex_spaniel', '071-Irish_water_spaniel', '072-kuvasz', '073-schipperke', '074-groenendael', '075-malinois', '076-briard', '077-kelpie', '078-komondor', '079-Old_English_sheepdog', '080-Shetland_sheepdog', '081-collie', '082-Border_collie', '083-Bouvier_des_Flandres', '084-Rottweiler', '085-German_shepherd', '086-Doberman', '087-miniature_pinscher', '088-Greater_Swiss_Mountain_dog', '089-Bernese_mountain_dog', '090-Appenzeller', '091-EntleBucher', '092-boxer', '093-bull_mastiff', '094-Tibetan_mastiff', '095-French_bulldog', '096-Great_Dane', '097-Saint_Bernard', '098-Eskimo_dog', '099-malamute', '100-Siberian_husky', '101-affenpinscher', '102-basenji', '103-pug', '104-Leonberg', '105-Newfoundland', '106-Great_Pyrenees', '107-Samoyed', '108-Pomeranian', '109-chow', '110-keeshond', '111-Brabancon_griffon', '112-Pembroke', '113-Cardigan', '114-toy_poodle', '115-miniature_poodle', '116-standard_poodle', '117-Mexican_hairless', '118-dingo', '119-dhole', '120-African_hunting_dog']
```
 　

### 画像データが取得されているか確認
　下記のコードでは、各犬の画像に対して正しいラベル名になっているか確認しています。


```python
plt.figure(figsize=(10, 10))
for images, labels in train_ds.take(1):
  for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(images[i].numpy().astype("uint8"))
    plt.title(class_names[labels[i]])
    plt.axis("off")
```
![スクリーンショット 2024-06-17 184737.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817925/0c75af25-f18e-f7bc-6fec-20ead9a6bfc2.png)


### モデルの学習について
　使用する画像の数が多いので、VGG16は使用せずモデルを制作してみました。
 2つの畳み込み層、活性関数はrelu、epochsは10から始めました。

```python
num_classes = 120

model = tf.keras.Sequential([
  tf.keras.layers.Rescaling(1./255),
  tf.keras.layers.Conv2D(32, 3, activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  Dropout(0.25),
  tf.keras.layers.Conv2D(32, 3, activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  Dropout(0.25),
  #tf.keras.layers.Conv2D(32, 3, activation='relu'),
  #tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(num_classes, activation='softmax')
])


model.compile(
  optimizer='adam',
  loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
  metrics=['accuracy'])

model.fit(
  train_ds,
  #validation_data=val_ds,
  epochs=10
)
```
エポック数10
```
Epoch 10/10
300/300 [==============================] - 16s 52ms/step - loss: 3.2831 - accuracy: 0.1917
```
エポック数20
```
Epoch 20/20
300/300 [==============================] - 16s 52ms/step - loss: 0.3577 - accuracy: 0.8945
```

　エポック数を10から20まで変えてみたところ、正解率が89.45％まですることができたので、モデルの学習を終え評価を行います。

 ### モデルの評価

 ```python
 scores = model.evaluate(val_ds, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])
```
```
75/75 [==============================] - 165s 2s/step - loss: 14.8252 - accuracy: 0.0483
Test loss: 14.8251953125
Test accuracy: 0.04833333194255829
```
　結果としては正解率4.8％というとても低い数値となってしまいました。つまりこれは**汎化性能が低い**ということであり、訓練データに対しては精度が良いが、未知のデータ（今回でいうマロンなど）には、限りなく精度が低いのでさらなる検証が必要になりました。


　しかし筆者はここで、このまま汎化性能を上げるのは難しいのではないかと感じました。理由としては、

 - **多くの犬種がいるが特徴の近い犬が多いこと（色や形など）**
 - **同じ犬種でも似ていない犬がいること**

などの問題もあり、筆者の技術では機械学習で犬種を判別するには、ラベル数が多いとによる汎化性能の低下を解決することは難しそうなので、このやり方を断念しました。

 
### 試行錯誤

　今回当初犬種120種、各100枚の画像でやっており上記のような考察が上げられたので、**犬種を5種、各100枚**でやってみました。犬種の選定はできるだけ似ていない犬を選び、1種類だけマロンに似ている犬種を選びました。
 
※本来の目的では、多くの犬種から似ている犬種を判別するつもりでしたが、筆者が似ている犬を選定しているのは許してください（笑）
  
ということでやってみました。


```
Found 500 files belonging to 5 classes.  #5種、500枚の画像
Using 400 files for training.　#400枚を訓練データに使用
Found 500 files belonging to 5 classes.
Using 100 files for validation.
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817925/333f74d0-4601-12f8-0bff-8e679c06ffcc.png)

　犬種はできるだけ特徴が違う犬を選びました。

 
学習モデル　エポック数20
 ```
Epoch 19/20
13/13 [==============================] - 1s 53ms/step - loss: 0.0065 - accuracy: 1.0000
Epoch 20/20
13/13 [==============================] - 1s 53ms/step - loss: 0.0054 - accuracy: 1.0000
```

　学習モデルはエポック数20で正解率100％になりました。

モデルの評価
```
4/4 [==============================] - 0s 63ms/step - loss: 1.4546 - accuracy: 0.6200
Test loss: 1.4546433687210083
Test accuracy: 0.6200000047683716
```
　検証データでのモデル評価は正解率62％となり少し低いですが先ほどの正解率4.8％のモデルに比べて10倍以上よくすることができました。先ほどのモデルより良くなったので、このモデルでマロンの写真を判別してもらいます。

### マロンの犬種判定
　マロンの犬種を判別する為に、Aidemyで作成したアプリを少し変更して使用しました。今回はマロンの写真を2枚用意しました。
 マロンの写真はこちら
 ![IMG_4776_(2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817925/4887fa9d-e269-a789-c1ad-6c44224d8a87.jpeg)

![image_123650291 (1).JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817925/10392bc2-c843-9246-c28d-f4ba5605408e.jpeg)

　はいかわいいですね。こちらを1枚ずつアプリに入れて判別します。結果は...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817925/f331c4ad-915c-fb77-8d1f-12a481a1e1bb.png)
![n02106662_11620 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817925/11103178-104e-d9f8-f611-87521b52f9b5.jpeg)


シェパードとなりました。確かにマロンとの類似性がありますし、今回100種から5種に筆者自身で選んだ中で一番似ている犬がシェパードだと感じていたので、うまく判定されて良かったです。しかし、今回モデルで選定した5種類の犬については自分で選んでいますし、汎化性能が上がりきらなかったことはまだ改善の余地があると感じています。

 
# 反省と考察
　無事マロンの犬種を判別するアプリまで製作することができました。しかし課題も多く、

 - 犬種が5種類以上だと正解率が上がらないこと
 - 5種類でも汎化性能に改善の余地があること

などまだまだ改善することが多くありました。今回は犬のデータセットを使用しましたが、正解率をより良くし、100種類の犬種を判別するには、画像の数や画像の下処理がまだまだ足りていないと感じました。

# まとめ
　まず率直に初めて自分で一からプログラミングをしてアプリまで製作できて楽しかったです。課題に対してトライアンドエラーしながら自分の成果物が形になることにやりがいを感じました。今回の成果物もそうですが学ぶことはまだまだありますので、Aidemyを復習してより実際にあるアプリのようなプログラミングを製作できるよう努力していきます。

 ・「このブログはAidemy Premiumのカリキュラムの一環で、受講修了条件を満たすために公開しています」