핀아의 저장소 ( •̀ ω •́ )✧

[ML] 혼자 공부하는 머신러닝_3장-1 본문

Big Data/ML & DL

[ML] 혼자 공부하는 머신러닝_3장-1

_핀아_ 2023. 5. 22. 15:55
💡 학습 목표
1. 지도 학습 알고리즘 중 하나인 회귀에 대해 학습
2. K-최근접 이웃 회귀와 선형 회귀 그리고 다항 회귀에 대해 학습
3. 다중 회귀에 대해 알아보고 특성 공학에 대해 학습

목차

  1. K-최근접 이웃 회귀
    1. 농아 데이터 준비
    2. 데이터 분리
    3. K-최근접 이웃 회귀 알고리즘 학습 및 정확도 측정
    4. 과대적합 vs 과소적합
  2. 선형 회귀
    1. K-최근접 이웃의 한계
    2. 선형회귀 알고리즘
    3. 다항 회귀
  3. 다중 회귀
    1. 특성공학
    2. 데이터 준비
    3. 특성 만들기
    4. 다중 회귀 모델 훈련 및 모델 점수 측정
  4. 규제
    1. 릿지(ridge)
    2. 라쏘(lasso)

1️⃣ K-최근접 이웃 회귀

💡 회귀 알고리즘에 대해 알아보고 회귀 알고리즘의 한 종류인 K-최근접 이웃 회귀에 대해 정리하고 농어의 길이만 가지고 무게를 예측해보기

회귀란?

  • 지도 학습 알고리즘의 한 종류
  • 클래스 중 하나로 분류하는 것이 아니라 임의의 어떤 숫자를 예측하는 알고리즘

K-최근접 이웃 회귀란?

  • 1장~2장에서 학습한 K-최근접 이웃 분류 알고리즘과 똑같이 예측하려는 샘플에 가장 가까운 샘플 K개를 선택함
  • 이때 예측하려는 샘플의 타깃은 어떤 클래스가 아니라 임의의 수치임
  • 예측 샘플의 타깃을 구하는 방법은 예측 샘플로부터 가까운 K개의 샘플 타깃 값의 평균을 구함

농어 데이터 준비

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, label="perch")
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
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(train_input.shape, test_input.shape)
# (42,) (14,) 1차원 튜플의 형태

⚠️ 사이킷런에 사용할 훈련 및 테스트 입력 값은 2차원 배열이여야 함

# 1차원 튜플 -> 2차원 배열 형태로 형태 변환
# reshape 메서드를 통해 형태를 변환 할 수 있음
# reshape의 매개변수 중 -1의 이미는 배열의 크기를 자동으로 지정함을 의미함

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) 2차원 배열 형태

 K-최근접 이웃 회귀 알고리즘 학습 및 정확도 측정

💡 사이킷런의 fit() 메서드를 사용하여 알고리즘 훈련을 수행하고 score() 메서드를 통해 출력값의 의미를 확인해봄 K-최근접 이웃 회귀 알고리즘은 사이킷런의 KNeighborsRegressor 클래스에 구현되어 있음
from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor()
knr.fit(train_input, train_target)

knr.score(test_input, test_target)
#0.992809406101064
  • 1장~2장의 분류의 socre 값은 테스트 세트에 있는 샘플을 정확하게 분류한 개수의 비율을 의미했음
  • 3장의 회귀의 경우 예측하는 값과 타깃 모두 임의의 수치기 때문에 분류의 socre 값처럼 정답을 맞힌 개수의 비율을 측정하기가 어려움

결정계수

💡 회귀의 score 값은 분류와 다른 값으로 평가하는데 이 점수를 결졍계수(R^2)라 부름

  • 타깃의 평균 정도를 예측하는 수준이라면 분자와 분모의 값이 비슷해져 결정 계수는 0에 가까워지고 타깃이 예측에 아주 가까워지면 분모가 0에 가까워지기 때문에 결정계수는 1에 가까워짐

 타깃과 예측의 절댓값 오차의 평균

💡 위에 계산된 score 0.99의 수치가 얼마나 좋은 값인지 이해하기 어려우니 평균 절댓값 오차을 사용하여 직관적인 수치로 확인해봄예측이 평균적으로 19g 정도 타깃값과 다르다는 것을 알 수 있음
  • 예측이 평균적으로 19g 정도 타깃값과 다르다는 것을 알 수 있음

과대적합 vs 과소적합

