TensorFlow 學習指南 三、學習
原文: ofollow,noindex">LearningTensorFlow.com
譯者: 飛龍
協議: CC BY-NC-SA 4.0
自豪地採用 谷歌翻譯
聚類和 KMeans
我們現在冒險進入我們的第一個應用,即使用 k-means 演算法進行聚類。 聚類是一種資料探勘練習,我們獲取大量資料並找到彼此相似的點的分組。 K-means 是一種非常善於在許多型別的資料集中查詢簇的演算法。
對於簇和 k-means的 更多資訊,請參閱 scikit-learn.org/stable/modules/clustering.html#k-means" target="_blank" rel="nofollow,noindex">k-means 演算法的 scikit-learn 文件 或觀看 此視訊 。
生成樣本
首先,我們需要生成一些樣本。 我們可以隨機生成樣本,但這可能會給我們提供非常稀疏的點,或者只是一個大分組 - 對於聚類來說並不是非常令人興奮。
相反,我們將從生成三個質心開始,然後在該點周圍隨機選擇(具有正態分佈)。 首先,這是一個執行此操作的方法
import tensorflow as tf import numpy as np def create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed): np.random.seed(seed) slices = [] centroids = [] # 為每個簇建立樣本 for i in range(n_clusters): samples = tf.random_normal((n_samples_per_cluster, n_features), mean=0.0, stddev=5.0, dtype=tf.float32, seed=seed, name="cluster_{}".format(i)) current_centroid = (np.random.random((1, n_features)) * embiggen_factor) - (embiggen_factor/2) centroids.append(current_centroid) samples += current_centroid slices.append(samples) # 建立一個大的“樣本”資料集 samples = tf.concat(slices, 0, name='samples') centroids = tf.concat(centroids, 0, name='centroids') return centroids, samples
這種方法的工作方式是隨機建立 n_clusters
個不同的質心(使用 np.random.random((1, n_features))
)並將它們用作 tf.random_normal
的質心。 tf.random_normal
函式生成正態分佈的隨機值,然後我們將其新增到當前質心。 這會在該形心周圍建立一些點。 然後我們記錄質心( centroids.append
)和生成的樣本( slices.append(samples)
)。 最後,我們使用 tf.concat
建立“一個大樣本列表”,並使用 tf.concat
將質心轉換為 TensorFlow 變數。
將 create_samples
方法儲存在名為 functions.py
的檔案中,允許我們為這個(以及下一個!)課程,將這些方法匯入到我們的指令碼中。 建立一個名為 generate_samples.py
的新檔案,其中包含以下程式碼:
import tensorflow as tf import numpy as np from functions import create_samples n_features = 2 n_clusters = 3 n_samples_per_cluster = 500 seed = 700 embiggen_factor = 70 np.random.seed(seed) centroids, samples = create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed) model = tf.global_variables_initializer() with tf.Session() as session: sample_values = session.run(samples) centroid_values = session.run(centroids)
這只是設定了簇和特徵的數量(我建議將特徵的數量保持為 2,以便我們以後可以視覺化它們),以及要生成的樣本數。 增加 embiggen_factor
將增加簇的“散度”或大小。 我在這裡選擇了一個提供良好學習機會的值,因為它可以生成視覺上可識別的叢集。
為了使結果視覺化,我們使用 matplotlib
建立繪圖函式。 將此程式碼新增到 functions.py
:
def plot_clusters(all_samples, centroids, n_samples_per_cluster): import matplotlib.pyplot as plt # 繪製出不同的簇 # 為每個簇選擇不同的顏色 colour = plt.cm.rainbow(np.linspace(0,1,len(centroids))) for i, centroid in enumerate(centroids): # 為給定簇抓取樣本,並用新顏色繪製出來 samples = all_samples[i*n_samples_per_cluster:(i+1)*n_samples_per_cluster] plt.scatter(samples[:,0], samples[:,1], c=colour[i]) # 還繪製質心 plt.plot(centroid[0], centroid[1], markersize=35, marker="x", color='k', mew=10) plt.plot(centroid[0], centroid[1], markersize=30, marker="x", color='m', mew=5) plt.show()
所有這些程式碼都是使用不同的顏色繪製每個簇的樣本,並在質心位置建立一個大的紅色 X
。 質心提供為引數,稍後會很方便。
更新 generate_samples.py
,通過將 import plot_clusters
新增到檔案頂部來匯入此函式。 然後,將這行程式碼新增到底部:
plot_clusters(sample_values, centroid_values, n_samples_per_cluster)
執行 generate_samples.py
現在應該提供以下繪圖:

image
初始化
k-means 演算法從初始質心的選擇開始,初始質心只是資料中實際質心的隨機猜測。 以下函式將從資料集中隨機選擇多個樣本作為此初始猜測:
def choose_random_centroids(samples, n_clusters): # 第 0 步:初始化:選擇 n_clusters 個隨機點 n_samples = tf.shape(samples)[0] random_indices = tf.random_shuffle(tf.range(0, n_samples)) begin = [0,] size = [n_clusters,] size[0] = n_clusters centroid_indices = tf.slice(random_indices, begin, size) initial_centroids = tf.gather(samples, centroid_indices) return initial_centroids
這段程式碼首先為每個樣本建立一個索引(使用 tf.range(0, n_samples)
,然後隨機打亂它。從那裡,我們使用 tf.slice
選擇固定數量( n_clusters
)的索引。這些索引與我們的初始質心相關,然後使用 tf.gather
組合在一起形成我們的初始質心陣列。
將這個新的 choose_random_centorids
函式新增到 functions.py
中,並建立一個新指令碼(或更新前一個指令碼),寫入以下內容:
import tensorflow as tf import numpy as np from functions import create_samples, choose_random_centroids, plot_clusters n_features = 2 n_clusters = 3 n_samples_per_cluster = 500 seed = 700 embiggen_factor = 70 centroids, samples = create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed) initial_centroids = choose_random_centroids(samples, n_clusters) model = tf.global_variables_initializer() with tf.Session() as session: sample_values = session.run(samples) updated_centroid_value = session.run(initial_centroids) plot_clusters(sample_values, updated_centroid_value, n_samples_per_cluster)
這裡的主要變化是我們為這些初始質心建立變數,並在會話中計算其值。 然後,我們將初始猜測繪製到 plot_cluster
,而不是用於生成資料的實際質心。
執行此操作會將得到與上面類似的影象,但質心將處於隨機位置。 嘗試執行此指令碼幾次,注意質心移動了很多。
更新質心
在開始對質心位置進行一些猜測之後,然後 k-means 演算法基於資料更新那些猜測。 該過程是為每個樣本分配一個簇號,表示它最接近的質心。 之後,將質心更新為分配給該簇的所有樣本的平均值。 以下程式碼處理分配到最近的簇的步驟:
def assign_to_nearest(samples, centroids): # 為每個樣本查詢最近的質心 # START from http://esciencegroup.com/2016/01/05/an-encounter-with-googles-tensorflow/ expanded_vectors = tf.expand_dims(samples, 0) expanded_centroids = tf.expand_dims(centroids, 1) distances = tf.reduce_sum( tf.square( tf.subtract(expanded_vectors, expanded_centroids)), 2) mins = tf.argmin(distances, 0) # END from http://esciencegroup.com/2016/01/05/an-encounter-with-googles-tensorflow/ nearest_indices = mins return nearest_indices
請注意,我從 這個頁面 借用了一些程式碼,這些程式碼具有不同型別的 k-means 演算法,以及許多其他有用的資訊。
這種方法的工作方式是計算每個樣本和每個質心之間的距離,這通過 distances =
那行來實現。 這裡的距離計算是歐幾里德距離。 這裡重要的一點是 tf.subtract
會自動擴充套件兩個引數的大小。 這意味著將我們作為矩陣的樣本,和作為列向量的質心將在它們之間產生成對減法。 為了實現,我們使用 tf.expand_dims
為樣本和質心建立一個額外的維度,強制 tf.subtract
的這種行為。
下一步程式碼處理質心更新:
def update_centroids(samples, nearest_indices, n_clusters): # 將質心更新為與其相關的所有樣本的平均值。 nearest_indices = tf.to_int32(nearest_indices) partitions = tf.dynamic_partition(samples, nearest_indices, n_clusters) new_centroids = tf.concat([tf.expand_dims(tf.reduce_mean(partition, 0), 0) for partition in partitions], 0) return new_centroids
此程式碼選取每個樣本的最近索引,並使用 tf.dynamic_partition
將這些索引分到單獨的組中。 從這裡開始,我們在一個組中使用 tf.reduce_mean
來查詢該組的平均值,從而形成新的質心。 我們只需將它們連線起來形成我們的新質心。
現在我們有了這個部分,我們可以將這些呼叫新增到我們的指令碼中(或者建立一個新指令碼):
import tensorflow as tf import numpy as np from functions import * n_features = 2 n_clusters = 3 n_samples_per_cluster = 500 seed = 700 embiggen_factor = 70 data_centroids, samples = create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed) initial_centroids = choose_random_centroids(samples, n_clusters) nearest_indices = assign_to_nearest(samples, initial_centroids) updated_centroids = update_centroids(samples, nearest_indices, n_clusters) model = tf.global_variables_initializer() with tf.Session() as session: sample_values = session.run(samples) updated_centroid_value = session.run(updated_centroids) print(updated_centroid_value) plot_clusters(sample_values, updated_centroid_value, n_samples_per_cluster)
此程式碼將:
- 從初始質心生成樣本
- 隨機選擇初始質心
- 關聯每個樣本和最近的質心
- 將每個質心更新為與關聯的樣本的平均值
這是 k-means 的單次迭代! 我鼓勵你們練習一下,把它變成一個迭代版本。
1)傳遞給 generate_samples
的種子選項可確保每次執行指令碼時,“隨機”生成的樣本都是一致的。 我們沒有將種子傳遞給 choose_random_centroids
函式,這意味著每次執行指令碼時這些初始質心都不同。 更新指令碼來為隨機質心包含新的種子。
2)迭代地執行 k 均值演算法,其中來自之前迭代的更新的質心用於分配簇,然後用於更新質心,等等。 換句話說,演算法交替呼叫 assign_to_nearest
和 update_centroids
。 在停止之前,更新程式碼來執行此迭代 10 次。 你會發現,隨著 k-means 的更多迭代,得到的質心平均上更接近。 (對於那些對 k-means 有經驗的人,未來的教程將研究收斂函式和其他停止標準。)
訓練和收斂
大多數人工智慧和機器學習的關鍵組成部分是迴圈,即系統在多次訓練迭代中得到改善。 以這種方式訓練的一種非常簡單的方法,就是在 for
迴圈中執行更新。 我們在第 2 課中看到了這種方式的一個例子:
import tensorflow as tf x = tf.Variable(0, name='x') model = tf.global_variables_initializer() with tf.Session() as session: for i in range(5): session.run(model) x = x + 1 print(session.run(x))
我們可以改變此工作流,使用變數來收斂迴圈,如下所示:
import tensorflow as tf x = tf.Variable(0., name='x') threshold = tf.constant(5.) model = tf.global_variables_initializer() with tf.Session() as session: session.run(model) while session.run(tf.less(x, threshold)): x = x + 1 x_value = session.run(x) print(x_value)
這裡的主要變化是,迴圈現在是一個 while
迴圈,測試( tf.less
用於小於測試)為真時繼續迴圈。 在這裡,我們測試 x
是否小於給定閾值(儲存在常量中),如果是,我們繼續迴圈。
梯度下降
任何機器學習庫都必須具有梯度下降演算法。 我認為這是一個定律。 無論如何,Tensorflow 在主題上有一些變化,它們可以直接使用。
梯度下降是一種學習演算法,嘗試最小化某些誤差。 你問哪個誤差? 嗯,這取決於我們,雖然有一些常用的方法。
讓我們從一個基本的例子開始:
import tensorflow as tf import numpy as np # x 和 y 是我們的訓練資料的佔位符 x = tf.placeholder("float") y = tf.placeholder("float") # w 是儲存我們的值的變數。 它使用“猜測”來初始化 # w[0] 是我們方程中的“a”,w[1] 是“b” w = tf.Variable([1.0, 2.0], name="w") # 我們的模型是 y = a*x + b y_model = tf.multiply(x, w[0]) + w[1] # 我們的誤差定義為差異的平方 error = tf.square(y - y_model) # GradientDescentOptimizer 完成繁重的工作 train_op = tf.train.GradientDescentOptimizer(0.01).minimize(error) # TensorFlow 常規 - 初始化值,建立會話並執行模型 model = tf.global_variables_initializer() with tf.Session() as session: session.run(model) for i in range(1000): x_value = np.random.rand() y_value = x_value * 2 + 6 session.run(train_op, feed_dict={x: x_value, y: y_value}) w_value = session.run(w) print("Predicted model: {a:.3f}x + {b:.3f}".format(a=w_value[0], b=w_value[1]))
這裡的主要興趣點是 train_op = tf.train.GradientDescentOptimizer(0.01).minimize(error)
,其中定義了訓練步長。 它旨在最小化誤差變數的值,該變數先前被定義為差的平方(常見的誤差函式)。 0.01 是嘗試學習更好的值所需的步長。
其它優化器
TensorFlow 有一整套優化器,並且你也可以定義自己的優化器(如果你對這類事情感興趣)。 如何使用它們的 API,請參閱 此頁面 。 列表如下:
GradientDescentOptimizer AdagradOptimizer MomentumOptimizer AdamOptimizer FtrlOptimizer RMSPropOptimizer
其他優化方法可能會出現在 TensorFlow 的未來版本或第三方程式碼中。 也就是說,上述優化對於大多數深度學習技術來說已經足夠了。 如果你不確定要使用哪一個,請使用 AdamOptimizer
,除非失敗。
譯者注:原文推薦隨機梯度優化器,在所有優化器裡是最爛的,已更改。
這裡一個重要的注意事項是,我們只優化了一個值,但該值可以是一個數組。 這就是為什麼我們使用 w
作為變數,而不是兩個單獨的變數 a
和 b
。
繪製誤差

image
這個程式碼是上面的一個小改動。 首先,我們建立一個列表來儲存誤差。然後,在迴圈內部,我們顯式地計算 train_op
和誤差。 我們在一行中執行此操作,因此誤差僅計算一次。 如果我們在單獨的行中這樣做,它將計算誤差,然後是訓練步驟,並且在這樣做時,它將需要重新計算誤差。
下面我把程式碼放在上一個程式的 tf.global_variables_initializer()
行下面 - 這一行上面的所有內容都是一樣的。
errors = [] with tf.Session() as session: session.run(model) for i in range(1000): x_train = tf.random_normal((1,), mean=5, stddev=2.0) y_train = x_train * 2 + 6 x_value, y_value = session.run([x_train, y_train]) _, error_value = session.run([train_op, error], feed_dict={x: x_value, y: y_value}) errors.append(error_value) w_value = session.run(w) print("Predicted model: {a:.3f}x + {b:.3f}".format(a=w_value[0], b=w_value[1])) import matplotlib.pyplot as plt plt.plot([np.mean(errors[i-50:i]) for i in range(len(errors))]) plt.show() plt.savefig("errors.png")
你可能已經注意到我在這裡採用視窗平均值 - 使用 np.mean(errors[i-50:i])
而不是僅使用 errors[i]
。 這樣做的原因是我們只在迴圈中測試一次,所以雖然誤差會減小,但它會反彈很多。 採用這個視窗平均值可以平滑一點,但正如你在上面所看到的,它仍然會跳躍。
1)建立第 6 課中的 k-means 示例的收斂函式,如果舊質心與新質心之間的距離小於給定的 epsilon
值,則停止訓練。
2)嘗試從梯度下降示例( w
)中分離 a
和 b
值。
3)我們的例子一次只訓練一個示例,這是低效的。 擴充套件它來一次使用多個(例如 50 個)訓練樣本來學習。
TFLearn
已更新到最新的 TFLearn API。
這些教程主要關注 TensorFlow 的機制,但真正的用例是機器學習。 TensorFlow 有許多用於構建機器學習模型的方法,其中許多可以在官方 API 頁面上找到。 這些函式允許你從頭開始構建模型,包括自定義層面,例如如何構建神經網路中的層。
在本教程中,我們將檢視 TensorFlow Learn,它是名為 skflow
的軟體包的新名稱。 TensorFlow Learn(以下簡稱:TFLearn)是一個機器學習包裝器,基於 scikit-learn API,允許你輕鬆執行資料探勘。 這意味著什麼? 讓我們一步一步地完成它:
機器學習
機器學習是一種概念,構建從資料中學習的演算法,以便對新資料執行操作。 在這種情況下,這意味著我們有一些輸入的訓練資料和預期結果 - 訓練目標。 我們將看看著名的數字資料集,這是一堆手繪數字的影象。 我們的輸入訓練資料是幾千個這些影象,我們的訓練目標是預期的數字。
任務是學習一個模型,可以回答“這是什麼數字?”,對於這樣的輸入:

image
這是一個分類任務,是資料探勘最常見的應用之一。 還有一些稱為迴歸和聚類的變體(以及許多其他變體),但在本課中我們不會涉及它們。
如果你想了解資料探勘的更多資訊,請檢視我的書“ Python-Robert-Layton/dp/1784396052" target="_blank" rel="nofollow,noindex">Python 資料探勘 ”。
Scikit-Learn API
Scikit-learn 是一個用於資料探勘和分析的 Python 包,它非常受歡迎。 這是因為它廣泛支援不同的演算法,令人驚歎的文件,以及龐大而活躍的社群。 其他一個因素是它的一致介面,它的 API,允許人們構建可以使用 scikit-learn 輔助函式訓練的模型,並允許人們非常容易地測試不同的模型。
我們來看看 scikit-learn 的 API,但首先我們需要一些資料。 以下程式碼載入了一組可以使用 matplotlib.pyplot
顯示的數字影象:
from sklearn.datasets import load_digits from matplotlib import pyplot as plt digits = load_digits()
我們可以使用 pyplot.imshow
顯示其中一個影象。 在這裡,我設定 interpolation ='none'
來完全按原樣檢視資料,但是如果你刪除這個屬性,它會變得更清晰(也嘗試減小數字大小)。
fig = plt.figure(figsize=(3, 3)) plt.imshow(digits['images'][66], cmap="gray", interpolation='none') plt.show()
在 scikit-learn 中,我們可以構建一個簡單的分類器,訓練它,然後使用它來預測影象的數字,只需使用四行程式碼:
from sklearn import svm classifier = svm.SVC(gamma=0.001) classifier.fit(digits.data, digits.target) predicted = classifier.predict(digits.data)
第一行只是匯入支援向量機模型,這是一種流行的機器學習方法。
第二行構建“空白”分類器, gamma
設定為 0.001。
第三行使用資料來訓練模型。 在這一行(這是該程式碼的大部分“工作”)中,調整 SVM 模型的內部狀態來擬合訓練資料。 我們還傳遞 digits.data
,因為這是一個展開的陣列,是該演算法的可接受輸入。
最後,最後一行使用這個訓練好的分類器來預測某些資料的類,在這種情況下再次是原始資料集。
要了解這是多麼準確,我們可以使用 NumPy 計算準確度:
import numpy as np print(np.mean(digits.target == predicted))
結果非常令人印象深刻(近乎完美),但這些有點誤導。 在資料探勘中,你永遠不應該在用於訓練的相同資料上評估你的模型。 潛在的問題被稱為“過擬合”,其中模型準確地學習了訓練資料所需的內容,但是無法很好地預測新的沒見過的資料。 為解決這個問題,我們需要拆分我們的訓練和測試資料:
from sklearn.cross_validation import train_test_split X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target)
結果仍然非常好,大約 98%,但這個資料集在資料探勘中是眾所周知的,其特徵已有詳細記錄。 無論如何,我們現在知道我們要做什麼,讓我們在 TFLearn 中實現它!
TFLearn
TensorFlow Learn 介面距離 scikit-learn 的介面只有一小步之遙:
from tensorflow.contrib import learn n_classes = len(set(y_train)) classifier = learn.LinearClassifier(feature_columns=[tf.contrib.layers.real_valued_column("", dimension=X_train.shape[1])], n_classes=n_classes) classifier.fit(X_train, y_train, steps=10) y_pred = classifier.predict(X_test)
唯一真正的變化是 import
語句和模型,它來自不同的可用演算法列表。 一個區別是分類器需要知道它將預測多少個類,可以使用 len(set(y_train))
找到,或者換句話說,訓練資料中有多少個唯一值。
另一個區別是,需要告知分類器預期的特徵型別。 對於這個例子,我們有真正重要的連續特徵,所以我們可以簡單地指定 feature_columns
值(它需要在列表中)。 如果你使用類別特徵,則需要單獨說明。 這方面的更多資訊,請檢視 TFLearn 示例的文件 。
可以像以前一樣評估結果,來計算準確性,但 scikit-learn 有 classification_report,它提供了更深入的瞭解:
from sklearn import metrics print(metrics.classification_report(y_true=y_test, y_pred=y_pred))
結果顯示每個類的召回率和精度,以及總體值和 f 度量。這些分數比準確性更可靠,更多資訊請參閱維基百科上的 此頁面 。
這是 TFLearn 的高階概述。你可以定義自定義分類器,你將在練習 3 中看到它們,並將分類器組合到流水線中(對此的支援很小,但正在改進)。該軟體包有可能成為工業和學術界廣泛使用的資料探勘軟體包。
1)將分類器更改為 DNNClassifier
並重新執行。隨意告訴所有朋友你現在使用深度學習來做資料分析。
2) DNNClassifier
的預設引數是好的,但不完美。嘗試更改引數來獲得更高的分數。
3)從 TFLearn 的文件中檢視 此示例 並下載 CIFAR 10 資料集。構建一個使用卷積神經網路預測影象的分類器。你可以使用此程式碼載入資料:
def load_cifar(file): import pickle import numpy as np with open(file, 'rb') as inf: cifar = pickle.load(inf, encoding='latin1') data = cifar['data'].reshape((10000, 3, 32, 32)) data = np.rollaxis(data, 3, 1) data = np.rollaxis(data, 3, 1) y = np.array(cifar['labels']) # 最開始只需 2 和 9 # 如果要構建大型模型,請刪除這些行 mask = (y == 2) | (y == 9) data = data[mask] y = y[mask] return data, y