python Kmeans算法解析

一. 概述

首先需要先介紹一下無監(jiān)督學(xué)習(xí),所謂無監(jiān)督學(xué)習(xí),就是訓(xùn)練樣本中的標(biāo)記信息是位置的,目標(biāo)是通過對(duì)無標(biāo)記訓(xùn)練樣本的學(xué)習(xí)來揭示數(shù)據(jù)的內(nèi)在性質(zhì)以及規(guī)律汤锨。通俗得說,就是根據(jù)數(shù)據(jù)的一些內(nèi)在性質(zhì)百框,找出其內(nèi)在的規(guī)律闲礼。而這一類算法,應(yīng)用最為廣泛的就是“聚類”铐维。

聚類算法可以對(duì)數(shù)據(jù)進(jìn)行數(shù)據(jù)歸約柬泽,即在盡可能保證數(shù)據(jù)完整的前提下,減少數(shù)據(jù)的量級(jí)方椎,以便后續(xù)處理聂抢。也可以對(duì)聚類數(shù)據(jù)結(jié)果直接應(yīng)用或分析。

而Kmeans 算法可以說是聚類算法里面較為基礎(chǔ)的一種算法棠众。

二. 從樣例開始

我們現(xiàn)在在二維平面上有這樣一些點(diǎn)
x y 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 ...
它在二維平面上的分布大概是這樣的:


image

好,這些點(diǎn)看起來隱約分成4個(gè)“簇”有决,那么我們可以假定它就是要分成4個(gè)“簇”闸拿。(雖然我們可以“看”出來是要分成4個(gè)“簇”,但實(shí)際上也可以分成其他個(gè)书幕,比如說5個(gè)新荤。)這里分成“4個(gè)簇“是我們看出來的。而在實(shí)際應(yīng)用中其實(shí)應(yīng)該由機(jī)器算得台汇,下面也會(huì)有介紹的苛骨。

找出4個(gè)”簇”之后,就要找出每個(gè)“簇”的中心了苟呐,我們可以“看出”大概的中心點(diǎn)痒芝,但機(jī)器不知道啊。那么機(jī)器是如何知道的呢牵素?答案是通過向量距離严衬,也叫向量相似性。這個(gè)相似性計(jì)算有多種方法笆呆,比如歐式距離请琳,曼哈頓距離胡桃,切比雪夫距離等等围苫。

我們這里使用的是歐式距離,歐式距離其實(shí)就是反應(yīng)空間中兩點(diǎn)的直線距離膨蛮。

知道這些后榕堰,我們就可以開始讓機(jī)器計(jì)算出4個(gè)“簇”了竖慧。

主要做法是這樣,先隨機(jī)生成4個(gè)點(diǎn),假設(shè)這4個(gè)點(diǎn)就是4個(gè)“簇”的中心测蘑。計(jì)算平面中每個(gè)點(diǎn)到4個(gè)中心點(diǎn)的距離灌危,平面中每個(gè)點(diǎn)選取距離最近的那個(gè)中心作為自己的中心。

此時(shí)我們就完成第一步碳胳,將平面中所有點(diǎn)分成4個(gè)”簇“勇蝙。但是剛剛那幾個(gè)中心都是隨機(jī)的,這樣分成的4個(gè)簇明顯不是我們想要的結(jié)果挨约。怎么辦呢味混?做法如下:

現(xiàn)在有4個(gè)簇,根據(jù)每個(gè)簇中所有點(diǎn)計(jì)算出每個(gè)簇的新中心點(diǎn)诫惭。這個(gè)新中心點(diǎn)就會(huì)比上一個(gè)舊的中心點(diǎn)更優(yōu)翁锡,因?yàn)樗又行摹H缓笫褂眯轮行狞c(diǎn)重復(fù)第一步的步驟夕土。即再對(duì)平面中所有點(diǎn)算距離馆衔,然后分發(fā)到4個(gè)新簇中。不斷迭代怨绣,直到誤差較小角溃。

這就是 Kmeans 算法的過程了。

三. 知識(shí)點(diǎn)淺析

3.1 確定“簇”的個(gè)數(shù)

上面所說的分成 4 個(gè)簇篮撑,這個(gè) 4 其實(shí)就是 Kmeans 中的K减细。要使用 Kmeans 首先就是要選取一個(gè) K 作為聚類個(gè)數(shù)。而上面的例子其實(shí)是我們主觀”看“出來的赢笨,但多數(shù)情況下我們是無法直觀”看“出分多少個(gè) K 比較好未蝌。那怎么辦呢?

我們可以從較低的 K 值開始茧妒。使用較簡(jiǎn)單的 Kmeans 算法的結(jié)果(即較少的迭代次數(shù)萧吠,不求最佳結(jié)果,但求最快)嘶伟。計(jì)算每個(gè)點(diǎn)到其歸屬的“簇”的中心點(diǎn)的距離怎憋,然后求和,求和結(jié)果就是誤差值九昧。
然后再增加 K 值绊袋,再計(jì)算誤差值。比如上面的例子铸鹰,我們可以從 K=2 開始癌别,計(jì)算 K 值從 2 到 7 的 Kmeans 算法的誤差值。
這樣會(huì)得到類似這樣一張圖:

image

里面的 Error 可以理解未 Kmeans 的誤差蹋笼,而當(dāng)分成越多“簇”的適合展姐,誤差肯定是越來越小躁垛。

但是不是“簇”越多越好呢?答案是否定的圾笨,有時(shí)候“簇”過多的話是不利于我們得到想要的結(jié)果或是做下一步操作的教馆。

所以我們通常會(huì)選擇誤差減小速度比較平緩的那個(gè)臨界點(diǎn),比如上圖中的 4擂达。

可以發(fā)現(xiàn)土铺,在分成 4 個(gè)簇之后,再增加簇的數(shù)量板鬓,誤差也不會(huì)有很大的減少悲敷。而取 4 個(gè)簇也和我們所看到的相符。

3.2 歐式距離

3.2 歐式距離

歐氏距離是一個(gè)通常采用的距離定義俭令,指在m維空間中兩個(gè)點(diǎn)之間的真實(shí)距離后德,計(jì)算公式如下:

image

而本例種的是在二維空間種,故而本例的計(jì)算公式如下:

image

四. 代碼和結(jié)果

加載數(shù)據(jù)的代碼抄腔,使用了 numpy 瓢湃,先是將代碼加載成 matrix 類型。

import numpy as np

def loadDataSet(fileName):
'''
加載數(shù)據(jù)集
:param fileName:
:return:
'''
# 初始化一個(gè)空列表
dataSet = []
# 讀取文件
fr = open(fileName)
# 循環(huán)遍歷文件所有行
for line in fr.readlines():
# 切割每一行的數(shù)據(jù)
curLine = line.strip().split('\t')
# 將數(shù)據(jù)轉(zhuǎn)換為浮點(diǎn)類型,便于后面的計(jì)算
# fltLine = [float(x) for x in curLine]
# 將數(shù)據(jù)追加到dataMat
fltLine = list(map(float,curLine)) # 映射所有的元素為 float(浮點(diǎn)數(shù))類型
dataSet.append(fltLine)
# 返回dataMat
return np.matrix(dataSet)

接下來需要生成 K 個(gè)初始的質(zhì)點(diǎn)妓柜,即中心點(diǎn)箱季。這里采用隨機(jī)生成的方法生成 k 個(gè)“簇”。  

def randCent(dataMat, k):
'''
為給定數(shù)據(jù)集構(gòu)建一個(gè)包含K個(gè)隨機(jī)質(zhì)心的集合,
隨機(jī)質(zhì)心必須要在整個(gè)數(shù)據(jù)集的邊界之內(nèi),這可以通過找到數(shù)據(jù)集每一維的最小和最大值來完成
然后生成0到1.0之間的隨機(jī)數(shù)并通過取值范圍和最小值,以便確保隨機(jī)點(diǎn)在數(shù)據(jù)的邊界之內(nèi)
:param dataMat:
:param k:
:return:
'''
# 獲取樣本數(shù)與特征值
m, n = np.shape(dataMat)
# 初始化質(zhì)心,創(chuàng)建(k,n)個(gè)以零填充的矩陣
centroids = np.mat(np.zeros((k, n)))
# 循環(huán)遍歷特征值
for j in range(n):
# 計(jì)算每一列的最小值
minJ = min(dataMat[:, j])
# 計(jì)算每一列的范圍值
rangeJ = float(max(dataMat[:, j]) - minJ)
# 計(jì)算每一列的質(zhì)心,并將值賦給centroids
centroids[:, j] = np.mat(minJ + rangeJ * np.random.rand(k, 1))
# 返回質(zhì)心
return centroids

歐式距離計(jì)算  


