まずは iVoca のデータを使って自分で IRT で計算してみるところからかな。
というわけで、ユーザの学習履歴から単語の難易度を求めるコードを書いてみた。
ソーシャルっぽい!
残念ながら IRT(項目反応理論) について書かれた文献を持っていないのだが、id:niam さんの SocialDict についてのプレゼン資料 のおかげで、「2値のラッシュモデル+逐次勾配降下」なら単純なロジスティック回帰であることがわかるので、簡単に実装できた。
しかも IRT の特徴ベクトルが疎であることを用いると、非常にシンプルなコードで済む。
# data には [ユーザID, 単語ID, 知ってる(1)/知らない(0)] を格納。 # users/words は各IDをキー、重み(ユーザの語彙力/単語の難しさ)を値とするHash 100.times do |k| eta = 1.0 / (k + 10) # 学習率(適当) e = 0 data.sort_by{rand}.each do |user_id, word_id, t| z = users[user_id] - words[word_id] y = 1.0 / (1.0 + Math.exp(-z)) # ロジスティックシグモイド # (累積)誤差関数 e -= if t == 1 then Math.log(y) else Math.log(1 - y) end grad_e_eta = eta * (y - t) # 誤差関数の勾配 users[user_id] -= grad_e_eta words[word_id] += grad_e_eta end puts "#{k}: #{e}" end
手元のデータでは、知ってる/知らないの二値ではなく、ユーザの単語に対する苦手度を float で得られるので、本当は多値ラッシュモデル(多クラスのロジスティック回帰をすればいいの?)を用いたかったところだけど、手抜き。
全ソースは例によって github に。
http://github.com/shuyo/iir/tree/master/irt/
でも、データが生のままでは公開できるものではないので、コミットされているのはスクリプトだけ。残念。
ここに iVoca から抽出した学習履歴データ(約40万件、7万5千問)を投入。
なにぶん件数が多いので、1000周学習となると一晩かかる……そろそろ Ruby で書くのはやめないとダメ?(苦笑
その結果は……って全部出力したら大変。
さて、どのあたりを切り出そう。
前回の記事に書いたとおり、iVoca では各ブック(単語帳)に「みんなが苦手な単語一覧」を表示させているが、実はこれはユーザの苦手度を単語ごとに平均をとって並べているだけ。
そのブックを勉強しているユーザが多ければ、こんな単純な方法でも十分それらしいリストが得られる。
が、ユーザが少ないブックではどうしても怪しいリストになってしまうことも多いので、ある程度データがたまらないと「みんなが苦手な単語一覧」は表示されないようになっている。
勉強しているユーザが少ないブックでも、もう少し信頼度の高い値が計算できると嬉しい。
そこで、勉強している人が 23人と、多くないけど少なすぎないブック「英検2級過去問の単語・表現(2009年1月)」 に含まれている単語について、IRT で計算した単語ごと重み(=単語の難易度)をみてみる。
問題 | 答え | 難易度 |
---|---|---|
幸運な | fortunate | 4.294 |
手数料 | fare | 4.268 |
取り上げる、奪う | take away | 4.255 |
確信して、自信を持って | confident | 4.241 |
熟考する | think over | 4.131 |
捧げる | dedicate | 4.130 |
徹底的に | in depth | 4.128 |
追い払う、顔をそむける | turn away | 4.099 |
(誰かの考えの)裏付けを取る | back up | 4.098 |
推薦 | recommendation | 4.091 |
矛盾する | conflict | 4.083 |
Aを当然のことと考える | take A for granted | 4.082 |
素早く | rapidly | 4.064 |
火が付く | catch fire | 4.053 |
有利な、儲けの多い | profitable | 4.051 |
(中略) | ||
契約 | contract | -1.700 |
ふりをする | pretend | -1.707 |
大多数 | majority | -1.789 |
容量、収容能力 | capacity | -1.905 |
無視する | ignore | -1.954 |
引退する、退職する | retire | -2.035 |
植える | plant | -2.055 |
態度 | attitude | -2.109 |
出席する | attend | -2.444 |
収入 | income | -3.166 |
値が大きいほど難易度が高い。
ちなみに、苦手度の平均による「みんなが苦手な問題」 TOP 5 は以下の通り。
rank | question | answer |
---|---|---|
1 | 結論づける、断定する | conclude |
2 | 焦点を合わせる、集中する | focus |
3 | 明白な、はっきりした | apparent |
4 | 中断する、さえぎる | interrupt |
5 | 幸運な | fortunate |
見比べると、特にどちらかが的外れということもなく。
すると、「苦手度の平均」の方がはるかに計算が楽なので、そちらに軍配が上がってしまう……
しかし、IRT ならブックを横断して単語の難易度の評価ができるのが嬉しい。
たとえば、「中学必修単語」 や 基礎英語 ステップ1」 などの、とても簡単な単語ばかりのブックでは、ほとんどの単語の難易度が 0 未満となる。
多くの単語の難易度が 3 を超える「英検2級過去問の単語・表現(2009年1月)」は、それらより難しいブックであるということがわかる。
「ユーザの知らない単語」を IRT で予測する場合の問題点
自分で IRT(項目反応理論)を実装してみて、これを使って「ユーザの知らない単語」を予測するのはいくつか問題がありそうだなあ、と感じた。
IRT では各項目は互いに独立であるという仮定を必要とする。
が、「各単語を知っているかどうか」は本当は独立ではない。
- コンピュータ関係の単語を知っているからといって、同じ「難易度」を持つ TOEIC 頻出単語を知っているかというと必ずしもそうではない。
- covariance (共分散)という単語を知っていれば、convolution (たたみこみ)という単語も知っている可能性は高い。
IRT の本来の主目的は、ユーザやその試験のスコアを1次元に射影する(個人の能力を実数値で評価する)こと。
つまり、各項目(単語)の難易度は単なる中間生成物であり、その独立性を仮定しても大きな支障はない。
けど「個々のユーザが単語を知っている確率を求める」のに IRT を使う場合には、各項目(単語)の難易度が最終結果に直結している。
そこでは独立性の仮定は都合の悪い方向に働きそうだ。
すなわち、ロジスティックシグモイド関数の値が 0.5 以上か未満かでユーザがその単語を知っている可能性が高いか、知らない可能性が高いかを予測することになるわけだが、それは結局 IRT で求めたユーザごとの特性値(ユーザの語彙力)が単語の難易度を超えているかいないかと同値である。
でも、上の「英検2級過去問の単語・表現(2009年1月)」の単語の難易度順リストを見ただけでも、その方法では精度が出ないだろう事がわかる。
このリストのある値から上は、一括で「知らない」ことにされてしまうのだから。
SocialDict では、現在のところ大学院生12名(同じ専攻?)による訓練&テスト、利用者も同じクラスタに属している雰囲気。
でも、多種多様なユーザが参加してきた場合には単語の難易度順リストは大きく変わってしまうので、同等の的中率を維持するのは、きっと厳しい。
IRT が要求する「各単語が独立である」という仮定が適していないとしたら。
各単語ごとの「知っているかどうか」という確率変数は自明でない共分散を持ち、各ユーザが知っている/知らない単語を事前情報とする事後確率によって「個々のユーザが単語を知っている確率を求める」、そういうイメージはどうだろう。
っつっても、そういうのにいい方法があるという提案が出来るとか、まともにやったら計算量がすごいことになるけどそこを回避するにはどうしたらいいかとか、そこらへんにアイデアがあるわけではないし、そもそも素人だから根本的に間違ってるかも、と予防線。
とか言いつつ、iVoca でやりたいと思っていることには、IRT で得られたこの「単語の難易度」でも十分役に立ちそうなので、うまくいけばちょっとおもしろいものが作れるかも。