💡 과대적합과 과소적합에 대해 알아보고 앞에서 훈련한 모델을 사용해 훈련 세트의 결정계수를 확인해봄
  • 과대적합(overfitting)
    • 모델의 훈련 세트 점수가 테스트 세트 점수보다 훨씬 높을 경우를 의미함
    • 훈련 세트에 너무 맞추어져 있기 때문에 훈련 세트 이외의 다양한 변수에는 대응하기 어려움
    • 이 문제를 해결하기 위해서 훈련 세트를 추가, 정규화 과정(규제, 드롭 아웃 등)을 통해 적당한 복잡도를 가지는 모델을 찾기, 훈련 세트의 잡음을 줄임 등 다양한 방법이 존재함
  • 과소적합(underfitting)
    • 모델의 훈련 세트와 테스트 세트 점수가 모두 동일하게 낮거나 테스트 성능이 오히려 높을 경우를 의미함
    • 모델이 너무 단순하여 훈련 세트에 적절히 훈련되지 않은 경우를 의미함
    • 이 문제를 해결하기 위해서 훈련 세트와 테스트 세트 추가, 복잡한 모델 만들기 등 다양한 방법이 존재함

훈련 세트를 기반으로 모델을 학습하기 때문에 일반적으로 훈련 세트의 정확도는 테스트 세트의 정확도 보다 더 높게 측정이됨

훈련 세트의 정확도와 테스트 세트의 정확도가 비슷하게 나오는 모델이 좋은 모델임을 알 수 있음

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor()
knr.fit(train_input, train_target)

print(knr.score(test_input, test_target))
#0.992809406101064

print(knr.score(train_input, train_target))
#0.9698823289099254
  • 테스트 세트의 정확도는 0.99, 훈련 세트의 정확도는 0.96의 값을 나타냄
  • 위 케이스의 경우 과소적합됨을 알 수 있음

과소적합 문제 해결하기

💡 과소접합의 문제를 해결하기 위해 모델을 복잡하게 만들어 봄
K-최근접 이웃 알고리즘에서 모델을 복잡하게 하는 만드는 방법으로 이웃의 개수를 줄여 일반적인 패턴을 줄이고 국지적 패턴에 민감하게 반응 하도록 함
# 이웃의 개수를 5개에서 3개로 줄임
knr.n_neighbors = 3
# 모델을 다시 훈련함
knr.fit(train_input, train_target)
print(knr.score(train_input, train_target))
#0.9804899950518966

print(knr.score(test_input, test_target))
#0.9746459963987609
  • 이웃 k 값을 줄여 모델을 훈련한 결과 훈련 세트의 정확도는 0.98, 테스트 세트의 정확도는 0.97의 값을 나타냄
  • k=5 모델 대비 k=3 모델일 때 훈련 세트의 정확도가 높아졌음을 확인 할 수 있음
  • 훈련 세트의 정확도가 높아지고 테스트 세트의 정확도가 낮아졌으므로 과소적합 문제를 해결함

2️⃣ 선형 회귀

💡 K-최근접 이웃 회귀와 선형 회귀의 차이점에 대해 학습해봄

 K-최근접 이웃의 한계

길이가 50, 70, 100인 농어의 무계를 예측해보고 K-최근접 이웃의 한계에 대해 확인해봄

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)
# 훈련 세트와 테스트 세트를 2차원 배열로 바꿉니다
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor(n_neighbors=3)
# k-최근접 이웃 회귀 모델을 훈련합니다
knr.fit(train_input, train_target)

#길이가 50, 70, 100인 농어의 무게 예측하기
print(knr.predict([[50]]))
#[1033.33333333]
print(knr.predict([[70]]))
#[1033.33333333]
print(knr.predict([[100]]))
#[1033.33333333]

  • 길이가 50, 70, 100인 농어의 무게를 예측해보니 모두 1033g으로 예측함
  • K-최근접 이웃 회귀 알고리즘 특성상 근접한 이웃 샘플의 평균 값을 구하여 예측하기 때문에 새로운 샘플이 훈련 세트의 범위를 벗어날 경우 엉뚱한 값을 예측할 수 있음
  • 이 문제를 해결하기 위해 길이가 큰 농어를 포함하여 훈련 세트를 다시 만들거나 다른 알고리즘을 사용하여 예측해야함

 선형회귀 알고리즘

💡 선형 회귀는 널리 사용되는 대표적인 회귀 알고리즘이며 특성이 하나인 경우 어떤 직선을 학습하는 알고리즘임
사이킷런 라이브러리의 LinearRegression 클래스로 구현되어있음
하나의 직선을 학습하기 위해 coef(기울기)와 intercept(절편)이 필요하며 y= ax+b의 형태를 가지고있음
여기서 x는 농어의 길이, y는 무게로 나타 낼 수 있음
from sklearn.linear_model import LinearRegression

