K-means

本文详细介绍了K-均值聚类算法的基本原理及其实现过程,包括算法流程、时间复杂度分析以及如何通过C++实现算法的具体步骤。此外还讨论了K-均值算法的局限性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      K-均值算法的基本思想是首先从含有N个数据对象的数据集中随机选择K个数据对象作为初始中心,然后计算每个数据对象到各中心的距离,根据最近邻原则,所有数据对象将会被划分到离它最近的那个中心所代表的簇中,接着分别计算新生成的各个簇中数据对象的均值作为各簇新的中心,比较新的中心和上一次得到的中心,如果新的中心没有发生变化,则算法收敛,输出结果,如果新的中心和上一次的中心相比发生变化,则要根据新的中心对所有数据对象重新进行划分。直到满足算法的收敛条件为止。

      K-means算法的过程可以描述为:

          算法:划分并计算基于簇中对象的平均值。 

          输入:簇的数目K和包含N个对象的数据库。 

         输出:平方误差总和最小条件下的K个簇。 

     方法:

          1) 任意选择K个对象作为初始的簇中心; 

          2) 分别计算数据集中每个元素与所选簇的中心计算距离(一般采用欧式距离),根据最近邻原则,将元素划分到相应的簇中; 

          3) 计算每个簇中对象的平均值,更新簇的中心; 

          4) 重复上面的步骤,直至更新的簇的中心与原簇的中心的差值在预定范围内或者达到预设的迭代次数; 

          5) 输出K个簇中心。 

 

     K-means 方法的时间复杂度为O(NKT),N代表总元素个数,K代表簇中心个数,T代表迭代次数。K-means算法是一种硬性划分的聚类,即每个数据点唯一地分配给一个聚类,由于事先不知道实际的聚类情况,因此可能是一种严重的局限。该算法对初始中心的选取非常敏感,初始中心随机选取,导致结果波动较大,稳定性较差。同时该算法对噪声数据和孤立点数据较为敏感。该算法通常采用欧式距离作为数据样本之间的度量方式,导致该算法对球状的簇有比较好的聚类效果,但是很难发现其他形状的簇。

#include <fstream>
#include <iomanip>
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;


template <typename DataType>
int readData( char* file_path , DataType** &data,int dimension)
{
    
    if(dimension <= 0)
        return -1;

    int data_number = 0;
    fstream infile;
    infile.open(file_path,ios::in);
    DataType datum;
    
    long int position = infile.tellg();
    while(!infile.eof())
    {
        infile >> datum;
        data_number++;
    }
    infile.close();
    position = infile.tellg();
    infile.seekg(0,ios::beg);
    position = infile.tellg();
    data_number /= dimension;
    infile.open(file_path,ios::in);
    data = new DataType*[data_number];
    
    for( int i = 0; i < data_number; i++)
    {
        data[i] = new DataType[dimension];
        for( int j = 0; j < dimension; j++)
        {
            infile >> data[i][j];
        }
    }

    infile.close();

    return data_number;
}

template <typename DataType>
void kmeans(DataType** &data,int data_number,int dimension, DataType** &centers,int K, int* &labels,  int iterations, DataType threshold)
{
    if(data == NULL)
        return;

    centers = new DataType*[K];
    labels = new int[data_number];
    DataType** sum;
    int* counts;
    sum = new DataType*[K];
    counts = new int[K];

    for(int i = 0; i < K; i++)
    {
        centers[i] = new DataType[dimension];
        sum[i] = new DataType[dimension];
        counts[i] = 0;
        for( int j = 0; j < dimension; j++)
        {
            sum[i][j] = 0;
        }
    }

    rand_init_centers(data,data_number,dimension,centers,K);
    
    int iteration_time = 0;
    DataType difference = INT_MAX;
    while( iteration_time < iterations || difference > threshold)
    {        
        for(int i = 0; i < K; i++)
        {    
            counts[i] = 0;
            for( int j = 0; j < dimension; j++)
            {
                sum[i][j] = 0;
            }
        }

        for(int i = 0; i < data_number; i++)
        {
            labels[i] = select_center(data[i],centers,K,dimension);
            counts[labels[i]]++;
            for( int j = 0; j < dimension; j++)
            {
                sum[labels[i]][j] += data[i][j];
            }
        }
        difference = 0;
        for( int i = 0; i < K; i++)
        {
            for( int j = 0; j < dimension; j++)
            {
                if(counts[i] > 0)
                {    
                    sum[i][j] /= counts[i];    
                    DataType delta = sum[i][j] - centers[i][j];
                    difference += delta*delta;
                    centers[i][j] = sum[i][j];
                    
                }                
            }
            
        }
        cout << iteration_time << '\t' << difference << endl;
        iteration_time++;
    }

    for( int i = 0; i < K; i++)
    {
        if(sum[i] != NULL)
        {
            delete[] sum[i];
        }
    }
    if(sum != NULL)
    {
        delete [] sum;
    }
    delete [] counts;
    
}

