crossorigin="anonymous"> [회귀 알고리즘과 모델규제] Chapter 3-1. k-Nearest Neighbors Regression

[인공지능] 혼자 공부하는 머신러닝 + 딥러닝

[회귀 알고리즘과 모델규제] Chapter 3-1. k-Nearest Neighbors Regression

Writing coder 2024. 9. 1. 20:53
반응형

● Chapter 3의 학습목표

1. 지도 학습 알고리즘의 한 종류인 회귀 알고리즘에 대해 배웁니다.

2. 다양한 선형 회귀 알고리즘의 장단점을 이해합니다.

 

● Chapter 3-1의 학습 목표

지도 학습의 한 종류인 회귀 문제를 이해하고 k-Nearest Neighbors 알고리즘을 사용해 농어의 무게를 예측하는 회귀 문제를 풀어 봅니다.

 

● 지난 Chapter에서 배운 내용

1. Input & Target을 통해 model 학습 (K-Nearest Neighbors algorithm)

2. 학습 시에는 Train set와 Test set를 나눈다.

3. 한쪽에 치우치지 않도록 Sampling bias가 없도록 한다.

4. 정확한 거리를 측정하여 학습이 이루어 질 수 있도록 Scaling을 한다.

 

회귀문제 푸는 scheme 요약

1. 데이터 불러오기

2. Input 데이터 2차원 array 만들기 (.reshape(-1,1))

3. Train data 학습 시키기

4. Model 평가하기 (Train & Test set의 결정계수, Test set를 사용한 Error)

5, Over/Under fitting 확인하고 Model의 복잡도로 개선하기

 

[문제 상황]

 

1. 한빛 마켓에서는 농어의 마리수 대신 '무게' 단위로 판매를 하려고 합니다. (마리로 하면 품질이 안좋은 생선이 있을수 있기 때문)

2. 그런데, 공급처에서 무게를 잘못 측정해서 보냈습니다.

3. 무게는 없고, 농어의 [길이, 높이, 두께] 데이터가 있습니다.

4. 그리고, 다른 56마리의 정확하게 [길이, 높이, 두께] + [무게] 까지 정확하게 측정한 데이터가 있습니다.

 

# 고르는 상황이 아닌 예측해야하는 문제는 회귀문제입니다.

 

회귀 (Regression) : 임의의 어떤 숫자를 예측하는 문제, 두 변수 사이의 상관관계를 분석하는 방법

ex1) 경제 성장률

ex2) 배달이 도착할 시간 예측

cf) 19세기 통계학자, 사회학자인 프랜시스 골턴 (Francis Galton)이 처음 사용. 키가 큰 사람의 아이가 부모보다 더 크지 않다는 사실을 관찰하고 이를 평균으로 회귀한다. 라고 표현

  • K-최근접 이웃 회귀 (K-Nearest Neighbors algorithm) : 가장 가까운 이웃 샘플을 찾고 이 샘플들의 타깃 값을 평균하여 예측으로 삼습니다.
  • KNeighborsRegressor : k-최근접 이웃 회귀 모델을 만드는 scikit learn 클래스. n_neighbors 매개변수로 이웃의 개수를 지정하며, 기본값은 5 다른 매개변수는 KNeighborsClassifier 클래스와 거의 동일

 

 

 

 

이제 회귀 문제를 해결할 코드를 작성해 봅시다.

 

# 1. 데이터 불러오기

import numpy as np

perch_length = np.array([8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0, 21.0,
       21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5, 22.5, 22.7,
       23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5, 27.3, 27.5, 27.5,
       27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0, 36.5, 36.0, 37.0, 37.0,
       39.0, 39.0, 39.0, 40.0, 40.0, 40.0, 40.0, 42.0, 43.0, 43.0, 43.5,
       44.0])
