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. 회귀 알고리즘과 모델 규제
주요 메서드
- 넘파이
- reshape() : https://numpy.org/doc/stable/reference/generated/numpy.reshape.html
- column_stack()
- 사이킷런
- KNeighborsRegressor
- mean_absolute_error()
- LinearRegression
- PolynomialFeatures
- Ridge
- Lasso
- 판다스
- read_csv
03-1. k-최근접 이웃 회귀
k-최근접 이웃 알고리즘
- 지도 학습 알고리즘
- 분류(classifier) : 샘플을 몇 개의 클래스 중 하나로 분류하는 문제
- 회귀(regression) : 임의의 어떤 숫자를 예측하는 문제
- k-최근접 이웃 분류 알고리즘
- 예측하려는 샘플에 가장 가까운 샘플 k개를 선택한다. (이웃한 샘플의 타킷은 어떤 클래스)
- 선택된 샘플 중에서 다수 클래스를 새로운 샘플의 클래스로 예측한다.
- k-최근접 이웃 회귀 알고리즘
- 예측하려는 샘플에 가장 가까운 샘플 k개를 선택한다. (이웃한 샘플의 타킷은 임의의 수치)
- 이웃한 샘플 수치들의 평균으로 예측 타깃값을 구한다.
결정계수(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) - 이웃의 개수를 줄이면 훈련 세트에 있는 국지적인 패턴에 민감해진다.
- 이웃의 개수를 늘리면 데이터 전반에 있는 일반적인 패턴을 따른다.
- k-최근접 이웃 알고리즘에서는
실습
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)
라고 부른다.
- $a$는
- 단순히 훈련 세트를 저장하는 것이 훈련의 전부인 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 실행 결과
'프로그래밍 > AI' 카테고리의 다른 글
[혼공머신 11기] 6주차 스터디 및 과제 (0) | 2024.02.18 |
---|---|
[혼공머신 11기] 5주차 스터디 및 과제 (1) | 2024.02.03 |
[혼공머신 11기] 4주차 스터디 및 과제 (1) | 2024.01.28 |
[혼공머신 11기] 3주차 스터디 및 과제 (0) | 2024.01.20 |
[혼공머신 11기] 1주차 스터디 및 과제 (1) | 2024.01.07 |