手搓人工智能—聚类分析(下)谱系聚类与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;
}
相关推荐
梦丶晓羽1 分钟前
自然语言处理:最大期望值算法
人工智能·python·自然语言处理·高斯混合模型·最大期望值算法
浊酒南街1 分钟前
XGBClassifiler函数介绍
算法·机器学习·xgb
gis收藏家2 分钟前
使用开放数据、ArcGIS 和 Sklearn 测量洛杉矶的城市相似性
人工智能·arcgis·sklearn
helpme流水2 小时前
【人工智能】Open WebUI+ollama+deepSeek-r1 本地部署大模型与知识库
人工智能·ubuntu·ai
Icomi_4 小时前
【神经网络】0.深度学习基础:解锁深度学习,重塑未来的智能新引擎
c语言·c++·人工智能·python·深度学习·神经网络
半问4 小时前
广告营销,会被AI重构吗?
人工智能·重构
movee4 小时前
一台低配云主机也能轻松愉快地玩RDMA
linux·人工智能·后端
张琪杭4 小时前
机器学习-随机森林解析
人工智能·随机森林·机器学习
訾博ZiBo4 小时前
AI日报 - 2025年3月11日
人工智能
刘大猫264 小时前
一、MyBatis简介:MyBatis历史、MyBatis特性、和其它持久化层技术对比、Mybatis下载依赖包流程
人工智能·数据挖掘·数据分析