アニメキャラ画像入力してキャラ名出力するAIの作り方

# Chat GPTへの質問
```
Pythonにてアニメキャラ画像を入力し、キャラクター名を出力する機械学習を行いたいです
コーディング例を教えてください
```
（おおざっぱ）

# なぜそんな質問をしたか
https://www.amazon.co.jp/Python-1%E5%B9%B4%E7%94%9F-%E4%BD%93%E9%A8%93%E3%81%97%E3%81%A6%E3%82%8F%E3%81%8B%E3%82%8B%EF%BC%81%E4%BC%9A%E8%A9%B1%E3%81%A7%E3%81%BE%E3%81%AA%E3%81%B9%E3%82%8B%EF%BC%81%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E3%81%97%E3%81%8F%E3%81%BF-%E6%A3%AE-%E5%B7%A7%E5%B0%9A/dp/4798153192/ref=asc_df_4798153192/?tag=jpgo-22&linkCode=df0&hvadid=295704876452&hvpos=&hvnetw=g&hvrand=111285838387405179&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009283&hvtargid=pla-526274318363&psc=1

上記書籍にて、ざっっっくりとPython×機械学習について学び、「機械学習っぽい」ことを実際にやってみたかったから。

# 回答は

>アニメキャラ画像を入力し、キャラクター名を出力する機械学習を行うためには、以下の手順が必要です。
>
>学習用の画像とキャラクター名を用意する
画像から特徴量を抽出する
特徴量を用いて機械学習モデルを学習する
テスト用の画像を用意し、モデルで予測を行いキャラクター名を出力する
>以下に、具体的なコード例を示します。
>
>1. 学習用の画像とキャラクター名を用意する
>学習用の画像とキャラクター名を用意します。
>例えば、「Naruto」というキャラクターの画像を10枚、
>「Luffy」というキャラクターの画像を10枚用意するとします。
>画像は、任意の場所からダウンロードすることができます。
>
>2. 画像から特徴量を抽出する
>画像から特徴量を抽出するためには、以下の手順が必要です。


```Python:Python
import numpy as np
from PIL import Image
from sklearn.feature_extraction.image import extract_patches_2d

# 学習用データの読み込み
naruto_images = []
for i in range(10):
    image = Image.open(f'naruto_{i}.jpg').convert('L')
    naruto_images.append(np.array(image))

luffy_images = []
for i in range(10):
    image = Image.open(f'luffy_{i}.jpg').convert('L')
    luffy_images.append(np.array(image))

# データセットの作成
X = []
y = []
for i, images in enumerate([naruto_images, luffy_images]):
    for image in images:
        patches = extract_patches_2d(image, (32, 32), max_patches=100)
        for patch in patches:
            X.append(patch.flatten())
            y.append(i)
```
>3.特徴量を用いて機械学習モデルを学習する
特徴量を用いて機械学習モデルを学習するためには、以下の手順が必要です。

```Python:Python
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

# 学習データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# SVMモデルの作成と学習
clf = SVC(kernel='linear')
clf.fit(X_train, y_train)

# テストデータでの正解率を計算
y_pred = clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy}")
```

>テスト用の画像を用意し、モデルで予測を行いキャラクター名を出力する
テスト用の画像を用意し、モデルで予測を行いキャラクター名を出力するためには、以下の手順が必要です。

```Python:Python
# テスト用画像の読み込み
test_image = Image.open('test.jpg').convert('L')
test_patches = extract_patches_2d(np.array(test_image), (32, 32), max_patches=100)

# 予測
predictions = clf.predict(test_patches.reshape(-1, 1024))

# 出力
print(predictions)
```

>以上の手順で、アニメキャラ画像を入力し、キャラクター名を出力する機械学習が行えます。

# とりあえず1スクリプトにまとめてみる

```Python:detect_character
import numpy as np
from PIL import Image
from sklearn.feature_extraction.image import extract_patches_2d

# 学習用データの読み込み
naruto_images = []
for i in range(10):
    image = Image.open(f'naruto_faces/naruto_{i}.jpg').convert('L')
    naruto_images.append(np.array(image))

luffy_images = []
for i in range(10):
    image = Image.open(f'luffy_faces/luffy_{i}.jpg').convert('L')
    luffy_images.append(np.array(image))

# データセットの作成
X = []
y = []
for i, images in enumerate([naruto_images, luffy_images]):
    for image in images:
        patches = extract_patches_2d(image, (32, 32), max_patches=100)
        for patch in patches:
            X.append(patch.flatten())
            y.append(i)

from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

# 学習データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# SVMモデルの作成と学習
clf = SVC(kernel='linear')
print("学習中...")
clf.fit(X_train, y_train)

# テストデータでの正解率を計算
y_pred = clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy}")

# テスト用画像の読み込み
test_image = Image.open('test.jpg').convert('L')
test_patches = extract_patches_2d(np.array(test_image), (32, 32), max_patches=100)

# 予測
predictions = clf.predict(test_patches.reshape(-1, 1024))

# 結果の判定
luffy_count = 0
naruto_count = 0
for prediction in predictions:
    if prediction == 0:
        naruto_count += 1
    else:
        luffy_count += 1

if naruto_count > luffy_count:
    print('結果：ナルト')
else:
    print('結果：ルフィー')
```

# 実行結果
ipynbファイルと同階層に「naruto_faces」「luffy_faces」というフォルダを作成。
それぞれに、naruto_1.jpg...、luffy_1.jpg...を10ファイル格納。
※これもChat GPTに聞きつつ別プログラム書きました（別記事）
https://qiita.com/horita_toshiki_r0/items/14229168fc0789e2da54

