Skip to content
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

Online NMF #2007

Merged
merged 161 commits into from
Jan 17, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
161 commits
Select commit Hold shift + click to select a range
343e46f
Implement first version of the algorithm
anotherbugmaster Mar 29, 2018
3171be3
Fix variable names
anotherbugmaster Mar 30, 2018
bd325bc
Add support for streaming corpora
anotherbugmaster Apr 2, 2018
19b3ba4
Add benchmark
anotherbugmaster Apr 2, 2018
9e52399
Fix bugs, introduce batches, add images to the benchmark notebook
anotherbugmaster Apr 15, 2018
c54fc92
Update notebook
anotherbugmaster Apr 22, 2018
6dc9d3e
Improve model
anotherbugmaster Apr 22, 2018
0554b7b
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Apr 22, 2018
5f4b3d3
Add show topics, change API
anotherbugmaster Apr 23, 2018
52fc956
Add more LDA-like API
anotherbugmaster Apr 23, 2018
ddebcf0
Fix logger name
anotherbugmaster Apr 23, 2018
6d0a1b3
Add more LDA API
anotherbugmaster Apr 23, 2018
cf430fc
Remove redundant method
anotherbugmaster Apr 23, 2018
df5a6e9
Remove commented out lines
anotherbugmaster Apr 23, 2018
25080b4
Fix flakes
anotherbugmaster Apr 23, 2018
83b1a6b
Cythonize
anotherbugmaster May 2, 2018
7f27f52
Dramatically improve performance
anotherbugmaster May 22, 2018
405e12f
Add parameters, improve accuracy and speed
anotherbugmaster Jun 2, 2018
7b45b23
Remove redundant W copying
anotherbugmaster Jun 5, 2018
a154a6e
Fix random seed again
anotherbugmaster Jun 5, 2018
e82628d
Optimize E/M step
anotherbugmaster Jun 12, 2018
1ca33f8
Add an eval_every option, use softmax for normalization
anotherbugmaster Jun 13, 2018
f19e6ce
Fixes
anotherbugmaster Jun 13, 2018
583cb15
Improve notebook examples a bit
anotherbugmaster Jun 13, 2018
fe0ab0a
Fix eval_every
anotherbugmaster Jun 13, 2018
8e647a1
Return outliers
anotherbugmaster Jun 16, 2018
89cc803
Optimizations
anotherbugmaster Jun 16, 2018
bbd3099
Experimenting with loss
anotherbugmaster Jun 16, 2018
f71ad89
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Aug 14, 2018
936e629
Fix PEP8
anotherbugmaster Aug 14, 2018
1c3a064
Return nmf import
anotherbugmaster Aug 14, 2018
ce4b7ee
Revert "Return nmf import"
anotherbugmaster Aug 20, 2018
f8de1d9
Fix
anotherbugmaster Aug 27, 2018
df9b8c7
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Aug 27, 2018
d159779
Fix minimum_probability & info -> debug logs
anotherbugmaster Aug 27, 2018
3dcdedc
Compute metrics
anotherbugmaster Aug 27, 2018
f11f2e2
Count error on-the-fly
anotherbugmaster Aug 28, 2018
8216541
Speed optimizations, changed error functions
anotherbugmaster Aug 28, 2018
ee3a7c7
Beat LDA
anotherbugmaster Aug 28, 2018
a3315f2
Outperform sklearn in speed (WTF)
anotherbugmaster Aug 28, 2018
3a03ff9
Remove redundant arg
anotherbugmaster Aug 28, 2018
70619e1
Add Olivietti faces
anotherbugmaster Aug 28, 2018
8c47ce0
Remove redundant code
anotherbugmaster Aug 28, 2018
e291664
Add Topics
anotherbugmaster Aug 28, 2018
3302b92
Make it pretty
anotherbugmaster Aug 28, 2018
5616bd6
Fix wrapper
anotherbugmaster Aug 28, 2018
ed8f29f
Save corpus & dict, minor fixes
anotherbugmaster Aug 30, 2018
2117c90
Add RandomCorpus
anotherbugmaster Aug 31, 2018
950115d
Dense -> sparse
anotherbugmaster Aug 31, 2018
54993c6
First doc2dense
anotherbugmaster Aug 31, 2018
572dc6c
Fix csc again
anotherbugmaster Aug 31, 2018
d40d89f
Fix len
anotherbugmaster Aug 31, 2018
7a3ef47
Experimenting
anotherbugmaster Sep 12, 2018
f94de09
Revert "Experimenting"
anotherbugmaster Sep 12, 2018
9ed2167
Fix evaluation
anotherbugmaster Sep 12, 2018
ad9443f
Sparse speedup
anotherbugmaster Sep 23, 2018
1a04660
Improve performance
anotherbugmaster Sep 25, 2018
87981bf
Divide A and B again
anotherbugmaster Sep 25, 2018
0b314c7
Fix A and B computation bug
anotherbugmaster Sep 25, 2018
b024dd6
Sparsify W init
anotherbugmaster Sep 25, 2018
35d5406
Experimenting
anotherbugmaster Sep 25, 2018
74acb37
New norm
anotherbugmaster Sep 25, 2018
8b28675
Sparse threshold -> sparse coefficient
anotherbugmaster Sep 25, 2018
588ef6a
Optimize residuals computation
anotherbugmaster Sep 26, 2018
8f84758
Fix residuals bug
anotherbugmaster Sep 26, 2018
8a67c44
W speedup
anotherbugmaster Sep 26, 2018
560f2bf
Experiment
anotherbugmaster Sep 26, 2018
cac2590
Revert changes a bit
anotherbugmaster Sep 26, 2018
060ab28
Fix corpus
anotherbugmaster Sep 26, 2018
cde937f
Fix init error|
anotherbugmaster Sep 26, 2018
66b753f
Merge branch 'online_nmf' of github.com:anotherbugmaster/gensim into …
anotherbugmaster Sep 26, 2018
18dbb6b
Resolve conflict
anotherbugmaster Sep 26, 2018
4b49d26
Fix corpus iteration issue
anotherbugmaster Sep 26, 2018
9c6cbc6
Switch to numpy algos
anotherbugmaster Oct 7, 2018
b23d016
Merge upstream
anotherbugmaster Oct 7, 2018
74ba37d
Train on wikipedia
anotherbugmaster Oct 7, 2018
c943264
Sparse coef -> density. More stable way to sparsify W matrix
anotherbugmaster Oct 9, 2018
a489807
Merge branch 'online_nmf' of github.com:anotherbugmaster/gensim into …
anotherbugmaster Oct 9, 2018
a95e345
Return old sparse algo
anotherbugmaster Oct 9, 2018
0f90484
Max
anotherbugmaster Oct 9, 2018
6ae43e4
Optimizations
anotherbugmaster Oct 10, 2018
335170b
Fix A and B computation
anotherbugmaster Oct 10, 2018
4cc8f1b
Fix A and B normalization
anotherbugmaster Oct 10, 2018
5c6fe60
Add random_state
anotherbugmaster Oct 23, 2018
dd459a2
Infer id2word
anotherbugmaster Oct 23, 2018
5121d85
Fix tests
anotherbugmaster Nov 6, 2018
5f4018a
Document __init__
anotherbugmaster Nov 14, 2018
dbd8474
Document whole nmf
anotherbugmaster Nov 14, 2018
5904f10
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Nov 14, 2018
cd4b9b0
Remove unnecessary comments
anotherbugmaster Nov 14, 2018
53a02a9
Add tutorial notebook
anotherbugmaster Nov 14, 2018
937e340
Document __init__
anotherbugmaster Nov 20, 2018
26a87bd
Fix flake version
anotherbugmaster Nov 28, 2018
261c13a
Fix flake warning
anotherbugmaster Nov 28, 2018
0147afc
Remove comments, reverse parallelization order
anotherbugmaster Nov 28, 2018
1ece3c1
Add NMF's cython extension to setup.py
anotherbugmaster Nov 28, 2018
e6409fa
Fix imports, add solve_r function
anotherbugmaster Nov 28, 2018
0743624
Remove comments
anotherbugmaster Nov 28, 2018
fd8088b
Add docstrings
anotherbugmaster Nov 28, 2018
e4ba0de
Common corpus and common dictionary
anotherbugmaster Nov 28, 2018
8537eef
Remove redundant test
anotherbugmaster Nov 28, 2018
d2e8385
Add signature flag
anotherbugmaster Nov 28, 2018
b72bf39
Add files to manifest
anotherbugmaster Nov 28, 2018
ed080a3
Fix flake8
anotherbugmaster Nov 29, 2018
67f6e75
Fix atol value
anotherbugmaster Nov 29, 2018
ee4373d
Implement top topics
anotherbugmaster Nov 29, 2018
d01c88c
Add rst files
anotherbugmaster Dec 10, 2018
8111080
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Dec 11, 2018
3de3646
Fix appveyor issue
anotherbugmaster Dec 11, 2018
183ea2d
Fix cython error
anotherbugmaster Dec 11, 2018
d2ac199
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Dec 12, 2018
2d664c6
Fix fmax/fmin not being on win-python27
anotherbugmaster Dec 12, 2018
c9a3577
Add word transformation test
anotherbugmaster Dec 12, 2018
fd0de20
Improve readability of residuals computation
anotherbugmaster Dec 21, 2018
fa384f2
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Dec 21, 2018
a811c67
Fix tests
anotherbugmaster Dec 21, 2018
d063a4f
A few fixes
anotherbugmaster Dec 21, 2018
b8f5d79
Blank line at the end of each docstring
anotherbugmaster Dec 21, 2018
361d160
Add blank line
anotherbugmaster Dec 21, 2018
e214582
Add the paper reference
anotherbugmaster Dec 21, 2018
9527f39
Fix long line
anotherbugmaster Dec 21, 2018
e1e1168
Add log_perplexity
anotherbugmaster Dec 30, 2018
3bf5be3
Merge remote-tracking branch 'remotes/upstream/develop' into online_nmf
anotherbugmaster Jan 7, 2019
d1c6e3e
Add NMF and LDA comparison table
anotherbugmaster Jan 9, 2019
7927b6b
Change the sign of log perplexity
anotherbugmaster Jan 9, 2019
1c6517e
Add Sklearn NMF comparison
anotherbugmaster Jan 9, 2019
278fb05
Merge sklearn and tm tables
anotherbugmaster Jan 9, 2019
a330327
Add F1
anotherbugmaster Jan 10, 2019
7ba9b84
Remove _solve_r
anotherbugmaster Jan 10, 2019
a14bfd3
Merge tutorial and benchmark
anotherbugmaster Jan 10, 2019
d28aef3
Identation's back
anotherbugmaster Jan 10, 2019
83ec0f6
Optimize optimizers
anotherbugmaster Jan 10, 2019
d25332f
Remove unnecessary pic
anotherbugmaster Jan 10, 2019
0e711d9
Optimize memory consumption
anotherbugmaster Jan 10, 2019
cc3085c
Add docstring
anotherbugmaster Jan 10, 2019
b090b6b
Optimize get_topic_words
anotherbugmaster Jan 10, 2019
e05a1c6
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Jan 10, 2019
ba8ce1c
Fix tests
anotherbugmaster Jan 10, 2019
6d78f83
Fix flake8
anotherbugmaster Jan 10, 2019
b16c1dd
Add missing test
anotherbugmaster Jan 11, 2019
7c1e240
Code review fixes
anotherbugmaster Jan 11, 2019
667ae99
n_tokens -> num_tokens
anotherbugmaster Jan 11, 2019
251d5f9
[skip ci] Add explicit normalize parameter
anotherbugmaster Jan 11, 2019
7a3f358
[skip ci] Add explicit normalize parameter[2]
anotherbugmaster Jan 11, 2019
c663f33
[skip ci] Update tutorial notebook
anotherbugmaster Jan 11, 2019
8e15cd4
[skip ci] [WIP] Update wikipedia notebook
anotherbugmaster Jan 11, 2019
3c76171
Merge branch 'online_nmf' of github.com:anotherbugmaster/gensim into …
anotherbugmaster Jan 15, 2019
4941745
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Jan 15, 2019
c4d6ebd
Add more description and metrics
anotherbugmaster Jan 15, 2019
3b1195d
[skip ci] Fix log_probabiliy
anotherbugmaster Jan 15, 2019
5edec1b
Multiple format fixes in notebook, outputs cleared til tomorrow
anotherbugmaster Jan 15, 2019
33ce1a3
Merge remote-tracking branch 'upstream/develop' into online_nmf
menshikh-iv Jan 16, 2019
1806bf6
Train on full corpus
anotherbugmaster Jan 16, 2019
3b9b8ea
Merge branch 'online_nmf' of github.com:anotherbugmaster/gensim into …
anotherbugmaster Jan 16, 2019
3f1af1d
[skip ci] Remove disclaimer
anotherbugmaster Jan 16, 2019
38143a9
Add RAM usage stats
anotherbugmaster Jan 16, 2019
72a02db
Native 20-newsgroups and additional text
anotherbugmaster Jan 16, 2019
7cf80e1
Truncate outputs
anotherbugmaster Jan 17, 2019
72178c0
Merge remote-tracking branch 'upstream/develop' into online_nmf
anotherbugmaster Jan 17, 2019
467a2ad
Fix last cell formatting
anotherbugmaster Jan 17, 2019
e34b939
[skip ci] Change model hyperparameters back
anotherbugmaster Jan 17, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions docs/notebooks/nmf_benchmark.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Re-run notebook (I see KeyboardInterrupt in some training)
  • More description (not only headers), links to referred notebooks (sklearn), what we do here and why

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Comparison between sklearn's and gensim's implementations of NMF"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from gensim.models.nmf import NMF as GensimNmf\n",
"from gensim.parsing.preprocessing import preprocess_documents\n",
"from sklearn.decomposition.nmf import NMF as SklearnNmf\n",
"from sklearn.datasets import fetch_20newsgroups\n",
"from sklearn.feature_extraction.text import CountVectorizer\n",
"import numpy as np\n",
"from matplotlib import pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"vectorizer = CountVectorizer()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"bow_matrix = vectorizer.fit_transform(fetch_20newsgroups().data)\n",
"bow_matrix = bow_matrix.todense()[:100]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sklearn NMF"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 54.4 s, sys: 38 s, total: 1min 32s\n",
"Wall time: 1min 29s\n"
]
}
],
"source": [
"%%time\n",
"\n",
"sklearn_nmf = SklearnNmf(n_components=5, tol=1e-5, max_iter=int(1e9))\n",
"\n",
"W = sklearn_nmf.fit_transform(bow_matrix)\n",
"H = sklearn_nmf.components_"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"184.40183405328017"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"np.linalg.norm(bow_matrix - W.dot(H), 'fro')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Gensim NMF"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 2min 7s, sys: 8.22 s, total: 2min 15s\n",
"Wall time: 2min 5s\n"
]
}
],
"source": [
"%%time\n",
"\n",
"gensim_nmf = GensimNmf(n_components=5)\n",
"\n",
"n_samples = np.array(bow_matrix).shape[0]\n",
"\n",
"gensim_nmf.fit(np.array(bow_matrix))\n",
"W, H = gensim_nmf.get_factor_matrices()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"353.4495647218574"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"np.linalg.norm(bow_matrix - W.dot(H), 'fro')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
193 changes: 193 additions & 0 deletions gensim/models/nmf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
from itertools import chain

import numpy as np
from scipy.stats import halfnorm


class NMF(object):
anotherbugmaster marked this conversation as resolved.
Show resolved Hide resolved
"""Online Non-Negative Matrix Factorization.

Attributes
----------
_W : matrix

"""

def __init__(self, n_components, lambda_=1., kappa=1.):
anotherbugmaster marked this conversation as resolved.
Show resolved Hide resolved
"""

Parameters
anotherbugmaster marked this conversation as resolved.
Show resolved Hide resolved
----------
n_components : int
Number of components in resulting matrices.
lambda_ : float
kappa : float
"""
anotherbugmaster marked this conversation as resolved.
Show resolved Hide resolved
self.n_features = None
self.n_components = n_components
self.lambda_ = lambda_
self.kappa = kappa
self._H = []
self.R = None
self.is_fitted = False

def _setup(self, X):
self.h, self.r = None, None
X_ = iter(X)
x = next(X_)
n_features = len(x)
avg = np.sqrt(x.mean() / n_features)
X = chain([x], X_)

self.n_features = n_features

self._W = np.abs(avg * halfnorm.rvs(size=(self.n_features, self.n_components)) /
anotherbugmaster marked this conversation as resolved.
Show resolved Hide resolved
np.sqrt(self.n_components))

self.A = np.zeros((self.n_components, self.n_components))
self.B = np.zeros((self.n_features, self.n_components))
return X

def fit(self, X, batch_size=None):
"""

Parameters
----------
X : matrix or iterator
anotherbugmaster marked this conversation as resolved.
Show resolved Hide resolved
Matrix to factorize.
batch_size : int or None
If None than batch_size equals 1 sample.
"""
if self.n_features is None:
X = self._setup(X)

prod = np.outer
if batch_size is not None:
prod = np.dot
length = X.shape[0]
n_batches = max(length // batch_size, 1)
X = np.array_split(X, n_batches, axis=0)
r, h = self.r, self.h
for v in X:
h, r = self._solveproj(v, self._W, self.lambda_, self.kappa, r=r, h=h)
self._H.append(h)
if self.R is not None:
self.R.append(r)

self.A += prod(h, h.T)
self.B += prod((v.T - r), h.T)
self._solve_w()
self.r = r
self.h = h

self.is_fitted = True

def _solve_w(self):
eta = self.kappa / np.linalg.norm(self.A, 'fro')
n = 0
lasttwo = np.zeros(2)
while n <= 2 or (np.abs(
(lasttwo[1] - lasttwo[0]) / lasttwo[0]) > 1e-5 and n < 1e9):
self._W -= eta * (np.dot(self._W, self.A) - self.B)
self._W = self._transform(self._W)
n += 1
lasttwo[0] = lasttwo[1]
lasttwo[1] = 0.5 * np.trace(self._W.T.dot(self._W).dot(self.A)) - \
np.trace(self._W.T.dot(self.B))

def transform(self, X, return_r=False):
H = []
if return_r:
R = []

num = None
W = self._W
lambda_ = self.lambda_
kappa = self.kappa
for v in X:
h, r = self._solveproj(v, W, lambda_, kappa, v_max=np.inf)
H.append(h.copy())
if return_r:
R.append(r.copy())

H = np.stack(H, axis=-1)
if return_r:
return H, np.stack(R, axis=-1)
else:
return H

def get_factor_matrices(self):
if len(self._H) > 0:
if len(self._H[0].shape) == 1:
H = np.stack(self._H, axis=-1)
else:
H = np.concatenate(self._H, axis=1)
return self._W, H
else:
return self._W, 0
anotherbugmaster marked this conversation as resolved.
Show resolved Hide resolved

@staticmethod
def _thresh(X, lambda_, v_max):
res = np.abs(X) - lambda_
np.maximum(res, 0.0, out=res)
res *= np.sign(X)
np.clip(res, -v_max, v_max, out=res)
return res

@staticmethod
def _mrdivide(B, A):
"""Solve xB = A
"""
if len(B.shape) == 2 and B.shape[0] == B.shape[1]:
return np.linalg.solve(B.T, A.T).T
else:
return np.linalg.lstsq(A.T, B.T, rcond=None)[0].T

def _transform(self, W):
newW = W.copy()
np.maximum(newW, 0, out=newW)
sumsq = np.sqrt(np.sum(W ** 2, axis=0))
np.maximum(sumsq, 1, out=sumsq)
return self._mrdivide(newW, np.diag(sumsq))

def _solveproj(self, v, W, lambda_, kappa=1, h=None, r=None, v_max=None, max_iter=1e9):
m, n = W.shape
v = v.T
if v_max is None:
v_max = v.max()
if len(v.shape) == 2:
batch_size = v.shape[1]
rshape = (m, batch_size)
hshape = (n, batch_size)
else:
rshape = m,
hshape = n,
if h is None or h.shape != hshape:
h = np.zeros(hshape)

if r is None or r.shape != rshape:
r = np.zeros(rshape)

eta = kappa / np.linalg.norm(W, 'fro') ** 2

iters = 0

while True:
iters += 1
# Solve for h
htmp = h
h = h - eta * np.dot(W.T, np.dot(W, h) + r - v)
np.maximum(h, 0.0, out=h)

# Solve for r
rtmp = r
r = self._thresh(v - np.dot(W, h), lambda_, v_max)

# Stop conditions
stoph = np.linalg.norm(h - htmp, 2)
stopr = np.linalg.norm(r - rtmp, 2)
stop = max(stoph, stopr) / m
if stop < 1e-5 or iters > max_iter:
break

return h, r