-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSVD.py
131 lines (93 loc) · 3.48 KB
/
SVD.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import numpy as np
import scipy as sp
from eigenvalues import eigs
from bidiagonalize import bidiagonalize
class SVD:
def __init__(self, n_components=None, atol=1e-8):
self.n_components = n_components
self.atol = atol
def fit(self, X, y=None):
return self
def transform(self, X):
U, Sigma, V = self.__compute_svd(X)
U, Sigma, V = self.fix_signs(U, Sigma, V)
U, Sigma, V = self.sort_singular_values(U, Sigma, V)
if self.n_components:
U = U[:, :self.n_components]
Sigma = Sigma[:self.n_components, :self.n_components]
V = V[:, :self.n_components]
if self.atol:
U[np.abs(U) < self.atol] = 0
V[np.abs(V) < self.atol] = 0
Sigma[np.abs(Sigma) < self.atol] = 0
return U, Sigma, V
def fit_transform(self, X, y=None):
self.fit(X)
return self.transform(X)
def __compute_svd(self, A):
m, n = A.shape
if (m < n):
raise ValueError("Matrix must respect shape s.t m >= n, shape (m={m}, n={n}) was given.")
B, _, H = bidiagonalize(A)
#eigvals, Q_tilde = sp.linalg.eig(B.T @ B)
evals, V_tilde = eigs(B.T @ B) # Symmetric eigen-decomposition
Q = H @ V_tilde
C = A @ Q
U, R, P = sp.linalg.qr(a=C, pivoting=True)
Sigma = R
V = Q[:, P]
return U, Sigma, V
def fix_signs(self,U, Sigma, V):
# Ammettiamo che Sigma sia di shape (r, r), con r = min(m, n)
r = min(Sigma.shape[0],Sigma.shape[1])
for i in range(r):
if Sigma[i, i] < 0:
Sigma[i, i] = -Sigma[i, i] # Rendo positivo
#U[:, i] = -U[:, i] # Cambio segno alla colonna i di U
V[:, i] = -V[:, i] #(invece di U[:, i], ma non entrambe!)
return U, Sigma, V
def sort_singular_values(self, U, Sigma, V):
# prendi la diagonale
diag_S = np.diag(Sigma)
idx = np.argsort(diag_S)[::-1] # Ordine decrescente
Sigma_sorted = np.diag(diag_S[idx]) # Ricostruisci diag con l'ordine
U_sorted = U[:, idx]
V_sorted = V[:, idx]
return U_sorted, Sigma_sorted, V_sorted
# Test the SVD class
if __name__ == "__main__":
from sklearn.decomposition import TruncatedSVD
np.random.seed(42)
# Create a random matrix A
A = np.random.rand(6, 4) # 6x4 matrix
# Print the results
print("\nMatrix A\n")
print(A)
# Specify the number of components for the truncated SVD
n_components = 4
# *** My SVD *** #
U, Sigma, V = SVD(None).fit_transform(A)
print("\n*** My SVD *** ")
print("\nMatrix U")
print(U)
U_orthogonal_check = np.allclose(U.T @ U, np.eye(U.shape[1]))
print("Is U an orthogonal matrix? ", U_orthogonal_check)
print("\nMatrix Sigma")
print(Sigma)
print("\nMatrix V.T")
print(V.T)
V_orthogonal_check = np.allclose(V.T @ V, np.eye(V.shape[1]))
print("Is V an orthogonal matrix? ", V_orthogonal_check)
print("\n---\n")
reconstruction_check = np.allclose(U @ Sigma @ V.T, A)
print("Is U @ Sigma @ V.T == A? ", reconstruction_check)
# *** Builtin SVD *** #
print("\n*** Builtin SVD *** ")
U_np, Sigma_np, Vt_np = np.linalg.svd(A, full_matrices=True)
print("\nMatrix U")
print(U_np)
print("\nMatrix Sigma")
print(np.diag(Sigma_np))
print("\nMatrix V.T")
print(Vt_np)
print()