第三章 使用距離向量構(gòu)建模型
作者:Trent Hauck
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
這一章中,我們會涉及到聚類。聚類通常和非監(jiān)督技巧組合到一起。這些技巧假設我們不知道結(jié)果變量。這會使結(jié)果模糊,以及實踐客觀。但是,聚類十分有用。我們會看到,我們可以使用聚類,將我們的估計在監(jiān)督設置中“本地化”。這可能就是聚類非常高效的原因。它可以處理很大范圍的情況,通常,結(jié)果也不怎么正常。
這一章中我們會瀏覽大量應用,從圖像處理到回歸以及離群點檢測。通過這些應用,我們會看到聚類通??梢酝ㄟ^概率或者優(yōu)化結(jié)構(gòu)來觀察。不同的解釋會導致不同的權(quán)衡。我們會看到,如何訓練模型,以便讓工具嘗試不同模型,在面對聚類問題的時候。
3.1 使用 KMeans 對數(shù)據(jù)聚類
聚類是個非常實用的技巧。通常,我們在采取行動時需要分治??紤]公司的潛在客戶列表。公司可能需要將客戶按類型分組,之后為這些分組劃分職責。聚類可以使這個過程變得容易。
KMeans 可能是最知名的聚類算法之一,并且也是最知名的無監(jiān)督學習技巧之一。
準備
首先,讓我們看一個非常簡單的聚類,之后我們再討論 KMeans 如何工作。
>>> from sklearn.datasets import make_blobs
>>> blobs, classes = make_blobs(500, centers=3)
同樣,由于我們繪制一些圖表,導入matplotlib,像這樣:
>>> import matplotlib.pyplot as plt
操作步驟
我們打算瀏覽一個簡單的例子,它對偽造數(shù)據(jù)進行聚類。之后我們會稍微談論一下,KMeans 如何工作,來尋找最優(yōu)的塊數(shù)量。
看一看我們的數(shù)據(jù)塊,我們可以看到,有三個不同的簇。
>>> f, ax = plt.subplots(figsize=(7.5, 7.5))
>>> ax.scatter(blobs[:, 0], blobs[:, 1], color=rgb[classes])
>>> rgb = np.array(['r', 'g', 'b'])
>>> ax.set_title("Blobs")
輸出如下:

現(xiàn)在我們可以使用 KMeans 來尋找這些簇的形心。第一個例子中,我們假裝知道有三個形心。
>>> from sklearn.cluster import KMeans
>>> kmean = KMeans(n_clusters=3)
>>> kmean.fit(blobs)
KMeans(copy_x=True, init='k-means++', max_iter=300, n_clusters=3,
n_init=10, n_jobs=1, precompute_distances=True,
random_state=None, tol=0.0001, verbose=0)
>>> kmean.cluster_centers_ array([[ 0.47819567, 1.80819197],
[ 0.08627847, 8.24102715],
[ 5.2026125 , 7.86881767]])
>>> f, ax = plt.subplots(figsize=(7.5, 7.5))
>>> ax.scatter(blobs[:, 0], blobs[:, 1], color=rgb[classes])
>>> ax.scatter(kmean.cluster_centers_[:, 0],
kmean.cluster_centers_[:, 1], marker='*', s=250,
color='black', label='Centers')
>>> ax.set_title("Blobs")
>>> ax.legend(loc='best')
下面的截圖展示了輸出:

其它屬性也很實用。例如,labels_屬性會產(chǎn)生每個點的預期標簽。
>>> kmean.labels_[:5]
array([1, 1, 2, 2, 1], dtype=int32)
我們可以檢查,例如,labels_是否和類別相同,但是由于 KMeans 不知道類別是什么,它不能給兩個類別分配相同的索引值:
>>> classes[:5]
array([0, 0, 2, 2, 0])
將類別中的1變成0來查看是否與labels_匹配。
transform函數(shù)十分有用,它會輸出每個點到形心的距離。
>>> kmean.transform(blobs)[:5]
array([[ 6.47297373, 1.39043536, 6.4936008 ],
[ 6.78947843, 1.51914705, 3.67659072],
[ 7.24414567, 5.42840092, 0.76940367],
[ 8.56306214, 5.78156881, 0.89062961],
[ 7.32149254, 0.89737788, 5.12246797]])
工作原理
KMeans 實際上是個非常簡單的算法,它使簇中的點到均值的距離的平方和最小。
首先它會設置一個預定義的簇數(shù)量K,之后執(zhí)行這些事情:
- 將每個數(shù)據(jù)點分配到最近的簇中。
- 通過計算初中每個數(shù)據(jù)點的均值,更新每個形心。
直到滿足特定條件。
3.2 優(yōu)化形心數(shù)量
形心難以解釋,并且也難以判斷是否數(shù)量正確。理解你的數(shù)據(jù)是否是未分類的十分重要,因為這會直接影響我們可用的評估手段。
準備
為無監(jiān)督學習評估模型表現(xiàn)是個挑戰(zhàn)。所以,在了解真實情況的時候,sklearn擁有多種方式來評估聚類,但在不了解時就很少。
我們會以一個簡單的簇模型開始,并評估它的相似性。這更多是出于機制的目的,因為測量一個簇的相似性在尋找簇數(shù)量的真實情況時顯然沒有用。
操作步驟
為了開始,我們會創(chuàng)建多個數(shù)據(jù)塊,它們可用于模擬數(shù)據(jù)簇。
>>> from sklearn.datasets import make_blobs
>>> import numpy as np
>>> blobs, classes = make_blobs(500, centers=3)
>>> from sklearn.cluster import KMeans
>>> kmean = KMeans(n_clusters=3)
>>> kmean.fit(blobs)
KMeans(copy_x=True, init='k-means++', max_iter=300, n_clusters=3,
n_init=10, n_jobs=1, precompute_distances=True,
random_state=None, tol=0.0001, verbose=0)
首先,我們查看輪廓(Silhouette)距離。輪廓距離是簇內(nèi)不相似性、最近的簇間不相似性、以及這兩個值最大值的比值。它可以看做簇間分離程度的度量。
讓我們看一看數(shù)據(jù)點到形心的距離分布,理解輪廓距離非常有用。
>>> from sklearn import metrics
>>> silhouette_samples = metrics.silhouette_samples(blobs,
kmean.labels_)
>>> np.column_stack((classes[:5], silhouette_samples[:5]))
array([[ 1., 0.87617292],
[ 1., 0.89082363],
[ 1., 0.88544994],
[ 1., 0.91478369],
[ 1., 0.91308287]])
>>> f, ax = plt.subplots(figsize=(10, 5))
>>> ax.set_title("Hist of Silhouette Samples")
>>> ax.hist(silhouette_samples)
輸出如下:

要注意,通常接近 1 的系數(shù)越高,分數(shù)就越高。
工作原理
輪廓系數(shù)的均值通常用于描述整個模型的擬合度。
>>> silhouette_samples.mean()
0.57130462953339578
這十分普遍,事實上,metrics模塊提供了一個函數(shù)來獲得剛才的值。
現(xiàn)在,讓我們訓練多個簇的模型,并看看平均得分是什么樣:
# first new ground truth
>>> blobs, classes = make_blobs(500, centers=10)
>>> sillhouette_avgs = []
# this could take a while
>>> for k in range(2, 60):
kmean = KMeans(n_clusters=k).fit(blobs)
sillhouette_avgs.append(metrics.silhouette_score(blobs,
kmean.labels_))
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.plot(sillhouette_avgs)
下面是輸出:

這個繪圖表明,輪廓均值隨著形心數(shù)量的變化情況。我們可以看到最優(yōu)的數(shù)量是 3,根據(jù)所生成的數(shù)據(jù)。但是最優(yōu)的數(shù)量看起來是 6 或者 7。這就是聚類的實際情況,十分普遍,我們不能獲得正確的簇數(shù)量,我們只能估計簇數(shù)量的近似值。
3.3 評估聚類的正確性
我們之前討論了不知道真實情況的條件下的聚類評估。但是,我們還沒有討論簇已知條件下的 KMeans 評估。在許多情況下,這都是不可知的,但是如果存在外部的標注,我們就會知道真實情況,或者至少是代理。
準備
所以,讓我們假設有一個世界,其中我們有一些外部代理,向我們提供了真實情況。
我們會創(chuàng)建一個簡單的數(shù)據(jù)集,使用多種方式評估相對于真實慶康的正確性。之后討論它們。
操作步驟
在我們開始度量之前,讓我們先查看數(shù)據(jù)集:
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> colors = ['r', 'g', 'b']
>>> for i in range(3):
p = blobs[ground_truth == i]
ax.scatter(p[:,0], p[:,1], c=colors[i],
label="Cluster {}".format(i))
>>> ax.set_title("Cluster With Ground Truth")
>>> ax.legend()
>>> f.savefig("9485OS_03-16")
下面是輸出:

