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

KNN_Regression&LinearRegression

by Mr.DonyStark 2024. 3. 13.

□ K 최근접 이웃 회귀(K-NN Regression)

  ○ K-최근접 이웃 회귀모델은 분류와 동일하게 가장먼저가까운 k개의 이웃을 찾음.

  ○ 그다음 이웃샘플의 타깃값을 평균하여 샘플의 예측값으로 사용.
  ○ 사이킷런은 회귀모델의 점수로 R^2 즉 결정계수 값을 반환.

  ○ 이 값은 1에 가까울수록 좋으며 정량적인 평가르 하고싶다면 사이킷런에서 제공하는 다른 평가 도구를 사용할 수 있음.

 

선형 회귀(LinearRegression)

  ○ 기본개념 : 평균(기대값)으로 돌아간다.
  ○ 즉, 평균값에 가까워지려는 경향을 의미하는게 회귀임.
  ○ 널리 사용되는 대표적인 회귀 알고리즘.
  ○ 선형이란 말에서 짐작할 수 있듯이 특성이 하나인 경우 어떤 직선을 학습하는 알고리즘.
  ○ 특성을 가장 잘 나타낼 수 있는 직선을 찾아야함.
  ○ 특성과 타깃사이의 관계를 잘 나타내는 선형방정식을 찾음.

    → *특성이 하나면 직선방정식
  ○ 선형 회귀가 찾은 특성과 타깃사이의 관계는 선형방정식의 계수 또는 가중치에 저장됨.

 

상황 : 농어 물고기의 길이와 무게 데이터를 학습 후 신규 농어 길이에 따른 무게를 예측해보자

○ 라이브러리 및 데이터 저장

#라이브러리 호출
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]
     )

 

○ 시각화 : 산점도

#시각화 : 산점도
import matplotlib.pyplot as plt
plt.scatter(perch_length,perch_weight)
plt.xlabel("length")
plt.ylabel("weight")
plt.show()

농어 길이가 증가됨에 따라 무게도 증가되는 것을 알 수 있음

 

○ 트레이닝/테스트 셋 나누기

#트레이닝/테스트 셋 나누기
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)

print(f"train_input:\n{train_input.ndim}")
print(f"train_input:\n{train_input.shape}")
print(f"train_input:\n{train_input[:5]}")
print(f"train_input:\n{test_input.ndim}")
print(f"test_input:\n{test_input.shape}")
print(f"test_input:\n{test_input[:5]}")
print("\n1차원 배열인 것을 2차원배열로 전환해야함")

 

○ 사이킷런의 train_test_split() 함수사용 후 학습을 위해 각 세트는 2차원 배열형태를 가져야함

train_input = train_input.reshape(-1,1) #-1을 지정하면 나머지 원소개수로 모두채우라는 의미임
test_input = test_input.reshape(-1,1)

print(f"train_input:\n{train_input.ndim}")
print(f"train_input:\n{train_input.shape}")
print(f"train_input:\n{train_input[:5]}")
print(f"train_input:\n{test_input.ndim}")
print(f"test_input:\n{test_input.shape}")
print(f"test_input:\n{test_input[:5]}")

트레이닝/테스트 세트 모두 2차원 배열로 전처리 완료

 

※ 분류에서는 정확도에 초점을 맞췄다면, 회귀는 정확한 숫자를 맞추는 것이 불가능.

왜냐하면 회귀에서 예측하는 값이나 타깃 모두 임의의 수치이기 때문.
※ 회귀에서는 정확도보다는 결정계수 R^2 로 모델을 평가함.
  → R^2= 1-((타깃-예측)^2 합 / (타깃-평균)^2 합)<br>
0~1 범위로 1에 가까울수록 예측 타깃에 가까워진다는 의미
분류에서는 standard scaling 작업에 집중했고, 그이유는 분류에서의 초점은 정확하게 분류하는 것이 목적이기 때문 For (표준점수 : Z)

반면 회귀는 실제데이터와 예측값에 대한 차이를 최대한 줄이는것이 목적이기때문에 평가지수가 R^2 임

 

○ K최근점 이웃 회귀 모델 생성 및 학습

