-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
ThinSVD.cs
142 lines (122 loc) · 4.7 KB
/
ThinSVD.cs
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
132
133
134
135
136
137
138
139
140
141
142
using System;
using Utilities.Extensions;
namespace Algorithms.Numeric.Decomposition;
/// <summary>
/// Singular Vector Decomposition decomposes any general matrix into its
/// singular values and a set of orthonormal bases.
/// </summary>
public static class ThinSvd
{
/// <summary>
/// Computes a random unit vector.
/// </summary>
/// <param name="dimensions">The dimensions of the required vector.</param>
/// <returns>The unit vector.</returns>
public static double[] RandomUnitVector(int dimensions)
{
Random random = new();
double[] result = new double[dimensions];
for (var i = 0; i < dimensions; i++)
{
result[i] = 2 * random.NextDouble() - 1;
}
var magnitude = result.Magnitude();
result = result.Scale(1 / magnitude);
return result;
}
/// <summary>
/// Computes a single singular vector for the given matrix, corresponding to the largest singular value.
/// </summary>
/// <param name="matrix">The matrix.</param>
/// <returns>A singular vector, with dimension equal to number of columns of the matrix.</returns>
public static double[] Decompose1D(double[,] matrix) =>
Decompose1D(matrix, 1E-5, 100);
/// <summary>
/// Computes a single singular vector for the given matrix, corresponding to the largest singular value.
/// </summary>
/// <param name="matrix">The matrix.</param>
/// <param name="epsilon">The error margin.</param>
/// <param name="maxIterations">The maximum number of iterations.</param>
/// <returns>A singular vector, with dimension equal to number of columns of the matrix.</returns>
public static double[] Decompose1D(double[,] matrix, double epsilon, int maxIterations)
{
var n = matrix.GetLength(1);
var iterations = 0;
double mag;
double[] lastIteration;
double[] currIteration = RandomUnitVector(n);
double[,] b = matrix.Transpose().Multiply(matrix);
do
{
lastIteration = currIteration.Copy();
currIteration = b.MultiplyVector(lastIteration);
currIteration = currIteration.Scale(100);
mag = currIteration.Magnitude();
if (mag > epsilon)
{
currIteration = currIteration.Scale(1 / mag);
}
iterations++;
}
while (lastIteration.Dot(currIteration) < 1 - epsilon && iterations < maxIterations);
return currIteration;
}
public static (double[,] U, double[] S, double[,] V) Decompose(double[,] matrix) =>
Decompose(matrix, 1E-5, 100);
/// <summary>
/// Computes the SVD for the given matrix, with singular values arranged from greatest to least.
/// </summary>
/// <param name="matrix">The matrix.</param>
/// <param name="epsilon">The error margin.</param>
/// <param name="maxIterations">The maximum number of iterations.</param>
/// <returns>The SVD.</returns>
public static (double[,] U, double[] S, double[,] V) Decompose(
double[,] matrix,
double epsilon,
int maxIterations)
{
var m = matrix.GetLength(0);
var n = matrix.GetLength(1);
var numValues = Math.Min(m, n);
// sigmas is be a diagonal matrix, hence only a vector is needed
double[] sigmas = new double[numValues];
double[,] us = new double[m, numValues];
double[,] vs = new double[n, numValues];
// keep track of progress
double[,] remaining = matrix.Copy();
// for each singular value
for (var i = 0; i < numValues; i++)
{
// compute the v singular vector
double[] v = Decompose1D(remaining, epsilon, maxIterations);
double[] u = matrix.MultiplyVector(v);
// compute the contribution of this pair of singular vectors
double[,] contrib = u.OuterProduct(v);
// extract the singular value
var s = u.Magnitude();
// v and u should be unit vectors
if (s < epsilon)
{
u = new double[m];
v = new double[n];
}
else
{
u = u.Scale(1 / s);
}
// save u, v and s into the result
for (var j = 0; j < u.Length; j++)
{
us[j, i] = u[j];
}
for (var j = 0; j < v.Length; j++)
{
vs[j, i] = v[j];
}
sigmas[i] = s;
// remove the contribution of this pair and compute the rest
remaining = remaining.Subtract(contrib);
}
return (U: us, S: sigmas, V: vs);
}
}