y_uti のブログ

統計、機械学習、自然言語処理などに興味を持つエンジニアの技術ブログです

Unity でディリクレ分布のグラフを描く

Unity の ParticleSystem を使って、3 変量のディリクレ分布のグラフを作成してみました。ウェブブラウザ上で動かせます。
f:id:y_uti:20140617063612p:plain
Unity Web Player | unity-dirichlet-distribution

ソースコードはこちらです。
y-uti/unity-dirichlet-distribution · GitHub

三角形の頂点が、それぞれ (x1, x2, x3) = (1, 0, 0), (0, 1, 0), (0, 0, 1) になります。スライダーを移動することで、パラメータ αi (i = 1, 2, 3) を変更できます。初期状態ではすべての αi が 1.0 に設定されています。設定できる範囲は、スライダーの左端が 0.1, 右端が 10.0 です。

プログラムの実装にあたって、ガンマ関数の近似計算が必要になります。これは Unity で使える数学関数の中に見つかりませんでしたので、以下のページで公開されているものを Unity の C# スクリプトに書き直して利用しました。
Machined Learnings: Faster LDA

ディリクレ分布は、多項分布の共役事前分布になることが知られており、たとえば、自然言語処理の分野での LDA などに利用されます。先日、第二回機械学習アルゴリズム実装会というイベントに参加して LDA を実装したので、それに合わせて今回のプログラムを作成してみました。
第二回機械学習アルゴリズム実装会 - connpass

なお、勉強会で実装した LDA のプログラムはこちらで公開しています。PHP と Hack で書いてみました。
y-uti/lda · GitHub

LDA では通常、各変数に対応する αi をすべて同一にして対称な分布を仮定しますが、αi をばらばらに動かすことで非対称な分布を作ることもできます。

ディリクレ分布の確率密度関数は xi^(αi - 1) をかけ合わせた形になるので、αi が 1 より大きければ xi について単調増加、αi が 1 より小さければ xi について単調減少になります。すべての αi を 1 未満にすると、各頂点のところで尖った分布になり、多項分布の事前分布として疎な分布を仮定できます。LDA では、文書-トピック分布、トピック-単語分布のそれぞれで疎な分布を仮定したいので、通常はパラメータを 1 未満にします。

PHP の array と Traversable

PHP の foreach に配列ではない値を渡すと、警告が出力されます。次のような test.php というスクリプトを作成して確認します。

<?php
$arr = null;
foreach ($arr as $k => $v) {
    print "$k => $v\n";
}

これを実行すると、次のような警告が出力されました。

$ php test.php
PHP Warning:  Invalid argument supplied for foreach() in /.../test.php on line 3

foreach に渡される配列が null かもしれない場合、配列にキャストするというテクニックがあるようです。以下のようにすると警告が出力されなくなります。

<?php
$arr = null;
foreach ((array) $arr as $k => $v) {
    print "$k => $v\n";
}

これは、null を配列にキャストすると空の配列になるためです。PHP での配列へのキャストについては以下に記載されています。
PHP: 配列 - Manual

一方で、PHP の foreach には、通常の配列のほかに Traversable インタフェースを実装したオブジェクトを渡すこともできます。Traversable を実装したクラスには、SPL の各種コンテナやジェネレータがあります*1

ところが、Traversable は配列ではないので、配列にキャストするときにはオブジェクトとして扱われます。上記のマニュアルに書かれているように、オブジェクトを配列にキャストすると、フィールドの名前と値が key-value として列挙された配列になります。したがって、配列にキャストして null を渡せるようにしたコードは、今度は Traversable を渡せなくなります。私が調べた限りでは、Traversable と通常の配列を統一的に扱う方法は見つかりませんでした。instanceof を使って判断するしかないようです。

結局、array, Traversable, null のどれでも渡せるように書くと、このようなコードになるでしょうか。

<?php
if (!$arr instanceof Traversable) {
    $arr = (array) $array;
}
foreach ($arr as $k => $v) {
    print "$k => $v\n";
}

*1:Traversable を継承した Iterator インタフェースを実装しています。