본문 바로가기

프로그래밍/AI

[혼공머신 11기] 2주차 스터디 및 과제

반응형

2주차 후기

새로운 용어가 마구마구 쏟아져 나왔다. 결정계수, 과대적합, 과소적합, 하이퍼파라미터 등등...
책 내용이 주어진 문제를 해결하고, 그 결과의 문제점을 찾고, 다시 새로운 방법으로 해결하는 방식으로 스토리가 있다. 마치 예전에 열심히 공부했던 토비의 스프링처럼.
그래서 큰 주제들은 지나온 과정을 되새기면 기억이 난다. 그러나 그 과정에서 불쑥불쑥 튀어나오는 용어들은 정확하게 기억이 나지 않아서 공부하며 책 뒷부분을 계속 찾아보면서 익숙해지기 위해 노력했다.

그리고 오랜만에 방정식을 보게 되어 당황했다. 이쪽 공부를 하면 수학공부도 하게 된다던데... 다행히 아직은 고등학교 수준이 나와서 쉽게 넘어갈 수 있었는데, 뒷부분에 더 어려운게 나오려나...? 수학책을 다시 펼쳐봐야 하나...?

마지막으로 밤에 혼공하고 누웠는데 갑자기 "책은 9장까지 있는데 왜 혼공은 6주차만 있지?"라는 생각이 들었다. 그래서 일정을 확인해보니 7장까지만 계획되어 있었다. 8,9장은 알아서 공부해야 하는건가 보다. 그래서 6주 동안 책 전부를 보려고 2주차에 미리 4장을 공부하기 시작했다. 그런데 4장은 내용이 더 어려워서 반복해서 보느라 공부 속도가 더 안난다. 그래도 대충대충 보지 말고, 의미있는 공부를 해보자!!

기본 미션 : Chapter 03-1. 2번 문제 출력 그래프 인증하기

과대적합과 과소적합에 대한 이해를 돕기 위해 복잡한 모델과 단순한 모델을 만들겠습니다.
앞서 만든 k-최근접 이웃 회귀 모델의 k 값을 1, 5, 10으로 바꿔가며 훈련해 보세요.
그 다음 농어의 길이를 5에서 45까지 바꿔가며 예측을 만들어 그래프로 나타내 보세요.
n이 커짐에 따라 모델이 단순해지는 것을 볼 수 있나요?

Code

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsRegressor

# 농어의 길이
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])

# 농어 데이터를 훈련 세트와 테스트 세트로 나누기
train_input, test_input, train_target, test_target = train_test_split(perch_length, perch_weight, random_state=42)
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)

# 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)
  # 지정한 범위 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('wieght')
  plt.show()

Colab 실행결과

선택 미션 : 모델 파라미터에 대해 설명하기

  • 모델 파라미터
    • 훈련을 통해 머신러닝 알고리즘이 찾는 값
    • 예를 들면 선형 회귀 알고리즘의 방정식은 y = ax + b 이다. 이때 a는 기울기(coefficient)이고, b는 절편(intercept)이다. 선형 회귀 알고리즘은 훈련을 통해 주어진 특성을 가장 잘 나타낼 수 있는 직선을 학습하는데, 그 결과 가장 잘 나타낼 수 있는 a, b 값을 찾게 된다.
  • 하이퍼 파라미터
    • 머신러닝 알고리즘이 학습할 수 없고, 사람이 직접 알려줘야 하는 값
    • 예상되는 여러 값 중에서 최적의 값을 찾기 위한 "탐색" 작업이 필요하다.
    • 예를 들면 머신러닝 모델이 훈련세트에 과대적합이 되지 않도록 규제를 하는데, 이때 규제 강도를 결정하는 alpha 매개변수가 하이퍼 파라미터이다. 즉, 여러 개의 값을 테스트하여 가장 적절한 규제 강도를 찾아야 한다.

03. 회귀 알고리즘과 모델 규제

주요 메서드

03-1. k-최근접 이웃 회귀

