1. 介紹
每當我在網站上遇到推薦引擎時,我都迫不及待地將其分解並理解它的底層是如何工作的。我想這是成為數據科學家的最重要的品質之一!
這些系統真正令我著迷的是我們如何將類似的物品,產品和用戶組合在一起。這種分組或分段適用於各個行業。這就是聚類概念在數據科學中如此重要的原因。
聚類有助於我們以獨特的方式理解我們的數據 - 通過將事物分組。
在本文中,我們將全面介紹K-means聚類及其擴展。我們將研究聚類,它為什麼重要,它的應用,然後深入研究K-means聚類(包括如何在真實數據集上用Python實現它)。
2. 什麼是聚類?
讓我們用一個簡單的例子來解決問題。銀行希望向其客戶提供信用卡優惠。目前,他們查看每個客戶的詳細信息,並根據這些信息,決定應該向哪個客戶提供哪個優惠。
現在,該銀行可能擁有數百萬客戶。分別查看每個客戶的詳細信息然後做出決定是否有意義?當然沒有!這是一個手動過程,需要花費大量時間。那麼銀行可以做些什麼呢?一種選擇是將其客戶劃分為不同的組。例如,銀行可以根據客戶的收入對其進行分組:
銀行現在可以制定三種不同的策略或優惠,每組一個。在這裡,他們不必為個人客戶創建不同的策略,而只需制定3種策略。這將減少時間和人力。我上面顯示的組稱為簇(clusers),創建這些組的過程稱為聚類(clustering)。在形式上,我們可以說:
聚類是基於數據中的模式將整個數據劃分為組(也稱為簇)的過程。
你能猜出聚類是哪種類型的學習問題嗎?這是一個有監督還是無監督的學習問題嗎?
考慮一下,並利用我們剛才看到的例子。是的,聚類是一種無監督的學習問題!
2.1. 聚類為什麼是一個無監督學習問題?
假設你正在開展一個需要預測市場銷售的項目:
或者,你的任務是預測是否批准貸款的項目:
在上面提到的兩個例子中,我們有一個想要預測的固定的目標。在銷售預測問題中,我們必須根據outlet_size,outlet_location_type等預測Item_Outlet_Sales,並且在貸款審批問題中,我們必須根據Gender,Married,ApplicantIncome等預測Loan_Status。
因此,當我們根據一組給定的預測因子變量或自變量去預測一個目標變量時,這些問題被稱為監督學習問題。
現在,可能存在沒有預測目標變量的情況。
沒有任何固定目標變量的這些問題被稱為無監督學習問題。在這些問題中,我們只有自變量而沒有目標/因變量。
在聚類中,我們沒有預測目標變量。我們查看數據然後嘗試進行類似的觀察並形成不同的簇。因此,這是一個無監督的學習問題。
我們現在知道什麼是簇和聚類的概念。接下來,讓我們看一下在形成簇時我們必須考慮的這些簇的屬性。
2.2. 簇的屬性
還是以上面提到銀行業務為例子,為簡單起見,我們假設銀行只想利用客戶的收入和債務來進行細分。他們收集了客戶數據並使用散點圖對其進行可視化:
X軸代表客戶的收入,y軸代表債務的數額。在這裡,我們可以清楚地看到這些客戶可以分為4個不同的組,如下所示:
這就是聚類怎樣從數據創建組(簇)。銀行可以進一步使用這些組制定策略並為其客戶提供折扣。那麼讓我們來看看這些組的屬性。
屬性1
組中的所有數據點應該彼此相似。讓我用上面的例子來說明它:
如果特定組中的客戶彼此不相似,那麼他們的要求可能會有所不同,對吧?如果銀行給他們相同的報價,他們可能不喜歡它,對銀行可能會比較失望,因此效果並不理想。
同一組中擁有類似的數據點有助於銀行使用有針對性的營銷。你可以考慮日常生活中的類似示例,並思考聚類將如何(或已經)影響業務戰略。
屬性2
來自不同組的數據點應儘可能不同。如果你掌握了上述屬性,這將是直觀有意義的。讓我們再次使用相同的示例來理解此屬性:
你認為上面哪種是更好的簇結果?如果你看一下案例一:
紅色和藍色簇中的客戶彼此非常相似。紅色簇中的前四個點與藍色簇中的前兩個客戶具有相似的屬性,他們有高收入和高債務。在這裡,我們對它們進行了不同的聚類。然而,如果你看看案例二:
紅色簇與藍色簇中的客戶完全不同。紅色簇中的所有客戶都有高收入和高負債,藍色簇中的客戶收入高,債務價。顯然,在這種情況下,我們有更好的客戶群。
因此,來自不同簇的數據點應儘可能彼此不同,以形成更有意義的簇。
到目前為止,我們已經了解了聚類是什麼以及聚類的不同屬性。但為什麼我們需要聚類?讓我們在下一節中清楚解答這個疑問,看一下簇的一些應用。
2.3. 聚類在真實世界中的應用
聚類是業界廣泛使用的技術。它實際上被用於各個領域,從銀行到推薦引擎,文檔聚類到圖像分割。
a. 客戶細分
我們之前介紹了這一點,聚類最常見的應用之一是客戶細分。但它不僅限於銀行業務,該戰略涉及各種功能,包括電信,電子商務,體育,廣告,銷售等。
b. 文檔聚類
這是聚類的另一種常見應用。假設你有多個文檔,並且需要將類似的文檔集中在一起。聚類可幫助我們對這些文檔進行分組,使類似文檔位於同一簇中。
c. 圖像分割
我們還可以使用聚類來執行圖像分割。在這裡,我們嘗試將圖像中的相似像素聚集在一起。我們可以應用聚類來創建在同一組中具有相似像素的各個簇。
你可以參考這篇文章[1],了解我們如何利用聚類進行圖像分割任務。
d. 推薦引擎
聚類也可用於推薦引擎。假設你想向朋友推薦歌曲。你可以查看該人喜歡的歌曲,然後使用聚類查找類似的歌曲,最後推薦最相似的歌曲。
還有更多的應用,我相信你已經想過了。你可以在下面的評論部分分享這些應用。接下來,讓我們看看我們如何評估我們聚類出來的簇。
2.4. 了解聚類的不同評估度量標準
聚類的主要目的不僅僅是創建簇,而是創建好的和有意義的簇。我們在下面的例子中看到了這一點:
在這裡,我們只使用了兩個特徵,因此我們很容易可視化並決定哪些簇更好。
不幸的是,這不是真實世界場景的工作方式,因為我們將擁有大量的特徵。讓我們再次看一下客戶細分示例,我們將擁有客戶收入,職業,性別,年齡等功能。將所有這些功能可視化並決定更好,更有意義的簇對我們來說是不可能的。
這是我們可以利用評估指標的地方。讓我們討論其中的一些,並了解我們如何使用它們來評估簇的質量。
a. Inertia
回想一下上面介紹的簇的第一個屬性。這就是Inertia評估。Inertia實際上計算簇內所有點到該簇的質心的距離的總和。
我們為所有簇計算這個總和,最終Inertia值是所有這些距離的總和。簇內的這個距離稱為簇內距離(intracluster distance.)。因此,Inertia為我們提供了簇內距離的總和:
現在,你認為一個好的簇的Inertia值應該是什麼?小的Inertia好還是大的Inertia好?我們希望同一簇中的點彼此相似,對吧?因此,它們之間的距離應儘可能小。
記住這一點,我們可以說Inertia越小,我們的聚類越好。
b. Dunn Index
我們現在知道Inertia試圖最小化簇內距離。它正試圖劃分更緊湊的簇。
讓我這樣說吧 - 如果一個簇的質心與該簇中的點之間的距離很小,則意味著這些點彼此更接近。因此,Inertia可確保滿足簇的第一個屬性。但它並不關心第二個屬性,不同的簇應儘可能彼此不同。
這就是Dunn Index可以用來評估的地方。
除了質心和點之間的距離,Dunn Index還考慮了兩個簇之間的距離。兩個不同簇的質心之間的距離稱為簇間距離(inter-cluster distance)。讓我們看看Dunn Index指數的公式:
Dunn Index是簇間距離的最小值與簇內距離的最大值之比。
我們希望最大化Dunn Index,Dunn Index的值越大,簇就越好。讓我們直觀理解下Dunn Index:
為了最大化Dunn Index的值,分子應該是最大的。在這裡,我們採用最小的簇間距離。因此,即使是最近的簇之間的距離也應該更大,這最終將確保簇彼此遠離。
此外,分母應該是最小的,以最大化Dunn Index。在這裡,我們採取最大的簇內距離。同樣,這裡的直覺也是一樣的。簇質心和點之間的最大距離總和應該最小,這最終將確保劃分的簇緊湊。
3. K-means聚類介紹
回想一下簇的第一個屬性,它表明簇中的點應該彼此相似。因此,我們的目標是最小化簇內點之間的距離。
有一種算法試圖通過它們的質心最小化簇中點的距離,那就是K-means聚類技術。
K-means是一種基於質心的算法,或基於距離的算法,我們計算將點分配給一個簇的距離。在K-means中,每個聚類都與一個質心相關聯。
K-means算法的主要目的是最小化點與它們各自的簇質心之間的距離之和。
現在讓我們舉個例子來了解K-means實際上是如何工作的:
我們有這8個點,我們想要應用K-means來為這些點劃分簇。下面將演示我們是怎樣做到的。
第一步:選擇簇的數目k
K-means的第一步是選擇簇的數目k。
第二步:從數據中選擇k個隨機點作為質心
接下來,我們為每個簇隨機選擇質心。假設我們想要有2個簇,所以k在這裡等於2。然後我們隨機選擇質心:
這裡,紅色和綠色圓圈代表這些簇的質心。
第三步:將所有點分配給到某個質心距離最近的簇
一旦我們初始化了質心,我們就將每個點分配給到某個質心距離最近的簇:
在這裡,你可以看到更接近紅點的點被分配給紅色簇,而更接近綠點的點被分配給綠色簇。
第四步:重新計算新形成的簇的質心
現在,一旦我們將所有點分配到任一簇中,下一步就是計算新形成的簇的質心:
在這裡,紅色和綠色叉號是新的質心。
第五步:重複第三步和第四步
然後我們重複第三步和第四步:
計算質心並基於它們與質心的距離將所有點分配給簇的步驟是單次迭代。但我們什麼時候應該停止這個過程?它不能永遠運行下去對吧?
停止K-means聚類的標準
基本上有三種停止標準可用於停止K-means算法:
新形成的簇的質心不會改變數據點保留在同一個簇中達到最大迭代次數如果新形成的簇的質心沒有變化,我們就可以停止算法。即使在多次迭代之後,所有簇都還是相同的質心,我們可以說該算法沒有學習任何新模式,並且它是停止訓練的標誌。
另一個明顯的跡象表明,在多次迭代訓練的之後,如果數據點仍然都在同一簇中,我們應該停止訓練過程。
最後,如果達到最大迭代次數,我們可以停止訓練。假設我們將迭代次數設置為100。在停止之前,該過程將重複100次迭代。
從零開始在Python中實現K-means聚類
是時候啟動我們的 Jupyter notebooks(或者你使用的任何一款IDE)!
我們將在大型市場銷售數據集上進行處理,你可以在此處下載[2]。
我建議你在此處[3]閱讀有關數據集和問題陳述的更多信息。這將幫助你可視化我們正在處理的工作(以及我們為什麼這樣做)。這是任何數據科學項目中的兩個非常重要的問題。
首先,導入所有必需的庫:
import pandas as pdimport numpy as npimport random as rdimport matplotlib.pyplot as plt現在,我們將讀取CSV文件並查看數據的前五行:
data = pd.read_csv('clustering.csv')data.head()
3對於本文,我們將僅從數據中獲取兩個變量:「LoanAmount」和「ApplicantIncome」。這樣也可以輕鬆地將步驟可視化。讓我們選擇這兩個變量並可視化數據點:
X = data[["LoanAmount","ApplicantIncome"]]#可視化plt.scatter(X["ApplicantIncome"],X["LoanAmount"],c='black')plt.xlabel('AnnualIncome')plt.ylabel('Loan Amount (In Thousands)')plt.show()
K-means的步驟1和2是關於選擇簇數(k)和為每個簇選擇隨機質心。我們將選擇3個簇,然後從數據中隨機選擇樣本作為質心:
#簇的個數K=3# 隨機選擇觀察值作為簇心Centroids = (X.sample(n=K))plt.scatter(X["ApplicantIncome"],X["LoanAmount"],c='black')plt.scatter(Centroids["ApplicantIncome"],Centroids["LoanAmount"],c='red')plt.xlabel('AnnualIncome')plt.ylabel('Loan Amount (In Thousands)')plt.show()
這裡,紅點代表每個簇的3個質心。請注意,我們是隨機選擇了這些點,因此每次運行此代碼時,你可能會得到不同的質心。
接下來,我們將定義一些條件來實現K-means聚類算法。我們先來看看代碼:
# 第三步:將所有點分配給到某個質心距離最近的簇# 第四步:重新計算新形成的簇的質心# 第五步:重複第三步和第四步diff = 1j=0while(diff!=0): XD=X i=1 for index1,row_c in Centroids.iterrows(): ED=[] for index2,row_d in XD.iterrows(): d1=(row_c["ApplicantIncome"]-row_d["ApplicantIncome"])**2 d2=(row_c["LoanAmount"]-row_d["LoanAmount"])**2 d=np.sqrt(d1+d2) ED.append(d) X[i]=ED i=i+1 C=[] for index,row in X.iterrows(): min_dist=row[1] pos=1 for i in range(K): if row[i+1] < min_dist: min_dist = row[i+1] pos=i+1 C.append(pos) X["Cluster"]=C Centroids_new = X.groupby(["Cluster"]).mean()[["LoanAmount","ApplicantIncome"]] if j == 0: diff=1 j=j+1 else: diff = (Centroids_new['LoanAmount'] - Centroids['LoanAmount']).sum() + (Centroids_new['ApplicantIncome'] - Centroids['ApplicantIncome']).sum() print(diff.sum()) Centroids = X.groupby(["Cluster"]).mean()[["LoanAmount","ApplicantIncome"]]
每次運行時,這些值可能會有所不同。在這裡,我們在兩次迭代後質心沒有變化時停止訓練。我們最初將diff定義為1並且在while循環內部,我們將此diff計算為前一次迭代中的質心與當前迭代之間的差異。
當這個差異為0時,我們停止訓練。現在讓我們看看我們得到的簇:
color=['blue','green','cyan']for k in range(K): data=X[X["Cluster"]==k+1] plt.scatter(data["ApplicantIncome"],data["LoanAmount"],c=color[k])plt.scatter(Centroids["ApplicantIncome"],Centroids["LoanAmount"],c='red')plt.xlabel('Income')plt.ylabel('Loan Amount (In Thousands)')plt.show()
在這裡,我們可以清楚地看到三個簇。紅點代表每個簇的質心。我希望你現在能清楚地了解K-means的工作原理。
但是,在某些情況下,此算法效果可能不是很好。讓我們來看看在使用K-means時你可能面臨的一些挑戰。
4. K-means聚類算法面臨的挑戰
使用K-means時我們面臨的普遍挑戰之一是簇的大小不同。假設我們有以下幾點:
與中心簇相比,左側和最右側的簇的點的數量比較少。現在,如果我們在這些點上應用K-means聚類,結果將是這樣的:
K-means的另一個挑戰是當原始點的密度不同時。比如下面這些原始點:
這裡,紅色簇中的點比較鬆散,而其餘簇中的點緊密地堆積在一起。現在,如果我們在這些點上應用K-means,我們將得到這樣的簇:
我們可以看到緊湊的點已分配給同一個簇。而實際鬆散分布但在同一簇中的點卻分配給不同的簇。效果並不理想,我們能做些什麼呢?
其中一個解決方案是使用更多的簇進行劃分。因此,在所有上述場景中,我們可以擁有更多的簇,而不是使用3個簇。也許設置k = 10可能會產生更有意義的簇。
還記得我們如何在K-means聚類中隨機初始化質心嗎?嗯,這也可能有問題,因為我們每次都可能得到不同的簇。因此,為了解決隨機初始化的這個問題,有一種稱為K-means ++的算法可用於為K-means選擇初始值或初始簇質心。
5. K-means ++為K-means聚類選擇初始簇質心
在某些情況下,如果簇的初始化不合適,K-means可能導致產生壞的簇。這就是K-means ++產生作用的地方。它指定了在使用標準K-means聚類算法向前推進之前初始化簇質心的過程。
使用K-means ++算法,我們優化了隨機選擇簇質心的步驟。在使用K-means ++初始化時,我們更有可能找到與最佳K-means解決方案競爭的解決方案。
使用K-means ++初始化質心的步驟是:
從我們想要聚類的數據點隨機均勻地選擇第一個簇。這類似於我們在K-means中所做的,但不是隨機挑選所有質心,而是在這裡選擇一個質心接下來,我們計算每個數據點到已經選擇的簇的質心的距離(D(x))然後,從數據點中選擇新的簇質心,最大點到起其最近的質心的距離的平方對應的點為新的簇的質心然後,我們重複步驟2和3,直到選擇了k個簇讓我們舉個例子來更清楚地理解這一點。假設我們有以下幾點,我們想在這裡劃分3個簇:
現在,第一步是隨機選擇一個數據點作為簇質心:
假設我們選擇綠點作為初始質心。現在,我們將使用此質心計算每個數據點與質心的距離(D(x)):
下一個質心將是其平方距離(D(x)2)離當前質心最遠的那個點:
在這種情況下,紅點將被選為下一個質心。現在,為了選擇最後一個質心,我們將計算所有點到其最近的質心的距離,其中具有最大平方距離的點作為下一個質心:
我們將選擇最後一個質心為:
初始化質心後,我們可以繼續使用K-means算法。使用K-means ++初始化質心往往會改善聚類結果。雖然相對於隨機初始化而言計算成本很高,但隨後的K-means通常會更快地收斂。
我確信自本文開頭以來你一直在想一個問題 :我們應該劃分多少個簇?執行K-means時最佳簇的個數應該是多少?
6. 如何在K-means聚類中選擇正確的簇的個數?
每個人在使用K-means時最常見的疑問之一就是如何選擇合適數量的簇。
那麼,讓我們看看一種技術,它將幫助我們為K-means算法選擇正確的簇的個數。我們來看看之前看到的客戶細分示例。總而言之,該銀行希望根據其收入和債務金額對客戶進行細分:
在這裡,我們可以有兩個簇,將客戶分開,如下所示:
所有低收入的客戶都在一個簇中,而高收入的客戶則在第二個簇中。我們還可以有4個簇:
在這裡,一個簇可能代表低收入和低債務的客戶,其他簇是客戶具有高收入和高負債等等。也可以有8個簇:
老實說,我們可以擁有任意數量的簇。你能猜出可能的最大簇數是多少嗎?我們想到將每個點分配給一個單獨的簇。因此,在這種情況下,簇的數量將等於點數或觀察數量。所以,
最大可能的簇數將等於數據集中的觀察數。
但我們如何才能確定最佳簇數?我們可以做的一件事是繪製圖形,其中x軸表示簇的數量,y軸將是評估度量。我們暫時說是Inertia 。
你也可以選擇任何其他評估指標,如Dunn Index:
接下來,我們將從一個小的簇值開始,比如說2使用2個簇訓練模型,計算該模型的Inertia,最後將其繪製在上圖中。假設我們得到Inertia大約為1000:
現在,我們將增加簇的數量,再次訓練模型,並繪製Inertia。這是我們得到的圖:
當我們將簇值從2更改為4時,Inertia急劇下降。隨著我們進一步增加簇的數量,Inertia的下降變得緩慢並最終變得穩定。
Inertia的減小幅度變為常數時對應的簇的個數可以選擇作為我們數據的正確簇的個數。
在這裡,我們可以選擇6到10之間的任意數量的簇的個數。我們可以有7個,8個甚至9個簇。你還必須在確定簇的個數時查看計算成本。如果我們增加簇的數量,計算成本也會增加。因此,如果你沒有高計算資源,我的建議是選擇較少數量的簇。
現在讓我們在Python中實現K-means聚類算法。我們還將看到如何使用K-means ++來初始化質心,並且還將繪製曲線以確定我們的數據集的正確簇的個數。
7. 在Python中實現K-means聚類
我們將致力於批發客戶細分問題。你可以使用此連結[4]下載數據集。數據託管在UCI機器學習存儲庫中。
這個問題的目的是根據各種產品類別(如牛奶,雜貨店,地區等)的年度支出對批發經銷商的客戶進行細分。讓我們開始動手吧!
我們將首先導入所需的庫:
import pandas as pdimport numpy as npimport matplotlib.pyplot as plt%matplotlib inlinefrom sklearn.cluster import KMeans接下來,讓我們讀取數據並查看前五行:
data=pd.read_csv("Wholesale customers data.csv")data.head()
我們有客戶對於不同產品的消費細節,如牛奶,雜貨,冷凍,洗滌劑等。現在,我們必須根據提供的細節細分客戶。在此之前,讓我們提取一些與數據相關的統計數據:
data.describe()
在這裡,我們看到數據的量綱存在很大差異。像Channel和Region這樣的變量量綱很小,而Fresh,Milk,Grocery等變量量綱較大。
由於K-means是基於距離的算法,因此這種幅度差異可能會產生問題。因此,讓我們首先將所有變量置於同一量綱下:
# 標準化數據from sklearn.preprocessing import StandardScalerscaler = StandardScaler()data_scaled = scaler.fit_transform(data)# 統計標準化後的數據pd.DataFrame(data_scaled).describe()
現在看起來差不多了,接下來,讓我們創建一個kmeans函數並將其擬合到數據上:
# 定義kmeans函數,並使用K-means++初始化kmeans = KMeans(n_clusters=2, init='K-means++')# 擬合標準化後的數據kmeans.fit(data_scaled)我們初始化了兩個簇並注意到初始化在這裡不是隨機的。我們已經使用了K-means ++初始化,它通常會產生更好的結果,正如我們在前一節中所討論的那樣。
讓我們評估形成的簇的好壞程度。為此,我們將計算簇的Inertia :
# 算法結束後的Inertia值kmeans.Inertia_Output: 2599.38555935614我們的Inertia值接近2600.現在,讓我們看看我們如何通過在Python中繪製曲線來確定的最佳簇數。我們將首先擬合多個K-means模型,我們將增加簇的數量。我們存儲每個模型的Inertia值,然後繪製它以顯示結果:
# 擬合多個K-means模型並將各個模型的Inertia值存儲到空的列表中SSE = []for cluster in range(1,20): kmeans = KMeans(n_jobs = -1, n_clusters = cluster, init='K-means++') kmeans.fit(data_scaled) SSE.append(kmeans.Inertia_)# 繪製圖形frame = pd.DataFrame({'Cluster':range(1,20), 'SSE':SSE})plt.figure(figsize=(12,6))plt.plot(frame['Cluster'], frame['SSE'], er='o')plt.xlabel('Number of clusters')plt.ylabel('Inertia')
你能從這個圖中分辨出最佳的簇的個數嗎?觀察上面的曲線,我們可以選擇5到8之間的任意值作為簇的個數。讓我們將聚類數設置為5並擬合模型:
kmeans = KMeans(n_jobs = -1, n_clusters = 5, init='K-means++')kmeans.fit(data_scaled)pred = kmeans.predict(data_scaled)最後,讓我們看一下上面形成的每個簇中的點的個數:
frame = pd.DataFrame(data_scaled)frame['cluster'] = predframe['cluster'].value_counts()
因此,有234個數據點屬於簇4(索引3),然後是簇2(索引1)中的125個點,依此類推。這就是我們如何在Python中實現K-means聚類。
8. 結語
在本文中,我們討論了一種最著名的聚類算法--K-means。我們從零開始逐步實現它。我們研究了在使用K-means時可能遇到的挑戰,並了解了在初始化簇質心時K-means ++起到的作用。
最後,我們實現了K-means並通過繪製和觀察曲線找到在K-means算法中的最佳簇數。
[1]: https://www.analyticsvidhya.com/blog/2019/04/introduction-image-segmentation-techniques-python/?utm_source=blog&utm_medium=comprehensive-guide-K-means-clustering
[2]: https://drive.google.com/file/d/1ZzEouo7lRJvajxK6jLM2K_p9xAwGw1tS/view?usp=sharing
[3]: https://datahack.analyticsvidhya.com/contest/practice-problem-big-mart-sales-iii/?utm_source=blog&utm_medium=comprehensive-guide-K-means-clustering
[4]: https://archive.ics.uci.edu/ml/machine-learning-databases/00292/Wholesale%20customers%20data.csv