mi@0
|
1 #!/usr/bin/python
|
mi@0
|
2 #
|
mi@0
|
3 # Copyright (C) Christian Thurau, 2010.
|
mi@0
|
4 # Licensed under the GNU General Public License (GPL).
|
mi@0
|
5 # http://www.gnu.org/licenses/gpl.txt
|
mi@0
|
6 """
|
mi@0
|
7 PyMF Singular Value Decomposition.
|
mi@0
|
8
|
mi@0
|
9 SVD : Class for Singular Value Decomposition
|
mi@0
|
10 pinv() : Compute the pseudoinverse of a Matrix
|
mi@0
|
11
|
mi@0
|
12 """
|
mi@0
|
13
|
mi@0
|
14
|
mi@0
|
15
|
mi@0
|
16 from numpy.linalg import eigh
|
mi@0
|
17 import scipy.sparse
|
mi@0
|
18
|
mi@0
|
19 try:
|
mi@0
|
20 import scipy.sparse.linalg.eigen.arpack as linalg
|
mi@0
|
21 except (ImportError, AttributeError):
|
mi@0
|
22 import scipy.sparse.linalg as linalg
|
mi@0
|
23
|
mi@0
|
24
|
mi@0
|
25 import numpy as np
|
mi@0
|
26
|
mi@0
|
27 def pinv(A, k=-1, eps=10**-8):
|
mi@0
|
28 # Compute Pseudoinverse of a matrix
|
mi@0
|
29 # calculate SVD
|
mi@0
|
30 svd_mdl = SVD(A, k=k)
|
mi@0
|
31 svd_mdl.factorize()
|
mi@0
|
32
|
mi@0
|
33 S = svd_mdl.S
|
mi@0
|
34 Sdiag = S.diagonal()
|
mi@0
|
35 Sdiag = np.where(Sdiag >eps, 1.0/Sdiag, 0.0)
|
mi@0
|
36
|
mi@0
|
37 for i in range(S.shape[0]):
|
mi@0
|
38 S[i,i] = Sdiag[i]
|
mi@0
|
39
|
mi@0
|
40 if scipy.sparse.issparse(A):
|
mi@0
|
41 A_p = svd_mdl.V.T * (S * svd_mdl.U.T)
|
mi@0
|
42 else:
|
mi@0
|
43 A_p = np.dot(svd_mdl.V.T, np.core.multiply(np.diag(S)[:,np.newaxis], svd_mdl.U.T))
|
mi@0
|
44
|
mi@0
|
45 return A_p
|
mi@0
|
46
|
mi@0
|
47
|
mi@0
|
48 class SVD():
|
mi@0
|
49 """
|
mi@0
|
50 SVD(data, show_progress=False)
|
mi@0
|
51
|
mi@0
|
52
|
mi@0
|
53 Singular Value Decomposition. Factorize a data matrix into three matrices s.t.
|
mi@0
|
54 F = | data - USV| is minimal. U and V correspond to eigenvectors of the matrices
|
mi@0
|
55 data*data.T and data.T*data.
|
mi@0
|
56
|
mi@0
|
57 Parameters
|
mi@0
|
58 ----------
|
mi@0
|
59 data : array_like [data_dimension x num_samples]
|
mi@0
|
60 the input data
|
mi@0
|
61
|
mi@0
|
62 Attributes
|
mi@0
|
63 ----------
|
mi@0
|
64 U,S,V : submatrices s.t. data = USV
|
mi@0
|
65
|
mi@0
|
66 Example
|
mi@0
|
67 -------
|
mi@0
|
68 >>> import numpy as np
|
mi@0
|
69 >>> data = np.array([[1.0, 0.0, 2.0], [0.0, 1.0, 1.0]])
|
mi@0
|
70 >>> svd_mdl = SVD(data, show_progress=False)
|
mi@0
|
71 >>> svd_mdl.factorize()
|
mi@0
|
72 """
|
mi@0
|
73
|
mi@0
|
74 _EPS=10**-8
|
mi@0
|
75
|
mi@0
|
76 def __init__(self, data, k=-1, rrank=0, crank=0):
|
mi@0
|
77 self.data = data
|
mi@0
|
78 (self._rows, self._cols) = self.data.shape
|
mi@0
|
79 if rrank > 0:
|
mi@0
|
80 self._rrank = rrank
|
mi@0
|
81 else:
|
mi@0
|
82 self._rrank = self._rows
|
mi@0
|
83
|
mi@0
|
84 if crank > 0:
|
mi@0
|
85 self._crank = crank
|
mi@0
|
86 else:
|
mi@0
|
87 self._crank = self._cols
|
mi@0
|
88
|
mi@0
|
89 # set the rank to either rrank or crank
|
mi@0
|
90 self._k = k
|
mi@0
|
91
|
mi@0
|
92 def frobenius_norm(self):
|
mi@0
|
93 """ Frobenius norm (||data - USV||) for a data matrix and a low rank
|
mi@0
|
94 approximation given by SVH using rank k for U and V
|
mi@0
|
95
|
mi@0
|
96 Returns:
|
mi@0
|
97 frobenius norm: F = ||data - USV||
|
mi@0
|
98 """
|
mi@0
|
99 if scipy.sparse.issparse(self.data):
|
mi@0
|
100 err = self.data - self.U*self.S*self.V
|
mi@0
|
101 err = err.multiply(err)
|
mi@0
|
102 err = np.sqrt(err.sum())
|
mi@0
|
103 else:
|
mi@0
|
104 err = self.data[:,:] - np.dot(np.dot(self.U, self.S), self.V)
|
mi@0
|
105 err = np.sqrt(np.sum(err**2))
|
mi@0
|
106
|
mi@0
|
107 return err
|
mi@0
|
108
|
mi@0
|
109
|
mi@0
|
110 def factorize(self):
|
mi@0
|
111 def _right_svd():
|
mi@0
|
112 AA = np.dot(self.data[:,:], self.data[:,:].T)
|
mi@0
|
113 values, u_vectors = eigh(AA)
|
mi@0
|
114
|
mi@0
|
115 # get rid of too low eigenvalues
|
mi@0
|
116 u_vectors = u_vectors[:, values > self._EPS]
|
mi@0
|
117 values = values[values > self._EPS]
|
mi@0
|
118
|
mi@0
|
119 # sort eigenvectors according to largest value
|
mi@0
|
120 idx = np.argsort(values)
|
mi@0
|
121 values = values[idx[::-1]]
|
mi@0
|
122
|
mi@0
|
123 # argsort sorts in ascending order -> access is backwards
|
mi@0
|
124 self.U = u_vectors[:,idx[::-1]]
|
mi@0
|
125
|
mi@0
|
126 # compute S
|
mi@0
|
127 self.S = np.diag(np.sqrt(values))
|
mi@0
|
128
|
mi@0
|
129 # and the inverse of it
|
mi@0
|
130 S_inv = np.diag(np.sqrt(values)**-1)
|
mi@0
|
131
|
mi@0
|
132 # compute V from it
|
mi@0
|
133 self.V = np.dot(S_inv, np.dot(self.U[:,:].T, self.data[:,:]))
|
mi@0
|
134
|
mi@0
|
135
|
mi@0
|
136 def _left_svd():
|
mi@0
|
137 AA = np.dot(self.data[:,:].T, self.data[:,:])
|
mi@0
|
138 values, v_vectors = eigh(AA)
|
mi@0
|
139
|
mi@0
|
140 # get rid of too low eigenvalues
|
mi@0
|
141 v_vectors = v_vectors[:, values > self._EPS]
|
mi@0
|
142 values = values[values > self._EPS]
|
mi@0
|
143
|
mi@0
|
144 # sort eigenvectors according to largest value
|
mi@0
|
145 # argsort sorts in ascending order -> access is backwards
|
mi@0
|
146 idx = np.argsort(values)[::-1]
|
mi@0
|
147 values = values[idx]
|
mi@0
|
148
|
mi@0
|
149 # compute S
|
mi@0
|
150 self.S= np.diag(np.sqrt(values))
|
mi@0
|
151
|
mi@0
|
152 # and the inverse of it
|
mi@0
|
153 S_inv = np.diag(1.0/np.sqrt(values))
|
mi@0
|
154
|
mi@0
|
155 Vtmp = v_vectors[:,idx]
|
mi@0
|
156
|
mi@0
|
157 self.U = np.dot(np.dot(self.data[:,:], Vtmp), S_inv)
|
mi@0
|
158 self.V = Vtmp.T
|
mi@0
|
159
|
mi@0
|
160 def _sparse_right_svd():
|
mi@0
|
161 ## for some reasons arpack does not allow computation of rank(A) eigenvectors (??) #
|
mi@0
|
162 AA = self.data*self.data.transpose()
|
mi@0
|
163 if self.data.shape[0] > 1:
|
mi@0
|
164 # do not compute full rank if desired
|
mi@0
|
165 if self._k > 0 and self._k < self.data.shape[0]-1:
|
mi@0
|
166 k = self._k
|
mi@0
|
167 else:
|
mi@0
|
168 k = self.data.shape[0]-1
|
mi@0
|
169
|
mi@0
|
170 values, u_vectors = linalg.eigen_symmetric(AA,k=k)
|
mi@0
|
171 else:
|
mi@0
|
172 values, u_vectors = eigh(AA.todense())
|
mi@0
|
173
|
mi@0
|
174 # get rid of too low eigenvalues
|
mi@0
|
175 u_vectors = u_vectors[:, values > self._EPS]
|
mi@0
|
176 values = values[values > self._EPS]
|
mi@0
|
177
|
mi@0
|
178 # sort eigenvectors according to largest value
|
mi@0
|
179 idx = np.argsort(values)
|
mi@0
|
180 values = values[idx[::-1]]
|
mi@0
|
181
|
mi@0
|
182 # argsort sorts in ascending order -> access is backwards
|
mi@0
|
183 self.U = scipy.sparse.csc_matrix(u_vectors[:,idx[::-1]])
|
mi@0
|
184
|
mi@0
|
185 # compute S
|
mi@0
|
186 self.S = scipy.sparse.csc_matrix(np.diag(np.sqrt(values)))
|
mi@0
|
187
|
mi@0
|
188 # and the inverse of it
|
mi@0
|
189 S_inv = scipy.sparse.csc_matrix(np.diag(1.0/np.sqrt(values)))
|
mi@0
|
190
|
mi@0
|
191 # compute V from it
|
mi@0
|
192 self.V = self.U.transpose() * self.data
|
mi@0
|
193 self.V = S_inv * self.V
|
mi@0
|
194
|
mi@0
|
195 def _sparse_left_svd():
|
mi@0
|
196 # for some reasons arpack does not allow computation of rank(A) eigenvectors (??)
|
mi@0
|
197 AA = self.data.transpose()*self.data
|
mi@0
|
198
|
mi@0
|
199 if self.data.shape[1] > 1:
|
mi@0
|
200 # do not compute full rank if desired
|
mi@0
|
201 if self._k > 0 and self._k < self.data.shape[1]-1:
|
mi@0
|
202 k = self._k
|
mi@0
|
203 else:
|
mi@0
|
204 k = self.data.shape[1]-1
|
mi@0
|
205 values, v_vectors = linalg.eigen_symmetric(AA,k=k)
|
mi@0
|
206 else:
|
mi@0
|
207 values, v_vectors = eigh(AA.todense())
|
mi@0
|
208 # get rid of too low eigenvalues
|
mi@0
|
209 v_vectors = v_vectors[:, values > self._EPS]
|
mi@0
|
210 values = values[values > self._EPS]
|
mi@0
|
211
|
mi@0
|
212 # sort eigenvectors according to largest value
|
mi@0
|
213 idx = np.argsort(values)
|
mi@0
|
214 values = values[idx[::-1]]
|
mi@0
|
215
|
mi@0
|
216 # argsort sorts in ascending order -> access is backwards
|
mi@0
|
217 self.V = scipy.sparse.csc_matrix(v_vectors[:,idx[::-1]])
|
mi@0
|
218
|
mi@0
|
219 # compute S
|
mi@0
|
220 self.S = scipy.sparse.csc_matrix(np.diag(np.sqrt(values)))
|
mi@0
|
221
|
mi@0
|
222 # and the inverse of it
|
mi@0
|
223 S_inv = scipy.sparse.csc_matrix(np.diag(1.0/np.sqrt(values)))
|
mi@0
|
224
|
mi@0
|
225 self.U = self.data * self.V * S_inv
|
mi@0
|
226 self.V = self.V.transpose()
|
mi@0
|
227
|
mi@0
|
228
|
mi@0
|
229 if self._rows > self._cols:
|
mi@0
|
230 if scipy.sparse.issparse(self.data):
|
mi@0
|
231 _sparse_left_svd()
|
mi@0
|
232 else:
|
mi@0
|
233 _left_svd()
|
mi@0
|
234 else:
|
mi@0
|
235 if scipy.sparse.issparse(self.data):
|
mi@0
|
236 _sparse_right_svd()
|
mi@0
|
237 else:
|
mi@0
|
238 _right_svd()
|
mi@0
|
239
|
mi@0
|
240 if __name__ == "__main__":
|
mi@0
|
241 import doctest
|
mi@0
|
242 doctest.testmod()
|