從零開始學機器學習——K-Means 聚類

努力的小雨發表於2024-11-20

首先給大家介紹一個很好用的學習地址:https://cloudstudio.net/columns

在上一章節中,我們重點探討了聚類的視覺化分析方法,幫助我們更好地理解資料之間的關係和結構。今天,我們將直接進入實際應用,使用聚類演算法中的經典方法——k-means,對資料進行訓練和預測。好的,我們直接開始。

構建模型

在進行資料清洗之前,我們首先回顧一下K-means聚類演算法的核心概念。K-means聚類的主要目標是透過不斷迭代最佳化質心,使得同一簇內的樣本之間更加相似,而不同簇之間的樣本差異則顯著增加,從而實現有效的聚類效果。然而,這種演算法也存在明顯的缺點:首先,它對異常值極為敏感,異常值可能會對質心的計算產生較大影響;其次,K-means演算法需要事先設定K個質心的數量,而這個預定的K值會直接影響模型的聚類效果。

儘管存在這些挑戰,幸運的是,我們有一些方法可以幫助我們更好地分析和選擇適合的K值。接下來,我們將開始清洗資料,為K-means聚類演算法的應用做好準備。

資料準備

首先,我們需要對資料進行清理,去除那些不必要的欄位以及包含大量異常值的特徵。因為在K-means訓練過程中,無用的特徵和異常值會對模型的效果產生干擾,影響聚類的準確性和有效性。為此,我們將採用箱型圖分析,這是一種直觀有效的工具,可以幫助我們識別和處理異常值。

箱型圖

箱型圖由五個重要的數值點構成,分別是最小觀察值(下邊緣)、25%分位數(Q1)、中位數、75%分位數(Q3)以及最大觀察值(上邊緣)。這些統計指標能夠有效地概括資料的分佈特徵,幫助我們瞭解資料的集中趨勢和離散程度。

在分析資料時,如果存在離群點,即異常值,它們的數值會超出最大或最小觀察值的範圍。在箱型圖中,這些離群點通常以“圓點”的形式呈現,便於我們直觀識別和處理。這些異常值需要特別關注,因為它們可能會對後續的K-means聚類分析產生負面影響。而對於箱型圖中其他的數值點,如分位數和中位數,目前我們可以暫時不做過多關注,重點放在識別和處理這些離群點上,以確保資料的質量和聚類分析的有效性。

image

資料清洗

在開始清洗資料之前,首先請確保已安裝所有相關的依賴包,以便順利進行後續操作。

pip install seaborn scikit-learn

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from sklearn.preprocessing import LabelEncoder

df = pd.read_csv("../data/nigerian-songs.csv")
df = df[(df['artist_top_genre'] == 'afro dancehall') | (df['artist_top_genre'] == 'afropop') | (df['artist_top_genre'] == 'nigerian pop')]
df = df[(df['popularity'] > 0)]

這段程式碼的編寫主要是基於我們在上一章節的分析結果,發現頭三個流派的資料量最多。因此,我們決定直接保留這三個流派的相關資料,而不再保留其他流派的資料,以便更集中地進行後續分析和處理。

plt.figure(figsize=(20,20), dpi=200)

plt.subplot(4,3,1)
sns.boxplot(x = 'popularity', data = df)

plt.subplot(4,3,2)
sns.boxplot(x = 'acousticness', data = df)

plt.subplot(4,3,3)
sns.boxplot(x = 'energy', data = df)

plt.subplot(4,3,4)
sns.boxplot(x = 'instrumentalness', data = df)

plt.subplot(4,3,5)
sns.boxplot(x = 'liveness', data = df)

plt.subplot(4,3,6)
sns.boxplot(x = 'loudness', data = df)

plt.subplot(4,3,7)
sns.boxplot(x = 'speechiness', data = df)

plt.subplot(4,3,8)
sns.boxplot(x = 'tempo', data = df)

plt.subplot(4,3,9)
sns.boxplot(x = 'time_signature', data = df)

plt.subplot(4,3,10)
sns.boxplot(x = 'danceability', data = df)

plt.subplot(4,3,11)
sns.boxplot(x = 'length', data = df)

plt.subplot(4,3,12)
sns.boxplot(x = 'release_date', data = df)

我們可以直接根據各列的資料繪製箱型圖,這樣能夠有效地展示資料的分佈情況和離群值。如圖所示:

image

接下來,我們將刪除那些顯示異常值的箱型圖,以便更好地集中於資料的主要趨勢和特徵。最終,我們將只保留如下所示的箱型圖:

image

接下來,我們都知道數值特徵在模型訓練中起著至關重要的作用,因此有必要對這些特徵進行適當的轉換。此前我們已經討論過相關的轉換方法,這裡就不再詳細贅述。下面是實現該轉換的程式碼:

from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
X = df.loc[:, ('artist_top_genre','popularity','danceability','acousticness','loudness','energy')]
y = df['artist_top_genre']
X['artist_top_genre'] = le.fit_transform(X['artist_top_genre'])
y = le.transform(y)

KMeans 聚類

由於資料來源於上一章節的觀察與分析,儘管我們對其進行了初步的審視,但在具體的資料特徵上,資料本身並不清楚其中存在三種不同的流派。因此,為了確定最佳的質心數量,我們需要藉助肘部圖進行深入分析,以便找到最合適的聚類設定。