k-최근접 이웃 알고리즘

  • 지도 학습 알고리즘
    • 분류(classifier) : 샘플을 몇 개의 클래스 중 하나로 분류하는 문제
    • 회귀(regression) : 임의의 어떤 숫자를 예측하는 문제
  • k-최근접 이웃 분류 알고리즘
    1. 예측하려는 샘플에 가장 가까운 샘플 k개를 선택한다. (이웃한 샘플의 타킷은 어떤 클래스)
    2. 선택된 샘플 중에서 다수 클래스를 새로운 샘플의 클래스로 예측한다.
  • k-최근접 이웃 회귀 알고리즘
    1. 예측하려는 샘플에 가장 가까운 샘플 k개를 선택한다. (이웃한 샘플의 타킷은 임의의 수치)
    2. 이웃한 샘플 수치들의 평균으로 예측 타깃값을 구한다.

결정계수(R2, coefficient of determination)

  • k-최근접 이웃 분류 알고리즘에서의 score() 메서드 결과는 테스트 세트에 있는 샘플을 얼마자 정확한게 분류하였는지 정답을 맞힌 개수의 비율을 뜻하는 정확도였다.
  • k-최근접 이웃 회기 알고리즘에서는 예측하는 값과 타깃 모두 임의의 수치이기 때문에 정확한 숫자를 맞히는 것을 거의 불가능하다.
  • 결정계수 : 대표적인 회귀 문제의 성능 측정 도구. 1에 가까울수록 좋고, 0에 가까우면 성능이 나쁜 모델이다.
  • 결정계수 계산법
    • $$R^2 = 1 - \frac{(타깃 - 예측)^2의,합}{(타깃 - 평균)^2의,합}$$
    • 타깃이 평균 정도를 예측하는 수준이면, 분자와 분모가 비슷해져 R2는 0에 가까워진다.
    • 예측이 타깃에 아주 가까워지면, 분자가 0에 가까워지기 때문에 1에 가까운 값이 된다.
  • 정량적인 평가에는 사이킷런에서 제공하는 다른 평가 도구를 사용할 수 있다. (ex> 절대값 오차)

과대적합 vs. 과소적합

일반적으로 훈련 세트의 점수가 테스트 세트의 점수보다 높고, 둘 간의 점수 차이가 크면 좋지 않다.

  • 과대적합(overfitting)
    • 훈련 세트에서 점수가 굉장히 좋았지만, 테스트 세트에서는 점수가 굉장히 나쁜 경우
    • 즉, 훈련 세트에만 맞는 모델
    • 모델을 덜 복잡하게 만들어야 한다.
  • 과소적합(underfitting)
    • 훈련 세트보다 테스트 세트의 점수가 높거나, 두 점수가 모두 너무 낮은 경우
    • 즉, 모델이 너무 단순하여 훈련 세트에 적절히 훈련되지 않은 경우
    • 훈련 세트와 테스트 세트의 크기가 매우 작은 경우 발생할 수 있다.
    • 훈련 세트에 더 잘 맞게 모델을 조금 더 복잡하게 만들어 해결할 수 있다.
      • k-최근접 이웃 알고리즘에서는 이웃의 개수를 줄여서 해결할 수 있다. (기본값 : 5)
      • 이웃의 개수를 줄이면 훈련 세트에 있는 국지적인 패턴에 민감해진다.
      • 이웃의 개수를 늘리면 데이터 전반에 있는 일반적인 패턴을 따른다.

실습

Code

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_absolute_error

# 농어의 길이
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])

# 농어 데이터의 형태를 파악하기 위한 산점도 그리기
plt.scatter(perch_length, perch_weight)
plt.xlabel('length')  # 특성 데이터
plt.ylabel('weight')  # 타깃 데이터
print("### 농어 길이, 무게 산점도 ###")
plt.show()
print()

# 농어 데이터를 훈련 세트와 테스트 세트로 나누기
train_input, test_input, train_target, test_target = train_test_split(perch_length, perch_weight, random_state=42)

