본문 바로가기
머신러닝, 딥러닝/머신러닝

[머신러닝] KNN 개념 정리

by 장찐 2021. 10. 6.

 ◈ 개념 

 

•  작동 방식이나 개념이 상대적으로 이해가 쉬워서, 가장 빈번하게 사용되는 알고리즘 중 하나이다. 

•  Make no assumptions about data. 즉, data-driven 성격이며 model-driven이 아니다. 

 

•  거리를 계산하는 방식은 여러가지가 있지만, 주로 Euclidean / Manhattan / Chybyshev distance 를 사용한다. 

•  계산에 사용하는 predictor 변수의 스케일 차이가 많이 나면, 거리 계산이 올바로 되지 않을 수 있으므로 정규화(normalize)를 실시하는 것이 적절하다. 

• 아래 예제에서 Manhattan은 2+1=3, Chybyshev은 2로 계산된다. 


◈ 장점 

• 데이터셋이 크고, 각 클래스가 multiple combinations of predicgtor values를 가지고 있을 때, 성과가 뛰어나다. 

• No assumption required, 매우 심플하다 

• 특정한 statistical model이 없이도 변수들 간의 complex interaction을 포착할 수 있다. 

 


◈ 단점 

• input-output 관계를 정확하게 파악할 수 없다. (변수 중요도에 대한 인사이트 파악 어렵다) 

• 신뢰할 수 있는 결과를 확보하기 위해서는, 학습용 데이터의 수가 많이 확보되어야 한다. 그런데, 데이터가 클 경우에 학습하는 데에 시간이 많이 소요된다. 최근에는 모든 데이터 포인트를 계산하지 않고도 거리를 도출하는 기법이 있다고 하니 추가 학습이 필요함 

• Predictor가 너무 많으면 → Curse of dimensionality 

  머신러닝에서 나타나는 전통적인 이슈이며, PCA 등을 이용한 차원 축소가 필요함 

 

 


◈ 최적의 K 선정하는 기준? 

• K : number of neraby neighbors to be used to classify the new record 

• KNN 에서는 vadlidation을 사용하여 이를 검증하고, 별도로 test set을 holdout 하는 게 적절하다 

• K가 작으면, 데이터의 local structure를 잘 포착할 수 있지만 왜곡이 발생할 수 있다. 

    반면, K가 크면 보다 smoothing & less noise라는 장점이 있지만 local structure를 포착하지 못할 수도 있다. 

 


 

◈ 예제 실습 : Lawn Mower Example (Classification)

 

잔디깍이 기계 예제를 사용하여 간단한 R코드 KNN 실습을 진행하였다.

✅ 데이터 불러오기 및 train/valid/test 분리 

#load data 
mower.df <-  read.csv("RidingMowers.csv", na.strings = "")

# convert the data type for Ownership 
mower.df$Ownership <-  factor(mower.df$Ownership)

spl <- sample(c(1:3),
              size=nrow(mower.df),
              replace=TRUE,
              prob=c(0.6,0.2,0.2))  #1,2,3으로 뽑힐 확률을 각각 지정 

train.df <- mower.df[spl==1,] 
valid.df <- mower.df[spl==2,] 
test.df <- mower.df[spl==3,]

•데이터를 불러오고 train/validation/test 분리를 실시한다. 정제되어 있는 형태로 배포된 데이터여서, 변수 유형 변환 외에 별도의 전처리가 필요 없다. 최적의 K 수를 탐색하는데 사용하기 위하여 valid 데이터 셋을 별도로 분리한다. 

 

✅ 예시 데이터 생성 

# 예측할 예시 데이터 생성 
new.df <- data.frame(Income = c(60,80,75), Lot_Size = c(20,25,55))


# 그래프 그리기 
plot(Lot_Size ~ Income, data = train.df, pch=ifelse(train.df$Ownership == "Owner", 1, 3))
text(train.df$Income, train.df$Lot_Size, rownames(train.df), pos = 4)
text(60, 20, "X")
legend("topright", c("owner", "non‐owner", "newhousehold"), pch = c(1, 3, 4))

•예측값을 도출할 임의의 데이터를 데이터 프레임 형태로 생성한다. 그리고 이 데이터의 경우 변수가 2개 이기 때문에, 2차원으로 시각화를 하여 전체 분포를 파악하는 것이 가능하다.

 

✅  데이터 전처리 : 정규화 

## 데이터 정규화 

#데이터 복사 
train.norm.df <- train.df 
valid.norm.df <- valid.df 
test.norm.df <-  test.df

#caret 패키지의 preprocess 함수 이용
library(caret)
norm.values <- preProcess(train.df[ , 1:2], method=c("center", "scale"))

#복사된 데이터의 값을 정규화된 값으로 대체 
train.norm.df[, 1:2] <- predict(norm.values, train.df[, 1:2])

#train 데이터의 center, scale 값으로 정규화 실시 
valid.norm.df[, 1:2] <- predict(norm.values, valid.df[, 1:2]) 
test.norm.df[, 1:2] <- predict(norm.values, test.df[, 1:2])

•  각 변수에 대한 정규화를 실시한다. 이때 Train 데이터의 값을 기준으로 valid/test 에서 정규화를 실시한다. KNN 은 거리를 기준으로 계산하기 때문에, 각 변수의 scale이 다를 경우 제대로 된 값을 계산하지 못한다. 따라서 정규화는 필수로 실시 

 

 

✅ 최적의 K 값 확인 

library(FNN)
library(class)