template <typename DataType>
void save_centers(char* file_path, DataType** centers,int K,int dimension)
{
    ofstream outfile;
    outfile.open(file_path,ios::out);

    for(int i = 0; i < K; i++)
    {
        outfile.setf(ios::left); 
        for(int j = 0; j < dimension; j++)
        {
            outfile.width(10);
            outfile << centers[i][j];
        }
        outfile.unsetf(ios::left);
        outfile << endl;
    }

    outfile.close();

}

template <typename DataType>
void save_labels(char* file_path, DataType** data, int* labels,int data_number,int dimension)
{
    ofstream outfile;
    outfile.open(file_path,ios::out);

    for(int i = 0; i < data_number; i++)
    {
        outfile.setf(ios::left);
        outfile.width(10);
        outfile << labels[i];
        for(int j = 0; j < dimension; j++)
        {
            outfile.width(10);
            outfile << data[i][j];
        }
        outfile.unsetf(ios::left);
        outfile<< endl;
    }

    outfile.close();
}


template <typename DataType>
void rand_init_centers(DataType** &data, int data_number,int dimension, DataType** &centers,int K)
{
    int step = data_number/K;
    for(int i = 0; i < K; i++)
    {
        srand(time(NULL));
        int m = rand()%step;
        for(int j = 0; j < dimension; j++)
        {            
            centers[i][j] = data[i*step+m][j];
        }
    }
}


template <typename DataType>
int select_center(DataType* &data_i, DataType** &centers, int K, int dimension)
{
    if( K < 0)
        return -1;
    int label = 0;

    DataType min_dist = calculate_dist(data_i, centers[0],dimension);
    DataType dist = 0;
    for(int i = 1; i < K; i++)
    {
        dist = calculate_dist(data_i, centers[i],dimension);
        if(min_dist > dist)
        {
            min_dist = dist;
            label = i;
        }
    }

    return label;
}

template <typename DataType>
DataType calculate_dist(DataType* &data_i, DataType* &centers_i,int dimension)
{
    if(data_i == NULL || centers_i == NULL)
    {
        return (DataType)-1;
    }
    DataType dist = 0;

    for( int j = 0; j < dimension; j++)
    {
        DataType delta = data_i[j] - centers_i[j];
        dist += delta*delta;
    }

    return dist;
}
Kmeans.h
#include <iostream>
using namespace std;

#include "Kmeans.h"

int main(int argc, char* argv[])
{
    float** data_source;
    float** clusters;
    int* labels;

    data_source = NULL;
    clusters = NULL;
    labels = NULL;

    int K = 5;
    int iterations = 50;
    float threshold = 0.001;
    int dimension = 1764;
    int data_number = readData("D:/Users/Surge/Desktop/test.txt",data_source,dimension);
    kmeans(data_source,data_number,dimension,clusters,K,labels,iterations,threshold);
    save_centers("D:/Users/Surge/Desktop/test_centers.txt",clusters,K,dimension);
    save_labels("D:/Users/Surge/Desktop/test_labels.txt",data_source,labels,data_number,dimension);

    for(int i = 0; i < data_number; i++)
    {
        if(data_source[i] != NULL)
        {
            delete[] data_source[i];
        }
    
    }

    for(int i = 0; i < K; i++)
    {
        if(clusters[i] != NULL)
        {
            delete[] clusters[i];
        }
    }

    if(data_source != NULL)
    {
        delete[] data_source;
    }
    if(clusters != NULL)
    {
        delete[] clusters;
    }
    if(labels != NULL)
    {
        delete[] labels;
    }
    system("pause");
    return 0;
}
main.cpp

 

