k - NN

Wprowadzenie

Algorytm k-najbliższych sąsiadów, znany również jako KNN lub k-NN, jest nieparametrycznym, nadzorowanym klasyfikatorem uczenia się, który wykorzystuje bliskość do dokonywania klasyfikacji lub przewidywań dotyczących grupowania poszczególnych punktów danych. W przypadku klasyfikacji etykieta klasy jest przypisywana na podstawie głosowania większościowego, tzn. używana jest etykieta, która jest najczęściej reprezentowana wokół danego punktu danych.

Schemat k-NN

Podsumowując, celem algorytmu k-najbliższego sąsiada jest zidentyfikowanie najbliższych sąsiadów danego punktu, aby możliwe było przypisanie etykiety klasy do tego punktu

library(kknn)
library(ggplot2)

load("C:/Users/mbuko/OneDrive/Dokumenty/credit.RData")

Przygotowanie danych

Zmienne porządkowe

Analiza z wykorzystaniem klasyfikacji k-NN została przeprowadzona na podstawie wszystkich zmiennych ilościowych i jakościowych ze zbioru danych credit. W ramach przygotowania danych wskazano zmienne o charakterze porządkowym, do których zaliczono: stan konta czekowego, oszczędności i staż pracy.

levels(credit$konto_czekowe)
## [1] "< 0"   ">200"  "0-200" "brak"
credit$konto_czekowe <- factor(
  x = credit$konto_czekowe,
  levels = c("< 0", "brak", "0-200",">200"),
  ordered = T
)

levels(credit$oszczednosci)
## [1] "<100"     ">1000"    "100-500"  "500-1000" "brak"
credit$oszczednosci <- factor(
  x = credit$oszczednosci,
  levels = c( "brak" , "<100",  "100-500",  "500-1000" ,">1000" ),
  ordered = T
)

levels(credit$staz_pracy)
## [1] "<1 rok"     ">7"         "1-4"        "4-7"        "bezrobotny"
credit$staz_pracy <- factor(
  x = credit$staz_pracy,
  levels = c("bezrobotny",  "<1 rok", "1-4" , "4-7", ">7" ),
  ordered = T
)

levels(credit$oszczednosci)
## [1] "brak"     "<100"     "100-500"  "500-1000" ">1000"
levels(credit$staz_pracy)
## [1] "bezrobotny" "<1 rok"     "1-4"        "4-7"        ">7"
class(credit$konto_czekowe) # ordered factor - aby funkcja dobrze liczyła odległości
## [1] "ordered" "factor"
str(credit)
## 'data.frame':    1000 obs. of  21 variables:
##  $ konto_czekowe : Ord.factor w/ 4 levels "< 0"<"brak"<"0-200"<..: 1 3 2 1 1 2 2 3 2 3 ...
##  $ czas          : int  6 48 12 42 24 36 24 36 12 30 ...
##  $ historia      : Factor w/ 5 levels "brak","istniejace_spł",..: 3 2 3 2 4 2 2 2 2 3 ...
##  $ cel           : Factor w/ 10 levels "AGD","biznes",..: 8 8 3 5 9 3 5 10 8 9 ...
##  $ kwota         : int  1169 5951 2096 7882 4870 9055 2835 6948 3059 5234 ...
##  $ oszczednosci  : Ord.factor w/ 5 levels "brak"<"<100"<..: 1 2 2 2 2 1 4 2 5 2 ...
##  $ staz_pracy    : Ord.factor w/ 5 levels "bezrobotny"<"<1 rok"<..: 5 3 4 4 3 3 5 3 4 1 ...
##  $ rata_%doch    : int  4 2 2 2 3 2 3 2 2 4 ...
##  $ plec          : Factor w/ 2 levels "K","M": 2 1 2 2 2 2 2 2 2 2 ...
##  $ poreczyciel   : Factor w/ 2 levels "brak","tak": 1 1 1 2 1 1 1 1 1 1 ...
##  $ zamieszkanie  : int  4 2 3 4 4 4 4 2 4 2 ...
##  $ zabezpieczenie: Factor w/ 4 levels "brak","nieruchomosc",..: 2 2 2 4 1 1 4 3 2 3 ...
##  $ wiek          : int  67 22 49 45 53 35 53 35 61 28 ...
##  $ inne_zobow    : Factor w/ 3 levels "bank","brak",..: 2 2 2 2 2 2 2 2 2 2 ...
##  $ rodzaj_miesz  : Factor w/ 3 levels "czynsz","wlasne",..: 2 2 2 3 3 3 2 1 2 2 ...
##  $ l_kredytow    : int  2 1 1 1 2 1 1 1 1 2 ...
##  $ kwalifikacje  : Factor w/ 4 levels "niewykwal_nierez",..: 3 3 2 3 3 2 3 4 2 4 ...
##  $ l_osob        : int  1 1 2 2 2 2 1 1 1 1 ...
##  $ telefon       : Factor w/ 2 levels "brak","tak": 2 1 1 1 1 2 1 2 1 1 ...
##  $ obcokrajowiec : Factor w/ 2 levels "nie","tak": 2 2 2 2 2 2 2 2 2 2 ...
##  $ jakosc        : Factor w/ 2 levels "dobry","zly": 1 2 1 1 2 1 1 1 1 2 ...

Podział danych na zbiór uczący i testowy

W ramach przygotowania zbiór danych podzielono na zbiór uczący i zbiór testowy w proporcji 75% - 25%.

set.seed(121) # for reproducibility
random <- sample(1:nrow(credit), 0.75 * nrow(credit))


data_train<-credit[random, ]
data_test <- credit[-random, ]

Normalizacja

Aby określić, które obiekty znajdują się najbliżej danego punktu zapytania, należy obliczyć odległość między punktem zapytania a innymi obiektami ze zbioru danych. Istnieje kilka miar odległości. Najczęściej stosowana jest ogólna odległość potęgowa/Minkowskiego:

\[ d_{x,y} = (\sum_{i=1}^n|x_{i} - y_{i}|)^{1/p} \]

Aby obliczyć odległość między obiektami zmienne powinny być wyrażone na tej samej skali. Z tego względu należy dokonać normalizacji.

Normalizację zmiennych ilościowych przeprowadzono poprzez ich standaryzację Wartość po standaryzacji x’ może zostać obliczona ze wzoru: \[ x' = \frac{x - \mu}{\sigma} \]

Następnie zmienne \(v_{i}\) są normalizowane poprzez podzielenie przez przeciętne odchylenie standardowe zmiennych oraz przeważone przez odwrotność liczby wariantów (m):

\[ v^{'}_{i}= \frac{v_{i}}{\sqrt{\frac{1}{m}\sum_{i=1}^m s^2(v_i)}}\frac{1}{m} \]

Klasyfikacja

Model k-NN dla k = 5

Bo budowy modelu wykorzystano funkcję kknn z pakietu kknn. Jednym z wymaganych parametrów, które muszą być określone jest k - liczba obiektów, na podstawie których następuje przypisanie nowych obiektów do odpowiednich klas. W zalezności od przyjetej liczby k dokładność klasyfikacji może być różna. Poniżej przedstawiono wyniki klasyfikacji prz k=5.

knn.5 <- kknn(formula = jakosc ~ ., 
  train=data_train, test=data_test, 
  k = 5,
  distance = 2,
  kernel = "rectangular",
  scale = T)

head(knn.5$fitted.values, 15)
##  [1] dobry zly   dobry dobry dobry dobry dobry dobry dobry dobry dobry dobry
## [13] zly   dobry dobry
## Levels: dobry zly

ocena jakości za pomocą macierzy błędnych klasyfikacji i wielkości błędu

table(
    przewidywane = knn.5$fitted.values,
    rzeczywiste = data_test$jakosc)
##             rzeczywiste
## przewidywane dobry zly
##        dobry   153  63
##        zly      13  21
mean(data_test$jakosc != knn.5$fitted.values)
## [1] 0.304

Optymalne parametry modelu

Obliczona wielkość błędu klasyfikacji jest równa 0.304. Zastoswanie funkcji train.kknn daje możliwość zwiększenia dokładności klasyfikacji poprzez zmianę parametrów funkcji:

  • k - liczba sasiadów - w obliczeniach przyjęto liczbę k od 1 do 31, co 2;
  • kernel - sposób ważenia głosów w zależności od odległości sąsiadów. Możliwe parametry funkcji to “rectangular”, “triangular”, “epanechnikov”, “biweight”, “triweight”, “cos”, “inv”, “gaussian”, “rank”, “optimal”;
  • distance - parametr p przy obliczaniu odległości Minkowskiego. W obliczeniach przyjęto p = 1, 2 i 3.
accurency<-data.frame(matrix(ncol = 10, nrow = 0))
colnames(accurency)<-c( "rectangular", "triangular", "epanechnikov", "biweight", "triweight", "cos", 
                        "inv", "gaussian", "rank", "optimal")

for (i in 1:3) {
  knn.cv <- train.kknn(
  formula = jakosc ~ .,
  data = data_train,
  # kmax = 15,
  ks = seq(1, 31, 2),
  distance = i,
  kernel = c( "rectangular", "triangular", "epanechnikov", "biweight", "triweight", "cos", 
              "inv", "gaussian", "rank", "optimal"))
accurency<-rbind(accurency, as.data.frame(knn.cv$MISCLASS))
}
accurency$k<-rep(seq(1, 31, 2),3)
accurency$i<-c(rep(1, 16), rep(2, 16), rep(3, 16))

accurency<-as.matrix(accurency)

head(accurency)
##    rectangular triangular epanechnikov  biweight triweight       cos       inv
## 1    0.3053333  0.3053333    0.3053333 0.3053333 0.3053333 0.3053333 0.3053333
## 3    0.2826667  0.2933333    0.2973333 0.2973333 0.2973333 0.2933333 0.2853333
## 5    0.2626667  0.2866667    0.2866667 0.2986667 0.3026667 0.2893333 0.2640000
## 7    0.2640000  0.2720000    0.2666667 0.2906667 0.3000000 0.2720000 0.2613333
## 9    0.2466667  0.2613333    0.2586667 0.2773333 0.2893333 0.2600000 0.2453333
## 11   0.2520000  0.2480000    0.2480000 0.2733333 0.2893333 0.2453333 0.2480000
##     gaussian      rank   optimal  k i
## 1  0.3053333 0.3053333 0.3053333  1 1
## 3  0.2826667 0.2773333 0.3053333  3 1
## 5  0.2626667 0.2746667 0.2866667  5 1
## 7  0.2613333 0.2640000 0.2800000  7 1
## 9  0.2466667 0.2533333 0.2626667  9 1
## 11 0.2493333 0.2480000 0.2560000 11 1

Wyszukanie w macierzy najmniejszej wielkości błędów.

which(accurency == min(accurency), arr.ind = TRUE)
##     row col
## 131  23   7
## 131  23   8

Prezentacja graficzna wyników obliczeń w zależności od parametrów funkcji.

accurency<-as.data.frame(accurency)

for(j in 1:10) { 
  print(
    ggplot(accurency, aes(x = k, y = accurency[ ,j],
                              col = as.factor(accurency$i))) +
    geom_line()+
    ylab("błędne klasy") +
    ggtitle(colnames(accurency)[j])+
    labs(color='i')
  )
  print(colnames(accurency)[j])
  Sys.sleep(3)
}

## [1] "rectangular"

## [1] "triangular"

## [1] "epanechnikov"

## [1] "biweight"

## [1] "triweight"

## [1] "cos"

## [1] "inv"

## [1] "gaussian"

## [1] "rank"

## [1] "optimal"

Model k-NN przy optymalnych parametrach

Postać modelu

Przedstawione wyniki wskazują, iż najlepszy model uzyskać można przy następujacych wartościach parametrów:

  • k = 13,
  • distance = 2,
  • kernel = “gaussian”.

Wyniki obliczeń przedstawiono poniżej

knn <- kknn(formula = jakosc ~ ., 
            train=data_train, 
            test=data_test,
  k = 13,
  distance = 2,
  kernel = "gaussian",
  scale = T
)


macierz_kNN<-table(
  przewidywane = knn$fitted.values,
  rzeczywiste = data_test$jakosc)

macierz_kNN
##             rzeczywiste
## przewidywane dobry zly
##        dobry   161  66
##        zly       5  18
mean(data_test$jakosc != knn$fitted.values) # błąd dla modelu
## [1] 0.284

Ocena modelu w porównaniu z klasyfikacją 0R

Obliczony procent błędnych klasyfikacji może być porównany z procentem złych klas przy przyjęciu założenia, że wszystkie nowe obiekty posiadają klasę częściej występującą w zbiorze testowym:

macierz_0R<-table(data_test$jakosc)

macierz_0R
## 
## dobry   zly 
##   166    84
sum(data_test$jakosc == "zly")/nrow(data_test) #błąd przy klasyfikacji 0R
## [1] 0.336

Precyzja, czułość, miara F

Wzory dotyczące tych parametrów oceny znajdują się w dokumencie metody5.

  • Precyzja to liczba wartości prawidłowo sklasyfikowanych jako pozytywne (TP) przez sumę wszystkich wartości sklasyfikowanych jako pozytywnie (również tych błędnie sklasyfikowanych jako pozytywne). W efekcie dowiadujemy się, ile wśród przykładów sklasyfikowanych jako pozytywne jest rzeczywiście pozytywnych.
  • czułość oznacza udział prawidłowo sklasyfikowanych przypadków pozytywnych (TP) wśród wszystkich przypadków pozytywnych (również tych, które błędnie zostały zaklasyfikowane do negatywnych – FN). Warto pamiętać, że jeśli algorytm nie zaklasyfikuje żadnego pozytywnego przypadku błędnie (czyli nic nie trafi do kategorii FN), to czułość będzie wynosić 1.
  • miara F to średnia harmoniczna pomiędzy precyzją (precision) i czułością (recall). Im bliższa jest jedynki, tym lepiej to świadczy o algorytmie klasyfikującym.

Poniżej przedstawiono obliczenia dla modelu k-NN i 0R.

precyzja_kNN <- macierz_kNN[1,1]/(macierz_kNN[1,1]+macierz_kNN[1,2])
precyzja_kNN
## [1] 0.7092511
czulosc_kNN <-macierz_kNN[1,1]/(macierz_kNN[1,1]+macierz_kNN[2,1])
czulosc_kNN
## [1] 0.9698795
miaraF_kNN<-2/(1/precyzja_kNN+1/czulosc_kNN)
miaraF_kNN
## [1] 0.8193384
precyzja_0R <- macierz_0R[1]/(macierz_0R[1]+macierz_0R[2])
precyzja_0R
## dobry 
## 0.664
czulosc_0R <-macierz_0R[1]/(macierz_0R[1])
czulosc_0R
## dobry 
##     1
miaraF_0R<-2/(1/precyzja_0R+1/czulosc_0R)
miaraF_0R
##     dobry 
## 0.7980769

Porównanie modeli przy różnych kosztach błędu

Ocena jakości modelu może zostać ptrzeprowadzona nie tylko na podstawie dokładności klasyfikacji, ale także na podstawie kosztów błędów wynikających z niewłaściwej klasyfikacji. Przykładowo z punktu widzenia banku koszt akceptacji wniosku, który powinien zostać odrzucony może być dwa razy większy od kosztu nieprzyznania kredytu osobie, która byłaby dobrym kredytobiorcą. (Bank więcej traci dając kredyt tym którzy go nie powinni otrzymać niż niedając kredytu tym którzy powinni go dostać). Macierz kosztów ma postać:

koszty <- matrix(c(0, 1, 2, 0), 2, 2, 
                 dimnames = list( 
                                 prognozowane = c("dobry", "zly"),
                                 rzeczywiste = c("dobry", "zly")))
koszty
##             rzeczywiste
## prognozowane dobry zly
##        dobry     0   2
##        zly       1   0

Na jej podstawie można obliczyć koszt błędnej klasyfikacji dla modelu k-NN

conf_matrix<-as.matrix(table(
  przewidywane = knn$fitted.values,
  rzeczywiste = data_test$jakosc))

sum(koszty*conf_matrix)
## [1] 137

i dla modelu 0R

sum(koszty[1,]*as.matrix(table(data_test$jakosc)))
## [1] 168