2017年に、あだち充検定というものがありました。機械学習でこの検定に挑んだ内容の紹介です。
尚、学習用画像データは公開しませんのでご了承ください。
あだち充検定とは
「サンデーうぇぶり」で2017年に公開されたイベントです。あだち充先生のマンガはキャラクターがそっくりなことで有名ですが、これを見分けるクイズが作られたという訳です。
ちなみに著者本人が、このあだち充検定に挑んだ結果は76点だったという話もあります(笑)。すべてのキャラを学習させるのはあまりに大変なので、今回この画像の判断にトライします。
機械学習といっても最終的には人間の判断力が大事
今回、まずは学習データのために『みゆき』『タッチ』『陽あたり良好』を全巻まとめ買いしました。
あだち充先生の作品が好きな人なら、上の顔がだいたいこの辺りの時代に描かれた顔と見当つきます。
AIやデータサイエンスと言ってみても最終的にはその業界特有の知識(ドメイン知識)はすごく大事です。学習させる作業効率の面でも、無関係な学習が精度に影響する意味でも。
- 学習データの範囲として『みゆき』、『タッチ』、『陽あたり良好』と当たりをつけた
- 手持ちの『MIX』『QあんどA』も学習データに追加
※3つのキャラだけでは、適当に予測しても33%の確率で当たってしまうので - 右下の顔は除外した
見覚えがあります。『タッチ』で上杉達也に変装した上杉和也です
※知っているから除外というよりも、いわば「はずれ値」にあたり学習データが十分に集められないので
学習用データの収集
そしてマンガをスキャンし、ひたすら顔を集めます。
- さしあたって1キャラにつき60枚集めて、さらに水増しする方針
- ファイル名は答えが分かる形式で tatsuya_001.jpgなどとしておく
- 画像は300×300で統一
学習データの分類とラベルデータの作成
testデータとtrainデータが大体3:7になるように振り分けるために、60枚のうち
1~33をtestデータ、34~60をtrainデータにしました。
trainデータを3倍に水増しするので、testとtrainが33枚:81枚。だいたい3:7になります。
※当時はまだクロスバリデーションという手法を知らなかったので、当時の内容のまま紹介します
test, trainのサブフォルダ構成は以下の通りです。
ラベルを示したcsvも作成しています。
├─test
│ test_data.csv
│ masato_001.jpg
│ masato_002.jpg
│ masato_003.jpg
…
│
├─train
│ train_data.csv
│ masato_034.jpg
│ masato_035.jpg
│ masato_036.jpg
│ masato_037.jpg
…
そして教師あり学習のためにキャラクターごとに0,1,2,3,4と番号を割り当ててラベル付けをしました。
コード
import numpy as np
import pandas as pd
from PIL import Image
from sklearn.svm import SVC
from sklearn import metrics
import matplotlib.pyplot as plt
import pickle
#from sklearn.externals import joblib
%matplotlib inline
train_data = pd.read_csv("train/train_data.csv")
test_data = pd.read_csv("test/test_data.csv")
train_len = len(train_data)
X_train = np.empty((train_len * 3, 90000), dtype=np.uint8)
y_train = np.empty(train_len * 3, dtype=np.uint8)
for i in range(len(train_data)):
name = train_data.loc[i, "File name"]
train_org = Image.open(f"adachi/train/{name}").convert("L")
train_img = np.array(train_org) // 128 #2諧調化
train_img_f = train_img.flatten()
X_train[i] = train_img_f
y_train[i] = train_data.loc[i, "chr"]
# 左右反転させたものを訓練データに追加
train_img_lr = np.fliplr(train_img)
train_img_lr_f = train_img_lr.flatten()
X_train[i + train_len] = train_img_lr_f
y_train[i + train_len] = train_data.loc[i, "chr"]
# 左に10pixel移動させたものを訓練データに追加
train_img_sl = np.roll(train_img,-10)
train_img_sl_f = train_img_sl.flatten()
X_train[i + train_len * 2] = train_img_sl_f
y_train[i + train_len * 2] = train_data.loc[i, "chr"]
test_len = len(test_data)
X_test = np.empty((test_len, 90000), dtype=np.uint8)
y_test = np.empty(test_len, dtype=np.uint8)
for i in range(test_len):
name = test_data.loc[i, "File name"]
test_img = Image.open(f"adachi/test/{name}").convert("L")
test_img = np.array(test_img) // 128 #2諧調化
test_img_f = test_img.flatten()
X_test[i] = test_img_f
y_test[i] = test_data.loc[i, "chr"]
classifier = SVC(kernel="linear", probability=True)
classifier.fit(X_train, y_train)
y_pred = classifier.predict(X_test)
# 混同行列で正答数の確認
print(metrics.confusion_matrix(y_test, y_pred))
[[12 0 10 8 3]
[ 0 2 0 0 4]
[ 3 3 22 2 3]
[12 1 7 12 1]
[ 6 1 8 6 12]]
print(metrics.classification_report(y_test, y_pred))
precision recall f1-score support
0 0.36 0.36 0.36 33
1 0.29 0.33 0.31 6
2 0.47 0.67 0.55 33
3 0.43 0.36 0.39 33
4 0.52 0.36 0.43 33
accuracy 0.43 138
macro avg 0.41 0.42 0.41 138
weighted avg 0.44 0.43 0.43 138
ラベル1の枚数が少ないことに気づいた人もいるかもしれません。
データ収集の最後、『MIX』の段階になって、60枚撮ることに挫けてしまいMIXだけ20枚のサンプルでサボってしまいました。
何はともあれ、デタラメなら確率的に25%のところaccuracy 40くらい。無意味な機械学習ではなかったと言えるのではないでしょうか。もっと良い方法を求めて他の方法にもトライしていきます。
最後まで読んでいただきありがとうございます。
コメント