def distEclud(vecA, vecB):
'''
歐氏距離計(jì)算函數(shù)
:param vecA:
:param vecB:
:return:
'''
return np.sqrt(sum(np.power(vecA - vecB, 2)))

cost 方法將執(zhí)行一個(gè)簡(jiǎn)化的 kMeans 棍掐,即較少次數(shù)的迭代,計(jì)算出其中的誤差(即當(dāng)前點(diǎn)到簇質(zhì)心的距離,后面會(huì)使用該誤差來評(píng)價(jià)聚類的效果)  


def cost(dataMat, k, distMeas=distEclud, createCent=randCent,iterNum=300):
'''
計(jì)算誤差的多少拷况,通過這個(gè)方法來確定 k 為多少比較合適作煌,這個(gè)其實(shí)就是一個(gè)簡(jiǎn)化版的 kMeans
:param dataMat: 數(shù)據(jù)集
:param k: 簇的數(shù)目
:param distMeans: 計(jì)算距離
:param createCent: 創(chuàng)建初始質(zhì)心
:param iterNum:默認(rèn)迭代次數(shù)
:return:
'''
# 獲取樣本數(shù)和特征數(shù)
m, n = np.shape(dataMat)
# 初始化一個(gè)矩陣來存儲(chǔ)每個(gè)點(diǎn)的簇分配結(jié)果
# clusterAssment包含兩個(gè)列:一列記錄簇索引值,第二列存儲(chǔ)誤差(誤差是指當(dāng)前點(diǎn)到簇質(zhì)心的距離,后面會(huì)使用該誤差來評(píng)價(jià)聚類的效果)
clusterAssment = np.mat(np.zeros((m, 2)))
# 創(chuàng)建質(zhì)心,隨機(jī)K個(gè)質(zhì)心
centroids = createCent(dataMat, k)
clusterChanged = True
while iterNum > 0:
clusterChanged = False
# 遍歷所有數(shù)據(jù)找到距離每個(gè)點(diǎn)最近的質(zhì)心,
# 可以通過對(duì)每個(gè)點(diǎn)遍歷所有質(zhì)心并計(jì)算點(diǎn)到每個(gè)質(zhì)心的距離來完成
for i in range(m):
minDist = np.inf
minIndex = -1
for j in range(k):
# 計(jì)算數(shù)據(jù)點(diǎn)到質(zhì)心的距離
# 計(jì)算距離是使用distMeas參數(shù)給出的距離公式,默認(rèn)距離函數(shù)是distEclud
distJI = distMeas(centroids[j, :], dataMat[i, :])
# print(distJI)
# 如果距離比minDist(最小距離)還小,更新minDist(最小距離)和最小質(zhì)心的index(索引)
if distJI < minDist:
minDist = distJI
minIndex = j
# 更新簇分配結(jié)果為最小質(zhì)心的index(索引),minDist(最小距離)的平方
clusterAssment[i, :] = minIndex, minDist ** 2
iterNum -= 1;
# print(centroids)
# 遍歷所有質(zhì)心并更新它們的取值
for cent in range(k):
# 通過數(shù)據(jù)過濾來獲得給定簇的所有點(diǎn)
ptsInClust = dataMat[np.nonzero(clusterAssment[:, 0].A == cent)[0]]
# 計(jì)算所有點(diǎn)的均值,axis=0表示沿矩陣的列方向進(jìn)行均值計(jì)算
centroids[cent, :] = np.mean(ptsInClust, axis=0)
# 返回給定迭代次數(shù)后誤差的值
return np.mat(clusterAssment[:,1].sum(0))[0,0]

最后可以調(diào)用 Kmeans 算法來進(jìn)行計(jì)算。

