C++代码实现MATLAB中的spectralcluster函数功能

cpp 复制代码
// spectralcluster.cpp
// MATLAB spectralcluster 的 C++ 独立实现(无第三方依赖)
// 编译: g++ -std=c++11 -O2 spectralcluster.cpp -o spectralcluster
// 运行: ./spectralcluster

#include <vector>
#include <cmath>
#include <algorithm>
#include <random>
#include <iostream>
#include <limits>
#include <string>
#include <numeric>
#include <stdexcept>
#include <utility>
#include <cstdint>

// ============================================================
//  轻量稠密矩阵(行主序)
// ============================================================
struct Matrix {
    int rows = 0, cols = 0;
    std::vector<double> d;

    Matrix() = default;
    Matrix(int r, int c, double v = 0.0)
        : rows(r), cols(c), d((size_t)r * c, v) {}

    double&       operator()(int i, int j)       { return d[(size_t)i * cols + j]; }
    const double& operator()(int i, int j) const { return d[(size_t)i * cols + j]; }

    static Matrix identity(int n) {
        Matrix m(n, n, 0.0);
        for (int i = 0; i < n; ++i) m(i, i) = 1.0;
        return m;
    }
};

// ============================================================
//  对称矩阵 Jacobi 特征分解
//  输入 A(对称 n×n),输出 evals 升序,evecs 的列为对应特征向量
// ============================================================
static void jacobi_eigen(const Matrix& A,
                         std::vector<double>& evals,
                         Matrix& evecs) {
    const int n = A.rows;
    Matrix a = A;
    Matrix v = Matrix::identity(n);

    const double eps = 1e-12;
    const int max_sweeps = 200;

    for (int sweep = 0; sweep < max_sweeps; ++sweep) {
        double off = 0.0;
        for (int i = 0; i < n; ++i)
            for (int j = i + 1; j < n; ++j)
                off += a(i, j) * a(i, j);
        if (off < eps) break;

        for (int p = 0; p < n - 1; ++p) {
            for (int q = p + 1; q < n; ++q) {
                double apq = a(p, q);
                if (std::abs(apq) < 1e-15) continue;

                double theta = (a(q, q) - a(p, p)) / (2.0 * apq);
                double t = (theta >= 0.0 ? 1.0 : -1.0) /
                           (std::abs(theta) + std::sqrt(theta * theta + 1.0));
                double c   = 1.0 / std::sqrt(t * t + 1.0);
                double s   = t * c;
                double tau = s / (1.0 + c);

                double h = t * apq;
                a(p, p) -= h;
                a(q, q) += h;
                a(p, q) = 0.0;
                a(q, p) = 0.0;

                for (int j = 0; j < n; ++j) {
                    if (j == p || j == q) continue;
                    double gj = a(j, p);
                    double hj = a(j, q);
                    a(j, p) = gj - s * (hj + gj * tau);
                    a(p, j) = a(j, p);
                    a(j, q) = hj + s * (gj - hj * tau);
                    a(q, j) = a(j, q);
                }

                for (int i = 0; i < n; ++i) {
                    double g  = v(i, p);
                    double hh = v(i, q);
                    v(i, p) = g  - s * (hh + g  * tau);
                    v(i, q) = hh + s * (g  - hh * tau);
                }
            }
        }
    }

    std::vector<std::pair<double, int>> pairs(n);
    for (int i = 0; i < n; ++i) pairs[i] = {a(i, i), i};
    std::sort(pairs.begin(), pairs.end());

    evals.resize(n);
    evecs = Matrix(n, n);
    for (int j = 0; j < n; ++j) {
        evals[j] = pairs[j].first;
        int src  = pairs[j].second;
        for (int i = 0; i < n; ++i)
            evecs(i, j) = v(i, src);
    }
}

// ============================================================
//  SpectralCluster
// ============================================================
class SpectralCluster {
public:
    struct Params {
        std::string similarity_graph = "knn";     // "knn" | "radius"
        int         num_neighbors    = 0;         // 0 表示自动
        double      radius           = -1.0;      // <0 表示自动
        double      kernel_scale     = -1.0;      // <0 表示自动
        std::string laplacian_norm   = "rw";      // "rw" | "sym" | "none"
        int         max_iter         = 300;
        int         n_init           = 10;
        unsigned    seed             = 42;
    };

    explicit SpectralCluster(const Params& p);

    std::vector<int> fit_predict(const Matrix& X, int k);

private:
    Params params_;
    std::mt19937 rng_;

    Matrix build_similarity_matrix(const Matrix& X);
    Matrix knn_similarity(const Matrix& X, int k);
    Matrix radius_similarity(const Matrix& X, double radius);
    double estimate_kernel_scale(const Matrix& X, int k);
    Matrix pairwise_distance(const Matrix& X);

    std::vector<int> kmeans(const Matrix& V, int k);
    std::vector<int> kmeans_single(const Matrix& V, int k);

    // ---- 跨平台可复现采样工具(不依赖具体标准库分布实现)----
    double uniform01() {
        uint64_t r = (uint64_t)rng_();
        return r / (double)0xFFFFFFFFULL;   // mt19937 输出范围 [0, 2^32-1]
    }
    int uniform_int(int n) {
        int idx = (int)(uniform01() * n);
        return (idx >= n) ? n - 1 : idx;
    }
    int weighted_sample(const std::vector<double>& w) {
        double total = 0.0;
        for (double x : w) total += x;
        if (total <= 0.0) return 0;
        double r   = uniform01() * total;
        double cum = 0.0;
        for (size_t i = 0; i < w.size(); ++i) {
            cum += w[i];
            if (r < cum) return (int)i;
        }
        return (int)w.size() - 1;
    }
};

// 构造函数类外定义(C++11 兼容:避免默认实参在类定义期求值 Params{})
SpectralCluster::SpectralCluster(const Params& p) : params_(p) {
    rng_.seed(params_.seed);
}

// ============================================================
//  fit_predict:主流程
// ============================================================
std::vector<int> SpectralCluster::fit_predict(const Matrix& X, int k) {
    if (X.rows < k || X.cols == 0)
        throw std::invalid_argument("X rows must be >= k and cols > 0");

    // 1) 相似度矩阵
    Matrix S = build_similarity_matrix(X);
    const int n = S.rows;

    // 2) 度向量及 D^{-1/2}
    std::vector<double> deg(n, 0.0);
    for (int i = 0; i < n; ++i) {
        double s = 0.0;
        for (int j = 0; j < n; ++j) s += S(i, j);
        deg[i] = s;
    }
    std::vector<double> inv_sqrt_d(n, 0.0);
    for (int i = 0; i < n; ++i)
        if (deg[i] > 1e-15) inv_sqrt_d[i] = 1.0 / std::sqrt(deg[i]);

    // 3) 构建对称拉普拉斯矩阵 L_s
    //    none: L = D - S(对称)
    //    sym : L_s = I - D^{-1/2} S D^{-1/2}
    //    rw  : 用 L_s 求谱,再变换回 L_rw 的特征向量
    Matrix Ls(n, n, 0.0);
    if (params_.laplacian_norm == "none") {
        for (int i = 0; i < n; ++i) {
            Ls(i, i) = deg[i];
            for (int j = 0; j < n; ++j)
                Ls(i, j) -= S(i, j);
        }
    } else {
        for (int i = 0; i < n; ++i) {
            Ls(i, i) = 1.0;
            for (int j = 0; j < n; ++j)
                Ls(i, j) -= inv_sqrt_d[i] * S(i, j) * inv_sqrt_d[j];
        }
    }

    // 4) 特征分解(升序),取最小的 k 个特征向量
    std::vector<double> evals;
    Matrix evecs;
    jacobi_eigen(Ls, evals, evecs);

    // 5) 构造谱嵌入 V
    Matrix V(n, k, 0.0);
    if (params_.laplacian_norm == "rw") {
        // L_rw = D^{-1} L = I - D^{-1} S 与 L_s 相似:V = D^{-1/2} U
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < k; ++j)
                V(i, j) = inv_sqrt_d[i] * evecs(i, j);
    } else {
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < k; ++j)
                V(i, j) = evecs(i, j);
    }

    // 6) sym 情况:对 V 的每一行做单位化(Ng-Jordan-Weiss)
    if (params_.laplacian_norm == "sym") {
        for (int i = 0; i < n; ++i) {
            double norm = 0.0;
            for (int j = 0; j < k; ++j) norm += V(i, j) * V(i, j);
            norm = std::sqrt(norm);
            if (norm > 1e-15)
                for (int j = 0; j < k; ++j) V(i, j) /= norm;
        }
    }

    // 7) K-means
    return kmeans(V, k);
}

