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.
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.
Wybierz odpowiedź i pewność.
Odpowiedz, żeby odblokować lab.
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.
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.
Pobierz notebook (.ipynb). Otwórz go w Jupyterze albo wgraj do Google Colab; brakujące dane pobierze sam.
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.
GridSearchCVpon_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.
Czy to było pomocne?
Dzięki! To pomaga nam poprawiać Atlas.
Więcej w Atlasie
Hasła encyklopedii o pojęciach z tej lekcji: