[譯]與TensorFlow的第一次接觸(三)之聚類


轉自 [譯]與TensorFlow的第一次接觸(三)之聚類

 
2016.08.09 16:58* 字數 4316 閱讀 7916評論 5

      前一章節中介紹的線性回歸是一種監督學習算法,我們使用數據與輸出值(標簽)來建立模型擬合它們。但是我們並不總是有已經打標簽的數據,卻仍然想去分析它們。這種情況下,我們可以使用無監督的算法如聚類。因為聚類算法是一種很好的方法來對數據進行初步分析,所以它被廣泛使用。

      本章中,會講解K-means聚類算法。該算法廣泛用來自動將數據分類到相關子集合中,每個子集合中的元素都要比其它集合中的元素更相似。此算法中,我們沒有任何目標或結果來預測評估。

      本章中依然會介紹TensorFlow的使用,並介紹基礎數據結構tensor的更多細節。本章開頭介紹tensor的數據類型與分析可在該數據結構上執行的運算變換。接下來展示使用tensor來實現的K-means算法。

基礎數據結構—tensor

      TensorFlow使用基礎數據結構---tensor來表示所有數據。一個tensor可以看成是一個擁用靜態數據類型動態大小且多維的數組,它可以從布爾或string轉換成數值類型。下表是一些主要類型及在Python中對對應的類型:

 

     另外,每個tensor都有一個秩,也是tensor維度的數量。例如,下面的tensor的秩為2:

t = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

     tensor可以擁有任意秩。秩為2的tensor經常被看作矩陣,秩為1的tensor會被看作vector,秩為0的被看作標量值。

     TensorFlow文檔中使用三種不同稱謂來描述tensor的維度:Shape,Rank,Dimension。下面的表展示了它們三者之間的關系,

     TensorFlow提供的一系列操作來計算這些tensor,接下來我們會討論下表中的一些操作。

      通過本章,我們會繼續討論更多的細節。在Tensorflow的官方網站上能找到更多的操作列表及每一個操作的細節。

     舉個例子,假如你想擴展一個2*2000(2D tensor)為立方體(3D tensor)。可以使用tf.expand_dims函數,它可以向tensor中插入一個維度。

      tf.expand_dims會向tensor中插入一個維度,插入位置就是參數代表的位置(維度從0開始)。

      以可視化來展示的話,上面的轉換過程如下圖所示:

 

     正如你所看到的,我們得到一個3D tensor,但根據函數參數我們無法判斷新維度D0的大小。

     如果調用get_shape()來獲得tensor的shape,可以看到D0沒有大小:

print  expanded_vectors.get_shape()

顯示的結果如下:

TensorShape([Dimension(1),Dimension(2000), Dimension(2)])

      本章的稍后,我們可以看到,由於TensorFlow的shape傳遞特性,很多tensor的數學運算函數(正如第一章中提到的)可以自已發現未確定大小維度的大小,並將該值賦給它。

TensorFlow中的數據存儲

Tensorflow程序中主要有三種方式來獲取數據:

1.從數據文件

2.以常數與變量預加載

3.Python代碼提供的數據

下面簡要描述這三種方式:

1.數據文件

      通常,原始數據從數據文件中下載。這個過程並不復雜,建議讀者去TensorFlow官方網站查看如何從不同類型文件中下載數據的細節。你也可以查看input_data.py代碼(可以從github上下載),它會從文件中加載MNIST數據(下一章中使用該數據)。

2.變量與常數

      當提到小的數據集時,數據可提前加載到內存中;正如之前例子中看到的,有兩種基本方式來創建它們:

通過constant()來創建常數

通過Variable()來創建變量

TensorFlow提供了不同的操作來創建常數。下表中對最重要的幾個操作進行了總結:

      用TensorFlow訓練模型的過程中,參數以變量的形式保存在內存中。當變量創建后,可以將其作為初始值(可能是一個常數或隨機值)給一個tensor,該tensor可做為參數傳給一個函數。Tensorflow提供了一系列操作來產生不同分布的隨機tensor:

      一個重要的細節是所有這些操作都需要一個確定shape的tensor作為參數,返回的變量擁有同樣的shape。總而言之,變量擁有一個固定的shape,但如有需要,Tensorflow提供了reshape的機制。

      當創建變量后,它們必須在圖創建完之后且調用run()函數之前顯示初始化。可以通過調用tf.initialize_all_variables()來進行初始化。在訓練過程中與訓練完成后,可通過tf.train.Saver()類來將變量保存到磁盤中,該類的相關細節超過了本書的討論范圍。

