728x90
KNN구현
KNN Classifier를 이용하지 않고 Numpy를 이용하여 구현
import pandas as pd
import numpy as np
from collections import Counter
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
df = pd.read_excel("./data/Raisin_Dataset.xlsx")
df.head()
x = df.drop(["Area", "Class"], axis = 1)
y = df["Class"]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.3, stratify = y,
random_state = 23)
mm = MinMaxScaler()
scaled_train = mm.fit_transform(x_train)
scaled_test = mm.transform(x_test)
# 최근접 이웃의 수
K = 5
scaled_train.shape
(630, 6)
scaled_train[0].shape
(6,)
# 예측 예시
print(scaled_train[[0]])
print(scaled_test[0])
print(np.linalg.norm(scaled_train[[0]] - scaled_test[0], axis = 1)) # 행마다 거리 계산을 하므로(열이 없어지므로) axis = 1
[[0.52583543 0.46227141 0.85537041 0.57472036 0.92706218 0.53550554]]
[0.31813744 0.36943103 0.75062248 0.34477868 0.61720112 0.34253226]
[0.49885434]
# 예측 예시
print(scaled_train[[0]])
print(scaled_test[0])
print(np.linalg.norm(scaled_train[[0, 1]] - scaled_test[0], axis = 1))
[[0.52583543 0.46227141 0.85537041 0.57472036 0.92706218 0.53550554]]
[0.31813744 0.36943103 0.75062248 0.34477868 0.61720112 0.34253226]
[0.49885434 0.39796089]
# 예측
# 하나의 데이터와 모든 훈련데이터와의 거리
dist = np.linalg.norm(scaled_train - scaled_test[0], axis = 1)
dist.shape
(630,)
dist
array([0.49885434, 0.39796089, 0.27425311, 0.64416338, 0.4191368 ,
0.45832072, 0.83293975, 0.27274125, 0.1901054 , 0.20282574,
0.52353718, 0.34759694, 0.18502169, 0.37220821, 0.12189319,
...
# 거리가 가장 가까운 데이터들의 인덱스
k_index = dist.argsort()[:K]
k_index
array([248, 454, 42, 551, 194], dtype=int64)
# 거리가 가장 가까운 데이터들의 레이블
k_nearest_label = y_train.values[k_index]
k_nearest_label
array(['Besni', 'Besni', 'Besni', 'Kecimen', 'Kecimen'], dtype=object)
most_common = Counter(k_nearest_label).most_common(1)
most_common
[('Besni', 3)]
# KNN 클래스 정의(:int 는 주석)
class KNN:
def __init__(self, k:int = 5):
'''
KNN 분류기 초기화
Parameters:
k : 최근접 이웃의 수
'''
self.k = k
def fit(self, x:np.ndarray, y:np.ndarray):
'''
학습 데이터와 레이블을 저장
Parameters
x: 학습 데이터, shape -> (num_samples, num_features)
y: 레이블, shape -> (num_samples,)
'''
self.x_train = x
self.y_train = y
def predict(self, x:np.ndarray) -> np.ndarray:
'''
새로운 데이터 포인트에 대한 예측을 수행
Parameters:
x: 테스트 데이터, shape -> (num_test_samples, num_features)
Returns:
예측된 레이블, shape -> (num_test_samples,)
'''
predictions = []
for i in x:
# 모든 학습 데이터와의 거리 계산
distances = np.linalg.norm(self.x_train - i, axis = 1)
# 가장 가까운 k개의 이웃의 인덱스
k_indexes = distances.argsort()[:self.k]
# 가장 많이 등장한은 레이블을 예측
k_nearest_labels = self.y_train[k_indexes]
most_common = Counter(k_nearest_labels).most_common(1)
predictions.append(most_common[0][0])
return np.array(predictions)
knn = KNN(k = 5)
knn.fit(scaled_train, y_train.values)
knn.predict(scaled_test[[0]])
array(['Besni'], dtype='<U5')
knn.predict(scaled_test)
array(['Besni', 'Kecimen', 'Besni', 'Besni', 'Kecimen', 'Kecimen',
'Kecimen', 'Besni', 'Kecimen', 'Kecimen', 'Besni', 'Besni',
'Kecimen', 'Besni', 'Besni', 'Kecimen', 'Kecimen', 'Kecimen',
...
# KNN 클래스 거리 가중치 추가
class KNN:
def __init__(self, k:int = 5, weighted:bool = False):
'''
KNN 분류기 초기화
Parameters:
k : 최근접 이웃의 수
'''
self.k = k
self.weighted = weighted
def fit(self, x:np.ndarray, y:np.ndarray):
'''
학습 데이터와 레이블을 저장
Parameters
x: 학습 데이터, shape -> (num_samples, num_features)
y: 레이블, shape -> (num_samples,)
'''
self.x_train = x
self.y_train = y
def predict(self, x:np.ndarray) -> np.ndarray:
'''
새로운 데이터 포인트에 대한 예측을 수행
Parameters:
x: 테스트 데이터, shape -> (num_test_samples, num_features)
Returns:
예측된 레이블, shape -> (num_test_samples,)
'''
predictions = []
for i in x:
# 모든 학습 데이터와의 거리 계산
distances = np.linalg.norm(self.x_train - i, axis = 1)
# 가장 가까운 k개의 이웃의 인덱스
k_indexes = distances.argsort()[:self.k]
# 가장 많이 등장한은 레이블을 예측
k_nearest_labels = self.y_train[k_indexes]
if self.weighted:
# 거리 가중치를 사용하는 경우(가까울수록 가중치 높음)
# 거리에 작은 값을 더하여 0으로 나누어지는 경우를 방지
weights = 1 / (distances[k_indexes] + 1e-5)
label_weights = dict()
for label, weight in zip(k_nearest_labels, weights):
label_weights[label] = label_weights.get(label, 0) + weight
# {"A" : 1/2, "B": 7/12}
# [A, 1/2], [B, 1/3], [B, 1/4]
# 0 + 1/2 0 + 1/3 1/3 + 1/4
# 가장 높은 가중치를 가진 레이블을 선택
predictd_label = max(label_weights.items(), key = lambda item: item[1])[0]
else:
# 거리 가중치 미사용
most_common = Counter(k_nearest_labels).most_common(1)
predictd_label = most_common[0][0]
predictions.append(predictd_label)
return np.array(predictions)
knn = KNN(k = 5, weighted = True)
knn.fit(scaled_train, y_train.values)
knn.predict(scaled_test[[0]])
array(['Besni'], dtype='<U5')
knn.predict(scaled_test)
array(['Besni', 'Kecimen', 'Besni', 'Besni', 'Kecimen', 'Kecimen',
'Kecimen', 'Besni', 'Kecimen', 'Kecimen', 'Besni', 'Besni',
'Kecimen', 'Besni', 'Besni', 'Kecimen', 'Kecimen', 'Kecimen',
...
(y_test == knn.predict(scaled_test)).mean()
0.8407407407407408
# KNN 클래스 거리 가중치 추가, 거리 측정 방식 추가
class KNN:
def __init__(self, k:int = 5, weighted:bool = False, metric:str = "euclidean"):
'''
KNN 분류기 초기화
Parameters:
k : 최근접 이웃의 수
'''
self.k = k
self.weighted = weighted
self.metric = metric
def distance(self, x1, x2):
if self.metric == "euclidean":
return np.sqrt(np.sum((x1 - x2) ** 2, axis = 1))
elif self.metric == "manhattan":
return np.sum(np.abs(x1 - x2), axis = 1)
else:
ValueError("지원하지 않는 거리 계산 방식입니다")
def fit(self, x:np.ndarray, y:np.ndarray):
'''
학습 데이터와 레이블을 저장
Parameters
x: 학습 데이터, shape -> (num_samples, num_features)
y: 레이블, shape -> (num_samples,)
'''
self.x_train = x
self.y_train = y
def predict(self, x:np.ndarray) -> np.ndarray:
'''
새로운 데이터 포인트에 대한 예측을 수행
Parameters:
x: 테스트 데이터, shape -> (num_test_samples, num_features)
Returns:
예측된 레이블, shape -> (num_test_samples,)
'''
predictions = []
for i in x:
# 모든 학습 데이터와의 거리 계산
distances = self.distance(self.x_train, i)
# 가장 가까운 k개의 이웃의 인덱스
k_indexes = distances.argsort()[:self.k]
# 가장 많이 등장한은 레이블을 예측
k_nearest_labels = self.y_train[k_indexes]
if self.weighted:
# 거리 가중치를 사용하는 경우(가까울수록 가중치 높음)
# 거리에 작은 값을 더하여 0으로 나누어지는 경우를 방지
weights = 1 / (distances[k_indexes] + 1e-5)
label_weights = dict()
for label, weight in zip(k_nearest_labels, weights):
label_weights[label] = label_weights.get(label, 0) + weight
# {"A" : 1/2, "B": 7/12}
# [A, 1/2], [B, 1/3], [B, 1/4]
# 0 + 1/2 0 + 1/3 1/3 + 1/4
# 가장 높은 가중치를 가진 레이블을 선택
predictd_label = max(label_weights.items(), key = lambda item: item[1])[0]
else:
# 거리 가중치 미사용
most_common = Counter(k_nearest_labels).most_common(1)
predictd_label = most_common[0][0]
predictions.append(predictd_label)
return np.array(predictions)
# K = 5, 거리 가중치 사용, 맨하탄 거리 사용
knn = KNN(k = 5, weighted = True, metric = "manhattan")
knn.fit(scaled_train, y_train.values)
knn.predict(scaled_test[[0]])
array(['Besni'], dtype='<U5')
knn.predict(scaled_test)
array(['Besni', 'Kecimen', 'Besni', 'Besni', 'Kecimen', 'Kecimen',
'Kecimen', 'Besni', 'Kecimen', 'Kecimen', 'Besni', 'Besni',
'Kecimen', 'Besni', 'Besni', 'Kecimen', 'Kecimen', 'Kecimen',
...
(y_test == knn.predict(scaled_test)).mean()
0.8481481481481481
728x90
'08_ML(Machine_Learning)' 카테고리의 다른 글
09_선형 회귀 실습 (0) | 2025.04.04 |
---|---|
08_선형회귀 (0) | 2025.04.04 |
06_KNN 심화 (0) | 2025.04.04 |
05_KNN회귀 (0) | 2025.04.04 |
04_KNN_타이타닉 분류 (0) | 2025.04.02 |