转载于:https://www.cnblogs.com/surgewong/p/3666502.html

<think>嗯,用户想了解k-means聚类算法的介绍及实现方法。首先,我需要回顾一下k-means的基本概念。记得它是一种无监督学习算法,用于将数据分成k个簇。步骤的话,应该是初始化质心,分配数据点到最近的质心,重新计算质心,然后重复直到收敛。对了,初始质心的选择可能影响结果,所以有k-means++这样的改进方法。 然后,用户可能还关心实现方法。得准备用Python或者Matlab的例子。不过用户提供的引用里有Python和Matlab的代码示例,可能需要参考一下。比如引用3和4提到了Python实现,而引用1是Matlab的。需要确定用户偏向哪种语言,但问题里没有明确说明,所以最好都给出或者选一种常见的,比如Python。 接下来,考虑用户可能的需求。他们可能需要步骤的详细解释,避免太技术化的术语,但又要准确。比如,初始质心的选择,迭代过程,收敛条件等。还要提到优缺点,比如对初始值敏感,适合凸数据集,可能不适用于非球形簇。 另外,引用里提到了优化方法,比如k-means++、Mini-batch、二分k-means,这些也应该包括在回答里,作为改进方向。用户的问题还可能涉及到应用场景,比如客户分群,市场营销,所以举例子的时候可以提到这些。 需要检查LaTeX格式是否正确,确保数学表达式用$...$,比如欧氏距离公式$d(x, y) = \sqrt{\sum_{i=1}^n (x_i - y_i)^2}$。独立公式用$$...$$,比如目标函数SSE。还要注意引用标识的位置,例如在提到k-means++时引用[^2]。 可能的问题点:是否覆盖了所有关键点,步骤是否清晰,代码示例是否正确,引用是否正确关联。比如,引用4提到二分k-means,应该在优化部分提到。还要注意避免错误,比如k-means的停止条件通常是质心不再变化或迭代次数,需要准确描述。 最后,生成相关问题要考虑用户可能的后续疑问,比如如何选择k值,算法的时间复杂度,其他聚类算法的比较等。确保问题相关且有引导性。</think>### K-means聚类算法介绍及实现方法 K-means聚类算法是一种经典的无监督学习方法,通过迭代将数据集划分为$k$个簇,使得同一簇内的样本相似度高,不同簇间差异显著[^3]。 #### 一、算法原理 1. **初始化**:随机选择$k$个初始质心(每个簇的中心点)。 2. **分配样本**:计算每个样本到所有质心的距离(如欧氏距离$d(x, y) = \sqrt{\sum_{i=1}^n (x_i - y_i)^2}$),将其分配到最近的质心对应簇。 3. **更新质心**:重新计算每个簇的质心(即簇内样本均值)。 4. **迭代**:重复步骤2-3,直到质心变化小于阈值或达到最大迭代次数。 目标是最小化**簇内平方误差和(SSE)**: $$SSE = \sum_{i=1}^k \sum_{x \in C_i} \|x - \mu_i\|^2$$ 其中$\mu_i$是第$i$个簇的质心,$C_i$为第$i$个簇的样本集合[^4]。 #### 二、Python实现示例 ```python import numpy as np from sklearn.cluster import KMeans # 生成示例数据 X = np.array([[1, 2], [1, 4], [1, 0], [4, 2], [4, 4], [4, 0]]) # 使用K-means算法(k=2) kmeans = KMeans(n_clusters=2, init='k-means++', max_iter=300) kmeans.fit(X) print("质心坐标:", kmeans.cluster_centers_) print("样本所属簇:", kmeans.labels_) ``` #### 三、优化方法 - **K-means++**:优化初始质心选择,降低陷入局部最优的风险[^2]。 - **二分K-means**:通过逐步分裂簇提升稳定性。 - **Mini-batch K-means**:适用于大规模数据,每次迭代仅用部分样本加速计算。 #### 四、应用场景 1. 客户分群(如市场营销策略制定) 2. 图像压缩(颜色量化) 3. 异常检测(稀疏簇可能包含异常点) #### 五、优缺点 - **优点**:简单高效,时间复杂度为$O(n \cdot k \cdot t)$($n$样本数,$k$簇数,$t$迭代次数)。 - **缺点**:需预设$k$值;对初始质心敏感;仅适用于凸形簇。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值