画像分類モデルを作ってみる

### きっかけ
---
昨今話題の生成系AIのコア技術となるニューラルネットワークやディープラーニングの概念を理解したものの、実際に触ってみないと理解できない部分があるなと思い、今更ながらTensorFlowで画像分類モデルを作り、分析させてみました。

### やってみる
---
今回はさくっと試すだけなのでGoogle Colaboratoryで実装していきます。

まずは学習対象とするデータをDLする。
なお、今回は、[cifar10](https://www.cs.toronto.edu/~kriz/cifar.html)のデータを使います。
```
import tensorflow as tf
from tensorflow.keras.datasets import cifar10

# データセットをロードする（10クラスの画像セット）
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

# データの正規化（0～1の範囲に変換）
x_train, x_test = x_train / 255.0, x_test / 255.0
```
```
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 2s 0us/step
```

DLした中身を確認してみます。
```
import numpy as np

# データセットの中身を確認

#######################################
# x_train（数値化された画像データ）
#######################################
# データタイプ
print("x_train type : ", type(x_train))

# サンプルデータ（数値化された画像データ）
print("x_train sample : ", x_train[0])

# 形
print("x_train shape:", x_train.shape)

#######################################
# y_train（数値化されたラベル）
#######################################
# データタイプ
print("y_train type : ", type(y_train))

# サンプル
print("y_train sample : ", y_train[0])

# 形
print("y_train shape:", y_train.shape)

# ラベルのはんい
print("y_train unique : ", np.unique(y_train))
```
50000枚の画像ファイルが数値配列としてデータ化され、0～9までのラベルが付与されていることがわかります。
0～9のラベルの意味は、[cifar10](https://www.cs.toronto.edu/~kriz/cifar.html)の説明を参照ください。
```
x_train type :  <class 'numpy.ndarray'>
x_train sample :  [[[0.23137255 0.24313725 0.24705882]
  [0.16862745 0.18039216 0.17647059]
  ...
  [0.48235294 0.36078431 0.28235294]]]
x_train shape: (50000, 32, 32, 3)
y_train type :  <class 'numpy.ndarray'>
y_train sample :  [6]
y_train shape: (50000, 1)
y_train unique :  [0 1 2 3 4 5 6 7 8 9]
```
モデルを定義して、コンパイルします。
コンパイルしたモデルに先ほどの画像データを食わせて今回は学習回数を10として、学習させます。
```
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Input

# モデル定義（ハイパーパラメータ調整）
model = Sequential([
    Input(shape=(32, 32, 3)),
    Conv2D(32, (3, 3), activation='relu'),
    MaxPooling2D((2, 2)),
    Conv2D(64, (3, 3), activation='relu'),
    MaxPooling2D((2, 2)),
    Conv2D(64, (3, 3), activation='relu'),
    Flatten(),
    Dense(64, activation='relu'),
    Dense(10, activation='softmax')
])
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
# 学習開始（epochsはトレーニングデータセットに対する学習回数）
model.fit(x_train, y_train, epochs=10, validation_data=(x_test, y_test))
```
回数を重ねるうちに、accuracyは上がり、lossは下がっているため、精度向上していることがわかります。
```
Epoch 1/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 73s 45ms/step - accuracy: 0.3594 - loss: 1.7277 - val_accuracy: 0.5505 - val_loss: 1.2381
Epoch 2/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 69s 44ms/step - accuracy: 0.5748 - loss: 1.2031 - val_accuracy: 0.6137 - val_loss: 1.0965
Epoch 3/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 82s 44ms/step - accuracy: 0.6431 - loss: 1.0152 - val_accuracy: 0.6683 - val_loss: 0.9622
Epoch 4/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 82s 44ms/step - accuracy: 0.6858 - loss: 0.9056 - val_accuracy: 0.6748 - val_loss: 0.9317
Epoch 5/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 69s 44ms/step - accuracy: 0.7156 - loss: 0.8135 - val_accuracy: 0.6782 - val_loss: 0.9206
Epoch 6/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 85s 46ms/step - accuracy: 0.7324 - loss: 0.7630 - val_accuracy: 0.6896 - val_loss: 0.8914
Epoch 7/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 83s 47ms/step - accuracy: 0.7534 - loss: 0.7036 - val_accuracy: 0.6996 - val_loss: 0.8631
Epoch 8/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 77s 44ms/step - accuracy: 0.7710 - loss: 0.6543 - val_accuracy: 0.7051 - val_loss: 0.8716
Epoch 9/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 82s 44ms/step - accuracy: 0.7836 - loss: 0.6162 - val_accuracy: 0.6952 - val_loss: 0.9014
Epoch 10/10
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 81s 44ms/step - accuracy: 0.7961 - loss: 0.5739 - val_accuracy: 0.7039 - val_loss: 0.8824
```
モデルができたので、cifar10内のテストデータを使って、モデルを評価してみます。
```
# テストデータ（事前に用意された10カテゴリのテストファイル）を使ってモデルを評価
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2)
# テストデータに対しての正答率を出力する
print("\nTest accuracy:", test_acc)
```
正答率7割と、まぁまぁの結果になりました。
```
313/313 - 4s - 11ms/step - accuracy: 0.7039 - loss: 0.8824

Test accuracy: 0.7038999795913696
```
今度は自前で用意したファイルを使って分析させてみます。
ファイルはGDrive上に配置したものを使うので、認証を通してドライブをマウントします。
```
from google.colab import drive

# あくまでも自身のドライブへのアクセス権しか持たない
drive.mount('/content/drive')
```
画像を出力してみます。
（確認のために出力してるだけなので分析とは関係ないです）
```
from PIL import Image
import matplotlib.pyplot as plt

# 画像を読み込む
image_path = '/content/drive/My Drive/etc/TensorFlow/airplane_test.jpg'
test_img = Image.open(image_path)

# 画像を表示する
plt.imshow(test_img)
plt.axis('off')
plt.show()
```
ちなみに、画像はGeminiに作ってもらいましたw
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057132/55d7243a-1c4f-46c3-ac27-b85b9ac7549b.jpeg" alt="Image description" width="300" height="200">

実際に分析させてみます。
```
# 画像を32x32にリサイズ
image = test_img.convert('RGB')
image = image.resize((32, 32))

# 画像をnumpy配列に変換し、正規化
image_array = np.array(image) / 255.0
image_array = image_array.reshape((1, 32, 32, 3))

# モデルによる予測
predictions = model.predict(image_array)

# 予測結果の確認
predicted_class = np.argmax(predictions, axis=1)

# CIFAR-10のクラスラベル
class_labels = ['airplane', 'automobile', 'bird', 'cat', 'deer',
                'dog', 'frog', 'horse', 'ship', 'truck']

# 出力結果のrawデータ（各カテゴリの確率を配列にしたもの）
print(predictions)
# 一番可能性の高い配列index
print(predicted_class)
# 配列indexに応じたラベル
print("Predicted class name:", class_labels[predicted_class[0]])
```
出力としては、10分類の各指標が得られており、そのうち、最も高い数値は1つ目（index:0）という結果になりました。
1つ目のラベルは`airplane`なので、正しい分類が行われたことがわかります。
```
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 126ms/step
[[9.7352397e-01 5.4320579e-05 7.3415530e-03 4.5001831e-05 2.6936505e-03
  9.7711506e-07 2.2104199e-05 1.9527099e-07 1.6267302e-02 5.0913233e-05]]
[0]
Predicted class name: airplane
```

### 感想
---
こんなにも簡単にモデルができるとは思ってませんでしたが、データが事前に用意されてたからなんですよね。
本当に有用なモデルを作ろうとすると、学習データも大量に必要だし、そこにラベルも付与しないといけないし、学習も相当な時間がかかる（無料のGPUだったのもあるが、今回の学習だけでも数分かかってる）し、ハイパーパラメータの調整もいるし、、、と大変な作業だなというのが想像できます。

また、今まではAIは未知のものって感覚だったんですが、実際に触ってみることで、生の数値データを確認で、処理イメージがなんとなくですが、湧きました。

### おまけ
---
Geminiに「ネコっぽいイヌのイラスト」を描かせて分析してみる。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057132/c69abe77-7e27-41c8-bba3-69eb5e517e70.jpeg" alt="Image description" width="300" height="200">

結果。
イヌとネコの数値も高いが、馬が一番...w
調整って難しそうですね。
```
[[3.8005831e-04 6.1818724e-07 2.7024343e-03 1.8570235e-02 6.6630021e-03
  6.2341474e-02 1.8024031e-06 9.0927386e-01 4.4260210e-06 6.2017134e-05]]
[7]
Predicted class name: horse
```