Materiały kursów
Materiały / Machine Learning
Pobierz notatnik (.ipynb)

Ćwiczenie 8: Uczenie nienadzorowane - grupowanie i redukcja wymiarowości

Po co to ćwiczenie?

Wszystkie dotychczasowe ćwiczenia miały wspólny fundament: obok cech (ang. features) mieliśmy etykietę. Model przewidywał, my porównywaliśmy przewidywanie z prawdą i liczyliśmy, ile razy trafił. Tak działa uczenie nadzorowane (ang. supervised learning).

W praktyce dane z etykietami to luksus. Etykietę trzeba wytworzyć: ktoś musi postawić diagnozę, opisać zdjęcie, oznaczyć transakcję jako oszustwo. To kosztuje czas i pieniądze, więc w prawdziwych zbiorach zwykle mamy miliony wierszy i zero etykiet (ang. labels).

Uczenie nienadzorowane (ang. unsupervised learning) to praca właśnie z takimi danymi. I od razu pojawia się konsekwencja, która zmienia wszystko:

Nie ma etykiet, więc nie ma czego „trafić". Nie da się policzyć skuteczności, bo nie ma poprawnej odpowiedzi, z którą można by porównać wynik.

Jeśli w ćwiczeniu 01 najtrudniejszą częścią była uczciwa ocena modelu, tutaj problem jest jeszcze ostrzejszy: nie istnieje jedna liczba, która mówi, czy wynik jest dobry. Zamiast tego mamy wskaźniki opisujące kształt wyniku oraz - przede wszystkim - interpretację człowieka.

Czego się nauczysz

  1. Czym uczenie nienadzorowane różni się od nadzorowanego i dlaczego jego ocena jest trudniejsza.
  2. Jak działa grupowanie metodą k-średnich (ang. k-means clustering).
  3. Dlaczego przed k-średnimi trzeba skalować cechy - i co się dzieje, gdy tego nie zrobisz.
  4. Jak dobrać liczbę grup: metoda łokcia (ang. elbow method) i wskaźnik sylwetki (ang. silhouette score).
  5. Jak analiza głównych składowych (PCA, ang. principal component analysis) pozwala obejrzeć 8-wymiarowe dane na płaskim wykresie.
  6. Dlaczego grupy znalezione przez algorytm zwykle nie pokrywają się z tym, co akurat nas interesuje - i dlaczego to nie jest błąd algorytmu.

Zanim zaczniesz: uruchamiaj komórki po kolei (Shift+Enter). Późniejsze korzystają ze zmiennych zdefiniowanych wcześniej.

1. Te same dane, ale bez odpowiedzi

Zostajemy przy zbiorze dane/diabetes.csv, który znasz z poprzednich ćwiczeń. Tym razem jednak robimy coś, co może wydać się dziwne: zasłaniamy kolumnę Diabetic.

Udajemy, że jesteśmy w sytuacji, w której nikt nie postawił diagnozy. Mamy 10 000 kart pacjentów z wynikami badań i pytanie od zleceniodawcy: „czy ci pacjenci dzielą się na jakieś naturalne grupy?".

Kolumna Diabetic nie znika z pliku - odkładamy ją na bok i wrócimy do niej dopiero w sekcji 6, żeby sprawdzić, co algorytm „odkrył" bez naszej pomocy. To jest cały urok tego ćwiczenia: mamy rzadką możliwość ocenienia metody nienadzorowanej, bo akurat znamy odpowiedź, której jej nie pokazaliśmy.

PatientID - jak zawsze - nie jest cechą i wylatuje.

In [ ]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

dane = pd.read_csv('dane/diabetes.csv')

# X - cechy. Bez identyfikatora i BEZ etykiety: algorytm ma jej nie zobaczyć.
X = dane.drop(columns=['PatientID', 'Diabetic'])

# Etykieta odlozona na bok - uzyjemy jej dopiero w sekcji 6, do sprawdzenia.
y_ukryte = dane['Diabetic']

print("Kształt danych:", X.shape)
print("Cechy:", list(X.columns))
X.describe().T[['mean', 'std', 'min', 'max']].round(1)

2. Dlaczego skalowanie jest tu obowiązkowe