perch_weight = np.array([5.9, 32.0, 40.0, 51.5, 70.0, 100.0, 78.0, 80.0, 85.0, 85.0, 110.0,
       115.0, 125.0, 130.0, 120.0, 120.0, 130.0, 135.0, 110.0, 130.0,
       150.0, 145.0, 150.0, 170.0, 225.0, 145.0, 188.0, 180.0, 197.0,
       218.0, 300.0, 260.0, 265.0, 250.0, 250.0, 300.0, 320.0, 514.0,
       556.0, 840.0, 685.0, 700.0, 700.0, 690.0, 900.0, 650.0, 820.0,
       850.0, 900.0, 1015.0, 820.0, 1100.0, 1000.0, 1100.0, 1000.0,
       1000.0])
len(perch_length), len(perch_weight)

 

#2. 데이터 분포 파악하기

import matplotlib.pyplot as plt

plt.scatter(perch_length, perch_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

 

#3. 훈련세트와 테스트세트 나누기

from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = train_test_split(perch_length, perch_weight, random_state = 42)

 

배웠던것 처럼 sampling bias가 없이 나누어져 있는지 확인합니다.

#4. Train, Test 세트 위치 확인하기

plt.scatter(train_input, train_target, c = 'b', label = 'Train set')
plt.scatter(test_input, test_target, c = 'r', label = 'Test set')
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

 

 

1차원 array → 2차원 array 전환 (.reshape())함수

  • reshape() : 배열의 크기를 바꾸는 method 입니다. 바꾸고자 하는 배열의 크기를 매개변수로 전달합니다. 바꾸기 전후의 배열 원소 개수는 동일해야 합니다.
## reshape 함수로 1차원 배열 2차원으로 만들기
# sci-kit learn을 학습 할 때는 2차원 array가 필요.

test_array = np.array([1,2,3,4])
print(test_array.shape)

# reshape 함수로 1차원 배열 2차원으로 만들기
test_array = test_array.reshape(2,2)
test_array.shape

# -1은 원소의 개수로 하라는 의미.
# (-1,1)은 n개의 원소를 가진 list가 있다면 n x 1 array를 만듦.

train_input = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # 10개의 원소
train_input = train_input.reshape(-1, 1) # 10x1의 array를 만듦.

# 이렇게 -1을 활용하면, 매번 원소의 개수를 외우지 않아도 돼서 편리하다.

 

scikit-learn에서는 2차원 array를 input 값으로 받는데, input 값의 형태를 보니, 원소가 42개인 1차원 array인것을 확인 할 수 있습니다.

# input 형태 확인하기
train_input.shape # (42,)

 

따라서, .reshape() 함수를 이용해서 1차원 array를 2차원 array로 바꿔줍니다.

#4. train, test set를 2차원으로 만들기

train_input = train_input.reshape(-1,1)
test_input = test_input.reshape(-1,1)
print(train_input.shape, test_input.shape) # (42,1), (14,1)

 

cf. numpy는 종종 배열의 매서드와 동일한 함수를 별도로 제공합니다. 이때 함수의 첫 번째 매개변수는 바꾸고자 하는 배열입니다. 예를 들어 test_array.reshape(2,2)는 np.reshape(test_array, (2,2))와 같이 바꿔 쓸 수 있습니다.

# 나의 생각 : 왜 model을 학습 시킬 때, target은 안바꾸고, input들만 2차원 array로 변경하는것일까?

sci-kit learn에서 K-Nearest Neighbors method에서 Train_input과 Test_input은 2차원 array로 변환해야 하며, Train_target과 Test_target은 그럴 필요가 없다. 왜냐하면, K-Nearest Neighbors algorithm에서 input은 행은 샘플, 열은 특성을 나타내기 때문에, 2차원 배열이며, target은 특정 하나의 목표치를 가지기 때문이다.

 

2차원 array로 바뀐 input을 훈련 시킨다.

#5. KNeighborsRegressor model에 train set를 훈련 시킴

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor()

knr.fit(train_input, train_target)

 

KNN classification model에서 train set로 학습 시키고 test set를 이용해 score를 확인했던거 처럼, KNN regression 문제에서도 결정계수와 error를 통해 model의 정확도?를 확인한다.

#6. test set를 통해 R^2 값을 확인함.

knr.score(test_input, test_target) # 0.992809406101064
#7. Test set의 예측값과 실제 값의 차이를 이용해 오차를 구해보기.
from sklearn.metrics import mean_absolute_error

predict = knr.predict(test_input)
mae = mean_absolute_error(predict, test_target)
print(mae) # 19.157142857142862 실제 값과 model의 예측 값의 차이가 이정도 난다.

 

결정계수(R^2) : 대표적인 회귀 문제의 성능 측정 도구, 1에 가까울수록 좋고, 0에 가까울수록 성능이 나쁜 모델

일반적으로 훈련 세트의 결정계수가 테스트 세트보다 값이 조금 더 높음.

 

 

Error의 종류들

  • mean_absolute_error() : 회귀 모델의 평균 절댓값 오차를 계산합니다. 첫 번째 매개변수는 타깃, 두 번째 매개변수는 예측값을 전달합니다. 이와 비슷한 함수로는 평균 제곱 오차를 계산하는 mean_squared_error()가 있습니다. 이 함수는 타깃과 예측을 뺀 값을 제곱한 다음 전체 샘플에 대해 평균한 값을 반환합니다.

 

 

 

 

Test set는 확인해 보았는데, Train set에 대해서 결정 계수는 어떤지 동시에 비교를 해보자

 

과대, 과소적합 

#9. 과대, 과소 적합 판단

print(knr.score(train_input, train_target)), print(knr.score(test_input, test_target)) # 과소적합

# 0.9698823289099254, 0.992809406101064

 

  • 과대적합 (Overfitting) : "model이 훈련세트에만 너무 적합하다." 라는 의미. 훈련 세트에서 점수는 좋으나, 테스트 세트에서 점수가 낮은 경우.
  • 모델이 훈련 세트에 너무 집착해서 데이터에 내재된 거시적인 패턴을 감지하지 못합니다. 
  • 과소적합 (Underfitting) : "model이 훈련세트에 '덜' 적합하다." 훈련 세트, 테스트 세트에 대해서 둘다 점수가 좋지 않거나, 테스트 세트에서만 점수가 좋은 경우.
  • 이 경우 더 복잡한 모델을 사용해 model이 전체적인 데이터 경향을 따르도록, 훈련 세트에 잘 맞는 모델을 만들어야 합니다.

   # 과소적합이 나타나는 이유   

     1) 모델이 너무 단순하여 훈련 세트에 적절히 훈련되지 않은 경우 (일반적인 경우)

     2) 훈련 세트와 테스트 세트의 크기가 매우 작기 때문. → 데이터가 작으면 테스트 세트가 훈련 세트의 특징을 따르지 못할 수 도 있다.

 

우리의 모델에서 test set의 결정계수가 더 크기 때문에 과소적합이다.

 

따라서, 우리는 model을 더 복잡하게 만들면 된다.

 

[모델을 복잡하게 만드는 방법]

k를 감소 시킬 경우 : 국지적인 패턴에 더 민감한 model이 됨.

[모델을 단순하게 만드는 방법]

k를 증가 시킬 경우 : 일반적인 패턴에 더 유사한 model이 됨.

 

# 위에 대한 설명

과대적합의 경우, model이 너무 train set에 맞게 훈련 되있기 때문에 복잡한 model을 가지고 있다. 따라서, 일반적인 패턴을 따르도록 k 값을 증가 시키면 된다.

과소적합인 경우, model이 너무 전체적인 경향을 따르기 때문에 단순한 model을 가지고 있다. 따라서, 주변 데이터에 영향을 잘 받도록  즉, 자신의 개성이 강하도록 해주면 되기 때문에, model을 더  방법은 k를 줄이는것이다.

 

과대적합, 너무 개개인 데이터에 model이 훈련 되어 있다.
과소적합, model이 너무 일반적인 경향을 따른다.

 

따라서, 위의 model은 단순한 모델이기 때문에, 보다 복잡한 model을 만들기 위해서, k 값을 줄이는 방법을 선택한다.