#모델 생성 및 훈련 : 결정계수(R^2)
from sklearn.neighbors import KNeighborsRegressor
#모델생성
knr = KNeighborsRegressor()
#모델훈련
knr.fit(train_input,train_target)
#점수 확인
print(f"점수\t:\n{knr.score(test_input,test_target)}")

 

○ 학습된 모델에 테스트 세트를 입력 후 예측 진행

  - 예측 진행 후 mean_absolute_error를 활용해 실제값과 예측값으로 오차를 확인

from sklearn.metrics import mean_absolute_error

#테스트세트 예측
test_predict = knr.predict(test_input)
print(test_predict)

#결정계수 오차 확인
mae = mean_absolute_error(test_target, test_predict) #실제값과 예측값으로 오차확인
print(f"오차\t:\t{round(mae,2)}") #약 19g 정도의 오차가 발생함

 

과대적합/과소적합
   * 보통 훈련 세트의 점수가 조금더 높게나오는데, 그 이유는 훈련세트에서 모델을 훈련했으므로 훈련 세트에서 더 좋은 점수가 나옴
 (1) 과대적합 : 훈련 점수는 좋으나 테스트 점수가 굉장히 낮을경우
 (2) 과소적합 : 테스트 점수가 훈련 점수보다 다소 높거나 두점수가 모두 낮을 경우. 이는 훈련/테스트 데이터 양이 매우 적기때문임.

print(f"트레이닝 점수\t:\t{knr.score(train_input,train_target)}")
print(f"테스트 점수\t:\t{knr.score(test_input,test_target)}")
print("테스트 점수가 높은것으로 과소적합으로 보임")

 

과대/과소 적합 해결을 위해 우리는 K-neighbor 이웃 값을 조정하여 해결할 수 있음

  - 과소적합을 해결하려면 모델을 조금 더 복잡하게 만들면 됨.
  - k-최근접 이웃 알고리즘으로 모델을 더 복잡하게 만드는 방법은 이웃의 개수 K를 줄이는 것임.
  - k-최근접 이웃 알고리즘의 기본 k 값은 5임.

#이웃의 개수를 3으로 설정
knr.n_neighbors = 3 #지정을 안하면 기본값 5

#모델 재훈련
knr.fit(train_input,train_target)

#점수 확인
print(f"트레이닝 점수\t:\t{knr.score(train_input,train_target)}")
print(f"테스트 점수\t:\t{knr.score(test_input,test_target)}")

이웃의 개수를 조정함에 따라 훈련세트에 있는 국지적인 패턴에 민감해지고 이웃의 개수를 늘리면 데이터 전반에 있는 일반적인 패턴을 따름.

 

과대/과소 적합을 해결하였으나 우리는 K최근접 이웃 회귀분석의 한계를 경험함

  - 50cm 농어임에도 불구하고 1033g으로 예측됨.

  - 실제중량은 더 무거움.

  - 즉, 기존 학습데이터에서 벗어난 신규데이터가 입력될 경우 유의미한 예측이 불가하다는 한계에 다달음.

from sklearn.neighbors import KNeighborsRegressor

#k-최근접 이웃 회귀 모델 생성. 이웃은 기본값 5 에서 3으로 수정
knr = KNeighborsRegressor(n_neighbors=3)

#k-최근접 이웃 회귀 모델 훈련
knr.fit(train_input,train_target)

#예측 진행(길이 50cm농어)
nong_fish = np.array([[50]])
print(knr.predict(nong_fish))

 

원인파악을 위한 산점도 시각화

import matplotlib.pyplot as plt

#50cm 농어의 이웃을 구함
distances, indexes = knr.kneighbors(nong_fish)

#산점도 그리기
plt.scatter(train_input, train_target) #훈련세트
plt.scatter(50,1033, marker="*") #신규농어 50cm로 예측했을때 1033g으로 나왔기때문에
plt.scatter(train_input[indexes], train_target[indexes], marker="D") #이웃샘플 표시

plt.xlabel("length")
plt.ylabel("weight")
plt.show()

print(f"신규농어의 이웃 평균 무게 : {np.mean(train_target[indexes])}")

 

 

※ 시각화 그래프만으로 봤을때 길이가 커질수록 농어의 무게 또한 증가되는 것을 확인할 수 있음.

※ but 50cm 농어에서 가장 가까운것은 45cm 근방이기 때문에 k-최근접 이웃 알고리즘은 샘플들의 무게를 평균하기에 50cm에 대한 길이를 1033으로 예측했던것임.

※ K-최근접 이웃을 통한 예측 한계를 해결하려면 다른 알고리즘을 사용해야함.

 

○ 예측 모델인 Linear Regression 모델 생성 및 학습

#LinearRegression
from sklearn.linear_model import LinearRegression

lr = LinearRegression() #객체생성
lr.fit(train_input, train_target) #훈련학습

lr_nong = lr.predict([[50]]) #예측 : 길이 50인 농어
print(lr_nong)

모델을 바꿔 예측해보니 1241 무게를 예측

 

※ 예측과정
   - 하나의 예측을 위한 선형회귀를 위해 직선이 필요하며 하나의 직선을 그리려면 기울기와 절편이 필요함.
   - y=(a * x) +b
   - a=기울기, b=절편
    → 절편 : y축에 처음으로 지나는 지점
   - 선형회귀를 시행할때 기울기와 y절편이 중요함. 왜냐하면 기울기와 y절편을 바탕으로 회귀직선(추세선)을 그을 수 가 있는데 이때, 회귀직선(기존데이터 바탕으로 만들어진선)을 바탕으로 신규데이터에 대한 예측값을 산정할 수 있기 떄문임.

   - coef_(기울기)와 intercept_(절편)를 머신러닝 알고리즘이 찾은 값이라는 의미로 모델 파라미터라고 부름.

print(f"기울기(회계계수 또는 가중치) :\t{lr.coef_}")
print(f"절편 :\t{lr.intercept_}")

기울기는 구했지만 절편이 -이다. 뭔가 이상하여 시각화 진행.

○ 1차 다항식 + 산점도 시각화

#농어 길이 15~50까지 직선으로 그리기.
plt.scatter(train_input,train_target)

#15~50까지 1차 방정식 그래프 그리기 : 기울기 & 절편
plt.plot([15,50], [15*lr.coef_ + lr.intercept_, 50*lr.coef_ + lr.intercept_])

#50cm 농어 데이터
plt.scatter(50,1241.8, marker="^")
plt.xlabel("length")
plt.ylabel("weight")
plt.show()

print(lr.score(train_input, train_target)) #훈련 세트
print(lr.score(test_input, test_target)) #테스트 세트

과대, 과소적합은 정상으로 나타나는 것을 확인

 

※ 1차 직선을 적용해보니 위와 같이 20이하의 데이터가 들어오면 - 값을 예측하게됨. 이는 무게가 -인데 옳지않음. 따라서 1차 직선이 아닌 2차 다항식을 적용하는 것곽 같은 회귀차수를 높이는 절차가 필요함.

 

○ 2차 다항식을 위한 전처리 : y = (a*x^2) + (b*x) + c

  - a, b 는 기울기를 의미하고, c는 y 절편을 의미

  - 위 3개는 선행작업에서 진행함

train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))

print(train_poly.shape)
print(test_poly.shape)

 

2차원 배열로 전처리완료

 

○ 2차 다항식으로 전처리한 데이터 학습 및 회귀계수(기울기), y절편 구하기

#객체생성
lr = LinearRegression()

#학습
lr.fit(train_poly, train_target)

#예측
print(lr.predict([[50**2,50]]))

print(f"기울기(회귀계수 또는 가중치) :\t{lr.coef_}")
print(f"절편 :\t{lr.intercept_}")

50cm 가 들어왔을때 예상무게는 1573g 인것을 알 수 있음

 

○ 무게 = 1.01 * 길이^2 - 21.6 * 길이 + 116.05 를 의미하며 이를 2차 다항식 + 산점도 시각화

point = np.arange(15,50)
plt.scatter(train_input,train_target)
plt.plot(point, (1.01*point**2 - 21.6*point) + 116.05) #2차다항식을 바탕으로

plt.scatter(50,1574,marker="^")
plt.xlabel("length")
plt.ylabel("weight")
plt.show()

2차 다항식으로 시각화를 하고나서 보니, 회귀지수도 2차다항식과 같이 곡선을 보이고, 신규 길이값에 대한 무게 예측도 잘 된것으로 파악됨

 

'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
K-Nearest Neighbor : K-NN 알고리즘  (3) 2024.03.12