首先須要先介紹一下無監督學習,所謂無監督學習,就是訓練樣本中的標記信息是位置的,目標是經過對無標記訓練樣本的學習來揭示數據的內在性質以及規律。通俗得說,就是根據數據的一些內在性質,找出其內在的規律。而這一類算法,應用最爲普遍的就是「聚類」。算法
聚類算法能夠對數據進行數據歸約,即在儘量保證數據完整的前提下,減小數據的量級,以便後續處理。也能夠對聚類數據結果直接應用或分析。app
而Kmeans 算法能夠說是聚類算法裏面較爲基礎的一種算法。dom
咱們如今在二維平面上有這樣一些點函數
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 ...
它在二維平面上的分佈大概是這樣的: 學習
好,這些點看起來隱約分紅4個「簇」,那麼咱們能夠假定它就是要分紅4個「簇」。(雖然咱們能夠「看」出來是要分紅4個「簇」,但實際上也能夠分紅其餘個,好比說5個。)這裏分紅「4個簇「是咱們看出來的。而在實際應用中其實應該由機器算得,下面也會有介紹的。spa
找出4個」簇」以後,就要找出每一個「簇」的中心了,咱們能夠「看出」大概的中心點,但機器不知道啊。那麼機器是如何知道的呢?答案是經過向量距離,也叫向量類似性。這個類似性計算有多種方法,好比歐式距離,曼哈頓距離,切比雪夫距離等等。code
咱們這裏使用的是歐式距離,歐式距離其實就是反應空間中兩點的直線距離。blog
知道這些後,咱們就能夠開始讓機器計算出4個「簇」了。索引
主要作法是這樣,先隨機生成4個點,假設這4個點就是4個「簇」的中心。計算平面中每一個點到4個中心點的距離,平面中每一個點選取距離最近的那個中心做爲本身的中心。ip
此時咱們就完成第一步,將平面中全部點分紅4個」簇「。可是剛剛那幾個中心都是隨機的,這樣分紅的4個簇明顯不是咱們想要的結果。怎麼辦呢?作法以下:
如今有4個簇,根據每一個簇中全部點計算出每一個簇的新中心點。這個新中心點就會比上一個舊的中心點更優,由於它更加中心。而後使用新中心點重複第一步的步驟。即再對平面中全部點算距離,而後分發到4個新簇中。不斷迭代,直到偏差較小。
這就是 Kmeans 算法的過程了。
上面所說的分紅 4 個簇,這個 4 其實就是 Kmeans 中的K。要使用 Kmeans 首先就是要選取一個 K 做爲聚類個數。而上面的例子實際上是咱們主觀」看「出來的,但多數狀況下咱們是沒法直觀」看「出分多少個 K 比較好。那怎麼辦呢?
咱們能夠從較低的 K 值開始。使用較簡單的 Kmeans 算法的結果(即較少的迭代次數,不求最佳結果,但求最快)。計算每一個點到其歸屬的「簇」的中心點的距離,而後求和,求和結果就是偏差值。 而後再增長 K 值,再計算偏差值。好比上面的例子,咱們能夠從 K=2 開始,計算 K 值從 2 到 7 的 Kmeans 算法的偏差值。 這樣會獲得相似這樣一張圖:
裏面的 Error 能夠理解未 Kmeans 的偏差,而當分紅越多「簇」的適合,偏差確定是愈來愈小。
可是不是「簇」越多越好呢?答案是否認的,有時候「簇」過多的話是不利於咱們獲得想要的結果或是作下一步操做的。
因此咱們一般會選擇偏差減少速度比較平緩的那個臨界點,好比上圖中的 4。
能夠發現,在分紅 4 個簇以後,再增長簇的數量,偏差也不會有很大的減小。而取 4 個簇也和咱們所看到的相符。
歐氏距離是一個一般採用的距離定義,指在m維空間中兩個點之間的真實距離,計算公式以下:
而本例種的是在二維空間種,故而本例的計算公式以下:
加載數據的代碼,使用了 numpy ,先是將代碼加載成 matrix 類型。
import numpy as np def loadDataSet(fileName): ''' 加載數據集 :param fileName: :return: ''' # 初始化一個空列表 dataSet = [] # 讀取文件 fr = open(fileName) # 循環遍歷文件全部行 for line in fr.readlines(): # 切割每一行的數據 curLine = line.strip().split('\t') # 將數據轉換爲浮點類型,便於後面的計算 # fltLine = [float(x) for x in curLine] # 將數據追加到dataMat fltLine = list(map(float,curLine)) # 映射全部的元素爲 float(浮點數)類型 dataSet.append(fltLine) # 返回dataMat return np.matrix(dataSet)
接下來須要生成 K 個初始的質點,即中心點。這裏採用隨機生成的方法生成 k 個「簇」。
def randCent(dataMat, k): ''' 爲給定數據集構建一個包含K個隨機質心的集合, 隨機質心必需要在整個數據集的邊界以內,這能夠經過找到數據集每一維的最小和最大值來完成 而後生成0到1.0之間的隨機數並經過取值範圍和最小值,以便確保隨機點在數據的邊界以內 :param dataMat: :param k: :return: ''' # 獲取樣本數與特徵值 m, n = np.shape(dataMat) # 初始化質心,建立(k,n)個以零填充的矩陣 centroids = np.mat(np.zeros((k, n))) # 循環遍歷特徵值 for j in range(n): # 計算每一列的最小值 minJ = min(dataMat[:, j]) # 計算每一列的範圍值 rangeJ = float(max(dataMat[:, j]) - minJ) # 計算每一列的質心,並將值賦給centroids centroids[:, j] = np.mat(minJ + rangeJ * np.random.rand(k, 1)) # 返回質心 return centroids
歐式距離計算
def distEclud(vecA, vecB): ''' 歐氏距離計算函數 :param vecA: :param vecB: :return: ''' return np.sqrt(sum(np.power(vecA - vecB, 2)))
cost 方法將執行一個簡化的 kMeans ,即較少次數的迭代,計算出其中的偏差(即當前點到簇質心的距離,後面會使用該偏差來評價聚類的效果)
def cost(dataMat, k, distMeas=distEclud, createCent=randCent,iterNum=300): ''' 計算偏差的多少,經過這個方法來肯定 k 爲多少比較合適,這個其實就是一個簡化版的 kMeans :param dataMat: 數據集 :param k: 簇的數目 :param distMeans: 計算距離 :param createCent: 建立初始質心 :param iterNum:默認迭代次數 :return: ''' # 獲取樣本數和特徵數 m, n = np.shape(dataMat) # 初始化一個矩陣來存儲每一個點的簇分配結果 # clusterAssment包含兩個列:一列記錄簇索引值,第二列存儲偏差(偏差是指當前點到簇質心的距離,後面會使用該偏差來評價聚類的效果) clusterAssment = np.mat(np.zeros((m, 2))) # 建立質心,隨機K個質心 centroids = createCent(dataMat, k) clusterChanged = True while iterNum > 0: clusterChanged = False # 遍歷全部數據找到距離每一個點最近的質心, # 能夠經過對每一個點遍歷全部質心並計算點到每一個質心的距離來完成 for i in range(m): minDist = np.inf minIndex = -1 for j in range(k): # 計算數據點到質心的距離 # 計算距離是使用distMeas參數給出的距離公式,默認距離函數是distEclud distJI = distMeas(centroids[j, :], dataMat[i, :]) # print(distJI) # 若是距離比minDist(最小距離)還小,更新minDist(最小距離)和最小質心的index(索引) if distJI < minDist: minDist = distJI minIndex = j # 更新簇分配結果爲最小質心的index(索引),minDist(最小距離)的平方 clusterAssment[i, :] = minIndex, minDist ** 2 iterNum -= 1; # print(centroids) # 遍歷全部質心並更新它們的取值 for cent in range(k): # 經過數據過濾來得到給定簇的全部點 ptsInClust = dataMat[np.nonzero(clusterAssment[:, 0].A == cent)[0]] # 計算全部點的均值,axis=0表示沿矩陣的列方向進行均值計算 centroids[cent, :] = np.mean(ptsInClust, axis=0) # 返回給定迭代次數後偏差的值 return np.mat(clusterAssment[:,1].sum(0))[0,0]
最後能夠調用 Kmeans 算法來進行計算。
def kMeans(dataMat, k, distMeas=distEclud, createCent=randCent): ''' 建立K個質心,而後將每一個店分配到最近的質心,再從新計算質心。 這個過程重複數次,直到數據點的簇分配結果再也不改變爲止 :param dataMat: 數據集 :param k: 簇的數目 :param distMeans: 計算距離 :param createCent: 建立初始質心 :return: ''' # 獲取樣本數和特徵數 m, n = np.shape(dataMat) # 初始化一個矩陣來存儲每一個點的簇分配結果 # clusterAssment包含兩個列:一列記錄簇索引值,第二列存儲偏差(偏差是指當前點到簇質心的距離,後面會使用該偏差來評價聚類的效果) clusterAssment = np.mat(np.zeros((m, 2))) # 建立質心,隨機K個質心 centroids = createCent(dataMat, k) # 初始化標誌變量,用於判斷迭代是否繼續,若是True,則繼續迭代 clusterChanged = True while clusterChanged: clusterChanged = False # 遍歷全部數據找到距離每一個點最近的質心, # 能夠經過對每一個點遍歷全部質心並計算點到每一個質心的距離來完成 for i in range(m): minDist = np.inf minIndex = -1 for j in range(k): # 計算數據點到質心的距離 # 計算距離是使用distMeas參數給出的距離公式,默認距離函數是distEclud distJI = distMeas(centroids[j, :], dataMat[i, :]) # 若是距離比minDist(最小距離)還小,更新minDist(最小距離)和最小質心的index(索引) if distJI < minDist: minDist = distJI minIndex = j # 若是任一點的簇分配結果發生改變,則更新clusterChanged標誌 if clusterAssment[i, 0] != minIndex: clusterChanged = True # 更新簇分配結果爲最小質心的index(索引),minDist(最小距離)的平方 clusterAssment[i, :] = minIndex, minDist ** 2 # print(centroids) # 遍歷全部質心並更新它們的取值 for cent in range(k): # 經過數據過濾來得到給定簇的全部點 ptsInClust = dataMat[np.nonzero(clusterAssment[:, 0].A == cent)[0]] # 計算全部點的均值,axis=0表示沿矩陣的列方向進行均值計算 centroids[cent, :] = np.mean(ptsInClust, axis=0) # 返回全部的類質心與點分配結果 return centroids, clusterAssment
選取不一樣的 k 值對結果影響有多大呢?咱們來看看就知道了,下面給出的是 k 值爲 2 到 6 的效果。 圖中紅色方塊即爲「簇」的中心點,每一個「簇」所屬的點用不一樣的顏色表示。 K = 2
K = 3
K = 4
K = 5
K = 6