본문 바로가기
Python/머신러닝+딥러닝 Ⅰ

K-Nearest Neighbor : K-NN 알고리즘

by Mr.DonyStark 2024. 3. 12.

□ K-NN(K-Nearest Neighbor) 알고리즘 : k-최근접 이웃 알고리즘(또는 줄여서 k-NN)은 분류나 회귀에 사용되는 비모수 방식으로 두 경우 모두 입력이 특징 공간 내 k개의 가장 가까운 훈련 데이터로 구성

 

분석내용 : 도미와 빙어의 길이, 무게 속성 데이터로 새로 들어오는(입력되는) 물고기의 종류를 분류해보자

○라이브러리

# 라이브러리
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier #K-NN알고리즘활용을위해 호출

 

○데이터준비

#도미 데이터 준비
bream_length = [25.4, 26.3, 26.5, 29.0, 29.0, 29.7, 29.7, 30.0, 30.0, 30.7, 31.0, 31.0, 31.5, 32.0, 32.0, 32.0, 33.0, 33.0, 33.5, 33.5, 34.0, 34.0, 34.5, 35.0, 35.0, 35.0, 35.0, 36.0, 36.0, 37.0, 38.5, 38.5, 39.5, 41.0, 41.0]
bream_weight = [242.0, 290.0, 340.0, 363.0, 430.0, 450.0, 500.0, 390.0, 450.0, 500.0, 475.0, 500.0, 500.0, 340.0, 600.0, 600.0, 700.0, 700.0, 610.0, 650.0, 575.0, 685.0, 620.0, 680.0, 700.0, 725.0, 720.0, 714.0, 850.0, 1000.0, 920.0, 955.0, 925.0, 975.0, 950.0]
#빙어 데이터 준비
smelt_length = [9.8, 10.5, 10.6, 11.0, 11.2, 11.3, 11.8, 11.8, 12.0, 12.2, 12.4, 13.0, 14.3, 15.0]
smelt_weight = [6.7, 7.5, 7.0, 9.7, 9.8, 8.7, 10.0, 9.9, 9.8, 12.2, 13.4, 12.2, 19.7, 19.9]

 

○시각화

 

○지도학습 준비

  - 데이터 트레이닝, 즉, 훈련데이터와 정답데이터 준비

  - 정답 데이터를 준비하는 것에서 이미 정답인 데이터 셋을 구축하는 부분인것으로 머신러닝 지도학습으로 생각하면됨

  - 도미/빙어별 길이, 무게를 하나의 리스트로 생성 후 각 리스트에 라벨값, 즉, 정답을 지정함. 이작업이 라벨리이자, 정답을 지정하는 작업이라고 생각하면됨.

  - 아래 코드는 1은 도미, 0은 빙어를 의미

#지도학습 데이터준비
total_length = bream_length + smelt_length
total_weight = bream_weight + smelt_weight

fish_data = [[l,w] for l,w in zip(total_length,total_weight)]
print(f"학습데이터:\n{fish_data}")
print(len(fish_data))

#라벨링(정답)작업
fish_target = [1]*35 + [0]*14
print(f"라벨데이터:\n{fish_target}")
print(len(fish_target))

 

○지도학습 준비

  - 알고리즘 객체생성

  - 학습 : 데이터별 학습(fish data)/테스트 데이터(fish target)를 구분하는 변수로 학습

  - 정확도 판단

#알고리즘 모델객체 생성
kn = KNeighborsClassifier()
#학습
kn.fit(fish_data,fish_target)
#정확도
kn.score(fish_data,fish_target)

  - 정확도 검증결과 100%

  - 어쩔수없음. 왜냐하면 기존 데이터 자체 즉, 로우데이터 자체가 도미/빙어 데이터로 구분하여 순서대로 셋을 구축하였고, 라벨링 작업시 도미/빙어 순서에 맞춰 답을 부여하고 훈련했기 때문.

  - 이미 답은 정해져있던 과정이었다고 생각하면됨

 

○시각화

plt.scatter(bream_length, bream_weight)
plt.scatter(smelt_length, smelt_weight)
plt.scatter(30, 600,marker="^",c="r")
plt.xlabel("length")
plt.ylabel("weight")
plt.show()

학습 후 길이 30, 무게 600인 물고리를 표시해보니 도미쪽에 위치하는 것으로 보아 도미일 것으로 판단된다.

 

※ 하지만 k근접이웃 학습결과 100% 정확도를 가졌지만 미상의 값이 들어왔을때 실제로는 빙어 또는 도미임에도 다른 클래스로 분류될 수 있음. 즉, 위의 결과에 대한 정확도가 100%라고 확신할 수 없는 상황임.

※ 이런 상황을 고려하여 우리는 랜덤 셔플링을 통한 학습 데이터와 훈련데이터에 인덱스 번호를 부여하고 데이터를 섞어 학습/테스트 데이터 셋을 구비해야함. 즉, 도미와 빙어 데이터를 섞어서 훈련하고 테스트해야하는 과정이 필요함.

 

○데이터 준비

#도미와 빙어 데이터 합친것
fish_length = [25.4, 26.3, 26.5, 29.0, 29.0, 29.7, 29.7, 30.0, 30.0, 30.7, 31.0, 31.0,
                31.5, 32.0, 32.0, 32.0, 33.0, 33.0, 33.5, 33.5, 34.0, 34.0, 34.5, 35.0,
                35.0, 35.0, 35.0, 36.0, 36.0, 37.0, 38.5, 38.5, 39.5, 41.0, 41.0, 9.8,
                10.5, 10.6, 11.0, 11.2, 11.3, 11.8, 11.8, 12.0, 12.2, 12.4, 13.0, 14.3, 15.0]
fish_weight = [242.0, 290.0, 340.0, 363.0, 430.0, 450.0, 500.0, 390.0, 450.0, 500.0, 475.0, 500.0,
                500.0, 340.0, 600.0, 600.0, 700.0, 700.0, 610.0, 650.0, 575.0, 685.0, 620.0, 680.0,
                700.0, 725.0, 720.0, 714.0, 850.0, 1000.0, 920.0, 955.0, 925.0, 975.0, 950.0, 6.7,
                7.5, 7.0, 9.7, 9.8, 8.7, 10.0, 9.9, 9.8, 12.2, 13.4, 12.2, 19.7, 19.9]

print(len(fish_length))

 

○학습 데이터 준비

#학습데이터 준비
fish_data = [[l,w] for l,w in zip(fish_length,fish_weight)]
#라벨링작업
fish_target = [1]*35 + [0]*14

train_input = fish_data[:35]
train_target = fish_target[:35]
test_input = fish_data[35:]
test_target = fish_target[35:]

print(f"학습데이터:\n{fish_data}")
print(f"라벨데이터:\n{fish_target}")

 

○학습 및 정확도 테스트

#모델생성
kn=KNeighborsClassifier()
#학습
kn.fit(train_input,train_target)
#정확도
print(kn.score(test_input, test_target))

#확인
print(kn.predict(test_input))
print(test_target)

모델생성 및 학습 후 정확도가 0%가 나왔고 실제 학습용 데이터와 결과데이터를 비교해보니 서로 다른 데이터를 의미하는 것을 확인함

※ 정확도가 0%. 이유는 샘플링 편향떄문에 그럼. 따라서 이를 해결하고자 랜덤새플링을 위한 배열변환 및 인덱스 번호를활용한 shuffle + 전처리필요한 상황임

※ 위 같은 결과가 나온것은 도미 데이터 입력 후 빙어 데이터를 입력하였으므로 처음부터 샘플링 편향이라는 결과를 초래함

실제로 라벨 데이터를 보면 1 이 35번 연이어나온 후 0이 14번 나옴. 이를 섞지 않고 테스트 입력값 35개, 테스트 결과값 14개로 나눴으니 당연히 편향발생과 학습을해도 정확도가 0%밖에 안나오는 것임.

이 상황을 해결하기위해 전처리가 필요하고 샘플링 편향을 해소해야함. 이를 위해 np.random.shuffling()을 통해 데이터별 인덱스번호를 활용해 골고로 섞어 테스트 셋을 구축해야함.

input_arr = np.array(fish_data)
target_arr = np.array(fish_target)
print(f"input_arr:\n{input_arr}") #로우데이터 전처리
print(f"target_arr:\n{target_arr}") #라벨링(타겟)데이터 전처리

print(input_arr.shape) #2차원
print(target_arr.shape) #1차원

#셔플링작업
np.random.seed(42) #일정값 즉, 난수고정을 위해 시드값 설정
index = np.arange(49) #0~48까지 1차원 배열 생성. for 로우 데이터 셔플을 위해
np.random.shuffle(index) #위에서 생성한 1차원 배열 원소를 서로 섞음
print(f"shuffle데이터 :\n{index}")

 

○ np.random.shuffle()을 통해 섞인 인덱스번호를 테스트 셋에 적용하여, 훈련 데이터를 섞음

train_input = input_arr[index[:35]] #학습데이터 35개 섞음
train_target = target_arr[index[:35]] #라벨링데이터 35개 섞음
test_input = input_arr[index[35:]]
test_target = target_arr[index[35:]]

 

○ 시각화

plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(test_input[:,0], test_input[:,1])
plt.xlabel("length")
plt.ylabel("weight")
plt.show()

도비,빙어 데이터가 골고로 섞인것을 확인할 수 있음

○ 학습 및 정확도 테스트

#모델 객체생성
kn = KNeighborsClassifier()
#학습
kn.fit(train_input,train_target)
#정확도
print(kn.score(test_input, test_target))

#예측
a = kn.predict(test_input)
print(a)
b = test_target
print(b)

정확도 100%이며, 테스트용 입력 데이터와 테스트용 라벨 데이터를 보니 모두 일치하는 것을 확인

※ 중간정리

1. 로우데이터 전체를 학습시켰기때문에 정확도 100% 즉, 유의미한 정확도 산출불가
2. 로우데이터 중 train 35, test 14개를 나눠 학습해보니 샘플링 편향으로 정확도가 0%
3. 로우데이터 중 train 35, test 14개를 나누고 배열 변환 및 인덱스 shuffling을 통한 random 샘플링으로 학습 후 테스해보니 정확도가 100%

 

□ from sklearn.model_selection import train_test_split 을 활용한 트레이닝/테스트용 셋 구축

  ○ 해당 라이브러리를 활용한 트레이닝/테스트 셋을 구축하려면 데이터를 배열로 생성 및 np.column_stack을 활용하여 전처리를 해야함

#도미와 빙어 데이터 합친것
fish_length = [25.4, 26.3, 26.5, 29.0, 29.0, 29.7, 29.7, 30.0, 30.0, 30.7, 31.0, 31.0,
                31.5, 32.0, 32.0, 32.0, 33.0, 33.0, 33.5, 33.5, 34.0, 34.0, 34.5, 35.0,
                35.0, 35.0, 35.0, 36.0, 36.0, 37.0, 38.5, 38.5, 39.5, 41.0, 41.0, 9.8,
                10.5, 10.6, 11.0, 11.2, 11.3, 11.8, 11.8, 12.0, 12.2, 12.4, 13.0, 14.3, 15.0]
fish_weight = [242.0, 290.0, 340.0, 363.0, 430.0, 450.0, 500.0, 390.0, 450.0, 500.0, 475.0, 500.0,
                500.0, 340.0, 600.0, 600.0, 700.0, 700.0, 610.0, 650.0, 575.0, 685.0, 620.0, 680.0,
                700.0, 725.0, 720.0, 714.0, 850.0, 1000.0, 920.0, 955.0, 925.0, 975.0, 950.0, 6.7,
                7.5, 7.0, 9.7, 9.8, 8.7, 10.0, 9.9, 9.8, 12.2, 13.4, 12.2, 19.7, 19.9]

#로우데이터 전처리
fish_data = np.column_stack((fish_length,fish_weight))
print(f"fish_data :\n{fish_data[:5]}")
#정답(라벨) 데이터 생성
fish_target = np.concatenate((np.ones(35),np.zeros(14)))
print(f"fish target : {fish_target}")

훈련용 데이터를 column_stack을 사용하여 49행 2열인 데이터 셋으로 만듬

 

○트레이닝/테스트, 정답 데이터 구축

  - stratify를 사용하여 셋 구축시 샘플편향 현상을 방지