Spójrz na wypisaną tabelę statystyk. Zakresy cech są dramatycznie różne: SerumInsulin liczy się w setkach, BMI w dziesiątkach, a DiabetesPedigree przyjmuje wartości poniżej jedności.

W ćwiczeniu 01 skalowanie było kwestią wygody - regresja logistyczna zbiegała szybciej. Tutaj sprawa jest znacznie poważniejsza, bo k-średnich w całości opiera się na odległości euklidesowej (ang. Euclidean distance) między pacjentami:

$$d(a, b) = \sqrt{(a_1 - b_1)^2 + (a_2 - b_2)^2 + \ldots + (a_n - b_n)^2}$$

Zauważ, że różnice z poszczególnych cech są tu po prostu dodawane. Jeśli jedna cecha waha się w zakresie 800 jednostek, a druga w zakresie 2 jednostek, to pierwsza wnosi do sumy kilkaset tysięcy, a druga - cztery. Druga cecha przestaje istnieć dla algorytmu.

Efekt: bez skalowania nie grupujesz pacjentów według ich stanu zdrowia. Grupujesz ich według tej jednej cechy, która ma przypadkiem największe liczby. Wybór jednostek (mg/dl zamiast g/l) zmieniłby wynik - a to oczywisty sygnał, że coś jest nie tak z metodą.

Bez skalowania Ze skalowaniem (StandardScaler)
Co decyduje o odległości cecha o największym zakresie liczbowym wszystkie cechy po równo
Wpływ zmiany jednostek zmienia wynik grupowania żaden
Sens biznesowy grup zwykle żaden do zinterpretowania

StandardScaler przekształca każdą cechę tak, żeby miała średnią 0 i odchylenie standardowe 1 - czyli sprowadza wszystkie do wspólnej „waluty".

In [ ]:
from sklearn.preprocessing import StandardScaler

skaler = StandardScaler()
X_skal = skaler.fit_transform(X)

# Sprawdzamy, ze faktycznie: srednia ~0, odchylenie ~1
podsumowanie = pd.DataFrame({
    'przed - średnia': X.mean().round(1),
    'przed - odch.std': X.std().round(1),
    'po - średnia': X_skal.mean(axis=0).round(3),
    'po - odch.std': X_skal.std(axis=0).round(3),
})
podsumowanie

3. Pierwsze grupowanie: k-średnich

K-średnich (KMeans) działa według zaskakująco prostego przepisu:

  1. Wylosuj k punktów w przestrzeni cech - to będą środki grup (ang. centroids).
  2. Przypisz każdego pacjenta do najbliższego środka.
  3. Przesuń każdy środek na średnią (stąd nazwa) pacjentów, którzy do niego trafili.
  4. Wróć do punktu 2 i powtarzaj, aż środki przestaną się przesuwać.

Dwie rzeczy warto zauważyć od razu:

  • Liczbę grup k podajesz Ty, z góry. Algorytm jej nie odkrywa - on lojalnie podzieli dane na tyle części, ile każesz. Każesz mu podzielić na 7 grup dane, które naturalnie tworzą 2 - podzieli na 7.
  • Wynik zależy od losowania startowego. Dlatego n_init=10 (algorytm startuje 10 razy z różnych miejsc i wybiera najlepszy przebieg) oraz random_state=42 (powtarzalność).

Zaczniemy od k=3 - bez szczególnego powodu, żeby najpierw zobaczyć, jak to w ogóle wygląda. Doborem k zajmiemy się w następnej sekcji.

In [ ]:
from sklearn.cluster import KMeans

kmeans = KMeans(n_clusters=3, n_init=10, random_state=42)
grupy = kmeans.fit_predict(X_skal)   # zwraca numer grupy dla kazdego pacjenta

print("Numery grup, jakie powstały:", np.unique(grupy))
print()
print("Liczebność grup:")
for numer, liczba in pd.Series(grupy).value_counts().sort_index().items():
    print(f"  grupa {numer}: {liczba:5d} pacjentów ({liczba / len(grupy):5.1%})")

print()
print("Inercja (suma kwadratów odległości do własnego środka):", round(kmeans.inertia_, 1))

Co to znaczy „grupa 0"?

Nic. Dosłownie nic.

