1. 概述
K-means聚類算法也稱k均值聚類算法,是集簡單和經典於一身的基於距離的聚類算法。它採用距離作爲相似性的評價指標,即認爲兩個對象的距離越近,其相似度就越大。該算法認爲類簇是由距離靠近的對象組成的,因此把得到緊湊且獨立的簇作爲最終目標。
2. 算法核心思想
K-means聚類算法是一種迭代求解的聚類分析算法,其步驟是隨機選取K個對象作爲初始的聚類中心,然後計算每個對象與各個種子聚類中心之間的距離,把每個對象分配給距離它最近的聚類中心。聚類中心以及分配給它們的對象就代表一個聚類。每分配一個樣本,聚類的聚類中心會根據聚類中現有的對象被重新計算。這個過程將不斷重複直到滿足某個終止條件。終止條件可以是沒有(或最小數目)對象被重新分配給不同的聚類,沒有(或最小數目)聚類中心再發生變化,誤差平方和局部最小。
3. 算法實現步驟
1、首先確定一個k值,即我們希望將數據集經過聚類得到k個集合。
2、從數據集中隨機選擇k個數據點作爲質心。
3、對數據集中每一個點,計算其與每一個質心的距離(如歐式距離),離哪個質心近,就劃分到那個質心所屬的集合。
4、把所有數據歸好集合後,一共有k個集合。然後重新計算每個集合的質心。
5、如果新計算出來的質心和原來的質心之間的距離小於某一個設置的閾值(表示重新計算的質心的位置變化不大,趨於穩定,或者說收斂),我們可以認爲聚類已經達到期望的結果,算法終止。
6、如果新質心和原質心距離變化很大,需要迭代3~5步驟。
4. 算法步驟圖解
上圖a表達了初始的數據集,假設k=2。在圖b中,我們隨機選擇了兩個k類所對應的類別質心,即圖中的紅色質心和藍色質心,然後分別求樣本中所有點到這兩個質心的距離,並標記每個樣本的類別爲和該樣本距離最小的質心的類別,如圖c所示,經過計算樣本和紅色質心和藍色質心的距離,我們得到了所有樣本點的第一輪迭代後的類別。此時我們對我們當前標記爲紅色和藍色的點分別求其新的質心,如圖d所示,新的紅色質心和藍色質心的位置已經發生了變動。圖e和圖f重複了我們在圖c和圖d的過程,即將所有點的類別標記爲距離最近的質心的類別並求新的質心。最終我們得到的兩個類別如圖f。
上圖a表達了初始的數據集,假設k=4時,圖中質心的變化。
K-means術語:
簇:所有數據的點集合,簇中的對象是相似的。
質心:簇中所有點的中心(計算所有點的中心而來)
5. K-means算法優缺點
優點:
1、原理比較簡單,實現也是很容易,收斂速度快。
2、當結果簇是密集的,而簇與簇之間區別明顯時, 它的效果較好。
3、主要需要調參的參數僅僅是簇數k。
缺點:
1、K值需要預先給定,很多情況下K值的估計是非常困難的。
2、K-Means算法對初始選取的質心點是敏感的,不同的隨機種子點得到的聚類結果完全不同 ,對結果影響很大。
3、對噪音和異常點比較的敏感。用來檢測異常值。
4、採用迭代方法,可能只能得到局部的最優解,而無法得到全局的最優解。
6. 算法實現
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Time : 2020.03.18
# Author : cehkongfu
# File : kmeans.py
# Software: eclipse
#from : https://www.cnblogs.com/ahu-lichang/p/7161613.html
import sys
#reload(sys)
#sys.setdefaultencoding('UTF-8')
from numpy import *
import matplotlib.pyplot as plt
# 加載數據
def loadDataSet(fileName): # 解析文件,按tab分割字段,得到一個浮點數字類型的矩陣
dataMat = [] # 文件的最後一個字段是類別標籤
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = list(map(float, curLine)) # 將每個元素轉成float類型
dataMat.append(fltLine)
return dataMat
# 計算歐幾里得距離
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA - vecB, 2))) # 求兩個向量之間的距離
# 構建聚簇中心,取k個(此例中k=4)隨機質心
def randCent(dataSet, k):
n = shape(dataSet)[1]
centroids = mat(zeros((k,n))) # 每個質心有n個座標值,總共要k個質心
for j in range(n):
minJ = min(dataSet[:,j])
maxJ = max(dataSet[:,j])
rangeJ = float(maxJ - minJ)
centroids[:,j] = minJ + rangeJ * random.rand(k, 1)
return centroids
# k-means 聚類算法
def kMeans(dataSet, k, distMeans =distEclud, createCent = randCent):
'''
:param dataSet: 沒有lable的數據集 (本例中是二維數據)
:param k: 分爲幾個簇
:param distMeans: 計算距離的函數
:param createCent: 獲取k個隨機質心的函數
:return: centroids: 最終確定的 k個 質心
clusterAssment: 該樣本屬於哪類 及 到該類質心距離
'''
colors=['r', 'g', 'b', 'k']
m = shape(dataSet)[0] #m=80,樣本數量
clusterAssment = mat(zeros((m,2)))
# clusterAssment第一列存放該數據所屬的中心點,第二列是該數據到中心點的距離,
centroids = createCent(dataSet, k)
clusterChanged = True # 用來判斷聚類是否已經收斂
x=array(datMat[:,0]).ravel()
y=array(datMat[:,1]).ravel()
plt.figure(figsize=(10,9))
while clusterChanged:
clusterChanged = False;
for i in range(m): # 把每一個數據點劃分到離它最近的中心點
minDist = inf; minIndex = -1;
for j in range(k):
distJI = distMeans(centroids[j,:], dataSet[i,:])
if distJI < minDist:
minDist = distJI; minIndex = j # 如果第i個數據點到第j箇中心點更近,則將i歸屬爲j
if clusterAssment[i,0] != minIndex:
clusterChanged = True # 如果分配發生變化,則需要繼續迭代
clusterAssment[i,:] = minIndex,minDist**2 # 並將第i個數據點的分配情況存入字典
#畫圖
# print centroids
for cent in range(k): # 重新計算中心點
ptsInClust = dataSet[nonzero(clusterAssment[:,0].A == cent)[0]] # 去第一列等於cent的所有列
centroids[cent,:] = mean(ptsInClust, axis = 0) # 算出這些數據的中心點
plt.cla()
plt.scatter(x,y, marker='o')
xcent=array(centroids[:,0]).ravel()
ycent=array(centroids[:,1]).ravel()
plt.scatter( xcent, ycent, marker='x', color='r', s=50)
plt.pause(2)
return centroids, clusterAssment
# --------------------測試----------------------------------------------------
# 用測試數據及測試kmeans算法
if __name__ == '__main__':
datMat = mat(loadDataSet('test.txt'))
kMeans(datMat, 4)
數據集【test.txt】
1.658985 4.285136
-3.453687 3.424321
4.838138 -1.151539
-5.379713 -3.362104
0.972564 2.924086
-3.567919 1.531611
0.450614 -3.302219
-3.487105 -1.724432
2.668759 1.594842
-3.156485 3.191137
3.165506 -3.999838
-2.786837 -3.099354
4.208187 2.984927
-2.123337 2.943366
0.704199 -0.479481
-0.392370 -3.963704
2.831667 1.574018
-0.790153 3.343144
2.943496 -3.357075
-3.195883 -2.283926
2.336445 2.875106
-1.786345 2.554248
2.190101 -1.906020
-3.403367 -2.778288
1.778124 3.880832
-1.688346 2.230267
2.592976 -2.054368
-4.007257 -3.207066
2.257734 3.387564
-2.679011 0.785119
0.939512 -4.023563
-3.674424 -2.261084
2.046259 2.735279
-3.189470 1.780269
4.372646 -0.822248
-2.579316 -3.497576
1.889034 5.190400
-0.798747 2.185588
2.836520 -2.658556
-3.837877 -3.253815
2.096701 3.886007
-2.709034 2.923887
3.367037 -3.184789
-2.121479 -4.232586
2.329546 3.179764
-3.284816 3.273099
3.091414 -3.815232
-3.762093 -2.432191
3.542056 2.778832
-1.736822 4.241041
2.127073 -2.983680
-4.323818 -3.938116
3.792121 5.135768
-4.786473 3.358547
2.624081 -3.260715
-4.009299 -2.978115
2.493525 1.963710
-2.513661 2.642162
1.864375 -3.176309
-3.171184 -3.572452
2.894220 2.489128
-2.562539 2.884438
3.491078 -3.947487
-2.565729 -2.012114
3.332948 3.983102
-1.616805 3.573188
2.280615 -2.559444
-2.651229 -3.103198
2.321395 3.154987
-1.685703 2.939697
3.031012 -3.620252
-4.599622 -2.185829
4.196223 1.126677
-2.133863 3.093686
4.668892 -2.562705
-2.793241 -2.149706
2.884105 3.043438
-2.967647 2.848696
4.479332 -1.764772
-4.905566 -2.911070