// ============================================================
//  相似度图构建
// ============================================================
Matrix SpectralCluster::build_similarity_matrix(const Matrix& X) {
    if (params_.similarity_graph == "radius") {
        double r = params_.radius;
        if (r <= 0) {
            Matrix D = pairwise_distance(X);
            int n = X.rows;
            double sum = 0.0;
            for (int i = 0; i < n; ++i) {
                double best = std::numeric_limits<double>::max();
                for (int j = 0; j < n; ++j)
                    if (i != j && D(i, j) < best) best = D(i, j);
                sum += best;
            }
            r = (n > 0) ? sum / n : 1.0;
        }
        return radius_similarity(X, r);
    } else {
        int nn = params_.num_neighbors;
        if (nn <= 0)
            nn = std::max(1, (int)std::ceil(std::log2((double)X.rows)));
        return knn_similarity(X, nn);
    }
}

Matrix SpectralCluster::pairwise_distance(const Matrix& X) {
    int n = X.rows, d = X.cols;
    Matrix D(n, n, 0.0);
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            double s = 0.0;
            for (int t = 0; t < d; ++t) {
                double diff = X(i, t) - X(j, t);
                s += diff * diff;
            }
            double dist = std::sqrt(s);
            D(i, j) = dist;
            D(j, i) = dist;
        }
    }
    return D;
}

double SpectralCluster::estimate_kernel_scale(const Matrix& X, int k) {
    Matrix D = pairwise_distance(X);
    int n = X.rows;
    std::vector<double> knn_dists(n, 1.0);
    for (int i = 0; i < n; ++i) {
        std::vector<double> row;
        row.reserve(n - 1);
        for (int j = 0; j < n; ++j) if (i != j) row.push_back(D(i, j));
        int kk = std::min(k, (int)row.size());
        if (kk <= 0) continue;
        std::nth_element(row.begin(), row.begin() + kk - 1, row.end());
        knn_dists[i] = row[kk - 1];
    }
    int mid = n / 2;
    std::nth_element(knn_dists.begin(), knn_dists.begin() + mid, knn_dists.end());
    double med = knn_dists[mid];
    return med > 1e-15 ? med : 1.0;
}

Matrix SpectralCluster::knn_similarity(const Matrix& X, int k) {
    int n = X.rows;
    Matrix D = pairwise_distance(X);
    Matrix S(n, n, 0.0);

    double sigma = params_.kernel_scale;
    if (sigma <= 0) sigma = estimate_kernel_scale(X, k);

    std::vector<std::vector<int>> neighbors(n);
    for (int i = 0; i < n; ++i) {
        std::vector<std::pair<double, int>> di;
        di.reserve(n - 1);
        for (int j = 0; j < n; ++j)
            if (i != j) di.push_back({D(i, j), j});
        int kk = std::min(k, n - 1);
        std::partial_sort(di.begin(), di.begin() + kk, di.end());
        for (int t = 0; t < kk; ++t)
            neighbors[i].push_back(di[t].second);
    }

    for (int i = 0; i < n; ++i) {
        for (int j : neighbors[i]) {
            double z   = D(i, j) / sigma;
            double sim = std::exp(-z * z);
            if (sim > S(i, j)) {
                S(i, j) = sim;
                S(j, i) = sim;
            }
        }
    }
    return S;
}

