ML Atlas

Kurs uczenia maszynowego od zera · Lekcja 1.4 · 5 z 33 gotowych lekcji · 12 min

Najbliżsi sąsiedzi: najprostszy model

W skrócie

kNN nie uczy się reguły: nowy przypadek dostaje etykietę większości z k najbliższych przykładów. k decyduje o granicach, a jednostki o tym, kto jest blisko.

Rozgrzewka Dwa pytania z wcześniejszych lekcji. Minuta, a lekcji nie blokuje.
Z lekcji 1.3: Punkt odniesienia: z czym porównujesz model? Model regresji ma błąd 4096, a średnia 5076. O ile model jest lepszy od punktu odniesienia?

Pokaż odpowiedź

Poprawna odpowiedź: O 19,3%, bo 1 − 4096 / 5076 ≈ 0,193, czyli niewiele

Porównujesz z punktem odniesienia: prosta z BMI zmniejsza błąd o 19,3%, a model z 10 pomiarami o 44,3%.

Z lekcji 1.2: Dane, cechy, etykiety i podział zbioru Dlaczego zbioru testowego nie wolno ruszać aż do końca?

Pokaż odpowiedź

Poprawna odpowiedź: Bo szacuje wynik na nowych danych, tylko jeśli nie oparto na nim żadnej decyzji

Gdy zbiór testowy wpłynął na jakikolwiek wybór, jego wynik przestaje uczciwie zastępować przyszłe dane.

Najprostszy pomysł na model brzmi tak: podobne przypadki mają podobne odpowiedzi. Żeby rozpoznać nowego pingwina, znajdź k pingwinów do nauki, które są do niego najbardziej podobne, i zapytaj, jakiego są gatunku. To metoda k najbliższych sąsiadów (ang. k-nearest neighbours, kNN). Wracamy do pingwinów z lekcji 0.1, tym razem opisanych tylko dwoma pomiarami: długością dzioba i masą ciała.

Przewidź, potem sprawdź Przy k = 1 każdy pingwin do nauki jest swoim własnym najbliższym sąsiadem, więc model zna je wszystkie bezbłędnie. Co dzieje się z pingwinami, których nie widział?
Jak bardzo jesteś pewny(-a)?

Eksperyment

Ten interaktywny eksperyment działa w przeglądarce z włączonym JavaScriptem. Tekst wokół opisuje, co w nim widać.

Pokaż odpowiedź

Poprawna odpowiedź: Trafia mniej i średnio rzadziej niż kNN, który pyta 9 sąsiadów

Na pingwinach do nauki k = 1 ma 100%, a na testowych 94,2% w podziale 1. Średnio na 100 podziałach k = 1 trafia 92,2%, a k = 9 94,2%; k = 9 wygrywa w 78 podziałach, przegrywa w 9. Jeden sąsiad kopiuje każdy wyjątek, a głosowanie 9 sąsiadów go uśrednia.

Spróbuj

  • Lab startuje w jednostkach surowych, milimetrach i gramach, przy k = 1. Przełącz na standaryzowane: co dzieje się z granicami gatunków i z wynikiem?
  • Wciąż przy k = 1 przyjrzyj się granicom gatunków: gdzie są poszarpane?
  • Zwiększaj k. Przy jakim k granice się wygładzają, a przy jakim wynik na teście wyraźnie spada?
  • Porównaj ostatnią linię, średnią z 20 podziałów, dla k = 1 i k = 9.

Gdy już sprawdzisz to w labie, nazwijmy, co widzisz.

Pomysł w prostych słowach

kNN nie buduje żadnej reguły. „Uczenie” to zapamiętanie 239 pingwinów do nauki. Cała praca dzieje się przy pytaniu: dla nowego pingwina model liczy odległość do każdego zapamiętanego, bierze k najbliższych i oddaje głos większości. Tło w labie to właśnie ta odpowiedź w każdym punkcie wykresu.

Liczba k ustawia, jak bardzo model ufa pojedynczym przykładom. Przy k = 1 granica idzie wokół każdego pingwina, także wokół wyjątków, które leżą wśród innego gatunku. Przy dużym k, np. 101 z 239, głosuje prawie pół zbioru, granice robią się gładkie, a najrzadszy gatunek, Chinstrap, przegrywa głosowania: w podziale 1 trafność spada do 83,5%. Dobre k leży pośrodku i wybiera się je na danych, a nie na oko.

Drugie pokrętło jest mniej widoczne: jednostki. Odległość dodaje różnicę w długości dzioba do różnicy w masie. Dziób mieści się między 32,1 a 59,6 mm, a masa między 2700 a 6300 g. Bez przeskalowania różnica 100 g waży tyle, co 100 mm dzioba, więc o sąsiedztwie decyduje prawie sama masa. Standaryzacja zamienia każdy pomiar na liczbę odchyleń standardowych od średniej i oba pomiary zaczynają ważyć tyle samo.

