东方耀AI技术分享

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 2089|回复: 1
打印 上一主题 下一主题

[C/C++] 通用kmeans算法c++_OOP实现与python可视化输出数据_注释版

[复制链接]

1365

主题

1856

帖子

1万

积分

管理员

Rank: 10Rank: 10Rank: 10

积分
14441
QQ
跳转到指定楼层
楼主
发表于 2022-3-17 10:55:34 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式




通用kmeans算法c++_OOP实现与python可视化输出数据_注释版


  1. #include <iostream>
  2. #include <fstream>
  3. #include "zennze/kmeans_oop.hpp"

  4. using namespace std;
  5. using std::cin;
  6. using std::cout;
  7. using std::initializer_list;
  8. using std::runtime_error;


  9. // 通用kmeans算法c++_OOP实现与python可视化输出数据_注释版

  10. class NDimenPoint : public VirtualPoint
  11. {
  12. private:
  13.     int dimension;  // 多维点类的维度
  14.     vector<double> xs;   // 一个点的数据

  15. public:
  16.     // 构造一个点 只要维度
  17.     NDimenPoint(const int d) : dimension(d) { xs.resize(d); }
  18.     // 构造一个点 维度与点数据
  19.     NDimenPoint(const int d, vector<double> l) : dimension(d), xs(l){}
  20.     // 构造一个新点  用另外一个点
  21.     NDimenPoint(const NDimenPoint &p) : dimension(p.dimension), xs(p.xs) {}
  22.     ~NDimenPoint(){};
  23.     bool operator==(const VirtualPoint &p) override
  24.     {
  25.         // 类型转换:父类 转 子类
  26.         auto pp = static_cast<const NDimenPoint &>(p);
  27.         if (dimension != pp.dimension)
  28.             return false;
  29.         for (size_t i = 0; i < xs.size(); i++)
  30.             if (xs[i] != pp.xs[i])
  31.                 return false;
  32.         return true;
  33.     }
  34.     bool operator!=(const VirtualPoint &p) override
  35.     {
  36.         auto pp = static_cast<const NDimenPoint &>(p);
  37.         if (dimension != pp.dimension)
  38.             return true;
  39.         for (size_t i = 0; i < xs.size(); i++)
  40.             if (xs[i] != pp.xs[i])
  41.                 return true;
  42.         return false;
  43.     }
  44.     void add(const NDimenPoint &p)
  45.     {
  46.         // 定义 点的 加法
  47.         if (p.dimension != dimension)
  48.             throw runtime_error("dimension mismatch");
  49.         for (size_t i = 0; i < xs.size(); i++)
  50.             xs[i] += p.xs[i];
  51.     }
  52.     NDimenPoint operator/(const int n)
  53.     {
  54.         // 定义 点除以一个n的操作
  55.         if (n == 0)
  56.             throw std::runtime_error("divisor zero error!");
  57.         NDimenPoint res(dimension);
  58.         for (size_t i = 0; i < dimension; i++)
  59.         {
  60.             res.xs[i] = xs[i] / n;
  61.         }
  62.         return res;
  63.     }
  64.     double disTo(const NDimenPoint &p)
  65.     {
  66.         // 定义 两点之间的欧式距离 这样支持了多维数据了
  67.         double tmp = 0;
  68.         for (size_t i = 0; i < dimension; i++)
  69.             tmp += pow(xs[i] - p.xs[i], 2);
  70.         return sqrt(tmp);
  71.     }
  72.     string toString() override
  73.     {
  74.         stringstream ss;
  75.         ss << "[";
  76.         for (size_t i = 0; i < dimension; i++)
  77.         {
  78.             if (i > 0)
  79.                 ss << ", ";
  80.             ss << xs[i];
  81.         }
  82.         ss << "]";
  83.         return ss.str();
  84.     }


  85.     static double calcDisToCluster(const VirtualPoint &p, const Cluster &c)
  86.     {
  87.         // 静态方法: 点 到 簇质心的距离
  88.         auto pp = static_cast<const NDimenPoint &>(p);
  89.         auto cp = static_cast<const NDimenPoint &>(*(c.getCentroid()));
  90.         // 本质还是: 点到点 之间的距离
  91.         return pp.disTo(cp);
  92.     }
  93.     static sharedVPoint avgPoints(const vector<sharedVPoint> &points)
  94.     {
  95.         // 计算一堆点集合的质心
  96.         if (points.size() <= 0)
  97.             return nullptr;
  98.         NDimenPoint resPoint(static_cast<const NDimenPoint &>(*points[0]).dimension);
  99.         for (auto &&p : points)
  100.             resPoint.add(static_cast<const NDimenPoint &>(*p));
  101.         // 求和 再 除以n  均值
  102.         resPoint = resPoint / points.size();
  103.         // cerr << "DEBUG\t" << resPoint.toString() << ", POINTS.SIZE " << points.size() << endl;
  104.         return make_shared<NDimenPoint>(resPoint);
  105.     };
  106. };




  107. vector<NDimenPoint> geneData(int num, const int dimension, double maxVal = 1000)
  108. {
  109.     std::default_random_engine generator(time(NULL));
  110.     std::uniform_real_distribution<double> distribution(0, maxVal);
  111.     vector<NDimenPoint> points;
  112.     for (size_t i = 0; i < num; i++)
  113.     {
  114.         vector<double> tmpVec;
  115.         for (size_t j = 0; j < dimension; j++)
  116.             tmpVec.push_back(distribution(generator));
  117.         points.push_back(NDimenPoint(dimension, tmpVec));
  118.     }
  119.     return points;
  120. }


  121. void output(const vector<Cluster> &clusters, const int dimension)
  122. {
  123.     cout << "{"
  124.          << ""dimension":" << dimension << "," << endl
  125.          << ""clusters":[";
  126.     for (int i = 0; i < clusters.size(); i++)
  127.     {
  128.         if (i > 0)
  129.             cout << ", ";
  130.         std::cout << clusters[i].toString() << std::endl;
  131.     }
  132.     cout << "]}" << endl;
  133. }


  134. void output_json(const vector<Cluster> &clusters, const int dimension)
  135. {
  136.     std::string file_path = "./kmeans_visualization_py.json";
  137.     std::ofstream write_out_f;
  138.     write_out_f.open(file_path);


  139.     write_out_f << "{"
  140.          << ""dimension":" << dimension << "," << endl
  141.          << ""clusters":[";
  142.     for (int i = 0; i < clusters.size(); i++)
  143.     {
  144.         if (i > 0)
  145.             write_out_f << ", ";
  146.         write_out_f << clusters[i].toString() << std::endl;
  147.     }
  148.     write_out_f << "]}" << endl;

  149.     write_out_f.close();
  150. }


  151. void kmeans_work()
  152. {
  153.     const int maxRound = 1000;
  154.     const int pointCnt = 150;      // 数据集的点数
  155.     int dimension = 1;             // 点的维度
  156.     int k = 0;
  157.     cerr << "dimension, k: ";
  158.     cin >> dimension >> k;

  159.     vector<sharedVPoint> points;   // 点集的 共享指针
  160.     for (auto &&p : geneData(pointCnt, dimension))
  161.         points.push_back(make_shared<NDimenPoint>(p));
  162.    
  163.     auto clusters = KmeansAlg::run(points, k, NDimenPoint::calcDisToCluster, NDimenPoint::avgPoints, maxRound);
  164.    
  165.     output_json(clusters, dimension);
  166.     output(clusters, dimension);
  167. }

  168. int main()
  169. {
  170.     std::cout << "kmeans算法实现!" << endl;

  171.     kmeans_work();

  172.     return 0;
  173. }