from sklearn.model_selection import train_test_split #훈련용/테스트용 데이터 생성을위해 라이브러리호출
#위에서한 인덱스활용 랜덤샘플링대체 방법
#테스트 데이터가 샘플링편향방지를 위해 stratify 지정
train_input, test_input, train_target, test_target = train_test_split(fish_data, fish_target, \
                                                                     stratify=fish_target, random_state=42) 
print(train_input.shape)
print(test_input.shape)
print(train_target.shape)
print(test_target.shape)

훈련용 36건, 테스트용 13건. 훈련정답 36건, 테스트 정답 13건 구축 완료

 

○시각화 : 길이 25, 무게 150인 물고기가 들어왔다고 가정하고 시각화 진행

plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25,150,marker="^")
plt.xlabel("length")
plt.ylabel("weight")
plt.show()

 

※ 들어온 신원미상의 물고기가 빙어인데 도미쪽에 위치한다. 그렇다면 도미일까 빙어일까? 빙어라면 왜 도미쪽에 위치할까? 아니면 진짜 도미인데 작은 사이즈인가? 우리는 모른다.

이와 같은 궁금증 해소를 위해 바로 x/y축별 단위와, 범례 통일 및 데이터 셋에 대하여 데이터 샘플링 작업을 진행해야함.

  데이터 스케일링이란 서로다른 단위의 값이 틀릴 경우, 해당 값들을 하나의 통계적 분포에 위치(변환) 및 통일시켜 해당 분포에서 위치한 값을 학습/테스트하는 작업을 의미함.<br>표준점수 z를 구하기위해 속성별 평균값과 표준편차가 필요함. z = (실제값 - 평균)/표준편차.

mean = np.mean(train_input, axis=0) #평균 : 길이평균, 무게평균
std = np.std(train_input, axis=0) #표준편차 : 길이표준편차, 무게표준편차
print(f"mean : {mean}")
print(f"std : {std}")
train_scaled = (train_input-mean)/std #브로드캐스팅 발생 : 넘파이 배열 사이에서 연산작용
print(f"train_scaled(z값 표준점수) : {train_scaled}")

 

○ 신종 물고기 데이터에 대한 스케일링 진행 및 시각화

new = ([25,150]-mean)/std #새로운 데이터에 대한 z값(표준점수) 구함
plt.scatter(train_scaled[:,0], train_scaled[:,1]) #훈련데이터는 데이터 스케일링된 데이터로 길이와 평균값을 의미
plt.scatter(new[0],new[1],marker="^")
plt.xlabel("length")
plt.ylabel("weight")
plt.show()

 

○ 트레이닝/테스트 데이터 학습, 라벨 데이터 구축

#스케일링된 데이터로 재학습
kn.fit(train_scaled, train_target)

#정답(라벨) 데이터 스케일링 작업
test_scaled = (test_input-mean)/std
print(test_scaled)
print(kn.score(test_scaled,test_target))

#예측
print(kn.predict([new]))

데이터 스케일링을 통해 데이터별 표준점수 셋 구축완료. 정확도 100%. 예측해보니 도미가 맞는 것으로 나옴.

 

○ 신규 데이터 이웃 5개에 대한 데이터 스케일링 후 시각화

distances, indexes = kn.kneighbors([new]) #스케일링된 신규 데이터에 대한 이웃 구하기
plt.scatter(train_scaled[:, 0], train_scaled[:, 1])
plt.scatter(new[0], new[1], marker="^")
plt.scatter(train_scaled[indexes, 0], train_scaled[indexes, 1], marker="D")  # 근접한 이웃데이터와 근접한 스케일링된 훈련데이터
plt.xlabel("length")
plt.ylabel("weight")
plt.show()

이웃 데이터 또한 데이터 스케일링을하여 시각화를 진행해보니 신종 물고기가 도미란것을 알 수 있음

'Python > 머신러닝+딥러닝 Ⅰ' 카테고리의 다른 글

확률적경사하강법  (1) 2024.03.22
Logistic_KNN  (0) 2024.03.22
규제(L1, L2)와 Ridge, Lasso  (0) 2024.03.15
Multiple Regression(다중회귀)  (0) 2024.03.15
KNN_Regression&LinearRegression  (1) 2024.03.13