1. 程式人生 > >TensorFlow實現K-means演算法

TensorFlow實現K-means演算法

正如標題所示: 利用 tf 實現k-means演算法

由於我也是菜雞一隻,把程式碼註釋寫在這裡,給和我一樣是菜雞的人看。如果有哪裡註釋不對,或者不夠科學的地方,還請各位指正。

本文的主要程式碼來自於這篇部落格,更改了此篇部落格中 tf 減法運算函式錯誤,https://blog.csdn.net/yhhyhhyhhyhh/article/details/54429034
感謝這位大佬提供的程式碼指示,對我的程式碼編寫起到了很大的作用,然後根據自己的理解對每一行程式碼進行註釋。

首先看看最後的效果實現。
1、生成的原資料圖:
在這裡插入圖片描述

2、分類後的資料圖:
在這裡插入圖片描述

3、k-means演算法的流程圖:


在這裡插入圖片描述
4、Tensor圖示
在這裡插入圖片描述

5、其中程式碼註釋如圖:
在這裡插入圖片描述

6、完整的程式碼如下(這樣都看不懂,我也沒有辦法了。):

# -*- coding: utf-8 -*-
import numpy as np
from numpy.linalg import cholesky
import matplotlib.pyplot as plt
import tensorflow as tf
from random import shuffle  # 用於打亂資料
from numpy import array
import pandas as pd
import seaborn as sns

# 1、生成隨機測試資料
sampleNo = 1000  # 資料數量
mu = 3
# 產生二維正態分佈資料,加大資料之間的差異
mu = np.array([[1, 5]])

Sigma = np.array([[1, 0.5],
                  [1.5, 3]])
# 返回L的下三角陣
R = cholesky(Sigma)

# dot運算為矩陣的點積(矩陣相乘),srcdata儲存的實際是模擬產生的所有點
srcdata = np.dot(np.random.randn(sampleNo, 2), R) + mu

# 畫出當前的所有點
plt.plot(srcdata[:, 0], srcdata[:, 1], 'bo')


# 2、定義K-means演算法