To jest pierwsza pułapka uczenia nienadzorowanego: numery grup są etykietkami bez treści. Algorytm nie wie, że grupa 1 to „pacjenci ryzyka" - on wie tylko, że są bliżej siebie niż grupy 0. Przy innym ziarnie losowości te same grupy mogłyby dostać zamienione numery, a wynik byłby identyczny.

Żeby dowiedzieć się, czym właściwie są te grupy, trzeba obejrzeć ich profile: jak wyglądają średnie wartości cech w każdej grupie. Dopiero z tego człowiek buduje opis w rodzaju „grupa 1 to osoby starsze z wysokim BMI".

In [ ]:
# Profile grup - srednie wartosci cech, liczone na danych ORYGINALNYCH
# (nieskalowanych), bo tylko one maja czytelne jednostki.
profile = X.copy()
profile['grupa'] = grupy

srednie = profile.groupby('grupa').mean().round(1)
print("Średnie wartości cech w poszczególnych grupach:")
srednie

Skalujemy do liczenia, odwracamy skalowanie do czytania. Algorytm potrzebuje danych skalowanych, bo liczy odległości. Człowiek potrzebuje danych oryginalnych, bo „BMI = 31,4" coś znaczy, a „BMI = 0,82 odchylenia standardowego" nie znaczy nic. To rozdzielenie wraca w praktyce stale.

4. Ile grup? Dwie metody, żadna nie daje pewnej odpowiedzi

Skoro k podajemy z góry, to skąd wiedzieć, ile grup jest „naprawdę"? Uczciwa odpowiedź brzmi: nie da się tego rozstrzygnąć samymi danymi. Można jedynie zebrać przesłanki.

Metoda łokcia (ang. elbow method)

Liczymy inercję (ang. inertia) - sumę kwadratów odległości każdego punktu od środka jego grupy. Im więcej grup, tym inercja mniejsza; przy k równym liczbie pacjentów spadłaby do zera (każdy pacjent własną grupą). Sama inercja nie może więc być kryterium wyboru - zawsze wskazałaby „im więcej, tym lepiej".

Szukamy zamiast tego momentu, w którym spadek wyraźnie zwalnia - miejsca, gdzie wykres się „załamuje" jak zgięty łokieć. Do tego punktu każda dodatkowa grupa realnie porządkowała dane; po nim tylko rozdrabnia to, co już było spójne.

Wskaźnik sylwetki (ang. silhouette score)

Dla każdego punktu porównuje dwie rzeczy: jak blisko jest mu do własnej grupy i jak blisko do najbliższej grupy obcej. Wynik mieści się w przedziale od -1 do 1:

Wartość Interpretacja
bliska 1 punkt siedzi głęboko we własnej grupie, daleko od obcych - podział jest wyraźny
bliska 0 punkt leży na granicy dwóch grup - mógłby należeć do każdej z nich
ujemna punkt jest bliżej grupy obcej niż własnej - prawdopodobnie źle przypisany

Wskaźnik sylwetki dla całego zbioru to średnia z wszystkich punktów. W przeciwieństwie do inercji nie rośnie automatycznie z k, więc można go maksymalizować.

In [ ]:
from sklearn.metrics import silhouette_score

zakres_k = range(2, 9)
inercje = []
sylwetki = []

for k in zakres_k:
    km = KMeans(n_clusters=k, n_init=10, random_state=42)
    etykiety = km.fit_predict(X_skal)
    inercje.append(km.inertia_)
    sylwetki.append(silhouette_score(X_skal, etykiety))

tabela = pd.DataFrame({
    'k': list(zakres_k),
    'inercja': np.round(inercje, 1),
    'sylwetka': np.round(sylwetki, 4),
})
print(tabela.to_string(index=False))
print()
print("Najwyższy wskaźnik sylwetki dla k =", tabela.loc[tabela['sylwetka'].idxmax(), 'k'])
In [ ]:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4.5))

ax1.plot(list(zakres_k), inercje, marker='o')
ax1.set_xlabel('liczba grup k')
ax1.set_ylabel('inercja')
ax1.set_title('Metoda łokcia - szukamy załamania')
ax1.grid(alpha=0.3)

ax2.plot(list(zakres_k), sylwetki, marker='s', color='darkorange')
ax2.set_xlabel('liczba grup k')
ax2.set_ylabel('wskaźnik sylwetki')
ax2.set_title('Wskaźnik sylwetki - szukamy maksimum')
ax2.grid(alpha=0.3)

plt.tight_layout()
plt.show()

Uczciwa uwaga o obu wykresach

W podręcznikach „łokieć" jest ostry i widoczny gołym okiem. W prawdziwych danych - takich jak te - wykres inercji bywa gładką krzywą bez wyraźnego załamania, a wskaźnik sylwetki osiąga wartości niskie (rzędu 0,1-0,3), bez wyraźnego zwycięzcy.

To nie jest usterka Twojego kodu. To informacja o danych: pacjenci nie tworzą tu odseparowanych skupisk. Tworzą jedną chmurę o zmiennej gęstości, którą k-średnich i tak podzieli na k kawałków, bo do tego został napisany.

Wniosek, który warto zabrać z tego ćwiczenia: liczbę grup wybiera się na podstawie wskaźników razem z sensem merytorycznym i przeznaczeniem wyniku. Jeśli grupy mają posłużyć do zaprojektowania trzech ścieżek opieki, to k=3 jest sensowne nawet wtedy, gdy k=2 ma nieco wyższą sylwetkę.

5. PCA - jak obejrzeć dane 8-wymiarowe

Chcielibyśmy zobaczyć te grupy na wykresie. Problem: mamy 8 cech, czyli 8 wymiarów, a kartka ma 2.

Najprostszy pomysł - narysować dwie wybrane cechy - marnuje pozostałe sześć. Analiza głównych składowych (PCA) robi coś mądrzejszego: tworzy nowe cechy, z których każda jest kombinacją wszystkich oryginalnych, i układa je tak, żeby:

  • pierwsza składowa (PC1) wyjaśniała jak najwięcej zmienności w danych,
  • druga (PC2) wyjaśniała jak najwięcej z tego, co zostało, i była nieskorelowana z pierwszą,
  • i tak dalej.

Dzięki temu, biorąc dwie pierwsze składowe, zachowujemy największą możliwą część informacji, jaką da się zmieścić na płaszczyźnie.

Wariancja wyjaśniona (ang. explained variance ratio) mówi, jaką część zmienności zachowała każda składowa. Suma dla dwóch pierwszych to nasza „jakość rzutu": jeśli wynosi 50%, oglądamy połowę informacji - i trzeba o tym pamiętać, patrząc na obrazek.

PCA również wymaga skalowania, i to z tego samego powodu co k-średnich: szuka kierunków największej wariancji, a cecha o dużych liczbach ma z definicji największą wariancję.

In [ ]:
from sklearn.decomposition import PCA

pca = PCA(n_components=2, random_state=42)
X_2d = pca.fit_transform(X_skal)     # 10000 x 8  ->  10000 x 2

wariancja = pca.explained_variance_ratio_
print(f"PC1 wyjaśnia {wariancja[0]:.1%} zmienności")
print(f"PC2 wyjaśnia {wariancja[1]:.1%} zmienności")
print(f"Razem na wykresie widać {wariancja.sum():.1%} informacji zawartej w danych")
In [ ]:
fig, ax = plt.subplots(figsize=(8, 6.5))

for numer in np.unique(grupy):
    maska = grupy == numer
    ax.scatter(X_2d[maska, 0], X_2d[maska, 1], s=6, alpha=0.4, label=f'grupa {numer}')

# Srodki grup przeniesione do przestrzeni PCA - tym samym przeksztalceniem
srodki_2d = pca.transform(kmeans.cluster_centers_)
ax.scatter(srodki_2d[:, 0], srodki_2d[:, 1], s=260, c='black', marker='X',
           label='środki grup', zorder=5)

ax.set_xlabel(f'PC1 ({wariancja[0]:.1%} zmienności)')
ax.set_ylabel(f'PC2 ({wariancja[1]:.1%} zmienności)')
ax.set_title('Pacjenci w przestrzeni dwóch głównych składowych')
ax.legend(markerscale=3)
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()

Jak czytać ten wykres - i czego z niego nie odczytywać

Osie nie mają jednostek. PC1 to nie glukoza ani wiek, tylko mieszanka wszystkich ośmiu cech. Pytanie „ile wynosi PC1 = 2?" nie ma sensownej odpowiedzi w kategoriach medycznych.

Grupy mogą wyglądać na nachodzące na siebie, choć w pełnej przestrzeni są rozdzielone. Rzut z 8 wymiarów na 2 to jak cień trójwymiarowej rzeźby na ścianie - dwa odległe punkty potrafią rzucić cień w to samo miejsce. Jeśli dwie pierwsze składowe wyjaśniają połowę zmienności, to drugą połowę po prostu widzisz zgniecioną.

Dlatego wykres PCA służy do wyrobienia sobie intuicji, a nie do dowodzenia tez. Ocenę jakości podziału zostawiamy wskaźnikom.

6. Moment prawdy: czy grupy pokrywają się z chorobą?

Teraz odsłaniamy kolumnę, którą schowaliśmy na początku. Algorytm nigdy jej nie widział - podzielił pacjentów wyłącznie na podstawie wyników badań.

Pytanie brzmi: czy powstałe grupy odpowiadają temu, kto faktycznie choruje?

Do porównania użyjemy tabeli krzyżowej oraz skorygowanego indeksu Randa (ang. adjusted Rand index, adjusted_rand_score). Mierzy on zgodność dwóch podziałów tego samego zbioru, odpornie na to, że numery grup są przypadkowe. Wartość 1 to pełna zgodność, wartość 0 to zgodność na poziomie losowego przydziału.

In [ ]:
from sklearn.metrics import adjusted_rand_score

# Grupujemy na 2 grupy - zeby liczba grup odpowiadala liczbie klas
kmeans2 = KMeans(n_clusters=2, n_init=10, random_state=42)
grupy2 = kmeans2.fit_predict(X_skal)

tabela_krzyzowa = pd.crosstab(
    pd.Series(grupy2, name='grupa z k-średnich'),
    y_ukryte.rename('faktyczna cukrzyca'),
)
print(tabela_krzyzowa)
print()
print("Udział chorych w każdej grupie:")
for numer in sorted(np.unique(grupy2)):
    udzial = y_ukryte[grupy2 == numer].mean()
    print(f"  grupa {numer}: {udzial:.1%} chorych")

print()
print(f"Udział chorych w całym zbiorze:       {y_ukryte.mean():.1%}")
print(f"Skorygowany indeks Randa (k=2):       {adjusted_rand_score(y_ukryte, grupy2):.3f}")

Dlaczego zgodność jest tylko częściowa - i dlaczego to nie porażka

Prawdopodobnie zobaczysz, że grupy nie pokrywają się z chorobą: udziały chorych w grupach różnią się od średniej w zbiorze, ale żadna grupa nie okazuje się „grupą chorych". Indeks Randa wypada nisko.

Studenci odbierają to jako niepowodzenie. To nieporozumienie, i warto je wyprostować raz na zawsze:

K-średnich dzieli dane według najsilniejszej struktury, jaka w nich jest - a nie według tego, co akurat nas interesuje.

Najsilniejsza struktura w kartach pacjentów to niekoniecznie cukrzyca. To może być ogólny profil metaboliczny, wiek, masa ciała albo coś, co wynika ze sposobu zbierania danych. Algorytm nie ma pojęcia, że pytamy o cukrzycę - nie powiedzieliśmy mu tego. Brak etykiety oznacza brak celu.

Stąd praktyczne wnioski:

Sytuacja Właściwe narzędzie
Znam etykietę i chcę ją przewidywać uczenie nadzorowane (ćwiczenia 01-07)
Nie mam etykiet, chcę poznać strukturę danych grupowanie, PCA
Mam etykiety, ale grupuję „dla pewności" prawie zawsze błąd - marnujesz najcenniejszą informację, jaką masz

Ostatni wiersz jest najważniejszy. Jeśli masz etykiety, używanie grupowania do ich przewidywania jest krokiem wstecz: dobrowolnie rezygnujesz z jedynej informacji, która mówi algorytmowi, o co Ci chodzi.

Częściowa zgodność, którą widzisz, ma jednak swoją wymowę: pokazuje, że cukrzyca jest jedną z osi zmienności w tych danych - po prostu nie tą dominującą.


Zadania

Poniższe zadania wykonujesz samodzielnie. Wszystko, czego potrzebujesz, pojawiło się w przykładzie powyżej.

Zadanie 1: Co się dzieje bez skalowania?

