先日gensimのLDAにデータを突っ込んでLDAモデルを作成していたところ、とりあえずモデルはできたものの学習中にRuntimeWarningが発生して困ったことになりました。結果的には「ちゃんと公式のリファレンス見ろ」で解決したのですが、そのうち忘れかねないので備忘録も兼ねて記事にしました。
環境
- gensim (3.5.0)
- Python 3.6.5
- Mac 10.13.5
実行した命令
いたって普通のgensimによるldaモデルの作成命令です。corpusと辞書の中身は書いていませんが、フォーマット的な異常はありませんでした。
lda = gensim.models.ldamodel.LdaModel(corpus=corpus, num_topics=125, id2word=dictionary)
発生した警告
とりあえず自分が確認した警告は、以下の三種類です。
/usr/local/lib/python3.6/site-packages/gensim/models/ldamodel.py:775: RuntimeWarning: divide by zero encountered in log
diff = np.log(self.expElogbeta)
/usr/local/lib/python3.6/site-packages/gensim/models/ldamodel.py:582: RuntimeWarning: overflow encountered in exp2
perwordbound, np.exp2(-perwordbound), len(chunk), corpus_words
/usr/local/lib/python3.6/site-packages/gensim/models/ldamodel.py:495: RuntimeWarning: overflow encountered in true_divide
gammad = self.alpha + expElogthetad * np.dot(cts / phinorm, expElogbetad.T)
要約すると、上から「$\log 0$が出てきた」「オーバーフローした」「オーバーフローした」です。察しの良い方なら、このエラーが出た時点でなんとなく解決策が浮かんでいるかもしれません。どの警告も、使っている型が計算で扱いたいデータの大きさと合っていないことが原因です。
警告のうち上2つは見た目上はまともそうなトピックができるのですが、一番下の警告ではトピック所属確率の高い上位10単語の所属確率が全てNaNになったりしました。NaNだこれは。
(以下2019/06/27追記)
上から二番目のオーバーフローに関しては、実際にLDAの計算を行なっているわけではないので、無視してしまっても計算結果自体は特に影響がないようです。
プログラムをのぞいてみたところ、どうやらログ情報として書き込むための計算でオーバーフローを起こしているご様子。
解決策
解決方法自体は非常に簡単なもので、型が合っていないと書いた通り、適切な型を指定してあげればいいわけです。とはいえpythonでわざわざ明示的に型を指定したっけ?となりますが、そこはしっかりgensimのldaのリファレンスを読めという話でした。
import numpy as np
lda = gensim.models.ldamodel.LdaModel(corpus=corpus, num_topics=125, id2word=dictionary, dtype=np.float64)
新しいmodel作成時には、新たにdtype
を指定しています。これはldaモデルで扱う型を指定するもので、デフォルトではnumpyのfloat32が指定されています。今回はnumpyのfloat64を指定しており、32ビットの浮動小数点数型から64ビットの浮動小数点型に変更したため、32ビットではオーバーフローしていたり0として扱われていた数値が問題なく扱えます。ビット数が倍になったので、扱えるデータ幅は一気に$2^{32}$倍です。これなら大丈夫でしょう。
しかしこれでも同じ警告が出現した場合は、最終手段としてnp.float128
まで用意されています。……それでもダメだったら、自分ではちょっとお手上げです。
(以下2019/06/27追記)どうやらnumpy側にはfloat128まで用意されているものの、gensim側ではnp.float64までしかサポートされていませんでした。np.float128を指定すると、次のようなエラーが出現します。
File "/usr/local/lib/python3.7/site-packages/gensim/models/ldamodel.py", line 510, in __init__
self.expElogbeta = np.exp(dirichlet_expectation(self.state.sstats))
File "gensim/_matutils.pyx", line 159, in gensim._matutils.dirichlet_expectation
File "gensim/_matutils.pyx", line 191, in gensim._matutils.dirichlet_expectation_2d
UnboundLocalError: local variable 'out' referenced before assignment
これは型の判定を行なっているのですが、float128が存在しないためif文を全スルーしoutが定義されないままreturnしてしまうためのようです。
よって、残念ながら最大でもnp.float64までしかしようできないようです。
おまけ
折角なので、他にもldaモデル作成時に指定できる引数のうち、簡単に使えそうかなぁと思ったものを一つだけ紹介しておこうと思います。
そもそもどれだけ引数があるのか?と案外疑問に思う人もいるのかなぁと思ったので、とりあえず全部載せるとこんな感じです。
gensim.models.ldamodel.LdaModel(corpus=None,
num_topics=100,
id2word=None,
distributed=False,
chunksize=2000,
passes=1,
update_every=1,
alpha='symmetric',
eta=None,
decay=0.5,
offset=1.0,
eval_every=10,
iterations=50,
gamma_threshold=0.001,
minimum_probability=0.01,
random_state=None,
ns_conf=None,
minimum_phi_value=0.01,
per_word_topics=False,
callbacks=None,
dtype=<type 'numpy.float32'>)
……多くね?
とりあえず動かしてみた系だとだいたい出てこない引数ばっかりですね。まあ実際それもそうで、モデル作成に最低限必要なものはcorpusとnum_topicsだけです。辞書に関しては、モデルを作成するだけなら必要なかったりします。とはいえid表示の単語を出されても辛いですし、いちいちこっちで辞書を噛ませるのも面倒なので大抵の場合は指定するかと思います。
この中でそこそこ使いどころがありそうと思ったのはminimum_probability
です。この引数はデフォルトで0.001が指定されているわけですが、実はこの引数によってトピックを無視する際の所属確率の閾値を決定しています。
通常作成したモデルにコーパスを入れると、入れたコーパスの各トピックへの所属確率が算出されます。モデル作成時に引数を何もいじらなければ、この時所属確率を返されるトピックはコーパスの所属確率が0.001以上のトピックのみです。場合によってはどれだけ値が小さかろうと、とりあえず所属確率が欲しいということもあるので、その時はminimum_probability
をうんと小さい値に設定すれば出てきます。