肘部圖

肘部法則(Elbow Method)是一種常用的技術,用於確定 K-Means 聚類中簇的數量 (K)。該方法透過分析不同 K 值下的聚類效果,幫助我們找到一個合適的簇數。其優點在於直觀易懂,能夠有效地指導聚類數的選擇。

通常,隨著 K 值的增加,總平方誤差(SSE)會逐漸降低,但在某個特定的 K 值之後,誤差減少的幅度會顯著減小。這一轉折點被稱為“肘部”,它標誌著增加 K 值所帶來的收益逐漸減小,從而幫助我們識別出最佳的簇數。接下來,我們將繪製肘部圖,以便直觀地展示這一過程。

from sklearn.cluster import KMeans
wcss = []

for i in range(1, 11):
    kmeans = KMeans(n_clusters = i, init = 'k-means++', random_state = 42)
    kmeans.fit(X)
    wcss.append(kmeans.inertia_)
plt.figure(figsize=(10,5))
sns.lineplot(x=range(1, 11), y=wcss,marker='o',color='red')
plt.title('Elbow')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS')
plt.show()

接下來,我將對這段程式碼進行簡單的解釋。其主要目的是透過遍歷不同的 K 值,計算並儲存每個 K 對應的組內平方和(WCSS, Within-Cluster Sum of Squares),以便後續繪製肘部圖,幫助選擇最佳的簇數。

  • 設定一個迴圈,從 1 到 10(包含 1,但不包含 11),即測試 1 到 10 個簇。
  • init = 'k-means++':使用 K-means++ 初始化方法,以提高聚類結果的質量。
  • random_state = 42:設定隨機種子,確保每次執行結果可重現。否則,就算是同樣的資料,每次跑出來的結果也不一樣。
  • 將當前模型的 inertia_ 屬性(表示簇內平方和)新增到 wcss 列表中。inertia_ 是 KMeans 類的一個屬性,表示所有簇內的距離平方和,越小表示聚類效果越好。

在成功繪製肘部圖之後,如圖所示,我們可以清晰地觀察到 WCSS 隨著 K 值變化的趨勢。透過分析這張圖,可以明顯看出,當 K 值為 3 時,誤差的減少幅度顯著減小,形成了一個明顯的轉折點。這個轉折點表明,選擇 K 為 3 是最優的,因為在此點之後,增加簇的數量對聚類效果的改善不再顯著。

image

訓練模型

接下來,我們將應用 K-Means 聚類演算法,並設定質心的數量為 3,以評估模型的準確性和聚類效果。下面是實現這一過程的程式碼:

from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters = 3, init = 'k-means++', random_state = 42)
kmeans.fit(X)
labels = kmeans.labels_
correct_labels = sum(y == labels)
print("Result: %d out of %d samples were correctly labeled." % (correct_labels, y.size))
print('Accuracy score: {0:0.2f}'. format(correct_labels/float(y.size)))

Result: 105 out of 286 samples were correctly labeled.
Accuracy score: 0.37

令人失望的是,模型的準確性竟然不如隨機猜測。這一結果促使我們回顧之前分析的箱型圖,除了識別出一些明顯不正常的特徵外,我們還注意到許多保留下來的特徵同樣存在異常值。這些異常值無疑會對 K-Means 聚類的結果產生負面影響,因此,我們決定對這些特徵進行標準化處理,以提高模型的穩定性和準確性:

from sklearn.preprocessing import StandardScaler
kmeans = KMeans(n_clusters = 3, init = 'k-means++', random_state = 42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
kmeans.fit(X_scaled)
labels = kmeans.labels_
correct_labels = sum(y == labels)
print("Result: %d out of %d samples were correctly labeled." % (correct_labels, y.size))
print('Accuracy score: {0:0.2f}'. format(correct_labels/float(y.size)))

Result: 163 out of 286 samples were correctly labeled.
Accuracy score: 0.57

在使用 K-Means 聚類時,採用 StandardScaler 進行資料標準化通常能夠顯著提升聚類效果。這主要是因為 StandardScaler 會將每個特徵的均值調整為 0,標準差調整為 1,從而使得所有特徵在同一尺度上進行比較。這種處理方式有效地消除了不同特徵之間因尺度差異而導致的影響,避免了某些特徵因其數值範圍較大而在距離計算中佔據主導地位的情況。

因此,透過標準化,我們能夠更公平地評估每個特徵對聚類結果的貢獻,從而提升 K-Means 演算法的整體效能和準確性。

總結

在本文中,我們深入探討了K-means聚類演算法及其在資料分析中的應用,特別是如何有效清洗和準備資料以提高聚類效果。透過利用箱型圖,我們識別並處理了異常值,為後續的聚類分析奠定了堅實的基礎。在確定適合的質心數量時,我們運用了肘部法則,成功找到了最佳的K值。

雖然初步模型的準確性並不理想,但透過資料標準化,我們顯著提升了聚類效果,準確率達到了57%。這一過程不僅展示了K-means聚類的基本原理,也強調了資料預處理的重要性。清晰的資料不僅可以提高模型的可靠性,還能為資料分析提供更有意義的洞察。


我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。

💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。

🌟 歡迎關注努力的小雨!🌟

相關文章