Wybór liczby segmentów w algorytmie k-średnich

k-means, wykres osypiska, liczba grup, liczba segmentów, segmentacja, grupowanie, grupowanie gęstościowe, algorytmy segmentacji

Część algorytmów uczenia maszynowego wymaga, by z góry podać docelową liczbę grup. Jak zatem dokonać w sposób obiektywny najlepszego wyboru?

Z tego artykułu dowiesz się: 1. Jak wybrać liczbę grup w algorytmach segmentacyjnych? 3. Jak korzystać z wykresu osypiska?

W algorytmach segmentacji niekiedy musimy mierzyć się z problemem wyboru optymalnej liczby grup. Algorytmy takie jak k-średnich, czy k-prototypów wymagają od nas podania docelowej liczby grup jeszcze przed uruchomieniem kodu. Pozostałe, jak np. DBSCAN i OPTICS, są wolne od tego problemu (oczywiście muszą mierzyć się one z innymi, czasem znacznie bardziej złożonymi wyzwaniami jak np. optymalizacja parametrów, która w zależności od zbioru potrafi być arcytrudna). Skąd zatem mamy wiedzieć na ile grup podzielić nasz zbiór?

Na moim blogu temat wyboru optymalnej liczby segmentów pojawił się już przy okazji wpisu z okazji Międzynarodowego Dnia Dziecka. Używałem wtedy algorytmu KPrototypes, dlatego dziś wybiorę inny algorytm: k-średnich.

1. Wczytanie podstawowych bibliotek.

In [1]:
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.datasets.samples_generator import make_blobs

2. Wygenerowanie zbioru.

Na potrzeby tego wpisu posłużę się sztucznie wygenerowanym zbiorem. Pozwoli to mi na łatwiejsze pokazanie użyteczności poszczególnych technik. 🙂

In [2]:
X, y_true = make_blobs(n_samples=1000, centers=8, cluster_std=2.5, random_state=221119)
df = pd.DataFrame(X, columns = ['f1', 'f2'])
df['cluster'] = y_true
In [3]:
df.head()
Out[3]:
f1 f2 cluster
0 -9.029149 -7.367856 0
1 -3.294983 9.534324 1
2 6.114074 -5.055407 7
3 -4.573212 5.466237 4
4 -1.437384 3.459571 4
In [4]:
sns.lmplot(data=df, x='f1', y='f2', fit_reg=False, hue = 'cluster', palette = sns.color_palette("muted", 10)).set(title = 'Wykres punktowy zbioru')
plt.show()

Wygenerowałem zbiór, który teoretycznie składa się z 8 grup. By utrudnić jednoznaczne wskazanie ich optymalnej liczby "rozmyłem" je nieco poprzez manipulacje odchyleniem standardowym podczas losowania (parametr cluster_std). Dość trudno teraz wykonać separację poszczególnych segmentów.

3. Pierwsze testy algorytmu.

Zobaczmy jak zachowa się algorytm k-średnich dla liczby grup równej wartości początkowej użytej przy inicjowaniu zbioru.

In [5]:
kmeans = KMeans(n_clusters=8)
kmeans.fit(df)
y_kmeans = kmeans.predict(df)
In [6]:
df['cluster'] = y_kmeans
centers = kmeans.cluster_centers_
In [7]:
sns.lmplot(data=df, x='f1', y='f2', fit_reg=False, hue = 'cluster', palette = sns.color_palette("muted", 10)).set(title='Wizualizacja grup')
plt.scatter(centers[:, 0], centers[:, 1], c='black', s=100, alpha=0.5)
plt.show()

Przyznam, że nie wygląda to najlepiej. 🙂 Sprawdźmy zatem, co można zrobić, by to poprawić i dokonać wyboru liczby segmentów w sposób możliwie obiektywny.

Z pomocą przychodzi wykres osypiska. Najczęściej jest on stosowany w algorytmach służących do redukcji wymiarów, np. PCA, do opisywania stopnia wariancji wyjaśnianego przez wskazaną liczbę głównych składowych. Poniżej przykład wykresu osypiska.

Nic jednak nie stoi na przeszkodzie, by użyć go również w problemie poszukiwania odpowiedniej liczby segmentów. Na wykresie przedstawię:

