手搓人工智能—聚类分析(下)谱系聚类与K-mean聚类

"无论结果如何,至少我们存在过"

------《无人深空》

前言

除了上一篇手搓人工智能-聚类分析(上)中提到的两种简单聚类方式,还有一些更为常用、更复杂的聚类方式:谱系聚类,K-均值聚类。

谱系聚类

谱系聚类又被称为是系统聚类,层次聚类,它的思想主要来源于社会科学和生物分类学,目标不仅是要产生样本的不同聚类,而且要生成一个完整的样本分类谱系。

谱系聚类合并算法

先给出一般谱系聚类算法:


  • 初始化:每个样本作为一个单独的聚类,
  • 循环,直到所有样本属于一个聚类为止:
    • 寻找当前聚类中最相近的两个聚类:
    • 删除聚类 ,增加新的聚类
  • 输出:样本的合并过程,形成层次化谱系

但是,对于一个聚类问题来说,将所有样本合并为一个聚类的结果显然是毫无意义的。我们可以设置一个终止条件,当满足该条件时,输出当前的聚类。常用的终止条件包括

  • 预定类别数:预先设定一个目标聚类数,当合并过程中剩余的聚类数量达到预定的目标聚类数时,停止合并。
  • 距离阈值:预先设定一个距离阈值,当最近的两个聚类之间的距离大于阈值时,停止合并过程,输出当前的聚类。
  • "最优"聚类数:在合并过程中按照某种准则判断当前的聚类结果是否达到"最优"的聚类数,以"最优"聚类数作为终止合并的条件。依据什么样的准则确定一个给定样本集的"最优"聚类是大多聚类分析方法需要面对的问题。根据样本间距离准则,有
    • 最大距离法:以两个聚类 中相距最远的两个样本间距离度量聚类之间的相似程度
    • 最小距离法:以两个聚类 中相距最近的两个样本间距离度量聚类之间的相似程度
    • 平均距离法:计算两个集合中任意一对样本的距离,以所有的距离平均值来度量集合之间的相似程度
    • 平均样本法:以两类样本均值之间的距离度量集合之间的相似程度

算法在第 K 轮合并之前需要计算 个聚类之间的距离,生成整个谱系需要 轮合并,因此总的距离计算次数是:

在具体实现过程中,可以利用距离矩阵 来记录当前各个聚类之间的距离,通过寻找其中的最小元素来确定下一步进行合并的两个聚类;合并聚类后更新距离矩阵 ,根据所用不同距离度量方式计算新生成聚类与其他聚类之间的距离

优化后的算法流程如下:


  • 初始化:每个样本作为一个单独的聚类,,每个聚类的样本数:,计算每个样本间的距离,构成距离矩阵 ,聚类数
  • 循环,直到满足聚类的终止条件为止:
    • 寻找距离矩阵 中上三角矩阵元素的最小值
    • 删除聚类 ,增加新的聚类
    • 更新距离矩阵
  • 输出:聚类,聚类数

示例代码(C++)

采用距离度量为欧式距离,取最小距离

cpp 复制代码
void Clustering::Lineageclustering(std::vector<std::vector<double>> sample, double theta, const char* type, int p)
{
	Distance distance;
	std::vector<std::vector<double>> Distancemartix;
	std::vector<std::vector<double>> centers;
	//初始化
	for (int i = 0; i < sample.size(); i++) {
		clusters.push_back(std::vector<std::vector<double>>());
		clusters[i].push_back(sample[i]);
	}
	Distancemartix = distance.DistanceMartix(sample, type, -1);
    //遍历每个样本
	double min = 0;
	while (min < theta) {
		//更新聚类中心
		for (int m = 0; m < clusters.size(); m++) {
			centers.push_back(GetCenter(clusters[m]));
		}
		//找到最近的两个聚类,并合并聚类
		min = DBL_MAX;
		int select_index1 = 0;
		int select_index2 = 0;
		for (int i = 0; i < centers.size(); i++) {
			for (int j = i; j < centers.size(); j++) {
                if (i == j) continue;
				min = (min < distance.SelectDistance(centers[i], centers[j], type, p)) ? min : distance.SelectDistance(centers[i], centers[j], type, p);
				select_index1 = (min < distance.SelectDistance(centers[i], centers[j], type, p)) ? select_index1 : i;
				select_index2 = (min < distance.SelectDistance(centers[i], centers[j], type, p)) ? select_index2 : j;
			}
		}
		for (int i = 0; i < clusters[select_index2].size(); i++) {
			clusters[select_index1].push_back(clusters[select_index2][i]);
		}
		clusters.erase(clusters.begin() + select_index2);
		if (centers.size() < 2) {
			break;
		}
	}
}

输入测试样本 ,阈值设为2

cpp 复制代码
data = { {1, 1}, {1, 3}, {3, 5}, {3, 3}, {3, 7}, {3, 9} };

分类结果

