Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2024-06-29 07:05:28

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 Chao Peng
0003 
0004 /*  Fuzzy K Clustering Algorithms
0005  *
0006  *  Author: Chao Peng (ANL)
0007  *  Date: 10/14/2020
0008  *
0009  */
0010 
0011 #include "FuzzyKClusters.h"
0012 #include <exception>
0013 #include <iostream>
0014 #include <cmath>
0015 
0016 
0017 using namespace fkc;
0018 using namespace Eigen;
0019 
0020 
0021 // =================================================================================================
0022 //  KMeans Algorithm
0023 // =================================================================================================
0024 
0025 KMeans::KMeans() = default;
0026 KMeans::~KMeans() = default;
0027 
0028 MatrixXd KMeans::Fit(const MatrixXd &data, int k, double q, double epsilon, int max_iters)
0029 {
0030     auto res = Initialize(data, k, q);
0031 
0032     for (n_iters = 0; n_iters < max_iters; ++n_iters) {
0033         auto old_mems = mems;
0034         Distances(res, data);
0035         Memberships(q);
0036         FormClusters(res, data, q);
0037 
0038         if ((old_mems - mems).cwiseAbs().maxCoeff() < epsilon) {
0039             break;
0040         }
0041     }
0042 
0043     return res;
0044 }
0045 
0046 // initialize and guess the clusters
0047 MatrixXd KMeans::Initialize(const MatrixXd &data, int k, double q)
0048 {
0049     // resize matrices
0050     dists.resize(k, data.rows());
0051 
0052     // guess the cluster centers
0053     mems = MatrixXd::Random(k, data.rows());
0054     for (int j = 0; j < mems.cols(); ++j) {
0055         auto csum = mems.col(j).sum();
0056         for (int i = 0; i < mems.rows(); ++i) {
0057             mems(i, j) = mems(i, j)/csum;
0058         }
0059     }
0060 
0061     MatrixXd clusters(k, data.cols());
0062     FormClusters(clusters, data, q);
0063     return clusters;
0064 }
0065 
0066 // distance matrix (num_clusters, num_data)
0067 void KMeans::Distances(const MatrixXd &centroids, const MatrixXd &data)
0068 {
0069     for (int i = 0; i < centroids.rows(); ++i) {
0070         for (int j = 0; j < data.rows(); ++j) {
0071             dists(i, j) = (centroids.row(i) - data.row(j)).cwiseAbs2().sum();
0072         }
0073     }
0074 }
0075 
0076 // membership matrix (num_clusters, num_data)
0077 void KMeans::Memberships(double q)
0078 {
0079     // coeffcient-wise operation
0080     auto d = dists.array().pow(-1.0/(q - 1.0)).matrix();
0081 
0082     for (int j = 0; j < d.cols(); ++j) {
0083         auto dsum = d.col(j).sum();
0084         for (int i = 0; i < d.rows(); ++i) {
0085             mems(i, j) = d(i, j)/dsum;
0086         }
0087     }
0088 }
0089 
0090 // rebuild clusters
0091 void KMeans::FormClusters(MatrixXd &clusters, const MatrixXd &data, double q)
0092 {
0093     auto weights = mems.array().pow(q).matrix();
0094     for (int i = 0; i < clusters.rows(); ++i) {
0095         clusters.row(i) *= 0;
0096         for (int j = 0; j < data.rows(); ++j) {
0097             clusters.row(i) += data.row(j)*weights(i, j);
0098         }
0099         clusters.row(i) /= weights.row(i).sum();
0100     }
0101 }
0102 
0103 
0104 // =================================================================================================
0105 //  KRings Algorithm, extended from KMeans
0106 //  Reference:
0107 //      [1] Y. H. Man and I. Gath,
0108 //          "Detection and separation of ring-shaped clusters using fuzzy clustering,"
0109 //          in IEEE Transactions on Pattern Analysis and Machine Intelligence,
0110 //          vol. 16, no. 8, pp. 855-861, Aug. 1994, doi: 10.1109/34.308484.
0111 // =================================================================================================
0112 
0113 KRings::KRings() = default;
0114 KRings::~KRings() = default;
0115 
0116 MatrixXd KRings::Fit(const MatrixXd &data, int k, double q, double epsilon, int max_iters)
0117 {
0118     auto res = Initialize(data, k, q);
0119 
0120     for (n_iters = 0; n_iters < max_iters; ++n_iters) {
0121         auto old_mems = mems;
0122         Distances(res, data);
0123         Memberships(q);
0124         FormRadii(res, q);
0125         FormClusters(res, data, q);
0126 
0127         if ((old_mems - mems).cwiseAbs().maxCoeff() < epsilon) {
0128             break;
0129         }
0130     }
0131 
0132     return res;
0133 }
0134 
0135 // initialize and guess the clusters
0136 MatrixXd KRings::Initialize(const MatrixXd &data, int k, double q)
0137 {
0138     MatrixXd clusters(k, data.cols() + 1);
0139     auto centers = clusters.leftCols(data.cols());
0140 
0141     // call KMeans to help initialization
0142     KMeans fkm;
0143     centers = fkm.Fit(data, k, q, 1e-4, 5);
0144 
0145     dists.resize(k, data.rows());
0146     dists_euc = fkm.GetDistances().cwiseSqrt();
0147     mems = fkm.GetMemberships();
0148     FormRadii(clusters, q);
0149     return clusters;
0150 }
0151 
0152 // distance matrix (num_clusters, num_data)
0153 void KRings::Distances(const MatrixXd &centroids, const MatrixXd &data)
0154 {
0155     auto const centers = centroids.leftCols(centroids.cols() - 1);
0156     auto const radii = centroids.rightCols(1);
0157 
0158     for (int i = 0; i < centroids.rows(); ++i) {
0159         for (int j = 0; j < data.rows(); ++j) {
0160             dists_euc(i, j) = std::sqrt((centers.row(i) - data.row(j)).cwiseAbs2().sum());
0161             dists(i, j) = std::pow(dists_euc(i, j) - radii(i, 0), 2);
0162         }
0163     }
0164 }
0165 
0166 // rebuild clusters radii
0167 void KRings::FormRadii(MatrixXd &clusters, double q)
0168 {
0169     auto radii = clusters.rightCols(1);
0170     auto weights = mems.array().pow(q).matrix();
0171 
0172     for (int i = 0; i < weights.rows(); ++i) {
0173         radii(i, 0) = 0;
0174         for (int j = 0; j < weights.cols(); ++j) {
0175             radii(i, 0) += weights(i, j)*dists_euc(i, j);
0176         }
0177         radii(i, 0) /= weights.row(i).sum();
0178     }
0179 }
0180 
0181 // rebuild clusters centers
0182 void KRings::FormClusters(MatrixXd &clusters, const MatrixXd &data, double q)
0183 {
0184     auto centers = clusters.leftCols(data.cols());
0185     const auto &radii = clusters.rightCols(1);
0186     auto weights = mems.array().pow(q).matrix();
0187 
0188     for (int i = 0; i < weights.rows(); ++i) {
0189         MatrixXd icenter = centers.row(i);
0190         centers.row(i) *= 0;
0191         for (int j = 0; j < weights.cols(); ++j) {
0192             double scale = radii(i, 0)/dists_euc(i, j);
0193             centers.row(i) += weights(i, j)*(data.row(j) - (data.row(j) - icenter)*scale);
0194         }
0195         centers.row(i) /= weights.row(i).sum();
0196     }
0197 }
0198 
0199 
0200 // =================================================================================================
0201 //  KEllipses Algorithm, extended from KRings
0202 //  Reference:
0203 //      [1] I. Gath and D. Hoory, Pattern Recognition Letters 16 (1995) 727-741,
0204 //          https://doi.org/10.1016/0167-8655(95)00030-K.
0205 // =================================================================================================
0206 // @TODO