238 lines
6.5 KiB
Python
238 lines
6.5 KiB
Python
"""
|
|
Extended math utilities.
|
|
"""
|
|
# Authors: G. Varoquaux, A. Gramfort, A. Passos, O. Grisel
|
|
# License: BSD
|
|
|
|
import math
|
|
|
|
from . import check_random_state
|
|
import numpy as np
|
|
|
|
from scipy import linalg
|
|
|
|
def norm(v):
|
|
v = np.asarray(v)
|
|
__nrm2, = linalg.get_blas_funcs(['nrm2'], [v])
|
|
return __nrm2(v)
|
|
|
|
def _fast_logdet(A):
|
|
"""
|
|
Compute log(det(A)) for A symmetric
|
|
Equivalent to : np.log(np.linalg.det(A))
|
|
but more robust
|
|
It returns -Inf if det(A) is non positive or is not defined.
|
|
"""
|
|
# XXX: Should be implemented as in numpy, using ATLAS
|
|
# http://projects.scipy.org/numpy/browser/trunk/numpy/linalg/linalg.py#L1559
|
|
ld = np.sum(np.log(np.diag(A)))
|
|
a = np.exp(ld / A.shape[0])
|
|
d = np.linalg.det(A / a)
|
|
ld += np.log(d)
|
|
if not np.isfinite(ld):
|
|
return -np.inf
|
|
return ld
|
|
|
|
|
|
def _fast_logdet_numpy(A):
|
|
"""
|
|
Compute log(det(A)) for A symmetric
|
|
Equivalent to : np.log(nl.det(A))
|
|
but more robust
|
|
It returns -Inf if det(A) is non positive or is not defined.
|
|
"""
|
|
sign, ld = np.linalg.slogdet(A)
|
|
if not sign > 0:
|
|
return -np.inf
|
|
return ld
|
|
|
|
|
|
# Numpy >= 1.5 provides a fast logdet
|
|
if hasattr(np.linalg, 'slogdet'):
|
|
fast_logdet = _fast_logdet_numpy
|
|
else:
|
|
fast_logdet = _fast_logdet
|
|
|
|
try:
|
|
factorial = math.factorial
|
|
except AttributeError:
|
|
# math.factorial is only available in Python >= 2.6
|
|
import operator
|
|
def factorial(x):
|
|
# don't use reduce operator or 2to3 will fail.
|
|
# ripped from http://www.joelbdalley.com/page.pl?38
|
|
# Ensure that n is a Natural number
|
|
n = abs(int(n))
|
|
if n < 1: n = 1
|
|
|
|
# Store n! in variable x
|
|
x = 1
|
|
|
|
# Compute n!
|
|
for i in range(1, n + 1):
|
|
x = i * x
|
|
|
|
# Return n!
|
|
return x
|
|
|
|
|
|
try:
|
|
import itertools
|
|
combinations = itertools.combinations
|
|
except AttributeError:
|
|
def combinations(seq, r=None):
|
|
"""Generator returning combinations of items from sequence <seq>
|
|
taken <r> at a time. Order is not significant. If <r> is not given,
|
|
the entire sequence is returned.
|
|
"""
|
|
if r == None:
|
|
r = len(seq)
|
|
if r <= 0:
|
|
yield []
|
|
else:
|
|
for i in xrange(len(seq)):
|
|
for cc in combinations(seq[i+1:], r-1):
|
|
yield [seq[i]]+cc
|
|
|
|
|
|
def density(w, **kwargs):
|
|
"""Compute density of a sparse vector
|
|
|
|
Return a value between 0 and 1
|
|
"""
|
|
d = 0 if w is None else float((w != 0).sum()) / w.size
|
|
return d
|
|
|
|
|
|
def safe_sparse_dot(a, b, dense_output=False):
|
|
"""Dot product that handle the sparse matrix case correctly"""
|
|
from scipy import sparse
|
|
if sparse.issparse(a) or sparse.issparse(b):
|
|
ret = a * b
|
|
if dense_output and hasattr(ret, "toarray"):
|
|
ret = ret.toarray()
|
|
return ret
|
|
else:
|
|
return np.dot(a,b)
|
|
|
|
|
|
def fast_svd(M, k, p=None, q=0, transpose='auto', random_state=0):
|
|
"""Computes the k-truncated randomized SVD
|
|
|
|
Parameters
|
|
===========
|
|
M: ndarray or sparse matrix
|
|
Matrix to decompose
|
|
|
|
k: int
|
|
Number of singular values and vectors to extract.
|
|
|
|
p: int (default is k)
|
|
Additional number of samples of the range of M to ensure proper
|
|
conditioning. See the notes below.
|
|
|
|
q: int (default is 0)
|
|
Number of power iterations (can be used to deal with very noisy
|
|
problems).
|
|
|
|
transpose: True, False or 'auto' (default)
|
|
Whether the algorithm should be applied to M.T instead of M. The
|
|
result should approximately be the same. The 'auto' mode will
|
|
trigger the transposition if M.shape[1] > M.shape[0] since this
|
|
implementation of randomized SVD tend to be a little faster in that
|
|
case).
|
|
|
|
random_state: RandomState or an int seed (0 by default)
|
|
A random number generator instance to make behavior
|
|
|
|
Notes
|
|
=====
|
|
This algorithm finds the exact truncated singular values decomposition
|
|
using randomization to speed up the computations. It is particularly
|
|
fast on large matrices on which you whish to extract only a small
|
|
number of components.
|
|
|
|
(k + p) should be strictly higher than the rank of M. This can be
|
|
checked by ensuring that the lowest extracted singular value is on
|
|
the order of the machine precision of floating points.
|
|
|
|
References
|
|
==========
|
|
Finding structure with randomness: Stochastic algorithms for constructing
|
|
approximate matrix decompositions
|
|
Halko, et al., 2009 (arXiv:909)
|
|
|
|
A randomized algorithm for the decomposition of matrices
|
|
Per-Gunnar Martinsson, Vladimir Rokhlin and Mark Tygert
|
|
"""
|
|
if p == None:
|
|
p = k
|
|
|
|
random_state = check_random_state(random_state)
|
|
n_samples, n_features = M.shape
|
|
|
|
if transpose == 'auto' and n_samples > n_features:
|
|
transpose = True
|
|
if transpose:
|
|
# this implementation is a bit faster with smaller shape[1]
|
|
M = M.T
|
|
|
|
# generating random gaussian vectors r with shape: (M.shape[1], k + p)
|
|
r = random_state.normal(size=(M.shape[1], k + p))
|
|
|
|
# sampling the range of M using by linear projection of r
|
|
Y = safe_sparse_dot(M, r)
|
|
del r
|
|
|
|
# apply q power iterations on Y to make to further 'imprint' the top
|
|
# singular values of M in Y
|
|
for i in xrange(q):
|
|
Y = safe_sparse_dot(M, safe_sparse_dot(M.T, Y))
|
|
|
|
# extracting an orthonormal basis of the M range samples
|
|
from .fixes import qr_economic
|
|
Q, R = qr_economic(Y)
|
|
del R
|
|
|
|
# project M to the (k + p) dimensional space using the basis vectors
|
|
B = safe_sparse_dot(Q.T, M)
|
|
|
|
# compute the SVD on the thin matrix: (k + p) wide
|
|
from scipy import linalg
|
|
Uhat, s, V = linalg.svd(B, full_matrices=False)
|
|
del B
|
|
U = np.dot(Q, Uhat)
|
|
|
|
if transpose:
|
|
# transpose back the results according to the input convention
|
|
return V[:k, :].T, s[:k], U[:, :k].T
|
|
else:
|
|
return U[:, :k], s[:k], V[:k, :]
|
|
|
|
|
|
def logsum(arr, axis=0):
|
|
""" Computes the sum of arr assuming arr is in the log domain.
|
|
|
|
Returns log(sum(exp(arr))) while minimizing the possibility of
|
|
over/underflow.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> import numpy as np
|
|
>>> from sklearn.utils.extmath import logsum
|
|
>>> a = np.arange(10)
|
|
>>> np.log(np.sum(np.exp(a)))
|
|
9.4586297444267107
|
|
>>> logsum(a)
|
|
9.4586297444267107
|
|
"""
|
|
arr = np.rollaxis(arr, axis)
|
|
# Use the max to normalize, as with the log this is what accumulates
|
|
# the less errors
|
|
vmax = arr.max(axis=0)
|
|
out = np.log(np.sum(np.exp(arr - vmax), axis=0))
|
|
out += vmax
|
|
return out
|
|
|