ML入門6.0 手寫K-Means 聚類 (K-Means Clustering)
K-Means 簡介
聚類算法通常被歸類於無監督學習中,由於訓練樣本是未知的,目標是通過對無對無標記訓練樣本的學習來揭示數據的內在規律和性質,爲進一步的數據分析提供基礎。聚類試圖將數據集中的樣本劃分爲若干個不相交的子集,每個子集被稱爲一個簇,通過這樣的劃分,每個簇內的樣本可能擁有相同或者類似的特徵,從而對應於特定的類別。
k均值聚類是最著名的劃分聚類算法,由於簡潔和效率使得他成爲所有聚類算法中最廣泛使用的。給定一個數據點集合和需要的聚類數目k,k由用戶指定,k均值算法根據某個距離函數反覆把數據分入k個聚類中。 百度百科
K-Means應用場景十分廣泛,如:物品傳輸優化,識別犯罪地點,客戶分類,乘車數據分析,天文數據分析等
原理簡介
聚類問題輸入:數據集合
聚類問題輸出:簇
聚類指標—距離
閔可夫斯基距離:
當p=1時就是曼哈頓距離,p=2時就是歐式距離
Jaccard距離:
K-Means算法步驟
- 選擇初始化的 k 個樣本作爲初始聚類中心,
- 針對數據集中每個樣本計算它到 k 個聚類中心的距離並將其分到距離最小的聚類中心所對應的類中;(其中)
- 針對每個類別 , 重新計算它的聚類中心
- 重複上面 2 3 兩步操作,直到達到某個中止條件(迭代次數、最小誤差變化等)
實現代碼
Func1: loadDataSet(fileName) 加載數據集
def loadDataSet(fileName): # general function to parse tab -delimited floats
"""
加載數據
:param fileName:
:return: datamat
"""
dataMat = [] # assume last column is target value
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = map(float, curLine) # map all elements to float()
dataMat.append(fltLine)
return dataMat
Func2: distEclud(vecA, vecB) 計算距離
def distEclud(vecA, vecB):
"""
計算歐式距離
:param vecA: 向量A
:param vecB: 向量B
:return: 距離
"""
return sqrt(sum(power(vecA - vecB, 2))) # la.norm(vecA-vecB)
Func3: randCent(dataSet, k)給出隨機的聚類中心
def randCent(dataSet, k):
"""
隨機聚類中心
:param dataSet: 數據集
:param k: 中心個數
:return: 中心點
"""
n = shape(dataSet)[1]
centroids = mat(zeros((k, n))) # create centroid mat
for j in range(n): # create random cluster centers, within bounds of each dimension
minJ = min(dataSet[:, j])
rangeJ = float(max(dataSet[:, j]) - minJ)
centroids[:, j] = mat(minJ + rangeJ * random.rand(k, 1))
return centroids
Func4: Kmeans(dataSet, k, distMeas=distEclud, createCent=randCent) kmeans算法實現
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
"""
Kmeans算法實現
:param dataSet:數據集
:param k: 中心個數
:param distMeas: 距離算法
:param createCent: 得到中心的算法
:return:
"""
m = shape(dataSet)[0]
clusterAssment = mat(zeros((m, 2))) # create mat to assign data points
# to a centroid, also holds SE of each point
centroids = createCent(dataSet, k)
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m): # for each data point assign it to the closest centroid
minDist = inf;
minIndex = -1
for j in range(k):
distJI = distMeas(centroids[j, :], dataSet[i, :])
if distJI < minDist:
minDist = distJI;
minIndex = j
if clusterAssment[i, 0] != minIndex: clusterChanged = True
clusterAssment[i, :] = minIndex, minDist ** 2
print(centroids)
for cent in range(k): # recalculate centroids
ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]] # get all the point in this cluster
centroids[cent, :] = mean(ptsInClust, axis=0) # assign centroid to mean
return centroids, clusterAssment
Func5: clusterClubs(numClust=5) 對數據進行聚類並繪製相關圖像
def clusterClubs(numClust=5):
"""
測試K-means並繪圖
:param numClust:簇的個數
:return: 圖像
"""
print("clusterClubs")
datList = []
for line in open('places.txt').readlines():
lineArr = line.split('\t')
datList.append([float(lineArr[4]), float(lineArr[3])])
datMat = mat(datList)
myCentroids, clustAssing = biKmeans(datMat, numClust, distMeas=distSLC)
fig = plt.figure()
rect = [0.1, 0.1, 0.8, 0.8]
scatterMarkers = ['s', 'o', '^', '8', 'p', \
'd', 'v', 'h', '>', '<']
axprops = dict(xticks=[], yticks=[])
ax0 = fig.add_axes(rect, label='ax0', **axprops)
imgP = plt.imread('Portland.png')
ax0.imshow(imgP)
ax1 = fig.add_axes(rect, label='ax1', frameon=False)
for i in range(numClust):
ptsInCurrCluster = datMat[nonzero(clustAssing[:, 0].A == i)[0], :]
markerStyle = scatterMarkers[i % len(scatterMarkers)]
ax1.scatter(ptsInCurrCluster[:, 0].flatten().A[0], ptsInCurrCluster[:, 1].flatten().A[0], marker=markerStyle,
s=90)
ax1.scatter(myCentroids[:, 0].flatten().A[0], myCentroids[:, 1].flatten().A[0], marker='+', s=300)
plt.show()
運行結果
優缺點
優點
容易理解,聚類效果不錯,雖然是局部最優, 但往往局部最優就夠了;
處理大數據集的時候,該算法可以保證較好的伸縮性;
當簇近似高斯分佈的時候,效果非常不錯;
算法複雜度低。
缺點
K 值需要人爲設定,不同 K 值得到的結果不一樣;
對初始的簇中心敏感,不同選取方式會得到不同結果;
對異常值敏感;
樣本只能歸爲一類,不適合多分類任務;
不適合太離散的分類、樣本類別不平衡的分類、非凸形狀的分類。