言語判定のモデルパラメータを自己組織化マップで可視化

サイボウズでも巷の流行りに乗っかって、アドベントカレンダーなるものをやってて、担当した記事が今日公開された。

通常のアドベントカレンダーと違って、テーマは「技術ならなんでも」って広すぎるやろー。というわけで言語処理な人には当たり前で、それ以外の人にはおそらく興味がないという、なかなかニッチな記事に(よく言えば)。


本当は「なるほど、わからん」と言ってもらえるような記事が書きたくて、いくつかネタ候補を用意したんだけど、ことごとく自らボツに。実は先週の Kneser-Ney perplexity 記事もそんなボツネタの1つ。あの記事を一般技術者向けの Cybozu Inside Out に書いてみるという誘惑もあったんだけどねw


他にも ldig のパラメータを可視化して遊んでみるというネタもあって、これはギリギリまで悩んだんだけど、やっぱりボツにしちゃった。でもまあせっかくいろいろ準備したので、好きなようにできる自分のブログでお蔵出ししてしまおう〜。


ldig は twitter のような短文の言語判定をするプロトタイプツール。

詳しくはリンク先のスライドとかを見てもらうとして、今日の話に関係あることだけ言うと、

  • 判別器は多クラスロジスティック回帰
  • 素性は任意の部分文字列 (L1 正則化で素性選択)
  • 19 のラテン文字言語を判別する学習済みモデルを公開

というあたり。
そんなん言われてもわからん! じゃあ具体的に行こう。


ロジスティック回帰はこういう式で分類を推測する。


p(言語=L|文章) ∝ exp(Σ_s [言語 L での文字列 s の重み])


s は文章の全ての部分文字列を走る。比例とか exp 関数とかあるが、これらは大小関係を変えないので、つまり Σ_s [言語 L での文字列 s の重み] の一番大きい L が、一番確率の大きい言語ということだ。
ということは当然、一つ一つの [言語 L での文字列 s の重み] の値は「文字列 s の言語 L っぽさ」を表しているということになる。
ldig の学習済みモデルは、この「文字列 s の言語 L っぽさ」を19言語分持っているということだ。


実際の重みの値を一部見てみる。

feature th ij
ca -0.373 -0.522
cs -0.475 -0.181
da 0.583 -1.06
de -1.713 -2.554
en 6.196 -1.763
es 0.278 0.45
fi -0.875 0.506
fr -0.041 -0.406
hu -0.137 -0.119
id -0.681 -0.161
it -1.045 -0.278
nl -0.863 9.029
no -1.354 -1.225
pl -0.518 -0.267
pt -0.362
ro -0.138 -0.043
sv -0.449 -0.54
tr -0.072 -0.288
vi 2.528 -0.076


表の縦軸は ldig が配布している学習済みモデルに含まれる19言語だ。こんな記事をわざわざ読んでくれてる言語判定マニアには説明不要だろうが、一応念のため上からカタルーニャ語チェコ語、、デンマーク語、ドイツ語、英語、スペイン語フィンランド語、フランス語、ハンガリー語インドネシア語、イタリア語、オランダ語ノルウェー語、ポーランド語、ポルトガル語ルーマニア語スウェーデン語、トルコ語ベトナム語となっている。


th は英語(en)では大変良く見るお馴染みの綴りだが、実は他の言語ではほとんど使われないということが、この表を見るとわかる。英語での th の重みは 6.2、あとはベトナム語(vi)で 2.5 というのがあるくらいで、他はだいたい負の重みになっていて、「 th があったら、うちの言語じゃあ無いよ〜」状態なわけだ。
同様に ij という綴りはほぼオランダ語(nl)固有のものだということもわかる。


てな感じで、この表をつぶさに見ていくと、どういう部分文字列、つまり「綴り方の特徴」がその言語らしさを表しているのかを見て取れるのだが、実は横に 110万件あって、ちょっとまじめには見てられない。
一度に全部見られないほどのデータの特徴や傾向をつかむにはやっぱ可視化でしょう〜ってことで、このパラメータのでっかいかたまりをSOM(自己組織化マップ) に食わせてみた。やっと今日の本題。


