// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // RandomizedSVD.hpp — ランダム化 SVD // // Halko-Martinsson-Tropp (2011) アルゴリズム。 // 大規模行列の上位 k 個の特異値/ベクトルを O(mnk) で近似。 // // 参考: "Finding Structure with Randomness: Probabilistic Algorithms // for Constructing Approximate Matrix Decompositions" #ifndef CALX_MATH_LINALG_RANDOMIZED_SVD_HPP #define CALX_MATH_LINALG_RANDOMIZED_SVD_HPP #include #include #include #include #include #include namespace calx { template struct RandomizedSVDResult { Matrix U; // (m × k) 左特異ベクトル std::vector S; // (k) 特異値 Matrix Vt; // (k × n) 右特異ベクトル (転置) }; /// ランダム化 SVD /// @param A 入力行列 (m × n) /// @param k 求める特異値の数 /// @param oversampling オーバーサンプリング数 (精度向上、通常 5〜10) /// @param powerIter パワーイテレーション回数 (スペクトル減衰が緩い場合に有効) /// @param seed 乱数シード template RandomizedSVDResult randomizedSVD(const Matrix& A, size_t k, size_t oversampling = 5, size_t powerIter = 1, unsigned seed = 42) { size_t m = A.rows(), n = A.cols(); size_t l = std::min(k + oversampling, std::min(m, n)); std::mt19937 rng(seed); std::normal_distribution normal(T{0}, T{1}); // Step 1: ランダム射影 Ω (n × l) Matrix Omega(n, l); for (size_t i = 0; i < n; ++i) for (size_t j = 0; j < l; ++j) Omega(i, j) = normal(rng); // Step 2: Y = A * Ω (m × l) Matrix Y(m, l, T{0}); for (size_t i = 0; i < m; ++i) for (size_t j = 0; j < l; ++j) for (size_t p = 0; p < n; ++p) Y(i, j) += A(i, p) * Omega(p, j); // Step 3: パワーイテレーション (スペクトル減衰を改善) for (size_t q = 0; q < powerIter; ++q) { // Z = A^T * Y (n × l) Matrix Z(n, l, T{0}); for (size_t i = 0; i < n; ++i) for (size_t j = 0; j < l; ++j) for (size_t p = 0; p < m; ++p) Z(i, j) += A(p, i) * Y(p, j); // Y = A * Z (m × l) Y = Matrix(m, l, T{0}); for (size_t i = 0; i < m; ++i) for (size_t j = 0; j < l; ++j) for (size_t p = 0; p < n; ++p) Y(i, j) += A(i, p) * Z(p, j); } // Step 4: QR 分解 (Y = Q * R) — 修正グラムシュミット Matrix Q(m, l, T{0}); for (size_t j = 0; j < l; ++j) { for (size_t i = 0; i < m; ++i) Q(i, j) = Y(i, j); for (size_t p = 0; p < j; ++p) { T dot = T{0}; for (size_t i = 0; i < m; ++i) dot += Q(i, p) * Q(i, j); for (size_t i = 0; i < m; ++i) Q(i, j) -= dot * Q(i, p); } T norm = T{0}; for (size_t i = 0; i < m; ++i) norm += Q(i, j) * Q(i, j); norm = std::sqrt(norm); if (norm > T{1e-15}) for (size_t i = 0; i < m; ++i) Q(i, j) /= norm; } // Step 5: B = Q^T * A (l × n) Matrix B(l, n, T{0}); for (size_t i = 0; i < l; ++i) for (size_t j = 0; j < n; ++j) for (size_t p = 0; p < m; ++p) B(i, j) += Q(p, i) * A(p, j); // Step 6: B のフル SVD (小規模: l × n, l << m) // べき乗法で上位 k 個を抽出 Matrix Ub(l, k, T{0}); Matrix Vtb(k, n, T{0}); std::vector Sb(k, T{0}); Matrix Bwork = B; for (size_t r = 0; r < k; ++r) { std::vector v(n); for (size_t j = 0; j < n; ++j) v[j] = normal(rng); for (int iter = 0; iter < 100; ++iter) { // u = B * v std::vector u(l, T{0}); for (size_t i = 0; i < l; ++i) for (size_t j = 0; j < n; ++j) u[i] += Bwork(i, j) * v[j]; T nu = T{0}; for (auto x : u) nu += x * x; nu = std::sqrt(nu); if (nu < T{1e-15}) break; for (auto& x : u) x /= nu; // v = B^T * u std::fill(v.begin(), v.end(), T{0}); for (size_t j = 0; j < n; ++j) for (size_t i = 0; i < l; ++i) v[j] += Bwork(i, j) * u[i]; T nv = T{0}; for (auto x : v) nv += x * x; Sb[r] = std::sqrt(nv); if (Sb[r] < T{1e-15}) break; for (auto& x : v) x /= Sb[r]; } // u = B * v / sigma std::vector u(l, T{0}); for (size_t i = 0; i < l; ++i) for (size_t j = 0; j < n; ++j) u[i] += Bwork(i, j) * v[j]; if (Sb[r] > T{1e-15}) for (auto& x : u) x /= Sb[r]; for (size_t i = 0; i < l; ++i) Ub(i, r) = u[i]; for (size_t j = 0; j < n; ++j) Vtb(r, j) = v[j]; // デフレーション for (size_t i = 0; i < l; ++i) for (size_t j = 0; j < n; ++j) Bwork(i, j) -= Sb[r] * u[i] * v[j]; } // Step 7: U = Q * Ub (m × k) Matrix U(m, k, T{0}); for (size_t i = 0; i < m; ++i) for (size_t r = 0; r < k; ++r) for (size_t p = 0; p < l; ++p) U(i, r) += Q(i, p) * Ub(p, r); return { std::move(U), std::move(Sb), std::move(Vtb) }; } } // namespace calx #endif // CALX_MATH_LINALG_RANDOMIZED_SVD_HPP