#9. 과소적합을 해소하기 위해, k의 개수를 줄인다.

knr.n_neighbors = 3

knr.fit(train_input, train_target)
print(knr.score(train_input, train_target)), print(knr.score(test_input, test_target))

#0.9804899950518966, 0.9746459963987609

 

Train set의 점수가 높아졌으며, 두 결정계수의 차이가 크게 나지 않기 때문에 k값을 3으로 지정하면 된다.

최적의 k를 찾는것은 chapter 5에서 배울것이다.

 

# 연습문제 : 주어진 데이터로 훈련 시킨 후에, k = 1, 5, 10에 따라 model의 차이점을 확인한다. 길이를 1 ~ 45까지 바꿔가면서 예측 값을 반환하는 형식으로 model과 train data point를 그려보기

# 연습문제 2

# 과대적합과 과소적합에 대한 이해를 돕는 문제

# k 값을 1, 5, 10으로 바꿔가며 훈련 진행
# 그 다음 농어의 길이를 5에서 45까지 바꿔가며 예측을 만들어 그래프로 나타내봄.
# n이 커짐에 따라 모델이 단순해지는가?

# 데이터 불러오기
import numpy as np

perch_length = np.array([8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0, 21.0,
       21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5, 22.5, 22.7,
       23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5, 27.3, 27.5, 27.5,
       27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0, 36.5, 36.0, 37.0, 37.0,
       39.0, 39.0, 39.0, 40.0, 40.0, 40.0, 40.0, 42.0, 43.0, 43.0, 43.5,
       44.0])
perch_weight = np.array([5.9, 32.0, 40.0, 51.5, 70.0, 100.0, 78.0, 80.0, 85.0, 85.0, 110.0,
       115.0, 125.0, 130.0, 120.0, 120.0, 130.0, 135.0, 110.0, 130.0,
       150.0, 145.0, 150.0, 170.0, 225.0, 145.0, 188.0, 180.0, 197.0,
       218.0, 300.0, 260.0, 265.0, 250.0, 250.0, 300.0, 320.0, 514.0,
       556.0, 840.0, 685.0, 700.0, 700.0, 690.0, 900.0, 650.0, 820.0,
       850.0, 900.0, 1015.0, 820.0, 1100.0, 1000.0, 1100.0, 1000.0,
       1000.0])

# 훈련 세트와 테스트 세트로 나눔

from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = train_test_split(perch_length, perch_weight)

train_input = train_input.reshape(-1,1)

# 모델 형성

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor()

# x 값을 지정

x = np.arange(5,46).reshape(-1,1)

# k를 바꿔 가면서 예측 했을 때, 그래프 그리기

import matplotlib.pyplot as plt

plt.scatter(train_input, train_target, c = 'b', label = 'Train set') # 훈련세트
plt.legend()  

for n in [1,5,10]:
  
  knr.n_neighbors = n
  knr.fit(train_input, train_target)
  prediction = knr.predict(x)
  plt.plot(x, prediction, label = 'k={}'.format(n))  # model 만들기
  plt.xlabel('length')
  plt.ylabel('weight')
  plt.title('Model complexity by k')
  plt.legend()

 

 

  •  format() 함수 : Python의 문자열 포매팅(string formatting) 방법 중 하나이다. 이 방법을 사용하면 문자열에 변수의 값을 삽입할 수 있습니다. 
k = 5
label = 'k={}'.format(k)
print(label)  # 출력: 'k=5'

.format() 메서드는 문자열 내의 중괄호 {}를 채우는 데 사용됩니다. 중괄호 {}는 포맷 문자열 내에서 플레이스홀더로 작동하며, 이 자리에는 format() 메서드에 전달된 인자가 들어가게 됩니다.

 

 

 

1. K-Nearest Neighbors classification VS regression

https://velog.io/@cha-suyeon/%ED%98%BC%EA%B3%B5%EB%A8%B8-K-Nearest-Neighbors-R

 

2. 결정계수, error 설명 (나도코딩)

https://www.youtube.com/watch?v=TNcfJHajqJY

반응형