Kurs uczenia maszynowego od zera · Lekcja 4.3 · 22 z 33 gotowych lekcji · 12 min
Kategorie i skalowanie
W skrócie
kNN na winach bez skalowania ma trafność 0,695, po standaryzacji 0,963. Wyspy zapisane jako 0, 1, 2 udają porządek: one-hot podnosi R² z 0,469 do 0,546.
Rozgrzewka Dwa pytania z wcześniejszych lekcji. Minuta, a lekcji nie blokuje.
W lekcji 1.4 kNN na pingwinach trafiał 76,7% bez skalowania i 96,1% po standaryzacji, bo masa w gramach zagłuszała pozostałe pomiary. Dziś dwie rzeczy, które psują model, zanim zacznie się uczyć. Pierwsza to cechy w bardzo różnych skalach; skalowanie sprowadza je do porównywalnego zakresu. Druga to kategorie, czyli nazwy zamiast liczb, na przykład wyspa, z której pochodzi pingwin. Kodowanie kategorii zamienia je na liczby i da się to zrobić źle.
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ź: Znacznie więcej, bo po standaryzacji w odległości liczy się każdy z 13 pomiarów
Po standaryzacji 0,963 zamiast 0,695. Bez skalowania odległość między winami to prawie sama różnica proliny. Po standaryzacji każdy pomiar ma odchylenie 1 i głosuje na równi z innymi. Min-max daje 0,958, skalowanie odporne 0,946.
Spróbuj
- Zacznij od win bez skalowania. Który pomiar zajmuje na wykresie prawie całą wysokość?
- Przełącz na min-max, standaryzację i skalowanie odporne. Ile zyskuje kNN i jak bardzo różnią się te trzy sposoby?
- Przejdź do pingwinów. Porównaj kody 0, 1, 2 alfabetycznie i w kolejności z pliku. Czy model powinien zależeć od tego, w jakiej kolejności wypiszesz wyspy?
- Wybierz one-hot. Gdzie teraz leżą linie przewidywań względem kropek?
Gdy już sprawdzisz to w labie, nazwijmy, co widzisz.
Pomysł w prostych słowach
kNN uznaje za podobne te wina, które są blisko we wszystkich 13 pomiarach naraz. Prolina ma odchylenie standardowe 314, a najmniej zmienny pomiar tylko 0,124, więc w sumie różnic prolina przykrywa całą resztę. Skalowanie wyrównuje te zakresy. Min-max przesuwa każdy pomiar do przedziału od 0 do 1, standaryzacja do średniej 0 i odchylenia 1, skalowanie odporne odejmuje medianę i dzieli przez rozstęp międzykwartylowy, więc pojedyncze skrajne wartości mniej je ruszają. Na winach kNN ma trafność 0,958, 0,963 i 0,946 zamiast 0,695. Wybór sposobu jest mniej ważny niż to, żeby skalować w ogóle.
Kategorie mają inny kłopot. Wyspa to Biscoe, Dream albo Torgersen, a regresja liniowa potrzebuje liczb. Najprostszy pomysł to kody 0, 1, 2. Liczba niesie jednak porządek i odległość: model z jedną wagą uzna, że wyspa z kodem 1 leży dokładnie w połowie między dwiema pozostałymi. W labie regresja przewiduje masę pingwina z wyspy i płci. Z kodami alfabetycznymi R² w walidacji krzyżowej wynosi 0,469. Te same kody w kolejności z pliku dają 0,193, choć dane są identyczne. One-hot daje każdej wyspie osobną kolumnę 0/1 i osobną wagę: R² 0,546, a średni błąd spada z 476 do 411 g.
Jak to policzono
- Wina: Wine (Aeberhard i Forina, 1992; UCI, licencja CC BY 4.0), 178 win, 13 pomiarów chemicznych, 3 odmiany. kNN z k = 5, walidacja krzyżowa na 5 części, powtórzona 10 razy z innym losowaniem. Skaler dopasowany tylko na części treningowej każdego podziału.
- Pingwiny: Palmer Penguins (Gorman, Williams i Fraser, 2014; pakiet palmerpenguins: Horst, Hill i Gorman, 2020), licencja CC0. 333 z 344 pingwinów: 11 nie ma wpisanej płci. Regresja liniowa: masa z wyspy i płci (jedna kolumna 0/1), walidacja krzyżowa na 5 części, 10 powtórzeń. Linie na wykresie: model dopasowany na wszystkich 333.
- R²: część zmienności masy wyjaśniona na częściach walidacyjnych (1 = wszystko, 0 = tyle co średnia). Średni błąd: średnia bezwzględna różnica w gramach.
Dlaczego to działa
Odległość w kNN to pierwiastek z sumy kwadratów różnic. Pomiar o dużym rozrzucie daje duże różnice i to one rządzą sumą. Standaryzacja dzieli każdy pomiar przez jego odchylenie, więc typowa różnica w każdym pomiarze ma podobną wielkość. Średnie, odchylenia, minima i maksima skalera są wyliczone z danych, tak jak wagi modelu. Dlatego skaler dopasowuje się tylko na części treningowej: w kodzie niżej skaler dopasowany na wszystkich winach przed walidacją daje 0,965 zamiast 0,962. Tu różnica jest mała, ale taki wynik widział część walidacyjną, więc nie jest uczciwą oceną.
Z kodami wysp regresja liniowa ma jedną wagę na całą kolumnę. Z kodu 0 na 1 i z 1 na 2 przewidywana masa zmienia się więc o tyle samo. Z kodami alfabetycznymi model przewiduje, że pingwiny z Dream ważą o 625 g mniej niż z Biscoe, a z Torgersen o kolejne 625 g mniej. W danych pingwiny z Dream i Torgersen ważą prawie tyle samo (3719 i 3709 g), a z Biscoe 4719 g. W kolejności z pliku Biscoe dostaje środkowy kod, a prosta przez trzy wyspy prawie się spłaszcza: 243 g na krok. One-hot nie zakłada żadnej kolejności: model przewiduje dla Dream i Torgersen 997 g mniej niż dla Biscoe, tak jak jest w danych.
Częsty błąd
Prawda czy fałsz: „kategorie zakoduj jako 1, 2, 3, model sobie poradzi”. Fałsz. Liczba niesie porządek i równe odstępy, których między wyspami nie ma. W labie samo przestawienie kolejności wysp zmienia R² z 0,469 na 0,193, choć dane są te same. One-hot daje 0,546 przy każdej kolejności. W kodzie niżej one-hot wygrywa z kodami alfabetycznymi w 49 z 50 części walidacyjnych.
Wyjątek to płeć: przy dwóch kategoriach jedna kolumna 0/1 wystarczy, bo nie ma środka, w którym coś mogłoby się znaleźć. Kłopot zaczyna się od trzech kategorii bez naturalnego porządku.
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
- Modele liczące odległość, jak kNN, potrzebują cech w podobnej skali: na winach 0,695 bez skalowania, 0,963 po standaryzacji.
- Min-max, standaryzacja i skalowanie odporne dają tu podobne wyniki; najważniejsze, żeby skalować w ogóle.
- Skaler dopasowuj tylko na części treningowej, w każdym podziale osobno.
- Kategorie bez porządku koduj one-hot: kody 0, 1, 2 wymuszają kolejność i równe kroki, których w danych nie ma.
Głębiej: wzór, kod, praktyka
Matematyka
Min-max: x' = (x − min) / (max − min). Standaryzacja: z = (x − x̄) / s. Skalowanie odporne: x' = (x − mediana) / IQR, gdzie IQR = Q3 − Q1, czyli rozstęp międzykwartylowy. Odległość w kNN: d(a, b) = √(Σⱼ (aⱼ − bⱼ)²); pomiar o dużym s daje duże (aⱼ − bⱼ)² i przykrywa resztę.
Kody wysp w regresji liniowej: ŷ = a + w · kod + v · samiec. Przy kodach alfabetycznych ŷ(Dream) − ŷ(Biscoe) = ŷ(Torgersen) − ŷ(Dream) = w, czyli równe kroki. One-hot: ŷ = a + w_D · [Dream] + w_T · [Torgersen] + v · samiec, gdzie [Dream] to 1 dla pingwina z Dream i 0 dla innych. Każda wyspa ma własne przesunięcie, a Biscoe jest punktem odniesienia.
W kodzie (Python)
Ten sam eksperyment w scikit-learn: kNN na winach z czterema skalowaniami w walidacji krzyżowej 10 × 5 (skaler w pipeline'ie, więc dopasowuje się w każdym podziale), skaler dopasowany na wszystkich winach dla porównania i masa pingwinów z wyspą zakodowaną na trzy sposoby.
import numpy as np
import pandas as pd
from sklearn.datasets import load_wine
from sklearn.model_selection import cross_val_score, RepeatedStratifiedKFold, RepeatedKFold
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LinearRegression
X, y = load_wine(return_X_y=True)
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=0)
acc = {}
for name, scaler in [("no scaling", None), ("min-max", MinMaxScaler()), ("standardised", StandardScaler()), ("robust", RobustScaler())]:
model = make_pipeline(scaler, KNeighborsClassifier(5)) if scaler else KNeighborsClassifier(5) # skaler w pipeline'ie: dopasowany w każdym podziale
acc[name] = cross_val_score(model, X, y, cv=cv)
print("kNN on 178 wines, 10 x 5 CV: " + ", ".join(f"{k} {v.mean():.3f}" for k, v in acc.items()))
print(f"standardised beats no scaling in {(acc['standardised'] > acc['no scaling']).sum()} of 50 folds")
leaky = cross_val_score(KNeighborsClassifier(5), StandardScaler().fit_transform(X), y, cv=cv) # skaler widział też części walidacyjne
print(f"scaler fitted on all wines before the CV: {leaky.mean():.3f}")
df = pd.read_csv("data/raw/penguins.csv").dropna(subset=["sex", "body_mass_g"])
male = (df.sex == "MALE").to_numpy(float)[:, None]
codings = {
"codes alphabetical": df.island.map({"Biscoe": 0, "Dream": 1, "Torgersen": 2}).to_numpy(float)[:, None],
"codes in file order": df.island.map({"Torgersen": 0, "Biscoe": 1, "Dream": 2}).to_numpy(float)[:, None],
"one-hot": pd.get_dummies(df.island).to_numpy(float),
}
cv2 = RepeatedKFold(n_splits=5, n_repeats=10, random_state=0)
r2 = {}
for name, isl in codings.items():
Xe = np.hstack([isl, male])
r2[name] = cross_val_score(LinearRegression(), Xe, df.body_mass_g, cv=cv2, scoring="r2")
mae = -cross_val_score(LinearRegression(), Xe, df.body_mass_g, cv=cv2, scoring="neg_mean_absolute_error").mean()
print(f"{len(df)} penguins, body mass from island + sex, {name}: R2 {r2[name].mean():.3f}, mean error {mae:.0f} g")
print(f"one-hot beats the alphabetical codes in {(r2['one-hot'] > r2['codes alphabetical']).sum()} of 50 folds")
Wynik
kNN on 178 wines, 10 x 5 CV: no scaling 0.706, min-max 0.963, standardised 0.962, robust 0.952
standardised beats no scaling in 50 of 50 folds
scaler fitted on all wines before the CV: 0.965
333 penguins, body mass from island + sex, codes alphabetical: R2 0.464, mean error 477 g
333 penguins, body mass from island + sex, codes in file order: R2 0.179, mean error 618 g
333 penguins, body mass from island + sex, one-hot: R2 0.542, mean error 412 g
one-hot beats the alphabetical codes in 49 of 50 folds
Podziały są inne niż w labie, więc liczby też są trochę inne. Obraz jest ten sam: standaryzacja wygrywa z brakiem skalowania we wszystkich 50 częściach walidacyjnych, a one-hot z kodami alfabetycznymi w 49 z 50. Kolejność kodów znów decyduje o wyniku: 0,464 alfabetycznie, 0,179 w kolejności z pliku.
Pobierz notebook (.ipynb). Otwórz go w Jupyterze albo wgraj do Google Colab; brakujące dane pobierze sam.
Dla praktyka
- W scikit-learn:
StandardScaler,MinMaxScaleriRobustScalerdla liczb,OneHotEncoder(handle_unknown="ignore")dla kategorii.ColumnTransformerwysyła kolumny liczbowe i kategoryczne do różnych przekształceń, a całość wkładasz domake_pipeline, żeby walidacja krzyżowa dopasowywała je w każdym podziale. - Drzewa decyzyjne i lasy losowe nie potrzebują skalowania: dzielą jeden pomiar progiem, a próg przesuwa się razem ze skalą.
- Porządek, który naprawdę istnieje (mały, średni, duży; poziom wykształcenia), możesz zapisać kodami, ale w jawnie podanej kolejności (
OrdinalEncoder(categories=...)). - Kategorie z setkami wartości (kod pocztowy, numer produktu) dają przy one-hot setki kolumn. Wtedy łączy się rzadkie wartości w grupę „inne” albo używa kodowania średnią celu liczonego poza foldem (hasło w Atlasie), bo liczone na całych danych przecieka.
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: