研究が進んでいないので,記事を書いて,モチベーションを高めることに....
今回の紹介アルゴリズムはNMFで現在(2015年10月)で最速といわれている(筆者の知る限り)手法Greedy Coordinate Descent Algorithmの紹介です.
元論文は,
Hsieh, C.-J., & Dhillon, I. S. (2011). Fast coordinate descent methods with variable selection for non-negative matrix factorization. In ACM SIGKDD (pp. 1064–1072). https://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&cad=rja&uact=8&ved=0CDUQFjACahUKEwix5Oar-arIAhVJmZQKHcJJBik&url=https%3A%2F%2Fwww.cs.purdue.edu%2Fhomes%2Fdgleich%2Fconf%2Fslides%2Fmmm2012%2Fdhillon.pdf&usg=AFQjCNGssbMCO2vHvbaOIl3nkDCnuE6JiA&sig2=Tti3GHlrtBTuYGf8bVbAhA&bvm=bv.104317490,d.dGo
Dhillon先生は,超やばい研究者で,そのお弟子さんたちと合わせて,毎年恐ろしい本数の論文をトップ会議へ通してます.
それはおいといて, Greedy Coordinate Descentは要素毎に更新する手法です.
HALSも要素毎といえば要素毎の更新といえばそうなのですが,
何故,敢えて区別していうかは,読んでいただければわかってくると思います.
GCDの肝は,更新する要素を選択して重要な要素をgreedyに更新を行うところです.
HALSもMUも一回の更新で全ての要素を平等に更新します.
一方,GCDでは近似精度を上げるために重要な要素はそれが重要である限り何度でも更新し,不要であれば一切は更新しません.
そうすることで劇的に収束が早くなっています.
* GCD
では実際に更新方法を導出してきます.
その前に,どのような方針で最適化するかを大まかに述べます.(更新する行列はとします)
- 行列
の全ての要素に対して,要素毎の問題の最適解を計算
- 最適解を用いて各要素の重要度を計算 +一番重要な要素を更新
- 更新された要素に関係のある要素の最適解及び重要度を更新
- 2-3を繰り返す
これだけです.重要なのはStep3が実は高速に行えるという点です.
導出
まず,毎度お馴染みのNMF問題は
です.ちなみに転置するとも
も同じなので,今回は本当に
のみの話をします.
要素毎の最適解を見つけるのでについての最適化を考えます.(
毎の問題ではありませんので注意.)
最適なの値(他を固定している場合)はHALSのものと同じ結果になります.(ここが下線を引いた部分に対応します.どうしてそうなるのかを考えると,GCDが高速に分解できる理由が見えてきます.)
具体的には,GCDの論文の書き方にならって書くと,
さらに,更新の差分であるは,
と書けます.
あっさりではありますが,Step1は突破しました.
次にStep2です.GCD手法の最大の貢献はこの部分をテイラー展開であっさりと計算できるようにしたところです.(他分野ではあったと思いますが,NMFにはここで初めて持ち込まれました.)
を上の更新式で更新した場合の近似誤差の関数は
を引数とした関数
で書けます.
この式を2次以降は無視したテイラー展開で近似します.
そうすると,どれだけ目的関数を減らすかということが,差分で分かるようになります.その重要度とすると
ここで,と
の値はどうなっているかというと,
です.
ここで,GCDが高速に計算できる理由は,行列の更新において,互いに影響する要素は同じ行ベクトルに存在する要素のみということです.
具体的には,を更新したと時に,再計算が必要なのは
だけということです.
実際の更新は
です.
行ベクトルのみしか影響しないため,Step2とStep3は行ベクトル内で行います.
まとめると
行列の更新は
- 各行ベクトルに対して(
行目を載せる)
- while convergence
全ての
に対して.
全ての
に対して.
- while end
行ベクトルでのループの更新は全てで高速にできるため,高速に計算できます.
実験
実験の比較です.
前々回の記事で,紹介していますが,GCDのコードは著者によって提供されています.www.cs.utexas.edu
注意して欲しいのは,GCDはfor文を多用することになるので,MATLABやRでの実装は向きません.
実際に,提供されているコードはMATLABでCを呼び出すmexを用いています.(厳密に言えば公正な比較ではありません)
それでも,尚計算量の解析は元論文でされているのでそちらを参照してください.
ちなみに更新に提供されている関数は doiterと呼ばれるものです.
使い方は
mex doiter.cpp
しといて,
という形で用います.後ろ二つは更新の打ち切りに用いるものなのであまり影響を受けません.おまじないと思ってください.
詳しい話は論文に載っています.
として使って大丈夫です.
比較手法
MUとHALSの比較を行います.
MUについては,他のサイトの記事,HALSについては前回の記事を読んでください.
評価指標
前回と同じです.
- CPU時間
- 近似精度 Normalized Residual Value
実験設定
同様に分解後のサイズを変動させて挙動を観測します.
いくつかを固定した結果も載せます.
実験結果
まずは,全てのにおいて,100回の反復更新を行った場合の結果です.
HALSがのときにのみ急激に上がるのが気になりますね...
まぁおいといて,一回の計算量は何度も要素を更新している点で,遅いことが分かります.
の際の実際の計算時間はこちらです.
さて次は,更新によってどれだけ近似誤差が減少しているかです.
のときは
・・・あれ?
GCDがそんなに良くない...おかしいな....
計算時間でも
HALSに負けてる...?
ちょっと原因を探している状態です... おかしいな...
分かった方は教えてください...
まとめ
GCDは要素毎の更新を行うNMFです.
各要素に対して,重要度を算出して,それに沿ってgreedyに更新を行う手法です.
同一の行列内では,同じ行indexを持つ要素のみしか,互いに影響しないため,行ベクトル毎にループをするだけでよく,
これにより,高速に要素の更新+重要度の更新を行うことができます.
実験では高速化されるはずだったのですがされてませんでした.失敗している模様です.
コードは以下からダウンロードできます.前回のリンクと同じですがアップデートした形になります.
ただdoiter関数は含めていないので,使う場合は,著者のページからダウンロードして用いてください.
https://dl.dropboxusercontent.com/u/97469461/NMF.zip
NMFGCDのコードです.
function[U,V]=NMFGCD(X,U,V,iter); %%Input %% X = M x N matrix %% U = M x K matrix %% V = N x K matrix %% iter: #iterations %% initialization K=size(U,2); eps=1e-08; for loop=1:iter B=V'*V; A=(U *B) - (X*V); Unew=doiter(A',B,U',0.001,K^2); U=Unew'; B=U'*U; A=(V*B) -(X'*U); Vnew=doiter(A',B,V',0.001,K^2); V=Vnew'; end