機器學習——聚類(clustering):K-means演算法(非監督學習)
1、歸類
聚類(clustering):屬於非監督學習(unsupervised learning),是無類別標記(class label)
2、舉例
3、K-means演算法
(1)K-means演算法是聚類(clustering)中的經典演算法,資料探勘的十大經典演算法之一
(2)演算法接收引數K,然後將事先輸入的n個數據劃分為K個聚類以便使得所獲得的聚類滿足:
同一聚類中的物件相似度較高,而不同聚類中的物件相似度較低。
(3)演算法思想:以空間中K個點作為中心進行聚類,對最靠近它們的物件進行歸類;
通過迭代的方法,逐漸更新各聚類中心的值,直至得到最好的聚類結果。
(4)演算法的描述:
a、適當選擇c個類的初始中心
b、在第K次迭代中,對任意一個樣本,求其到c各中心的距離,將該樣本歸類到距離最短的中心所在的類。
c、利用均值等方法更新該類的中心值
d、對所有的c個聚類中心,如果利用b、c的迭代更新後,值保持不變,則迭代結束;否則繼續迭代。
(5)演算法的流程:
輸入K,data[n]
a、選擇K個初始中心,例如:c[0] = data[0],...,c[k-1] = data[k-1];
b、對於data[0]......data[n]分別c[0].....c[k-1]進行比較,假定與c[i]距離最小,就標記為i;
c、對於所有標記為i點,重新計算中心點c[i] = {所有標記為i的data[i]之和}/標記為i的個數;
d、重複b、c,直到所有的c[i]的值的變化小於給定的閾值。
4、舉例
舉例:將下面的4顆藥分成兩類,K值即為:2。
這裡給出的特徵值是2維的,可以表示在一個平面上;不論是幾維的向量,都可以在一個超平面上表示出來。
(1)初始兩類中心點分別為A(1,1)、B(2,1),對4個點分別計算到2箇中心點的歐式距離,距離哪個類的距離小,就歸為那一類。
矩陣D的第一行表示:4個點到第一個類中心(1,1)的距離,第二行表示:4個點到第二個類中心(2,1)的距離。
比較每個點到兩類的距離,歸為距離小的類,得到歸類的矩陣如下:1表示:歸為此類;0表示:不歸為此類。
第一次歸類結果為:A歸為:第一類;B、C、D歸為第二類。
(2)利用均值,重新計算每個聚類中心點座標
c1=(1,1);c2 = ((2+4+5)/3,(1+3+4)/3)=(11/3,8/3)
更新每個聚類中心點座標之後如下圖:
(3)再次計算4個點到新的聚類中心點的距離,第二次歸類結果為:A、B歸為:第一類;C、D歸為第二類。
(4)繼續迭代,求新的聚類中心的座標,繼續分類,直到分類結果不再發生變化。
(5)最終的分類結果:A、B歸為:第一類;C、D歸為第二類。
5、K-means演算法的優缺點:
優點:速度快、簡單
缺點:最終結果跟初始點選擇相關,容易陷入區域性最優,需要知道K值,有可能在分類之前不知道要分成幾類。
6、在Python中實現K-means演算法的上述例項
(1)隨機選取初始化中心點的情況
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:ZhengzhengLiu
import numpy as np
#x:資料集是一個numpy陣列(每行代表一個數據點,每列代表一個特徵值),k:分類數,maxIt:迭代次數
def kmeans(X,k,maxIt):
numPoints, numDim = X.shape #返回資料集X的行數和列數
dataSet = np.zeros((numPoints,numDim+1)) #初始化新的資料集dataSet比X多一列,用來存放分標籤
dataSet[:,:-1] = X #dataSet的所有行和除去最後一列的所有列的數值與X相同
#隨機初始化k箇中心點(利用randint函式從所有行例項中隨機選出k個例項作為中心點)
centroids = dataSet[np.random.randint(numPoints,size=k),:]
#centroids = dataSet[0:2,:] #初始化前兩個例項作為中心點(也可以隨機選取初始化中心點)
#為中心點最後一列初始化分類標記1到k
centroids[:,-1] = range(1,k+1)
iterations = 0 #迴圈次數
oldCentroids = None #舊的中心點
while not shouldStop(oldCentroids,centroids,iterations,maxIt):
print("iterations:\n",iterations) #列印當前迴圈迭代次數
print("dataSet:\n",dataSet) #列印當前資料集
print("centroids:\n",centroids) #列印當前的中心
oldCentroids = np.copy(centroids) #將當前中心點賦值到舊中心點中
iterations += 1 #迭代次數加1
#依照中心點為每個例項歸類
updateLabels(dataSet,centroids)
#根據歸類後資料集和k值,計算新的中心點
centroids = getCentroids(dataSet,k)
return dataSet
#迭代停止函式
def shouldStop(oldCentroids,centroids,iterations,maxIt):
if iterations > maxIt:
return True #迭代次數比最大迭代次數大,則停止迭代
return np.array_equal(oldCentroids,centroids) #比較新舊中心點的值是否相等,相等返回True,迭代終止
#依照中心點為每個例項歸類
def updateLabels(dataSet,centroids):
numPoints, numDim = dataSet.shape #獲取當前資料集的行列數
for i in range(0,numPoints):
#當前行例項與中心點的距離最近的標記作為該例項的標記
dataSet[i,-1] = getLabelFromClosesCentroid(dataSet[i,:-1],centroids)
def getLabelFromClosesCentroid(dataSetRow,centroids):
label = centroids[0,-1] #初始化當前標記賦值為第一個中心點的標記(第一行最後一列)
minDist = np.linalg.norm(dataSetRow - centroids[0,:-1]) #初始化計算當前行例項與第一個中心點例項的歐氏距離
for i in range(1,centroids.shape[0]): #遍歷第二個到最後一箇中心點
dist = np.linalg.norm(dataSetRow - centroids[i,:-1]) #計算當前行例項與每一箇中心點例項的歐氏距離
if dist < minDist:
minDist = dist
label = centroids[i,-1] #若當前的歐氏距離比初始化的小,則取當前中心點的標記作為該例項標記
print("minDist:",minDist)
return label
##根據歸類後資料集和k值,計算新的中心點
def getCentroids(dataSet,k):
#最後返回的新的中心點的值有k行,列數與dataSet相同
result = np.zeros((k,dataSet.shape[1]))
for i in range(1,k+1):
#將所有標記是當前同一類的例項的資料組成一個類
oneCluster = dataSet[dataSet[:,-1] == i,:-1]
result[i-1,:-1] = np.mean(oneCluster,axis = 0) #對同一類的例項求均值找出新的中心點
result[i-1,-1] = i
return result
x1 = np.array([1,1])
x2 = np.array([2,1])
x3 = np.array([4,3])
x4 = np.array([5,4])
testX = np.vstack((x1,x2,x3,x4)) #垂直方向合併numpy陣列
result = kmeans(testX,2,10)
print("final result:",result)
執行結果:
iterations:
0
dataSet:
[[ 1. 1. 0.]
[ 2. 1. 0.]
[ 4. 3. 0.]
[ 5. 4. 0.]]
centroids:
[[ 5. 4. 1.]
[ 2. 1. 2.]]
minDist: 1.0
minDist: 0.0
minDist: 1.41421356237
minDist: 0.0
iterations:
1
dataSet:
[[ 1. 1. 2.]
[ 2. 1. 2.]
[ 4. 3. 1.]
[ 5. 4. 1.]]
centroids:
[[ 4.5 3.5 1. ]
[ 1.5 1. 2. ]]
minDist: 0.5
minDist: 0.5
minDist: 0.707106781187
minDist: 0.707106781187
final result: [[ 1. 1. 2.]
[ 2. 1. 2.]
[ 4. 3. 1.]
[ 5. 4. 1.]]
(2)初始化前兩個例項作為中心點的情況
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:ZhengzhengLiu
import numpy as np
#x:資料集是一個numpy陣列(每行代表一個數據點,每列代表一個特徵值),k:分類數,maxIt:迭代次數
def kmeans(X,k,maxIt):
numPoints, numDim = X.shape #返回資料集X的行數和列數
dataSet = np.zeros((numPoints,numDim+1)) #初始化新的資料集dataSet比X多一列,用來存放分標籤
dataSet[:,:-1] = X #dataSet的所有行和除去最後一列的所有列的數值與X相同
#隨機初始化k箇中心點(利用randint函式從所有行例項中隨機選出k個例項作為中心點)
centroids = dataSet[np.random.randint(numPoints,size=k),:]
centroids = dataSet[0:2,:] #初始化前兩個例項作為中心點(也可以隨機選取初始化中心點)
#為中心點最後一列初始化分類標記1到k
centroids[:,-1] = range(1,k+1)
iterations = 0 #迴圈次數
oldCentroids = None #舊的中心點
while not shouldStop(oldCentroids,centroids,iterations,maxIt):
print("iterations:\n",iterations) #列印當前迴圈迭代次數
print("dataSet:\n",dataSet) #列印當前資料集
print("centroids:\n",centroids) #列印當前的中心
oldCentroids = np.copy(centroids) #將當前中心點賦值到舊中心點中
iterations += 1 #迭代次數加1
#依照中心點為每個例項歸類
updateLabels(dataSet,centroids)
#根據歸類後資料集和k值,計算新的中心點
centroids = getCentroids(dataSet,k)
return dataSet
#迭代停止函式
def shouldStop(oldCentroids,centroids,iterations,maxIt):
if iterations > maxIt:
return True #迭代次數比最大迭代次數大,則停止迭代
return np.array_equal(oldCentroids,centroids) #比較新舊中心點的值是否相等,相等返回True,迭代終止
#依照中心點為每個例項歸類
def updateLabels(dataSet,centroids):
numPoints, numDim = dataSet.shape #獲取當前資料集的行列數
for i in range(0,numPoints):
#當前行例項與中心點的距離最近的標記作為該例項的標記
dataSet[i,-1] = getLabelFromClosesCentroid(dataSet[i,:-1],centroids)
def getLabelFromClosesCentroid(dataSetRow,centroids):
label = centroids[0,-1] #初始化當前標記賦值為第一個中心點的標記(第一行最後一列)
minDist = np.linalg.norm(dataSetRow - centroids[0,:-1]) #初始化計算當前行例項與第一個中心點例項的歐氏距離
for i in range(1,centroids.shape[0]): #遍歷第二個到最後一箇中心點
dist = np.linalg.norm(dataSetRow - centroids[i,:-1]) #計算當前行例項與每一箇中心點例項的歐氏距離
if dist < minDist:
minDist = dist
label = centroids[i,-1] #若當前的歐氏距離比初始化的小,則取當前中心點的標記作為該例項標記
print("minDist:",minDist)
return label
##根據歸類後資料集和k值,計算新的中心點
def getCentroids(dataSet,k):
#最後返回的新的中心點的值有k行,列數與dataSet相同
result = np.zeros((k,dataSet.shape[1]))
for i in range(1,k+1):
#將所有標記是當前同一類的例項的資料組成一個類
oneCluster = dataSet[dataSet[:,-1] == i,:-1]
result[i-1,:-1] = np.mean(oneCluster,axis = 0) #對同一類的例項求均值找出新的中心點
result[i-1,-1] = i
return result
x1 = np.array([1,1])
x2 = np.array([2,1])
x3 = np.array([4,3])
x4 = np.array([5,4])
testX = np.vstack((x1,x2,x3,x4)) #垂直方向合併numpy陣列
result = kmeans(testX,2,10)
print("final result:",result)
執行結果:
iterations:
0
dataSet:
[[ 1. 1. 1.]
[ 2. 1. 2.]
[ 4. 3. 0.]
[ 5. 4. 0.]]
centroids:
[[ 1. 1. 1.]
[ 2. 1. 2.]]
minDist: 0.0
minDist: 0.0
minDist: 2.82842712475
minDist: 4.24264068712
iterations:
1
dataSet:
[[ 1. 1. 1.]
[ 2. 1. 2.]
[ 4. 3. 2.]
[ 5. 4. 2.]]
centroids:
[[ 1. 1. 1. ]
[ 3.66666667 2.66666667 2. ]]
minDist: 0.0
minDist: 1.0
minDist: 0.471404520791
minDist: 1.88561808316
iterations:
2
dataSet:
[[ 1. 1. 1.]
[ 2. 1. 1.]
[ 4. 3. 2.]
[ 5. 4. 2.]]
centroids:
[[ 1.5 1. 1. ]
[ 4.5 3.5 2. ]]
minDist: 0.5
minDist: 0.5
minDist: 0.707106781187
minDist: 0.707106781187
final result: [[ 1. 1. 1.]
[ 2. 1. 1.]
[ 4. 3. 2.]
[ 5. 4. 2.]]
對比兩種情況的聚類,結果一致。