def kMeans(dataMat, k, distMeas=distEclud, createCent=randCent):
'''
創(chuàng)建K個(gè)質(zhì)心,然后將每個(gè)店分配到最近的質(zhì)心,再重新計(jì)算質(zhì)心赚瘦。
這個(gè)過程重復(fù)數(shù)次,直到數(shù)據(jù)點(diǎn)的簇分配結(jié)果不再改變?yōu)橹?br> :param dataMat: 數(shù)據(jù)集
:param k: 簇的數(shù)目
:param distMeans: 計(jì)算距離
:param createCent: 創(chuàng)建初始質(zhì)心
:return:
'''
# 獲取樣本數(shù)和特征數(shù)
m, n = np.shape(dataMat)
# 初始化一個(gè)矩陣來存儲(chǔ)每個(gè)點(diǎn)的簇分配結(jié)果
# clusterAssment包含兩個(gè)列:一列記錄簇索引值,第二列存儲(chǔ)誤差(誤差是指當(dāng)前點(diǎn)到簇質(zhì)心的距離,后面會(huì)使用該誤差來評(píng)價(jià)聚類的效果)
clusterAssment = np.mat(np.zeros((m, 2)))
# 創(chuàng)建質(zhì)心,隨機(jī)K個(gè)質(zhì)心
centroids = createCent(dataMat, k)
# 初始化標(biāo)志變量,用于判斷迭代是否繼續(xù),如果True,則繼續(xù)迭代
clusterChanged = True
while clusterChanged:
clusterChanged = False
# 遍歷所有數(shù)據(jù)找到距離每個(gè)點(diǎn)最近的質(zhì)心,
# 可以通過對(duì)每個(gè)點(diǎn)遍歷所有質(zhì)心并計(jì)算點(diǎn)到每個(gè)質(zhì)心的距離來完成
for i in range(m):
minDist = np.inf
minIndex = -1
for j in range(k):
# 計(jì)算數(shù)據(jù)點(diǎn)到質(zhì)心的距離
# 計(jì)算距離是使用distMeas參數(shù)給出的距離公式,默認(rèn)距離函數(shù)是distEclud
distJI = distMeas(centroids[j, :], dataMat[i, :])
# 如果距離比minDist(最小距離)還小,更新minDist(最小距離)和最小質(zhì)心的index(索引)
if distJI < minDist:
minDist = distJI
minIndex = j
# 如果任一點(diǎn)的簇分配結(jié)果發(fā)生改變,則更新clusterChanged標(biāo)志
if clusterAssment[i, 0] != minIndex: clusterChanged = True
# 更新簇分配結(jié)果為最小質(zhì)心的index(索引),minDist(最小距離)的平方
clusterAssment[i, :] = minIndex, minDist ** 2
# print(centroids)
# 遍歷所有質(zhì)心并更新它們的取值
for cent in range(k):
# 通過數(shù)據(jù)過濾來獲得給定簇的所有點(diǎn)
ptsInClust = dataMat[np.nonzero(clusterAssment[:, 0].A == cent)[0]]
# 計(jì)算所有點(diǎn)的均值,axis=0表示沿矩陣的列方向進(jìn)行均值計(jì)算
centroids[cent, :] = np.mean(ptsInClust, axis=0)
# 返回所有的類質(zhì)心與點(diǎn)分配結(jié)果
return centroids, clusterAssment

選取不同的 k 值對(duì)結(jié)果影響有多大呢粟誓?我們來看看就知道了,下面給出的是 k 值為 2 到 6 的效果起意。
圖中紅色方塊即為“簇”的中心點(diǎn)鹰服,每個(gè)“簇”所屬的點(diǎn)用不同的顏色表示。
K = 2


image

K = 3


image

K = 4


image

K = 5


image

K = 6


image

以上就是 Kmeans 算法的內(nèi)容啦揽咕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悲酷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子亲善,更是在濱河造成了極大的恐慌设易,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛹头,死亡現(xiàn)場(chǎng)離奇詭異顿肺,居然都是意外死亡戏溺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門屠尊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旷祸,“玉大人,你說我怎么就攤上這事讼昆⊥邢恚” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵控淡,是天一觀的道長(zhǎng)嫌吠。 經(jīng)常有香客問我,道長(zhǎng)掺炭,這世上最難降的妖魔是什么辫诅? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮涧狮,結(jié)果婚禮上炕矮,老公的妹妹穿的比我還像新娘。我一直安慰自己者冤,他們只是感情好肤视,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涉枫,像睡著了一般邢滑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上愿汰,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天困后,我揣著相機(jī)與錄音,去河邊找鬼衬廷。 笑死摇予,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吗跋。 我是一名探鬼主播侧戴,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼跌宛!你這毒婦竟也來了酗宋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤秩冈,失蹤者是張志新(化名)和其女友劉穎本缠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體入问,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丹锹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年稀颁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楣黍。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匾灶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出租漂,到底是詐尸還是另有隱情阶女,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布哩治,位于F島的核電站秃踩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏业筏。R本人自食惡果不足惜憔杨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒜胖。 院中可真熱鬧消别,春花似錦、人聲如沸台谢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朋沮。三九已至蛇券,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間樊拓,已是汗流浹背怀读。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骑脱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓苍糠,卻偏偏與公主長(zhǎng)得像叁丧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子岳瞭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容