Jak to policzono
  • Dane: Palmer Penguins (Gorman, Williams i Fraser, 2014; pakiet palmerpenguins: Horst, Hill i Gorman, 2020), licencja CC0. 342 pingwiny z kompletem pomiarów, 3 gatunki.
  • Podział: 70/30 ze stratyfikacją; podział 1 to train_test_split(test_size=0.3, stratify=y, random_state=1) w scikit-learn, ten sam co w lekcji 0.1. Do nauki 239 pingwinów, w teście 103.
  • Model: kNN z odległością euklidesową, każdy sąsiad ma jeden głos, remis rozstrzyga kolejność alfabetyczna gatunków (jak w scikit-learn).
  • Standaryzacja: średnia i odchylenie standardowe liczone tylko na pingwinach do nauki.

Dlaczego to działa

kNN działa, gdy bliskość w pomiarach oznacza podobieństwo w odpowiedzi. U pingwinów tak jest: gatunki różnią się budową, więc pingwiny o podobnym dziobie i masie zwykle należą do tego samego gatunku. Dlatego już dwa pomiary dają 94,2% wobec 43,7% przy zgadywaniu „zawsze Adelie”.

Mały i duży k psują wynik z dwóch przeciwnych stron. Przy k = 1 model jest bardzo czuły na przypadek: jeden nietypowy pingwin do nauki zmienia odpowiedź w całym swoim otoczeniu. Wynik na nauce, 100%, nic tu nie mówi, bo każdy pingwin znajduje sam siebie. Przy bardzo dużym k model przestaje widzieć kształt danych i zbliża się do odpowiedzi „najczęstszy gatunek w okolicy”. To ten sam kompromis między dopasowaniem a uogólnieniem, który wróci w module 3.

Jednostki działają tak mocno, bo kNN nie wie, który pomiar jest ważny: liczy tylko odległość. W surowych jednostkach przy k = 5 trafia 77,7% testowych pingwinów, a po standaryzacji 94,2%. Na wszystkich czterech pomiarach, z masą w gramach, różnica jest jeszcze większa: 76,7% bez skalowania i 96,1% po standaryzacji, a średnio na 100 podziałach 76,7% i 98,9%.

Częsty błąd

Prawda czy fałsz: „Najmniejsze k jest najlepsze, bo model idealnie pasuje do danych”. To nieprawda. Przy k = 1 model ma 100% na pingwinach do nauki, bo każdy jest swoim sąsiadem, ale na nowych pingwinach średnio przegrywa z k = 9: 92,2% wobec 94,2% na 100 podziałach. Idealne dopasowanie do nauki to zapamiętanie także wyjątków. Wybieraj k po wyniku na danych, których model nie widział.

Gdzie ta analogia się psuje: „sąsiedzi” w kNN to nie pingwiny stojące obok na plaży, tylko te o podobnych pomiarach. A to, kto jest podobny, zależy od jednostek, które wybierzesz.

Quiz

Trzy pytania na sprawdzenie się. Wybierz odpowiedź albo rozwiń odpowiedź pod pytaniem. Z klawiatury: strzałki wybierają, Enter albo spacja zatwierdza.

1. Jak kNN odpowiada na pytanie o nowy przypadek?
Pokaż odpowiedź

Poprawna odpowiedź: Bierze k najbliższych przykładów do nauki i wybiera ich większość

kNN nie ma reguły: głosuje k najbliższych zapamiętanych przykładów. Tło w labie pokazuje wynik tego głosowania w każdym punkcie.

2. kNN z k = 1 ma 100% na danych do nauki. Co to mówi o nowych danych?
Pokaż odpowiedź

Poprawna odpowiedź: Prawie nic, bo każdy przykład do nauki jest swoim sąsiadem

Przy k = 1 przykład do nauki zawsze znajduje sam siebie. O nowych danych mówi tylko test: średnio 92,2% przy k = 1 i 94,2% przy k = 9.

3. Dlaczego przed kNN zwykle standaryzuje się pomiary?
Pokaż odpowiedź

Poprawna odpowiedź: Bo pomiar w dużych jednostkach, np. gramach, zagłusza pozostałe

Odległość dodaje różnice w pomiarach. Masa w tysiącach gramów przykrywa dziób w milimetrach: na czterech pomiarach 76,7% bez skalowania i 96,1% po standaryzacji.

Najważniejsze wnioski

  • kNN nie buduje reguły: nowy przypadek dostaje większość głosów k najbliższych przykładów do nauki.
  • Małe k idzie za każdym wyjątkiem, duże k wygładza granice aż do zgadywania; k wybiera się na danych, których model nie widział.
  • 100% na danych do nauki przy k = 1 nic nie znaczy, bo każdy przykład jest swoim sąsiadem.
  • kNN liczy odległość, więc jednostki decydują o wyniku: standaryzuj pomiary na danych do nauki.

Głębiej: wzór, kod, praktyka

Matematyka

