TensorFlow實戰之K-Means聚類算法實踐
原文地址: 這里
Google 最近開源了它的第二代人工智能與數值計算庫TensorFlow。TensorFlow由Google大腦團隊開發,并且能夠靈活地運行在多個平臺上——包括GPU平臺與移動設備中。
TensorFlow的核心就是使用所謂的數據流,可以參考Wikipedia上的有關于 Genetic Programming 的相關知識,譬如:
正如你理解的,整個以樹狀圖的架構來表示整個計算流。每個節點即代表一個操作,TensorFlow稱作OPS,即operations的縮寫。非葉子節 點還是很好理解的,一些葉子節點可以是特殊的操作類型,譬如返回一個常量值(譬如上述樹中的7或者2.2)。其他的一些葉子節點,譬如X或者Y這樣的,被當做placeholders,即會在運行中被動態地注入值。如果仔細觀察上圖中的箭頭的指向,可以發現這些箭頭指向就表明了不同節點之間輸出的依賴關系。因此,Data(在 TensorFlow中被稱為Tensors),會在不同的節點之間逆向流動,這就就是他們被稱為TensorFlow的原因。TensorFlow也提 供了其他的基于圖像抽象的組件,譬如持久化的數據存儲(被稱為Variables),以及在譬如神經網絡這樣的應用中對于Variables中的參數微調 而進行的優化手段。
TensorFlow提供了非常有好的Python的接口,在看本篇文章之前建議閱讀以下:
1. 基礎環境的搭建 或者筆者的翻譯
2.參閱 這個例子 來對TensorFlow的代碼風格有一個模糊的認識。
3.接下來 這個解釋 會闡述TensorFlow中的基礎的組件。
4.參考 詳細的例子 來看看TensorFlow是怎么解決常見的ML問題的。
5.在了解上述的基本知識后,可以閱讀 Python docs 這個接口文檔來作為開發中的參考。
接下來,我會以用TensorFlow來解決常見的K-Means問題作為例子來闡述如何使用它。
import tensorflow as tf from random import choice, shuffle from numpy import array def TFKMeansCluster(vectors, noofclusters): """ K-Means Clustering using TensorFlow. `vertors`應該是一個n*k的二維的NumPy的數組,其中n代表著K維向量的數目 'noofclusters' 代表了待分的集群的數目,是一個整型值 """ noofclusters = int(noofclusters) assert noofclusters < len(vectors) #找出每個向量的維度 dim = len(vectors[0]) #輔助隨機地從可得的向量中選取中心點 vector_indices = list(range(len(vectors))) shuffle(vector_indices) #計算圖 #我們創建了一個默認的計算流的圖用于整個算法中,這樣就保證了當函數被多次調用 #時,默認的圖并不會被從上一次調用時留下的未使用的OPS或者Variables擠滿 graph = tf.Graph() with graph.as_default(): #計算的會話 sess = tf.Session() ##構建基本的計算的元素 ##首先我們需要保證每個中心點都會存在一個Variable矩陣 ##從現有的點集合中抽取出一部分作為默認的中心點 centroids = [tf.Variable((vectors[vector_indices[i]])) for i in range(noofclusters)] ##創建一個placeholder用于存放各個中心點可能的分類的情況 centroid_value = tf.placeholder("float64", [dim]) cent_assigns = [] for centroid in centroids: cent_assigns.append(tf.assign(centroid, centroid_value)) ##對于每個獨立向量的分屬的類別設置為默認值0 assignments = [tf.Variable(0) for i in range(len(vectors))] ##這些節點在后續的操作中會被分配到合適的值 assignment_value = tf.placeholder("int32") cluster_assigns = [] for assignment in assignments: cluster_assigns.append(tf.assign(assignment, assignment_value)) ##下面創建用于計算平均值的操作節點 #輸入的placeholder mean_input = tf.placeholder("float", [None, dim]) #節點/OP接受輸入,并且計算0維度的平均值,譬如輸入的向量列表 mean_op = tf.reduce_mean(mean_input, 0) ##用于計算歐幾里得距離的節點 v1 = tf.placeholder("float", [dim]) v2 = tf.placeholder("float", [dim]) euclid_dist = tf.sqrt(tf.reduce_sum(tf.pow(tf.sub( v1, v2), 2))) ##這個OP會決定應該將向量歸屬到哪個節點 ##基于向量到中心點的歐幾里得距離 #Placeholder for input centroid_distances = tf.placeholder("float", [noofclusters]) cluster_assignment = tf.argmin(centroid_distances, 0) ##初始化所有的狀態值 ##這會幫助初始化圖中定義的所有Variables。Variable-initializer應該定 ##義在所有的Variables被構造之后,這樣所有的Variables才會被納入初始化 init_op = tf.initialize_all_variables() #初始化所有的變量 sess.run(init_op) ##集群遍歷 #接下來在K-Means聚類迭代中使用最大期望算法。為了簡單起見,只讓它執行固 #定的次數,而不設置一個終止條件 noofiterations = 100 for iteration_n in range(noofiterations): ##期望步驟 ##基于上次迭代后算出的中心點的未知 ##the _expected_ centroid assignments. #首先遍歷所有的向量 for vector_n in range(len(vectors)): vect = vectors[vector_n] #計算給定向量與分配的中心節點之間的歐幾里得距離 distances = [sess.run(euclid_dist, feed_dict={ v1: vect, v2: sess.run(centroid)}) for centroid in centroids] #下面可以使用集群分配操作,將上述的距離當做輸入 assignment = sess.run(cluster_assignment, feed_dict = { centroid_distances: distances}) #接下來為每個向量分配合適的值 sess.run(cluster_assigns[vector_n], feed_dict={ assignment_value: assignment}) ##最大化的步驟 #基于上述的期望步驟,計算每個新的中心點的距離從而使集群內的平方和最小 for cluster_n in range(noofclusters): #收集所有分配給該集群的向量 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)}) #為每個向量分配合適的中心點 sess.run(cent_assigns[cluster_n], feed_dict={ centroid_value: new_location}) #返回中心節點和分組 centroids = sess.run(centroids) assignments = sess.run(assignments) return centroids, assignments
需要注意的是,如果
for i in range(100): x = sess.run(tf.assign(variable1, placeholder))
像上面那樣看似無害地在每次執行的時候創建一個新的OP(譬如tf.assign或者tf.zeros這樣的),這樣會一定的影響性能。作為替代的,你應該為每個任務定義一個特定的OP,然后在循環中調用這個OP。可以使用len(graph.get_operations())這個方法來檢測是否有冗余的非必需的OPs。準確來說,sess.run應該是在迭代中唯一會與graph產生交互的方法。在上述代碼的138~139行中可以看出,一系列的ops/Variables可以組合在sess.run中使用。