前回の記事では、Twitter上の画像から二次元画像を選ぼうとすると二次元とも三次元とも言い難い画像が入ってくる問題があることを見ました。今回は、Active Learningという方法を使って境界領域の画像をうまく扱う方法を適用したいと思います。
Active Learningについて
Active Learningという言葉は教育業界と機械学習業界の両方で使われているので混乱がありますが、ここでは機械学習でのActive learningを指します。通常の機械学習の問題設定では学習データは既に与えられたものとして扱うことが多いですが、Active Leaningではどのデータを学習するかを選ぶことができるという設定のもとで学習を行います。これにより、少ないデータ数で学習が行えるようになることが期待できます。
(ICML 2019のActive Learningチュートリアルのスライドより。)
以下、Active Learning Literature Surveyの内容に沿って話を進めます。
Active Learningの主なシナリオには3つあります。
- Membership Query Synthesis: 学習器が入力空間中の任意のラベルなしインスタンスについてラベル付けを要求できる(新しく生成したインスタンスでも良い)
- Stream-Based Selective Sampling: 1つずつ流れてくるデータそれぞれについてラベルを要求するか破棄するかを決める
- Pool-Based Sampling: ラベル付きデータとラベルなしデータが与えられ、ラベルなしデータの中からどのデータにラベル付けを要求するか決める
どのデータに対してラベルを要求するかを決定する基準として最もよく使われているのがUncertainty Samplingという方式で、主なものが3種類あります。
- least confident: 一番確信度が低いものを選ぶ。数式で書くと、
が最大のものを選ぶ(
)。
- margin sampling: 一番可能性が高いクラスと二番目に可能性が高いクラスの分類確率の差が一番小さいものを選ぶ。数式で書くと、
が最小のものを選ぶ。
- entropy: エントロピーが最大のものを選ぶ。数式で書くと、
が最大のものを選ぶ。
二次元画像判別に対する応用
今回は、ラベル付けを行った画像とラベルがついていない画像が与えられているのでPool-Based Samplingのシナリオになります。とりあえず一番簡単そうなmargin samplingを使って、昨日ラベル付けをサボったデータに対してActive Learningをやってみようと思ったのですが、1個ずつラベル付けするのは面倒なので、分類確率の差が0.3より小さいデータがどんな感じのデータになるのかを見てみることにします。
import os import shutil import numpy as np from PIL import Image import more_itertools from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from tqdm import tqdm import i2v illust2vec = i2v.make_i2v_with_onnx("illust2vec_ver200.onnx") # 学習データの準備 X = [] y = [] batch_size = 4 negative_path = '0' negative_list = os.listdir(negative_path) for batch in tqdm(list(more_itertools.chunked(negative_list, batch_size))): img_list = [Image.open(os.path.join(negative_path, f)) for f in batch] features = illust2vec.extract_feature(img_list) X.extend(features) y.extend([0] * len(batch)) positive_path = '1' positive_list = os.listdir(positive_path) for batch in tqdm(list(more_itertools.chunked(positive_list, batch_size))): img_list = [Image.open(os.path.join(positive_path, f)) for f in batch] features = illust2vec.extract_feature(img_list) X.extend(features) y.extend([1] * len(batch)) # Random Forestの学習 clf = RandomForestClassifier(max_depth=2, random_state=0) clf.fit(X, y) # Unlabeled データをフォルダ分けする pool_path = 'unlabeled' pool_list = os.listdir(pool_path) for filename in pool_list: filename = os.path.join(pool_path, filename) img = Image.open(filename) feature = illust2vec.extract_feature([img]) prob = clf.predict_proba(feature)[0] # 確率値の差が0.3以下ならラベル付けを要求する if np.abs(prob[0]-prob[1]) < 0.3: shutil.move(filename, 'uncertain') elif prob[0] > prob[1]: shutil.move(filename, 'negative') else: shutil.move(filename, 'positive')
Unlabeledデータ2021枚のうち、uncertain
に分類されたものが193枚、negative
に分類されたものが1783枚、positive
に分類されたものが52枚でした。
uncertain
に分類された画像をさらに詳しく見てみました。
- イラスト: 96枚
- ゲームのスクリーンショット: 34枚(アイマスが多かった)
- アニメのスクリーンショット: 20枚(球詠とかくしごとが多かった)
- コスプレ画像: 15枚
- VRChatのスクリーンショット: 10枚
- 線画: 7枚
- VTuberのスクリーンショット: 4枚
- フィギュアの画像: 4枚
- 初音ミク: 2枚
- イラストを撮影したもの: 1枚(以下のツイート)
京都大学吉田構内にKMCの看板を設置しています pic.twitter.com/h4oNh92ntq
— 京大マイコンクラブ (@KMC_JP) 2020年4月15日
前回の記事で述べた紛らわしい種類の画像がきちんとuncertain
に分類されており、Random Forestによる分類確率が紛らわしさをきちんと捉えていることが確認できました。
positive
に分類された画像はアニメのスクリーンショット1枚を除いて全てイラストでした。
negative
に分類された画像のうちイラストは34枚でした。これらの画像は、コントラストが薄めである・人間がたくさん書かれているなどの理由から漫画と間違えられた可能性が高いと思っています。(今回のラベリングではコマ割りがあるまたは白黒の画像は全て二次元イラストではないとしています)
以上の結果から、margin samplingは二次元画像分類の境界ケースをきちんと集めることができそうだという感触を得ました。これを学習データに加えたら精度が上がったという実験結果を出せればよかったのですが、ランダムサンプリングでも95%くらいの精度が出ていたのでActive Learningで有意差を出すことが難しそうでした。Active Learningをするというよりは、棄却オプションをつけて不確かな画像は人手で分類するようにするのが良さそうです。
次回はこの結果を使って二次元画像だけのタイムラインを表示するアプリを作ろうと思います。