無監(jiān)督學(xué)習(xí)的類型
本文主要討論兩種類型的無監(jiān)督學(xué)習(xí):數(shù)據(jù)變換與聚類
數(shù)據(jù)集的無監(jiān)督變換(unsupervised transformation)是創(chuàng)建數(shù)據(jù)新的表示的算法,與數(shù)據(jù)的原始表示相比巧涧,新的表示可能更容易被人或其他機(jī)器學(xué)習(xí)算法所理解姥饰。無監(jiān)督變換的一個常見應(yīng)用是降維(dimensionality reduction),它接受包含許多特征的數(shù)據(jù)的高維表示,并找到表示該數(shù)據(jù)的一種新方法,用較少的特征就可以概括其重要特性。降維的一個常見應(yīng)用是為了可視化將數(shù)據(jù)降為二維。
無監(jiān)督變換的另一種應(yīng)用是找到“構(gòu)成”數(shù)據(jù)的各個組成部分谎僻。這方面的一個例子就是對文本文檔集合進(jìn)行主題提取。這里的任務(wù)是找到每個文檔中討論的未知主題寓辱,并學(xué)習(xí)每個文檔中出現(xiàn)了哪些主題艘绍。這可以用于追蹤社交媒體上的話題討論,比如選舉秫筏、槍支管制或流行歌手等話題鞍盗。
與之相反,聚類算法(clustering algorithm)將數(shù)據(jù)劃分成不同的組跳昼,每組包含相似的物項般甲。
無監(jiān)督學(xué)習(xí)的挑戰(zhàn)
無監(jiān)督學(xué)習(xí)的一個主要挑戰(zhàn)就是評估算法是否學(xué)到了有用的東西。無監(jiān)督學(xué)習(xí)算法一般用于不包含任何標(biāo)簽值得數(shù)據(jù)鹅颊,所以我們不知道正確的輸出應(yīng)該是什么敷存。因此很難判斷一個模型是否“表現(xiàn)良好”。例如堪伍,假設(shè)我們的聚類算法已經(jīng)將所有的側(cè)臉照片和所有的正面照片進(jìn)行分組锚烦。這肯定是人臉照片集合的一種可能的劃分方法,但并不是我們想要的那種方法帝雇。然而涮俄,我們沒有辦法“告訴”算法我們要的是什么,通常來說,評估無監(jiān)督算法結(jié)果的一種方法就是人工檢查。
因此,如果數(shù)據(jù)科學(xué)家想要更好地理解數(shù)據(jù)逾礁,那么無監(jiān)督算法通嘲ⅲ可以用于探索性的目的畸肆,而不是作為大型自動化系統(tǒng)的一部分。無監(jiān)督算法的另一個常見應(yīng)用是作為監(jiān)督算法的預(yù)處理步驟宙址。學(xué)習(xí)數(shù)據(jù)的一種新表示轴脐,有時可以提高監(jiān)督算法的精度,或者作為減少內(nèi)存占用和時間開銷抡砂。
在開始學(xué)習(xí)“真正的”無監(jiān)督算法之前大咱,我們先簡要討論幾種簡單又常用的預(yù)處理方法。
預(yù)處理與縮放
一些算法(如神經(jīng)網(wǎng)絡(luò)和SVM)對數(shù)據(jù)縮放非常敏感注益。因此碴巾,通常的做法是對特征進(jìn)行調(diào)節(jié),使數(shù)據(jù)表示更適合于這些算法聊浅。通常來說餐抢,這是對數(shù)據(jù)的一種簡單的按特征的縮放和移動:
import sys
print("Python version:{}".format(sys.version))
import pandas as pd
print("pandas version:{}".format(pd.__version__))
import matplotlib
print("matplotlib version:{}".format(matplotlib.__version__))
import matplotlib.pyplot as plt
import numpy as np
print("Numpy version:{}".format(np.__version__))
import scipy as sp
print("Scipy version:{}".format(sp.__version__))
import IPython
print("IPython version:{}".format(IPython.__version__))
import sklearn
print("scikit-learn version:{}".format(sklearn.__version__))
import mglearn
import graphviz
mglearn.plots.plot_scaling()
上圖就是對數(shù)據(jù)集縮放和預(yù)處理的各種方法现使。第一張圖顯示的是一個模擬的有兩個特征的二分類數(shù)據(jù)集低匙。第一個特征(x軸)位于10到15之間。第二個特征(y軸)大約位于1到9之間碳锈。接下來的4張圖展示了4種數(shù)據(jù)變換方法顽冶,都生成了更加標(biāo)準(zhǔn)的范圍。scikit-learn中的StandardScaler確保每個特征的平均值為0售碳、方差為1强重,使所有特征都位于同一量級。但這種縮放不能保證特征任何特定的最大值和最小值贸人。RobustScaler的工作原理與StandardScaler類似间景,確保每個特征的統(tǒng)計屬性都位于同一范圍。但RobustScaler使用的是中位數(shù)和四分位數(shù)艺智,而不是平均值和方差倘要。這樣RobustScaler會忽略與其他點(diǎn)有很大不同的數(shù)據(jù)點(diǎn)(比如測量誤差)。這些與眾不同的數(shù)據(jù)點(diǎn)也叫異常值(outlier)十拣,可能會給其他縮放方法造成麻煩封拧。
與之相反,MinMaxScaler移動數(shù)據(jù)夭问,使所有特征都剛好位于0到1之間泽西。對于二維數(shù)據(jù)集來說,所有的數(shù)據(jù)都包含在x軸0到1與y軸0到1組成的矩形中缰趋。
最后,Normalizer用到一種完全不同的縮放方法捧杉。它對每個數(shù)據(jù)點(diǎn)進(jìn)行縮放陕见,使得特征向量的歐式長度等于1。換句話說糠溜,它將一個數(shù)據(jù)點(diǎn)投射到半徑為1的圓上(對于更高維度的情況淳玩,是球面)。這意味著每個數(shù)據(jù)點(diǎn)的縮放比例都不相同(乘以其長度的倒數(shù))非竿。如果只有數(shù)據(jù)的方向(或角度)是重要的蜕着,而特征向量的長度無關(guān)緊要,那么通常會使用這種歸一化红柱。
應(yīng)用數(shù)據(jù)變換
我們將核SVM(SVC)應(yīng)用在cancer數(shù)據(jù)集上承匣,需要使用MinMaxScaler來預(yù)處理數(shù)據(jù)。首先加載數(shù)據(jù)集并將其分為訓(xùn)練集和測試集:
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
cancer=load_breast_cancer()
X_train,X_test,y_train,y_test=train_test_split(cancer.data,cancer.target,random_state=1)
print(X_train.shape)
print(X_test.shape)
我們首先導(dǎo)入實現(xiàn)預(yù)處理的類锤悄,然后將其實例化:
from sklearn.preprocessing import MinMaxScaler
scaler=MinMaxScaler()
然后韧骗,使用fit方法擬合縮放器(scaler),并將其應(yīng)用于訓(xùn)練集零聚。對于MinMaxScaler來說袍暴,fit方法計算訓(xùn)練集中每個特征的最大值和最小值:
scaler.fit(X_train)
為了應(yīng)用剛剛學(xué)習(xí)的變換(即對訓(xùn)練數(shù)據(jù)進(jìn)行實際縮放),我們使用縮放器的transform方法隶症。在scikit-learn中政模,每當(dāng)模型返回數(shù)據(jù)的一種新表示時,都可以使用transform方法:
# 變換數(shù)據(jù)
X_train_scaled=scaler.transform(X_train)
# 在縮放之前和之后分別打印數(shù)據(jù)集屬性
print("transformed shape:{}".format(X_train_scaled.shape))
print("per-feature minimum before scaling:\n{}".format(X_train.min(axis=0)))
print("per-feature maximum before scaling:\n{}".format(X_train.max(axis=0)))
print("per-feature minimum after scaling:\n{}".format(X_train_scaled.min(axis=0)))
print("per-feature maximum after scaling:\n{}".format(X_train_scaled.max(axis=0)))
transformed shape:(426, 30)
per-feature minimum before scaling:
[6.981e+00 9.710e+00 4.379e+01 1.435e+02 5.263e-02 1.938e-02 0.000e+00
0.000e+00 1.060e-01 5.024e-02 1.153e-01 3.602e-01 7.570e-01 6.802e+00
1.713e-03 2.252e-03 0.000e+00 0.000e+00 9.539e-03 8.948e-04 7.930e+00
1.202e+01 5.041e+01 1.852e+02 7.117e-02 2.729e-02 0.000e+00 0.000e+00
1.566e-01 5.521e-02]
per-feature maximum before scaling:
[2.811e+01 3.928e+01 1.885e+02 2.501e+03 1.634e-01 2.867e-01 4.268e-01
2.012e-01 3.040e-01 9.575e-02 2.873e+00 4.885e+00 2.198e+01 5.422e+02
3.113e-02 1.354e-01 3.960e-01 5.279e-02 6.146e-02 2.984e-02 3.604e+01
4.954e+01 2.512e+02 4.254e+03 2.226e-01 9.379e-01 1.170e+00 2.910e-01
5.774e-01 1.486e-01]
per-feature minimum after scaling:
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
- 0.]
per-feature maximum after scaling:
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
- 0.]
- 1.]
變換后的數(shù)據(jù)形狀與原始數(shù)據(jù)相同蚂会,特征只是發(fā)生了移動和縮放淋样。你可以看到,現(xiàn)在所有特征都位于0和1之間胁住,這也符合我們的預(yù)期趁猴。
為了將SVM應(yīng)用到縮放后的數(shù)據(jù)上,還需要對測試集進(jìn)行變換彪见。這可以通過對X_test調(diào)用transform方法來完成:
- 1.]
# 對測試集數(shù)據(jù)進(jìn)行變換
X_test_scaled=scaler.transform(X_test)
# 在縮放之后打印測試數(shù)據(jù)的屬性
print("per-feature minimum after scaling:\n{}".format(X_test_scaled.min(axis=0)))
print("per-feature maximum after scaling:\n{}".format(X_test_scaled.max(axis=0)))
per-feature minimum after scaling:
[ 0.0336031 0.0226581 0.03144219 0.01141039 0.14128374 0.04406704
0. 0.1540404 -0.00615249 -0.00137796 0.00594501
0.00430665 0.00079567 0.03919502 0.0112206 0. 0.
-0.03191387 0.00664013 0.02660975 0.05810235 0.02031974 0.00943767
0.1094235 0.02637792 0. 0. -0.00023764 -0.00182032]
per-feature maximum after scaling:
[0.9578778 0.81501522 0.95577362 0.89353128 0.81132075 1.21958701
0.87956888 0.9333996 0.93232323 1.0371347 0.42669616 0.49765736
0.44117231 0.28371044 0.48703131 0.73863671 0.76717172 0.62928585
1.33685792 0.39057253 0.89612238 0.79317697 0.84859804 0.74488793
0.9154725 1.13188961 1.07008547 0.92371134 1.20532319 1.63068851]
你可以發(fā)現(xiàn)儡司,對測試集縮放后的最大值和最小值不是1和0,這或許有些出乎意料余指。有些特征甚至在0~1的范圍之外捕犬!對此的解釋是,MinMaxScaler(以及其他縮放器)總是對訓(xùn)練集和測試集應(yīng)用完全相同的變換浪规。也就是說或听,transform方法總是減去訓(xùn)練集的最小值,然后除以訓(xùn)練集的范圍笋婿,而這兩個值可能與測試集的最小值和范圍并不相同誉裆。
對訓(xùn)練集和測試數(shù)據(jù)集進(jìn)行相同的縮放
為了讓監(jiān)督模型能夠在測試集上運(yùn)行,對訓(xùn)練集和測試集應(yīng)用完全相同的變換是很重要的缸濒。如果我們使用測試集的最大值和最小值足丢,我們看下會發(fā)生什么:
from sklearn.datasets import make_blobs
# 構(gòu)造數(shù)據(jù)
X,_=make_blobs(n_samples=50,centers=5,random_state=4,cluster_std=2)
# 將其分為訓(xùn)練集和測試集
X_train,X_test=train_test_split(X,random_state=5,test_size=.1)
#繪制訓(xùn)練集和測試集
fig,axes=plt.subplots(1,3,figsize=(13,4))
axes[0].scatter(X_train[:,0],X_train[:,1],c=mglearn.cm2(0),label="Training set",s=60)
axes[0].scatter(X_test[:,0],X_test[:,1],marker='^',c=mglearn.cm2(1),label="Test set",s=60)
axes[0].legend("upper left")
axes[0].set_title("Original Data")
# 利用MinMaxScaler縮放數(shù)據(jù)
scaler=MinMaxScaler()
scaler.fit(X_train)
X_train_scaled=scaler.transform(X_train)
X_test_scaled=scaler.transform(X_test)
# 將正確縮放的數(shù)據(jù)可視化
axes[1].scatter(X_train_scaled[:,0],X_train_scaled[:,1],c=mglearn.cm2(0),label="Training set",s=60)
axes[1].scatter(X_test_scaled[:,0],X_test_scaled[:,1],marker='^',c=mglearn.cm2(1),label="Test set",s=60)
axes[1].set_title("Scaled Data")
# 單獨(dú)對數(shù)據(jù)集進(jìn)行縮放
# 使得測試集的最小值為0粱腻,最大值為1
# 千萬不要這么做!這里只是為了舉例
test_scaler=MinMaxScaler()
test_scaler.fit(X_test)
X_test_scaler_badly=test_scaler.transform(X_test)
# 將錯誤縮放的數(shù)據(jù)可視化
axes[2].scatter(X_train_scaled[:,0],X_train_scaled[:,1],c=mglearn.cm2(0),label="Training set",s=60)
axes[2].scatter(X_test_scaler_badly[:,0],X_test_scaler_badly[:,1],marker='^',c=mglearn.cm2(1),label="Test set",s=60)
axes[2].set_title("Improperly Scaled Data")
for ax in axes:
ax.set_xlabel("Feature 0")
ax.set_ylabel("Feature 1")
第一張圖示未縮放的二維數(shù)據(jù)集斩跌,其中訓(xùn)練集使用橢圓表示绍些,測試集用三角形表示,第二張圖中是同樣的數(shù)據(jù)耀鸦,但使用MinMaxScaler縮放柬批。這里我們調(diào)用fit作用在訓(xùn)練集上,然后調(diào)用transformat作用在訓(xùn)練集和測試集上袖订。你可以發(fā)現(xiàn)氮帐,第二張圖中的數(shù)據(jù)集看起來與第一張圖中的完全相同,只是坐標(biāo)軸刻度發(fā)生了變化÷骞茫現(xiàn)在所有特征都位于0和1之間上沐。你還可以發(fā)現(xiàn),測試數(shù)據(jù)(三角形)的特征最大值和最小值并不是1和0楞艾。
第三張圖展示了如果我們對訓(xùn)練集和測試集分別進(jìn)行縮放會發(fā)生什么参咙。在這種情況下,對訓(xùn)練集和測試集而言硫眯,特征的最大值和最小值都是1和0蕴侧。但現(xiàn)在數(shù)據(jù)集看起來不一樣。測試集相對訓(xùn)練集的移動不一致舟铜,因為它們分別做了不同的縮放戈盈。我們隨意改變了數(shù)據(jù)的排列奠衔。這顯然不是我們想要做的事情谆刨。
再換一種思考方式,想象你的測試集只有一個點(diǎn)归斤。對于一個點(diǎn)而言痊夭,無法將其正確地縮放以滿足MinMaxScaler的最大值和最小值的要求。但是測試集的大小不應(yīng)該對你的處理方式有影響脏里。
預(yù)處理對監(jiān)督學(xué)習(xí)的作用
現(xiàn)在回到cancer數(shù)據(jù)集她我,觀察使用MinMaxScaler對學(xué)習(xí)SVC的作用。首先迫横,為了對比番舆,我們再次在原始數(shù)據(jù)上擬合SVC:
from sklearn.svm import SVC
X_train,X_test,y_train,y_test=train_test_split(cancer.data,cancer.target,random_state=0)
svm=SVC(C=100)
svm.fit(X_train,y_train)
print("Test set accuracy:{:.2f}".format(svm.score(X_test,y_test)))
下面先用MinMaxScaler對數(shù)據(jù)進(jìn)行縮放,然后再擬合SVC:
# 使用0-1縮放進(jìn)行預(yù)測
scaler=MinMaxScaler()
scaler.fit(X_train)
X_train_scaled=scaler.transform(X_train)
X_test_scaled=scaler.transform(X_test)
# 在縮放后的訓(xùn)練數(shù)據(jù)上學(xué)習(xí)SVM
svm.fit(X_train_scaled,y_train)
# 在縮放后的數(shù)據(jù)集上計算分?jǐn)?shù)
print("Scaled test set accuracy:{:.2f}".format(svm.score(X_test_scaled,y_test)))
Scaled test set accuracy:0.97
正如我們上面所見矾踱,數(shù)據(jù)縮放的作用非常顯著恨狈。雖然數(shù)據(jù)縮放不涉及任何復(fù)雜的數(shù)學(xué),但良好的做法仍然是使用scikit-learn提供的縮放機(jī)制呛讲,而不是自己重新實現(xiàn)它們禾怠,因為即使你在這些點(diǎn)的計算中也容易犯錯返奉。
你也可以通過改變使用的類將一種預(yù)處理算法輕松替換成另一種,因為所有的預(yù)處理都具有相同的接口吗氏,都包含fit和transform方法:
# 利用零均值和單位方差的縮放方法進(jìn)行預(yù)處理
from sklearn.preprocessing import StandardScaler
scaler=StandardScaler()
scaler.fit(X_train)
X_train_scaled=scaler.transform(X_train)
X_test_scaled=scaler.transform(X_test)
# 在縮放后的訓(xùn)練數(shù)據(jù)上學(xué)習(xí)SVM
svm.fit(X_train_scaled,y_train)
# 在縮放后的數(shù)據(jù)集上計算分?jǐn)?shù)
print("SVM test set accuracy:{:.2f}".format(svm.score(X_test_scaled,y_test)))
SVM test set accuracy:0.96
降維芽偏、特征提取與流形學(xué)習(xí)
利用無監(jiān)督學(xué)習(xí)進(jìn)行數(shù)據(jù)變換可能有很多種目的。最常見的目的就是可視化弦讽、壓縮數(shù)據(jù)污尉,以及尋找信息量更大的數(shù)據(jù)表示以用于進(jìn)一步的處理。
為了實現(xiàn)這些目的往产,最簡單也是最常用的一種算法就是主成分分析十厢。我們也將學(xué)習(xí)另外兩種算法:非負(fù)矩陣分解(NMF)和t-SNE,前者通常用于特征提取,后者通常用于二維散點(diǎn)圖的可視化捂齐。
主成分分析
主成分分析(principal component analysis,PCA)是一種旋轉(zhuǎn)數(shù)據(jù)集的方法蛮放,旋轉(zhuǎn)后的特征在統(tǒng)計上不相關(guān)。在做完這種旋轉(zhuǎn)之后奠宜,通常是根據(jù)新特征對解釋數(shù)據(jù)的重要性來選擇它的一個子集包颁。如下展示了對一個模擬二維數(shù)據(jù)集的作用:
mglearn.plots.plot_pca_illustration()
第一張圖(左上)顯示的是原始數(shù)據(jù)點(diǎn),用不同顏色加以區(qū)分压真。算法首先找到方差最大的方向娩嚼,將其標(biāo)記為“成分1”(Component 1)。這是數(shù)據(jù)中包含最多信息的方向(或向量)滴肿,換句話說岳悟,沿著這個方向的特征之間最為相關(guān)。然后泼差,算法找到與第一個方向正交(成直角)且包含最多信息的方向贵少。在二維空間中,只有一個成直角的方向堆缘,但在更高維的空間中會有(無窮)多的正交方向滔灶。雖然這兩個成分都畫成箭頭,但其頭尾的位置并不重要吼肥。我們也可以將第一個成分畫成從中心指向左上录平,而不是指向游俠。利用這一過程找到的方向稱為主成分(principal component),因為它們是數(shù)據(jù)方差的主要方向缀皱。一般來說斗这,主成分的個數(shù)與原始特征相同。
第二張圖顯示的是同樣的數(shù)據(jù)啤斗,但現(xiàn)在將其旋轉(zhuǎn)表箭,使得第一主成分與x軸平行且第二主成分與y軸平行。在旋轉(zhuǎn)之前争占,從數(shù)據(jù)中減去平均值燃逻,使得變換后的數(shù)據(jù)以零為中心序目。在PCA找到的旋轉(zhuǎn)表示中,兩個坐標(biāo)軸是不相關(guān)的伯襟,也就是說猿涨,對于這種數(shù)據(jù)表示,除了對角線姆怪,相關(guān)矩陣全部為零叛赚。
我們可以通過僅保留一部分主成分來使用PCA進(jìn)行降維。在這個例子中稽揭,我們可以僅保留第一個主成分俺附,正如第三張圖所示(左下)。這將數(shù)據(jù)從二維數(shù)據(jù)降為一維數(shù)據(jù)集溪掀。但要注意事镣,我們沒有保留原始特征之一,而是找到了最有趣的分析(第一張圖中從左上到右下)并保留這一方向揪胃,即第一主成分璃哟。
最后,我們可以反向旋轉(zhuǎn)并將平均值重新加到數(shù)據(jù)中喊递。這樣會得到上面最后一張圖的數(shù)據(jù)随闪。這些數(shù)據(jù)點(diǎn)位于原始特征空間中,但我們僅保留了第一主成分中包含的信息骚勘。這種變換有時用于去除數(shù)據(jù)中的噪聲影響铐伴,或者將主成分中保留的那部分信息可視化。
將主成分應(yīng)用于cancer數(shù)據(jù)集并可視化
PCA最常見的應(yīng)用之一就是將高維數(shù)據(jù)集可視化俏讹。對于兩個以上特征的數(shù)據(jù)当宴,很難繪制散點(diǎn)圖。對于Iris(鳶尾花)數(shù)據(jù)集藐石,我們可以創(chuàng)建散點(diǎn)圖矩陣即供,通過展示特征所有可能的兩兩組合來表示數(shù)據(jù)的局部圖像定拟。但如果我們想要查看乳腺癌數(shù)據(jù)集,即使用散點(diǎn)圖矩陣也很困難。這個數(shù)據(jù)集包含30個特征浊服,這就導(dǎo)致需要繪制30*14=420張散點(diǎn)圖栽烂!我們永遠(yuǎn)不可能仔細(xì)觀察所有這些圖像,更不用說試圖理解它們了延窜。
不過我們可以使用一種更簡單的可視化方法———對每個特征分別計算兩個類別(良性腫瘤和惡性腫瘤)的直方圖:
fig,axes=plt.subplots(15,2,figsize=(10,20))
malignant=cancer.data[cancer.target==0]
benign=cancer.data[cancer.target==1]
ax=axes.ravel()
for i in range(30):
_,bins=np.histogram(cancer.data[:,i],bins=50)
ax[i].hist(malignant[:,i],bins=bins,color=mglearn.cm3(0),alpha=.5)
ax[i].hist(benign[:,i],bins=bins,color=mglearn.cm3(2),alpha=.5)
ax[i].set_title(cancer.feature_names[i])
ax[i].set_yticks(())
ax[0].set_xlabel("Feature magnitude")
ax[0].set_ylabel("Frequency")
ax[0].legend(["malignant","benign"],loc="best")
fig.tight_layout()
這里我們?yōu)槊總€特征創(chuàng)建一個直方圖恋腕,計算具有某一特征的數(shù)據(jù)點(diǎn)在特定范圍內(nèi)(叫作bin)的出現(xiàn)概率。每張圖都包含兩個直方圖逆瑞,一個是良性類別的所有點(diǎn)(藍(lán)色)荠藤,一個是惡性類別的所有點(diǎn)(紅色)伙单。這樣我們可以了解每個特征在兩個類別中的分布情況,也可以猜測哪些特征能夠更好地區(qū)分良性樣本和惡性樣本哈肖。例如吻育,“smoothness error”特征似乎沒有什么信息量,因為兩個直方圖大部分都重疊在一起淤井,而“worst concave points”特征看起來信息量相當(dāng)大布疼,因為兩個直方圖的交集很小。
但是币狠,這種圖無法向我們展示變量之間的相互作用以及這種相互作用與類別之間的關(guān)系游两。利用PCA,我們可以獲取到主要的相互作用,并得到稍微完整的圖像漩绵。我們可以找到前兩個主成分贱案,并在這個新的二維空間中利用散點(diǎn)圖將數(shù)據(jù)可視化。
在應(yīng)用PCA之前止吐,我們利用StandardScaler縮放數(shù)據(jù)轰坊,使每個特征的方差均為1:
from sklearn.datasets import load_breast_cancer
cancer=load_breast_cancer()
scaler=StandardScaler()
scaler.fit(cancer.data)
X_scaled=scaler.transform(cancer.data)
學(xué)習(xí)并應(yīng)用PCA變換與應(yīng)用預(yù)處理變換一樣簡單。我們將PCA對象實例化祟印,調(diào)用fit方法找到主成分肴沫,然后調(diào)用transform來旋轉(zhuǎn)并降維。默認(rèn)情況下蕴忆,PCA僅旋轉(zhuǎn)(并移動)數(shù)據(jù)颤芬,但保留所有的主成分。為了降低數(shù)據(jù)的維度套鹅,我們需要在創(chuàng)建PCA對象時指定想要保留的主成分個數(shù):
from sklearn.decomposition import PCA
# 保留數(shù)據(jù)的前兩個主成分
pca=PCA(n_components=2)
# 對乳腺癌數(shù)據(jù)擬合PCA模型
pca.fit(X_scaled)
# 將數(shù)據(jù)變換到前兩個主成分的方向上
X_pca=pca.transform(X_scaled)
print("Original shape:{}".format(str(X_scaled.shape)))
print("Reduced shape:{}".format(str(X_pca.shape)))
現(xiàn)在我們可以對前兩個主成分作圖:
# 對第一個和第二個主成分作圖站蝠,按類別著色
plt.figure(figsize=(8,8))
mglearn.discrete_scatter(X_pca[:,0],X_pca[:,1],cancer.target)
plt.legend(cancer.target_names,loc="best")
plt.gca().set_aspect("equal")
plt.xlabel("First principal component")
plt.ylabel("Second principal component")
重要的是要注意,PCA是一種無監(jiān)督方法卓鹿,菱魔,在尋找旋轉(zhuǎn)方向時沒有用到任何類別信息。它只是觀察數(shù)據(jù)中的相關(guān)性吟孙。對于這里所示的散點(diǎn)圖澜倦,我們繪制了第一主成分與第二主成分的關(guān)系,然后利用類別信息對數(shù)據(jù)點(diǎn)進(jìn)行著色杰妓。你可以看到藻治,在這個二維空間中兩個類別被很好地分離。這讓我們相信巷挥,即使是線性分類器(在這個空間中學(xué)習(xí)一條直線)也可以在區(qū)分這兩個類別時表現(xiàn)得相當(dāng)不錯桩卵。我們還可以看到,惡性點(diǎn)比良性點(diǎn)更加分散,這一點(diǎn)也可以之前的直方圖中看出來雏节。
PCA的一個缺點(diǎn)在于胜嗓,通常不容易對圖中的兩個軸做出解釋。主成分對應(yīng)于原始數(shù)據(jù)中的方向钩乍,所以它們是原始特征的組合兼蕊。但這些組合往往非常復(fù)雜,這一點(diǎn)我們很快就會看到件蚕。在擬合過程中孙技,主成分被保留在PCA對象的components_屬性中:
print("PCA component shape:{}".format(pca.components_.shape))
PCA component shape:(2, 30)
components_的每一行對應(yīng)于一個主成分,它們按重要性(第一主成分排在首位排作,以此類推)牵啦。列對應(yīng)于PCA的原始特征屬性,在本例中即為“mean radius”“mean texture”等妄痪。我們來看一下components_的內(nèi)容:
print("PCA component:\n{}".format(pca.components_))
PCA component:
[[ 0.21890244 0.10372458 0.22753729 0.22099499 0.14258969 0.23928535
0.25840048 0.26085376 0.13816696 0.06436335 0.20597878 0.01742803
0.21132592 0.20286964 0.01453145 0.17039345 0.15358979 0.1834174
0.04249842 0.10256832 0.22799663 0.10446933 0.23663968 0.22487053
0.12795256 0.21009588 0.22876753 0.25088597 0.12290456 0.13178394]
[-0.23385713 -0.05970609 -0.21518136 -0.23107671 0.18611302 0.15189161
0.06016536 -0.0347675 0.19034877 0.36657547 -0.10555215 0.08997968
-0.08945723 -0.15229263 0.20443045 0.2327159 0.19720728 0.13032156
0.183848 0.28009203 -0.21986638 -0.0454673 -0.19987843 -0.21935186
0.17230435 0.14359317 0.09796411 -0.00825724 0.14188335 0.27533947]]
我們還可以用熱圖將系數(shù)可視化哈雏,這可能更容易理解:
plt.matshow(pca.components_,cmap="viridis")
plt.yticks([0,1],["First component","Second component"])
plt.colorbar()
plt.xticks(range(len(cancer.feature_names)),cancer.feature_names,rotation=60,ha="left")
plt.xlabel("Feature")
plt.ylabel("Principal components")
你可以看到,在第一個主成分中衫生,所有特征的符號相同(均為正裳瘪,但前面我們提到過,箭頭指向哪個方向無關(guān)緊要)罪针。這意味著在所有特征之間存在普遍的相關(guān)性彭羹。如果一個測量值較大的話,其他的測量值可能也較大泪酱。第二個主成分的符號有正有負(fù)派殷,而且兩個主成分都包含所有30個特征。這種所有特征的混合使得解釋上圖中的坐標(biāo)軸變的十分困難墓阀。
特征提取的特征臉
PCA的另一個應(yīng)用是特征提取毡惜。特征提取背后的思想是,可以找到一種數(shù)據(jù)表示斯撮,比給定的原始表示更適合于分析经伙。特征提取很有用,它的一個很好的應(yīng)用實例就是圖像勿锅。圖像由像素組成帕膜,通常存儲為紅綠藍(lán)(RGB)強(qiáng)度。圖像中的對象通常由上千個像素組成粱甫,它們只有放在一起才有意義泳叠。
我們將給出用PCA對圖像做特征提取的一個簡單應(yīng)用,即處理Wild數(shù)據(jù)集Labeled Faces(標(biāo)記人臉)中的人臉圖像茶宵。這一數(shù)據(jù)集包含從互聯(lián)網(wǎng)下載的名人臉部圖像,它包含從21世紀(jì)初開始的政治家宗挥、歌手乌庶、演員和運(yùn)動員的人臉圖像种蝶。我們使用這些圖像的灰度版本,并將它們按比例縮小以加快處理速度:
from sklearn.datasets import fetch_lfw_people
people=fetch_lfw_people(min_faces_per_person=25,resize=0.7)
image_shape=people.images[0].shape
fix,axes=plt.subplots(2,5,figsize=(15,8),subplot_kw={'xticks':(),'yticks':()})
for target,image,ax in zip(people.target,people.images,axes.ravel()):
ax.imshow(image)
ax.set_title(people.target_names[target])
一共有2588張圖像瞒大,每張大小為87像素*65像素螃征,分別屬于62個不同的人:
print("people.images.shape:{}".format(people.images.shape))
print("Number of classes:{}".format(people.target_names))
people.images.shape:(2588, 87, 65)
Number of classes:['Alejandro Toledo' 'Alvaro Uribe' 'Andre Agassi' 'Ariel Sharon'
'Arnold Schwarzenegger' 'Bill Clinton' 'Colin Powell' 'David Beckham'
'Donald Rumsfeld' 'George W Bush' 'Gerhard Schroeder'
'Gloria Macapagal Arroyo' 'Gray Davis' 'Guillermo Coria' 'Hans Blix'
'Hugo Chavez' 'Jack Straw' 'Jacques Chirac' 'Jean Chretien'
'Jennifer Capriati' 'John Ashcroft' 'John Negroponte'
'Juan Carlos Ferrero' 'Junichiro Koizumi' 'Kofi Annan' 'Laura Bush'
'Lleyton Hewitt' 'Luiz Inacio Lula da Silva' 'Mahmoud Abbas'
'Megawati Sukarnoputri' 'Nestor Kirchner' 'Recep Tayyip Erdogan'
'Ricardo Lagos' 'Roh Moo-hyun' 'Rudolph Giuliani' 'Serena Williams'
'Silvio Berlusconi' 'Tom Daschle' 'Tom Ridge' 'Tony Blair' 'Vicente Fox'
'Vladimir Putin']
但這個數(shù)據(jù)有些傾斜,其中包含George W.Bush(小布什)和Coin Powell(科林.鮑威爾)的大量圖像盯滚,正如你在下面所見:
# 計算每個目標(biāo)出現(xiàn)的次數(shù)
counts=np.bincount(people.target)
# 將次數(shù)與目標(biāo)名稱一起打印出來
for i,(count,name) in enumerate(zip(counts,people.target_names)):
print("{0:25} {1:3}".format(name,count),end=' ')
if (i+1)%3==0:
print()
Alejandro Toledo 39 Alvaro Uribe 35 Andre Agassi 36
Ariel Sharon 77 Arnold Schwarzenegger 42 Bill Clinton 29
Colin Powell 236 David Beckham 31 Donald Rumsfeld 121
George W Bush 530 Gerhard Schroeder 109 Gloria Macapagal Arroyo 44
Gray Davis 26 Guillermo Coria 30 Hans Blix 39
Hugo Chavez 71 Jack Straw 28 Jacques Chirac 52
Jean Chretien 55 Jennifer Capriati 42 John Ashcroft 53
John Negroponte 31 Juan Carlos Ferrero 28 Junichiro Koizumi 60
Kofi Annan 32 Laura Bush 41 Lleyton Hewitt 41
Luiz Inacio Lula da Silva 48 Mahmoud Abbas 29 Megawati Sukarnoputri 33
Nestor Kirchner 37 Recep Tayyip Erdogan 30 Ricardo Lagos 27
Roh Moo-hyun 32 Rudolph Giuliani 26 Serena Williams 52
Silvio Berlusconi 33 Tom Daschle 25 Tom Ridge 33
Tony Blair 144 Vicente Fox 32 Vladimir Putin 49
為了降低數(shù)據(jù)傾斜,我們對每個人最多只取50張圖像(否則背率,特征提取將會被George W.Bush的可能性大大影響):
mask=np.zeros(people.target.shape,dtype=np.bool)
for target in np.unique(people.target):
mask[np.where(people.target==target)[0][:50]]=1
X_people=people.data[mask]
y_people=people.target[mask]
# 將灰度值縮放到0和1之間寝姿,而不是在0和255之間
# 以得到更好的數(shù)據(jù)穩(wěn)定性
X_people=X_people/255
人臉識別的一個常見任務(wù)就是某個前所未見的人臉是否屬于數(shù)據(jù)庫中的某個已知人物划滋。
這在照片收集饵筑、社交媒體和安全應(yīng)用中都有應(yīng)用。解決這個問題的方法之一就是構(gòu)建一個分類器处坪,每個人都是一個單獨(dú)的類別翻翩。但人臉數(shù)據(jù)庫中通常由許多不同的人,而同一個人的圖像很少(也就是說稻薇,每個類別的訓(xùn)練樣例很少)嫂冻。這使得大多數(shù)分類器的訓(xùn)練都很困難。另外塞椎,通常你還想要能夠輕松添加新的人物桨仿,不需要重新訓(xùn)練一個大型模型。
一種簡單的解決辦法是使用單一最近鄰分類器案狠,尋找與你要分類的人臉最為相似的人臉。這個分類器原則上可以處理每個類別只有一個訓(xùn)練樣例的情況。下面看一下KNeighborsClassifier的表現(xiàn)如何:
from sklearn.neighbors import KNeighborsClassifier
# 將數(shù)據(jù)分為訓(xùn)練集和測試集
X_train,X_test,y_train,y_test=train_test_split(X_people,y_people,stratify=y_people,random_state=0)
# 使用一個鄰居構(gòu)建KNeighborsClassifier
knn=KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train,y_train)
print("Test set score of 1-nn:{:.2f}".format(knn.score(X_test,y_test)))
Test set score of 1-nn:0.27
我們得到的精度為27%。對于包含62個類別的分類問題來說操刀,這實際上不算太差(隨機(jī)猜測的精度約為1/62=1.5%)欢唾,但也不算好杀迹。我們每識別四次僅正確識別了一個人。
這里就可以用到PCA。想要度量人臉的相似度 ,計算原始像素空間中的距離是一種相當(dāng)糟糕的方法。用像素表示來比較兩張圖像時,我們比較的是每個像素的灰度值與另一張圖像對應(yīng)位置的像素灰度值帅容。這種表示與人們對人臉圖像的解釋方式有很大不同麦乞,使用這種原始表示很難獲取面部特征简肴。例如佣渴,如果使用像素距離,那么將人類向右移動一個像素將會發(fā)生巨大的變化,得到一個完全不同的表示突硝。我們希望护盈,使用沿著主成分方向的距離可以提高精度整慎。這里我們啟用PCA的白化(whitening)選項拧揽,它將主成分縮放到相同的尺度。變換后的結(jié)果與使用StandardScaler相同。再次使用之前圖的數(shù)據(jù),白化不僅對應(yīng)于旋轉(zhuǎn)數(shù)據(jù),還對應(yīng)于縮放數(shù)據(jù)使其形狀是圓形而不是橢圓:
mglearn.plots.plot_pca_whitening()
我們對訓(xùn)練數(shù)據(jù)擬合PCA對象崇摄,并提取前100個主成分捆交。然后對訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)進(jìn)行變換:
pca=PCA(n_components=100,whiten=True,random_state=0).fit(X_train)
X_train_pca=pca.transform(X_train)
X_test_pca=pca.transform(X_test)
print("X_train_pca.shape:{}".format(X_train_pca.shape))
X_train_pca.shape:(1221, 100)
新數(shù)據(jù)有100個特征肉瓦,即前100個主成分■瓿茫現(xiàn)在疫剃,可以對新表示使用單一最近鄰分類器來講我們的圖像分類:
knn=KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train_pca,y_train)
print("Test set score of 1-nn:{:.2f}".format(knn.score(X_test_pca,y_test)))
Test set score of 1-nn:0.37
我們的精度有了相當(dāng)顯著的提高咨油,從27%提升到37%冀膝,這證實了我們的直覺脊奋,即主成分可能提供了一種更好的數(shù)據(jù)表示。
對于圖像數(shù)據(jù)效五,我們還可以很容易地將找到的主成分可視化地消。請記住,成分對于輸入空間里的方向畏妖。這里的輸入空間是87像素 * 65像素的灰度圖像,所以在這個空間中的方向也是87像素 * 65像素的灰度圖像。
我們來看一下前幾個主成分:
print("pca.components_.shape:{}".format(pca.components_.shape))
pca.components_.shape:(100, 5655)
fix,axes=plt.subplots(3,5,figsize=(15,12),subplot_kw={'xticks':(),'yticks':()})
for i,(component,ax) in enumerate(zip(pca.components_,axes.ravel())):
ax.imshow(component.reshape(image_shape),cmap='viridis')
ax.set_title("{}.component".format((i+1)))
雖然我們肯定無法理解這些成分的所有內(nèi)容,但可以猜測一些主成分捕捉到了人臉圖像的哪些方面。第一個主成分似乎主要編碼的是人臉與背景的對比渠脉,第二個主成分編碼的是人左半部分和右半部分的明暗程度差異,如此等等痘绎。雖然這種表示比原始像素值的語義稍強(qiáng)轻腺,但它仍與人們感知人臉的方式相去甚遠(yuǎn)知纷。由于PCA模型是基于像素的,因此人臉的相對位置(眼睛、下巴和鼻子的位置)和明暗程度都對兩張圖像在像素表示中的相似程度有很大影響咬崔。但人臉的相對位置和明暗程度可能并不是人們首先感知的內(nèi)容。在要求人們評價人臉的相似度時们何,他們更可能會使用年齡、性別诚啃、面部表情和發(fā)型等屬性,而這些屬性很難從像素強(qiáng)度中推斷出來浑玛。重要的是要記住绍申,算法對數(shù)據(jù)(特別是視覺數(shù)據(jù),比如人們非常熟悉的圖像)的解釋通常與人類的解釋方式大不相同顾彰。
非負(fù)矩陣分解
非負(fù)矩陣分解(non-negative matrix factorization,NMF)是另一種無監(jiān)督學(xué)習(xí)算法极阅,其目的在于提取有用的特征。它的工作原理類似于PCA涨享,也可以用于降維筋搏。與PCA相同,我們試圖將每個數(shù)據(jù)點(diǎn)寫成一些分量的加權(quán)求和厕隧。但在PCA中奔脐,我們想要的是正交分量,并且能夠解釋盡可能多的數(shù)據(jù)方差吁讨;而在NMF中髓迎,我們希望分量和系數(shù)均為非負(fù),也就是說建丧,我們希望分量和系數(shù)都大于或等于0排龄。因此,這種方法只能應(yīng)用于每個特征都是非負(fù)的數(shù)據(jù)翎朱,因為非負(fù)分量的非負(fù)求和不可能變?yōu)樨?fù)值橄维。
將數(shù)據(jù)分解成非負(fù)加權(quán)求和的這個過程,對由多個獨(dú)立源相加(或疊加)創(chuàng)建而成的數(shù)據(jù)特別有用拴曲,比如多人說話的音軌或包含多種樂器的音樂争舞。在這種情況下,NMF可以識別出組合成數(shù)據(jù)的原始分量澈灼【捍ǎ總的來說,與PCA相比,NMF得到的分量更容易解釋流译,因為負(fù)的分量和系數(shù)可能會導(dǎo)致難以解釋的抵消效應(yīng)(cancellation effect)逞怨。
在將NMF應(yīng)用于人臉數(shù)據(jù)集之前,我們先來簡要回顧一下模擬數(shù)據(jù):
將NMF應(yīng)用于模擬數(shù)據(jù)
與使用PCA不同福澡,我們需要保證數(shù)據(jù)是正的叠赦,NMF能夠?qū)?shù)據(jù)進(jìn)行操作。這說明數(shù)據(jù)相對于原點(diǎn)(0,0)的位置實際上對NMF很重要革砸。因此除秀,你可以將提取出來的非負(fù)分量看作是從(0,0)到數(shù)據(jù)的方向。
下面給出NMF對二維玩具數(shù)據(jù)上的結(jié)果:
mglearn.plots.plot_nmf_illustration()
對于兩個分量的NMF(如上左圖所示)算利,顯然所有數(shù)據(jù)點(diǎn)都可以寫成兩個分量的正數(shù)組合册踩。如果有足夠多的分量能夠完美地重建數(shù)據(jù)(分量個數(shù)與特征個數(shù)相同),那么算法會選擇指向數(shù)據(jù)極值的方向效拭。
如果我們僅使用一個分量暂吉,那么NMF會創(chuàng)建一個指向平均值的分量,因為指向這里可以對數(shù)據(jù)做出最好的解釋缎患。你可以看到慕的,與PCA不同旦事,減少分量個數(shù)不僅會刪除一些方向牍戚,而且會創(chuàng)建一組完全不同的分量顾腊!NMF的分量也沒有按任何特定方法排序镣煮,所以不存在“第一非負(fù)分量”:所有分量的地位平等。
NMF使用了隨機(jī)初始化恃锉,根據(jù)隨機(jī)種子的不同可能會產(chǎn)生不同的結(jié)果壶冒。在相對簡單的情況下(比如兩個分量的模擬數(shù)據(jù))福青,所有數(shù)據(jù)都可以被完美地解釋眼刃,那么隨機(jī)性的影響很腥葡健(雖然可能會影響分量的順序或尺度)。在更加復(fù)雜的情況下擂红,影響可能會很大引镊。
將NMF應(yīng)用于人臉圖像
現(xiàn)在我們將NMF應(yīng)用于之前用過的Wild數(shù)據(jù)集中的Labeled Faces。NMF的主要參數(shù)是我們想要提取的分量個數(shù)篮条。通常來說,這個數(shù)字要小于輸入特征的個數(shù)(否則的話吩抓,將每個像素作為單獨(dú)的分量就可以對數(shù)據(jù)進(jìn)行解釋)涉茧。
首先,我們來觀察分量個數(shù)如何影響NMF重建數(shù)據(jù)的好壞:
mglearn.plots.plot_nmf_faces(X_train,X_test,image_shape)
反向變換的數(shù)據(jù)質(zhì)量與使用PCA時類似疹娶,但要稍差一些伴栓。這是符合預(yù)期的,因為PCA找到的是重建的最佳方向。NMF通常并不用于對數(shù)據(jù)進(jìn)行重建或編碼钳垮,而是用于在數(shù)據(jù)中尋找有趣的模式惑淳。
我們嘗試僅提取一部分分量(比如15個),初步觀察一下數(shù)據(jù):
from sklearn.decomposition import NMF
nmf=NMF(n_components=15,random_state=0)
nmf.fit(X_train)
X_train_nmf=nmf.transform(X_train)
X_test_nmf=nmf.transform(X_test)
fix,axes=plt.subplots(3,5,figsize=(15,12),subplot_kw={'xticks':(),'yticks':()})
for i,(component,ax) in enumerate(zip(nmf.components_,axes.ravel())):
ax.imshow(component.reshape(image_shape))
ax.set_title("{}.component".format(i))
這些分量都是正的饺窿,因此比之前的PCA分量效果更像人臉原型歧焦。例如,你可以清楚地看到肚医,分量3(component3)顯示了稍微向右轉(zhuǎn)動的人臉绢馍,而分量7(component 7)則顯示了稍微向左轉(zhuǎn)動的人臉。我們來看一下這兩個分量特別大的那些圖像:
compn=3
# 按第3個分量排序肠套,繪制前10張圖像
inds=np.argsort(X_train_nmf[:,compn][::-1])
fig,axes=plt.subplots(2,5,figsize=(15,8),subplot_kw={'xticks':(),'yticks':()})
for i,(ind,ax) in enumerate(zip(inds,axes.ravel())):
ax.imshow(X_train[ind].reshape(image_shape))
compn=7
# 按第7個分量排序舰涌,繪制前10張圖像
inds=np.argsort(X_train_nmf[:,compn][::-1])
fig,axes=plt.subplots(2,5,figsize=(15,8),subplot_kw={'xticks':(),'yticks':()})
for i,(ind,ax) in enumerate(zip(inds,axes.ravel())):
ax.imshow(X_train[ind].reshape(image_shape))
正如所料,分量3系數(shù)較大的人臉都是向右看的人臉你稚,而分量7系數(shù)較大的人臉都向左看瓷耙。如前所述,提取這樣的模式最適合于具有疊加結(jié)構(gòu)的數(shù)據(jù)刁赖,包括音頻搁痛、基因表達(dá)和文本數(shù)據(jù)。我們通過一個模擬數(shù)據(jù)的例子來看一下這種用法乾闰。
假設(shè)我們對一個信號感興趣落追,它是三個不同信號源合成的:
S=mglearn.datasets.make_signals()
plt.figure(figsize=(6,1))
plt.plot(S,'-')
plt.xlabel("Time")
plt.ylabel("Signal")
不幸的是,我們無法觀測到原始信號涯肩,只能觀測到三個信號的疊加混合轿钠。我們想要將混合信號分解為原始分量。假設(shè)我們有許多不同的方法來觀測混合信號(比如有100臺測量裝置)病苗,每種方法都為我們提供了一系列測量結(jié)果:
# 將數(shù)據(jù)混合成100維的狀態(tài)
A=np.random.RandomState(0).uniform(size=(100,3))
X=np.dot(S,A.T)
print("Shape of measurements:{}".format(X.shape))
Shape of measurements:(2000, 100)
我們可以用NMF來還原這三個信號:
nmf=NMF(n_components=3,random_state=42)
S_=nmf.fit_transform(X)
print("Recovered signal shape:{}".format(S_.shape))
Recovered signal shape:(2000, 3)
為了對比疗垛,我們也應(yīng)用了PCA:
pca=PCA(n_components=3)
H=pca.fit_transform(X)
下面使用NMF和PCA發(fā)現(xiàn)信號:
models=[X,S,S_,H]
names=['Observations(first three measurements)','True sources','NMF recovered signals','PCA recovered signals']
fig,axes=plt.subplots(4,figsize=(8,4),gridspec_kw={'hspace':0.5},subplot_kw={'xticks':(),'yticks':()})
for model,name,ax in zip(models,names,axes):
ax.set_title(name)
ax.plot(model[:,:3],'-')
models=[X,S,S_,H]
names=['Observations(first three measurements)','True sources','NMF recovered signals','PCA recovered signals']
fig,axes=plt.subplots(4,figsize=(8,4),gridspec_kw={'hspace':0.5},subplot_kw={'xticks':(),'yticks':()})
for model,name,ax in zip(models,names,axes):
ax.set_title(name)
ax.plot(model[:,:3],'-')
圖片中包含來自X的100次測量中的3次,用于參考硫朦〈螅可以看到,NMF在發(fā)現(xiàn)原始信號源時得到了不錯的結(jié)果咬展,而PCA則失敗了泽裳,僅使用第一個成分來解釋數(shù)據(jù)中
的大部分變化。要記住破婆,NMF生成的分量是沒有順序的涮总。在這個例子中,NMF分量的順序與原始信號完全相同(參見三條曲線的顏色)祷舀,但這純屬偶然瀑梗。
用t-SNE進(jìn)行流形學(xué)習(xí)
雖然PCA通常是用于變換數(shù)據(jù)的首選方法烹笔,使你能夠用散點(diǎn)圖將其可視化,但這一切方法的性質(zhì)(先旋轉(zhuǎn)然后減少方向)限制其有效性抛丽,正如我們在Wild數(shù)據(jù)集Labeled Faces的散點(diǎn)圖中所看到的那樣谤职。有一類用于可視化的算法叫作流形學(xué)習(xí)算法(manifold learning algorithm),它允許進(jìn)行更復(fù)雜的映射,通常也可以給出更好的可視化亿鲜。其中特別有用的一個是t-SNE算法允蜈。
流形學(xué)習(xí)算法主要用于可視化,因此很少用來生成兩個以上的新特征狡门。其中一些算法(包括t-SNE)計算訓(xùn)練數(shù)據(jù)的一種新表示陷寝,但不允許變換新數(shù)據(jù)。 流形學(xué)習(xí)對探索性數(shù)據(jù)分析是很有用的其馏,但如果最終目標(biāo)是監(jiān)督學(xué)習(xí)的話凤跑,則很少使用。t-SNE背后的思想是找到數(shù)據(jù)的一個二維表示叛复,盡可能地保持?jǐn)?shù)據(jù)點(diǎn)之間的距離仔引。t-SNE首先給出每個數(shù)據(jù)點(diǎn)的隨機(jī)二維表示,然后嘗試讓在原始特征空間中距離較近的點(diǎn)更加靠近褐奥,原始特征空間中相距較遠(yuǎn)的點(diǎn)更加遠(yuǎn)離咖耘。t-SNE重點(diǎn)關(guān)注距離較近的點(diǎn),而不是保持距離較遠(yuǎn)的點(diǎn)之間的距離撬码。換句話說儿倒,它試圖保存那些表示哪些點(diǎn)比較靠近的信息。
我們將對scikit-learn包含的一個手寫數(shù)字?jǐn)?shù)據(jù)集應(yīng)用t-SNE流形學(xué)習(xí)算法呜笑。在這個數(shù)據(jù)集中夫否,每個數(shù)據(jù)點(diǎn)都是0到9之間手寫數(shù)字的一張8 * 8 灰度圖像:
from sklearn.datasets import load_digits
digits=load_digits()
fig,axes=plt.subplots(2,5,figsize=(10,5),subplot_kw={'xticks':(),'yticks':()})
for ax,img in zip(axes.ravel(),digits.images):
ax.imshow(img)
我們用PCA將降到二維的數(shù)據(jù)可視化。我們對前兩個主成分作圖叫胁,并按類別對數(shù)據(jù)點(diǎn)著色:
# 構(gòu)建一個PCA原型
pca=PCA(n_components=2)
pca.fit(digits.data)
# 將digits數(shù)據(jù)變換到前兩個主成分的方向上
digits_pca=pca.transform(digits.data)
colors=['#476A2A','#7851B8','#BD3430','#4A2D4E','#875525','#A83683','#4E655E','#853541','#3A3120','#535D8E']
plt.figure(figsize=(10,10))
plt.xlim(digits_pca[:,0].min(),digits_pca[:,0].max())
plt.ylim(digits_pca[:,1].min(),digits_pca[:,1].max())
for i in range(len(digits.data)):
# 將數(shù)據(jù)實際繪制成文本凰慈,而不是散點(diǎn)
plt.text(digits_pca[i,0],digits_pca[i,1],str(digits.target[i]),color=colors[digits.target[i]],fontdict={'weight':'bold','size':9})
plt.xlabel("First principal component")
plt.ylabel("Second principal component")
實際上,這里我們用每個類別對應(yīng)的數(shù)字作為符號來顯示每個類別的位置驼鹅。利用前兩個主成分可以將數(shù)字0微谓、6和4比較好地分開,盡管仍有重疊输钩。大部分其他數(shù)字都大量重疊在一起豺型。
我們將t-SNE應(yīng)用于同一數(shù)據(jù)集,并對結(jié)果進(jìn)行比較买乃。由于t-SNE不支持變換新數(shù)據(jù)触创,所以TSNE類沒有transform方法。我們可以調(diào)用fit_transform方法來代替为牍,它會構(gòu)建模型并立刻返回變換后的數(shù)據(jù):
from sklearn.manifold import TSNE
tsne=TSNE(random_state=42)
# 使用fit_transform而不是fit哼绑,因為TSNE沒有transform方法
digits_tsne=tsne.fit_transform(digits.data)
plt.figure(figsize=(10,10))
plt.xlim(digits_tsne[:,0].min(),digits_tsne[:,0].max()+1)
plt.ylim(digits_tsne[:,1].min(),digits_tsne[:,1].max()+1)
for i in range(len(digits.data)):
# 將數(shù)據(jù)實際繪制成文本,而不是散點(diǎn)
plt.text(digits_tsne[i,0],digits_tsne[i,1],str(digits.target[i]),color=colors[digits.target[i]],fontdict={'weight':'bold','size':9})
plt.xlabel("t-SNE feature 0")
plt.ylabel("t-SNE feature 1")
t-SNE的結(jié)果非常棒碉咆。所有類別都被明確分開抖韩。數(shù)字1和9被分成幾塊,但大多數(shù)類別都形成一個密集的組疫铜。要記住茂浮,這種方法并不知道類別標(biāo)簽:它完全是無監(jiān)督的。但它能夠找到數(shù)據(jù)的一種二維表示壳咕,僅根據(jù)原始空間中數(shù)據(jù)點(diǎn)之間的靠近程度就能夠?qū)⒏鱾€類別明確分開席揽。
t-SNE算法有一些調(diào)節(jié)參數(shù),雖然默認(rèn)參數(shù)的效果通常就很好谓厘。我們可以嘗試修改perplexity和early_exaggeration,但作用一般很小幌羞。
聚類
聚類(clustering)是將數(shù)據(jù)集劃分成組的任務(wù),這些組叫作簇(cluster)竟稳,其目標(biāo)是劃分?jǐn)?shù)據(jù)属桦,使得一個簇內(nèi)的數(shù)據(jù)點(diǎn)發(fā)車相似且不同簇內(nèi)的數(shù)據(jù)點(diǎn)非常不同。與分類算法類似他爸,聚類算法為每個數(shù)據(jù)點(diǎn)分配(或預(yù)測)一個數(shù)字聂宾,表示這個點(diǎn)屬于哪個簇。
k均值聚類
k均值聚類是最簡單也最常用的聚類算法之一诊笤。它試圖找到代表數(shù)據(jù)特定區(qū)域的簇中心(cluster center)系谐。算法交替執(zhí)行以下兩個步驟:將每個數(shù)據(jù)點(diǎn)分配給最近的簇中心,然后將每個簇中心設(shè)置為所分配的所有數(shù)據(jù)點(diǎn)的平均值讨跟。如果簇的分配不再發(fā)生變化纪他,那么算法結(jié)束。
下面在一個模擬數(shù)據(jù)集上對這一算法進(jìn)行說明:
mglearn.plots.plot_kmeans_algorithm()
簇中心用三角形表示许赃,而數(shù)據(jù)點(diǎn)用圓形表示止喷。顏色表示簇成員。我們指定要尋找三個簇混聊,所以通過聲明三個隨機(jī)數(shù)據(jù)點(diǎn)為簇中心來將算法初始化弹谁。然后開始迭代算法。首先句喜,每個數(shù)據(jù)點(diǎn)被分配給距離最近的簇中心预愤。接下來,將簇中心修改為所分配點(diǎn)的平均值咳胃。然后將這一個過程再重復(fù)兩次植康。在第三次迭代之后,為簇中心分配的數(shù)據(jù)點(diǎn)保持不變展懈,因此算法結(jié)束销睁。
給定新的數(shù)據(jù)點(diǎn)供璧,k均值hi將其分配給最近的簇中心。下面展示學(xué)到的簇中心邊界:
mglearn.plots.plot_kmeans_boundaries()
用sickit-learn應(yīng)用k均值相當(dāng)簡單冻记。下面我們將其應(yīng)用于上圖中的模擬數(shù)據(jù)睡毒。我們將KMeans類實例化,并設(shè)置我們要尋找的簇個數(shù)冗栗。然后對數(shù)據(jù)調(diào)用fit方法:
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
# 生成模擬的二維數(shù)據(jù)
X,y=make_blobs(random_state=1)
# 構(gòu)建聚類模型
kmeans=KMeans(n_clusters=3)
kmeans.fit(X)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=3, n_init=10, n_jobs=None, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
算法運(yùn)行期間演顾,為X中的每個訓(xùn)練數(shù)據(jù)點(diǎn)分配一個簇標(biāo)簽。你可以在kmeans.lables_屬性中找到這些標(biāo)簽:
print("Cluster memberships:\n{}".format(kmeans.labels_))
Cluster memberships:
[0 1 1 1 2 2 2 1 0 0 1 1 2 0 2 2 2 0 1 1 2 1 2 0 1 2 2 0 0 2 0 0 2 0 1 2 1
1 1 2 2 1 0 1 1 2 0 0 0 0 1 2 2 2 0 2 1 1 0 0 1 2 2 1 1 2 0 2 0 1 1 1 2 0
0 1 2 2 0 1 0 1 1 2 0 0 0 0 1 0 2 0 0 1 1 2 2 0 2 0]
因為我們要找的是3個簇隅居,所以簇的編號是0到2钠至。
你也可以用predict方法為新數(shù)據(jù)點(diǎn)分配簇標(biāo)簽。預(yù)測時會將最近的簇中心分配給每個新數(shù)據(jù)點(diǎn)胎源,但現(xiàn)有模型不會改變棉钧。對訓(xùn)練集運(yùn)行predict會返回與labels_相同的結(jié)果:
print(kmeans.predict(X))
[0 1 1 1 2 2 2 1 0 0 1 1 2 0 2 2 2 0 1 1 2 1 2 0 1 2 2 0 0 2 0 0 2 0 1 2 1
1 1 2 2 1 0 1 1 2 0 0 0 0 1 2 2 2 0 2 1 1 0 0 1 2 2 1 1 2 0 2 0 1 1 1 2 0
0 1 2 2 0 1 0 1 1 2 0 0 0 0 1 0 2 0 0 1 1 2 2 0 2 0]
可以看到,聚類算法與分類算法有些相似乒融,每個元素都有一個標(biāo)簽掰盘。但不存在真實的標(biāo)簽,因此標(biāo)簽本身并沒有先驗意義赞季。我們回到之前討論過的人臉圖像聚類的例子愧捕。聚類的結(jié)果可能是,算法找到的第3個簇僅包含你朋友Bela的面孔申钩。但只有在查看圖片之后才能知道這一點(diǎn)次绘,而且數(shù)字3是任意的。算法給你的唯一信息就是所有標(biāo)簽為3的人臉都是相似的撒遣。
對于我們剛剛在二維玩具數(shù)據(jù)集上運(yùn)行的聚類算法邮偎,這意味著我們不應(yīng)該為其中一組的標(biāo)簽是0、另一組的標(biāo)簽是1這一事實賦予任何意義义黎。再次運(yùn)行該算法可能會得到不同的簇編號禾进,原因在于初始化的隨機(jī)性質(zhì)。
我們可以查看剛才模擬數(shù)據(jù)集的標(biāo)簽廉涕。簇中心被保存在cluster_centers_屬性中泻云,我們用三角形表示它們:
mglearn.discrete_scatter(X[:,0],X[:,1],kmeans.labels_,markers='o')
mglearn.discrete_scatter(kmeans.cluster_centers_[:,0],kmeans.cluster_centers_[:,1],[0,1,2],markers='^',markeredgewidth=2)
我們也可以使用更多或更少的簇中心:
fig,axes=plt.subplots(1,2,figsize=(10,5))
# 使用2個簇中心
kmeans=KMeans(n_clusters=2)
kmeans.fit(X)
assignments=kmeans.labels_
mglearn.discrete_scatter(X[:,0],X[:,1],assignments,ax=axes[0])
# 使用5個簇中心
kmeans=KMeans(n_clusters=5)
kmeans.fit(X)
assignments=kmeans.labels_
mglearn.discrete_scatter(X[:,0],X[:,1],assignments,ax=axes[1])
k均值的失敗案例
即使你知道給定數(shù)據(jù)集中簇的“正確”個數(shù),k均值可能也不是總能找到它們狐蜕。每個簇僅由其中心定義宠纯,這意味著每個簇都是凸形(convex)。因此层释,k均值只能找到相對簡單的形狀婆瓜。k均值還假設(shè)所有簇在某種程度上具有相同的“直徑”,它總是將簇之間的邊界剛好畫在簇中心的中間位置贡羔。有時這會導(dǎo)致令人驚訝的結(jié)果:
X_varied,y_varied=make_blobs(n_samples=200,cluster_std=[1.0,2.5,0.5],random_state=170)
y_pred=KMeans(n_clusters=3,random_state=0).fit_predict(X_varied)
mglearn.discrete_scatter(X_varied[:,0],X_varied[:,1],y_pred)
plt.legend(["cluster 0","cluster 1","cluster 2"],loc='best')
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
你可能認(rèn)為廉白,左下方的密集區(qū)是第一個簇个初,右上方的密集區(qū)是第二個,中間密度較小的區(qū)域是第三個蒙秒。但事實上勃黍,簇0和簇1都包含一些遠(yuǎn)離簇中其他點(diǎn)的點(diǎn)。
k均值還假設(shè)所有方向?qū)γ總€簇同等重要晕讲。如下顯示了一個二維數(shù)據(jù)集,數(shù)據(jù)中包含明確分開的三部分马澈。但是這三部分被沿著對角線方向拉長瓢省。由于k均值僅考慮到最近簇中心的距離,所以它無法處理這種類型的數(shù)據(jù):
# 生成一些隨機(jī)分組數(shù)據(jù)
X,y=make_blobs(random_state=170,n_samples=600)
rng=np.random.RandomState(74)
# 變換數(shù)據(jù)使其拉長
transformation=rng.normal(size=(2,2))
X=np.dot(X,transformation)
# 將數(shù)據(jù)聚類成3個簇
kmeans=KMeans(n_clusters=3)
kmeans.fit(X)
y_pred=kmeans.predict(X)
# 畫出簇分配和簇中心
plt.scatter(X[:,0],X[:,1],c=y_pred,cmap=mglearn.cm3)
plt.scatter(kmeans.cluster_centers_[:,1],kmeans.cluster_centers_[:,1],marker='^',c=[0,1,2],s=100,linewidth=2,cmap=mglearn.cm3)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
如果簇的形狀更加復(fù)雜痊班,那么k均值表現(xiàn)也很差:
# 生成模擬的two_moons數(shù)據(jù)集
from sklearn.datasets import make_moons
X,y=make_moons(n_samples=200,noise=0.05,random_state=0)
# 將數(shù)據(jù)聚類成2個簇
kmeans=KMeans(n_clusters=2)
kmeans.fit(X)
y_pred=kmeans.predict(X)
# 畫出簇分配和簇中心
plt.scatter(X[:,0],X[:,1],c=y_pred,cmap=mglearn.cm2,s=60)
plt.scatter(kmeans.cluster_centers_[:,0],kmeans.cluster_centers_[:,1],marker='^',c=[mglearn.cm2(0),mglearn.cm2(1)],s=100,linewidth=2)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
上圖我們希望聚類算法能夠發(fā)現(xiàn)兩個半月形勤婚。但利用k均值算法是不可能做到這一點(diǎn)的。
矢量量化涤伐,或者將k均值看作分解
雖然k均值是一種聚類算法馒胆,但在k均值和分解方法(比如PCA和NMF)之間存在一些有趣的共同之處。PCA試圖找到數(shù)據(jù)中方差最大的方向凝果,而NMF試圖找到累加的分量祝迂,這通常對應(yīng)與數(shù)據(jù)的“極值”和“部分”。兩種方法都試圖將數(shù)據(jù)點(diǎn)表示為一些分量之和器净。與此相反型雳,k均值則嘗試?yán)么刂行膩肀硎久總€數(shù)據(jù)點(diǎn)。你可以將其看作僅用一個分量來表示每個數(shù)據(jù)點(diǎn)山害,該分量由簇中心給出纠俭。這種觀點(diǎn)將k均值看作是一種分解方法,其中每個點(diǎn)利用單一分量來表示浪慌,這種觀點(diǎn)被稱為矢量化(vector quantization)冤荆。
我們來并排比較PCA、NMF和k均值权纤,分別顯示提取的分量钓简,以及利用100個分量對數(shù)據(jù)集中人臉的重建。對于k均值妖碉,重建就是在訓(xùn)練集中找到的最近的簇中心:
X_train,X_test,y_train,y_test=train_test_split(X_people,y_people,stratify=y_people,random_state=0)
nmf=NMF(n_components=100,random_state=0)
nmf.fit(X_train)
pca=PCA(n_components=100,random_state=0)
pca.fit(X_train)
kmeans=KMeans(n_clusters=100,random_state=0)
kmeans.fit(X_train)
X_reconstructed_pca=pca.inverse_transform(pca.transform(X_test))
X_reconstructed_nmf=np.dot(nmf.transform(X_test),nmf.components_)
X_reconstructed_kmeans=kmeans.cluster_centers_[kmeans.predict(X_test)]
fig,axes=plt.subplots(3,5,figsize=(8,8),subplot_kw={'xticks':(),'yticks':()})
fig.suptitle("Extracted Components")
for ax,comp_kmeans,comp_pca,comp_nmf in zip(axes.T,kmeans.cluster_centers_,pca.components_,nmf.components_):
ax[0].imshow(comp_kmeans.reshape(image_shape))
ax[1].imshow(comp_pca.reshape(image_shape),cmap='viridis')
ax[2].imshow(comp_nmf.reshape(image_shape))
axes[0,0].set_ylabel("kmeans")
axes[1,0].set_ylabel("pca")
axes[2,0].set_ylabel("nmf")
fig,axes=plt.subplots(4,5,figsize=(8,8),subplot_kw={'xticks':(),'yticks':()})
fig.suptitle("Reconstructions")
for ax,orig,rec_kmeans,rec_pca,rec_nmf in zip(axes.T,X_test,X_reconstructed_kmeans,X_reconstructed_pca,X_reconstructed_nmf):
ax[0].imshow(orig.reshape(image_shape))
ax[1].imshow(rec_kmeans.reshape(image_shape))
ax[2].imshow(rec_pca.reshape(image_shape))
ax[3].imshow(rec_nmf.reshape(image_shape))
axes[0,0].set_ylabel("original")
axes[1,0].set_ylabel("kmeans")
axes[2,0].set_ylabel("pca")
axes[3,0].set_ylabel("nmf")
利用k均值做矢量量化的一個有趣之處在于涌庭,可以用比輸入維度更多的簇來對數(shù)據(jù)進(jìn)行編碼。對于two_moons數(shù)據(jù)欧宜,使用PCA或NMF,我們對這個數(shù)據(jù)無能為力坐榆,因為它只有兩個維度。使用PCA或NMF將其降到一維冗茸,將會完全破壞數(shù)據(jù)的結(jié)構(gòu)席镀。但通過使用更多的簇中心匹中,我們可以用k均值找到一種更具表現(xiàn)力的表示:
X,y=make_moons(n_samples=200,noise=0.05,random_state=0)
kmeans=KMeans(n_clusters=10,random_state=0)
kmeans.fit(X)
y_pred=kmeans.predict(X)
plt.scatter(X[:,0],X[:,1],c=y_pred,s=60,cmap='Paired')
plt.scatter(kmeans.cluster_centers_[:,0],kmeans.cluster_centers_[:,1],s=60,marker='^',c=range(kmeans.n_clusters),linewidth=2,cmap='Paired')
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
print("Cluster memberships:\n{}".format(y_pred))
Cluster memberships:
[9 2 5 4 2 7 9 6 9 6 1 0 2 6 1 9 3 0 3 1 7 6 8 6 8 5 2 7 5 8 9 8 6 5 3 7 0
9 4 5 0 1 3 5 2 8 9 1 5 6 1 0 7 4 6 3 3 6 3 8 0 4 2 9 6 4 8 2 8 4 0 4 0 5
6 4 5 9 3 0 7 8 0 7 5 8 9 8 0 7 3 9 7 1 7 2 2 0 4 5 6 7 8 9 4 5 4 1 2 3 1
8 8 4 9 2 3 7 0 9 9 1 5 8 5 1 9 5 6 7 9 1 4 0 6 2 6 4 7 9 5 5 3 8 1 9 5 6
3 5 0 2 9 3 0 8 6 0 3 3 5 6 3 2 0 2 3 0 2 6 3 4 4 1 5 6 7 1 1 3 2 4 7 2 7
3 8 6 4 1 4 3 9 9 5 1 7 5 8 2]
我們使用了10個簇中心,也就是說豪诲,現(xiàn)在每個點(diǎn)都被分配了0到9之間的一個數(shù)字顶捷。我們可以將其看作是10個分量表示的數(shù)據(jù)(我們有10個新特征),只有表示該店對應(yīng)的簇中心那個特征不為0屎篱,其他特征均為0.利用這個10維表示服赎,現(xiàn)在可以用線性模型來劃分兩個半月形态辛,而利用原始的兩個特征是不可能做到這一點(diǎn)起惕。將到每個簇中心的距離作為特征,還可以得到一種表現(xiàn)力更強(qiáng)的數(shù)據(jù)表示廷支∏厥浚可以利用kmeans的transform方法來完成這一點(diǎn):
distance_features=kmeans.transform(X)
print("Distance feature shape:{}".format(distance_features.shape))
print("Distance features:{}".format(distance_features))
Distance feature shape:(200, 10)
Distance features:[[0.9220768 1.46553151 1.13956805 ... 1.16559918 1.03852189 0.23340263]
[1.14159679 2.51721597 0.1199124 ... 0.70700803 2.20414144 0.98271691]
[0.78786246 0.77354687 1.74914157 ... 1.97061341 0.71561277 0.94399739]
...
[0.44639122 1.10631579 1.48991975 ... 1.79125448 1.03195812 0.81205971]
[1.38951924 0.79790385 1.98056306 ... 1.97788956 0.23892095 1.05774337]
[1.14920754 2.4536383 0.04506731 ... 0.57163262 2.11331394 0.88166689]]
k均值是非常流行的聚類算法缺厉,因為它不僅相對容易理解和實現(xiàn),而且運(yùn)行速度也相對較快隧土。k均值可以輕松擴(kuò)展到大型數(shù)據(jù)集提针,scikit-learn甚至在MiniBatchMeans類中包含了一種更具可擴(kuò)展性的變體,可以處理非常大的數(shù)據(jù)集曹傀。
k均值的缺點(diǎn)之一在于辐脖,它依賴于隨機(jī)初始化,也就是說卖毁,算法的輸出依賴于隨機(jī)種子揖曾。默認(rèn)情況下,scikit-learn用10種不同的隨機(jī)初始化將算法運(yùn)行10次亥啦,并返回最佳結(jié)果炭剪。k均值還有一個缺點(diǎn),就是對簇形狀的假設(shè)的約束性較強(qiáng)翔脱,而且還要求指定所要尋找的簇的個數(shù) (在現(xiàn)實世界的應(yīng)用中可能并不知道這個數(shù)字)奴拦。
凝聚聚類
凝聚聚類(agglomerative clustering)指的是許多基于相同原則構(gòu)建的聚類算法,這一原則是:算法首先聲明每個點(diǎn)是自己的簇届吁,然后合并兩個最相似的簇错妖,直到滿足某種停止準(zhǔn)則為止。scikit-learn中實現(xiàn)的停止準(zhǔn)則是簇的個數(shù)疚沐,因此相似的簇被合并暂氯,直到僅剩下指定個數(shù)的簇。還有一些鏈接(linkage)準(zhǔn)則亮蛔,規(guī)定如何度量“最相似的簇”痴施。這種度量總是定義在兩個現(xiàn)有的簇之間。
scikit-learn中實現(xiàn)了以下三種選項。
ward:默認(rèn)選項辣吃。ward挑選兩個簇來合并动遭,使得所有簇中方差增加最小。這通常會得到大小差不多相等的簇神得。
avearge:average鏈接將簇中所有點(diǎn)之間平均距離最小的兩個簇合并厘惦。
complete:complete鏈接(也稱為最大鏈接)將簇中點(diǎn)之間最大距離最小的兩個簇合并。
ward適用于大多數(shù)數(shù)據(jù)集哩簿。如果簇中的成員個數(shù)非常不同(比如其中一個比其他所有都大得多)宵蕉,那么average或complete可能效果更好。
mglearn.plots.plot_agglomerative_algorithm()
我們來看一下凝聚聚類對上面的三簇數(shù)據(jù)的效果如何节榜。由于算法的工作原理国裳,凝聚聚類不能對新數(shù)據(jù)點(diǎn)做出預(yù)測。因此AgglomerativeClustering沒有predict方法全跨。為了構(gòu)造模型并得到訓(xùn)練集上簇的成員關(guān)系∫谒欤可以改用fit_predict方法浓若,結(jié)果如下:
from sklearn.cluster import AgglomerativeClustering
X,y=make_blobs(random_state=1)
agg=AgglomerativeClustering(n_clusters=3)
assignment=agg.fit_predict(X)
mglearn.discrete_scatter(X[:,0],X[:,1],assignment)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
凝聚聚類算法完美地完成了聚類。雖然凝聚聚類的sikit-learn實現(xiàn)需要你指定希望算法找到的簇的個數(shù)蛇数,但凝聚聚類方法為選擇正確的個數(shù)提供了一些幫助挪钓。
層次聚類與樹狀圖
凝聚聚類生成了所謂的層次聚類(hierachical clustering)。聚類過程迭代進(jìn)行耳舅,每個點(diǎn)從一個單點(diǎn)簇變?yōu)閷儆谧罱K的某個簇碌上。每個中間步驟都提供了數(shù)據(jù)的一種聚類(簇的個數(shù)也不相同)。有時候浦徊,同時查看所有可能的聚類是有幫助的馏予。如下的例子疊加顯示了所有可能的聚類,主要有助于深入了解每個簇如何分解為較小的簇:
mglearn.plots.plot_agglomerative()
雖然這種可視化為層次聚類提供了非常詳細(xì)地視圖盔性,但它依賴于數(shù)據(jù)的二維性質(zhì)霞丧,因此不能用于具有兩個以上特征的數(shù)據(jù)集。但還有另一個將層次聚類可視化的工具冕香,叫作樹狀圖(dendrogram),它可以處理多維數(shù)據(jù)集蛹尝。
借助Scipy可以繪制樹狀圖:
from scipy.cluster.hierarchy import dendrogram,ward
X,y=make_blobs(random_state=0,n_samples=12)
# 將ward聚類應(yīng)用于數(shù)據(jù)數(shù)組X
# Scipy的ward函數(shù)返回一個數(shù)組,指定執(zhí)行凝聚聚類時跨越的距離
linkage_array=ward(X)
# 現(xiàn)在為包含簇之間距離的linkage_array繪制樹狀圖
dendrogram(linkage_array)
# 在樹中標(biāo)記劃分成兩個簇或三個簇的位置
ax=plt.gca()
bounds=ax.get_xbound()
ax.plot(bounds,[7.25,7.25],'--',c='k')
ax.plot(bounds,[4,4],'--',c='k')
ax.text(bounds[1],7.25,' two clusters',va='center',fontdict={'size':15})
ax.text(bounds[1],4,' three clusters',va='center',fontdict={'size':15})
plt.xlabel("Sample Index")
plt.ylabel("Cluster distance")
DBSCAN
DBSCAN(density-based spatial clustering of applications with noise,即“具有噪聲的基于密度的空間聚類應(yīng)用”)悉尾。DBSCAN的主要優(yōu)點(diǎn)是它不需要用戶先驗地設(shè)置簇的個數(shù)突那,可以劃分具有復(fù)雜形狀的簇,還可以找出不屬于任何簇的點(diǎn)构眯。DBSCAN比凝聚聚類和k均值稍慢愕难,但仍可以擴(kuò)展到相對較大的數(shù)據(jù)集。
DBSCAN的原理是識別特征空間的“擁擠”區(qū)域中的點(diǎn),在這些區(qū)域中許多數(shù)據(jù)點(diǎn)靠近在一起务漩。這些區(qū)域被稱為特征空間中的密集(dense)區(qū)域拄衰。DBSCAN背后的思想是,簇形成數(shù)據(jù)的密集區(qū)域饵骨,并由相對較空的區(qū)域隔開翘悉。
在密集區(qū)域內(nèi)的點(diǎn)被稱為核心樣本(core sample,或核心點(diǎn)),它們的定義如下居触。DBSCAN有兩個參數(shù):min_samples和eps妖混。如果在距一個給定數(shù)據(jù)點(diǎn)eps的距離內(nèi)至少有min_samples個數(shù)據(jù)點(diǎn),那么這個數(shù)據(jù)點(diǎn)就是核心樣本轮洋。DBSCAN將彼此距離小于eps的核心樣本放到同一個簇中制市。
我們將DBSCAN應(yīng)用于演示凝聚聚類的模擬數(shù)據(jù)集。與凝聚聚類類似弊予,DBSCAN也不允許對新的測試數(shù)據(jù)進(jìn)行預(yù)測祥楣,所以我們將使用fit_predict方法來執(zhí)行聚類并返回簇標(biāo)簽。
from sklearn.cluster import DBSCAN
X,y=make_blobs(random_state=0,n_samples=12)
dbscan=DBSCAN()
clusters=dbscan.fit_predict(X)
print("Cluster memberships:\n{}".format(clusters))
Cluster memberships:
[-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1]
所有數(shù)據(jù)點(diǎn)都被分配了標(biāo)簽-1汉柒,這代表噪聲误褪。這是eps和min_samples默認(rèn)參數(shù)設(shè)置的結(jié)果,對于小型的玩具數(shù)據(jù)集并沒有調(diào)節(jié)這些參數(shù)碾褂。min_samples和eps取不同值時的簇分類如下所示兽间,其可視化結(jié)果如下圖:
mglearn.plots.plot_dbscan()
min_samples: 2 eps: 1.000000 cluster: [-1 0 0 -1 0 -1 1 1 0 1 -1 -1]
min_samples: 2 eps: 1.500000 cluster: [0 1 1 1 1 0 2 2 1 2 2 0]
min_samples: 2 eps: 2.000000 cluster: [0 1 1 1 1 0 0 0 1 0 0 0]
min_samples: 2 eps: 3.000000 cluster: [0 0 0 0 0 0 0 0 0 0 0 0]
min_samples: 3 eps: 1.000000 cluster: [-1 0 0 -1 0 -1 1 1 0 1 -1 -1]
min_samples: 3 eps: 1.500000 cluster: [0 1 1 1 1 0 2 2 1 2 2 0]
min_samples: 3 eps: 2.000000 cluster: [0 1 1 1 1 0 0 0 1 0 0 0]
min_samples: 3 eps: 3.000000 cluster: [0 0 0 0 0 0 0 0 0 0 0 0]
min_samples: 5 eps: 1.000000 cluster: [-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1]
min_samples: 5 eps: 1.500000 cluster: [-1 0 0 0 0 -1 -1 -1 0 -1 -1 -1]
min_samples: 5 eps: 2.000000 cluster: [-1 0 0 0 0 -1 -1 -1 0 -1 -1 -1]
min_samples: 5 eps: 3.000000 cluster: [0 0 0 0 0 0 0 0 0 0 0 0]
在上述圖中,屬于簇的點(diǎn)是實心的正塌,而噪聲點(diǎn)則顯示為空心的嘀略。核心樣本顯示為較大的標(biāo)記,而邊界點(diǎn)則顯示為較小的標(biāo)記乓诽。增大eps(在圖中從左到右)帜羊,更多的點(diǎn)會被包含在一個簇中。這讓簇變大问裕,但可能也會導(dǎo)致多個簇合并成一個逮壁。增大min_samples(在圖中從上到下),核心點(diǎn)會變的更少粮宛,更多的點(diǎn)被標(biāo)記為噪聲窥淆。
參數(shù)eps在某種程度上更加重要,因為它決定了點(diǎn)與點(diǎn)之間“接近”的含義巍杈。將eps設(shè)置得非常小忧饭,意味著沒有點(diǎn)是核心樣本,可能會導(dǎo)致所有點(diǎn)都被標(biāo)記為噪聲筷畦。將eps設(shè)置得非常大词裤,可能會導(dǎo)致所有點(diǎn)形成單個簇刺洒。
設(shè)置min_samples主要是為了判斷稀疏區(qū)域內(nèi)的點(diǎn)被標(biāo)記為異常值還是形成自己的簇。如果增大min_samples吼砂,任何一個包含少于min_samples個樣本的簇現(xiàn)在將被標(biāo)記為噪聲逆航。因此,min_samples決定簇的最小尺寸渔肩。在上圖中eps=1.5時因俐,從min_samples=3到min_samples=5,可以清楚地看到這一點(diǎn)。min_samples=3時有三個簇:一個包含4個點(diǎn)周偎,一個包含5個點(diǎn)抹剩,一個包含3個點(diǎn)。min_samples=5時蓉坎,兩個較小的簇(分別包含3個點(diǎn)和4個點(diǎn))現(xiàn)在都被標(biāo)記為噪聲澳眷,只保留包含5個樣本的簇。
雖然DBSCAN不需要顯示地設(shè)置簇的個數(shù)蛉艾,但設(shè)置eps可以隱式地控制找到的簇的個數(shù)钳踊。使用StandardScaler或MinMaxScaler對數(shù)據(jù)進(jìn)行縮放之后,有時會更容易找到eps的較好取值勿侯,因為使用這些縮放技術(shù)將確保所有特征具有相似的范圍箍土。
下面將展示在two_moons數(shù)據(jù)集上運(yùn)行DBSCAN的結(jié)果。利用默認(rèn)設(shè)置罐监,算法找到了兩個半圓形并將其分開:
X,y=make_moons(n_samples=200,noise=0.05,random_state=0)
# 將數(shù)據(jù)縮放承平均值為0,方差為1
scaler=StandardScaler()
scaler.fit(X)
X_scaled=scaler.transform(X)
dbscan=DBSCAN()
clusters=dbscan.fit_predict(X_scaled)
# 繪制簇分配
plt.scatter(X_scaled[:,0],X_scaled[:,1],c=clusters,cmap=mglearn.cm2,s=60)
plt.xlabel("Feature 0")
plt.ylabel("Feature ")
由于算法找到了我們想要的簇的個數(shù)(2個)瞒爬,因此參數(shù)設(shè)置的效果似乎很好弓柱。如果將eps減小到0.2(默認(rèn)值為0.5),我們將會得到8個簇侧但,這顯然太多了矢空。將eps增大到0.7則會導(dǎo)致只有一個簇。
在使用DBSCAN時禀横,你需要謹(jǐn)慎處理返回的簇分配屁药,如果使用簇標(biāo)簽對另一個數(shù)據(jù)進(jìn)行索引,那么使用-1表示噪聲可能會產(chǎn)生意料之外的結(jié)果柏锄。
聚類算法的對比與評估
在應(yīng)用聚類算法時酿箭,其挑戰(zhàn)之一就是很難評估一個算法的效果好壞,也很難比較不同算法的結(jié)果趾娃。在討論完k均值缭嫡、凝聚聚類和DBSCAN背后的算法之后,下面我們將在一些現(xiàn)實世界的數(shù)據(jù)集上比較它們抬闷。
1.用真實值評估聚類
有一些指標(biāo)可用于評估聚類算法相對于真實聚類的結(jié)果妇蛀,其中最重要的是調(diào)整rand指數(shù)(adjusted rand index,ARI)和歸一化互信息(normalized mutual information,NMI),二者都給出了定量的度量耕突,其最佳值為1,0表示不相關(guān)的聚類(雖然ARI可以去負(fù)值)。
下面我們使用ARI來比較k均值评架,凝聚聚類和DBSCAN算法眷茁。為了對比,我們還添加了將點(diǎn)隨機(jī)分配到兩個簇中的圖像:
from sklearn.metrics.cluster import adjusted_rand_score
X,y=make_moons(n_samples=200,noise=0.05,random_state=0)
# 將數(shù)據(jù)縮放成平均值為0纵诞,方差為1
scaler=StandardScaler()
scaler.fit(X)
X_scaled=scaler.transform(X)
fig,axes=plt.subplots(1,4,figsize=(15,3),subplot_kw={'xticks':(),'yticks':()})
# 列出要使用的算法
algorithms=[KMeans(n_clusters=2),AgglomerativeClustering(n_clusters=2),DBSCAN()]
# 創(chuàng)建一個隨機(jī)的簇分配上祈,作為參考
random_state=np.random.RandomState(seed=0)
random_clusters=random_state.randint(low=0,high=2,size=len(X))
# 繪制隨機(jī)分配
axes[0].scatter(X_scaled[:,0],X_scaled[:,1],c=random_clusters,cmap=mglearn.cm3,s=60)
axes[0].set_title("Random assignment-ARI:{:.2f}".format(adjusted_rand_score(y,random_clusters)))
for ax,algorithm in zip(axes[1:],algorithms):
# 繪制簇分配和簇中心
clusters=algorithm.fit_predict(X_scaled)
ax.scatter(X_scaled[:,0],X_scaled[:,1],c=clusters,cmap=mglearn.cm3,s=60)
ax.set_title("{} - ARI:{:.2f}".format(algorithm.__class__.__name__,adjusted_rand_score(y,clusters)))
調(diào)整rand指數(shù)給出了符合直覺的結(jié)果,隨機(jī)簇分配的分?jǐn)?shù)為0挣磨,而DBSCAN(完美地找到了期望中的聚類)的分?jǐn)?shù)為1雇逞。
用這種方式評估聚類時,一個常見的錯誤是使用accuracy_score而不是adjusted_rand_score茁裙、normalized_mutual_info_score或其他聚類指標(biāo)塘砸。使用精度的問題在于,它要求分配的簇標(biāo)簽與真實值完全匹配晤锥。但簇標(biāo)簽本身毫無意義——唯一重要的是哪些點(diǎn)位于同一個簇中掉蔬。
from sklearn.metrics import accuracy_score
# 這兩種點(diǎn)標(biāo)簽對應(yīng)于相同的聚類
clusters1=[0,0,1,1,0]
clusters2=[1,1,0,0,1]
# 精度為0,因為二者標(biāo)簽完全不同
print("Accuracy:{:.2f}".format(accuracy_score(clusters1,clusters2)))
# 調(diào)整rand分?jǐn)?shù)為1矾瘾,因為二者聚類完全相同
print("ARI:{:.2f}".format(adjusted_rand_score(clusters1,clusters2)))
Accuracy:0.00
ARI:1.00
2.在沒有真實值的情況下評估聚類
在實踐中女轿,通常沒有真實值來比較結(jié)果。如果我們知道了數(shù)據(jù)的正確聚類壕翩,那么可以使用這一信息構(gòu)建一個監(jiān)督模型(比如分類器)蛉迹。因此,使用類似ARI和NMI的指標(biāo)通常僅有助于開發(fā)算法放妈,但對評估應(yīng)用是否成功沒有幫助北救。
在一些聚類的評分指標(biāo)不需要真實值,比如輪廓系數(shù)(silhouette coeffient)芜抒。但它們在實踐中的效果并不好珍策。輪廓分?jǐn)?shù)計算一個簇的緊致度,其值越大越好宅倒,最高分?jǐn)?shù)為1攘宙。雖然緊致的簇很好,但緊致度不允許復(fù)雜的形狀拐迁。
下面是一個例子蹭劈,利用輪廓分?jǐn)?shù)在two_moons數(shù)據(jù)集上比較k均值、凝聚聚類和DBSCAN:
from sklearn.metrics.cluster import silhouette_score
X,y=make_moons(n_samples=200,noise=0.05,random_state=0)
# 將數(shù)據(jù)縮放成平均值為0线召、方差為1
scaler=StandardScaler()
scaler.fit(X)
X_scaled=scaler.transform(X)
fig,axes=plt.subplots(1,4,figsize=(15,3),subplot_kw={'xticks':(),'yticks':()})
# 創(chuàng)建一個隨機(jī)的簇分配链方,作為參考
random_state=np.random.RandomState(seed=0)
random_clusters=random_state.randint(low=0,high=2,size=len(X))
# 繪制隨機(jī)分配
axes[0].scatter(X_scaled[:,0],X_scaled[:,1],c=random_clusters,cmap=mglearn.cm3,s=60)
axes[0].set_title("Random assignment:{:.2f}".format(silhouette_score(X_scaled,random_clusters)))
algorithms=[KMeans(n_clusters=2),AgglomerativeClustering(n_clusters=2),DBSCAN()]
for ax,algorithm in zip(axes[1:],algorithms):
clusters=algorithm.fit_predict(X_scaled)
# 繪制簇分配和簇中心
ax.scatter(X_scaled[:,0],X_scaled[:,1],c=clusters,cmap=mglearn.cm3,s=60)
ax.set_title("{}:{:.2f}".format(algorithm.__class__.__name__,silhouette_score(X_scaled,clusters)))
如上圖所示,k均值的輪廓分?jǐn)?shù)最高灶搜,盡管我們可能更喜歡DBSCAN的結(jié)果祟蚀。對于評估聚類工窍,稍好的策略是使用基于魯棒性的(robustness-based)聚類指標(biāo),并對結(jié)果進(jìn)行比較前酿。其思想是患雏,如果許多算法和許多數(shù)據(jù)擾動返回相同的結(jié)果,那么它很可能是可信的罢维。不幸的是淹仑,sickit-learn還沒有實現(xiàn)這一策略。
在實踐中肺孵,即使我們得到一個魯棒性很好的聚類或非常高的輪廓分?jǐn)?shù)匀借,但仍然不知道聚類中是否有任何語義含義,或者聚類是否反映了數(shù)據(jù)中我們感興趣的某個方面平窘。要想知道聚類是否對應(yīng)于我們感興趣的內(nèi)容吓肋,唯一的辦法就是對簇進(jìn)行人工分析。
3.在人臉數(shù)據(jù)集上比較算法
我們將k均值瑰艘、DBSCAN和凝聚聚類算法應(yīng)用于Wild數(shù)據(jù)集中的Labeled Faces,并查看他們是否找到了有趣的結(jié)構(gòu)是鬼。我們將使用數(shù)據(jù)的特征臉表示,它由包含100個成分的PCA(whiten=True)生成:
# 從lfw數(shù)據(jù)中提取特征臉紫新,并對數(shù)據(jù)進(jìn)行變換
from sklearn.decomposition import PCA
pca=PCA(n_components=100,whiten=True,random_state=0)
pca.fit_transform(X_people)
X_pca=pca.transform(X_people)
用DBSCAN分析人臉數(shù)據(jù)集均蜜,看能否找到類似的簇:
dbscan=DBSCAN()
labels=dbscan.fit_predict(X_pca)
print("Unique labels:{}".format(np.unique(labels)))
Unique labels:[-1]
所有返回的標(biāo)簽都是-1,因此所有數(shù)據(jù)都被DBSCAN標(biāo)記為“噪聲”芒率。我們可以改變兩個參數(shù)來改進(jìn)這一點(diǎn):第一囤耳,我們可以增大eps,從而擴(kuò)展每個點(diǎn)的領(lǐng)域;第二偶芍,我們可以減小min_samples紫皇,從而將更小的點(diǎn)組視為簇。我們先嘗試改變min_samples:
dbscan=DBSCAN(min_samples=3)
labels=dbscan.fit_predict(X_pca)
print("Unique labels:{}".format(np.unique(labels)))
Unique labels:[-1]
即使僅考慮由三個點(diǎn)構(gòu)成的組腋寨,所有點(diǎn)也被標(biāo)記為噪聲。因此我們需要增大eps:
dbscan=DBSCAN(min_samples=3,eps=15)
labels=dbscan.fit_predict(X_pca)
print("Unique labels:{}".format(np.unique(labels)))
Unique labels:[-1 0]
使用更大的eps(其值為15)化焕,我們只得到了單一簇和噪聲點(diǎn)萄窜。我們可以利用這一結(jié)果找出“噪聲”相對于其他數(shù)據(jù)的形狀。為了進(jìn)一步理解發(fā)生的事情撒桨,我們查看有多少點(diǎn)是噪聲查刻,有多少點(diǎn)在簇內(nèi):
# 計算所有簇中的點(diǎn)數(shù)和噪聲中的的點(diǎn)數(shù)。
# bincount不允許負(fù)值凤类,所以我們需要加1
# 結(jié)果中第一個數(shù)字對應(yīng)于噪聲點(diǎn)穗泵。
print("Number of points per cluster:{}".format(np.bincount(labels+1)))
Number of points per cluster:[ 30 1598]
噪聲點(diǎn)非常少——只有27個,因此我們可以查看所有的噪聲點(diǎn):
noise=X_people[labels==-1]
fig,axes=plt.subplots(3,9,subplot_kw={'xticks':(),'yticks':()},figsize=(12,4))
for image,ax in zip(noise,axes.ravel()):
ax.imshow(image.reshape(image_shape),vmin=0,vmax=1)
觀察這些圖像不難發(fā)現(xiàn)谜疤,這些圖像都包含奇怪的角度佃延,或者太近或太寬的剪切等现诀。
這種類型的分析——嘗試找出“奇怪的那一個”——被稱為異常值檢測(outlier detection)。如果這是一個真實的應(yīng)用履肃,那么我們可能會嘗試更好地剪切圖像仔沿,以得到更加均勻的數(shù)據(jù)。對于照片中的人有時戴著帽子尺棋、喝水或在面前舉著某物封锉,我們能做的事情很少。但需要知道它們是數(shù)據(jù)中存在的問題膘螟,我們應(yīng)用任何算法都需要解決這些問題成福。
如果我們需要找到更有趣的簇,而不是一個非常大的簇荆残,那么需要將eps設(shè)置得更小奴艾,取值在15和0.5(默認(rèn)值)之間。我們來看一下eps不同取值對應(yīng)的結(jié)果:
for eps in [1,3,5,7,9,11,13]:
print("\neps={}".format(eps))
dbscan=DBSCAN(eps=eps,min_samples=3)
lables=dbscan.fit_predict(X_pca)
print("Clusters present:{}".format(np.unique(labels)))
print("Cluster sizes:{}".format(np.bincount(labels+1)))
eps=1
Clusters present:[-1 0]
Cluster sizes:[ 30 1598]
eps=3
Clusters present:[-1 0]
Cluster sizes:[ 30 1598]
eps=5
Clusters present:[-1 0]
Cluster sizes:[ 30 1598]
eps=7
Clusters present:[-1 0]
Cluster sizes:[ 30 1598]
eps=9
Clusters present:[-1 0]
Cluster sizes:[ 30 1598]
eps=11
Clusters present:[-1 0]
Cluster sizes:[ 30 1598]
eps=13
Clusters present:[-1 0]
Cluster sizes:[ 30 1598]
我們再以eps=7來可視化這一聚類:
dbscan=DBSCAN(min_samples=3,eps=7)
labels=dbscan.fit_predict(X_pca)
for cluster in range(max(labels)+1):
mask=labels==cluster
n_images=np.sum(mask)
fig,axes=plt.subplots(1,n_images,figsize=(n_images*1.5,4),subplot_kw={'xticks':(),'yticks':()})
for image,label,ax in zip(X_people[mask],y_people[mask],axes):
ax.imshow(image.reshape(image_shape),vmin=0,vmax=1)
ax.set_title(people.target_names[label].split()[-1])
用k均值分析人臉數(shù)據(jù)集
使用k均值我們可以將簇的數(shù)量設(shè)置為數(shù)據(jù)集中的已知人數(shù)脊阴,雖然無無監(jiān)督聚類算法不太可能完全找到它們握侧。相反,我們可以首先設(shè)置一個比較小的簇的數(shù)量嘿期,比如10個品擎,這樣我們可以分析每個簇:
# 用k均值提取簇
km=KMeans(n_clusters=10,random_state=0)
labels_km=km.fit_predict(X_pca)
print(labels_km)
print("Cluster sizes k-means:{}".format(np.bincount(labels_km)))
[9 3 1 ... 5 9 2]
Cluster sizes k-means:[276 288 163 148 133 116 71 51 139 243]
k均值聚類將數(shù)據(jù)劃分為大小相似的簇,其大小在64和386之間备徐。這與DBSCAN的結(jié)果非常不同萄传。
我們可以通過將簇中心可視化來進(jìn)一步分析k均值的結(jié)果。由于我們是在PCA生成的表示中進(jìn)行聚類蜜猾,因此我們需要使用pca.inverse_transform將簇中心旋轉(zhuǎn)回到原始空間并可視化:
fig,axes=plt.subplots(2,5,subplot_kw={'xticks':(),'yticks':()},figsize=(12,4))
for center,ax in zip(km.cluster_centers_,axes.ravel()):
ax.imshow(pca.inverse_transform(center).reshape(image_shape),vmin=0,vmax=1)
k均值找到的簇中心是非常平滑的人臉秀菱。這并不奇怪,因為每個簇中心都是64到386張人臉圖像的平均蹭睡。使用降維的PCA表示衍菱,可以增加圖像的平滑度。聚類似乎捕捉到人臉的不同方向肩豁、不同表情(第三個簇中心似乎顯示的是一張笑臉)脊串,以及是否有襯衫領(lǐng)子(見倒數(shù)第二個簇中心)。
mglearn.plots.plot_kmeans_faces(km,pca,X_pca,X_people,y_people,people.target_names)
上圖為k均值為每個簇找到的樣本圖像——簇中心在最左邊清钥,然后是五個距中心最近的點(diǎn)琼锋,然后是五個距該簇距中心最遠(yuǎn)的點(diǎn)盯漂。
用凝聚聚類分析人臉數(shù)據(jù)集
下面我們來看一下凝聚聚類的結(jié)果:
# 用ward凝聚聚類提取簇
agglomerative=AgglomerativeClustering(n_clusters=10)
labels_agg=agglomerative.fit_predict(X_pca)
print("Cluster sizes agglomerative clustering:{}".format(np.bincount(labels_agg)))
Cluster size agglomerative clustering:[170 48 504 43 162 121 168 22 56 334]
凝聚聚類生成的也是大小相近的簇夺巩,其大小在26和623之間界酒。這比k均值生成的簇更不均勻裕坊,但比DBSCAN生成的簇要更加均勻狞甚。
我們可以通過計算ARI來度量凝聚聚類和k均值給出的兩種數(shù)據(jù)劃分是否相似:
print("ARI:{:.2f}".format(adjusted_rand_score(labels_agg,labels_km)))
ARI:0.06
ARI只有0.13虫给,說明labels_agg和labels_km這兩種聚類的共同點(diǎn)很少交煞。這并不奇怪括堤,原因在于以下事實:對于k均值,遠(yuǎn)離簇中心的點(diǎn)似乎沒有什么共同點(diǎn)叉谜。
下面旗吁,我們繪制樹狀圖。我們將限制圖中樹的深度停局,因為如果分支到2063個數(shù)據(jù)點(diǎn)很钓,圖像將密密麻麻無法閱讀:
linkage_array=ward(X_pca)
# 現(xiàn)在我們?yōu)榘刂g距離的linkage_array繪制樹狀圖
plt.figure(figsize=(20,5))
dendrogram(linkage_array,p=7,truncate_mode='level',no_labels=True)
plt.xlabel("Sample index")
plt.ylabel("Cluster distance")
要想創(chuàng)建10個簇,我們在頂部有10條豎線的位置將樹橫切董栽。在上圖所示的玩具數(shù)據(jù)的樹狀圖中码倦,你可以從分支的長度中看出,兩個或三個簇可以很好地劃分?jǐn)?shù)據(jù)锭碳。對于人臉數(shù)據(jù)而言袁稽,似乎沒有非常自然的切割點(diǎn)。有一些分支代表更為不同的組擒抛,但似乎沒有一個特別合適的簇的數(shù)量推汽。則并不奇怪,因為DBSCAN的結(jié)果是試圖將所有的點(diǎn)都聚類在一起歧沪。
我們將10個簇可視化歹撒,正如之前對k均值所做的那樣。請注意诊胞,在凝聚聚類中沒有簇中心的概念(雖然我們計算平均值)暖夭,我們只是給出了每個簇的前幾個點(diǎn)。我們在第一張圖像的左側(cè)給出了每個簇中的點(diǎn)的數(shù)量:
n_clusters=10
for cluster in range(n_clusters):
mask=labels_agg==cluster
fig,axes=plt.subplots(1,10,subplot_kw={'xticks':(),'yticks':()},figsize=(15,8))
axes[0].set_ylabel(np.sum(mask))
for image,label,asdf,ax in zip(X_people[mask],y_people[mask],labels_agg[mask],axes):
ax.imshow(image.reshape(image_shape),vmin=0,vmax=1)
ax.set_title(people.target_names[label].split()[-1],fontdict={'fontsize':9})
上圖演示生成的簇中的隨機(jī)圖像——每一行對應(yīng)一個簇撵孤,左側(cè)的數(shù)字表示每個簇中圖像的數(shù)量迈着。
雖然某些簇似乎具有語義上的主題,但許多簇都太大而實際上很難是均勻的邪码。為了得到更加均勻的簇裕菠,我們可以再次運(yùn)行算法,這次使用40個簇闭专,并挑選承一些特別有趣的簇:
# 用ward凝聚聚類提取簇
agglomerative=AgglomerativeClustering(n_clusters=40)
labels_agg=agglomerative.fit_predict(X_pca)
print("Cluster sizes agglomerative clustering:{}".format(np.bincount(labels_agg)))
n_clusters=10
for cluster in [10,13,19,22,36]:#手動挑選“有趣的”簇
mask=labels_agg==cluster
fig,axes=plt.subplots(1,15,subplot_kw={'xticks':(),'yticks':()},figsize=(15,8))
cluster_size=np.sum(mask)
axes[0].set_ylabel("#{}:{}".format(cluster,cluster_size))
for image,label,asdf,ax in zip(X_people[mask],y_people[mask],labels_agg[mask],axes):
ax.imshow(image.reshape(image_shape),vmin=0,vmax=1)
ax.set_title(people.target_names[label].split()[-1],fontdict={'fontsize':9})
for i in range(cluster_size,15):
axes[i].set_visible(False)
上圖為將簇的數(shù)量設(shè)置為40時奴潘,從凝聚聚類找到的簇中挑選的圖像——左側(cè)文本表示簇的編號和簇中的點(diǎn)的總數(shù)。
聚類方法小結(jié)
聚類的應(yīng)用與評估是一個非常定性的過程喻圃,通常在數(shù)據(jù)分析的探索階段很有幫助。每種算法的優(yōu)點(diǎn)稍有不同粪滤。k均值可以用簇的平均值來表示簇斧拍。它還可以被看作是一種分解方法,每個數(shù)據(jù)點(diǎn)都由其簇中心表示杖小。DBSCAN可以檢測到?jīng)]有分配任何簇的“噪聲點(diǎn)”肆汹,還可以幫助自動判斷簇的數(shù)量愚墓。與其他兩種方法不同,它允許簇具有復(fù)雜的形狀昂勉。DBSCAN有時會生成大小差別很大的簇浪册,這可能是它的優(yōu)點(diǎn),也可能是缺點(diǎn)岗照。凝聚聚類可以提供數(shù)據(jù)的可能劃分的整個層次結(jié)果村象,可以通過樹狀圖輕松查看