# 2.1 、定義K-means演算法函式
def kmeans(vectors, k_num):
    """
    使用 TensorFlow 實現K-Means 演算法
    :param vectors: 是一個 n * k 的Numpy陣列,n代表k維向量的數量,也就是模擬產生的資料點的Tensor
    :param k_num: 表示需要分類的個數,是一個整數
    """
    # 將 k 轉換為整數
    k_num = int(k_num)

    # 異常處理,防止後續陣列下標越界,防止出現分類個數大於實際的點的個數,如分4類,然而只有2個點的情況
    assert k_num < len(vectors)

    # 找出每個向量的維度,平面點的維度為2(x,y),空間點維度為3(x,y,z)
    dim = len(vectors[0])

    # 獲取 vectors 長度大小的隨機資料(本例中為1000)
    vector_indices = list(range(len(vectors)))

    # 打亂 vector_indices 中的所有資料,能夠更好的泛化
    shuffle(vector_indices)

    # 計算圖
    # 我們建立了一個預設的計算流的圖用於整個演算法中,這樣就保證了當函式被多次呼叫
    # 時,始終使用的是預設的圖
    # https://www.cnblogs.com/studylyn/p/9105818.html
    graph = tf.Graph()
    with graph.as_default():
        # 建立會話
        with tf.Session() as sess:
            # 構建基本的計算的元素
            # 首先我們需要保證每個中心點都會存在一個Variable矩陣
            # 從現有的點集合中 vector_indices 抽取出前 k_num 個數據作為預設的中心點,並且定義為 tf 的變數,
            # 用於後續的中心點的運算
            centroids = [tf.Variable((vectors[vector_indices[i]]))for i in range(k_num)]

            # 建立一個placeholder用於存放各個分類的中心點
            centroid_value = tf.placeholder(dtype=tf.float64, shape=[dim])

            # centroid_value = tf.placeholder("float64", [dim])

            # 給 k_num 箇中心點向量進行賦值,cent_assigns 用於儲存中心點的位置資訊
            cent_assigns = []
            for centroid in centroids:
                cent_assigns.append(tf.assign(centroid, centroid_value))

            # assignments 用於儲存 sampleNo 個點的經過計算分類後位置
            assignments = [tf.Variable(0) for i in range(len(vectors))]
            # 儲存每個單獨的點到 k_num 個分類的最短距離
            assignment_value = tf.placeholder(dtype=tf.int32)
            # cluster_assigns 的大小是 sampleNo = 1000,儲存的是每個點到 k_num 箇中心點中的最小的一個距離
            cluster_assigns = []
            # 初始化 cluster_assigns
            for assignment in assignments:
                cluster_assigns.append(tf.assign(assignment, assignment_value))

            # 下面建立用於計算平均值的操作節點
            # 輸入的placeholder
            mean_input = tf.placeholder(dtype=tf.float64, shape=[None, dim])
            # 節點/OP接受輸入,並且計算0維度的平均值,如輸入的向量列表
            mean_op = tf.reduce_mean(mean_input, 0)

            # 用於計算歐幾里得距離的節點 distance = ((x1 - x2)^2 + (y1 - y2)^2)^(1/2)
            v1 = tf.placeholder(dtype=tf.float64, shape=[dim])
            v2 = tf.placeholder(dtype=tf.float64, shape=[dim])
            # 注意:tf.mul  tf.sub   tf.neg 已經廢棄, 分別可用tf.multiply  tf.subtract  tf.negative替代.
            euclid_dist = tf.sqrt(tf.reduce_sum(tf.pow(tf.subtract(v1, v2), 2)))

            # 這個OP會決定應該將向量歸屬到哪個節點
            # 基於向量到中心點的歐幾里得距離
            # Placeholder for input
            centroid_distances = tf.placeholder(dtype=tf.float64, shape=[k_num])
            # cluster_assignment 計算 k_num 箇中心點的最短歐幾里得距離
            cluster_assignment = tf.argmin(centroid_distances, 0)

            # 初始化所有的狀態值,Variable_initializer應該定
            # 義在所有的Variables被構造之後,這樣所有的Variables才會被納入初始化
            init_op = tf.global_variables_initializer()

            # 初始化所有的變數
            sess.run(init_op)

            # 建立tensor圖,並儲存在當前的log目錄下
            tf.summary.FileWriter("./log", sess.graph)

            # 叢集遍歷
            # 接下來在K-Means聚類迭代中使用最大期望演算法。為了簡單起見,只讓它執行固
            # 定的訓練的次數為20次,而不設定一個終止條件
            noofiterations = 20
            for iteration_n in range(noofiterations):

                # 期望步驟
                # 基於上次迭代後算出的中心點的位置
                # 1.首先遍歷所有的向量,len(vectors)在此案例中值為 sampleNo = 1000
                # 計算每個點到 k_num 個分類中心點的最短距離,並存儲在 cluster_assigns 中
                for vector_n in range(len(vectors)):
                    # 獲取第 vector_n 個向量,取值範圍在[0,999]
                    vect = vectors[vector_n]

                    # 當前點與 k_num 個分類的中心點歐幾里得距離
                    distances = [sess.run(euclid_dist, feed_dict={
                        v1: vect, v2: sess.run(centroid)}) for centroid in centroids]

                    # 獲取當前點到 k_num 個分類中心點的最短距離,目的是為了後續選擇最近距離的中心點
                    assignment = sess.run(cluster_assignment, feed_dict={
                        centroid_distances: distances})

                    # 接下來為每個向量分配合適的值
                    sess.run(cluster_assigns[vector_n], feed_dict={
                        assignment_value: assignment})

                # 2.將所有點進行分類
                # 基於上述的期望步驟,計算每個新的中心點的距離從而使叢集內的平方和最小
                for cluster_n in range(k_num):
                    # 收集 k_num 個分類中,對應每個分類的資料
                    assigned_vects = [vectors[i] for i in range(len(vectors))
                                      if sess.run(assignments[i]) == cluster_n]

                    # 採用平均值的計算方式重新計算每個分類叢集新的中心點
                    new_location = sess.run(mean_op, feed_dict={
                        mean_input: array(assigned_vects)})

                    # 為 k_num 個分類分配新的中心點
                    sess.run(cent_assigns[cluster_n], feed_dict={
                        centroid_value: new_location})

            # 返回 k_num 箇中心節點
            centroids = sess.run(centroids)
            # 返回 k_num 個分組
            assignments = sess.run(assignments)

            return centroids, assignments


# 2.2、定義聚類的個數,並使用kmeans演算法去計算
k = 4
center, result = kmeans(srcdata, k)

print np.shape(result)


# 列印 k 箇中心點
print center

# 3、整理結果,並且使用 seaborn 畫圖
res = {"x": [], "y": [], "kmeans_res": []}

for i in xrange(len(result)):
    res["x"].append(srcdata[i][0])
    res["y"].append(srcdata[i][1])
    res["kmeans_res"].append(result[i])

pd_res = pd.DataFrame(res)
sns.lmplot("x", "y", data=pd_res, fit_reg=False, height=5, hue="kmeans_res")
plt.show()