复制代码


  1. #include <algorithm>
  2. #include <cmath>
  3. #include <ctime>
  4. #include <exception>
  5. #include <iostream>
  6. #include <memory>
  7. #include <random>
  8. #include <sstream>
  9. #include <string>
  10. #include <vector>
  11. using std::cerr;
  12. using std::endl;
  13. using std::make_shared;
  14. using std::pow;
  15. using std::shared_ptr;
  16. using std::sqrt;
  17. using std::string;
  18. using std::stringstream;
  19. using std::to_string;
  20. using std::vector;

  21. /**
  22. * kmeans - 点作为数据,cluster是点的聚簇
  23. * BEGIN
  24. *      选出来 k 个点作为中心点生成聚簇
  25. *      循环
  26. *           计算点与聚簇的距离
  27. *           每个点加入到距离最近的聚簇中
  28. *           更新聚簇中心点
  29. *           聚簇中心点未变?退出
  30. *      输出聚簇
  31. * END
  32. *
  33. * 数据结构
  34. * 点 - ==() toString()
  35. * 聚簇 - 计算中心点()
  36. * calcDis(point cluster)
  37. * kmeans() -
  38. * 为了设计出更为通用的结构,选择采用OOP面向对象设计,结构比较复杂,尤其是距离计算,求质心这两个函数
  39. * VirtualPoint - 虚拟点类(抽象类),无数据成员,定义了 == != 两个纯虚函数
  40. Cluster - 聚簇类,数据成员: VirtualPoint的集合 和 中心点(VirtualPoint类型)  
  41.           函数成员: 设置质心 更新质心 清空点...
  42. KmeansAlg - 算法框架,run方法实现了聚类算法,提供必要参数(点之间距离计算,求平均点方法),无需重写算法即可运行

  43. ------------------

  44. NDimenPoint - 多维点类,继承VirtualPoint,用来处理多维数据

  45. * 两个通用类 - 虚拟点与聚簇,实际使用的时候,继承VirtualPoint类
  46. */

  47. class VirtualPoint
  48. {
  49. private:
  50. public:
  51.     VirtualPoint() {}
  52.     virtual ~VirtualPoint() {}
  53.     // 纯虚函数
  54.     virtual bool operator==(const VirtualPoint &p) = 0;
  55.     virtual bool operator!=(const VirtualPoint &p) = 0;
  56.     virtual string toString() = 0;
  57. };

  58. // 为何用智能指针 因为 簇里 不停的清空点集与add点,所以为了提高效率 直接操作指针
  59. typedef shared_ptr<VirtualPoint> sharedVPoint;
  60. // 求平均点的方法也可能是任意的,因此需要作为参数传递给算法(函数指针)
  61. typedef sharedVPoint avgPointFunc(const vector<sharedVPoint> &);


  62. class Cluster
  63. {
  64.     // 簇类:管理 质心与 该簇所有的元素
  65. private:
  66.     vector<sharedVPoint> points;   // 频繁操作点 用指针提高效率
  67.     sharedVPoint centroid;   // centroid质心 的点
  68.     avgPointFunc *avgPoints;  // 求质心的函数指针

  69. public:
  70.     Cluster(avgPointFunc avg) { avgPoints = avg; }
  71.     ~Cluster() {}
  72.     Cluster &setCentroid(sharedVPoint p)
  73.     {
  74.         centroid = p;
  75.         // 把质心 放进一堆点里 这是为何? 对结果没有影响  影响输出了
  76.         points.push_back(p);
  77.         return *this;
  78.     }
  79.     bool updateCentroid()
  80.     {
  81.         sharedVPoint tmpPoint = avgPoints(points);
  82.         // 哪种情况 计算出来为 nullptr ?  如果points.size()==0吗?
  83.         if (tmpPoint == nullptr)
  84.             return false;
  85.         bool changed;
  86.         // 质心是否改变  true为改变了
  87.         if (tmpPoint != nullptr && centroid != nullptr)
  88.             changed = (*tmpPoint) != (*centroid);   // 计算出来的与原来的一样 才为false
  89.         else
  90.             changed = true;
  91.         centroid = tmpPoint;  // 计算出来的质心 更新一下
  92.         return changed;
  93.     }
  94.     void clear() { points.clear(); }
  95.     void addPoint(sharedVPoint p)
  96.     {
  97.         points.push_back(p);
  98.     }
  99.     string toString() const
  100.     {
  101.         stringstream ss;
  102.         if (centroid == nullptr || points.size() == 0){
  103.             // setCentroid()不把质心加入到点集 会影响这里
  104.             return "{}";
  105.         }
  106.             
  107.         // 打印质心 与 该簇的所有点
  108.         ss << "{"centroid": " << centroid->toString() << ","points": [";
  109.         for (int i = 0; i < points.size(); i++)
  110.         {
  111.             if (i > 0)
  112.                 ss << ", ";
  113.             ss << points[i]->toString();
  114.         }
  115.         ss << "]}";

  116.         return ss.str();
  117.     }
  118.     // 得到该簇的质心
  119.     sharedVPoint getCentroid() const { return centroid; }
  120.     // 得到该簇的所有的元素
  121.     const vector<sharedVPoint> &getPoints() { return points; }
  122. };

  123. // 计算 VirtualPoint 与 Cluster的质心 之间的距离
  124. // 距离的计算方法 可能是任意的(不仅仅欧式距离),因此需要作为参数传递给算法(函数指针)
  125. typedef double calcFunc(const VirtualPoint &, const Cluster &);

  126. class KmeansAlg
  127. {
  128. public:
  129.     KmeansAlg() {}
  130.     ~KmeansAlg() {}
  131.     // 生成 k 个 位于 [0, n) 中的不同的随机数, n < 100000000
  132.     static vector<int> randDiffNumbers(int n, int k)
  133.     {
  134.         // 选择随机的k个初始质心
  135.         const int maxn = 100000000;
  136.         vector<int> res;
  137.         if (n <= 0 || n >= maxn)
  138.             throw std::runtime_error("n is less than zero or greater than maxn(100,000,000)");
  139.         for (int i = 0; i < n; i++)
  140.             res.push_back(i);
  141.         random_shuffle(res.begin(), res.end());
  142.         res.resize(k);
  143.         return res;
  144.     }
  145.     static vector<Cluster> run(vector<sharedVPoint> data, int k, calcFunc calcDis, avgPointFunc avgPoints, const int maxRound = 1000)
  146.     {
  147.         if (k <= 1)
  148.             throw std::runtime_error("k is less than 1");
  149.         vector<Cluster> clusters;
  150.         
  151.         for (auto &&i : randDiffNumbers(data.size(), k)){
  152.             // 从data里随机选择k个 作为初始的 质心
  153.             // Cluster(avgPoints)这是构造了一个 簇
  154.             // clusters.size() == k
  155.             // && 是右值引用   & 是左值引用
  156.             clusters.push_back(Cluster(avgPoints).setCentroid(data[i]));
  157.         }
  158.             
  159.         for (int round = 0; round < maxRound; round++)
  160.         {
  161.             // 每次迭代就需要把簇的点集清空 因为都要重新计算
  162.             for (auto &&c : clusters)
  163.                 c.clear();
  164.             for (size_t i = 0; i < data.size(); i++)
  165.             {
  166.                 // 遍历计算所有的数据点,将其就近分配到对应的簇
  167.                 double minDis = calcDis(*(data[i]), clusters[0]);
  168.                 int minIndex_cluster = 0;  // 离哪个簇质心距离最小的簇的索引
  169.                 for (size_t j = 1; j < clusters.size(); j++)
  170.                 {
  171.                     // j为从1开始 因为前面已经算过了
  172.                     double tmpDis = calcDis(*(data[i]), clusters[j]);
  173.                     if (tmpDis < minDis)
  174.                         minDis = tmpDis, minIndex_cluster = j;
  175.                 }
  176.                 // 以上的目的是:看当前的点 离哪个簇的质心 最近
  177.                 // 现在就知道你这个数据点 属于哪个簇了
  178.                 clusters[minIndex_cluster].addPoint(data[i]);
  179.             }
  180.             

  181.             bool changed = false;
  182.             for (auto &&c : clusters){
  183.                 // 每个簇更新各自的簇质心 看是否有改变
  184.                 changed = changed || c.updateCentroid();
  185.             }

  186.             std::cout << "第" << round << "轮迭代:" <<"簇质心是否有改变=" << changed << std::endl;

  187.             // 簇质心没有改变了 就可以退出迭代了
  188.             if (!changed)
  189.                 break;

  190.         }
  191.         return clusters;
  192.     }
  193. };