cpp 复制代码
Cluster 0: (1, 1)
Cluster 1: (1, 3)
Cluster 2: (3, 5)
Cluster 3: (3, 3)
Cluster 4: (3, 7) (3, 9)

K-mean 聚类

K-均值算法的想法最早由 Hugo Steinhaus 于 1957 年提出,Stuart Lloyd 于 1957 年在 Bell 实验室给出了标准K-均值聚类算法。由于算法实现简单,计算复杂度和存储复杂度低,对很多简单的聚类问题可以得到令人满意的结果,K-均值算法已经成为最著名和常用的聚类算法之一

K-mean 算法的目标是将 个样本依据最小化类内距离的准则分到 K 个聚类之中

直接对上述类内距离准则优化存在一定困难,不妨换一个思路:

首先假设每个聚类的均值 是固定已知的,那么这个优化问题就很容易求解了

现在这个问题变成了:

每一个样本 选择加入一个聚类 ,使得类内距离准则最小

很显然,如果 ,应该将样本 放入聚类 ,这样可以使得 最小。

然而,已知每个聚类的均值 的假设是不成立的,因为在知道每个聚类包含哪个样本之前是无法求得样本均值的,聚类的均值只能根据这个聚类中所有样本求得。

K-均值的思想是首先对每类均值做出假设 ,得到对聚类结果的猜想 ,将样本分类后更新均值,得到一个迭代的过程

经过多轮迭代,分类结果不再发生变化,可以认为算法收敛到了一个对 的优化结果

K-mean 聚类算法

K-mean算法流程


  • 初始化:随机选择K个聚类均值
  • 循环,直到K个均值不在变化为止
    • for i = 1 to n
    • 更新 K 个聚类的均值:
  • 输出:聚类

示例代码(C++)

cpp 复制代码
struct Point {
    std::vector<double> coordinates;
};

// 计算两个数据点之间的欧氏距离
double distance(const Point& p1, const Point& p2) {
    double sum = 0.0;
    for (size_t i = 0; i < p1.coordinates.size(); ++i) {
        sum += std::pow(p1.coordinates[i] - p2.coordinates[i], 2);
    }
    return std::sqrt(sum);
}

// K-means聚类算法
std::vector<int> kMeans(const std::vector<Point>& data, int k, int maxIterations) {
    std::vector<int> labels(data.size(), 0); // 存储每个数据点的簇标签
    std::vector<Point> centroids(k); // 存储簇中心

    // 随机初始化簇中心
    std::srand(std::time(0));
    for (int i = 0; i < k; ++i) {
        int randomIndex = std::rand() % data.size();
        centroids[i] = data[randomIndex];
    }

    for (int iteration = 0; iteration < maxIterations; ++iteration) {
        // 分配数据点到最近的簇中心
        for (size_t i = 0; i < data.size(); ++i) {
            double minDist = std::numeric_limits<double>::max();
            int closestCentroid = 0;
            for (int j = 0; j < k; ++j) {
                double dist = distance(data[i], centroids[j]);
                if (dist < minDist) {
                    minDist = dist;
                    closestCentroid = j;
                }
            }
            labels[i] = closestCentroid;
        }

        // 更新簇中心
        std::vector<int> count(k, 0);
        std::vector<Point> newCentroids(k, {std::vector<double>(data[0].coordinates.size(), 0.0)});
        for (size_t i = 0; i < data.size(); ++i) {
            int clusterIndex = labels[i];
            count[clusterIndex]++;
            for (size_t j = 0; j < data[i].coordinates.size(); ++j) {
                newCentroids[clusterIndex].coordinates[j] += data[i].coordinates[j];
            }
        }
        for (int i = 0; i < k; ++i) {
            for (size_t j = 0; j < data[0].coordinates.size(); ++j) {
                centroids[i].coordinates[j] = newCentroids[i].coordinates[j] / count[i];
            }
        }
    }

    return labels;
}
相关推荐
ZOMI酱9 分钟前
【AI系统】模型转换基本介绍
人工智能
CodeDevMaster21 分钟前
LangChain之检索增强生成RAG
人工智能·python·llm
今天又是学习1 小时前
深度学习5
人工智能·深度学习
新加坡内哥谈技术1 小时前
RAG架构类型
大数据·人工智能·语言模型·chatgpt
Topstip2 小时前
iOS 19 重大更新泄露,将带来更“聪明”的 Siri 挑战 ChatGPT
人工智能·ios·ai·chatgpt
Nerinic2 小时前
深度学习基础1
人工智能·深度学习
数字扫地僧2 小时前
深度学习与知识图谱嵌入的结合:从理论到实践
人工智能·深度学习·知识图谱
ZOMI酱2 小时前
【AI系统】昇腾 AI 架构介绍
人工智能·架构
说私域3 小时前
精准零售驱动下的中国零售业变革与“开源 2+1 链动小程序”应用探究
人工智能·小程序·开源