# 입력(input) 데이터를 2차원 배열로 바꾸기
#   - 사이킷런에 사용할 훈련 세트는 2차원 배열이어야 한다.
#   - perch_length가 1차원 배열이기 때문에 train_input, test_input 모두 1차원 배열이다.
#   - numpy의 reshape() 메서드에 -1을 지정하면 나머지 원소 개수로 모두 지정하라는 의미이다.
print("### 2차원 배열로 바꾸기 ###")
print(f"before train_input shape : {train_input.shape}")
train_input = train_input.reshape(-1, 1)
print(f"after train_input shape : {train_input.shape}")
print(f"before test_input shape : {test_input.shape}")
test_input = test_input.reshape(-1, 1)
print(f"after test_input shape : {test_input.shape}")
print()

###
# k-최근접 이웃 회기 알고리즘
###
# 훈련 세트로 모델을 훈련하고, 테스트 세트/훈련 세트로 평가하기
knr = KNeighborsRegressor()
knr.fit(train_input, train_target)  # 훈련
print("### k-최근접 이웃 회기 알고리즘 ###")
print(f"테스트 세트의 결정계수 : {knr.score(test_input, test_target)}")
print(f"훈련 세트의 결정계수 : {knr.score(train_input, train_target)} - 테스트 세트의 결정계수보다 낮다 > 과소적합")
print()

# 테스트 세트에 대한 예측을 만든 후 테스트 세트에 대한 평균 절대값 오차 계산하기
print("### mean_absolute_error를 사용하여 예측이 어느정도 벗어났는지 가늠하기###")
test_prediction = knr.predict(test_input)
print(f"테스트 세트에 대한 예측 : {test_prediction}")
mae = mean_absolute_error(test_target, test_prediction)
print(f"테스트 세트에 대한 평균 절대값 오차 : {mae} - 결과에서 예측이 평균적으로 19g 정도 타깃값과 다름을 의미")
print()

# k-최근접 이웃 알고리즘의 이웃의 개수를 줄여 과소적합 해결하기
knr.n_neighbors = 3
knr.fit(train_input, train_target)
print("### k-최근접 이웃 알고리즘의 이웃의 개수를 5->3으로 줄이기 ###")
print(f"테스트 세트의 결정계수 : {knr.score(test_input, test_target)}")
print(f"훈련 세트의 결정계수 : {knr.score(train_input, train_target)} - 테스트 세트의 결정계수보다 높아졌다.")

Colab 실행결과


03-2. 선형 회귀

k-최근접 이웃의 한계

  • k-최근접 이웃 회귀는 가장 가까운 샘플을 찾아 평균을 구해 타깃값으로 결정한다. 따라서 샘플이 훈련 세트의 범위를 벗어나면 엉뚱한 값을 예측할 수 있다.

실습

Code

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)

# 길이가 50cm인 농어의 무게를 예측한다.
print(f"길이 50cm 농어의 예측 무게 : {knr.predict([[50]])} - 실제 농어의 무게는 훨씬 더 무겁다고 한다...!!")
print(f"길이 100cm 농어의 예측 무게 : {knr.predict([[100]])} - 50cm 농어의 예측 무게랑 똑같다....\n")


###
# 훈련 세트와 50cm 농어 및 최근접 이웃의 산점도
###
import matplotlib.pyplot as plt

# 50cm 농어의 이웃을 구한다.
distances, indexes = knr.kneighbors([[50]])
distances2, indexes2 = knr.kneighbors([[100]])

# 훈련 세트의 산점도를 그린다.
plt.scatter(train_input, train_target)

# 훈련 세트 중에서 이웃 샘플만 다시 그린다.
plt.scatter(train_input[indexes], train_target[indexes], marker='D')

# 50cm 농어 데이터
plt.scatter(50, 1033, marker='^')
plt.scatter(100, 1033, marker='*')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
print()