3.Python代碼提供數據

      最后,在程序執行過程中,我們可通過叫做“符號變量”或placeholder來操作數據。Placeholder()的調用,包含了元素類型與tensor的shape為參數,還有一個可選參數name。

      在Python代碼中調用Session.run()或Tensro.eval()的同時,這個tensor與feed_dict參數中指定的數據相關聯。第一章中的代碼如下:

      代碼的最后一行中調用sess.run()時,我們通過feed_dict參數給兩個tensor賦值。

      通過簡短分析tensor,希望從現在開始讀者閱讀接下來章節的代碼時,沒有任何困難。

K-means算法

       K-means是一種用來解決聚類問題的無監督算法。該算法依據一個簡單容易的方式來對數據集分成一定數目(假設K個類別)的類別。一個類別中的數據點是相似的,不同類別中的數據點是各種各樣的,也就是說同一子類別中的元素比其它子類別中的元素更相似。

      算法的結果是生成K個點集合,叫做centroids,這是不同組的焦點,標簽代表了集合中的點,k個聚類都有自己的tag。一個類中的所有點離centroid要比其它任意centroid要近。

      如果我們想直接最小化error function,則生成聚類是非常耗計算的(也就是NP問題);一些算法通過啟發式方法來達到局部快速收斂。更通用的算法使用迭代優化技術,僅覆蓋幾次迭代。

      一般來說,這種技術主要有三步:

1.初始化(step 0):初始化K個centroid的集合

2.分配(step 1):將每一個對象賦給最近的組

3.更新(step 2):計算每個新組的新centroid

      有多種方法來初始化K個centroid。其中之一就是在數據集中隨機選取K個對像並將它們看作centroid;接下來的例子中我們會使用這種方法。

      分配(step 1)與更新(step 2)在循環中是可選的,循環直到算法開始收斂,舉例來說就是分配點到組后,就不再發生變化。

      因為這是一個啟發式算法,無法保證算法收斂到最佳目標,結果依賴於初始集合。因為算法通常運行很快,可用不同的初始centroid來多次執行該算法,然后評估結果。

      開始在Tensorflow中編碼實現K-means算法前,建議先生成一些數據用來進行實驗。有一種簡單的方式,在2D空間中隨機生成2000個點,它們服從兩個正態分布,我們可畫出空間分布來更好的理解結果。示例代碼如下:

      如我們在之前章節中所做的,我們可使用Python圖庫來用圖表畫出這些點。建議使用matplotlib,這次我們使用基於matplotlib的可視化庫Seaborn,操作數據用庫pandas,該庫能運算更復雜的數據結構。

      如果你沒有安裝這些庫,在繼續下一步前可能過pip來安裝它們。

      建議使用如下代碼來顯示我們隨機生成的點:

      這段代碼生成兩維空間下的點圖如下:

       TensorFlow中實現的K-means算法來對上面生成的點進行分組,假如四個類,求例代碼如下(基於Shawn Simister在他博客中發表的模型):

     建議讀者用如下代碼來檢查assignment_values tensor中的結果,它會生成一張分布圖:

上面代碼執行后生成的分布圖如下:

新組

      也許讀者會對上面的K-means代碼感到困惑。接下來詳細分析每一行代碼,我們會特別關注相關tensor的變化及它們在程序中如何運算。

      首先需要做的是把我們的數據移到tensor中。我們將所有隨機生成的點保存到常量tensor中:

vectors=tf.constant(conjunto_vectors)

      根據之前講解的算法,我們在開始就要決定初始centroids。一種方法就是從輸入數據中隨機選擇K個對像。下面的代碼就能達到這個目的,隨機排列這些點並選擇前K個點作為centroids:

      這K個點保存在一個2D tensor中。可通過調用tf.Tensor.get_shape()獲得這些tensor的shape:

      我們可以看到,vectors是一個數組,D0包含了2000個positions,D1包含了每一個點的坐標x,y。centroids是包含四個元素的矩陣,D0代表每一個形心的位置,D1代表點的坐標x,y。

      接下來,算法進入一個循環。第一步就是為每一個點,根據平方歐氏距離(只能被用來比較距離)計算最近的centroid。

      為計算該值,需要使用tf.sub(vectors, centroides)。雖然這兩個相減的tensor都是2維的,但在第1維度上有不同的大小(2000VS 4 D0中),實際上,這也代表了不同的意義。

     為解決這個問題,我們需要使用之前提到的函數,如tf.expand_dims用來在兩個tensor中插入一個維度。目的是把這兩個tensor從2維轉換成3維,使得大小匹配可以進行減法:

      tf.expand_dims在每一個tensor中插入一個維度,在vector的tensor中第一維度(D0)插入,在centroides tensor中第二維度(D1)插入。從圖片來看,擴展后的tensor中各維度有了同樣的含義:

 

      看上去這個問題解決了,實際上,如果仔細來看,兩個tensor中都有維度不能確定大小。通過調用get_shape()可以看到:

輸出如下:

      1代表沒有賦予大小。

      之前就已經說明TensorFlow允許傳遞,所以tf.sub函數能夠自己發現如何在兩個tensor間進行減法。

      直觀地來看上面的圖,兩個tensor的形狀是匹配的,而且在指定維度上也有相同的大小。這些數學運算就像發生在D2維度上那樣。然而,D0中只有expanded_centroides有固定大小。

       在這種情況下,TensorFlow假設expanded_vectors擁有同樣的大小,如果我們想執行元素對元素的減法。

       對於expended_centroides的D1同樣如此,Tensorflow推斷出expanded_vectors的D1大小。

       在分配步驟(step 1),算法可實現為如下四行代碼,用來計算平方歐氏距離:

      如果我們察看tensor的形狀,diff, sqr,distances and assignments的大小分別為:

       tf.sub返回一個tensor,包含了兩個tensor相減的值(vector表明D1的大小,centroid表明D0的大小。D2中表明了x,y)。sqr tensor包含了它們的平方。在distance tensor中,已經減少了一個維度,減少的維度在tf.reduce_sum函數中表明。

      通過這個例子來表明TensorFlow提供了一些操作來進行運算,就像tf.reduce_sum來減少tensor的維度。下面的表中總結了一些很重要的操作:

      最后,通過tf.argmin來賦值,它返回tensor某一維度中的最小值索引(此處為D0,代表centroid)。同樣也有tf.argmax操作:

      實際上,上面的四行代碼可以總結成一行代碼中:

assignments=tf.argmin(tf.reduce_sum(tf.square(tf.sub(expanded_vectors,expanded_centroides)),2),0)

     不管如何,定義結點與執行內部圖的那些內部tensor與操作都跟我們之前提到一樣。

計算新形心

      一旦在迭代中創建了新組,需要記住算法的新步驟中包含了計算組的新形心。正如我們之前看到的代碼;

means=tf.concat(0,[tf.reduce_mean(tf.gather(vectors,tf.reshape(tf.where(tf.equal(assignments,c)),[1,-1])),reduction_indices=[1])forcinxrange(k)])

      這段代碼中,means tensor是連接k個tensor的結果,這k個tensor都是由那些平均值屬於每一個k類的點組成的。

     接下來,會詳細分析計算每一個點屬於哪個cluster的代碼:

A.通過equal獲得一個布爾tensor(Dimension(2000)),true代表了assignment tensor與K cluster相匹配的位置,同時我們也計算了點的平均值

B.where根據傳進來的布爾tensor中元素值為true的位置來構造一個tensor(Dimension(1) x Dimension(2000))

C.reshape根據vectors tensor內部那些屬於c cluster的點的索引來構建一個tensor(Dimension(2000) x Dimension(1))

D.gather從c cluster中收集所有點的坐標並創建tensor(Dimension(1) x Dimension(2000))

E.reduce_mean則是根據c cluster中所有點的平均值來創建tensor(Dimension(1) x Dimension(2))

      如果讀者想了解代碼的更多細節,可訪問TensorFlow api頁面,通過解說例子來了解所有操作的細節。

圖執行

     最后,我們來描述循環相關的代碼部分與用新計算的平均值tensor來更新centroide的部分。

     當run()方法被調用時,我們要在更新的centroids值在下輪迭代使用前,先創建一個賦值操作符用means tensor值更新centroids值:

update_centroides=tf.assign(centroides,means)

     我們同樣需要在運行圖之前創建一個操作來初始化所有變量:

init_op=tf.initialize_all_variables()

     到現在為止,所有都准備就緒,可以開始運行圖:

sess=tf.Session()

sess.run(init_op)

for step in xrange(num_steps):

 _,centroid_values,assignment_values=sess.run([update_centroides,centroides,assignments])

     在這段代碼中,每次迭代時,centroids與為每個點新分配的cluster都會被更新。

      代碼中指定了三個操作,同時需要查看run()的執行狀態,並按順序來運行這三個操作。因為有三個值需要查找,sess.run()返回了三個numpy數組,每個數組分別包含了訓練過程中相應的內容。

      因為update_centroides這個操作的結果並不需要返回,在返回的turple中相應元素內容為空,用“_”表示不接收該參數。

      對於另外兩個值,centroids與將點賦給每一個cluster,一旦完成所有迭代計算后,我們可以將這兩個變量打印在屏幕上。

      使用簡單的打印命令,輸出如下:

     希望讀者的電腦上也有接近的值,這說明讀者已經成功執行了本章中的相關代碼。

     建議讀者在繼續進行下一步之前,先嘗試修改某些值。例如num_points,尤其聚類的數量,然后通過生成結果圖來查看assignment_values如何變化。

     為了測試本章中的代碼,可通過github下載本代碼。包含本章代碼的文件為Kmeans.py,

     本章中已經了解了一些TensorFlow的知識,尤其通過TensorFlow中實現一個聚類算法K-means來學習基礎數據結構tensor。

     了解了tensor后,我們在下章中可以一步步建立一個單層神經網絡。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM