Introduktion
K-nærmeste naboer (KNN) Algoritme er en type overvåget maskinlæringsalgoritme, der bruges til klassificering, regression samt afvigende detektion. Det er ekstremt nemt at implementere i sin mest basale form, men kan udføre ret komplekse opgaver. Det er en doven læringsalgoritme, da den ikke har en specialiseret træningsfase. Tværtimod bruger den alle data til træning, mens den klassificerer (eller regresserer) et nyt datapunkt eller en instans.
KNN er en ikke-parametrisk indlæringsalgoritme, hvilket betyder, at den ikke antager noget om de underliggende data. Dette er en yderst nyttig funktion, da de fleste af de virkelige data ikke rigtig følger nogen teoretisk antagelse, f.eks. lineær adskillelighed, ensartet fordeling osv.
I denne guide vil vi se, hvordan KNN kan implementeres med Pythons Scikit-Learn-bibliotek. Inden da vil vi først undersøge, hvordan vi kan bruge KNN og forklare teorien bag det. Derefter tager vi et kig på California Housing datasæt vi vil bruge til at illustrere KNN-algoritmen og flere af dens variationer. Først og fremmest tager vi et kig på, hvordan man implementerer KNN-algoritmen for regression, efterfulgt af implementeringer af KNN-klassifikationen og outlier-detektion. Til sidst vil vi afslutte med nogle af fordele og ulemper ved algoritmen.
Hvornår skal du bruge KNN?
Antag, at du gerne ville leje en lejlighed og for nylig fandt ud af, at din vens nabo kunne sætte hendes lejlighed til leje om 2 uger. Da lejligheden endnu ikke er på et udlejningswebsted, hvordan kan du så prøve at anslå dens lejeværdi?
Lad os sige, at din ven betaler $1,200 i husleje. Din lejeværdi kan være omkring det tal, men lejlighederne er ikke helt ens (orientering, areal, møbelkvalitet osv.), så det ville være rart at have flere data om andre lejligheder.
Ved at spørge andre naboer og se på lejlighederne fra den samme bygning, som var opført på en udlejningshjemmeside, er de nærmeste tre nabolejligheder $1,200, $1,210, $1,210 og $1,215. Disse lejligheder er på samme blok og etage som din vens lejlighed.
Andre lejligheder, der er længere væk, på samme etage, men i en anden blok, har husleje på $1,400, $1,430, $1,500 og $1,470. Det ser ud til, at de er dyrere på grund af mere lys fra solen om aftenen.
I betragtning af lejlighedens nærhed ser det ud til, at din anslåede husleje ville være omkring $1,210. Det er den generelle idé om, hvad K-Nearest Neighbours (KNN) algoritmen gør! Den klassificerer eller regresserer nye data baseret på deres nærhed til allerede eksisterende data.
Oversæt eksemplet til teori
Når den estimerede værdi er et kontinuerligt tal, såsom lejeværdien, bruges KNN til regression. Men vi kunne også inddele lejligheder i kategorier baseret på f.eks. minimum- og maksimumleje. Når værdien er diskret, hvilket gør den til en kategori, bruges KNN til klassificering.
Der er også mulighed for at vurdere, hvilke naboer der er så forskellige fra andre, at de nok holder op med at betale husleje. Dette er det samme som at opdage, hvilke datapunkter der er så langt væk, at de ikke passer ind i nogen værdi eller kategori, når det sker, bruges KNN til afvigende påvisning.
I vores eksempel kendte vi også allerede huslejen for hver lejlighed, hvilket betyder, at vores data var mærket. KNN bruger tidligere mærkede data, hvilket gør det til en overvåget læringsalgoritme.
KNN er ekstremt let at implementere i sin mest basale form, og udfører alligevel ret komplekse klassifikations-, regression- eller outlier-detektionsopgaver.
Hver gang der tilføjes et nyt punkt til dataene, bruger KNN kun én del af dataene til at bestemme værdien (regression) eller klasse (klassificering) af det tilføjede punkt. Da det ikke behøver at se på alle punkterne igen, gør det det til en doven læringsalgoritme.
KNN antager heller ikke noget om de underliggende datakarakteristika, det forventer ikke, at dataene passer ind i en eller anden form for distribution, såsom ensartet, eller at de er lineært adskillelige. Det betyder, at det er en ikke-parametrisk indlæringsalgoritme. Dette er en ekstremt nyttig funktion, da de fleste af de virkelige data ikke rigtig følger nogen teoretisk antagelse.
Visualisering af forskellige anvendelser af KNN
Som det er blevet vist, er intuitionen bag KNN-algoritmen en af de mest direkte af alle overvågede maskinlæringsalgoritmer. Algoritmen beregner først afstand af et nyt datapunkt til alle andre træningsdatapunkter.
Bemærk: Afstanden kan måles på forskellige måder. Du kan bruge en Minkowski, Euklidisk, Manhattan, Mahalanobis eller Hamming-formlen, for at nævne nogle få målinger. Med højdimensionelle data begynder den euklidiske afstand ofte at svigte (høj dimensionalitet er... underligt), og Manhattan-afstand bruges i stedet.
Efter at have beregnet afstanden, vælger KNN et antal nærmeste datapunkter – 2, 3, 10 eller i virkeligheden et hvilket som helst heltal. Dette antal point (2, 3, 10 osv.) er K i K-Nærmeste Naboer!
I det sidste trin, hvis det er en regressionsopgave, vil KNN beregne den gennemsnitlige vægtede sum af de K-nærmeste punkter for forudsigelsen. Hvis det er en klassifikationsopgave, vil det nye datapunkt blive tildelt den klasse, som størstedelen af de valgte K-nærmeste punkter tilhører.
Lad os visualisere algoritmen i aktion ved hjælp af et simpelt eksempel. Overvej et datasæt med to variable og en K på 3.
Når der udføres regression, er opgaven at finde værdien af et nyt datapunkt, baseret på den gennemsnitlige vægtede sum af de 3 nærmeste punkter.
KNN med K = 3
, Når bruges til regression:
KNN-algoritmen starter med at beregne afstanden af det nye punkt fra alle punkterne. Den finder så de 3 punkter med mindst afstand til det nye punkt. Dette er vist i den anden figur ovenfor, hvor de tre nærmeste punkter, 47
, 58
og 79
er blevet omringet. Derefter beregner den den vægtede sum af 47
, 58
, 79
– i dette tilfælde er vægtene lig med 1 – vi betragter alle punkter som lige, men vi kunne også tildele forskellige vægte baseret på afstand. Efter beregning af den vægtede sum er den nye pointværdi 61,33
.
Og når du udfører en klassificering, skal KNN-opgaven klassificere et nyt datapunkt i "Purple"
or "Red"
klasse.
KNN med K = 3
, Når bruges til klassificering:
KNN-algoritmen starter på samme måde som før, ved at beregne afstanden af det nye punkt fra alle punkterne, finde de 3 nærmeste punkter med den mindste afstand til det nye punkt, og derefter, i stedet for at beregne et tal, tildeler den det nye punkt til klassen, som flertallet af de tre nærmeste punkter tilhører, den røde klasse. Derfor vil det nye datapunkt blive klassificeret som "Red"
.
Outlier-detektionsprocessen er forskellig fra begge ovenstående, vi vil tale mere om den, når vi implementerer den efter regressions- og klassifikationsimplementeringerne.
Bemærk: Koden i denne vejledning er blevet udført og testet med følgende Jupyter notesbog.
Scikit-Learn California Housing Dataset
Vi vil bruge Californien boligdatasæt for at illustrere, hvordan KNN-algoritmen fungerer. Datasættet blev afledt fra 1990 US-folketællingen. En række af datasættet repræsenterer folketællingen for én blokgruppe.
I dette afsnit gennemgår vi detaljerne i California Housing Dataset, så du kan få en intuitiv forståelse af de data, vi vil arbejde med. Det er meget vigtigt at lære dine data at kende, før du begynder at arbejde på dem.
A blokere gruppe er den mindste geografiske enhed, som US Census Bureau udgiver prøvedata for. Udover blokgruppe er et andet anvendt udtryk husholdning, en husstand er en gruppe mennesker, der bor i et hjem.
Datasættet består af ni attributter:
MedInc
– medianindkomst i blokgruppeHouseAge
– median husalder i en blokgruppeAveRooms
– det gennemsnitlige antal værelser (leveret pr. husstand)AveBedrms
– det gennemsnitlige antal soveværelser (leveret pr. husstand)Population
– blokgruppepopulationAveOccup
– det gennemsnitlige antal husstandsmedlemmerLatitude
– blokgruppebreddegradLongitude
– blokgruppe længdegradMedHouseVal
– median husværdi for distrikter i Californien (hundredetusindvis af dollars)
Datasættet er allerede en del af Scikit-Learn-biblioteket, vi behøver kun at importere det og indlæse det som en dataramme:
from sklearn.datasets import fetch_california_housing
california_housing = fetch_california_housing(as_frame=True)
df = california_housing.frame
Import af data direkte fra Scikit-Learn importerer mere end kun kolonnerne og tallene og inkluderer databeskrivelsen som en Bunch
objekt – så vi har lige udtrukket frame
. Yderligere oplysninger om datasættet er tilgængelige link..
Lad os importere pandaer og tage et kig på de første par rækker af data:
import pandas as pd
df.head()
Udførelse af koden vil vise de første fem rækker af vores datasæt:
MedInc HouseAge AveRooms AveBedrms Population AveOccup Latitude Longitude MedHouseVal
0 8.3252 41.0 6.984127 1.023810 322.0 2.555556 37.88 -122.23 4.526
1 8.3014 21.0 6.238137 0.971880 2401.0 2.109842 37.86 -122.22 3.585
2 7.2574 52.0 8.288136 1.073446 496.0 2.802260 37.85 -122.24 3.521
3 5.6431 52.0 5.817352 1.073059 558.0 2.547945 37.85 -122.25 3.413
4 3.8462 52.0 6.281853 1.081081 565.0 2.181467 37.85 -122.25 3.422
I denne guide vil vi bruge MedInc
, HouseAge
, AveRooms
, AveBedrms
, Population
, AveOccup
, Latitude
, Longitude
at forudse MedHouseVal
. Noget der ligner vores motivationsfortælling.
Lad os nu springe direkte ind i implementeringen af KNN-algoritmen for regression.
Regression med K-Nærmeste Naboer med Scikit-Learn
Indtil videre har vi lært vores datasæt at kende og kan nu fortsætte til andre trin i KNN-algoritmen.
Forbehandling af data til KNN-regression
Forbehandlingen er, hvor de første forskelle mellem regressions- og klassifikationsopgaverne optræder. Da dette afsnit handler om regression, vil vi forberede vores datasæt i overensstemmelse hermed.
Til regression skal vi forudsige en anden median husværdi. For at gøre det, vil vi tildele MedHouseVal
til y
og alle andre kolonner til X
bare ved at droppe MedHouseVal
:
y = df['MedHouseVal']
X = df.drop(['MedHouseVal'], axis = 1)
Ved at se på vores variabelbeskrivelser kan vi se, at vi har forskelle i målinger. For at undgå at gætte, lad os bruge describe()
metode til at kontrollere:
X.describe().T
Dette resulterer i:
count mean std min 25% 50% 75% max
MedInc 20640.0 3.870671 1.899822 0.499900 2.563400 3.534800 4.743250 15.000100
HouseAge 20640.0 28.639486 12.585558 1.000000 18.000000 29.000000 37.000000 52.000000
AveRooms 20640.0 5.429000 2.474173 0.846154 4.440716 5.229129 6.052381 141.909091
AveBedrms 20640.0 1.096675 0.473911 0.333333 1.006079 1.048780 1.099526 34.066667
Population 20640.0 1425.476744 1132.462122 3.000000 787.000000 1166.000000 1725.000000 35682.000000
AveOccup 20640.0 3.070655 10.386050 0.692308 2.429741 2.818116 3.282261 1243.333333
Latitude 20640.0 35.631861 2.135952 32.540000 33.930000 34.260000 37.710000 41.950000
Longitude 20640.0 -119.569704 2.003532 -124.350000 -121.800000 -118.490000 -118.010000 -114.310000
Her kan vi se, at mean
værdi af MedInc
er ca 3.87
og mean
værdi af HouseAge
er om 28.64
, hvilket gør den 7.4 gange større end MedInc
. Andre funktioner har også forskelle i middelværdi og standardafvigelse - for at se det, se på mean
, std
værdier og observere, hvordan de er fjerne fra hinanden. Til MedInc
std
er ca 1.9
, For HouseAge
, std
is 12.59
og det samme gælder for de andre funktioner.
Vi bruger en algoritme baseret på afstand og afstandsbaserede algoritmer lider meget under data, der ikke er i samme skala, såsom disse data. Punkternes skala kan (og gør det i praksis næsten altid) forvrænge den reelle afstand mellem værdier.
For at udføre funktionsskalering vil vi bruge Scikit-Learn's StandardScaler
klasse senere. Hvis vi anvender skaleringen lige nu (før en tog-test split), vil beregningen inkludere testdata, effektivt utæt testdataoplysninger ind i resten af pipelinen. Den slags datalækage er desværre ofte sprunget over, hvilket resulterer i irreproducerbare eller illusoriske fund.
Opdeling af data i tog- og testsæt
For at være i stand til at skalere vores data uden lækage, men også for at evaluere vores resultater og for at undgå overpasning, opdeler vi vores datasæt i tog- og testopdelinger.
En ligetil måde at skabe tog- og testsplit er train_test_split
metode fra Scikit-Learn. Opdelingen opdeles ikke lineært på et tidspunkt, men prøver X% og Y% tilfældigt. For at gøre denne proces reproducerbar (for at få metoden til altid at prøve de samme datapunkter), indstiller vi random_state
argument til et bestemt SEED
:
from sklearn.model_selection import train_test_split
SEED = 42
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=SEED)
Dette stykke kode prøver 75 % af dataene til træning og 25 % af dataene til test. Ved at ændre test_size
til 0.3, for eksempel, kan du træne med 70 % af dataene og teste med 30 %.
Ved at bruge 75 % af dataene til træning og 25 % til test, ud af 20640 poster, indeholder træningssættet 15480 og testsættet indeholder 5160. Vi kan inspicere disse tal hurtigt ved at udskrive længden af det fulde datasæt og opdelte data :
len(X)
len(X_train)
len(X_test)
Store! Vi kan nu tilpasse dataskaleren på X_train
sæt og skaler begge X_train
, X_test
uden at lække nogen data fra X_test
ind X_train
.
Funktionsskalering til KNN-regression
Ved at importere StandardScaler
, instansierer det, tilpasser det i henhold til vores togdata (forhindrer lækage) og transformerer både tog- og testdatasæt, kan vi udføre funktionsskalering:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
Bemærk: Da du ofte ringer scaler.fit(X_train)
efterfulgt af scaler.transform(X_train)
– du kan ringe til en single scaler.fit_transform(X_train)
efterfulgt af scaler.transform(X_test)
for at gøre opkaldet kortere!
Nu er vores data skaleret! Skaleren vedligeholder kun datapunkterne og ikke kolonnenavnene, når de anvendes på en DataFrame
. Lad os organisere dataene i en DataFrame igen med kolonnenavne og brug describe()
at observere ændringerne i mean
, std
:
col_names=['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude']
scaled_df = pd.DataFrame(X_train, columns=col_names)
scaled_df.describe().T
Dette vil give os:
count mean std min 25% 50% 75% max
MedInc 15480.0 2.074711e-16 1.000032 -1.774632 -0.688854 -0.175663 0.464450 5.842113
HouseAge 15480.0 -1.232434e-16 1.000032 -2.188261 -0.840224 0.032036 0.666407 1.855852
AveRooms 15480.0 -1.620294e-16 1.000032 -1.877586 -0.407008 -0.083940 0.257082 56.357392
AveBedrms 15480.0 7.435912e-17 1.000032 -1.740123 -0.205765 -0.108332 0.007435 55.925392
Population 15480.0 -8.996536e-17 1.000032 -1.246395 -0.558886 -0.227928 0.262056 29.971725
AveOccup 15480.0 1.055716e-17 1.000032 -0.201946 -0.056581 -0.024172 0.014501 103.737365
Latitude 15480.0 7.890329e-16 1.000032 -1.451215 -0.799820 -0.645172 0.971601 2.953905
Longitude 15480.0 2.206676e-15 1.000032 -2.380303 -1.106817 0.536231 0.785934 2.633738
Bemærk, hvordan alle standardafvigelser er nu 1
og midlerne er blevet mindre. Det er det, der gør vores data mere ensartet! Lad os træne og evaluere en KNN-baseret regressor.
Træning og forudsigelse af KNN-regression
Scikit-Learns intuitive og stabile API gør træning af regressorer og klassifikatorer meget ligetil. Lad os importere KNeighborsRegressor
klasse fra sklearn.neighbors
modul, instansier det og tilpas det til vores togdata:
from sklearn.neighbors import KNeighborsRegressor
regressor = KNeighborsRegressor(n_neighbors=5)
regressor.fit(X_train, y_train)
I ovenstående kode er n_neighbors
er værdien for K, eller antallet af naboer algoritmen vil tage i betragtning ved valg af en ny medianhusværdi. 5
er standardværdien for KNeighborsRegressor()
. Der er ingen ideel værdi for K, og den vælges efter test og evaluering, men for at starte ud, 5
er en almindeligt brugt værdi for KNN og blev således sat som standardværdi.
Det sidste trin er at lave forudsigelser på vores testdata. For at gøre det skal du udføre følgende script:
y_pred = regressor.predict(X_test)
Vi kan nu vurdere, hvor godt vores model generaliserer til nye data, som vi har etiketter (grundsandhed) for – testsættet!
Evaluering af algoritmen for KNN-regression
De mest almindeligt anvendte regressionsmetrikker til at evaluere algoritmen er middel absolut fejl (MAE), middelkvadratfejl (MSE), rodmiddelkvadratfejl (RMSE) og bestemmelseskoefficient (R2):
- Gennemsnitlig absolut fejl (MAE): Når vi trækker de forudsagte værdier fra de faktiske værdier, opnår vi fejlene, summerer de absolutte værdier af disse fejl og får deres middelværdi. Denne metrik giver en forestilling om den samlede fejl for hver forudsigelse af modellen, jo mindre (tættere på 0) jo bedre:
$$
mae = (frac{1}{n})sum_{i=1}^{n}venstre | Faktisk – forudsagt rigtigt |
$$
Bemærk: Du kan også støde på y
, ŷ
(læses som y-hat) notation i ligningerne. Det y
henviser til de faktiske værdier og ŷ
til de forudsagte værdier.
- Mean Squared Error (MSE): Den ligner MAE-metrikken, men den kvadrerer de absolutte værdier af fejlene. Ligesom med MAE, jo mindre, eller tættere på 0, jo bedre. MSE-værdien er kvadreret for at gøre store fejl endnu større. En ting at være meget opmærksom på, det er, at det normalt er svært at fortolke på grund af størrelsen af dens værdier og det faktum, at de ikke er på samme skala som dataene.
$$
mse = sum_{i=1}^{D}(Faktisk – forudsagt)^2
$$
- Root Mean Squared Error (RMSE): Forsøger at løse fortolkningsproblemet, der er rejst med MSE ved at få kvadratroden af dens endelige værdi, for at skalere det tilbage til de samme enheder af dataene. Det er lettere at fortolke og godt, når vi skal vise eller vise den faktiske værdi af dataene med fejlen. Det viser, hvor meget dataene kan variere, så hvis vi har en RMSE på 4.35, kan vores model lave en fejl, enten fordi den tilføjede 4.35 til den faktiske værdi eller havde brug for 4.35 for at nå den faktiske værdi. Jo tættere på 0, jo bedre også.
$$
rmse = sqrt{ sum_{i=1}^{D}(Faktisk – forudsagt)^2}
$$
mean_absolute_error()
, mean_squared_error()
fremgangsmåder til sklearn.metrics
kan bruges til at beregne disse metrics, som det kan ses i følgende uddrag:
from sklearn.metrics import mean_absolute_error, mean_squared_error
mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = mean_squared_error(y_test, y_pred, squared=False)
print(f'mae: {mae}')
print(f'mse: {mse}')
print(f'rmse: {rmse}')
Outputtet af ovenstående script ser sådan ud:
mae: 0.4460739527131783
mse: 0.4316907430948294
rmse: 0.6570317671884894
R2 kan beregnes direkte med score()
metode:
regressor.score(X_test, y_test)
Hvilken udgang:
0.6737569252627673
Resultaterne viser, at vores KNN-algoritme overordnede fejl og middelfejl er omkring 0.44
og 0.43
. RMSE viser også, at vi kan gå over eller under den faktiske værdi af data ved at tilføje 0.65
eller trække fra 0.65
. Hvor godt er det?
Lad os se, hvordan priserne ser ud:
y.describe()
count 20640.000000
mean 2.068558
std 1.153956
min 0.149990
25% 1.196000
50% 1.797000
75% 2.647250
max 5.000010
Name: MedHouseVal, dtype: float64
Middelværdien er 2.06
og standardafvigelsen fra middelværdien er 1.15
så vores score på ~0.44
er ikke rigtig fantastisk, men er ikke så slemt.
Med R2, jo tættest på 1 vi kommer (eller 100), jo bedre. R2 fortæller hvor meget af ændringerne i data eller data varians bliver forstået eller forklarede af KNN.
$$
R^2 = 1 – frac{sum(Faktisk – Forudsagt)^2}{sum(Faktisk – Faktisk gennemsnit)^2}
$$
Med en værdi på 0.67
, kan vi se, at vores model forklarer 67 % af datavariansen. Det er allerede mere end 50%, hvilket er ok, men ikke særlig godt. Er der nogen måde, vi kan gøre det bedre?
Vi har brugt et forudbestemt K med en værdi på 5
, så vi bruger 5 naboer til at forudsige vores mål, hvilket ikke nødvendigvis er det bedste antal. For at forstå, hvad der ville være et ideelt antal K'er, kan vi analysere vores algoritmefejl og vælge den K, der minimerer tabet.
At finde den bedste K til KNN-regression
Ideelt set ville du se, hvilken metrik der passer bedst ind i din kontekst – men det er normalt interessant at teste alle metrics. Når du kan teste dem alle, så gør det. Her vil vi vise, hvordan du vælger den bedste K ved kun at bruge den gennemsnitlige absolutte fejl, men du kan ændre den til enhver anden metrik og sammenligne resultaterne.
For at gøre dette vil vi oprette en for loop og køre modeller, der har fra 1 til X naboer. Ved hver interaktion vil vi beregne MAE og plotte antallet af K'er sammen med MAE-resultatet:
error = []
for i in range(1, 40):
knn = KNeighborsRegressor(n_neighbors=i)
knn.fit(X_train, y_train)
pred_i = knn.predict(X_test)
mae = mean_absolute_error(y_test, pred_i)
error.append(mae)
Lad os nu plotte error
s:
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(range(1, 40), error, color='red',
linestyle='dashed', marker='o',
markerfacecolor='blue', markersize=10)
plt.title('K Value MAE')
plt.xlabel('K Value')
plt.ylabel('Mean Absolute Error')
Ser man på plottet, ser det ud til, at den laveste MAE-værdi er, når K er 12
. Lad os se nærmere på plottet for at være sikre ved at plotte færre data:
plt.figure(figsize=(12, 6))
plt.plot(range(1, 15), error[:14], color='red',
linestyle='dashed', marker='o',
markerfacecolor='blue', markersize=10)
plt.title('K Value MAE')
plt.xlabel('K Value')
plt.ylabel('Mean Absolute Error')
Tjek vores praktiske, praktiske guide til at lære Git, med bedste praksis, brancheaccepterede standarder og inkluderet snydeark. Stop med at google Git-kommandoer og faktisk lærer det!
Du kan også få den laveste fejl og indekset for dette punkt ved hjælp af den indbyggede min()
funktion (virker på lister) eller konverter listen til et NumPy-array og få argmin()
(indeks for elementet med den laveste værdi):
import numpy as np
print(min(error))
print(np.array(error).argmin())
Vi begyndte at tælle naboer på 1, mens arrays er 0-baserede, så det 11. indeks er 12 naboer!
Det betyder, at vi skal bruge 12 naboer for at kunne forudsige et punkt med den laveste MAE-fejl. Vi kan udføre modellen og metrics igen med 12 naboer for at sammenligne resultater:
knn_reg12 = KNeighborsRegressor(n_neighbors=12)
knn_reg12.fit(X_train, y_train)
y_pred12 = knn_reg12.predict(X_test)
r2 = knn_reg12.score(X_test, y_test)
mae12 = mean_absolute_error(y_test, y_pred12)
mse12 = mean_squared_error(y_test, y_pred12)
rmse12 = mean_squared_error(y_test, y_pred12, squared=False)
print(f'r2: {r2}, nmae: {mae12} nmse: {mse12} nrmse: {rmse12}')
Følgende kode udsendes:
r2: 0.6887495617137436,
mae: 0.43631325936692505
mse: 0.4118522151025172
rmse: 0.6417571309323467
Med 12 naboer forklarer vores KNN-model nu 69 % af variansen i dataene og har mistet lidt mindre, fra 0.44
til 0.43
, 0.43
til 0.41
og 0.65
til 0.64
med de respektive målinger. Det er ikke en særlig stor forbedring, men det er ikke desto mindre en forbedring.
Bemærk: Hvis man går videre i denne analyse, kan en Exploratory Data Analysis (EDA) sammen med restanalyse hjælpe med at vælge funktioner og opnå bedre resultater.
Vi har allerede set, hvordan man bruger KNN til regression – men hvad nu hvis vi ville klassificere et punkt i stedet for at forudsige dets værdi? Nu kan vi se på, hvordan man bruger KNN til klassificering.
Klassificering ved hjælp af K-Nearest Neighbors med Scikit-Learn
I denne opgave ønsker vi i stedet for at forudsige en kontinuerlig værdi at forudsige den klasse, som disse blokgrupper tilhører. For at gøre det kan vi opdele den gennemsnitlige husværdi for distrikter i grupper med forskellige husværdiintervaller eller siloer.
Når du vil bruge en kontinuert værdi til klassificering, kan du som regel bin dataene. På denne måde kan du forudsige grupper i stedet for værdier.
Forbehandling af data til klassificering
Lad os oprette databakkerne for at omdanne vores kontinuerlige værdier til kategorier:
df["MedHouseValCat"] = pd.qcut(df["MedHouseVal"], 4, retbins=False, labels=[1, 2, 3, 4])
Derefter kan vi opdele vores datasæt i dets attributter og etiketter:
y = df['MedHouseValCat']
X = df.drop(['MedHouseVal', 'MedHouseValCat'], axis = 1)
Siden vi har brugt MedHouseVal
kolonne for at oprette skraldespande, skal vi droppe MedHouseVal
kolonne og MedHouseValCat
kolonner fra X
. På denne måde DataFrame
vil indeholde de første 8 kolonner i datasættet (dvs. attributter, funktioner), mens vores y
vil kun indeholde MedHouseValCat
tildelt etiket.
Bemærk: Du kan også vælge kolonner vha .iloc()
i stedet for at droppe dem. Når du dropper, skal du blot være opmærksom på, at du skal tildele y
værdier før tildeling X
værdier, fordi du ikke kan tildele en droppet kolonne af en DataFrame
til et andet objekt i hukommelsen.
Opdeling af data i tog- og testsæt
Da det er blevet gjort med regression, vil vi også opdele datasættet i trænings- og testopdelinger. Da vi har forskellige data, skal vi gentage denne proces:
from sklearn.model_selection import train_test_split
SEED = 42
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=SEED)
Vi vil bruge standard Scikit-Learn-værdien på 75 % togdata og 25 % testdata igen. Det betyder, at vi vil have det samme tog- og testantal af rekorder som i regressionen før.
Funktionsskalering til klassificering
Da vi har at gøre med det samme ubehandlede datasæt og dets varierende måleenheder, vil vi udføre funktionsskalering igen, på samme måde som vi gjorde for vores regressionsdata:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
Træning og forudsigelse til klassificering
Efter at have binning, opdeling og skalering af dataene kan vi endelig passe en klassificering på den. Til forudsigelsen vil vi bruge 5 naboer igen som udgangspunkt. Du kan også instansiere KNeighbors_
klasse uden argumenter, og den vil automatisk bruge 5 naboer. Her, i stedet for at importere KNeighborsRegressor
, vil vi importere KNeighborsClassifier
, klasse:
from sklearn.neighbors import KNeighborsClassifier
classifier = KNeighborsClassifier()
classifier.fit(X_train, y_train)
Efter montering af KNeighborsClassifier
, kan vi forudsige klasserne af testdataene:
y_pred = classifier.predict(X_test)
Tid til at evaluere forudsigelserne! Ville forudsige klasser være en bedre tilgang end at forudsige værdier i dette tilfælde? Lad os evaluere algoritmen for at se, hvad der sker.
Evaluering af KNN for klassificering
Til evaluering af KNN-klassifikatoren kan vi også bruge score
metode, men den udfører en anden metrik, da vi scorer en klassifikator og ikke en regressor. Den grundlæggende metrik for klassificering er accuracy
– den beskriver, hvor mange forudsigelser vores klassificerer fik rigtige. Den laveste nøjagtighedsværdi er 0, og den højeste er 1. Vi gange normalt denne værdi med 100 for at få en procentdel.
$$
nøjagtighed = frac{tekst{antal korrekte forudsigelser}}{tekst{samlet antal forudsigelser}}
$$
Bemærk: Det er ekstremt svært at opnå 100 % nøjagtighed på nogen reelle data, hvis det sker, skal du være opmærksom på, at der kan ske en lækage eller noget forkert – der er ingen konsensus om en ideel nøjagtighedsværdi, og den er også kontekstafhængig. Afhængigt af omkostninger ved fejl (hvor slemt det er, hvis vi stoler på klassificereren, og det viser sig at være forkert), kan en acceptabel fejlrate være 5 %, 10 % eller endda 30 %.
Lad os score vores klassificering:
acc = classifier.score(X_test, y_test)
print(acc)
Ved at se på den resulterende score kan vi udlede, at vores klassificering fik ~62% af vores klasser rigtige. Dette hjælper allerede i analysen, selvom det er svært at forbedre det ved kun at vide, hvad klassificereren fik rigtigt.
Der er 4 klasser i vores datasæt - hvad nu hvis vores klassifikator fik 90 % af klasse 1, 2 og 3 højre, men kun 30% af klasse 4 højre?
Et systemisk svigt af en eller anden klasse, i modsætning til et afbalanceret svigt, der deles mellem klasser, kan begge give en nøjagtighedsscore på 62 %. Nøjagtighed er ikke en rigtig god målestok for faktisk evaluering – men fungerer som en god proxy. Oftere end ikke, med afbalancerede datasæt, er en nøjagtighed på 62 % relativt jævnt fordelt. Desuden er datasæt oftere end ikke afbalancerede, så vi er tilbage ved udgangspunktet, hvor nøjagtigheden er utilstrækkelig.
Vi kan se dybere ind i resultaterne ved hjælp af andre målinger for at kunne bestemme det. Dette trin er også forskelligt fra regression, her vil vi bruge:
- Forvirringsmatrix: At vide, hvor meget vi fik ret eller forkert for hver klasse. De værdier, der var korrekte og korrekt forudsagte, kaldes sande positive dem, der blev forudsagt som positive, men ikke var positive, kaldes falske positive. Den samme nomenklatur af sande negativer , falske negativer bruges til negative værdier;
- Precision: For at forstå, hvilke korrekte forudsigelsesværdier, der blev betragtet som korrekte af vores klassifikator. Præcision vil dividere disse sande positive værdier med alt, der blev forudsagt som positivt;
$$
præcision = frac{tekst{sand positiv}}{tekst{sand positiv} + tekst{falsk positiv}}
$$
- Recall: for at forstå, hvor mange af de sande positive, der blev identificeret af vores klassifikator. Tilbagekaldelsen beregnes ved at dividere de sande positive med alt, der skulle have været forudsagt som positivt.
$$
recall = frac{tekst{sand positiv}}{tekst{sand positiv} + tekst{falsk negativ}}
$$
- f1 score: Er den balancerede eller harmonisk middel af præcision og genkaldelse. Den laveste værdi er 0 og den højeste er 1. Hvornår
f1-score
er lig med 1, betyder det, at alle klasser var korrekt forudsagt – dette er en meget svær score at opnå med rigtige data (undtagelser findes næsten altid).
$$
tekst{f1-score} = 2* frac{tekst{præcision} * tekst{recall}}{tekst{præcision} + tekst{recall}}
$$
Bemærk: Der findes også en vægtet F1-score, og det er bare en F1, der ikke tillægger samme vægt til alle klasser. Vægten er typisk dikteret af klasserne support – hvor mange instanser "understøtter" F1-scoren (andelen af etiketter, der tilhører en bestemt klasse). Jo lavere støtte (jo færre forekomster af en klasse), jo lavere vægtet F1 for den klasse, fordi den er mere upålidelig.
confusion_matrix()
, classification_report()
metoder til sklearn.metrics
modul kan bruges til at beregne og vise alle disse metrikker. Det confusion_matrix
er bedre visualiseret ved hjælp af et varmekort. Klassificeringsrapporten giver os allerede accuracy
, precision
, recall
og f1-score
, men du kan også importere hver af disse metrics fra sklearn.metrics
.
For at få metrics skal du udføre følgende uddrag:
from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns
classes_names = ['class 1','class 2','class 3', 'class 4']
cm = pd.DataFrame(confusion_matrix(yc_test, yc_pred),
columns=classes_names, index = classes_names)
sns.heatmap(cm, annot=True, fmt='d');
print(classification_report(y_test, y_pred))
Outputtet af ovenstående script ser sådan ud:
precision recall f1-score support
1 0.75 0.78 0.76 1292
2 0.49 0.56 0.53 1283
3 0.51 0.51 0.51 1292
4 0.76 0.62 0.69 1293
accuracy 0.62 5160
macro avg 0.63 0.62 0.62 5160
weighted avg 0.63 0.62 0.62 5160
Resultaterne viser, at KNN var i stand til at klassificere alle 5160 poster i testsættet med 62 % nøjagtighed, hvilket er over gennemsnittet. Understøttelserne er nogenlunde lige store (jævn fordeling af klasser i datasættet), så den vægtede F1 og den uvægtede F1 vil være nogenlunde den samme.
Vi kan også se resultatet af metrikken for hver af de 4 klasser. Ud fra det kan vi mærke det class 2
havde den laveste præcision, laveste recall
, og lavest f1-score
. Class 3
er lige bagved class 2
for at have den laveste score, og så har vi det class 1
med de bedste resultater efterfulgt af class 4
.
Ved at se på forvirringsmatricen kan vi se, at:
class 1
mest blev forvekslet medclass 2
i 238 tilfældeclass 2
forumclass 1
i 256 poster, og forclass 3
i 260 tilfældeclass 3
mest blev taget fejl afclass 2
, 374 poster, ogclass 4
, i 193 tilfældeclass 4
var fejlagtigt klassificeret somclass 3
for 339 poster, og somclass 2
i 130 tilfælde.
Bemærk også, at diagonalen viser de sande positive værdier, når man ser på den, er det tydeligt at se, at class 2
, class 3
har de mindst korrekt forudsagte værdier.
Med disse resultater kunne vi gå dybere ind i analysen ved at inspicere dem yderligere for at finde ud af, hvorfor det skete, og også forstå, om 4 klasser er den bedste måde at samle data på. Måske værdier fra class 2
, class 3
var for tæt på hinanden, så det blev svært at skelne dem fra hinanden.
Prøv altid at teste dataene med et andet antal skraldespande for at se, hvad der sker.
Udover det vilkårlige antal databakker, er der også et andet vilkårligt antal, som vi har valgt, antallet af K naboer. Den samme teknik, som vi anvendte til regressionsopgaven, kan anvendes på klassifikationen, når man bestemmer antallet af K'er, der maksimerer eller minimerer en metrisk værdi.
At finde den bedste K til KNN-klassificering
Lad os gentage, hvad der er blevet gjort for regression, og plotte grafen af K-værdier og den tilsvarende metrik for testsættet. Du kan også vælge, hvilken metrik der passer bedst til din kontekst, her vælger vi f1-score
.
På denne måde vil vi plotte f1-score
for de forudsagte værdier af testsættet for alle K-værdier mellem 1 og 40.
Først importerer vi f1_score
fra sklearn.metrics
og beregn derefter dens værdi for alle forudsigelserne af en K-Nearest Neighbors-klassifikator, hvor K varierer fra 1 til 40:
from sklearn.metrics import f1_score
f1s = []
for i in range(1, 40):
knn = KNeighborsClassifier(n_neighbors=i)
knn.fit(X_train, y_train)
pred_i = knn.predict(X_test)
f1s.append(f1_score(y_test, pred_i, average='weighted'))
Det næste skridt er at plotte f1_score
værdier mod K-værdier. Forskellen fra regression er, at i stedet for at vælge den K-værdi, der minimerer fejlen, vil vi denne gang vælge den værdi, der maksimerer f1-score
.
Udfør følgende script for at oprette plottet:
plt.figure(figsize=(12, 6))
plt.plot(range(1, 40), f1s, color='red', linestyle='dashed', marker='o',
markerfacecolor='blue', markersize=10)
plt.title('F1 Score K Value')
plt.xlabel('K Value')
plt.ylabel('F1 Score')
Outputgrafen ser sådan ud:
Fra outputtet kan vi se, at f1-score
er den højeste, når værdien af K er 15
. Lad os omskole vores klassificeringsorgan med 15 naboer og se, hvad det gør ved vores klassifikationsrapportresultater:
classifier15 = KNeighborsClassifier(n_neighbors=15)
classifier15.fit(X_train, y_train)
y_pred15 = classifier15.predict(X_test)
print(classification_report(y_test, y_pred15))
Dette udsender:
precision recall f1-score support
1 0.77 0.79 0.78 1292
2 0.52 0.58 0.55 1283
3 0.51 0.53 0.52 1292
4 0.77 0.64 0.70 1293
accuracy 0.63 5160
macro avg 0.64 0.63 0.64 5160
weighted avg 0.64 0.63 0.64 5160
Bemærk, at vores målinger er forbedret med 15 naboer, vi har 63 % nøjagtighed og højere precision
, recall
og f1-scores
, men vi er stadig nødt til at se nærmere på skraldespandene for at prøve at forstå, hvorfor f1-score
for klasser 2
, 3
er stadig lav.
Udover at bruge KNN til regression og bestemmelse af blokværdier og til klassificering, til at bestemme blokklasser – kan vi også bruge KNN til at detektere, hvilke gennemsnitlige blokværdier der er forskellige fra de fleste – dem der ikke følger, hvad de fleste data gør. Vi kan med andre ord bruge KNN til påvisning af afvigere.
Implementering af KNN til Outlier Detection med Scikit-Learn
Outlier-detektion bruger en anden metode, der adskiller sig fra det, vi tidligere havde gjort til regression og klassificering.
Her vil vi se, hvor langt hver af naboerne er fra et datapunkt. Lad os bruge standard 5 naboer. For et datapunkt vil vi beregne afstanden til hver af de K-nærmeste naboer. For at gøre det vil vi importere en anden KNN-algoritme fra Scikit-learn, som ikke er specifik for hverken regression eller klassificering kaldet blot NearestNeighbors
.
Efter importen instansierer vi en NearestNeighbors
klasse med 5 naboer – du kan også instansiere det med 12 naboer for at identificere outliers i vores regressionseksempel eller med 15, for at gøre det samme for klassifikationseksemplet. Vi vil derefter tilpasse vores togdata og bruge kneighbors()
metode til at finde vores beregnede afstande for hvert datapunkt og naboindekser:
from sklearn.neighbors import NearestNeighbors
nbrs = NearestNeighbors(n_neighbors = 5)
nbrs.fit(X_train)
distances, indexes = nbrs.kneighbors(X_train)
Nu har vi 5 afstande for hvert datapunkt - afstanden mellem sig selv og dets 5 naboer og et indeks, der identificerer dem. Lad os tage et kig på de første tre resultater og formen på arrayet for at visualisere dette bedre.
For at se på de første tre afstande skal du udføre:
distances[:3], distances.shape
(array([[0. , 0.12998939, 0.15157687, 0.16543705, 0.17750354],
[0. , 0.25535314, 0.37100754, 0.39090243, 0.40619693],
[0. , 0.27149697, 0.28024623, 0.28112326, 0.30420656]]),
(3, 5))
Bemærk, at der er 3 rækker med hver 5 afstande. Vi kan også se og naboernes indeks:
indexes[:3], indexes[:3].shape
Dette resulterer i:
(array([[ 0, 8608, 12831, 8298, 2482],
[ 1, 4966, 5786, 8568, 6759],
[ 2, 13326, 13936, 3618, 9756]]),
(3, 5))
I outputtet ovenfor kan vi se indekserne for hver af de 5 naboer. Nu kan vi fortsætte med at beregne gennemsnittet af de 5 afstande og plotte en graf, der tæller hver række på X-aksen og viser hver middelafstand på Y-aksen:
dist_means = distances.mean(axis=1)
plt.plot(dist_means)
plt.title('Mean of the 5 neighbors distances for each data point')
plt.xlabel('Count')
plt.ylabel('Mean Distances')
Bemærk, at der er en del af grafen, hvor middelafstandene har ensartede værdier. Det punkt på Y-aksen, hvor middelværdierne ikke er for høje eller for lave, er præcis det punkt, vi skal identificere for at afskære yderværdierne.
I dette tilfælde er det, hvor middelafstanden er 3. Lad os plotte grafen igen med en vandret stiplet linje for at kunne se den:
dist_means = distances.mean(axis=1)
plt.plot(dist_means)
plt.title('Mean of the 5 neighbors distances for each data point with cut-off line')
plt.xlabel('Count')
plt.ylabel('Mean Distances')
plt.axhline(y = 3, color = 'r', linestyle = '--')
Denne linje markerer middelafstanden, for hvilken alle værdier varierer. Det betyder, at alle punkter med en mean
afstand over 3
er vores outliers. Vi kan finde ud af indeksene for disse punkter ved hjælp af np.where()
. Denne metode vil output enten True
or False
for hvert indeks med hensyn til mean
over 3 tilstand:
import numpy as np
outlier_index = np.where(dist_means > 3)
outlier_index
Ovenstående kode udsender:
(array([ 564, 2167, 2415, 2902, 6607, 8047, 8243, 9029, 11892,
12127, 12226, 12353, 13534, 13795, 14292, 14707]),)
Nu har vi vores outlier point indekser. Lad os finde dem i datarammen:
outlier_values = df.iloc[outlier_index]
outlier_values
Dette resulterer i:
MedInc HouseAge AveRooms AveBedrms Population AveOccup Latitude Longitude MedHouseVal
564 4.8711 27.0 5.082811 0.944793 1499.0 1.880803 37.75 -122.24 2.86600
2167 2.8359 30.0 4.948357 1.001565 1660.0 2.597809 36.78 -119.83 0.80300
2415 2.8250 32.0 4.784232 0.979253 761.0 3.157676 36.59 -119.44 0.67600
2902 1.1875 48.0 5.492063 1.460317 129.0 2.047619 35.38 -119.02 0.63800
6607 3.5164 47.0 5.970639 1.074266 1700.0 2.936097 34.18 -118.14 2.26500
8047 2.7260 29.0 3.707547 1.078616 2515.0 1.977201 33.84 -118.17 2.08700
8243 2.0769 17.0 3.941667 1.211111 1300.0 3.611111 33.78 -118.18 1.00000
9029 6.8300 28.0 6.748744 1.080402 487.0 2.447236 34.05 -118.78 5.00001
11892 2.6071 45.0 4.225806 0.903226 89.0 2.870968 33.99 -117.35 1.12500
12127 4.1482 7.0 5.674957 1.106998 5595.0 3.235975 33.92 -117.25 1.24600
12226 2.8125 18.0 4.962500 1.112500 239.0 2.987500 33.63 -116.92 1.43800
12353 3.1493 24.0 7.307323 1.460984 1721.0 2.066026 33.81 -116.54 1.99400
13534 3.7949 13.0 5.832258 1.072581 2189.0 3.530645 34.17 -117.33 1.06300
13795 1.7567 8.0 4.485173 1.120264 3220.0 2.652389 34.59 -117.42 0.69500
14292 2.6250 50.0 4.742236 1.049689 728.0 2.260870 32.74 -117.13 2.03200
14707 3.7167 17.0 5.034130 1.051195 549.0 1.873720 32.80 -117.05 1.80400
Vores outlier-detektion er afsluttet. Sådan ser vi hvert datapunkt, der afviger fra den generelle datatendens. Vi kan se, at der er 16 punkter i vores togdata, som bør ses nærmere på, undersøges, måske behandles eller endda fjernes fra vores data (hvis de fejlagtigt blev indtastet) for at forbedre resultaterne. Disse punkter kan være et resultat af skrivefejl, inkonsistens i gennemsnitlige blokværdier eller endda begge dele.
Fordele og ulemper ved KNN
I dette afsnit vil vi præsentere nogle af fordele og ulemper ved at bruge KNN-algoritmen.
FORDELE
- Det er nemt at implementere
- Det er en doven indlæringsalgoritme og kræver derfor ikke træning på alle datapunkter (kun ved at bruge K-nærmeste naboer til at forudsige). Dette gør KNN-algoritmen meget hurtigere end andre algoritmer, der kræver træning med hele datasættet som f.eks Support vektormaskiner, lineær regressionOsv
- Da KNN ikke kræver træning før forudsigelser, kan nye data tilføjes problemfrit
- Der kræves kun to parametre for at arbejde med KNN, nemlig værdien af K og afstandsfunktionen
ULEMPER
- KNN-algoritmen fungerer ikke godt med højdimensionelle data, fordi med et stort antal dimensioner bliver afstanden mellem punkter "underlig", og de afstandsmålinger, vi bruger, holder ikke op
- Endelig fungerer KNN-algoritmen ikke godt med kategoriske træk, da det er svært at finde afstanden mellem dimensioner med kategoriske træk
Gå videre – Håndholdt ende-til-ende-projekt
I dette guidede projekt – vil du lære, hvordan du bygger kraftfulde traditionelle maskinlæringsmodeller såvel som deep learning-modeller, bruger Ensemble Learning og træner meta-elever til at forudsige huspriser ud fra en pose Scikit-Learn og Keras-modeller.
Ved at bruge Keras, deep learning API bygget oven på Tensorflow, eksperimenterer vi med arkitekturer, bygger et ensemble af stablede modeller og træner en meta-lærer neuralt netværk (niveau-1 model) for at finde ud af prisen på et hus.
Dyb læring er fantastisk – men før man griber til det, anbefales det også at forsøge at løse problemet med enklere teknikker, som f.eks. overfladisk læring algoritmer. Vores baseline præstation vil være baseret på en Tilfældig skovregression algoritme. Derudover – vi vil udforske at skabe ensembler af modeller gennem Scikit-Learn via teknikker som f.eks sække , afstemning.
Dette er et ende-til-ende-projekt, og ligesom alle Machine Learning-projekter starter vi ud med Udforskende dataanalyse, efterfulgt af Dataforarbejdning og endelig Bygning lavvandet , Deep Learning-modeller for at passe til de data, vi tidligere har udforsket og renset.
Konklusion
KNN er en enkel, men kraftfuld algoritme. Det kan bruges til mange opgaver såsom regression, klassificering eller afvigende detektion.
KNN er blevet meget brugt til at finde dokumentlighed og mønstergenkendelse. Det er også blevet brugt til at udvikle anbefalingssystemer og til dimensionsreduktion og forbehandlingstrin til computersyn – især ansigtsgenkendelsesopgaver.
I denne vejledning – har vi gennemgået regression, klassificering og afvigende detektion ved hjælp af Scikit-Learns implementering af K-Nearest Neighbor-algoritmen.