第三章 使用距離向量構(gòu)建模型
作者:Trent Hauck
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
這一章中雀监,我們會涉及到聚類立哑。聚類通常和非監(jiān)督技巧組合到一起仔雷。這些技巧假設(shè)我們不知道結(jié)果變量水醋。這會使結(jié)果模糊管削,以及實踐客觀翔始。但是罗心,聚類十分有用里伯。我們會看到,我們可以使用聚類渤闷,將我們的估計在監(jiān)督設(shè)置中“本地化”疾瓮。這可能就是聚類非常高效的原因。它可以處理很大范圍的情況飒箭,通常狼电,結(jié)果也不怎么正常。
這一章中我們會瀏覽大量應(yīng)用弦蹂,從圖像處理到回歸以及離群點檢測肩碟。通過這些應(yīng)用,我們會看到聚類通惩勾唬可以通過概率或者優(yōu)化結(jié)構(gòu)來觀察削祈。不同的解釋會導(dǎo)致不同的權(quán)衡。我們會看到脑漫,如何訓(xùn)練模型髓抑,以便讓工具嘗試不同模型,在面對聚類問題的時候优幸。
3.1 使用 KMeans 對數(shù)據(jù)聚類
聚類是個非常實用的技巧吨拍。通常,我們在采取行動時需要分治网杆「危考慮公司的潛在客戶列表。公司可能需要將客戶按類型分組碳却,之后為這些分組劃分職責(zé)队秩。聚類可以使這個過程變得容易。
KMeans 可能是最知名的聚類算法之一追城,并且也是最知名的無監(jiān)督學(xué)習(xí)技巧之一刹碾。
準(zhǔn)備
首先,讓我們看一個非常簡單的聚類座柱,之后我們再討論 KMeans 如何工作迷帜。
>>> from sklearn.datasets import make_blobs
>>> blobs, classes = make_blobs(500, centers=3)
同樣,由于我們繪制一些圖表色洞,導(dǎo)入matplotlib
戏锹,像這樣:
>>> import matplotlib.pyplot as plt
操作步驟
我們打算瀏覽一個簡單的例子,它對偽造數(shù)據(jù)進(jìn)行聚類火诸。之后我們會稍微談?wù)撘幌陆跽耄琄Means 如何工作,來尋找最優(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)生每個點的預(yù)期標(biāo)簽。
>>> 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è)置一個預(yù)定義的簇數(shù)量K
桦卒,之后執(zhí)行這些事情:
- 將每個數(shù)據(jù)點分配到最近的簇中绊谭。
- 通過計算初中每個數(shù)據(jù)點的均值,更新每個形心敬辣。
直到滿足特定條件。
3.2 優(yōu)化形心數(shù)量
形心難以解釋零院,并且也難以判斷是否數(shù)量正確溉跃。理解你的數(shù)據(jù)是否是未分類的十分重要,因為這會直接影響我們可用的評估手段告抄。
準(zhǔn)備
為無監(jiān)督學(xué)習(xí)評估模型表現(xiàn)是個挑戰(zhàn)撰茎。所以,在了解真實情況的時候打洼,sklearn
擁有多種方式來評估聚類龄糊,但在不了解時就很少。
我們會以一個簡單的簇模型開始募疮,并評估它的相似性炫惩。這更多是出于機(jī)制的目的,因為測量一個簇的相似性在尋找簇數(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ù)越高唱捣,分?jǐn)?shù)就越高两蟀。
工作原理
輪廓系數(shù)的均值通常用于描述整個模型的擬合度。
>>> silhouette_samples.mean()
0.57130462953339578
這十分普遍震缭,事實上赂毯,metrics
模塊提供了一個函數(shù)來獲得剛才的值。
現(xiàn)在拣宰,讓我們訓(xù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 評估誉帅。在許多情況下,這都是不可知的右莱,但是如果存在外部的標(biāo)注蚜锨,我們就會知道真實情況,或者至少是代理隧出。
準(zhǔn)備
所以踏志,讓我們假設(shè)有一個世界,其中我們有一些外部代理胀瞪,向我們提供了真實情況针余。
我們會創(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)訓(xùn)練了模型,讓我們看看簇的形心:
>>> 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)看做分類練習(xí)伪朽,在其語境中有用的方法在這里也有用:
>>> for i in range(3):
print (kmeans.labels_ == ground_truth)[ground_truth == i]
.astype(int).mean()
0.0778443113772
0.990990990991
0.0570570570571
很顯然我們有一些錯亂的簇轴咱。所以讓我們將其捋直,之后我們查看準(zhǔn)確度烈涮。
>>> 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
分?jǐn)?shù)靠近 0坚洽,就說明標(biāo)簽的分配可能不是按照相似過程生成的戈稿。但是分?jǐn)?shù)靠近 1,就說明兩個標(biāo)簽有很強(qiáng)的一致性讶舰。
例如鞍盗,讓我們看一看互信息分?jǐn)?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ī)范化的互信息是互信息除以每個真實值和標(biāo)簽的熵的乘積的平方根。
更多
有一個度量方式我們尚未討論鹅颊,并且不依賴于真實情況敷存,就是慣性(inertia)度量。當(dāng)前挪略,它作為一種度量并沒有詳細(xì)記錄历帚。但是滔岳,它是 KMeans 中最簡單的度量杠娱。
慣性是每個數(shù)據(jù)點和它所分配的簇的平方差之和。我們可以稍微使用 NumPy 來計算它:
>>> kmeans.inertia_
3.4 使用 MiniBatch KMeans 處理更多數(shù)據(jù)
KMeans 是一個不錯的方法谱煤,但是不適用于大量數(shù)據(jù)摊求。這是因為 KMenas 的復(fù)雜度。也就是說刘离,我們可以使用更低的算法復(fù)雜度來獲得近似解室叉。
準(zhǔn)備
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)勢渗稍,最好還是要閱讀它們。
既然準(zhǔn)備已經(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])
我們可能要問的下一個問題就是,兩個形心距離多遠(yuǎn)灸姊。
>>> 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])
工作原理
這里的批次就是關(guān)鍵。批次被迭代來尋找批次均值力惯。對于下一次迭代來說碗誉,前一個批次的均值根據(jù)當(dāng)前迭代來更新。有多種選項父晶,用于控制 KMeans 的通用行為哮缺,和決定 MiniBatch KMeans 的參數(shù)。
batch_size
參數(shù)決定批次應(yīng)為多大甲喝。只是玩玩的話尝苇,我們可以運行 MiniBatch,但是埠胖,此時我們將批次數(shù)量設(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)是否能達(dá)到侮东,是不一定的。
3.5 使用 KMeans 聚類來量化圖像
圖像處理是個重要的話題豹芯,其中聚類有一些應(yīng)用悄雅。值得指出的是,Python 中有幾種非常不錯的圖像處理庫铁蹈。Scikit-image 是 Scikit-learn 的“姐妹”項目宽闲。如果你打算做任何復(fù)雜的事情,都值得看一看它握牧。
準(zhǔn)備
我們在這篇秘籍中會有一些樂趣容诬。目標(biāo)是使用聚類來把圖像變模糊。
首先沿腰,我們要利用 SciPy 來讀取圖像览徒。圖像翻譯為三維數(shù)組,x
和y
坐標(biāo)描述了高度和寬度颂龙,第三個維度表示每個圖像的 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ù)點,并且對點進(jìn)行聚類來降低圖像中的不同顏色的數(shù)量 -- 這是一個簡單的量化方式笋婿。
首先誉裆,讓我們使數(shù)組變形,它是個 NumPy 數(shù)組萌抵,所以非常簡單:
>>> x, y, z = img.shape
>>> long_img = img.reshape(x*y, z)
>>> long_img.shape (176400, 3)
現(xiàn)在我們開始聚類過程找御。首先,讓我們導(dǎo)入聚類模塊绍填,并創(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)訓(xùn)練了 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]])
工作原理
既然我們擁有了形心揭糕,我們需要的下一個東西就是標(biāo)簽。它會告訴我們锻霎,哪個點關(guān)聯(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é)果與我們的預(yù)期比較蜀细。
準(zhǔn)備
Scikit-learn 中舟铜,有個叫做sklearn.metrics.pairwise
的底層工具。它包含一些服務(wù)函數(shù)奠衔,計算矩陣X
中向量之間的距離谆刨,或者X
和Y
中的向量距離。
這對于信息檢索來說很實用归斤。例如痴荐,提供一組客戶信息,帶有屬性X
官册,我們可能希望選取有個客戶代表生兆,并找到與這個客戶最接近的客戶。實際上膝宁,我們可能希望將客戶按照相似性度量的概念鸦难,使用距離函數(shù)來排序。相似性的質(zhì)量取決于特征空間選取员淫,以及我們在空間上所做的任何變換合蔽。
操作步驟
我們會使用pairwise_distances
函數(shù)來判斷對象的接近程度。要記住介返,接近程度就像我們用于聚類/分類的距離函數(shù)拴事。
首先,讓我們從metric
模塊導(dǎo)入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ù)來度量蛮放。通常為歐幾里得距離,它是:
詳細(xì)來說奠宜,它計算了兩個向量每個分量的差包颁,計算它們的平方,求和挎塌,之后計算它的平方根徘六。這看起來很熟悉,因為在計算均方誤差的時候榴都,我們使用的東西很相似待锈。如果我們計算了平方根,就一樣了嘴高。實際上竿音,經(jīng)常使用的度量是均方根誤差(RMSE),它就是距離函數(shù)的應(yīng)用拴驮。
在 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 文檔潜沦。
cityblock
cosine
euclidean
l1
l2
manhattan
我們現(xiàn)在可以解決問題了萄涯。例如,如果我們站在原點處的格子上唆鸡,并且線是街道涝影,為了到達(dá)點(5,5)
,我們需要走多遠(yuǎn)呢争占?
>>> 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 中伯襟,我們假設(shè)簇的方差是相等的。這會導(dǎo)致空間的細(xì)分刻蟹,這決定了簇如何被分配逗旁。但是,如果有一種場景舆瘪,其中方差不是相等的片效,并且每個簇中的點擁有一個與之相關(guān)的概率,會怎么樣英古?
準(zhǔn)備
有一種更加概率化的方式淀衣,用于查看 KMeans 聚類。KMeans 聚類相當(dāng)于將協(xié)方差矩陣S
應(yīng)用于高斯混合模型召调,這個矩陣可以分解為單位矩陣成誤差膨桥。對于每個簇,協(xié)方差結(jié)構(gòu)是相同的唠叛。這就產(chǎn)生了球形聚類只嚣。
但是,如果我們允許S
變化艺沼,就可以估計 GMM册舞,并將其用于預(yù)測。我們會以單變量的角度看到它的原理障般,之后擴(kuò)展為多個維度调鲸。
操作步驟
首先,我們需要創(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()
下面是輸出:
下面青自,我們的興趣是株依,對分組二次抽樣,訓(xùn)練分布性穿,之后預(yù)測剩余分組勺三。
>>> 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)驗分布,基于訓(xùn)練集:
>>> 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ù)點從每個分布中生成的概率,并且最可能的分布會分配合適的標(biāo)簽呆万。當(dāng)然商源,我們會看到有多么準(zhǔn)確。
>>> m_pdf.pdf(m[0])
0.043532673457165431
>>> w_pdf.pdf(m[0])
9.2341848872766183e-07
要注意概率中的差異谋减。
假設(shè)當(dāng)男性的概率更高時牡彻,我們會猜測,但是如果女性的概率更高,我們會覆蓋它庄吼。
>>> guesses_m = np.ones_like(m_test)
>>> guesses_m[m_pdf.pdf(m_test) < w_pdf.pdf(m_test)] = 0
顯然缎除,問題就是我們有多么準(zhǔn)確。由于正確情況下guesses_m
為 1总寻,否則為 0器罐,我們計算向量的均值來獲取準(zhǔn)確度。
>>> 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)建訓(xùn)練集:
>>> 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ù)科學(xué)家孽文,我們創(chuàng)建訓(xùn)練集:
>>> 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)
訓(xùn)練和預(yù)測的完成方式驻襟,和 Scikit-learn 的其它對象相同。
>>> gmm.fit(X[train])
>>> gmm.predict(X[train])[:5]
array([0, 0, 0, 0, 0])
既然模型已經(jīng)訓(xùn)練了芋哭,有一些值得一看的其它方法沉衣。
例如,使用score_examples
减牺,我們實際上可以為每個標(biāo)簽獲得每個樣例的可能性豌习。
3.8 將 KMeans 用于離群點檢測
這一章中,我們會查看 Kmeans 離群點檢測的機(jī)制和正義拔疚。它對于隔離一些類型的錯誤很實用肥隆,但是使用時應(yīng)多加小心。
準(zhǔn)備
這個秘籍中稚失,我們會使用 KMeans栋艳,對簇中的點執(zhí)行離群點檢測。要注意句各,提及離群點和離群點檢測時有很多“陣營”吸占。以便面,我們可能通過移除離群點凿宾,來移除由數(shù)據(jù)生成過程生成的點矾屯。另一方面,離群點可能來源于測量誤差或一些其它外部因素初厚。
這就是爭議的重點件蚕。這篇秘籍的剩余部分有關(guān)于尋找離群點。我們的假設(shè)是,我們移除離群點的選擇是合理的排作。
離群點檢測的操作是牵啦,查找簇的形心,之后通過點到形心的距離來識別潛在的離群點纽绍。
操作步驟
首先蕾久,我們會生成 100 個點的單個數(shù)據(jù)塊势似,之后我們會識別 5 個離形心最遠(yuǎn)的點拌夏。它們就是潛在的離群點。
>>> 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)在栅迄,讓我們觀察繪圖站故。對于那些遠(yuǎ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)在西篓,讓我們看看哪個點離得最遠(yuǎ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')
下面是輸出:
顯然,形心沒有移動多少,僅僅移除五個極端點時俱病,我們的預(yù)期就是這樣胜宇。這個過程可以重復(fù),知道我們對數(shù)據(jù)表示滿意作瞄。
工作原理
我們已經(jīng)看到茶宵,高斯分布和 KMeans 聚類之間有本質(zhì)聯(lián)系。讓我們基于形心和樣本的協(xié)方差矩陣創(chuàng)建一個經(jīng)驗高斯分布宗挥,并且查看每個點的概率 -- 理論上是我們溢出的五個點乌庶。這剛好展示了,我們實際上溢出了擁有最低可能性的值属韧。距離和可能性之間的概念十分重要安拟,并且在你的機(jī)器學(xué)習(xí)訓(xùn)練中會經(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è)計宵喂,但是我們可能打算在特征空間的“口袋”中運行回歸糠赦。我們可以認(rèn)為,我們的數(shù)據(jù)集要經(jīng)過多道數(shù)據(jù)處理工序。如果是這樣拙泽,只訓(xùn)練相似數(shù)據(jù)點是個不錯的想法淌山。
準(zhǔn)備
我們的老朋友,回歸顾瞻,可以用于聚類的上下文中泼疑。回歸顯然是個監(jiān)督學(xué)習(xí)技巧荷荤,所以我們使用 KNN 而不是 KMeans退渗。
對于 KNN 回歸來說,我們使用特征空間中的 K 個最近點蕴纳,來構(gòu)建回歸会油,而不像常規(guī)回歸那樣使用整個特征空間。
操作步驟
對于這個秘籍古毛,我們使用iris
數(shù)據(jù)集翻翩。如果我們打算預(yù)測一些東西,例如每朵花的花瓣寬度稻薇,根據(jù)iris
物種來聚類可能會給我們更好的結(jié)果嫂冻。KNN 回歸不會根據(jù)物種來聚類,但我們的假設(shè)是塞椎,相同物種的 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)']
我們嘗試基于萼片長度和寬度來預(yù)測花瓣長度。我們同時訓(xùn)練一個線性回歸莺戒,來對比觀察 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
讓我們看看瘪校,當(dāng)我們讓它使用最接近的 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()
輸出如下:
很顯然阱扬,預(yù)測大部分都是接近的。但是讓我們與實際情況相比伸辟,看看 Setosa 物種的預(yù)測:
>>> 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 個最接近點的均值卡啰。
讓我們手動預(yù)測單個點:
>>> 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
我們可以看到它非常接近預(yù)期警没。