Python實現K-Means聚類演算法
宣告:程式碼的執行環境為Python3。Python3與Python2在一些細節上會有所不同,希望廣大讀者注意。本部落格以程式碼為主,程式碼中會有詳細的註釋。相關文章將會發布在我的個人部落格專欄《Python從入門到深度學習》,歡迎大家關注~
根據訓練樣本是否包含標籤資訊,機器學習可以分為監督學習和無監督學習(這裡我們不考慮半監督學習)。聚類演算法是典型的無監督學習演算法,它是對事務自動歸類的一種演算法,在聚類演算法中利用樣本的標籤,將具有相似屬性的事物聚集到一類中。
一、常用的相似性度量
K-Means演算法(K-均值演算法)是基於相似性的無監督學習演算法,即通過比較樣本之間的相似性,將較為相似的樣本劃分到同一個類別中。為了度量兩個樣本(以樣本X和樣本Y為例)之間的相似性,通常會定義一個距離函式d(X,Y),利用這個距離函式來定義樣本X和樣本Y之間的相似性。
1、閔可夫斯基距離
空間中兩個點X,Y的座標分別為:
那麼,點X與點Y之間的閔可夫斯基距離可以定義為:
2、曼哈頓距離
同樣的兩個點X,Y其曼哈頓距離可以表示為:
3、歐氏距離
同樣的,點X與點Y的歐氏距離可以表示為:
由上面的定義可知,曼哈頓距離和歐式距離是閔可夫斯基距離的特殊形式。當p=1時,閔可夫斯基距離就是曼哈頓距離;當p=2時,閔可夫斯基距離就是歐式距離。在K-Means演算法中,我們使用歐氏距離作為其相似性度量。因為歐氏距離的根號存在與否對其度量性沒有影響,簡單起見,我們使用歐氏距離的平方作為最終的相似性度量。
首先將需要載入相關的模組:
import numpy as np
歐氏距離的平方實現程式碼如下:
def distance(vecA, vecB):
'''
計算兩個向量之間歐氏距離的平方
:param vecA: 向量A的座標
:param vecB: 向量B的座標
:return: 返回兩個向量之間歐氏距離的平方
'''
dist = (vecA - vecB) * (vecA - vecB).T
return dist[0, 0]
二、K-Means演算法原理
首先,我們需要人為的指定最終的聚類個數,假設我們最終的聚類個數為k個,即我們需要初始化k個聚類中心,通過計算每個樣本與聚類中心的相似度,將樣本點劃分到距離最近的聚類中心。然後,通過每個類的樣本重新計算每個類的聚類中心,重複這個操作直至聚類中心不再改變即為最終的聚類結果。
三、K-Means演算法步驟
1、隨機初始化K個聚類中心,其程式碼如下:
def randomCenter(data, k):
'''
隨機初始化聚類中心
:param data: 訓練資料
:param k: 聚類中心的個數
:return: 返回初始化的聚類中心
'''
n = np.shape(data)[1] # 特徵的個數
cent = np.mat(np.zeros((k, n))) # 初始化K個聚類中心
for j in range(n): # 初始化聚類中心每一維的座標
minJ = np.min(data[:, j])
rangeJ = np.max(data[:, j]) - minJ
cent[:, j] = minJ * np.mat(np.ones((k, 1))) + np.random.rand(k, 1) * rangeJ # 在最大值和最小值之間初始化
return cent
2、計算每個樣本與k個聚類中心的相似度,將樣本劃分到與之最相似的類中;
3、計算劃分到每個類別中所有樣本特徵的均值,並將該均值作為每個類別新的聚類中心;
4、重複2、3步操作,直至聚類中心不再改變,輸出最終的聚類中心。
構建K-Means演算法的程式碼如下:
def kmeans(data, k, cent):
'''
kmeans演算法求解聚類中心
:param data: 訓練資料
:param k: 聚類中心的個數
:param cent: 隨機初始化的聚類中心
:return: 返回訓練完成的聚類中心和每個樣本所屬的類別
'''
m, n = np.shape(data) # m:樣本的個數;n:特徵的維度
subCenter = np.mat(np.zeros((m, 2))) # 初始化每個樣本所屬的類別
change = True # 判斷是否需要重新計算聚類中心
while change == True:
change = False # 重置
for i in range(m):
minDist = np.inf # 設定樣本與聚類中心的最小距離,初始值為正無窮
minIndex = 0 # 所屬的類別
for j in range(k):
# 計算i和每個聚類中心的距離
dist = distance(data[i, ], cent[j, ])
if dist < minDist:
minDist = dist
minIndex = j
# 判斷是否需要改變
if subCenter[i, 0] != minIndex: # 需要改變
change = True
subCenter[i, ] = np.mat([minIndex, minDist])
# 重新計算聚類中心
for j in range(k):
sum_all = np.mat(np.zeros((1, n)))
r = 0 # 每個類別中樣本的個數
for i in range(m):
if subCenter[i, 0] == j: # 計算第j個類別
sum_all += data[i, ]
r += 1
for z in range(n):
try:
cent[j, z] = sum_all[0, z] / r
except:
print("ZeroDivisionError: division by zero")
return subCenter, cent
四、K-Means演算法舉例
1、資料集:資料集含有兩個特徵,如下圖所示:
2、載入資料集
我們此處使用如下方法載入資料集,也可使用其他的方式進行載入,此處可以參考我的另外一篇文章《Python兩種方式載入檔案內容》。載入檔案內容程式碼如下:
def load_data(file_path):
'''
匯入資料
:param file_path: 檔案路徑
:return: 以矩陣的形式返回匯入的資料
'''
f = open(file_path)
data = []
for words in f.readlines():
row = [] # 記錄每一行
word = words.strip().split("\t")
for x in word:
row.append(float(x)) # 將文字中的特徵轉換成浮點數
data.append(row)
f.close()
return np.mat(data) # 以矩陣的形式返回
3、儲存聚類結果
通過K-Means聚類之後,我們可以使用如下方法儲存聚類結果:
def save_result(file_name, data):
'''
儲存source中的結果到file_name檔案中
:param file_name: 儲存的檔名
:param data: 需要儲存的資料
:return:
'''
m, n = np.shape(data)
f = open(file_name, "w")
for i in range(m):
tmp = []
for j in range(n):
tmp.append(str(data[i, j]))
f.write("\t".join(tmp) + "\n")
f.close()
4、呼叫K-Means演算法
呼叫K-Means演算法:
if __name__ == "__main__":
k = 4 # 聚類中心的個數
file_path = "tfidf.txt"
subCenter, center = kmeans(data, k, randomCenter(load_data(file_path), k))
save_result("result/kmeans_sub", subCenter)
save_result("result/kmeans_center", center)
5、結果展示
得到的聚類結果如圖所示:
你們在此過程中遇到了什麼問題,歡迎留言,讓我看看你們都遇到了哪些問題。