Matrix SpectralCluster::radius_similarity(const Matrix& X, double radius) {
    int n = X.rows;
    Matrix D = pairwise_distance(X);
    Matrix S(n, n, 0.0);

    double sigma = params_.kernel_scale;
    if (sigma <= 0) sigma = radius;

    for (int i = 0; i < n; ++i)
        for (int j = i + 1; j < n; ++j)
            if (D(i, j) <= radius) {
                double z   = D(i, j) / sigma;
                double sim = std::exp(-z * z);
                S(i, j) = sim;
                S(j, i) = sim;
            }
    return S;
}

// ============================================================
//  K-means(多次初始化 + k-means++)
// ============================================================
std::vector<int> SpectralCluster::kmeans(const Matrix& V, int k) {
    std::vector<int> best_labels;
    double best_inertia = std::numeric_limits<double>::max();
    int n = V.rows, d = V.cols;

    for (int run = 0; run < params_.n_init; ++run) {
        std::vector<int> labels = kmeans_single(V, k);

        Matrix centroids(k, d, 0.0);
        std::vector<int> counts(k, 0);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < d; ++j)
                centroids(labels[i], j) += V(i, j);
            counts[labels[i]]++;
        }
        for (int c = 0; c < k; ++c)
            if (counts[c] > 0)
                for (int j = 0; j < d; ++j)
                    centroids(c, j) /= counts[c];

        double inertia = 0.0;
        for (int i = 0; i < n; ++i) {
            double s = 0.0;
            for (int j = 0; j < d; ++j) {
                double diff = V(i, j) - centroids(labels[i], j);
                s += diff * diff;
            }
            inertia += s;
        }
        if (inertia < best_inertia) {
            best_inertia = inertia;
            best_labels  = labels;
        }
    }
    return best_labels;
}

std::vector<int> SpectralCluster::kmeans_single(const Matrix& V, int k) {
    int n = V.rows, d = V.cols;

    // k-means++ 初始化
    std::vector<int> centers;
    centers.reserve(k);
    centers.push_back(uniform_int(n));

    std::vector<double> min_dist2(n, std::numeric_limits<double>::max());
    for (int c = 1; c < k; ++c) {
        int last = centers.back();
        for (int i = 0; i < n; ++i) {
            double s = 0.0;
            for (int j = 0; j < d; ++j) {
                double diff = V(i, j) - V(last, j);
                s += diff * diff;
            }
            if (s < min_dist2[i]) min_dist2[i] = s;
        }
        centers.push_back(weighted_sample(min_dist2));
    }

    Matrix centroids(k, d, 0.0);
    for (int c = 0; c < k; ++c)
        for (int j = 0; j < d; ++j)
            centroids(c, j) = V(centers[c], j);

    std::vector<int> labels(n, -1);
    for (int iter = 0; iter < params_.max_iter; ++iter) {
        bool changed = false;
        for (int i = 0; i < n; ++i) {
            double best_d = std::numeric_limits<double>::max();
            int best_c = 0;
            for (int c = 0; c < k; ++c) {
                double s = 0.0;
                for (int j = 0; j < d; ++j) {
                    double diff = V(i, j) - centroids(c, j);
                    s += diff * diff;
                }
                if (s < best_d) { best_d = s; best_c = c; }
            }
            if (labels[i] != best_c) { labels[i] = best_c; changed = true; }
        }
        if (!changed && iter > 0) break;

        Matrix new_c(k, d, 0.0);
        std::vector<int> counts(k, 0);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < d; ++j)
                new_c(labels[i], j) += V(i, j);
            counts[labels[i]]++;
        }
        for (int c = 0; c < k; ++c)
            if (counts[c] > 0)
                for (int j = 0; j < d; ++j)
                    new_c(c, j) /= counts[c];
        centroids = new_c;
    }
    return labels;
}

