基於Python——Kmeans聚類算法的實現

1、概述

本篇博文爲數據挖掘算法系列的第一篇。現在對於Kmeans算法進行簡單的介紹,Kmeans算法是屬於無監督的學習的算法,並且是最基本、最簡單的一種基於距離的聚類算法。

下面簡單說一下Kmeans算法的步驟:
  1. 選隨機選取K的簇中心(注意這個K是自己選擇的)
  2. 計算每個數據點離這K個簇中心的距離,然後將這個點劃分到距離最小的簇中
  3. 重新計算簇中心,即將每個簇的所有數據點相加求均值,將這個均值作爲對應簇的新簇中心。
  4. 重複2、3步,直到滿足了你設置的停止算法迭代的條件

注意:停止算法迭代的條件一般有三個:

  1. 沒有(或最小數目)對象被重新分配給不同的聚類。
  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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章