Skip to content
Snippets Groups Projects
nmf.py 2.31 KiB
Newer Older
Pierre Neveu's avatar
Pierre Neveu committed
# NMF by alternative non-negative least squares using projected gradients
# Author: Chih-Jen Lin, National Taiwan University
# Python/numpy translation: Anthony Di Franco

from numpy import *
from numpy.linalg import norm
from time import time
from sys import stdout

def nmf(V,Winit,Hinit,tol,timelimit,maxiter):
 """
 (W,H) = nmf(V,Winit,Hinit,tol,timelimit,maxiter)
 W,H: output solution
 Winit,Hinit: initial solution
 tol: tolerance for a relative stopping condition
 timelimit, maxiter: limit of time and iterations
 """

 W = Winit; H = Hinit; initt = time();

 gradW = dot(W, dot(H, H.T)) - dot(V, H.T)
 gradH = dot(dot(W.T, W), H) - dot(W.T, V)
 initgrad = norm(r_[gradW, gradH.T])
# print 'Init gradient norm %f' % initgrad 
 tolW = max(0.001,tol)*initgrad
 tolH = tolW

 for iter in xrange(1,maxiter):
  # stopping condition
  projnorm = norm(r_[gradW[logical_or(gradW<0, W>0)],
                                 gradH[logical_or(gradH<0, H>0)]])
  if projnorm < tol*initgrad or time() - initt > timelimit: break
  
  (W, gradW, iterW) = nlssubprob(V.T,H.T,W.T,tolW,1000)
  W = W.T
  gradW = gradW.T
  
  if iterW==1: tolW = 0.1 * tolW

  (H,gradH,iterH) = nlssubprob(V,W,H,tolH,1000)
  if iterH==1: tolH = 0.1 * tolH

#  if iter % 10 == 0: stdout.write('.')

 #print '\nIter = %d Final proj-grad norm %f' % (iter, projnorm)
 return (W,H)

def nlssubprob(V,W,Hinit,tol,maxiter):
 """
 H, grad: output solution and gradient
 iter: #iterations used
 V, W: constant matrices
 Hinit: initial solution
 tol: stopping tolerance
 maxiter: limit of iterations
 """
 
 H = Hinit
 WtV = dot(W.T, V)
 WtW = dot(W.T, W) 

 alpha = 1; beta = 0.1;
 for iter in xrange(1, maxiter):  
  grad = dot(WtW, H) - WtV
  projgrad = norm(grad[logical_or(grad < 0, H >0)])
  if projgrad < tol: break

  # search step size 
  for inner_iter in xrange(1,20):
   Hn = H - alpha*grad
   Hn = where(Hn > 0, Hn, 0)
   d = Hn-H
   gradd = sum(grad * d)
   dQd = sum(dot(WtW,d) * d)
   suff_decr = 0.99*gradd + 0.5*dQd < 0;
   if inner_iter == 1:
    decr_alpha = not suff_decr; Hp = H;
   if decr_alpha: 
    if suff_decr:
     H = Hn; break;
    else:
     alpha = alpha * beta;
   else:
      if not suff_decr or (Hp == Hn).all():
       H = Hp; break;
      else:
       alpha = alpha/beta; Hp = Hn;

  if iter == maxiter:
   print 'Max iter in nlssubprob'
 return (H, grad, iter)