Zrób to, czego sekcja 2 zabrania - i zobacz skutki na własne oczy.

  1. Uruchom KMeans(n_clusters=3, n_init=10, random_state=42) na danych nieskalowanych (X, nie X_skal).
  2. Wypisz profile grup (średnie cech), tak jak w sekcji 3.
  3. Porównaj je z profilami grup na danych skalowanych.

Pytanie, na które odpowiadasz: która cecha zdecydowała o podziale? Podpowiedź: sprawdź, dla której cechy różnice średnich między grupami są największe, i porównaj to z zakresami cech z pierwszej komórki.

In [ ]:
# TWÓJ KOD TUTAJ
# Podpowiedź: schemat jest identyczny jak w sekcji 3, zmienia się tylko
# to, na czym uruchamiasz fit_predict.

Zadanie 2: Wskaźnik sylwetki dla różnych k

W sekcji 4 policzyliśmy sylwetkę dla k od 2 do 8 i zrobiliśmy to samo dla inercji.

  1. Napisz własną pętlę liczącą wskaźnik sylwetki dla k od 2 do 10.
  2. Wypisz wyniki w formie tabeli.
  3. Wskaż k o najwyższej sylwetce.
  4. Odpowiedz sobie: czy różnica między najlepszym a drugim najlepszym k jest na tyle duża, żeby uznać wybór za rozstrzygnięty?

Uwaga na czas: silhouette_score liczy odległości między parami punktów, więc przy 10 000 wierszy potrafi działać kilkanaście sekund dla każdego k. Jeśli czekasz zbyt długo, przekaż argument sample_size=2000, random_state=42 - policzy wskaźnik na losowej próbce.

In [ ]:
# TWÓJ KOD TUTAJ

Zadanie 3: Ile składowych PCA naprawdę potrzeba?

W sekcji 5 wzięliśmy dwie składowe, bo tyle mieści się na wykresie. To decyzja podyktowana rysunkiem, a nie danymi.

  1. Dopasuj PCA() bez podawania n_components - wyliczy wszystkie składowe.
  2. Wypisz wariancję wyjaśnioną przez każdą składową oraz jej sumę skumulowaną (np.cumsum).
  3. Narysuj wykres sumy skumulowanej: na osi X numer składowej, na osi Y skumulowana wariancja.
  4. Odczytaj, ile składowych wystarczy, żeby zachować 90% zmienności.
  5. Zastanów się: skoro cech jest 8, a do 90% wystarcza mniej - co to mówi o powiązaniach między cechami?
In [ ]:
# TWÓJ KOD TUTAJ
# Podpowiedź: przyda się ax.axhline(0.9, ...) jako linia odniesienia na wykresie.

Zadanie 4: Opisz grupy słowami

Liczby w tabeli profili to jeszcze nie wynik. Wynikiem jest opis zrozumiały dla zleceniodawcy.

  1. Wykonaj grupowanie na k wybrane przez Ciebie w zadaniu 2.
  2. Wypisz profile grup (średnie cech na danych oryginalnych) oraz liczebność każdej grupy.
  3. Nadaj każdej grupie własną nazwę w dwóch, trzech słowach - na przykład „młodzi o niskim BMI".
  4. Zapisz te nazwy w komórce tekstowej pod spodem wraz z krótkim uzasadnieniem: która cecha najbardziej wyróżnia tę grupę.

To zadanie nie ma jednej poprawnej odpowiedzi i o to chodzi. W uczeniu nienadzorowanym interpretacja jest częścią wyniku, a nie dodatkiem do niego.

In [ ]:
# TWÓJ KOD TUTAJ

Tutaj wpisz nazwy i opisy swoich grup (kliknij dwukrotnie, żeby edytować tę komórkę).

  • Grupa 0: ...
  • Grupa 1: ...
  • Grupa 2: ...

Zadanie 5: Grupowanie po redukcji wymiarowości

PCA bywa używane nie tylko do rysowania, ale i jako krok przygotowujący dane przed innym algorytmem.

  1. Zredukuj dane do 2 składowych (PCA(n_components=2)) i uruchom na nich KMeans z k=3.
  2. Uruchom KMeans z k=3 na pełnych ośmiu skalowanych cechach (to już masz z sekcji 3).
  3. Porównaj oba podziały: użyj adjusted_rand_score(grupy_pelne, grupy_pca).
  4. Porównaj też wskaźniki sylwetki obu podziałów - ale każdy w swojej przestrzeni (podział na PCA oceniaj na danych PCA).

