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執行結果:
下面是之前我用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演算法的看流程圖如下:
程式碼如下:
#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;
}
}