본문 바로가기
Programming/Python

[ML] KN 회귀 알고리즘, 선형회귀, 다항회귀

KN 분류: 말 그대로 주변 바탕으로 (분류 번호 예측 후) 자동 분류 작업

KN 회귀: 주변 바탕으로 특정 데이터 값을 예측

 

이번 데이터는 농어의 길이와 무게 샘플을 바탕으로 잘못 측정된 한 농어의 무게를 추정하는 상황이다.(길이는 괜찮다.)

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, random_state=42)
# 종류가 하나 뿐이므로 stratify를 굳이 사용하지 않는다. 생략시 stratify = False 적용
# 해당 split 함수는 length(input)를 통해 weight(target)를 예측하는 구조이다.

print(train_input.shape, test_input.shape)

(42,) (14,)

 

문제점은 해당 배열이 1차원. 사이킷런은 2차원 mtx(배열) 다루는 것을 예상하기에 인위적으로 바꿔줘야함.

 

 

이는 reshape(-1, 1)로 可   (-1로 나머지 원소 개수로 자동정렬)

예제)

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

(4,)   <- 1차원 배열을 바꿔보자

test_array = test_array.reshape(-1, 1) 
test_array   # -1이 아닌 경우 해당 size mtx 출력(단 원소 개수 일치해야함)

array([[1],
       [2],
       [3],
       [4]])

 

1차원을 2차원으로 바꾼 모습이다. 해당 행렬의 크기는 test_array.shape에 의해 (4.1)로 출력된다.

 

즉, 적용하면

 

train_input = train_input.reshape(-1, 1) # 1행 기준으로 세로로 변환(ML학습위해)
test_input = test_input.reshape(-1, 1)
print(train_input.shape, test_input.shape)

(42, 1) (14, 1)

 

이제 머신러닝 회귀를 적용하자

 

 

 

from sklearn.neighbors import KNeighborsRegressor   

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

0.992809406101064  <- Coefficient of determinination(결정계수)이다.    KN classify에서는 맞춘 개수의 비율이였다.

높으면 정확도 높고 낮으면 정확도 낮다.

 

target ~ 예측값 -> 분자가 0이됨 -> 1에 가까워짐 -> 높은 정확도.

평균값 ~ 예측값 -> 분자가 분모와 거의 같아짐 -> 0에 가까워짐 -> 낮은 정확도

 

 

결정계수 정확도는 MAE로 확인한다.

from sklearn.metrics import mean_absolute_error # 단독으로 잘 사용은 안함. MAE

# test set 예측
test_prediction = knr.predict(test_input)  # the coefficient of determination

mae = mean_absolute_error(test_target, test_prediction) # test set 평균 절댓값 오차 계산(MAE)
print(mae)

19.157142857142862   <- target과 예측값의 차이 평균이다. (평균 19g 차이)

 

 

 

그렇다면 train data의 결정계수는 어떠할까?

print(knr.score(train_input, train_target))

0.9698823289099254

 

결정계수 값이 train data < test data   인 이상한 결과 도출

 

[다시 개념정리]

Overfitting(과대적합): 정확도가 train은 좋은데 test가 엉망인 경우; 실전 예측성 떨어짐

Underfitting(과소적합): 정확도가 train은 엉망인데 test가 좋은 경우; 또는 두 점수 모두 너무 낮은 경우

                                     why?) model is too simple!! or data set is too small! -> not trained well

 

 

여기선 underfitting 문제 발생!

 

본 예제에서는 데이터 크기를 더 늘릴 수 없으니 model 복잡화를 진행한다.

KN 회귀에서는 n 개수를 줄임으로써 복잡화를 진행(데이터 경향따르기보다 적은 데이터로 회귀해야하니 복잡해짐)

n_neighbors 

knr.n_neighbors = 3  # 근방 개수 줄임(복잡화)
knr.fit(train_input, train_target)  # model 재훈련
print(knr.score(train_input, train_target))
print(knr.score(test_input, test_target))

0.9804899950518966  <- train

0.9746459963987609  <- test

 

underfitting 문제 해결

 

 

 

실제로 n의 값이 커질수록 예측선은 단순해진다.

# k-최근접 이웃 회귀 객체를 만듭니다
knr = KNeighborsRegressor()
# 5에서 45까지 x 좌표를 만듭니다
x = np.arange(5, 45).reshape(-1, 1)