既然我們已經(jīng)訓練了模型,讓我們看看簇的形心:
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> colors = ['r', 'g', 'b']
>>> for i in range(3):
p = blobs[ground_truth == i]
ax.scatter(p[:,0], p[:,1], c=colors[i], label="Cluster {}".format(i))
>>> ax.scatter(kmeans.cluster_centers_[:, 0],
kmeans.cluster_centers_[:, 1], s=100,
color='black',
label='Centers')
>>> ax.set_title("Cluster With Ground Truth")
>>> ax.legend()
>>> f.savefig("9485OS_03-17")
下面是輸出:

既然我們能夠?qū)⒕垲惐憩F(xiàn)看做分類練習,在其語境中有用的方法在這里也有用:
>>> for i in range(3):
print (kmeans.labels_ == ground_truth)[ground_truth == i]
.astype(int).mean()
0.0778443113772
0.990990990991
0.0570570570571
很顯然我們有一些錯亂的簇。所以讓我們將其捋直,之后我們查看準確度。
>>> new_ground_truth = ground_truth.copy()
>>> new_ground_truth[ground_truth == 0] = 2
>>> new_ground_truth[ground_truth == 2] = 0
>>> for i in range(3):
print (kmeans.labels_ == new_ground_truth)[ground_truth == i]
.astype(int).mean()
0.919161676647
0.990990990991
0.90990990991
所以我們 90% 的情況下都是正確的。第二個相似性度量是互信息( mutual information score)得分。
>>> from sklearn import metrics
>>> metrics.normalized_mutual_info_score(ground_truth, kmeans.labels_)
0.78533737204433651
分數(shù)靠近 0,就說明標簽的分配可能不是按照相似過程生成的。但是分數(shù)靠近 1,就說明兩個標簽有很強的一致性。
例如,讓我們看一看互信息分數(shù)自身的情況:
>>> metrics.normalized_mutual_info_score(ground_truth, ground_truth)
1.0
通過名稱,我們可以分辨出可能存在未規(guī)范化的mutual_info_score:
>>> metrics.mutual_info_score(ground_truth, kmeans.labels_)
0.78945287371677486
這非常接近了。但是,規(guī)范化的互信息是互信息除以每個真實值和標簽的熵的乘積的平方根。
更多
有一個度量方式我們尚未討論,并且不依賴于真實情況,就是慣性(inertia)度量。當前,它作為一種度量并沒有詳細記錄。但是,它是 KMeans 中最簡單的度量。
慣性是每個數(shù)據(jù)點和它所分配的簇的平方差之和。我們可以稍微使用 NumPy 來計算它:
>>> kmeans.inertia_
3.4 使用 MiniBatch KMeans 處理更多數(shù)據(jù)
KMeans 是一個不錯的方法,但是不適用于大量數(shù)據(jù)。這是因為 KMenas 的復雜度。也就是說,我們可以使用更低的算法復雜度來獲得近似解。
準備
MiniBatch Kmeans 是 KMeans 的更快實現(xiàn)。KMeans 的計算量非常大,問題是 NPH 的。
但是,使用 MiniBatch KMeans,我們可以將 KMeans 加速幾個數(shù)量級。這通過處理多個子樣本來完成,它們叫做 MiniBatch。如果子樣本是收斂的,并且擁有良好的初始條件,就得到了常規(guī) KMeans 的近似解。
操作步驟
讓我們對 MiniBatch 聚類做一個概要的性能分析。首先,我們觀察總體的速度差異,之后我們會觀察估計中的誤差。
>>> from sklearn.datasets import make_blobs
>>> blobs, labels = make_blobs(int(1e6), 3)
>>> from sklearn.cluster import KMeans, MiniBatchKMeans
>>> kmeans = KMeans(n_clusters=3) >>> minibatch = MiniBatchKMeans(n_clusters=3)
要理解這些度量的目的是暴露問題。所以,需要多加小心,來確保跑分的高精度性。這個話題還有大量可用的信息。如果你真的希望了解,MiniBatch KMeans 為何在粒度上更具優(yōu)勢,最好還是要閱讀它們。
既然準備已經(jīng)完成,我們可以測量時間差異:
>>> %time kmeans.fit(blobs) #IPython Magic CPU times: user 8.17 s, sys: 881 ms, total: 9.05 s Wall time: 9.97 s
>>> %time minibatch.fit(blobs) CPU times: user 4.04 s, sys: 90.1 ms, total: 4.13 s Wall time: 4.69 s
CPU 時間上有很大差異。聚類性能上的差異在下面展示:
>>> kmeans.cluster_centers_[0] array([ 1.10522173, -5.59610761, -8.35565134])
>>> minibatch.cluster_centers_[0] array([ 1.12071187, -5.61215116, -8.32015587])
我們可能要問的下一個問題就是,兩個形心距離多遠。
>>> from sklearn.metrics import pairwise
>>> pairwise.pairwise_distances(kmeans.cluster_centers_[0],
minibatch.cluster_centers_[0])
array([[ 0.03305309]])
看起來十分接近了。對角線包含形心的差異:
>>> np.diag(pairwise.pairwise_distances(kmeans.cluster_centers_,
minibatch.cluster_centers_))
array([ 0.04191979, 0.03133651, 0.04342707])
工作原理
這里的批次就是關鍵。批次被迭代來尋找批次均值。對于下一次迭代來說,前一個批次的均值根據(jù)當前迭代來更新。有多種選項,用于控制 KMeans 的通用行為,和決定 MiniBatch KMeans 的參數(shù)。
batch_size參數(shù)決定批次應為多大。只是玩玩的話,我們可以運行 MiniBatch,但是,此時我們將批次數(shù)量設置為和數(shù)據(jù)集大小相同。
>>> minibatch = MiniBatchKMeans(batch_size=len(blobs))
>>> %time minibatch.fit(blobs)
CPU times: user 34.6 s, sys: 3.17 s, total: 37.8 s Wall time: 44.6 s
顯然,這就違背了問題的核心,但是這的確展示了重要東西。選擇差勁的初始條件可能影響我們的模型,特別是聚類模型的收斂。使用 MiniBatch KMeans,全局最優(yōu)是否能達到,是不一定的。
3.5 使用 KMeans 聚類來量化圖像
圖像處理是個重要的話題,其中聚類有一些應用。值得指出的是,Python 中有幾種非常不錯的圖像處理庫。Scikit-image 是 Scikit-learn 的“姐妹”項目。如果你打算做任何復雜的事情,都值得看一看它。
準備
我們在這篇秘籍中會有一些樂趣。目標是使用聚類來把圖像變模糊。
首先,我們要利用 SciPy 來讀取圖像。圖像翻譯為三維數(shù)組,x和y坐標描述了高度和寬度,第三個維度表示每個圖像的 RGB 值。
# in your terminal
$ wget http://blog.trenthauck.com/assets/headshot.jpg
操作步驟
現(xiàn)在,讓我們在 Python 中讀取圖像:
>>> from scipy import ndimage
>>> img = ndimage.imread("headshot.jpg")
>>> plt.imshow(img)
下面就是圖像:

嘿,這就是(年輕時期的)作者。
既然我們已經(jīng)有了圖像,讓我們檢查它的維度:
>>> img.shape
(420, 420, 3)
為了實際量化圖像,我們需要將其轉(zhuǎn)換為二維數(shù)組,長為420x420,寬為 RGB 值。思考它的更好的方法,是擁有一堆三維空間中的數(shù)據(jù)點,并且對點進行聚類來降低圖像中的不同顏色的數(shù)量 -- 這是一個簡單的量化方式。
首先,讓我們使數(shù)組變形,它是個 NumPy 數(shù)組,所以非常簡單:
>>> x, y, z = img.shape
>>> long_img = img.reshape(x*y, z)
>>> long_img.shape (176400, 3)
現(xiàn)在我們開始聚類過程。首先,讓我們導入聚類模塊,并創(chuàng)建 KMeans 對象。我們傳入n_clusters=5,使我們擁有 5 個簇,或者實際上是 5 個不同顏色。
這是個不錯的秘籍,我們使用前面提到的輪廓距離:
>>> from sklearn import cluster
>>> k_means = cluster.KMeans(n_clusters=5)
>>> k_means.fit(long_img)
既然我們已經(jīng)訓練了 KMeans 對象,讓我們看看我們的眼色:
>>> centers = k_means.cluster_centers_
>>> centers
array([[ 142.58775848, 206.12712986, 226.04416873],
[ 86.29356543, 68.86312505, 54.04770507],
[ 194.36182899, 172.19845258, 149.65603813],
[ 24.67768412, 20.45778933, 16.19698314],
[ 149.27801776, 132.19850659, 115.32729167]])
工作原理
既然我們擁有了形心,我們需要的下一個東西就是標簽。它會告訴我們,哪個點關聯(lián)哪個簇。
>>> labels = k_means.labels_
>>> labels[:5] array([1, 1, 1, 1, 1], dtype=int32)
這個時候,我們需要最簡的 NumPy 操作,之后是一個變形,我們就擁有的新的圖像:
>>> plt.imshow(centers[labels].reshape(x, y, z))
下面就是產(chǎn)生的圖像:

3.6 尋找特征空間中的最接近對象
有時,最簡單的事情就是求出兩個對象之間的距離。我們剛好需要尋找一些距離的度量,計算成對(Pairwise)距離,并將結(jié)果與我們的預期比較。
準備
Scikit-learn 中,有個叫做sklearn.metrics.pairwise的底層工具。它包含一些服務函數(shù),計算矩陣X中向量之間的距離,或者X和Y中的向量距離。
這對于信息檢索來說很實用。例如,提供一組客戶信息,帶有屬性X,我們可能希望選取有個客戶代表,并找到與這個客戶最接近的客戶。實際上,我們可能希望將客戶按照相似性度量的概念,使用距離函數(shù)來排序。相似性的質(zhì)量取決于特征空間選取,以及我們在空間上所做的任何變換。
操作步驟
我們會使用pairwise_distances函數(shù)來判斷對象的接近程度。要記住,接近程度就像我們用于聚類/分類的距離函數(shù)。
首先,讓我們從metric模塊導入pairwise_distances函數(shù),并創(chuàng)建用于操作的數(shù)據(jù)集:
>>> from sklearn.metrics import pairwise
>>> from sklearn.datasets import make_blobs
>>> points, labels = make_blobs()
用于檢查距離的最簡單方式是pairwise_distances:
>>> distances = pairwise.pairwise_distances(points)
distances是個 NxN的矩陣,對角線為 0。在最簡單的情況中,讓我們先看看每個點到第一個點的距離:
>>> np.diag(distances) [:5]
array([ 0., 0., 0., 0., 0.])
現(xiàn)在我們可以查找最接近于第一個點的點:
>>> distances[0][:5]
array([ 0., 11.82643041,1.23751545, 1.17612135, 14.61927874])
將點按照接近程度排序,很容易使用np.argsort做到:
>>> ranks = np.argsort(distances[0])
>>> ranks[:5]
array([ 0, 27, 98, 23, 67])
argsort的好處是,現(xiàn)在我們可以排序我們的points矩陣,來獲得真實的點。
>>> points[ranks][:5]
array([[ 8.96147382, -1.90405304],
[ 8.75417014, -1.76289919],
[ 8.78902665, -2.27859923],
[ 8.59694131, -2.10057667],
[ 8.70949958, -2.30040991]])
觀察接近的點是什么樣子,可能十分有用。結(jié)果在意料之中:

工作原理
給定一些距離函數(shù),每個點都以成對函數(shù)來度量。通常為歐幾里得距離,它是:

詳細來說,它計算了兩個向量每個分量的差,計算它們的平方,求和,之后計算它的平方根。這看起來很熟悉,因為在計算均方誤差的時候,我們使用的東西很相似。如果我們計算了平方根,就一樣了。實際上,經(jīng)常使用的度量是均方根誤差(RMSE),它就是距離函數(shù)的應用。
在 Python 中,這看起來是:
>>> def euclid_distances(x, y):
return np.power(np.power(x - y, 2).sum(), .5)
>>> euclid_distances(points[0], points[1])
11.826430406213145
Scikit-learn 中存在一些其他函數(shù),但是 Scikit-learn 也會使用 SciPy 的距離函數(shù)。在本書編寫之時,Scikit-learn 距離函數(shù)支持稀疏矩陣。距離函數(shù)的更多信息請查看 SciPy 文檔。
cityblockcosineeuclideanl1l2manhattan
我們現(xiàn)在可以解決問題了。例如,如果我們站在原點處的格子上,并且線是街道,為了到達點(5,5),我們需要走多遠呢?
>>> pairwise.pairwise_distances([[0, 0], [5, 5]], metric='cityblock')[0]
array([ 0., 10.])
更多
使用成對距離,我們可以發(fā)現(xiàn)位向量之間的相似性。這是漢明距離的事情,它定義為:

使用下列命令:
>>> X = np.random.binomial(1, .5, size=(2, 4)).astype(np.bool)
>>> X
array([[False, True, False, False],
[False, False, False, True]], dtype=bool)
>>> pairwise.pairwise_distances(X, metric='hamming')
array([[ 0. , 0.25],
[ 0.25, 0. ]])
3.7 使用高斯混合模型的概率聚類
在 KMeans 中,我們假設簇的方差是相等的。這會導致空間的細分,這決定了簇如何被分配。但是,如果有一種場景,其中方差不是相等的,并且每個簇中的點擁有一個與之相關的概率,會怎么樣?
準備
有一種更加概率化的方式,用于查看 KMeans 聚類。KMeans 聚類相當于將協(xié)方差矩陣S應用于高斯混合模型,這個矩陣可以分解為單位矩陣成誤差。對于每個簇,協(xié)方差結(jié)構(gòu)是相同的。這就產(chǎn)生了球形聚類。
但是,如果我們允許S變化,就可以估計 GMM,并將其用于預測。我們會以單變量的角度看到它的原理,之后擴展為多個維度。
操作步驟
首先,我們需要創(chuàng)建一些數(shù)據(jù)。例如,讓我們模擬女性和男性的身高。我們會在整個秘籍中使用這個例子。這是個簡單的例子,但是會展示出我們在 N 維空間中想要完成的東西,這比較易于可視化:
>>> import numpy as np
>>> N = 1000
>>> in_m = 72
>>> in_w = 66
>>> s_m = 2
>>> s_w = s_m
>>> m = np.random.normal(in_m, s_m, N)
>>> w = np.random.normal(in_w, s_w, N)
>>> from matplotlib import pyplot as plt
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.set_title("Histogram of Heights")
>>> ax.hist(m, alpha=.5, label="Men");
>>> ax.hist(w, alpha=.5, label="Women");
>>> ax.legend()
下面是輸出:

下面,我們的興趣是,對分組二次抽樣,訓練分布,之后預測剩余分組。
>>> random_sample = np.random.choice([True, False], size=m.size)
>>> m_test = m[random_sample]
>>> m_train = m[~random_sample]
>>> w_test = w[random_sample]
>>> w_train = w[~random_sample]
現(xiàn)在我們需要獲得男性和女性高度的經(jīng)驗分布,基于訓練集:
>>> from scipy import stats
>>> m_pdf = stats.norm(m_train.mean(), m_train.std())
>>> w_pdf = stats.norm(w_train.mean(), w_train.std())
對于測試集,我們要計算,基于數(shù)據(jù)點從每個分布中生成的概率,并且最可能的分布會分配合適的標簽。當然,我們會看到有多么準確。
>>> m_pdf.pdf(m[0])
0.043532673457165431
>>> w_pdf.pdf(m[0])
9.2341848872766183e-07
要注意概率中的差異。
假設當男性的概率更高時,我們會猜測,但是如果女性的概率更高,我們會覆蓋它。
>>> guesses_m = np.ones_like(m_test)
>>> guesses_m[m_pdf.pdf(m_test) < w_pdf.pdf(m_test)] = 0
顯然,問題就是我們有多么準確。由于正確情況下guesses_m為 1,否則為 0,我們計算向量的均值來獲取準確度。
>>> guesses_m.mean()
0.93775100401606426
不是太糟。現(xiàn)在,來看看我們在女性的分組中做的有多好,使用下面的命令:
>>> guesses_w = np.ones_like(w_test)
>>> guesses_w[m_pdf.pdf(w_test) > w_pdf.pdf(w_test)] = 0
>>> guesses_w.mean() 0.93172690763052213
讓我們允許兩組間的方差不同。首先,創(chuàng)建一些新的數(shù)組:
>>> s_m = 1
>>> s_w = 4
>>> m = np.random.normal(in_m, s_m, N)
>>> w = np.random.normal(in_w, s_w, N)
之后,創(chuàng)建訓練集:
>>> m_test = m[random_sample]
>>> m_train = m[~random_sample]
>>> w_test = w[random_sample]
>>> w_train = w[~random_sample]
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.set_title("Histogram of Heights")
>>> ax.hist(m_train, alpha=.5, label="Men");
>>> ax.hist(w_train, alpha=.5, label="Women");
>>> ax.legend()
讓我們看看男性和女性之間的方差差異:

現(xiàn)在我們可以創(chuàng)建相同的 PDF:
>>> m_pdf = stats.norm(m_train.mean(), m_train.std())
>>> w_pdf = stats.norm(w_train.mean(), w_train.std())
下面是輸出:

你可以在多維空間中想象他:
>>> class_A = np.random.normal(0, 1, size=(100, 2))
>>> class_B = np.random.normal(4, 1.5, size=(100, 2))
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.scatter(class_A[:,0], class_A[:,1], label='A', c='r')
>>> ax.scatter(class_B[:,0], class_B[:,1], label='B')
下面是輸出:

工作原理
好的,所以既然我們看過了,我們基于分布對點分類的方式,讓我們看看如何在 Scikit 中首先:
>>> from sklearn.mixture import GMM
>>> gmm = GMM(n_components=2)
>>> X = np.row_stack((class_A, class_B))
>>> y = np.hstack((np.ones(100), np.zeros(100)))
由于我們是小巧的數(shù)據(jù)科學家,我們創(chuàng)建訓練集:
>>> train = np.random.choice([True, False], 200)
>>> gmm.fit(X[train]) GMM(covariance_type='diag', init_params='wmc', min_covar=0.001,
n_components=2, n_init=1, n_iter=100, params='wmc',
random_state=None, thresh=0.01)
訓練和預測的完成方式,和 Scikit-learn 的其它對象相同。
>>> gmm.fit(X[train])
>>> gmm.predict(X[train])[:5]
array([0, 0, 0, 0, 0])
既然模型已經(jīng)訓練了,有一些值得一看的其它方法。
例如,使用score_examples,我們實際上可以為每個標簽獲得每個樣例的可能性。
3.8 將 KMeans 用于離群點檢測
這一章中,我們會查看 Kmeans 離群點檢測的機制和正義。它對于隔離一些類型的錯誤很實用,但是使用時應多加小心。
準備
這個秘籍中,我們會使用 KMeans,對簇中的點執(zhí)行離群點檢測。要注意,提及離群點和離群點檢測時有很多“陣營”。以便面,我們可能通過移除離群點,來移除由數(shù)據(jù)生成過程生成的點。另一方面,離群點可能來源于測量誤差或一些其它外部因素。
這就是爭議的重點。這篇秘籍的剩余部分有關于尋找離群點。我們的假設是,我們移除離群點的選擇是合理的。
離群點檢測的操作是,查找簇的形心,之后通過點到形心的距離來識別潛在的離群點。
操作步驟
首先,我們會生成 100 個點的單個數(shù)據(jù)塊,之后我們會識別 5 個離形心最遠的點。它們就是潛在的離群點。
>>> from sklearn.datasets import make_blobs
>>> X, labels = make_blobs(100, centers=1)
>>> import numpy as np
非常重要的是,Kmeans 聚類只有一個形心。這個想法類似于用于離群點檢測的單類 SVM。
>>> from sklearn.cluster import KMeans
>>> kmeans = KMeans(n_clusters=1)
>>> kmeans.fit(X)
現(xiàn)在,讓我們觀察繪圖。對于那些遠離中心的點,嘗試猜測哪個點會識別為五個離群點之一:
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.set_title("Blob")
>>> ax.scatter(X[:, 0], X[:, 1], label='Points')
>>> ax.scatter(kmeans.cluster_centers_[:, 0],
kmeans.cluster_centers_[:, 1],
label='Centroid',
color='r')
>>> ax.legend()
下面就是輸出:

現(xiàn)在,讓我們識別五個最接近的點:
>>> distances = kmeans.transform(X)
# argsort returns an array of indexes which will sort the array in ascending order
# so we reverse it via [::-1] and take the top five with [:5]
>>> sorted_idx = np.argsort(distances.ravel())[::-1][:5]
現(xiàn)在,讓我們看看哪個點離得最遠:
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.set_title("Single Cluster")
>>> ax.scatter(X[:, 0], X[:, 1], label='Points')
>>> ax.scatter(kmeans.cluster_centers_[:, 0],
kmeans.cluster_centers_[:, 1],
label='Centroid', color='r')
>>> ax.scatter(X[sorted_idx][:, 0], X[sorted_idx][:, 1],
label='Extreme Value', edgecolors='g',
facecolors='none', s=100)
>>> ax.legend(loc='best')
下面是輸出:

如果我們喜歡的話,移除這些點很容易。
>>> new_X = np.delete(X, sorted_idx, axis=0)
同樣,移除這些點之后,形心明顯變化了。
>>> new_kmeans = KMeans(n_clusters=1)
>>> new_kmeans.fit(new_X)
讓我們將舊的和新的形心可視化:
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.set_title("Extreme Values Removed")
>>> ax.scatter(new_X[:, 0], new_X[:, 1], label='Pruned Points')
>>> ax.scatter(kmeans.cluster_centers_[:, 0],
kmeans.cluster_centers_[:, 1], label='Old Centroid',
color='r', s=80, alpha=.5)
>>> ax.scatter(new_kmeans.cluster_centers_[:, 0],
new_kmeans.cluster_centers_[:, 1], label='New Centroid',
color='m', s=80, alpha=.5)
>>> ax.legend(loc='best')
下面是輸出:

顯然,形心沒有移動多少,僅僅移除五個極端點時,我們的預期就是這樣。這個過程可以重復,知道我們對數(shù)據(jù)表示滿意。
工作原理
我們已經(jīng)看到,高斯分布和 KMeans 聚類之間有本質(zhì)聯(lián)系。讓我們基于形心和樣本的協(xié)方差矩陣創(chuàng)建一個經(jīng)驗高斯分布,并且查看每個點的概率 -- 理論上是我們溢出的五個點。這剛好展示了,我們實際上溢出了擁有最低可能性的值。距離和可能性之間的概念十分重要,并且在你的機器學習訓練中會經(jīng)常出現(xiàn)。
使用下列命令來創(chuàng)建經(jīng)驗高斯分布:
>>> from scipy import stats
>>> emp_dist = stats.multivariate_normal(
kmeans.cluster_centers_.ravel())
>>> lowest_prob_idx = np.argsort(emp_dist.pdf(X))[:5]
>>> np.all(X[sorted_idx] == X[lowest_prob_idx]) True
3.9 將 KNN 用于回歸
回歸在這本書的其它地方有所設計,但是我們可能打算在特征空間的“口袋”中運行回歸。我們可以認為,我們的數(shù)據(jù)集要經(jīng)過多道數(shù)據(jù)處理工序。如果是這樣,只訓練相似數(shù)據(jù)點是個不錯的想法。
準備
我們的老朋友,回歸,可以用于聚類的上下文中?;貧w顯然是個監(jiān)督學習技巧,所以我們使用 KNN 而不是 KMeans。
對于 KNN 回歸來說,我們使用特征空間中的 K 個最近點,來構(gòu)建回歸,而不像常規(guī)回歸那樣使用整個特征空間。
操作步驟
對于這個秘籍,我們使用iris數(shù)據(jù)集。如果我們打算預測一些東西,例如每朵花的花瓣寬度,根據(jù)iris物種來聚類可能會給我們更好的結(jié)果。KNN 回歸不會根據(jù)物種來聚類,但我們的假設是,相同物種的 X 會接近,或者這個案例中,是花瓣長度。
對于這個秘籍,我們使用iris數(shù)據(jù)集:
>>> from sklearn import datasets
>>> iris = datasets.load_iris()
>>> iris.feature_names ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
我們嘗試基于萼片長度和寬度來預測花瓣長度。我們同時訓練一個線性回歸,來對比觀察 KNN 回歸有多好。
>>> from sklearn.linear_model import LinearRegression
>>> lr = LinearRegression()
>>> lr.fit(X, y)
>>> print "The MSE is: {:.2}".format(np.power(y - lr.predict(X),
2).mean())
The MSE is: 0.15
現(xiàn)在,對于 KNN 回歸,使用下列代碼:
>>> from sklearn.neighbors import KNeighborsRegressor
>>> knnr = KNeighborsRegressor(n_neighbors=10)
>>> knnr.fit(X, y)
>>> print "The MSE is: {:.2}".format(np.power(y - knnr.predict(X),
2).mean())
The MSE is: 0.069
讓我們看看,當我們讓它使用最接近的 10 個點用于回歸時,KNN 回歸會做什么?
>>> f, ax = plt.subplots(nrows=2, figsize=(7, 10))
>>> ax[0].set_title("Predictions")
>>> ax[0].scatter(X[:, 0], X[:, 1], s=lr.predict(X)*80, label='LR
Predictions', color='c', edgecolors='black')
>>> ax[1].scatter(X[:, 0], X[:, 1], s=knnr.predict(X)*80, label='k-NN
Predictions', color='m', edgecolors='black')
>>> ax[0].legend()
>>> ax[1].legend()
輸出如下:

很顯然,預測大部分都是接近的。但是讓我們與實際情況相比,看看 Setosa 物種的預測:
>>> setosa_idx = np.where(iris.target_names=='setosa')
>>> setosa_mask = iris.target == setosa_idx[0]
>>> y[setosa_mask][:5] array([ 0.2, 0.2, 0.2, 0.2, 0.2])
>>> knnr.predict(X)[setosa_mask][:5]
array([ 0.28, 0.17, 0.21, 0.2 , 0.31])
>>> lr.predict(X)[setosa_mask][:5]
array([ 0.44636645, 0.53893889, 0.29846368, 0.27338255, 0.32612885])
再次觀察繪圖,Setosa 物種(左上方的簇)被線性回歸估計過高,但是 KNN 非常接近真實值。
工作原理
KNN 回歸非常簡單,它計算被測試點的 K 個最接近點的均值。
讓我們手動預測單個點:
>>> example_point = X[0
現(xiàn)在,我們需要獲取離我們的our_example_point最近的 10 個點:
>>> from sklearn.metrics import pairwise
>>> distances_to_example = pairwise.pairwise_distances(X)[0]
>>> ten_closest_points = X[np.argsort(distances_to_example)][:10]
>>> ten_closest_y = y[np.argsort(distances_to_example)][:10]
>>> ten_closest_y.mean()
0.28000
我們可以看到它非常接近預期。