# 이웃 샘플들의 무게 평균
print(f"50cm 농어의 이웃 샘플 무게 평균 : {np.mean(train_target[indexes])}")
print(f"100cm 농어의 이웃 샘플 무게 평균 : {np.mean(train_target[indexes2])}")
print("# 결론 : 무게가 늘어나도 이웃 샘플이 동일하기 때문에 같은 값으로 예측한다.")

Colab 실행 결과


선형 회귀 (linear regression)

  • 대표적인 회귀 알고리즘
  • 특성이 하나인 경우 그 특성을 가장 잘 나타낼 수 있는 직선을 학습한다.
  • 선형 회귀는 $y = a \times x + b$ 직선 방정식에서 데이터에 가장 잘 맞는 $a$(기울기)와 $b$(절편)를 찾는다.
    • $a$는 coef_ 속성, $b$는 intercept_ 속성에 저장되어 있다.
    • 기울기를 의미하는 coef_는 계수(coefficient) 또는 가중치(weight)라고도 부른다.
    • coef_와 intercept_는 머신러닝 알고리즘이 찾는 값으로 모델 파라미터(model parameter)라고 부른다.
  • 단순히 훈련 세트를 저장하는 것이 훈련의 전부인 k-최근접 이웃 알고리즘사례 기반 학습이라고 부르고, 훈련 과정을 통해 최적의 모델 파라미터를 찾는 선형 회귀모델 기반 학습이라고 부른다.
  • 선형 회귀는 특성이 많을 수록 효과가 좋다.

실습

Code

import numpy as np
from sklearn.model_selection import train_test_split

# 농어의 길이
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])

# 훈련 세트와 테스트 세트로 나눈다.
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.linear_model import LinearRegression

lr = LinearRegression()

# 선형 회귀 모델 훈련
lr.fit(train_input, train_target)

# 50cm 농어의 무게 예측
print(f"50cm 농어의 예측 무게 : {lr.predict([[50]])}")
print(f"100cm 농어의 예측 무게 : {lr.predict([[100]])}\n")

# 모델 파라미터 출력
print("### 선형회귀 모델 파라미터 ###")
print(f"coef_ : {lr.coef_}, intercept_ : {lr.intercept_}\n")


###
# 산점도
###
import matplotlib.pyplot as plt

# 훈련 세트 산점도 그리기
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')

print("### 선형회귀 알고리즘이 찾은 최적의 직선 ###")
plt.show()
print()

# 결정계수
print("### 결정계수 ###")
print(f"훈련 세트 : {lr.score(train_input, train_target)}")
print(f"테스트 세트 : {lr.score(test_input, test_target)}\n")

print("# 결론 : 전체적으로 과서적합되었다. 그리고 그래프를 보면 농어의 무게가 0kg 이하로 예측될 수 있는 문제점이 있다.")
print(f"15cm 농어의 예측 무게 : {lr.predict([[15]])} - 농어의 무게를 -(마이너스)로 예측한다...!!")

Colab 실행 결과


다항 회귀 (polynomial regression)

  • 다항식을 사용한 선형 회귀
  • 선형 회귀는 $y = a \times x^2 + b \times x + c$ 직선 방정식에서 데이터에 가장 잘 맞는 $a$, $b$, $c$를 찾는다.

실습

Code

import numpy as np
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

# 농어의 길이
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])

# 훈련 세트와 테스트 세트로 나눈다.
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)

# 데이터의 제곱 데이터를 원본 데이터 앞에 붙인다.
train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))
print(train_poly.shape, test_poly.shape)
print()

###
# 다항 회귀
###
from sklearn.linear_model import LinearRegression

lr = LinearRegression()

# 훈련
lr.fit(train_poly, train_target)

print("### 다항 회귀 ###")
print(f"50cm 농어의 예측 무게 : {lr.predict([[50**2, 50]])}")
print(f"100cm 농어의 예측 무게 : {lr.predict([[100**2, 100]])}\n")

# 모델 파라미터 출력
print("### 선형회귀 모델 파라미터 ###")
print(f"coef_ : {lr.coef_}, intercept_ : {lr.intercept_}\n")

###
# 산점도
###
# 구간별 직선을 그리기 위해 15에서 49까지 정수 배열을 만든다.
point = np.arange(15, 50)

# 훈련 세트의 산점도를 그린다.
plt.scatter(train_input, train_target)

# 15에서 49까지 2차 방정식 그래프를 그린다.
plt.plot(point, 1.01*point**2 - 21.6*point + 116.05)

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

plt.xlabel('length')
plt.ylabel('weight')

print("### 선형회귀 알고리즘이 찾은 최적의 직선 ###")
plt.show()
print()

# 결정계수
print("### 결정계수 ###")
print(f"훈련 세트 : {lr.score(train_poly, train_target)}")
print(f"테스트 세트 : {lr.score(test_poly, test_target)}\n")

print("# 결론 : 점수가 크게 높아졌지만 여전히 테스트 세트의 점수가 더 높아 과소적합이 아직 남아 있다.")

Colab 실행 결과


03-3. 특성 공학과 규제

특성 공학

  • 주어진 특성을 조합하여 새로운 특성을 만드는 일련의 작업 과정

다중 회귀 (multiple regression)

  • 여러 개의 특성을 사용한 선형 회귀
    • 특성 1개 : 직선을 학습 (2차원)
    • 특성 2개 : 평면을 학습 (3차원)
  • 선형 회귀는 특성이 많을 수록 효과가 좋다.
  • 특성 공학 (feature engineering)
    • 기존 특성을 사용해 새로운 특성을 뽑아내는 작업

사아킷런의 변환기 (transformer)

  • 특성을 만들거나 전처리하기 위해 사이킷런에서 제공하는 클래스

    • PolynomialFeatures : 각 특성을 제곱한 항을 추가하고, 특성끼리 서로 곱한 항을 추가한다.
  • fit(), transform() 메서드 제공

  • PolynomialFeatures예시

    • 방정식 :
    • 2와 3을 제곱한 4, 9 추가
    • 2와 3을 서로 곱한 6 추가
    • 1은 왜 추가되었을까?
      include_bias=True (기본값) 일 때, 선형 방정식($y = a \times x + b \times y + c \times z + d \times 1$)의 절편에서 항상 1인 특성과 곱해지는 계수이기 때문에 추가된다.

  • from sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures(include_bias=True) poly.fit([[2, 3]]) print(poly.transform([[2, 3]])) # 결과 : [[1, 2, 3, 4, 6, 9]]

실습

Code

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

###
# 특성 3개 농어 데이터 가져오기
###
# 농어 데이터 (길이, 높이, 두께)
# 판다스로 csv 파일을 데이터프레임으로 읽은 후 넘파이 배열로 변환
df = pd.read_csv('http://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
# print("### 농어 데이터 (길이, 높이, 무게) ###")
# print(perch_full)
print()

# 타깃 데이터
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])

# 훈련 세트와 테스트 세트
train_input, test_input, train_target, test_target = train_test_split(perch_full, perch_weight, random_state=42)

for degree in [2, 5]:
  ###
  # 사이킷런 변환기
  ###
  from sklearn.preprocessing import PolynomialFeatures

  poly = PolynomialFeatures(degree=degree, include_bias=False) # degree 기본값 : 2
  poly.fit(train_input)

  train_poly = poly.transform(train_input)
  test_poly = poly.transform(test_input)  # 훈련 세트를 기준으로 테스트 세트를 변환하는 것이 좋다.

  print(f"--------------- PolynomialFeatures의 degree={degree} ---------------")
  print("### PolynomialFeatures 클래스 변환기 ###")
  print(f"변환한 훈련 세트 데이터 - 배열의 크기 : {train_poly.shape}")
  print(f"변환한 테스트 세트 데이터 - 배열의 크기 : {test_poly.shape}")
  print(f"변환한 데이터 - 특성 조합 : {poly.get_feature_names_out()}\n")

  ###
  # 다중 회귀 모델 훈련
  ###
  from sklearn.linear_model import LinearRegression

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

  # 결정계수
  print("### 결정계수 ###")
  print(f"훈련 세트 : {lr.score(train_poly, train_target)}")
  print(f"테스트 세트 : {lr.score(test_poly, test_target)}\n")

print("# 결론 : degree 값을 늘리면 특성이 많아져 훈련 세트는 좋아지지만, 과대적합이 되어 테스트 세트는 점수가 좋지 않다.")

Colab 실행 결과


규제 (regularization)

  • 머신러닝 모델이 훈련 세트를 너무 과도하게 학습하지 못하도록 하는 것 이다. 즉, 모델이 훈련 세트에 과대적합이 되지 않도록 만드는 것이다.
  • 선형 회귀 모델의 경우 특성에 곱해지는 계수(또는 기울기)의 크기를 작게 만드는 일이다.
  • 선형 회귀 모델의 규제 방법에는 대표적으로 릿지(ridge)라쏘(lasso)가 있다.

표준화, 정규화

  • 규제를 하기 위해서는 먼저 특성의 스케일을 조정해야 한다.
  • 대표적인 방법으로는 z 점수, 표준점수를 사용한다.
  • 사이킷런의 StandardScaler 클래스를 사용하여 쉽게 표준화 할 수 있다.

릿지 회귀, 라쏘 회귀

  • 릿지
    • 계수(가중치)를 제곱한 값을 기준으로 규제를 적용
    • 선형회귀 모델에서는 릿지라고 부르지만, 다른 모델에서는 L2 규제라고도 부른다.
    • 일반적으로 라쏘 회귀보다 릿지 회귀를 더 선호한다.
  • 라쏘
    • 계수의 절대값을 기준으로 규제를 적용
    • 선형회귀 모델에서는 릿지라고 부르지만, 다른 모델에서는 L1 규제라고도 부른다.
  • 규제 강도
    • 모델 객체 생성 시 alpha 매개변수로 규제의 강도를 조절할 수 있다. (기본값 : 1)
    • alpha 값이 큼 > 규제 강도가 강해짐 > 계수 값을 줄임 > 과소적합 되도록 유도
    • alpha 값이 작음 > 규제 강도가 약해짐 > 계수를 줄이는 역할이 줄어듬 > 선형 회귀 모델과 유사해져 과대적합 가능성 높아짐
  • 하이퍼파라미터(hyperparameter)
    • 규제 강도를 조정하는 alpha 값과 같이 머신러닝 모델이 학습할 수 없고, 사람이 직접 알려줘야 하는 파라미터
    • 여러 값을 사용해가면서 최적의 하이퍼파라미터를 찾는 과정을 "하이퍼파라미터 탐색"이라고 부른다.
    • 아래 실습 코드에서는 [0.001, 0.01, 1, 10, 100] 값들 중에서 훈련 세트 점수가 높으면서, 훈련 세트와 테스트 세트의 점수가 가장 근접한 값을 최적의 값으로 판단했다.

실습

릿지 회귀 Code

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

###
# 특성 3개 농어 데이터 가져오기
###
# 농어 데이터 (길이, 높이, 두께)
df = pd.read_csv('http://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
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])

# 훈련 세트와 테스트 세트
train_input, test_input, train_target, test_target = train_test_split(perch_full, perch_weight, random_state=42)


###
# 사이킷런 변환기
###
from sklearn.preprocessing import PolynomialFeatures

degree = 5
poly = PolynomialFeatures(degree=degree, include_bias=False) # degree 기본값 : 2
poly.fit(train_input)

train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)  # 훈련 세트를 기준으로 테스트 세트를 변환하는 것이 좋다.

###
# 규제 적용 전 정규화
###
from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_poly)

train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)

###
# 릿지 회귀
###
from sklearn.linear_model import Ridge

ridge = Ridge()
ridge.fit(train_scaled, train_target)
print(f"--------------- PolynomialFeatures의 degree={degree} ---------------")
print("### 릿지 회귀 결정계수 ###")
print(f"훈련 세트 : {ridge.score(train_scaled, train_target)}")
print(f"테스트 세트 : {ridge.score(test_scaled, test_target)}\n")

# 하이퍼파라미터 탐색 : 적절한 alpha 값 찾기
train_score = []
test_score = []
alpha_list = [0.001, 0.01, 1, 10, 100]
for alpha in alpha_list:
  ridge = Ridge(alpha=alpha)
  ridge.fit(train_scaled, train_target)
  train_score.append(ridge.score(train_scaled, train_target))
  test_score.append(ridge.score(test_scaled, test_target))

import matplotlib.pyplot as plt
plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
print("### alpha 값 별 릿지 회귀 결정계수 그래프 ###")
plt.show()
print()

print("### 최적의 alpha 값 찾기 ###")
alpha = 0.01
ridge = Ridge(alpha=alpha)
ridge.fit(train_scaled, train_target)
print(f"alpha={alpha} 일때, 훈련 세트 점수 : {ridge.score(train_scaled, train_target)}")
print(f"alpha={alpha} 일 때, 테스트 세트 점수 : {ridge.score(test_scaled, test_target)}\n")

릿지 회귀 Colab 실행 결과

라쏘 회귀 Code

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

###
# 특성 3개 농어 데이터 가져오기
###
# 농어 데이터 (길이, 높이, 두께)
df = pd.read_csv('http://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
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])

# 훈련 세트와 테스트 세트
train_input, test_input, train_target, test_target = train_test_split(perch_full, perch_weight, random_state=42)


###
# 사이킷런 변환기
###
from sklearn.preprocessing import PolynomialFeatures

degree = 5
poly = PolynomialFeatures(degree=degree, include_bias=False) # degree 기본값 : 2
poly.fit(train_input)

train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)  # 훈련 세트를 기준으로 테스트 세트를 변환하는 것이 좋다.

print(f"--------------- PolynomialFeatures의 degree={degree} ---------------")

###
# 규제 적용 전 정규화
###
from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_poly)

train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)

###
# 라쏘 회귀
###
from sklearn.linear_model import Lasso

lasso = Lasso()
lasso.fit(train_scaled, train_target)

print("### 라쏘 회귀 결정계수 ###")
print(f"훈련 세트 : {lasso.score(train_scaled, train_target)}")
print(f"테스트 세트 : {lasso.score(test_scaled, test_target)}\n")

# 하이퍼파라미터 탐색 : 적절한 alpha 값 찾기
max_iter = 200000

train_score = []
test_score = []

alpha_list = [0.001, 0.01, 1, 10, 100]
for alpha in alpha_list:
  lasso = Lasso(alpha=alpha, max_iter=max_iter)
  lasso.fit(train_scaled, train_target)
  train_score.append(lasso.score(train_scaled, train_target))
  test_score.append(lasso.score(test_scaled, test_target))

import matplotlib.pyplot as plt
plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
print(f"### alpha 값 별 라쏘 회귀 결정계수 그래프 (max_iter={max_iter}) ###")
plt.show()
print()

print("### 최적의 alpha 값 찾기 ###")
alpha = 10
lasso = Lasso(alpha=alpha, max_iter=max_iter)
lasso.fit(train_scaled, train_target)
print(f"alpha={alpha} 일때, 훈련 세트 점수 : {lasso.score(train_scaled, train_target)}")
print(f"alpha={alpha} 일 때, 테스트 세트 점수 : {lasso.score(test_scaled, test_target)}\n")

print("### 실제로 사용한 특성의 개수 ###")
total_feature_count = len(poly.get_feature_names_out())
unused_feature_count = np.sum(lasso.coef_ == 0)
print(f"PolymonialFeatures로 변환한 특성의 총 개수 : {total_feature_count}")
print(f"계수 값이 0인 라쏘 모델 특성 개수 : {unused_feature_count}")
print(f"라쏘 모델이 실제 사용한 특성 개수 : {total_feature_count - unused_feature_count}")

라쏘 회귀 Colab 실행 결과

반응형