パラメータ行列は numpy.dump で書き出されているので、Python で SOM できる PyMVPA を試したが、こいつは pure Python で書かれているようで、数千件食わせたら一晩回したあげくエラーを吐いて落ちた。


じゃあパラメータ行列をテキストに変換するところまで Python でやって、あとは R でやろう。
さすがに 115万件全部は多すぎるし必要ないので、パラメータの絶対値が大きい方から10000件を、CRAN にもある kohonen パッケージで SOM 分析。わずかな時間で完了。さすが。


ところが、部分文字列のリストを R で読み込むのに手こずる。SOM の地図上に表示したいだけなのに。
最終的には "æ" が "a" に化けるという怪現象に遭遇。

Mac では "æ" はちゃんと "æ" らしいので、Windows 版固有の症状かもしれない。
というわけで R で描画することは断念し、SOM の結果をまたまたテキストファイルに吐いて、Python で読み込み matplotlib で描画。
おかげで、たいしたことしてないのにソースは3つ。なんたるちあ。

これで描いた SOM 地図がこちら。


  • 素性(部分文字列)を10000個はさすがに表示できないので、ユニットからの距離が近い順に4つ表示
  • 空白文字はアンダースコアで、行頭・行末は $ で表示
  • 多様性確保のため、前方一致・後方一致する部分文字列は長い方を採用
  • 各ユニットの右下に、もっとも重みの大きい言語ラベルを表示
  • 5つの言語は重みに応じてユニットを色塗り(シアン=スペイン語、マゼンダ=ポルトガル語、赤=ノルウェー語、緑=デンマーク語、黄色=スウェーデン語)

すべての言語にそれぞれ色を割り振って塗り分けられたらいいのだが、なにしろ19言語もあって、しかも重みによってグラデーションとかした日にはきっと区別つかない。


SOM によって描かれたこの地図を見ると、いろいろ思いつくことが。
ここにあがっている部分文字列がそれぞれの言語を強く特徴付けるものだろうなあ、というのが当然まず第一にくる。例えば右辺まんなかあたりには「いかにも英語(en)らしい綴り」が、左辺まんなかには「いかにもイタリア語(it)らしい綴り」がある。
中央少し下にポーランド語(pl)、トルコ語(tr) があるが、これらは文字で区別がつくんだろうな、ということも読み取れる。


次は配置を見てみよう。
原則として似ている言語は近くに配置されるが、「似ているけど見分けづらい言語」はできるだけ離れた位置に配置されるという傾向がありそうだ。
例えばノルウェー語(no)とデンマーク語(da)はとてもとてもとても似ているのだが、地図では左上と右下の両極に位置している。でもやっぱり似ているので、デンマーク語の領地をノルウェー語が侵食している。
同じことがスペイン語(es) 対 カタルーニャ語(ca)・イタリア語(it)・ポルトガル語(pt)にも言える。スペイン語に雰囲気似てて、でも見分けるのはあまり難しくないルーマニア語(ro)はスペイン語に隣接しているのがまた面白い。


見分けるのが易しい言語は優勢なユニットが少なく、難しい言語はユニットが多いという傾向もある。
先程から名前の上がっている言語はだいたいどれも結構広い陣地を持っているのに対し、トルコ語(tr)、ベトナム語(vi)、チェコ語(cs)、ポーランド語(pl)、ハンガリー語(hu)、インドネシア語(id) などは陣地が少ない。


こんな感じで言語判定マニアなら飽きずに何時間でも眺めてられる ldig SOM 地図の高解像度版をここに転がしといたので、印刷して壁に貼って楽しもう。


おまけの他の色バージョン(シアン=英語、マゼンダ=ドイツ語、黄色=オランダ語、緑=フランス語、赤=イタリア語)。