复制代码



  1. # -*- coding: utf-8 -*-
  2. __author__ = u'东方耀 微信:dfy_88888'
  3. __date__ = '2022/3/16 下午5:21'
  4. __product__ = 'PyCharm'
  5. __filename__ = '14_kmeans聚类结果的可视化_for_c++'

  6. # 运行kmeans算法
  7. # 将结果(JSON化)输出到文件中
  8. # 使用Python读取文件内容
  9. # 使用pyplot可视化

  10. from mpl_toolkits.mplot3d import Axes3D
  11. import matplotlib.pyplot as plt
  12. import json
  13. import random

  14. colors = [
  15.     "#ff0000", "#00ff00", "#0000ff", "#404040", "#ff00ff", "#00ffff", "#C0ff00", "#ffC000", "#ff00C0", "#000070",
  16.     "#007000", "#700000",
  17. ]


  18. def paint(ax, xs, ys, color, zs=None, marker='.', s=30):
  19.     if zs != None:
  20.         # print("这是打印三维的")
  21.         ax.scatter(xs=xs, ys=ys, zs=zs, zdir='z', c=color, marker=marker, s=s)
  22.     else:
  23.         ax.scatter(x=xs, y=ys, c=color, marker=marker, s=s)


  24. def readData():
  25.     random.shuffle(colors)
  26.     output_json_c_file = "/home/jiang/jjj_eigen_works/my_use_eigen_demos/build/kmeans_visualization_py.json"
  27.     data = json.load(open(output_json_c_file, mode="r", encoding="utf-8"))
  28.     dimension = data["dimension"]
  29.     clusters = []
  30.     clusterCnt = 0
  31.     for tmpRawCluster in data["clusters"]:
  32.         tmpCluster = {"centroid": None, "xss": [],
  33.                       "color": colors[clusterCnt % 140]}
  34.         if "centroid" in tmpRawCluster:
  35.             tmpCluster["centroid"] = tmpRawCluster["centroid"]
  36.         for i in range(0, dimension):
  37.             tmpCluster["xss"].append([])
  38.         if "points" in tmpRawCluster:
  39.             for tmpRawPoint in tmpRawCluster["points"]:
  40.                 for j in range(0, len(tmpRawPoint)):
  41.                     tmpCluster["xss"][j].append(tmpRawPoint[j])
  42.         clusters.append(tmpCluster)
  43.         clusterCnt += 1
  44.     return {"dimension": dimension, "clusters": clusters}


  45. def work():
  46.     data = readData()
  47.     print("读入的数据:维度=%d, 类别k=%d" % (int(data["dimension"]), len(data["clusters"])))
  48.     fig = plt.figure()
  49.     if data["dimension"] == 2:
  50.         ax = fig.add_subplot(111)
  51.         for cluster in data["clusters"]:
  52.             if cluster["centroid"]:
  53.                 paint(ax, cluster["xss"][0],
  54.                       cluster["xss"][1], cluster["color"], marker='o')
  55.                 # 画质心用大点
  56.                 paint(ax, [cluster["centroid"][0]], [
  57.                     cluster["centroid"][1]], "#000000", marker='^', s=150)
  58.     elif data["dimension"] == 3:
  59.         ax = fig.add_subplot(111, projection='3d')
  60.         for cluster in data["clusters"]:
  61.             paint(ax, cluster["xss"][0], cluster["xss"]
  62.             [1], cluster["color"], cluster["xss"][2], marker='o')
  63.             # 画质心用大点
  64.             paint(ax, cluster["centroid"][0], cluster["centroid"]
  65.             [1], "#000000", cluster["centroid"][2], marker='^', s=150)
  66.     plt.show()
  67.     pass


  68. if __name__ == "__main__":
  69.     work()
复制代码






kmeans算法c++_OOP实现01.png (18.74 KB, 下载次数: 138)

kmeans算法c++_OOP实现01.png

kmeans算法c++_OOP实现02.png (148.83 KB, 下载次数: 138)

kmeans算法c++_OOP实现02.png
让天下人人学会人工智能!人工智能的前景一片大好!
回复

使用道具 举报

0

主题

32

帖子

90

积分

注册会员

Rank: 2

积分
90
沙发
发表于 2022-4-4 16:07:00 | 只看该作者

请问怎样才有198的阅读权限?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|人工智能工程师的摇篮 ( 湘ICP备2020019608号-1 )

GMT+8, 2024-5-28 13:19 , Processed in 0.189481 second(s), 21 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表