// ============================================================
//  main:三簇高斯数据演示
//  使用自实现的确定性 PRNG 生成数据,避免依赖标准库分布实现
// ============================================================
int main() {
    const int n = 150;
    Matrix X(n, 2, 0.0);

    // 确定性 PRNG(LCG + Box-Muller)
    uint64_t state = 12345ULL;
    auto next_uniform = [&]() -> double {
        state = state * 6364136223846793005ULL + 1442695040888963407ULL;
        return (double)((state >> 33) & 0x7FFFFFFFULL) / (double)0x7FFFFFFFULL;
    };
    auto next_gauss = [&]() -> double {
        double u1 = next_uniform();
        double u2 = next_uniform();
        if (u1 < 1e-12) u1 = 1e-12;
        return std::sqrt(-2.0 * std::log(u1)) *
               std::cos(2.0 * 3.14159265358979323846 * u2);
    };

    for (int i = 0; i < 50; ++i) {
        X(i, 0) =  2.0 + 0.5 * next_gauss();
        X(i, 1) =  2.0 + 0.5 * next_gauss();
    }
    for (int i = 50; i < 100; ++i) {
        X(i, 0) = -2.0 + 0.5 * next_gauss();
        X(i, 1) =  2.0 + 0.5 * next_gauss();
    }
    for (int i = 100; i < 150; ++i) {
        X(i, 0) =  0.0 + 0.5 * next_gauss();
        X(i, 1) = -2.0 + 0.5 * next_gauss();
    }

    SpectralCluster::Params params;
    params.similarity_graph = "knn";
    params.laplacian_norm   = "rw";     // Shi-Malik 归一化(MATLAB 默认)
    params.n_init           = 10;

    SpectralCluster sc(params);
    std::vector<int> labels = sc.fit_predict(X, 3);

    // 输出 1:前 10 个样本的标签
    std::cout << "前 10 个样本的标签: ";
    for (int i = 0; i < 10; ++i)
        std::cout << labels[i] << (i + 1 < 10 ? " " : "");
    std::cout << "\n";

    // 输出 2:每簇样本数
    std::vector<int> cnt(3, 0);
    for (int l : labels) cnt[l]++;
    std::cout << "簇 0: " << cnt[0]
              << ", 簇 1: " << cnt[1]
              << ", 簇 2: " << cnt[2] << "\n";

    return 0;
}
相关推荐
鸽鸽程序猿1 小时前
【项目】【在线判题系统】c端:竞赛报名
c语言·开发语言
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 个人资料管理的需求分析与接口设计
java·开发语言·交友
行者全栈架构师1 小时前
【鸿蒙心迹】让应用“悬浮“起来——闪控球(floatingBall)从接入到实战全链路(HarmonyOS 7.x)
前端·算法·架构
1024奇点1 小时前
【仓颉语言入门 · 第二篇 第 8 课】
开发语言·数据库·ide·mysql·开源
云的雨 雨的云1 小时前
手写算法动画上位机:从排序到线性代数可视化
线性代数·算法
孙琦Ray1 小时前
开源MATLAB工具LIBquiv:650小时老化仿真仅139秒
matlab·开源·锂离子电池·等效电路模型·电池老化·libquiv·电池建模
gopher95111 小时前
RUST:序列化与反序列化 serde + derive 详解
开发语言·后端·rust
Pniubi2 小时前
快速排序(力扣基础)(递归)
java·数据结构·算法
xiaoye-duck2 小时前
《C++ 项目:手写大模型接入 SDK》Trae IDE 开发环境搭建与依赖管理
c++·ide·语言模型