Pytanie: czy redukcja do 2 wymiarów zmieniła podział zasadniczo, czy kosmetycznie? I czy wyższa sylwetka po PCA oznacza, że podział jest lepszy?

In [ ]:
# TWÓJ KOD TUTAJ

Zadanie 6: Czy przy innym k grupy lepiej pasują do choroby?

W sekcji 6 sprawdziliśmy zgodność dla k=2. Może przy innej liczbie grup byłoby lepiej?

  1. Dla k od 2 do 6 wykonaj grupowanie i policz adjusted_rand_score względem y_ukryte.
  2. Dla najlepszego k wypisz tabelę krzyżową oraz udział chorych w każdej grupie.
  3. Sprawdź, czy któraś grupa ma udział chorych wyraźnie wyższy niż średnia w zbiorze.

Pytanie do przemyślenia: gdyby któraś grupa miała 80% chorych, czy oznaczałoby to, że powstał dobry klasyfikator cukrzycy? Co jeszcze trzeba by sprawdzić?

In [ ]:
# TWÓJ KOD TUTAJ

Zadanie 7 (trudniejsze): Z czego zbudowane są główne składowe?

PC1 to kombinacja wszystkich ośmiu cech - ale z jakimi wagami? Te wagi nazywa się ładunkami (ang. loadings) i są dostępne jako pca.components_.

  1. Dopasuj PCA(n_components=2) na skalowanych danych.
  2. Zbuduj ramkę danych: wiersze to cechy (X.columns), kolumny to PC1 i PC2, wartości to pca.components_.T.
  3. Narysuj wykres słupkowy ładunków dla PC1 (przyda się ax.barh).
  4. Odpowiedz: które cechy budują pierwszą składową? Czy mają wspólny mianownik merytoryczny?
  5. Sprawdź znaki ładunków. Co oznacza, że dwie cechy mają ładunki o przeciwnych znakach?

Uwaga: znak całej składowej jest umowny - PCA mógłby zwrócić PC1 odwrócone i byłoby to równie poprawne. Znaczenie ma względny znak ładunków w obrębie jednej składowej, nie znak bezwzględny.

In [ ]:
# TWÓJ KOD TUTAJ

Pytania do przemyślenia

Na te pytania odpowiadasz słowami, nie kodem. Jeśli potrafisz odpowiedzieć na wszystkie, ćwiczenie spełniło swoje zadanie.

  1. Dlaczego w uczeniu nienadzorowanym nie ma podziału na zbiór uczący i testowy w takiej roli, jaką pełnił w ćwiczeniu 01? Czy przeuczenie w ogóle ma tu sens?
  2. Inercja zawsze maleje wraz ze wzrostem k. Dlaczego więc nie wybieramy po prostu największego możliwego k?
  3. Dlaczego drzewo decyzyjne nie potrzebuje skalowania, a k-średnich bez niego w ogóle nie działa sensownie? Co takiego robi drzewo, czego nie robi k-średnich?
  4. Grupy z k-średnich tylko częściowo pokryły się z cukrzycą. Wymień dwa różne wyjaśnienia tego faktu - jedno mówiące o algorytmie, drugie o danych.
  5. Dwie pierwsze składowe PCA wyjaśniają na przykład 45% zmienności. Co dokładnie oznacza pozostałe 55% i gdzie „podziało się" na wykresie?
  6. Kierownik prosi Cię o „pogrupowanie klientów, żeby wiedzieć, którzy odejdą". Co jest nie tak z tym poleceniem i jak je przeformułować?
  7. K-średnich zawsze znajdzie grupy, nawet w danych całkowicie losowych. Jak sprawdzić, czy znalezione grupy są czymś więcej niż artefaktem metody?

Chcesz wiedzieć więcej

W kolejnym ćwiczeniu (09 - Projekt końcowy) nie będzie już przykładu prowadzonego. Dostaniesz nowy zbiór danych, opis zadania i kryteria oceny - resztę robisz samodzielnie, łącząc wszystko z ćwiczeń 01-08.