-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
First NMF function #2
base: NMF
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
""" Sparse Nonnegative matrix factorization | ||
""" | ||
# Author: Vamsi Krishna Potluru | ||
# License: BSD 3 clause | ||
|
||
from __future__ import division | ||
import numpy as np | ||
import scipy.io | ||
|
||
|
||
def sparse_nmf(X, r, maxiter, spar, W=None, H=None): | ||
"""Learns a sparse NMF model given data X. | ||
|
||
Parameters | ||
---------- | ||
|
||
X : {array}, shape = [n_features, n_samples] | ||
r : rank of factorization | ||
maxiter : number of updates of the factor matrices | ||
spar : sparsity of the features given by measure | ||
sp(x) = (sqrt(n)-|x|_1/|x|_2 )/(sqrt(n)-1) | ||
|
||
Returns | ||
------- | ||
|
||
W : {array} | ||
Feature matrix to the sparse NMF problem. | ||
|
||
Reference | ||
--------- | ||
|
||
Block Coordinate Descent for Sparse NMF | ||
Vamsi K. Potluru, Sergey M. Plis, Jonathan Le Roux, | ||
Barak A. Pearlmutter, Vince D. Calhoun, Thomas P. Hayes | ||
ICLR 2013. | ||
http://arxiv.org/abs/1301.3527 | ||
""" | ||
m, n = np.shape(X) | ||
if not W and not H: | ||
W, H = init_nmf(X, r, spar) | ||
Obj = np.zeros(maxiter) | ||
for i in range(maxiter): | ||
Obj[i] = np.linalg.norm(X - np.dot(W, H), 'fro') | ||
print 'iter:', i + 1, 'Obj: ', Obj[i] | ||
W = update_W(X, W, H, spar) | ||
H = update_H(X, W, H) | ||
|
||
return W | ||
|
||
|
||
def init_nmf(X, r, spar): | ||
""" Initialize the matrix factors for NMF. | ||
|
||
Parameters | ||
---------- | ||
|
||
X: {array}, shape = [n_features, n_samples] | ||
r: rank of factorization | ||
|
||
Returns | ||
------- | ||
|
||
W : {array} | ||
Feature matrix of the factorization | ||
H : {array} | ||
Weight matrix of the factorization | ||
|
||
where X ~ WH | ||
""" | ||
m, n = np.shape(X) | ||
W = np.zeros((m, r)) | ||
k = np.sqrt(m) - spar * (np.sqrt(m) - 1) | ||
for i in range(r): | ||
W[:, i] = sparse_opt(np.sort(np.random.rand(m))[::-1], k) | ||
|
||
W = np.random.rand(m, r) | ||
H = np.random.rand(r, n) | ||
return (W, H) | ||
|
||
|
||
def update_W(X, W, H, spar): | ||
"""Update the feature matrix based on user-defined sparsity""" | ||
m, n = np.shape(X) | ||
m, r = np.shape(W) | ||
cach = np.zeros((m, r)) | ||
HHt = np.dot(H, H.T) | ||
cach = -np.dot(X, H.T) + np.dot(W, np.dot(H, H.T)) | ||
for i in range(r): | ||
W, cach = W_sparse_ith(W, HHt, cach, spar, i) | ||
|
||
return W | ||
|
||
|
||
def update_H(X, W, H): | ||
"""Update the weight matrix using the regular multiplicative updates""" | ||
m, n = np.shape(X) | ||
WtX = np.dot(W.T, X) | ||
WtW = np.dot(W.T, W) | ||
for j in range(10): | ||
H = H * WtX / (np.dot(WtW, H) + np.spacing(1)) | ||
|
||
return H | ||
|
||
|
||
def W_sparse_ith(W, HHt, cach, spar, i): | ||
""" Update the columns sequentially""" | ||
m, r = np.shape(W) | ||
C = cach[:, i] - W[:, i] * HHt[i, i] | ||
V = np.zeros(m) | ||
k = np.sqrt(m) - spar * (np.sqrt(m) - 1) | ||
a = sparse_opt(np.sort(-C)[::-1], k) | ||
ind = np.argsort(-C)[::-1] | ||
V[ind] = a | ||
cach = cach + np.outer(V - W[:, i], HHt[i, :]) | ||
W[:, i] = V | ||
return (W, cach) | ||
|
||
|
||
def sparse_opt(b, k): | ||
""" Project a vector onto a sparsity constraint | ||
|
||
Solves the projection problem by taking into account the | ||
symmetry of l1 and l2 constraints. | ||
|
||
Parameters | ||
--------- | ||
b : sorted vector in decreasing value | ||
k : Ratio of l1/l2 norms of a vector | ||
|
||
Returns | ||
------- | ||
z : closest vector satisfying the required sparsity constraint. | ||
|
||
""" | ||
n = len(b) | ||
sumb = np.cumsum(b) | ||
normb = np.cumsum(b * b) | ||
pnormb = np.arange(1, n + 1) * normb | ||
y = (pnormb - sumb * sumb) / (np.arange(1, n + 1) - k * k) | ||
bot = np.ceil(k * k) | ||
z = np.zeros(n) | ||
if bot > n: | ||
print 'Looks like the sparsity measure is not between 0 and 1\n' | ||
return | ||
obj = (-np.sqrt(y) * (np.arange(1, n + 1) + k) + sumb) / \ | ||
np.arange(1, n + 1) | ||
indx = np.argmax(obj[bot:n]) | ||
p = indx + bot - 1 | ||
p = min(p, n - 1) | ||
p = max(p, bot) | ||
lam = np.sqrt(y[p]) | ||
mue = -sumb[p] / (p + 1) + k / (p + 1) * lam | ||
z[:p + 1] = (b[:p + 1] + mue) / lam | ||
return z | ||
|
||
|
||
if __name__ == '__main__': | ||
r = 25 | ||
spar = 0.5 | ||
maxiter = 200 | ||
X = scipy.io.loadmat('../data/orlfaces.mat') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. try to simulate some simple cases where you even know the ground truth of original "bases" |
||
W = sparse_nmf(X['V'], r, maxiter, spar) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and that is the point of "unit-tests": to verify/guarantee that things work as they should. Next step would be to create There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see. Btw, I am not sure if we want to make it necessarily part of scikit-learn. On Sun, May 3, 2015 at 10:24 AM, Yaroslav Halchenko <
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually |
||
#import pylab as plt | ||
#for i in range(r): | ||
# plt.subplot(np.sqrt(r), np.sqrt(r), i + 1) | ||
# plt.imshow(np.reshape(W.T[i], [92, 112]).T) | ||
# plt.axis('off') | ||
# plt.ion() | ||
|
||
#plt.show(True) | ||
#import ipdb | ||
#ipdb.set_trace() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great. But since we are doing it within scikit-learn, better to make this
nmf
as a module under sklearn, right? so move it there, add also an__init__.py
file so Python recognizes it as a submoduleThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually better as
sklearn/decomposition/sparse_nmf.py
I guessThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, we will have implementations which work in Spark clusters as well as
GPU machines.
So, not sure if we want it in the sklearn branch.
For example:
https://github.com/arnlaugsson/sparkIt
Okay, will do the tests similar to the NMF function in scikits-learn.
On Sun, May 3, 2015 at 10:27 AM, Yaroslav Halchenko <
[email protected]> wrote: