最近在看《機器學習實戰》這本書,因為自己本身很想深入的了解機器學習算法,加之想學python,就在朋友的推薦之下選擇了這本書進行學習,在寫這篇文章之前對FCM有過一定的了解,所以對K均值算法有一種莫名的親切感,言歸正傳,今天我和大家一起來學習K-均值聚類算法。
一 K-均值聚類(K-means)概述
1. 聚類
“類”指的是具有相似性的集合。聚類是指將數據集划分為若干類,使得類內之間的數據最為相似,各類之間的數據相似度差別盡可能大。聚類分析就是以相似性為基礎,對數據集進行聚類划分,屬於無監督學習。
2. 無監督學習和監督學習
上一篇對KNN進行了驗證,和KNN所不同,K-均值聚類屬於無監督學習。那么監督學習和無監督學習的區別在哪兒呢?監督學習知道從對象(數據)中學習什么,而無監督學習無需知道所要搜尋的目標,它是根據算法得到數據的共同特征。比如用分類和聚類來說,分類事先就知道所要得到的類別,而聚類則不一樣,只是以相似度為基礎,將對象分得不同的簇。
3. K-means
k-means算法是一種簡單的迭代型聚類算法,采用距離作為相似性指標,從而發現給定數據集中的K個類,且每個類的中心是根據類中所有值的均值得到,每個類用聚類中心來描述。對於給定的一個包含n個d維數據點的數據集X以及要分得的類別K,選取歐式距離作為相似度指標,聚類目標是使得各類的聚類平方和最小,即最小化:
結合最小二乘法和拉格朗日原理,聚類中心為對應類別中各數據點的平均值,同時為了使得算法收斂,在迭代過程中,應使最終的聚類中心盡可能的不變。
4. 算法流程
K-means是一個反復迭代的過程,算法分為四個步驟:
1) 選取數據空間中的K個對象作為初始中心,每個對象代表一個聚類中心;
2) 對於樣本中的數據對象,根據它們與這些聚類中心的歐氏距離,按距離最近的准則將它們分到距離它們最近的聚類中心(最相似)所對應的類;
3) 更新聚類中心:將每個類別中所有對象所對應的均值作為該類別的聚類中心,計算目標函數的值;
4) 判斷聚類中心和目標函數的值是否發生改變,若不變,則輸出結果,若改變,則返回2)。
用以下例子加以說明:
圖1 圖2
圖3 圖4
圖1:給定一個數據集;
圖2:根據K = 5初始化聚類中心,保證 聚類中心處於數據空間內;
圖3:根據計算類內對象和聚類中心之間的相似度指標,將數據進行划分;
圖4:將類內之間數據的均值作為聚類中心,更新聚類中心。
最后判斷算法結束與否即可,目的是為了保證算法的收斂。
二 python實現
首先,需要說明的是,我采用的是python2.7,直接上代碼:
#k-means算法的實現 #-*-coding:utf-8 -*-
from numpy import *
from math import sqrt import sys sys.path.append("C:/Users/Administrator/Desktop/k-means的python實現") def loadData(fileName): data = [] fr = open(fileName) for line in fr.readlines(): curline = line.strip().split('\t') frline = map(float,curline) data.append(frline) return data ''' #test a = mat(loadData("C:/Users/Administrator/Desktop/k-means/testSet.txt")) print a '''
#計算歐氏距離
def distElud(vecA,vecB): return sqrt(sum(power((vecA - vecB),2))) #初始化聚類中心
def randCent(dataSet,k): n = shape(dataSet)[1] center = mat(zeros((k,n))) for j in range(n): rangeJ = float(max(dataSet[:,j]) - min(dataSet[:,j])) center[:,j] = min(dataSet[:,j]) + rangeJ * random.rand(k,1) return center ''' #test a = mat(loadData("C:/Users/Administrator/Desktop/k-means/testSet.txt")) n = 3 b = randCent(a,3) print b '''
def kMeans(dataSet,k,dist = distElud,createCent = randCent): m = shape(dataSet)[0] clusterAssment = mat(zeros((m,2))) center = createCent(dataSet,k) clusterChanged = True while clusterChanged: clusterChanged = False for i in range(m): minDist = inf minIndex = -1
for j in range(k): distJI = dist(dataSet[i,:],center[j,:]) if distJI < minDist: minDist = distJI minIndex = j if clusterAssment[i,0] != minIndex:#判斷是否收斂
clusterChanged = True clusterAssment[i,:] = minIndex,minDist ** 2
print center for cent in range(k):#更新聚類中心
dataCent = dataSet[nonzero(clusterAssment[:,0].A == cent)[0]] center[cent,:] = mean(dataCent,axis = 0)#axis是普通的將每一列相加,而axis=1表示的是將向量的每一行進行相加
return center,clusterAssment ''' #test dataSet = mat(loadData("C:/Users/Administrator/Desktop/k-means/testSet.txt")) k = 4 a = kMeans(dataSet,k) print a '''
三 MATLAB實現
之前用MATLAB做過一些聚類算法方面的優化,自然使用它相比python更得心應手一點。根據算法的步驟,編程實現,直接上程序:
%%%K-means clear all clc %% 構造隨機數據 mu1=[0 0 0]; S1=[0.23 0 0;0 0.87 0;0 0 0.56]; data1=mvnrnd(mu1,S1,100); %產生高斯分布數據 %%第二類數據 mu2=[1.25 1.25 1.25]; S2=[0.23 0 0;0 0.87 0;0 0 0.56]; data2=mvnrnd(mu2,S2,100); %第三個類數據 mu3=[-1.25 1.25 -1.25]; S3=[0.23 0 0;0 0.87 0;0 0 0.56]; data3=mvnrnd(mu3,S3,100); mu4=[1.5 1.5 1.5]; S4=[0.23 0 0;0 0.87 0;0 0 0.56]; data4 =mvnrnd(mu4,S4,100); %顯示數據 figure; plot3(data1(:,1),data1(:,2),data1(:,3),'+'); title('原始數據'); hold on plot3(data2(:,1),data2(:,2),data2(:,3),'r+'); plot3(data3(:,1),data3(:,2),data3(:,3),'g+'); plot3(data4(:,1),data4(:,2),data3(:,3),'y+'); grid on; data=[data1;data2;data3;data4]; [row,col] = size(data); K = 4; max_iter = 300;%%迭代次數 min_impro = 0.1;%%%%最小步長 display = 1;%%%判定條件 center = zeros(K,col); U = zeros(K,col); %% 初始化聚類中心 mi = zeros(col,1); ma = zeros(col,1); for i = 1:col mi(i,1) = min(data(:,i)); ma(i,1) = max(data(:,i)); center(:,i) = ma(i,1) - (ma(i,1) - mi(i,1)) * rand(K,1); end %% 開始迭代 for o = 1:max_iter %% 計算歐氏距離,用norm函數 for i = 1:K dist{i} = []; for j = 1:row dist{i} = [dist{i};data(j,:) - center(i,:)]; end end minDis = zeros(row,K); for i = 1:row tem = []; for j = 1:K tem = [tem norm(dist{j}(i,:))]; end [nmin,index] = min(tem); minDis(i,index) = norm(dist{index}(i,:)); end %% 更新聚類中心 for i = 1:K for j = 1:col U(i,j) = sum(minDis(:,i).*data(:,j)) / sum(minDis(:,i)); end end %% 判定 if display end if o >1, if max(abs(U - center)) < min_impro; break; else center = U; end end end %% 返回所屬的類別 class = []; for i = 1:row dist = []; for j = 1:K dist = [dist norm(data(i,:) - U(j,:))]; end [nmin,index] = min(dist); class = [class;data(i,:) index]; end %% 顯示最后結果 [m,n] = size(class); figure; title('聚類結果'); hold on; for i=1:row if class(i,4)==1 plot3(class(i,1),class(i,2),class(i,3),'ro'); elseif class(i,4)==2 plot3(class(i,1),class(i,2),class(i,3),'go'); elseif class(i,4) == 3 plot3(class(i,1),class(i,2),class(i,3),'bo'); else plot3(class(i,1),class(i,2),class(i,3),'yo'); end end grid on;
最終的結果如下圖5和圖6:
圖5 原始數據
圖6 聚類結果
總結:在這次程序的調試中,其實出現的問題還是蠻多的,相似度指標依舊選用的是歐氏距離。在之前,一直是按照公式直接計算的,可歐氏距離其實就是2范數啊,2范數屬於酉不變范數,因此矩陣的2范數就是矩陣的最大奇異值,在求解過程中可以直接采用norm函數簡化。
上圖中的結果可以清晰的看到算法具有一定的聚類效果,要進一步驗證的話,可以采取MCR或者NMI和ARI這些常用的准則進行衡量聚類結果的優劣,在此我選取MCR進行驗證,代碼如下:
%% 采用MCR判定聚類效果 B = class(:,4); B = reshape(B,1,row); A = [ones(1,100),2 * ones(1,100),3 *ones(1,100),4 * ones(1,100)]; sum = 0; for i = 1:row if ( A(1,i) ~= B(1,i)) sum = sum + 1; end end MCR = sum / row; fprintf('MCR = %d\n',MCR);
多次計算平均求得的MCR= 0.53,表明誤分率還是蠻大的,聚類效果並不是很理想,究其原因:雖然算法收斂,但算法只是收斂到了局部最小值,而並非全局最小值,所以可以引入二分K-均值對算法進行優化。
除此之外,FCM算法在一定程度上也是對算法的一個優化吧。
進而導入UCI數據庫中的wine數據進行測試,結果甚是不理想,至於原因吧,算法本身的性能是占一部分的,還有可能是數據的維數相對較多......在此我也不敢妄加猜測,之后慢慢驗證吧......