1、概述
本篇博文爲數據挖掘算法系列的第一篇。現在對於Kmeans算法進行簡單的介紹,Kmeans算法是屬於無監督的學習的算法,並且是最基本、最簡單的一種基於距離的聚類算法。
下面簡單說一下Kmeans算法的步驟:
- 選隨機選取K的簇中心(注意這個K是自己選擇的)
- 計算每個數據點離這K個簇中心的距離,然後將這個點劃分到距離最小的簇中
- 重新計算簇中心,即將每個簇的所有數據點相加求均值,將這個均值作爲對應簇的新簇中心。
- 重複2、3步,直到滿足了你設置的停止算法迭代的條件
注意:停止算法迭代的條件一般有三個:
- 沒有(或最小數目)對象被重新分配給不同的聚類。
- 沒有(或最小數目)聚類中心再發生變化。
- 誤差平方和局部最小。
常用的距離公式有:1、歐式距離; 2、曼哈頓距離;3、切比雪夫距離等等
二、實現
下面給出實現代碼,在這裏我設置的停止條件是第三種,即誤差平方和最小
import numpy as np
import random
import matplotlib.pyplot as plt
from calculate_distance_algorithm import euclid_distance
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用來正常顯示中文標籤
plt.rcParams['axes.unicode_minus'] = False # 用來正常顯示負號
class KMeans:
def __init__(self, n_cluster, algorithm=euclid_distance, iterators=None):
self.n_cluster = n_cluster
self.cluster_centers_ = None
self.algorithm = euclid_distance
self.iterations = iterators
self.loss = None
def fit(self, data):
"""
進行k-means算法迭代,劃分簇
:param Y: Y是對應X正確的種類
:param iterators: 算法迭代次數
:param data: 數據集(X, Y) X是測試點,
:param k:最終要劃分出簇的個數
:param calculate_method:計算距離使用的公式 默認爲計算兩點間的歐式距離,可以通過傳遞計算距離的方法名來更改計算距離方式
"""
# 獲得隨機劃分的質心
clusters = self.random_choose_cluster(data, self.n_cluster)
clusters_collection = {}
# 一開始的損失值
loss_value = 1 << 30
# 統計迭代次數
count = 0
while True:
# 如果達到了指定的迭代次數後,就不迭代了
if count == self.iterations and self.iterations != -1:
break
count += 1
# 初始化每個簇集合
for index, cluster in enumerate(clusters):
clusters_collection[index] = []
for pos, x in enumerate(data):
min = 1 << 30
min_index = -1
# 遍歷每個數據,計算與k個簇的質心的距離
for index, cluster in enumerate(clusters):
# 計算每個點與對應質心的距離
dis = self.algorithm(x, cluster)
if dis < min:
min = dis
min_index = index
# 將這個數據點劃分到距離最近的簇中
clusters_collection[min_index].append(x)
# 計算當前的損失值
now_loss_value = self.loss_function(clusters_collection, clusters)
if now_loss_value > loss_value:
# 重新計算每個簇的質心
self.calculate_centroid(clusters_collection, clusters)
elif now_loss_value < loss_value:
print("算法正在運行,迭代次數:{}".format(count))
# 重新計算每個簇的質心
self.calculate_centroid(clusters_collection, clusters)
elif now_loss_value == loss_value:
self.cluster_centers_ = clusters
self.loss = now_loss_value
return self
# 更新損失值
loss_value = now_loss_value
def predict(self, X):
"""
預測函數
:param X: 需要預測的數據點
:param clusters: 分配好了的簇中心集合
:return: 返回對應數據點預測對應的簇種類
"""
result = []
for x in X:
min_index = -1
max_dis = 1 << 30
for index, i in enumerate(self.cluster_centers_):
dis = self.algorithm(x, i)
if max_dis > dis:
max_dis = dis
min_index = index
result.append(min_index)
return np.array(result)
def random_choose_cluster(self, data, k):
"""
隨機在數據data中選取k個簇
:param data: 數據集
:param k: 選取的簇的個數
:return: 返回包含選取k個簇座標的列表
"""
clusters = []
pos = random.sample(range(len(data)), k)
for i in pos:
clusters.append(data[i])
return np.array(clusters)
def calculate_centroid(self, collection, clusters):
"""
計算集合的質心
計算方法:將對應集合所有數據的x、y加起來,求平均值,將這個平均值點返回
:param collection: 需要計算質心的集合
:return: 返回這個集合的質心
"""
# 重新計算每個簇的質心
for i in collection.keys():
if len(collection[i]) > 0:
result = np.mean(collection[i], axis=0)
clusters[i] = result
def loss_function(self, data, clusters):
"""
衡量K-means算法停止迭代的損失函數
:param data: 所有簇集合
:param clusters: 每個簇對應的質心
:return: 返回損失值
"""
total = 0
for i in data:
for x in data[i]:
total += self.algorithm(x, clusters[i])
return total
距離算法公式實現(calculate_distance_algorithm.py):
import numpy as np
def manhattan_distance(x1, x2):
"""
計算兩點間的曼哈頓距離
:param x1:(x1, y1)
:param x2:(x2, y2)
:return:返回兩點之間的曼哈頓距離
"""
result = np.abs(x1[0] - x2[0]) + np.abs(x1[1] - x1[1])
return result
def chebyshev_distance(x1, x2):
"""
計算兩點間的切比雪夫距離
:param x1:(x1, y1)
:param x2:(x2, y2)
:return:返回兩點之間的切比雪夫距離
"""
return np.max(np.abs(x1 - x2))
def euclid_distance(x1, x2):
"""
計算兩點間的歐式距離
:param x1:(x1, y1)
:param x2:(x2, y2)
:return: 返回兩點之間的歐式距離
"""
return np.sqrt(np.sum((x1 - x2) ** 2))
下面給出使用Kmeans聚類算法對圖片進行聚類的實現。
聚類圖片對象爲:
聚類結果圖像:
測試代碼:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
import cv2 as cv
from K_means import KMeans
from calculate_distance_algorithm import manhattan_distance, chebyshev_distance, euclid_distance
def recreate_image(clusters, labels, w, h):
"""
重新創建圖像
:param clusters: 聚類中心
:param labels: 預測的種類集合
:param w: 圖像的寬
:param h: 圖像的高
:return: 返回圖像
"""
d = clusters.shape[1]
# 構建圖像 w:寬, h:高, d:聚類個數
# print(clusters)
image = np.zeros((w, h, d))
label_idx = 0
for i in range(w):
for j in range(h):
image[i][j] = clusters[labels[label_idx]]
# print(image[i][j])
label_idx += 1
return image
def get_data(data, k):
"""
將對應的三維數組轉換成 n*4維的矩陣,前3列是數據,最後一列是該類數據對應的樣本標籤值k
:param data: 數據
:param k: 標籤
:return: 轉換好的n*4維數據
"""
# 展開成n*3維
data = data.reshape(-1, 3)
# 生成顏色對應的標籤
data_label = np.ones((data.shape[0], 1))
data_label *= k
# 將標籤列與數據合併
return np.hstack((data, data_label))
if __name__ == '__main__':
# ----------------------------->讀入數據,創建數據集並劃分訓練集和測試集<-----------------------------#
file = "fruit.jpg"
img = cv.imread(file)
img[img == 0] = 1 # 將0值填充爲1,防止後續聚類出現nan值
img = np.array(img, dtype=np.float32) / 255 # 除255進行歸一化,變成(0, 1)
# 選取黑色部分的數據
black = img[239:243, 320:360]
black = get_data(black, 0)
# 選取白色部分的數據
white = img[280:360, 30:110]
white = get_data(white, 1)
# 選取黃色部分的數據
yellow = img[60:130, 30:110]
yellow = get_data(yellow, 2)
# 選取綠色部分的數據
green = img[70:210, 290:370]
green = get_data(green, 3)
# 選擇紅色數據
red = img[340:410, 310:390]
red = get_data(red, 4)
# 總數據
data = np.vstack((black, white, yellow, green, red))
# 劃分測試集和訓練集
x_train, x_test, y_train, y_test = train_test_split(data[:, :3], data[:, -1],
test_size=0.3, random_state=0, shuffle=True)
# 轉換成numpy的array形式
x_train = np.array(x_train)
x_test = np.array(x_test)
y_train = np.array(y_train)
y_test = np.array(y_test)
# -------------------------------------->對圖片進行聚類<--------------------------------------#
# 獲得圖片的寬、高、顏色深度
w, h, d = img.shape
# 展開成n*3維的矩陣,-1代表自適應
img = np.array(img.reshape(-1, 3), dtype=np.float64)
# -----------------------------使用曼哈頓距離進行聚類-----------------------------
plt.figure()
plt.subplot(3, 1, 1)
print("使用曼哈頓距離聚類開始。。。。。。。")
# 建立Kmeans分類
estimator = KMeans(n_cluster=5, algorithm=manhattan_distance) # 默認是使用歐式距離計算
# 使用訓練集來聚類,找到每個種類對應的簇中心
estimator.fit(x_train)
# 根據訓練好的結果,對整個圖像進行聚類
y_predict = estimator.predict(img)
# 將聚類結果顯示
image = recreate_image(estimator.cluster_centers_, y_predict, w, h)
plt.title("使用曼哈頓距離聚類所得的圖像")
plt.imshow(image)
# -----------------------------使用歐式距離進行聚類-----------------------------
# 建立Kmeans分類
plt.subplot(3, 1, 2)
print("使用歐式距離聚類開始。。。。。。。")
estimator = KMeans(n_cluster=5, algorithm=euclid_distance) # 默認是使用歐式距離計算
# 使用訓練集來聚類,找到每個種類對應的簇中心
estimator.fit(x_train)
# 根據訓練好的結果,對整個圖像進行聚類
y_predict = estimator.predict(img)
# 將聚類結果顯示
image = recreate_image(estimator.cluster_centers_, y_predict, w, h)
plt.title("使用歐式距離聚類所得的圖像")
plt.imshow(image)
# -----------------------------使用切比雪夫距離進行聚類-----------------------------
# 建立Kmeans分類
plt.subplot(3, 1, 3)
print("使用切比雪夫距離聚類開始。。。。。。。")
estimator = KMeans(n_cluster=5, algorithm=chebyshev_distance) # 默認是使用歐式距離計算
# 使用訓練集來聚類,找到每個種類對應的簇中心
estimator.fit(x_train)
# 根據訓練好的結果,對整個圖像進行聚類
y_predict = estimator.predict(img)
# 將聚類結果顯示
image = recreate_image(estimator.cluster_centers_, y_predict, w, h)
plt.title("使用切比雪夫距離聚類所得的圖像")
plt.imshow(image)
plt.show()
因爲聚類是無監督的學習算法,所以一般來說是不會用來分類的。評價一個聚類模型的好壞,可以根據模型的輪廓係數來判定,至於這個輪廓係數是啥東西,大家可以參考sklearn關於Kmeans的說明,或者百度,Google。