Część algorytmów uczenia maszynowego wymaga, by z góry podać docelową liczbę grup. Jak zatem dokonać w sposób obiektywny najlepszego wyboru?
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.¶
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. 🙂
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
df.head()
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.
kmeans = KMeans(n_clusters=8)
kmeans.fit(df)
y_kmeans = kmeans.predict(df)
df['cluster'] = y_kmeans
centers = kmeans.cluster_centers_
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ę:
- oś x - liczba grup,
- oś y - wartość inercji (o tym, czym jest inercja pisałem w poprzednim wpisie).
Plan jest zatem następujący:
- W sposób iteracyjny buduję kolejne wersje algorytmu k-średnich dla n-grup (2, 15).
- Sprawdzam wartość inercji i zapisuję ją do tablicy.
- Nanoszę uzyskane wyniki na wykresie osypiska.
- Buduję finalny model i wizualizuję grupy.
Zaczynajmy zatem! 🙂
4. Modelowanie.¶
Zaczynam od budowy kolejnych wersji algorytmu k-średnich dla n-grup (2, 15).
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'])
res
Do powyższej tabeli dodam jeszcze zmienną spadek procentowy inercji, po dodaniu nowej grupy.
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)
res.round(3)
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.
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.¶
kmeans = KMeans(n_clusters=4)
kmeans.fit(df)
y_kmeans = kmeans.predict(df)
df['cluster'] = y_kmeans
centers = kmeans.cluster_centers_
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 :-)
Dodaj komentarz