# n = 1, 5, 10일 때 예측 결과를 그래프로 그립니다.
for n in [1, 5, 10]:
    # 모델 훈련
    knr.n_neighbors = n
    knr.fit(train_input, train_target)
    print(knr.score(train_input, train_target))
    # 지정한 범위 x에 대한 예측 구하기 
    prediction = knr.predict(x)
    # 훈련 세트와 예측 결과 그래프 그리기
    plt.scatter(train_input, train_target)
    plt.plot(x, prediction)
    plt.title('n_neighbors = {}'.format(n))    
    plt.xlabel('length')
    plt.ylabel('weight')
    plt.show()

 

 

 

이 머신러닝을 실제 50cm의 경우를 예측하는데에 적용해보자

 

 

print(knr.predict([[50]]))

[1033.33333333]

 

 

42cm or 43cm의 무게가 1033근처인데 50cm도 차이가 없다는 점이 이상하다.

 

 

그래프를 확인하면

import matplotlib.pyplot as plt

distances, indexes = knr.kneighbors([[50]]) # 50cm 농어 이웃 3개
print('indexes : ', indexes)
print('distances : ', distances)
plt.scatter(train_input, train_target) # 훈련 세트 산점도
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
# 훈련 세트 중 이웃 샘플 Diamond

plt.scatter(50, 1033, marker='^')  # 50cm 농어 데이터
plt.show()

indexes :  [[34  8 14]]
distances :  [[6. 7. 7.]]

 

print(knr.predict([[100]]))  #길이가 100이면 무게가 얼마나 될까? (결과가 이상함)

[1033.33333333]

 

KN 근접방식을 바탕으로한 회귀 알고리즘의 한계인듯 하다.

 

 

 

 

선형회귀 알고리즘을 사용해야한다.

lr.coef_     lr.intercept_      

from sklearn.linear_model import LinearRegression

lr = LinearRegression() # 선형회귀 객체화
lr.fit(train_input, train_target) # 훈련

print(lr.predict([[50]])) # 50cm 농어에 대한 예측 출력
print(lr.coef_, lr.intercept_)  #회귀선 계수(기울기)와 절편값 출력

plt.scatter(train_input, train_target)
plt.plot([15, 50], [15*lr.coef_+lr.intercept_, 50*lr.coef_+lr.intercept_])
# 15에서 50까지 1차 함수 그래프 생성위해 범위 지정. 즉, interval: x in [15,50] & y는 회귀식 in []

plt.scatter(50, 1241.8, marker='^')  # 50cm 농어 데이터
plt.show()

[1241.83860323]    <- 50cm 농어 예측 weight

[39.01714496]         -709.0186449535477   <- 회귀선 계수 및 절편값 

 

print(lr.score(train_input, train_target))   # train 회귀 정확도 측정.
print(lr.score(test_input, test_target))     # test 회귀 정확도 측정

0.9398463339976039
0.8247503123313558

 

 

 

 

근데 이 선은 무게가 0 밑으로 내려가는 경우를 상정한다는 문제점이 있다.

-> 직선보단 곡선으로 정확도를 높여보자.

 

 

2차함수 생성위해 그냥 x제곱한 항목 추가하고, 같이 stack로 묶어 새로 정의한다.

train_poly = np.column_stack((train_input ** 2, train_input)) # 제곱이랑 그냥 x데이터 삽입
test_poly = np.column_stack((test_input ** 2, test_input))

print(train_poly.shape, test_poly.shape) #multi linear regresssion

(42, 2) (14, 2)

 

 

 

lr = LinearRegression()  # 객체화
lr.fit(train_poly, train_target)  # 훈련
print(lr.predict([[50**2, 50]]))  # 제곱한 항에 맞춰서 input도 제곱값 , 기존값 같이 적용
print(lr.coef_, lr.intercept_)

[1573.98423528]

[  1.01433211 -21.55792498] 116.0502107827827

 

 

 

point = np.arange(15, 50)  # 구간별 직선 그리기 위해 15에서 49까지 정수 배열 생성

plt.scatter(train_input, train_target)
plt.plot(point, 1.01*point**2 - 21.6*point + 116.05)  # 15에서 49까지 2차 함수 그래프
plt.scatter([50], [1574], marker='^')  # 50cm 농어 데이터 1574는 위 predict 값에서 가져옴
plt.show()

print(lr.score(train_poly, train_target))  # 정확도 수치 파악
print(lr.score(test_poly, test_target))

0.9706807451768623   <- train set
0.9775935108325122   <- test set          정확도가 이전보다 상승 (그러나 train<test 라서 찜찜한 부분이 아직도 남음)

 

 

 

다음 게시물에서 계속

 

 

 

 

728x90
반응형