LDA で実験 その1:stop words の扱い方でどう変わる?


というわけで連載じゃあないけど第3回。わざわざ自前で実装したんだから、LDA で細かい設定で実験してみる。


NLTK のブラウンコーパスの 0〜99 までの 100 個のドキュメントをコーパスとし、トピック数は K=20、ハイパーパラメータはα=0.5, β=0.5、イテレーションは 100 回、というのが基本条件。*1
そして stop words の扱いを「除外(-s 0)」、「除外しないで他の単語と同様に扱う(-s 1)」、そして「初期化時にストップワードを1つのトピック(k=0)に集中させる。その他の単語は残りのトピック(k>0)に分散させる (-s 2)」と変えてみて、それぞれ 10回推論を行わせて、perplexity やトピック-単語分布について確認する。ただし -s 0 のときは -s 2 との対比でトピック数 K=19 にしている。
つまりストップワードを全く特別扱いしない -s 1 に対して、除外したり固めたりすると結果がどう変わるか、を見ようとしているわけだ。


追記】一番新しい実装では、ストップワードを1つのトピックに集中させる機能は削除されている。代わりに、LDA で実験 その2:初期値を逐次サンプリングにしてみた - Mi manca qualche giovedi`? の機能が追加されている。【/追記


ストップワードは当初 NLTK のものを使おうとしたのだが、nltk.corpus.stopwords.words('english') には 127語しか入っていなくて、今回の用途にはちょっと少ないかな〜。
ということでこちらのサイトのリストを使用させてもらった。


それぞれ 10回の推論について、その最終パープレキシティを小さい順に並べたのが以下の表。
なお、-s 1 および 2 のときの語彙数は 16271語であるのに対し、-s 0 のときの語彙数は 15834語とストップワードを除外した分減少している。

python ./lda.py -c 0:100 -s 0 -i 100 -k 19 --alpha=0.5 --beta=0.5
python ./lda.py -c 0:100 -s 1 -i 100 -k 20 --alpha=0.5 --beta=0.5
python ./lda.py -c 0:100 -s 2 -i 100 -k 20 --alpha=0.5 --beta=0.5
sw除外 swあり swを1トピックに
3857.91 1266.47 1285.07
3881.76 1275.33 1288.23
3895.36 1275.36 1288.26
3904.56 1278.58 1288.64
3911.43 1280.28 1289.56
3922.56 1281.85 1290.95
3940.05 1283.24 1290.95
3959.56 1283.30 1292.25
3971.81 1284.08 1293.43
4035.45 1300.99 1294.43


ストップワードを除外した場合はパープレキシティがかなり大きくなってしまっている。
もし語彙数が均等に減れば、通常はそれぞれの単語の生成確率が相対的に上がるのでパープレキシティは下がるのだが、今回の場合は頻度の高い語彙=生成確率の極めて高い単語を優先的に除外してしまっているため、当然こうなる。
まあ、どっちにしてもコーパスの条件が違うわけで、パープレキシティで単純比較するのは難しい。


ストップワードを特別扱いするとパープレキシティが小さくなることを正直期待していたのだが、ここでは思惑通りには行かなかった。
初期化直後のパープレキシティは -s 1 より -s 2 の方がかなり小さいのだが、なぜかイテレーションを繰り返すうちに逆転してしまう。
一方で、ストップワードを1トピックに固めるとパープレキシティが安定しやすい傾向があることもわかる。
このようになる理由の推測については、後ほど。

トピック-単語分布

今回の実装では学習後に次のような形式でトピックごとの単語分布を出力する。

-- topic: 14
sale: 0.004446
stock: 0.002761
price: 0.002199
equipment: 0.002106
	:

これは 14 番のトピックの生成確率の高い単語順に出力されており、"sale" が確率 0.004446 で、"stock" が確率 0.002761 で、……、それぞれ生成される、という意味だ。
これを見ることで、文書のトピック分けがどの程度うまくいきそうかを定性的に確認できる。
このトピックごとの単語分布がストップワードの扱い方によってそれぞれどのような傾向を示すかを見てみよう。


そもそも、なぜストップワードを特別扱いしたいのか?
単純には the や of などの頻出語が複数のトピックにたまたま均等に割り当てられた場合、 今回の推論手法はそこから抜け出すのが難しい(何回もイテレーションする必要がある)。実際、頻出語は「他の単語から見た場合に」共起しやすいわけで(もちろん頻出語側から見たらそんなことはない)、もともと複数のトピックにまたがりやすいということもある。


実際、-s 1 では次のように「ストップワードトピック」が複数出てきてしまうことは珍しくない。

-- topic: 8
of: 0.026604
to: 0.019300
a: 0.018950
in: 0.016588
	:

-- topic: 12
of: 0.046236
a: 0.040280
and: 0.036654
to: 0.032378
	:


これが起きるか起きないかは初期値や推論の乱数の具合で決まり、分割されてしまうとパープレキシティが大きくなってしまうので、 -s 1 でのパープレキシティの上下動の幅が大きくなる、というわけ。


また、ストップワードが他のトピックにまじってしまう、ということも比較的多く起こりうる。以下は her や she が「音楽」とおぼしきトピックに混じってしまっているところ。*2

-- topic: 0
her: 0.006625
music: 0.005046
she: 0.003975
miss: 0.003411
musical: 0.002396
jazz: 0.002340
song: 0.002340
season: 0.002340
concert: 0.002284
performance: 0.002284
dance: 0.002227
art: 0.002002
orchestra: 0.001945
	:


これらの状態で安定してしまうと、多少推論を繰り返したくらいでは抜け出せない(さらに 100回とか 1000回とかどこまでも繰り返せばいつかは抜け出せるのだけど)。


そこであらかじめストップワードを手当てすることで、これらの停滞状態を回避できるか確認したい、というわけだ。
ストップワードの除外(-s 0)では、代表的な高頻出語は最初から取り除かれているため、当然ストップワードトピックは存在しない。
が、ストップワードリストに含まれない高頻出語が行き場を失い、多くのトピックにまたがるという現象が起こる。

-- topic: 6
wa: 0.018416
ha: 0.014984
time: 0.005148
year: 0.004946
state: 0.004744
man: 0.004175
american: 0.004083
world: 0.004010
member: 0.003441
church: 0.003386
people: 0.003276
good: 0.003257
day: 0.002780
make: 0.002762
united: 0.002762
life: 0.002744
u: 0.002744
made: 0.002689
president: 0.002670
war: 0.002615


"wa" と "ha" は NLTK の lemmatize(原形変換)によって変換された "was" と "has" で、そのせいで除外から漏れてしまったストップワードなのだが、それ以外にも time, year, good, day, make などのストップワードに入れてもいい(少なくともトピック分けの役には立たない)単語が非常に多く含まれている。
が、それらの単語以外をよく見ると、一応これは政治やアメリカのトピックなのだ。


逆に、初期化時にストップワードを一つのトピックに固めるアプローチ(-s 2)では、ストップワードから漏れた高頻出語も、そうやって作られたストップワードトピックに吸い寄せられて、上の例に挙げられていたような準ストップワードたちは他のトピックを汚さない。
が、その一方で、今回の文書集合の中ではたまたま頻度が低かったという単語がストップワードとして1箇所に固められてしまうと、そこから抜け出してより適した(=パープレキシティが小さくなる)トピックに引っ越しする、というのがやはり今回の推論手法では難しい。
そのせいで -s 2 でのパープレキシティが -s 1 よりおおむね大きくなってしまうのだろうと思われる。


といったことなどから、トピック-単語分布をつらつら見る限りでは、-s 2 の場合の結果が人間の望む結果に一番近いのではないかという印象(結果が安定しているというのも使いやすいし)。


さて、今回は初期化時にストップワードの扱いを変える方法で、LDA でのストップワード対策を考えてみたが、もともとストップワードをうまく扱えるモデルというのも試してみたいところ。
と言うわけで次回は HDP-LDA を……と思っていたが、上の結果を見るとストップワードのリストを用意するより、文書集合での実際の頻度を使ってクリーニングする方が良いかも、というイメージがふつふつ湧いてくるので、次はそこらへんを試す。

*1:K=20 にしたのは HDP-LDA が推論したトピック数が 24〜25 前後だったので、そのあたりの切りのいい数を採用している。

*2:ただしこれは学習に使う文書集合が小さいせいでもある