lr = LinearRegression()
# 선형 회귀 모델 훈련
lr.fit(train_input, train_target) # 학습 세트와 훈련 세트는 앞 절에서 정의하여 생략함

# 50cm 농어에 대한 예측
print(lr.predict([[50]]))
#[1241.83860323]

#기울기(coef)와 절편(intercept) 확인
#기울기 대신 계수(coefficient) 또는 가중치(weight)라 부를 수 있음
print(lr.coef_, lr.intercept_)
#[39.01714496] -709.0186449535477
  • 길이 50 농어에 대해 무게를 예측한 결과 1241g으로 예측함
  • 기울기의 값은 39.017, 절편의 값은 -709.018 임을 확인함

# 훈련 세트의 산점도를 그립니다
plt.scatter(train_input, train_target, label='train set')
# 15에서 50까지 1차 방정식 그래프를 그립니다
plt.plot([15, 50], [15*lr.coef_+lr.intercept_, 50*lr.coef_+lr.intercept_], label = 'ax+b')
# 50cm 농어 데이터
plt.scatter(50, 1241.8, marker='^', label='new fish')
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

print(lr.score(train_input, train_target))
#0.939846333997604
print(lr.score(test_input, test_target))
#0.8247503123313558

  • 선형 회귀 알고리즘이 이 데이터 세트에서 찾은 최적의 직선을 확인함
  • 훈련 세트와 테스트 세트의 점수가 둘다 높지 않음, 과소적합됨을 확인함
  • 길이가 약 20cm 이하인 농어의 무게를 예측 할 시 음수가 나오는 문제를 확인함

다항 회귀

💡 선형 회귀를 사용하여 예측 했을때 예측 값이 음수가 나오는 문제를 해결하기 위해 다항회귀를 사용하여 농어의 무게를 예측해봄
다항 회귀란 다항식을 사용하여 특성과 타깃 사이의 관계를 나타낸 선형 회귀임
다항 회귀는 2차 방정식 그래프(곡선)를 학습시켜 예측 값이 음수가 나오는 것을 방지함
2차 방정식 그래프를 구하기 위해 y = ax^2+bx+c의 수식이 필요하며, x는 길이, y는 무게로 나타낼 수 있음
# 하나의 특성(무게)을 사용하여 다항식에 사용될 같은 종류의 새로운 특성 값을 만듦

#numpy 브로드 캐스팅을 사용하여 모든 원소를 제곱함
train_poly = np.column_stack((train_input ** 2, train_input))
'''
array([[ 384.16,   19.6 ],
       [ 484.  ,   22.  ],
       [ 349.69,   18.7 ],
       [ 302.76,   17.4 ],
       [1296.  ,   36.  ],
				...
'''
test_poly = np.column_stack((test_input ** 2, test_input))
'''
array([[  70.56,    8.4 ],
       [ 324.  ,   18.  ],
       [ 756.25,   27.5 ],
       [ 453.69,   21.3 ],
       [ 506.25,   22.5 ],
				...
'''

print(train_poly.shape, test_poly.shape)
#(42, 2) (14, 2)

lr = LinearRegression()
lr.fit(train_poly, train_target)

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

print(lr.coef_, lr.intercept_)
#[  1.01433211 -21.55792498] 116.0502107827827
  • 길이, 길이 제곱근 특성 값을 기반으로 학습 시킴
  • 길이 50인 농어의 무게를 1573g으로 예측함
  • 길이 제곱근에 대한 기울기의 값은 1.01, 길이에 대한 기울기의 값은 -21.55, 절편 값은 116.05임을 확인함

# 구간별 직선을 그리기 위해 15에서 49까지 정수 배열을 만듭니다
point = np.arange(15, 50)
# 훈련 세트의 산점도를 그립니다
plt.scatter(train_input, train_target, label='train set')
# 15에서 49까지 2차 방정식 그래프를 그립니다
plt.plot(point, 1.01*point**2 - 21.6*point + 116.05, label="ax^2+bx+c")
# 50cm 농어 데이터
plt.scatter([50], [1574], marker='^', label = 'new fish')
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

print(lr.score(train_poly, train_target))
#0.9706807451768623
print(lr.score(test_poly, test_target))
#0.9775935108325121

  • 특정 길이 이하 농어의 무게가 음수가 되는 문제를 해결함
  • 테스트 세트와 훈련 세트에 대한 점수가 크게 높아져 개선된 모델임을 알 수 있음
  • 그러나 여전히 테스트 세트의 점수가 훈련 세트의 점수보다 더 높게 측정됨(과소적합)
Comments