Plan jest zatem następujący:

  1. W sposób iteracyjny buduję kolejne wersje algorytmu k-średnich dla n-grup (2, 15).
  2. Sprawdzam wartość inercji i zapisuję ją do tablicy.
  3. Nanoszę uzyskane wyniki na wykresie osypiska.
  4. Buduję finalny model i wizualizuję grupy.

Zaczynajmy zatem! 🙂

4. Modelowanie.

Zaczynam od budowy kolejnych wersji algorytmu k-średnich dla n-grup (2, 15).

In [8]:
res = []
for n in range(2, 16):
    kmeans = KMeans(n_clusters=n)
    kmeans.fit(df)
    res.append([n, kmeans.inertia_])
res = pd.DataFrame(res, columns = ['liczba_grup', 'inercja'])
In [9]:
res
Out[9]:
liczba_grup inercja
0 2 54165.888007
1 3 36937.266306
2 4 21082.838674
3 5 16481.456985
4 6 12924.584029
5 7 10482.867729
6 8 8738.028747
7 9 8094.391006
8 10 7552.246019
9 11 7037.395873
10 12 6601.317312
11 13 6114.813432
12 14 5883.273286
13 15 5401.760309

Do powyższej tabeli dodam jeszcze zmienną spadek procentowy inercji, po dodaniu nowej grupy.

In [10]:
diff = [0]
for n in range(0, 13):
    diff.append(((res.iloc[n,1] - res.iloc[n+1, 1])/res.iloc[n, 1]*100))
res = res.assign(zysk_proc = diff)
res.set_index('liczba_grup', inplace = True)
In [11]:
res.round(3)
Out[11]:
inercja zysk_proc
liczba_grup
2 54165.888 0.000
3 36937.266 31.807
4 21082.839 42.923
5 16481.457 21.825
6 12924.584 21.581
7 10482.868 18.892
8 8738.029 16.645
9 8094.391 7.366
10 7552.246 6.698
11 7037.396 6.817
12 6601.317 6.197
13 6114.813 7.370
14 5883.273 3.787
15 5401.760 8.184

W powyższej tabeli widać, że procentowe spadki inercji zaczynają być bardzo małe od 8 kroku, czyli inicjalnej liczby grup zakładanej przy generowaniu zbioru. Znaczny spadek jest również widoczny po dodaniu 4 zmiennej. By podjąć możliwie najlepszą decyzję, wykonam wizualizację wyników na wykresie.

In [12]:
plt.figure(figsize=(10,7))
sns.set(font_scale=1.4, style="whitegrid")
sns.lineplot(data = res.drop('zysk_proc', axis = 1), palette = ['#eb6c6a']).set(title = "Miara odmienności grup vs liczba grup")
plt.show()

"Łokieć" jest ewidentnie widoczny przy liczbie grup równej 4 i finalnie zdecyduję się na tę wartość w końcowym modelu.

5. Finalny model.

In [13]:
kmeans = KMeans(n_clusters=4)
kmeans.fit(df)
y_kmeans = kmeans.predict(df)
In [14]:
df['cluster'] = y_kmeans
centers = kmeans.cluster_centers_
In [15]:
sns.set(font_scale=1.4, style="white")
sns.lmplot(data=df, x='f1', y='f2', fit_reg=False, hue = 'cluster', palette = sns.color_palette("muted", 10)).set(title='Wizualizacja grup')
plt.scatter(centers[:, 0], centers[:, 1], c='black', s=100, alpha=0.5)
plt.show()

Powyższy wykres pokazuje, że dla analizowanego zbiory liczba grup równa 4 jest wartością optymalną.

Podsumowanie

Mam nadzieję, że ten wpis przypadł Ci do gustu, a wykres osypiska i współczynnik spadku wartości inercji okażą się dla Ciebie przydatne w pracy lub w zwykłym eksperymentowaniu ze zbiorami danych. Nie zapomnij, proszę, że wykres osypiska jest uniwersalnym narzędziem. Można go używać w wielu sytuacjach - nie tylko przy analizie głównych składowych PCA, co dobitnie pokazuje powyższy przykład. 😉

Podobał Ci się ten artykuł?

Jeśli tak, to zarejestruj się, by otrzymywać informacje o nowych wpisach. Dodatkowo w prezencie wyślę Ci bezpłatny poradnik 🙂

Bądź pierwszy, który skomentuje ten wpis!

Dodaj komentarz

Twój adres email nie zostanie opublikowany.


*