Labeled LDA (Ramage+ EMNLP2009) の perplexity 導出と Python 実装

3年前に実装したものの github に転がして放ったらかしにしてた Labeled LDA (Ramage+ EMNLP2009) について、英語ブログの方に「試してみたいんだけど、どういうデータ食わせたらいいの?」という質問コメントが。
うーん、そうね、そういうところ書いてないから全然わかんないよね。手に入りやすいコーパスをだれでも食わせられるようにしてあると、やっぱり評価しやすいよね。
ということで、Labeled LDA に nltk のコーパスを食わせるスクリプトをさっくり書いてみた。


同じディレクトリに llda.py がある状態で、そのまま起動すれば nltk.corpus.reuters から 100件のドキュメントをサンプリングして、適当なパラメータで Labeled LDA の推論して、ラベル-単語分布を出力する( LDA と違ってトピック単語分布ではないところがミソ)。
Labeled LDA はマルチラベルに対応しており、nltk.corpus.reuters も各文書が複数カテゴリを持つので、データとしてはぴったり。ただし、カテゴリ毎の文書数が違いすぎるので、ちゃんと均したデータを作ったほうがいいかもしれない、と思いつつそこは手抜き。


自分の llda.py のコードを久しぶりに見たが、まあ約3年も前に書いたコードなものだから全般的に直したい衝動が湧き上がってくるのはおいとくとしても、perplexity のメソッドにただ一行 pass って書いてあるのはさすがにひどすぎる。この機会にこれくらいはさすがにやっつけておこう。
そこで Labeled LDA(Ramage+ EMNLP2009) を読みかえしてみたら、perplexity の式が論文内では導出されていなかった。
llda.py を実装したのは 2010年の 7月…… PRML 読書会ラス2が行われ、TokyoNLP の第1回が開催され、gihyo.jp の連載も始まったばかりの頃……。ははあ、さてはお前自力で perplexity の式が導出できなかったんだな(生暖かい目)。
というわけでさっくり導出&実装を perplexity 対応に更新。


perplexity 計算のために導出した式もさらしておこう。間違いあればご指摘歓迎。
いつもの LDA っぽくやるには、ドキュメント-トピック分布とトピック-単語分布を推定する必要がある。Labeled LDA (Ramage+ EMNLP2009) とは異なるが、それらを慣れた記号θとφで表すと、

  • \theta_{dz}=\mathbb{E}[p(z|d)]=\begin{cases}\frac{n_{dz}+\alpha}{n_{d\cdot}+M_d\alpha}\hspace{20px}&\text{if }z\in\lambda^{(d)}\\0&\text{otherwize}\end{cases}
  • \phi_{zw}=\mathbb{E}[p(w|z)]=\frac{n_{zw}+\eta}{n_{z\cdot}+V\eta}


λ^(d) というのは文書 d がもつラベルに対応するトピックの集合、M_d はその集合のサイズ。
Labeled LDA は要はカテゴリラベルごとにトピックが割り当てられて、各文書のトピックが観測済みのカテゴリラベルによる制約を受けるモデルなので、ひもづけられていないθ_dz が消える必要があり、ちょっとややこしくなっている。
この実装 llda.py では common カテゴリを勝手に増やして、すべての文書のラベルに追加している。こうすることで「多くの文書で同様な出現分布を持つ単語」は common カテゴリに入る。つまり stop words がキレイに common カテゴリに入る。ここは Labeled LDA の見どころ。


ちなみに Labeled LDA では1つのカテゴリには1つのトピックが対応付けられるわけだが、もちろんそれが最適であるとは限らない。複数のラベルでトピックを共有したほうがよい場合も十分考えられる(coffee カテゴリと tea カテゴリは共通のトピックがかなりあるだろう)。そこらへんも同時に推論するのが以前紹介した Dirichlet Process with Mixed Random Measures (Kim+ ICML2012) だったりする。


DP-MRM も実装する気まんまんだったのだが、いやもうここだけの話、HDP-LDA よりはるかにめんどくさい。自分の HDP-LDA 実装を参考に眺めているうちに、久しぶりにコード見たけどこれはひどいな、あー実装し直したい(どこかで聞いたな……)という衝動に負けて hdplda.py をゼロから実装しなおしてお腹いっぱいになってしまい。まあそのうちごにょごにょ。