Odległość euklidesowa między pingwinami a i b opisanymi dwoma pomiarami to d(a, b) = √((a₁ − b₁)² + (a₂ − b₂)²). Model sortuje pingwiny do nauki po d i bierze k pierwszych; odpowiedzią jest gatunek, który ma wśród nich najwięcej głosów.

Standaryzacja zamienia pomiar x na z = (x − μ) / σ, gdzie μ i σ to średnia i odchylenie standardowe tego pomiaru na danych do nauki. Bez niej różnica 100 g w masie dodaje do d² aż 100² = 10000, a różnica 10 mm w dziobie tylko 10² = 100. Po standaryzacji każdy pomiar mierzy się w odchyleniach standardowych i oba ważą tyle samo.

Przy k = 1 każdy przykład do nauki ma odległość 0 do samego siebie, więc trafność na nauce wynosi 100%, o ile żadne dwa przykłady o różnych etykietach nie mają identycznych pomiarów.

W kodzie (Python)

Ten sam eksperyment w scikit-learn: dwa pomiary z labu w surowych jednostkach i po standaryzacji dla każdego k, wynik k = 1 na pingwinach do nauki, k = 1, 9 i 101 na 100 podziałach oraz wszystkie cztery pomiary przy k = 5. Notebook podlinkowany pod wynikiem sam pobierze dane.

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

df = pd.read_csv("data/raw/penguins.csv")
cols4 = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"]
d = df.dropna(subset=cols4)  # 342 pingwiny z kompletem czterech pomiarów
y = d["species"]
K = [1, 3, 5, 9, 15, 25, 51, 101]
def knn(k, scale):
    model = KNeighborsClassifier(n_neighbors=k)
    return make_pipeline(StandardScaler(), model) if scale else model  # skaler uczy się średniej i odchylenia tylko na części do nauki
def score(cols, k, scale, seed):
    Xtr, Xte, ytr, yte = train_test_split(d[cols], y, test_size=0.3, stratify=y, random_state=seed)
    m = knn(k, scale).fit(Xtr, ytr)
    return m.score(Xte, yte), m.score(Xtr, ytr)
lab = ["bill_length_mm", "body_mass_g"]  # dwa pomiary z labu: milimetry i gramy
for scale in (False, True):
    print(("standardized:" if scale else "raw mm and g:"), " ".join(f"k={k} {score(lab, k, scale, 1)[0]:.3f}" for k in K))
print(f"k=1 on the learning penguins: {score(lab, 1, True, 1)[1]:.3f}")
runs = {k: np.array([score(lab, k, True, s)[0] for s in range(100)]) for k in (1, 9, 101)}
print(f"100 splits, standardized: k=1 {runs[1].mean():.3f}, k=9 {runs[9].mean():.3f}, k=101 {runs[101].mean():.3f}; k=9 beats k=1 in {(runs[9] > runs[1]).sum()}, loses in {(runs[9] < runs[1]).sum()}")
four = [[score(cols4, 5, scale, s)[0] for s in range(100)] for scale in (False, True)]
print(f"all 4 measurements, k=5: raw {four[0][1]:.3f}, standardized {four[1][1]:.3f} on split 1; mean of 100 splits {np.mean(four[0]):.3f} and {np.mean(four[1]):.3f}")

Wynik

raw mm and g: k=1 0.835 k=3 0.748 k=5 0.777 k=9 0.757 k=15 0.777 k=25 0.748 k=51 0.738 k=101 0.738
standardized: k=1 0.942 k=3 0.922 k=5 0.942 k=9 0.942 k=15 0.942 k=25 0.942 k=51 0.942 k=101 0.835
k=1 on the learning penguins: 1.000
100 splits, standardized: k=1 0.922, k=9 0.942, k=101 0.856; k=9 beats k=1 in 78, loses in 9
all 4 measurements, k=5: raw 0.767, standardized 0.961 on split 1; mean of 100 splits 0.767 and 0.989

Na podziale 1 wyniki są takie same jak w labie. Lab liczy średnią z 20 własnych podziałów, a ten kod ze 100 podziałów scikit-learn, dlatego średnie trochę się różnią, ale kierunek jest ten sam: k = 9 średnio bije k = 1.

Dla praktyka
  • Skaluj w potoku: make_pipeline(StandardScaler(), KNeighborsClassifier()). Skaler dopasowany na całym zbiorze przed podziałem przemyca do nauki informację z testu.
  • Wybieraj k walidacją krzyżową, np. GridSearchCV po n_neighbors; przy dwóch klasach nieparzyste k unika remisów.
  • weights="distance" daje bliższym sąsiadom więcej głosu; to często pomaga przy dużym k.
  • kNN odpowiada wolno przy dużych danych, bo liczy odległość do każdego przykładu, i słabnie przy wielu pomiarach, gdy wszystkie odległości robią się podobne.
  • Pomiary nieistotne dla zadania też wchodzą do odległości i psują sąsiedztwo; wybierz cechy, zanim użyjesz kNN.

Więcej w Atlasie

Hasła encyklopedii o pojęciach z tej lekcji: