LDA で実験 その2:初期値を逐次サンプリングにしてみた

Collapsed Variational Bayesian での LDA 推論も実装してみたのだが、そのときに「パープレキシティが下がりきるのは非常に早いのに、その時点ではトピック-単語分布がストップワードだらけ」「イテレーションの最初のうちはパープレキシティがほとんど動かない」という現象にぶちあたってしまった。
で、その解決方法を考えているうちに、一つひらめいたことがあったので、また Collapsed Gibbs LDA に戻ってちょいと試してみた。


といっても大層なことではなく、推論の初期値に各 term のトピックをランダムに割り振るのだが、それを完全にランダム( K 個のトピックが一様分布)にするのではなく、Gibbs サンプリングに用いる事後分布を逐次更新しつつ、その分布からトピックをサンプリングするようにしてみたのだ。
つまり p( z_mn | z_mn より一つ前までの z , x_mn までの x ) から z_mn をサンプリングして、それを次の z_m(n+1) のサンプリングに使って……ということ。
ソース的にはこんな感じ。

        self.z_m_n = [] # topics of words of documents
        self.n_m_z = numpy.zeros((len(self.docs), K)) + alpha     # word count of each document and topic
        self.n_z_t = numpy.zeros((K, V)) + beta # word count of each topic and vocabulary
        self.n_z = numpy.zeros(K) + V * beta    # word count of each topic

        self.N = 0
        for m, doc in enumerate(docs):
            self.N += len(doc)
            z_n = []
            for t in doc:
                p_z = self.n_z_t[:, t] * self.n_m_z[m] / self.n_z         # 逐次更新された(事後)分布 
                z = numpy.random.multinomial(1, p_z / p_z.sum()).argmax() # z_mn を p_z からサンプリング
                z_n.append(z)
                self.n_m_z[m, z] += 1
                self.n_z_t[z, t] += 1
                self.n_z[z] += 1
            self.z_m_n.append(numpy.array(z_n))


普通の一様ランダムなら、コメントを付けた2行が z = numpy.random.randint(0, K) とかになってたイメージだから、変更量は少なめ。*1
けど、これがなかなかいい感じ。


一様ランダム初期化では 100回ほどイテレーションしないといけないところが、20〜30回でそれを上回る良い結果が出るようになった。
実装はいつものこちらに転がっている。


実装の説明はこの辺。


この工夫は Collapsed Variational Bayesian や HDP-LDA ではもっと有効だったので、そっちの話はまた今度。

*1:実際には1ドキュメント分を一気にサンプリングしてたので、その2行以外にもちょっぴりいじっているが、それらを含めてもたいしたことない。