TensorFlow實現K-means演算法
阿新 • • 發佈:2019-01-05
正如標題所示: 利用 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()