#빈 df 생성
accuracy.df <- data.frame(k = seq(1, 10, 1), accuracy = rep(0, 10))

#k 값 1~10까지 루프로 확인 
for(i in 1:10){ 
  knn.pred <- class::knn(train = train.norm.df[, 1:2], 
                         test = valid.norm.df[, 1:2], 
                         cl = train.norm.df[, 3], 
                         k = i)
  accuracy.df[i, 2] <- confusionMatrix(as.factor(knn.pred), as.factor(valid.norm.df[,3]))$overall[1]   #overall[1] 은 accuracy만 뽑아내는 것 
}

accuracy.df

• Class 패키지는 예측 클래스만 확인할 수 있지만, FNN 패키지는 예측 결과를 포함하여 여러 정보를 함께 제공한다. 

 최적의 k 값을 확인하기 위해서 validation set을 사용한다. k 값을 1부터 10까지 사용해서 결과를 확인하고, accuracy.df에 저장한다. 정확도와 이웃수를 고려하여 k=4로 선정했다. 

 

 

✅ test 데이터 예측 

knn.pred.test <- class::knn(train = train.norm.df[, 1:2], 
                       test = test.norm.df[, 1:2],   
                       cl = train.norm.df[, 3], 
                       k = 4)
knn.pred.test

confusionMatrix(as.factor(knn.pred.test), as.factor(test.norm.df[,3]))

 •test set에 대해서 예측을 실시하고, confusion matrix를 확인한다. 

 

 

✅ 새로운 데이터 예측 

#정규화
new.norm.df <- predict(norm.values, new.df)

nn.reuslt2 <- FNN::knn(train = train.norm.df[, 1:2], 
               test = new.norm.df,    
               cl = train.norm.df[, 3], 
               k = 4)
nn.reuslt2


##이웃 데이터 확인하기 
attr(nn.reuslt2, 'nn.index') #k에 해당하는 데이터의 인덱스 

name <- row.names(train.df)[attr(nn.reuslt2, 'nn.index')]

matrix(name, nrow=3, ncol=4,byrow=FALSE) #매트릭스로 제시

 •앞서 임의로 생성한 new.df에 대해서도 정규화 실시 후 예측을 실시한다. FNN 패키지를 이용하면, 주변 이웃이 어떤 데이터인지 확인할 수 있다. attr(  , 'nn.index')에서 출력되는 값은, 이웃 데이터의 index이므로 유의. 

 


 

◈ 실습 예제 : Boston Housing price Prediction 

 

 입문 단계에서 많이 사용되는 데이터셋인 Boston House Price 데이터를 사용한다. 보스턴 지역의 주택에 대한 정보가 포함되어 있고, 각 주택의 가격을 예측하는 용도로 사용할 수 있다. 데이터는 506 x 14 로 굉장히 작다. 

 

✅ 데이터 불러오기 및 Train/Test 분리 

library(MASS)
data(Boston)
housing.df <- Boston

# train/test 분리 
train.index = sample(1:nrow(housing.df), 0.6*nrow(housing.df)) 

train.df = housing.df[train.index, ]
valid.df = housing.df[-train.index,

 R의 MASS 패키지를 이용해서 데이터를 불러오고, train/test split을 실시한다. 

 

✅ 전처리 : 정규화 

#데이터 복사
train.norm.df <- train.df
valid.norm.df <- valid.df
housing.norm.df <- housing.df

#데이터 정규화 실시 
library(caret) 
norm.values <- preProcess(train.df[, -14], method=c("center", "scale")) 

train.norm.df[, -14] <- predict(norm.values, train.df[, -14]) 
valid.norm.df[, -14] <- predict(norm.values, valid.df[, -14])

 Caret 패키지의 preprocess 함수 사용하여 변수 정규화를 실시한다. 이때, train 데이터의 값을 기준으로 정규화를 실시한다는 것을 유의한다. 

 

✅ Test 데이터 예측 

#KNN으로 예측 실시 
library(FNN)
pred5 = knn.reg(train = train.norm.df, test = valid.norm.df, y = train.norm.df[,14], k = 5) 
pred5

#RMSE 계산 
RMSE(pred5$pred, valid.df$medv)

 

 분류 문제에서는 knn()을 사용할 때, cl 파라미터를 입력한다. 예측의 경우 knn.reg() 을 사용하고, cl 대신 y 파라미터를 입력한다. 

 

✅ 새로운 값 예측 

#새로운 데이터값 생성 
new.rec <- data.frame(0.2, 0, 7.2, 0, 0.538, 6.3, 62, 4.7, 4, 307, 21, 10, 31) 
new.rec 
dim(new.rec) 
names(new.rec) <- names(train.norm.df)[-14]
new.norm.rec <- predict(norm.values, new.rec)


knn.pred <- knn.reg(train = train.norm.df[,-14], test = new.norm.rec, y = train.df$medv, k = 3)
knn.pred

 모델에 새로운 데이터 값을 임의로 생성해서 입력해볼 수 있다. 첫 번째 라인에 변수 순서대로 한 row를 만들고, 정규화를 실시한 후에 동일한 방식으로 예측할 수 있다 

 

 


◈ 자료 출처 

Data Mining for Business Analytics: Concepts, Techniques, and Application in R" by R, Galit Shmueli, Peter C. Bruce, Inbal Yahav, Nitin R. Patel, Kenneth C. Lichtendahl Jr. Wiley. 1st edition. Wiley, 2017.

 

 

댓글