1. 程式人生 > >MatLab建模學習筆記14——K-Means聚類演算法

MatLab建模學習筆記14——K-Means聚類演算法

網際網路的發展帶動雲端計算、虛擬化、大資料等IT新技術的興起,各行各業的網際網路化日趨明顯。其中大資料的興起和發展壯大成為了IT時代或者說資訊時代最為典型的特徵之一。僅就大資料本身而言,其本身就具有資料體積大、資料多樣性、價值密度低、資料更新快等特點。所以,要想獲取有價值的資訊就必須對資料進行充分有效的挖掘和分析。資料探勘是指從大量的、不完全、有噪聲的、模糊的、隨機的資料中,提取隱含在其中的、人們事先不知道的但又是潛在有用資訊和知識的過程。而且,資料探勘利用了來自如下一些領域的思想:(1) 來自統計學的抽樣、估計和假設檢驗,(2)人工智慧、模式識別和機器學習的搜尋演算法、建模技術和學習理論。資料探勘也迅速地接納了來自其他領域的思想,這些領域包括最優化、進化計算、資訊理論、訊號處理、視覺化和資訊檢索等。在對資料進行分析之前需要進行一定規則的劃分,將資料劃分成若干個聚類。
聚類分析是研究分類問題的一種統計分析方法,包括劃分法、層次法、基於密度的方法、基於網路的方法和基於模型的方法。基於劃分的基本思想就是通過迭代的方法將含有N個數據物件的資料集分成K個聚類。具體的步驟就是,先給出要劃分的個數,然後通過一定的演算法反覆的進行分組,每次得到的分組比前一次更加接近預期目標,是否優化的判定標準是同組資料之間不同資料之間的相似程度,同組資料相似程度越大,元件相似程度越小越優化。
K-means聚類演算法的核心思想隨機從資料集中選取K個點,每個點初試的代表每個類的聚類中心,然後計算剩餘各個樣本到聚類中心的距離,將它劃分到最近的簇中。然後計算每個聚類的平均值,再次劃分直到相鄰兩次聚類中心沒有明顯變化,說明聚類中心收斂。演算法的終止條件可以是一下任何一個:
(1)沒有資料物件被重新分配到不同的聚類中;
(2)聚類中心收斂;
(3)誤差平方和區域性最小。
通過對聚類思想和K-Means演算法思想的瞭解,K-Means演算法步驟如下:
(1)輸入N個數據,並選取其中K(K小於N)個數據作為初始聚類中心;
(2)對剩餘的元素分別計算到各個聚類聚點中心的距離,並將該點劃分到最近的類中;
(3)重新計算各個聚類的聚點中心;
(4)與之前的聚點中心比較,如果聚點中心發生變化,轉到(2),否則結束迭代輸出結果。
K-means演算法的特點:
(1)K-means演算法中K值事先給定,這個K值的選定難以估計;
(2)K-means演算法中,需要根據初試的聚類中心來確定一個初試劃分,然後初試劃分進行優化;
(3)K-means演算法需要不斷進行樣本分類調整,不斷計算調整後的新的聚類中心,當資料量非常大時,演算法的時間開銷很大。
(4)K-means演算法對離散點和初試K值的選取比較敏感,不同的距離吃是指對同樣的樣本資料樣本可能會有不同的結果。
案例如下:K值取2,計算下列資料的聚類中心
資料

Matlab程式碼:

clc
clear
%儲存資料
x=[0 0;1 0;0 1;1 1;2 1;1 2;2 2;3 2;6 6;7 6;8 6;6 7;7 7;8 7;9 7;7 8;8 8;9 8;8 9;9 9];
%z儲存前一次聚類中心
z=zeros(2,2);
%z1儲存K值為2的聚類中心座標
z1=zeros(2,2);
z=x(1:2,1:2);
%計算聚類中心
while 1
    count=zeros(2,1);
    allsum=zeros(2,2);
    %計算樣本資料到聚類中心的距離
    for i=1:20
        temp1=sqrt((z(1
,1)-x(i,1)).^2+(z(1,2)-x(i,2)).^2); temp2=sqrt((z(2,1)-x(i,1)).^2+(z(2,2)-x(i,2)).^2); if (temp1 < temp2) count(1)=count(1)+1; allsum(1,1)=allsum(1,1)+x(i,1); allsum(1,2)=allsum(1,2)+x(i,2); else count(2)=count(2)+1; allsum(2
,1)=allsum(2,1)+x(i,1); allsum(2,2)=allsum(2,2)+x(i,2); end end z1(1,1)=allsum(1,1)/count(1); z1(1,2)=allsum(1,2)/count(1); z1(2,1)=allsum(2,1)/count(2); z1(2,2)=allsum(2,2)/count(2); if (z == z1) break; else z=z1; end end %聚類結果 %z1為聚類中心 disp(z1); plot(x(:,1),x(:,2),'b*'); hold on plot(z(:,1),z(:,2),'ro'); title('K-means聚類圖'); xlabel('X1'); ylabel('X2');

Matlab執行結果:
Matlab執行結果

下面是之前我用C/C++語言寫的一個模擬K-means的演算法。
(一)變數和函式說明
(1)定義結構體型別,用於儲存資料點座標、所在聚類、與聚類中心距離
typedef struct point
{
float x,y;
string className;
float distance;
}Point;
(2)變數宣告
vector dataVector:儲存從檔案讀取的資料
vector classPoints:儲存聚類座標
vector &totalPoints):儲存所有的資料點
(3)函式宣告
字串轉換函式:將整型變數轉換成字串型別:
string converToString(int x);
讀入資料函式:從檔案讀入座標資料:
vector readDataFile(string fileName);
初始化資料集合函式:
void initDataset(int classNum,vector dataVector,vector &classPoints,vector &totalPoints);
計算各個資料點點距離聚點中心歐氏距離的函式:
string computerDistance(Point *p_totalPoints,vector &classPoints);
將各個點劃分到相應類的函式:
void kMeansClustering(int classNum,vector totalPoints,vector classPoints);
(二)實現思路
通過以上對K-means演算法的瞭解,該演算法主要是通過迭代的思想來求解K個聚類的中心。由於傳統陣列需要先定義再使用,且在使用的過程中不能實現陣列長度的動態增長。同時考慮到設計該演算法時,沒有涉及到迭代過程中各個資料點的插入和刪除,各個資料點具體劃分到那個聚類中,是由結構體成員變數中的className來標識,因此選用了Vector來作為儲存資料的容器,這樣從檔案輸入大量資料時,有程式自己開闢需要的儲存空間。同時,也可通過Vector向量容器提供的size和d迭代器方法,實現遍歷並輸出各個資料點所劃分的聚類。
每個資料點都含有X、Y座標,演算法初試時,指定聚類的具體個數K,初試狀態的K個聚類中心有輸入檔案的前K個數據來指定。演算法在每一次迭代中,需要計算各個點到K個聚類中心座標的歐氏距離,並選擇距離最近的一個聚類,用該聚類的名稱標識當前節點。當所有資料點遍歷完後,計算劃分到每個聚類中所有資料點X與Y的均值,並將該均值與前一次聚類中心點的座標相比較。當X與Y的誤差小於或者等於1e-6時,則結束迭代輸出收斂後的K歌聚類的中心座標。
(三)K-means演算法的看流程圖如下:
K-means演算法的看流程圖

程式碼如下:

#include<iostream>
#include<fstream>
#include<string>
#include<cstring>
#include<vector>
#include <climits>
#include<algorithm>
#include<cmath>
#include<sstream>
#include<ctime>
#include <iomanip>
using namespace std;
typedef struct point
{
    float x,y;
    string className;
    float distance;
}Point;

string converToString(int x);
void initDataset(int classNum,vector<Point> dataVector,vector<Point> &classPoints,vector<Point> &totalPoints);
vector<Point> readDataFile(string fileName);
string computerDistance(Point *p_totalPoints,vector<Point> &classPoints);
void kMeansClustering(int classNum,vector<Point> totalPoints,vector<Point> classPoints);
void stdDeviation(vector<Point> totalPoints,vector<Point> classPoints);

int main()
{
    int classNum;//記錄分類類別個數
    string fileName;//檔案路徑名
    vector<Point>  dataVector;//讀輸入資料
    vector<Point>  classPoints;//聚類座標點
    vector<Point>  totalPoints;//所有的資料點
    dataVector.clear();
    classPoints.clear();
    totalPoints.clear();
    //cout<<"Enter the fileName:";
    //cin>>fileName;
    fileName="input.txt";
    dataVector=readDataFile(fileName);//獲得資料集
    //cout<<"Enter the classNum:";
    //cin>>classNum;
    classNum=3;
    initDataset(classNum,dataVector,classPoints,totalPoints);
    kMeansClustering(classNum,totalPoints,classPoints);
    printf("Time used= %.6f\n",(double)clock()/CLOCKS_PER_SEC);
    return 0;
}

string converToString(int x)
{
    ostringstream o;
    if(o<<x)
        return o.str();
    return "conversion error";
}

vector<Point> readDataFile(string fileName)
{
    ifstream fin("input.txt");
    vector<Point> dataVector;
    float x,y;
    while(fin>>x>>y)
    {
        Point point;
        point.x=x;
        point.y=y;
        point.distance=999.9;
        dataVector.push_back(point);
    }
    fin.close();
    return dataVector;
}

void initDataset(int classNum,vector<Point>dataVector,vector<Point>&classPoints,vector<Point>&totalPoints)
{
    int i,j;
    Point point;
    for(i=0,j=1;i<dataVector.size();i++)
    {
        if(j<=classNum)
        {
            point.x=dataVector[i].x;
            point.y=dataVector[i].y;
            point.distance=dataVector[i].distance;
            point.className=converToString(j);//轉換成字元
            classPoints.push_back(point);
            j++;
        }
        point.x=dataVector[i].x;
        point.y=dataVector[i].y;
        point.distance=dataVector[i].distance;
        totalPoints.push_back(point);
    }
}

string computerDistance(Point *p_totalPoints,vector<Point> &classPoints)
{
    vector<Point>::iterator p_classPoints;
    float temp=INT_MAX;
    string className;
    for(p_classPoints=classPoints.begin();p_classPoints!=classPoints.end();p_classPoints++)
    {
        float distance=((p_classPoints->x - p_totalPoints->x)*(p_classPoints->x - p_totalPoints->x)) +((p_classPoints->y - p_totalPoints->y)*(p_classPoints->y - p_totalPoints->y));
        if(temp>distance)
        {
            temp=distance;
            (*p_classPoints).x=(*p_classPoints).x;
            (*p_classPoints).y=(*p_classPoints).y;
            className=(*p_classPoints).className;
        }
    }
    return className;
}

void kMeansClustering(int classNum,vector<Point> totalPoints,vector<Point> classPoints)
{
    float tempX=0;
    float tempY=0;
    int count=0;
    float error=INT_MAX;
    vector<Point>::iterator p_totalPoints;
    vector<Point>::iterator p_classPoints;
    Point temp;
    int i;
    while(error > 0.01*classNum)
    {
        for(p_totalPoints=totalPoints.begin();p_totalPoints!=totalPoints.end();p_totalPoints++)
        {
            //將所有的點就近分類
            string className=computerDistance(p_totalPoints,classPoints);
            (*p_totalPoints).className=className;
        }
        error=0;
        //按照均值重新劃分聚類中心點
        for(p_classPoints=classPoints.begin();p_classPoints!=classPoints.end();p_classPoints++)
        {
            count=0;
            tempX=0;
            tempY=0;
            cout<<"Partition to cluster center "<<p_classPoints->className<<":"<<endl;
            for(p_totalPoints=totalPoints.begin();p_totalPoints!=totalPoints.end();p_totalPoints++)
            {
                if((*p_totalPoints).className==(*p_classPoints).className)
                {   
                    cout<<"("<<(*p_totalPoints).x<<","<<(*p_totalPoints).y<<") ";
                    count++;
                    if(count%8==0)
                        cout<<endl;
                    tempX+=(*p_totalPoints).x;
                    tempY+=(*p_totalPoints).y;
                }
            }
            cout<<endl;
            tempX /=count;
            tempY /=count;
            error +=fabs(tempX - (*p_classPoints).x);
            error +=fabs(tempY - (*p_classPoints).y);
            //計算均值
            (*p_classPoints).x=tempX;
            (*p_classPoints).y=tempY;
        }
        stdDeviation(totalPoints,classPoints);
        int i=0;
        for(p_classPoints=classPoints.begin();p_classPoints!=classPoints.end();p_classPoints++,i++)
        {
            cout<<"Cluster center "<<i+1<<":  x="<<(*p_classPoints).x<<"  y="<<(*p_classPoints).y<<endl;
        }

        cout<<"-----------------------------------------------------------------"<<endl;

    }   
    stdDeviation(totalPoints,classPoints);
        cout<<"Result value convergence"<<endl;
        i=0;
        for(p_classPoints=classPoints.begin();p_classPoints!=classPoints.end();p_classPoints++,i++)
        {
            cout<<"Cluster center "<<i+1<<":  x="<<(*p_classPoints).x<<"  y="<<(*p_classPoints).y<<endl;
        }
        cout<<"-----------------------------------------------------------------"<<endl;
}

void stdDeviation(vector<Point> totalPoints,vector<Point> classPoints)
{
    vector<Point>::iterator p_totalPoints;
    vector<Point>::iterator p_classPoints;
    int i=0;
    for(p_classPoints=classPoints.begin();p_classPoints!=classPoints.end();p_classPoints++)
    {
        int count=0;
        i++;
        float centerX=p_classPoints->x,centerY=p_classPoints->y,sumX=0,sumY=0,stdX=0,stdY=0;
        for(p_totalPoints=totalPoints.begin();p_totalPoints!=totalPoints.end();p_totalPoints++)
        {
            if(p_totalPoints->className==p_classPoints->className)
            {
                count++;
                sumX+=(p_totalPoints->x - centerX)*(p_totalPoints->x - centerX);
                sumY+=(p_totalPoints->y - centerY)*(p_totalPoints->y - centerY);
            }
        }
        stdX=sqrt(sumX/count);
        stdY=sqrt(sumY/count);
        cout<<"standard deviation of Cluster center "<<i<<":x="<<stdX<<"  y="<<stdY<<endl;
    }
}