重み付き非負行列分解というタスクがある.欠損ありのNMFとかに使える. 研究に必要だったので,オーソドックスな次の手法を実装した.
実装は簡単で普通のNMFの更新式をちょっと変えるだけ.
フロベニウスノルム尺度のものとKL尺度のもの.W
が重み行列.W=ones(n,m)
で通常のNMFになる.
using LinearAlgebra using Random using Arpack function D_KL(A, B) n, m = size(A) kl = 0.0 for i = 1:n for j = 1:m kl += A[i,j] * log( A[i,j] / B[i,j] ) - A[i,j] + B[i,j] end end return kl end function wnmf_euc(A, W, r ; max_iter=200, tol = 1.0E-4, verbose = false) m, n = size(A) U = rand(m, r) V = rand(r, n) error_at_init = norm(A - U*V) previous_error = error_at_init for iter = 1:max_iter V .= V .* ( U' * (W .* A) ) ./ ( U' * (W .* (U * V) ) ) U .= U .* ( (W .* A) * V') ./ ( (W .* (U * V)) * V' ) if tol > 0 && iter % 1 == 0 error = norm(A - U*V) if verbose println("iter: $iter cost: $error") end if (previous_error - error) / error_at_init < tol break end previous_error = error end end return U, V end function wnmf_kl(A, W, r ; max_iter=200, tol = 1.0E-3, verbose = false) m, n = size(A) U = rand(m, r) V = rand(r, n) one_mn = ones(m, n) error_at_init = D_KL(A, U*V) previous_error = error_at_init for iter in 1:max_iter V .= (V ./ (U'*W)) .* (U' * (( W .* A ) ./ (U*V))) U .= (U ./ (W*V')) .* ((( W .* A ) ./ (U*V)) * V' ) if tol > 0 && iter % 10 == 0 error = D_KL(A, U*V) if verbose println("iter: $iter cost: $error") end if (previous_error - error) / error_at_init < tol break end previous_error = error end end return U, V end
U*V
がr
ランクの行列になっている.
追記:
コスト関数は,本当は,重み行列W
でマスクしないといけない.なので,D_KL(A, U*V)
はD_KL(W.*A, W.*(U*V))
に変更が必要と思う.norm(A - U*V)
も,多分norm(W.* A - W.*(U*V))
が正しいと思う.