みなさんはデレステというゲームをご存知ですか。私はよく知りませんが、今回はこのゲームに登場するキャラクター(アイドル)のタイプを分類するというタスクを扱いたいと思います。なお、この記事で利用したコードの全体は
https://github.com/yotapoon/deresute/tree/master
に掲載しています。また「ゼロから始めるディープラーニング2」のモジュールをいくつか利用しています。以下のURLのch04とcommonというディレクトリをインポートすれば動くはずです。
https://github.com/oreilly-japan/deep-learning-from-scratch-2
ただ具体的な実装を示した文書分類の良い記事がすでにたくさんあるため、そこは上述のコードを参考にしてもらうことにしてここでは詳しく説明しません。今回は解析結果や考察にフォーカスすることとします。
0 概要
今回の記事の概要を述べます。
0.1 デレステとは
アイドルマスターシンデレラガールズ スターライトステージ(以下デレステ)には、アイドルとしてたくさんのキャラクターが登場します。それぞれのキャラクターは各々の性格を反映してキュート・クール・パッションのいずれかのタイプを有しています。
参考までに、何人かのキャラクターとそのセリフを示します。
島村卯月(キュート)
「はじめまして、○○プロデューサーさん! 島村卯月、17歳です。私、精一杯頑張りますから、一緒に夢叶えましょうね♪よろしくお願いしますっ!」
渋谷凛(クール)
「ふーん、アンタが私のプロデューサー?……まあ、悪くないかな…。私は渋谷凛。今日からよろしくね」
本田未央(パッション)
「本田未央15歳。高校一年生ですっ! 元気に明るく、トップアイドル目指して頑張りまーっす! えへへ。今日からよろしくお願いしまーす♪」
確かにタイプが表す傾向が、セリフに現れていることがわかります。
0.2 目的
この記事では、あるアイドルのセリフ(特にアイドルコメント)から、そのアイドルのタイプを正しく判定することを目標とします。またその中で、
①自然言語処理の手法について学び、②デレステへの理解を深める
ことが本記事の目的です。
0.3 結果
まずは今回扱った手法とその結果を簡単に示します。
- 手作業による特徴量抽出:それほどの精度は出なかった(50%以下)
- word2vec+SVM:訓練データ・テストデータともに60%程度の精度が出た。
- LSTM:過学習が生じやすく、50%程度の精度しか出なかった。
とくにword2vec+SVMにおいては、人間による分類と同じような間違い方をしているということが特徴的でした。ちなみに、デレステを知らない方三名に50問を解いてもらったところ、その精度は42%,54%,54%でした。ここからword2ec+SVMによる手法はかなり良く訓練できていることがわかります。
以下ではどのような手法を利用したかをより詳細に説明します。
1 データの収集
さきほどのような、キャラクターのタイプとセリフのデータを集めます。データは以下のサイトを利用させてもらいました。
https://seesaawiki.jp/imascg
1.1 データの詳細
前述したアイドルコメントは、同一人物であっても特訓前後で変わります。また、イベント等で手に入る二つ名付き(?)のアイドルに関しても、アイドルコメントは変化します。わかりづらいと思うので、一例を示します。
双葉杏(キュート)
「い、いやだっ! 私は働かないぞっ! アイドルだろうとなんだろうと…お断りだーっ!! ……え? アイドルになれば印税で一生楽に生きていける? ほ、本当? …は、話を聞かせてもらおうじゃないか」
双葉杏+(キュート)
「最近、プロデューサー頑張りすぎじゃない? 私、なんだか心配だよ。杏のことは気にしなくていいから、今週は仕事をお休みにしよう? うんうん、それがいいと思うよ」
[ぐうたら王国]双葉杏(キュート)
「は~、じゃすとふぃ~っと♪いいよ~、この椅子。最高すぎて、もはや玉座。杏、もっとダメにされちゃう…
極楽じゃ、余は働きとうない。王国のことは、全部○○プロデューサーに任せるね~」
[ぐうたら王国]双葉杏+(キュート)
「我が王国民よ、怠けてるか~?はい、けっこーけっこー。えらいみんなには、ご褒美のキャンディシャワーだ!
へへ、杏もひとつ…ん、あま~♪そんじゃ、我々の団結を示す一曲、ダラっといってみよ~!」
「+」がついているのが特訓後のセリフになります。
今回はこれらのセリフをすべて区別して扱うこととしました。また、名前の情報は分離させ、純粋にセリフだけから判定できるかどうかを試します。このようにして得られたセリフの総数は2857個であり、それなりのデータ数は集まりました。
1.2 スクレイピング
pythonで、上記のサイトから必要なデータを抽出するコードを書きました。しかしスクレイピングのコードを載せるのはアレなので、アイドルコメントとアイドル名をそれぞれ抽出したテキストファイルをgithubに掲載しています。
2 手作業での特徴量選択による方法
まずはデータの確認をしつつ、手作業により特徴量を抽出し、分類を行います。
2.1 データの確認
各タイプに関して出現頻度のトップ5の単語を確認すると、以下のようになりました。
キュート | クール | パッション | |||||
---|---|---|---|---|---|---|---|
単語 | 頻度 | 単語 | 頻度 | 単語 | 頻度 | ||
、 | 2810 | … | 2781 | 、 | 2704 | ||
! | 1506 | 、 | 2688 | ! | 2386 | ||
… | 1466 | 。 | 1785 | の | 1370 | ||
て | 1339 | の | 1416 | て | 1237 | ||
の | 1301 | て | 1392 | に | 1108 |
このデータから、各タイプごとに特徴的な単語の選択が見て取れます。クールタイプは「…」の使用頻度が他と比べて圧倒的に高いです。パッションタイプは「!」がよく使われています。またキュートタイプはそれらのハイブリッドのような形になっています。なお、より良く見てみると「♪」という記号がキュートタイプには特徴的に表れていることもわかりました。これらの結果は何となくイメージできることではありますが、データとして現れると面白いです。
2.2 特徴量選択
先ほどの結果から、最も素朴な実装として「…」「!」「♪」という記号のセリフ中の出現頻度を特徴量として取り入れることを考えます。具体的には、あるセリフが$N$個の単語$w_1,\cdots,w_N$で構成されているとしたとき、特徴量とする単語$\bar{w}$の密度
$$
\rho(\bar{w}) = \dfrac{1}{N} \sum_{i=1}^N \delta_{w_i, \bar{w}}
$$
というものを導入し、これを特徴量とします。これにより、各セリフは$3$次元のベクトルに変換されることとなります。その実装については非常に単純なのでここでは省略します。
2.3 実際の分類
先ほどの特徴量に変換したセリフたちを分類していくことにします。今回はSVMを工夫せず適用することにします。全セリフの80%を訓練データに、残り20%をテストデータに使用しました。
from sklearn.svm import SVC
model = SVC(kernel = "linear")
model.fit(train_x,train_y)
accuracy = model.score(train_x,train_y)
print("train:accuracy {0:.2%}".format(accuracy))
accuracy = model.score(test_x,test_y)
print('test:accuracy {0:.2%}'.format(accuracy))
結果は以下のように、チャンスレート(33%)よりは高い正答率を出していますが、まだまだ不十分です。以降では特徴量の次元を増やすためにword2vecを利用し、実際に精度が向上することをみます。
train:accuracy 45.91%
test:accuracy 44.93%
3 word2vecによる手法
先ほどの手法では特徴量は「…」「!」「♪」の三次元であり、複雑な分類ができないことは容易に想像できます。そこで特徴量の次元を増やすことを考えたいですが、単純に単語数を増やしてもうまくいかないでしょう。ここではword2vecによって単語の分散表現を獲得し、それをもとに分類するという手法を取ることにします。
3.1 分散表現とは
先ほどは非常に単純な手法により、それぞれの単語を$3$次元ベクトルに変換したのでした。しかしこれではあまりに表現力が低いため、さらに高次元のベクトルに変換したいというのは自然な発想です。そこで利用できるのは単語の分散表現です。
単語の分散表現とは、コーパスと呼ばれる長い文章をもとに訓練を行うことで、単語を高次元(たとえば$100$次元など)のベクトルにより表現するというものです。この分散表現を得るために、word2vecによる学習がよく行われます。
3.2 word2vecとは
word2vecと"word to vector"のことであり単語をベクトルに変換する手法です。
話は変わりますが、以下のようなタスクを解くことを考えましょう:
コーパスと呼ばれる長い文章中から$T=s_1+s_2+1$個の連続する単語$w_{t-s_1},\cdots, w_{t},\cdots, w_{t+s_2}$を取り出します。$s_1$と$s_2$は適当に選びます($s_1 = s_2 = 5$など)。このとき単語$w_t$を除いた$w_{t-s_1},\cdots, w_{t-1},w_{t+1},w_{t+s_2}$を利用して$w_t$を予測するタスクを行います。入力は$w_{t-s_1},\cdots,w_{t-1},w_{t+1},\cdots,w_{t+s_2}$のような複数の単語(を単語idに直した数字たち)であり、ターゲットは$w_t$です。入力を行列$W$によって隠れ状態のベクトル($H=100$次元など)に変換し、さらに行列$W'$を掛けることにより、単語を出力させます。
このタスクを行わせることにより、最適化された重み$W$が求まります。この重み行列$W$の第$i$列が、単語idが$i$の単語を$H$次元に変換したベクトルになります。なぜこの方法がうまくいくのかなどについて、より詳しい説明が以下のURLにあります。なお、ここで説明したものはword2vecのうちCBoW (Continuous Bags-of-Words)と呼ばれるものにあたります。
http://tkengo.github.io/blog/2016/05/09/understand-how-to-learn-word2vec/
3.3 word2vec+SVM
話をデレステに戻します。
word2vecを利用して得られた分散表現を用いて、セリフ中に現れる単語を高次元のベクトルに変換することができるようになりました。ここではセリフ全体を分類したいので、セリフを表現するベクトルとしてセリフを構成するベクトルの相加平均を用いることにします。つまりあるセリフが$\vec{w_0},\cdots, \vec{w_{T-1}}$という単語(のベクトル)で構成されているとき、セリフを表すベクトル$\vec{s}$は
$$
\vec{s} = \dfrac{1}{T} \sum_t \vec{w_t}
$$
であるものとします。これは自然な定義に思えますが、もう少し工夫の余地もあるかもしれません。
セリフをベクトルに変換できたので、先ほどと同様にSVMにより学習させます。
from sklearn.svm import SVC
model = SVC(kernel = "linear")
model.fit(train_x,train_y)
accuracy = model.score(train_x,train_y)
print("train:accuracy {0:.2%}".format(accuracy))
accuracy = model.score(test_x,test_y)
print('test:accuracy {0:.2%}'.format(accuracy))
このときの正答率は訓練データ・テストデータともに60%程度となり、かなりの高精度で分類ができることがわかりました。少なくともデレステを知らない人よりはうまく分類できるようになっています。
train:accuracy 60.00%
test:accuracy 59.79%
3.4 word2vec+SVM v.s. LSTM
word2vecによる手法では、セリフの時系列の情報が完全に落ちてしまいます。この情報を活かすことでより精度を改善できるのではないかと思い、LSTM (Long Short-Term Memory)を実装しました。しかし、LSTMでは容易に過学習が生じてしまい、訓練データについては70%程度の精度が出た一方、テストデータについては50%にとどまりました(ただしハイパラ調整なし)。
この原因としては、LSTMには単語idをそのまま渡してしまっているがために、ある単語に過敏に反応しているという可能性が考えられます。例えば、「杏」という単語は双葉杏(キュート)しか使わないと想像されるので、そのような単語に集中して学習すれば訓練データに対しての精度はかなり上昇するでしょう。
一方、word2vecでは単語ベクトルの相加平均をとっているため、そのような過敏な反応は生じないことが期待されます。これがword2vecでは過学習が生じなかった原因と考えられます。
5 デレステへの深い理解に向けて
word2vec+SVMによる結果をもとに、デレステのタイプ分類について考察します。
5.2 タイプ間の相関に対する解析
機械の分類の「クセ」をつかみたいとすれば、どのタイプをどのタイプだと間違えたのかということが気になります。これを定量的に評価するものとして混同行列というものがあります。これは行が「本当のタイプ」、列が「機械が予測したタイプ」を表しており、各成分はその割合を表しています。実際に見てみる方が早いでしょう。
キュート | クール | パッション | |
---|---|---|---|
キュート | 0.510 | 0.284 | 0.206 |
クール | 0.171 | 0.711 | 0.118 |
パッション | 0.267 | 0.157 | 0.576 |
ここから以下のような傾向が読み取れます:
- キュートタイプに対する正答率は51.0%であり、少し苦手としている。クールに誤って割り当てられるセリフが少し多い。
- クールタイプに対しては71.1%の正答率があり、非常によい成績である。特にパッションと割り当ててしまうミスはかなり少ない。
- パッションタイプに関しては57.6%の正答率であり、まずまずの精度。クールに割り当ててしまうことは少ないが、キュートに割り当てることはしばしばある。
- 全体的な傾向として、パッションとクールが対照的であると認識しているように見える。キュートはそれらのハイブリッドのようになっている。
なお、前述した人間によるテストにおいても同じような傾向が見られたことに注意します。つまり、非常に簡単なセットアップであるものの、人間の感性を非常によく模倣した分類器を作ることが出来ました。
5.3 具体的なセリフの解析
先ほどの傾向を具体的なセリフを観察しつつ確認します。
true: cute pred: cool
name: [冬のメロディー]緒方智絵里
serif: ○○さん、冬ってお好きですか? …私、あんまり、好きじゃなかったんです。街が…暗い色になっちゃうし…寒さで、寂しくなるから…。でも、今は、ちょっとだけ好き、かも…ほら、雪…
~~この可愛さでクールは無理でしょ。~~初めこの結果を見たときにはブチギレそうになりましたが、セリフをよく見ると仕方ないようにも思えます。例えば[小さなドキドキ]佐城雪美+を見てみましょう。
true: cool pred: cool
name: [小さなドキドキ]佐城雪美+
serif: ドキドキ……みんな…見てる……。こんな…お洋服……着たこと…ない……。………すごく…恥ずかしい……。けど……少し…ペロみたい……?ふふっ……ちょっとだけ…うれしい……かも……
確かに(デレステについて多少知識があろうとも)このセリフを区別するのは難しいかもしれません。他にも人間と非常に似た間違いをしている様子が確認でき、かなり人間らしい振る舞いをしているところが面白いです。
6 結論
結論としては、word2vec+SVMによる手法では60%程度の精度を達成することができました。これは人間と同程度、あるいは人間を上回る性能です。またLSTMによる手法では改善が出来なかったことから、時系列の情報はそれほど重要ではないのかもしれないと考えられます。特筆すべきこととして、機械の分類の「クセ」に、人間と同じような傾向が見受けられたことがあります。このタスクを通じて、デレステに対する理解が深まりました。