というわけで連載じゃあないけど第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 を……と思っていたが、上の結果を見るとストップワードのリストを用意するより、文書集合での実際の頻度を使ってクリーニングする方が良いかも、というイメージがふつふつ湧いてくるので、次はそこらへんを試す。