```:実行結果
学習中...
Accuracy: 0.5175
結果：ナルト
```
Accuracy：0.5、ほぼあてにならない？
4回試して3勝1敗
学習量10ずつだとまだ外れることも多そう

ちなみに、実行結果出るまでは5分程度かかりました。
clf.fit(X_train, y_train)（学習）のところで結構かかるらしい。

# プログラムをざっくりとひも解く

### ☆第一ステップ：学習用の画像を配列に
```Python:Python
naruto_images = []
for i in range(10):
    image = Image.open(f'naruto_faces/naruto_{i}.jpg').convert('L')
    naruto_images.append(np.array(image))
```
#### ざっくり：
１．画像を読み込む

２．グレースケールに変換

３．NumPy配列に変換

#### 極限までざっくり
⇒画像ファイルを数字に変換して配列に格納する

### ☆第二ステップ：データセット作成
```Python:Python
X = []
y = []
for i, images in enumerate([naruto_images, luffy_images]):
    for image in images:
        patches = extract_patches_2d(image, (32, 32), max_patches=100)
        for patch in patches:
            X.append(patch.flatten())
            y.append(i)
```

#### ざっくり：
０．配列X,yを準備
⇒Xには「パッチ」のデータが格納。
　yには各パッチのラベル、つまりナルトは「0」、ルフィーは「1」

１．第一ステップで配列化した各画像について、「パッチ」として100個に分割
※1パッチのサイズは32px*32px

２．それぞれの「パッチ」化した配列について一次元配列に変換

#### もっとざっくり：
「ナルト」と「ルフィー」のそれぞれ10枚の学習素材をそれぞれさらに100に細分化して、学習用データにする。

#### 疑問：
●なんでわざわざパッチ化するの？画像をそのまま学習すればいいじゃない
⇒画像全体を1枚のデータとして扱うと、画像全体の特徴が把握しにくくなるためです。パッチに分割することで、小さな領域に特徴が集中し、学習が容易になります。

●どうして一次元配列化したの？
⇒SVMなどの機械学習アルゴリズムは、入力として1次元配列を受け取るためです

### ☆第三ステップ：パッチを「学習用」「テスト用」に分ける
```Python:Python
# 学習データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
```

#### ざっくり：
第二ステップで作ったパッチ全部を学習用に使うわけではない
↓
パッチのうち80％だけを学習用に使い、残り20％で学習による予測精度の確認に使う


### ☆第四ステップ：学習！
```Python:Python
clf = SVC(kernel='linear')
print("学習中...")
clf.fit(X_train, y_train)
```

#### ざっくり：
SVM（Support Vector Machine）モデルの学習をします
時間がかかります

X_trainが入力データ、y_trainが教師データです。
つまり、「こんな感じ」のやつは、「ルフィー」だよ！を詰め込んでいくイメージでしょうか

### ☆第五ステップ：精度確認
```Python:Python
y_pred = clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy}")
```

#### ざっくり
１．20％分取っておいたパッチについて、学習成果をもとに、予測

２．y_test（教師データ、つまり正答）と↑の予測を比較

### ☆第六ステップ：「本番」の予測

```Python:Python
test_image = Image.open('test.jpg').convert('L')
test_patches = extract_patches_2d(np.array(test_image), (32, 32), max_patches=100)

# 予測
predictions = clf.predict(test_patches.reshape(-1, 1024))
```

#### ざっくり
１．「ナルト」か「ルフィー」か判定したい画像を入力⇒グレースケールに

２．↑をパッチ化（100個に分割）

３．予測
⇒predictionsには、100個に分割されたパッチについて、「ルフィー！」「ルフィー！」「ナルト！」「ルフィー！」「ルフィー！」「ナルト！」と判定結果が格納される


### ☆最終ステップ：結果出力
```Python:Python
luffy_count = 0
naruto_count = 0
for prediction in predictions:
    if prediction == 0:
        naruto_count += 1
    else:
        luffy_count += 1

if naruto_count > luffy_count:
    print('結果：ナルト')
else:
    print('結果：ルフィー')
```

#### ざっくり：
配列predictionsの要素のうち、「ルフィー」が多ければ「ルフィー」、「ナルト」が多ければ「ナルト」が答えと出力

# 最後に
今後機械学習を勉強するうえで頻出しそうなメソッド

### ●convert('L')
・・・ライブラリpillowのメソッドで、引数が'L'の時はグレースケールに変換してくれる
※引数を'1'にすると、1ビットの白黒画像にするらしい

### ●extract_patches_2d(image, (32, 32), max_patches=100)
・・・2次元の配列（画像）を小さなブロックに分割

引数は以下の通りです。

●image: パッチを抽出したい画像（2次元のNumPy配列）。
●patch_size: 抽出するパッチのサイズを指定するタプル。(n, m)の形式で指定します。
●max_patches: 抽出するパッチの最大数。デフォルトはNoneで、全パッチを抽出します。

### ●train_test_split(X, y, test_size=0.2)
・・・、機械学習のデータセットを訓練用データセットとテスト用データセットに分割

引数は以下の通りです。

●特徴量データ配列
●正解データ配列
●テストデータに割り当てる割合

### clf.fit(X_train, y_train)
・・・学習

入力データと正解データを引数として、両者の関連性をインプット。


### clf.predict(X_test)
・・・予測

一次元配列化したパッチを、学習結果をもとに予測
それぞれのパッチについて、結果を配列に格納する。