Coursera で機械学習に入門成功できたので応用に挑戦してみました。ちょうど季節の変わり目ということで、過去に見て気にいったアニメの特徴を学習して、未知のアニメを、気にいりそうなアニメと気にいらなそうなアニメに分類するツールを作って、ソフトウェアに今季見るべきアニメを推薦してもらいたいと思います。
アニメの特徴量
あるアニメを気にいるかどうかは、話のおもしろさや、絵柄の感じ、キャラクターの魅力などによって決まりそうです。ただ、話のおもしろさや、絵の美しさ、キャラクターの魅力を特徴量として数値化するのはむずかしいので、アニメの映像を制作しているスタッフや会社、声を当てているキャストにフォーカスすることにしました。
Courseraの機械学習のコースでは、特徴として妥当かどうかを判断するのに、人間が同じ特徴を与えられて分類といったタスクが可能かを考えてみよとアドバイスしていました。アニメ作品の制作会社や監督、脚本、出演声優の名前を見ればなんとなく自分に合いそうなアニメかはわかる気がするので、特徴としては良さそうです。
特徴ベクトルの表現
各成分がアニメの制作会社やスタッフ、キャストなどに対応するような、ベクトルを考えます。あるアニメの特徴ベクトルは、そのベクトルによって表現します。ベクトルの各成分の値はその成分に対応するキャストやらが、そのアニメに関係していれば1していなれば0とします。例えば以下のようになります
タイトル属性 |
サンライズ |
鳥海浩輔 |
櫻井孝宏 |
中村悠一 |
沢城みゆき |
遠藤綾 |
金元寿子 |
... |
鉄血のオルフェンズ |
1 |
1 |
1 |
1 |
0 |
0 |
1 |
... |
ワンパンマン |
0 |
1 |
1 |
1 |
1 |
0 |
0 |
... |
おそ松さん |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
... |
ここではベクトルの大きさは7くらいですが、実際にはもっと沢山のアニメ関係者をベクトルに対応づけます。今回は学習に使ったアニメと推薦する対象になる今期のアニメの関係者を、出現頻度のおおいほうから1000くらい選びました。
しょぼいカレンダーの利用
アニメに関係している制作会社やスタッフ、キャストを自分で調査するのは非常に大変なので、いつもお世話になっているしょぼいカレンダーを利用させていただきました。
しょぼいカレンダーにはアニメ作品ごとのページがあって(例: http://cal.syoboi.jp/tid/3424)、スタッフやキャストの情報がそこにまとめらています。公開されているAPI を利用すれば、この情報を取得できるので利用します。
また、しょぼいカレンダーは各アニメにIDを振ってくれているので、そのIDをプログラム中で特定のアニメを指定するための識別子として利用させてもらいました。
過去に見たアニメの評価
自分の好みを学習して使いたいので、過去に見たアニメに対してラベル付けしていきます。ラベルはそのアニメを自分が特に気にいっていれば1を、そうでもなければ0をつけます。
ラベルづけのために、2013年から2015年に放送されたアニメを順番に表示して評価を入力できる、簡単なスクリプトを書きました。このスクリプトを使って、アニメにどんどん評価をあたえていってるのが以下の画像です。yと入力すれば特に気にいってる、それ以外はそうでもないという意味です。この調子で580個くらいのアニメを評価してラベルを付けました。
ニューラルネットワークを使った分類器
ここまでで580個くらいのラベル付きのアニメの特徴ベクトルが得られましたので、このデータに基づいて分類器を構築します。機械学習による分類器の手法をいろいろ試してみましたが、一番うまくいったニューラルネットワークを使った分類器を紹介します。
ニューラルネットワークの分類器を自分で丁寧に作っても良かったのですが、できるだけすでにある物を使うべしと、Courseraの機械学習でもいってましたし、Chainerを使ってみました。
Chainerではニューラルネットワーク自体は以下のように定義します。これは3層のニューラルネットワークになるような計算グラフを構築しています。入力ベクトルの長さを1000としているので、入力層は1000ノードあります。隠れ層は100ノードあって、出力層は2ノードです。活性化関数としてはsigmoid関数を使っています。
出力は長さが2のベクトルになっていて、1番目の成分の値は、入力された特徴ベクトルが0にラベル付けされるべき度数をあらわします。2番目の成分の値は同様に、入力された特徴ベクトルが1にラベル付けされるべき度数をあらわします。
from chainer import Chain
from chainer.links import Linear
from chainer.functions import sigmoid
class AnimeChain(Chain):
def __init__(self):
super(AnimeChain, self).__init__(
l1=Linear(1000, 300),
l2=Linear(300, 2)
)
def __call__(self, x):
h1 = sigmoid(self.l1(x))
o = self.l2(h1)
return o
このニューラルネットワークに特徴ベクトルと正解のラベルを学習させるような関数を定義します。この関数では、ある段階でのニューラルネットワークで計算した結果と本来得たい数値の誤差を計算して、その誤差に基づいてニューラルネットワークを誤差が最小になる方向に更新していきます。この時に誤差を計算グラフの逆方向に伝搬させていったりする必要があるのですが、そこはライブラリがうまく隠蔽してくれていて、たくさんコードを書かずにすんでいます。細かなコードの意味はChainerのチュートリアルなどを参考にしてください。
from chainer.optimizers import Adam
from chainer import Variable
from chainer.functions import softmax_cross_entropy
def train(X, t):
model = AnimeChain()
optimizer = Adam()
optimizer.setup(model)
for e in range(1500):
V_X = Variable(X)
V_t = Variable(np.array(t, dtype='int32'))
V_y = model(V_X)
model.zerograds()
loss = softmax_cross_entropy(V_y, V_t)
loss.backward()
optimizer.update()
if loss.data < 0.001:
break
return model
このtrain関数に対して、用意したアニメごとの特徴ベクトルと、手でがんばって入力したラベルを教師データとして与えることで、その内容を学習した分類器ができあがります。
教師データをあたえるときには1とラベル付けされたデータと0と、ラベル付けされたデータの数が同じになるように、オーバーサンプリングしました。0とラベル付けされたのほうが多かったので、オーバーサンプリングしないとあまり性能がでませんでした(F値で0.6くらいになってだいぶしょぼい)。
分類器の性能
用意したラベル付きのアニメの特徴ベクトル群を教師データとバリデーションデータにわけたあと、教師データをつかって学習した分類器でバリデーションデータのラベルを予測できるかどうかを確認しました。評価値としては、F値を使います。
この分類器の学習曲線は以下のようになります。教師データの数を増やせば増やすほど、バリデーションデータにおけるF値が0.9以上となっており、うまく学習できていることがわかります。だいたいサチってそうですが、教師データを増やすともうすこし良くなるかもしれません。
追記: 2016/04/16 12:54
オーバーサンプリングした結果をそのあとシャッフルしていたので、教師データとバリデーションデータに同じデータがはいってそうでした。細かくしらべてますが、こんなにスコアは良くなさそう..。0.7とかくらいでは...。グラフもまちがってそうです。
実際に予測してみた
この分類器を使って実際に僕が今季見るべきアニメを予測しました。
僕が気にいったアニメを学習した分類器の結果なので、他の誰の役にも立ちませんが、自分としてはふむふむ納得という結果が得られました。けいおんの再放送がはいっていますが、ここ3年くらいのアニメしか教師データとしてあたえていないはずなので、これも機械によって予測されているはずです。けいおん最高。
実際のコード
Python3で書いたコードが素朴にGitHubにおいてあります。気がむいたら実行の仕方など書こうかと思います。 READMEに実行の仕方を追加しました。
まとめ
というわけで、Coursera で機械学習を学んだ成果として今季見るべきアニメ推薦くんを作ってみました。アニメの制作にかかわる、キャストとスタッフの人名にもとづいた特徴に基づいた学習で、なかなかの分類精度が出ることがわかりました。(追記: 2016/04/16 13:35 測定がミスっていて精度でているか謎です.. => 精度についての記事 を書きました。) 。そこそこ便利なツールができたと思います。
ここでは一本道で紹介しましたが、実際のところはロジスティック回帰やサポートベクターマシンなどいろんな手法をためしてみたり、あれこれパラメータ変更してみたり試行錯誤を重ねました。一週間くらいはF値が0.6からあがらなくてあきらめかけたものの、ためしにオーバーサンプリングしてラベルが1と0の教師データの割合を1:1にしたところめっちゃくちゃ精度があがったりして、良い学び体験を得られましたこれまちがってそうでした..つらい。
とにかく何か作ってみることには成功したのでニコニコしつつも、よく理解してないところも沢山あることもわかったので、なんか勉強します。