이전 글에서 농어의 길이를 이용하여 무게를 예측하기 위해 다항 회귀를 사용했다. 하지만 훈련 세트의 점수보다 테스트 세트의 점수가 약간 높게 나왔다. 이 과소 적합 문제를 해결하기 위해서는 고차항이 필요하다. 하지만 얼마만큼의 고차항을 더 넣어야 할 지 모르고 수동으로 넣어주는 것도 한계가 있다. 추가로 농어의 높이와 두께 데이터가 주어졌다고 할 때 농어의 무게에 대한 더 정확한 예측을 해보자.
다중 회귀(Multiple Regression)
1개의 특성(농어의 무게)을 사용했을 때 선형 회귀 모델이 학습하는 것은 직선이다. 그렇다면 특성이 1개가 아닌 2개라면 어떤 것을 학습할까? 바로 평면이다. 특성이 2개면 타깃값과 함께 3차원 공간을 형성하고 y = a * x1 + b * x2 + c 형태의 방정식이 만들어진다.
더 복잡한 모델을 만들기 위해 농어의 길이뿐만 아니라 농어의 높이와 두께도 함께 사용하겠다. 그냥 무작정 넣는다기보다는 농어의 길이 * 농어의 높이 를 새로운 특성으로 만들어서 사용한다. 이렇게 기존 특성을 사용해서 새로운 특성을 뽑아내는 작업을 특성 공학(Feature Engineering)이라고 불린다.
데이터 준비
판다스(Pandas)는 데이터 분석 라이브러리이다. 데이터프레임(Dataframe)은 판다스의 핵심 데이터 구조인데, 넘파이와 비슷하게 다차원 배열을 다룰 수 있지만 훨씬 더 많은 기능을 제공한다. 또 데이터프레임을 넘파이 배열로 쉽게 바꿀 수도 있다.
판다스를 사용해 농어 데이터를 인터넷에서 내려받아 데이터프레임에 저장한 뒤 넘파이 배열로 변환하여 선형 회귀 모델을 훈련해보자. 판다스 데이터프레임을 만들기 위해 많이 사용하는 파일은 CSV파일인데, 이는 콤마로 나누어져 있는 텍스트 파일이다.
전체 파일 내용을 보고 싶다면 https://bit.ly/perch_csv_data 에 접속해보자. 파일을 읽으려면 판다스의 read_csv() 함수에 주소를 넣어주고, 넘파이 배열로 바꾸려면 to_numpy() 메서드를 사용하면 된다.
import pandas as pd
df = pd.read_csv('https://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
타깃 데이터는 이전과 같은 방식으로 준비한다. perch_full과 perch_weight를 훈련 세트와 테스트 세트로 나눈다.
import numpy as np
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_full, perch_weight, random_state = 42
)
사이킷런의 변환기
사이킷런은 특성을 만들거나 전처리하기 위한 다양한 클래스를 제공한다. 이런 클래스를 변환기(Transformer)라고 부른다. 사이킷런의 모델 클래스에 일관된 fit(), score(), predict() 메서드가 있는 것처럼 변환기 클래스는 모두 fit(), transform() 메서드를 제공한다. 간단한 예제로 PolynomialFeatures 클래스를 사용해보자.
*transform() 전에 fit()을 반드시 해야할까?
-> 훈련을 해야 변환이 가능하다. 그래서인지 두 메서드를 하나로 붙인 fit_transform() 메서드도 있다고한다.
우리가 사용했던 fit()메서드와 다른점은 타깃 데이터가 필요없다는 점이다. 입력 데이터만 전달해주면 된다.
결괏값을 보면, [2,3]이 [1. 2. 3. 4. 6. 9.]로 바뀌었다.
PolynomialFeatures 클래스는 각 특성을 제곱한 항을 추가하고 특성끼리 곱한 항을 추가한다. 2와 3을 제곱한 4와 9, 2와 3을 곱한 6이 추가되었다. 사이킷런의 선형 모델은 자동으로 절편을 추가해주기때문에 특성에 1을 넣을 필요가 없기때문에 include_bias = False로 설정하여 특성을 변환하면 1이 제거된다.
위에서 했던 방식으로 데이터를 변환하자.
poly = PolynomialFeatures(include_bias=False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
train_poly 배열의 크기를 보면 (42, 9)로 9개의 특성이 만들어졌다. get_feature_names() 메서드를 호출하면 어떤 조합으로 특성이 만들어졌는지 확인할 수 있다.
테스트 세트를 변환하자. 훈련 세트에 적용했던 변환기로 테스트 세트를 변환해야한다.
test_poly = poly.transform(test_input)
다중 회귀 모델 훈련
다중 회귀 모델을 훈련시키는 것은 여러 개의 특성을 사용하여 선형 회귀를 수행하는 것과 같기 때문에 LinearRegression 클래스를 import하여 훈련시키자.
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_poly, train_target)
점수를 확인해보자.
확실히 특성이 많아지니 점수도 매우 높게 나온다. 테스트 점수도 확인해보자.
테스트 세트에 대한 점수는 높아지지는 않앗지만 과소 적합 문제는 해결됐다.
만약 특성을 더 추가하면 어떻게 될까? degree 매개변수를 사용하여 5제곱항까지 만들어보자.
poly = PolynomialFeatures(degree=5, include_bias=False) #degree를 이용해서 특성을 3제곱, 4제곱 등 고차항의 최대 차수를 지정할 수 있다.
poly.fit(train_input)
train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)
특성의 개수가 무려 55개로 늘었다.
훈련세트와 테스트 세트의 점수를 확인해보자.
훈련 세트의 점수는 거의 완벽에 가깝지만 테스트 세트의 점수는 아주 큰 음수가 나온다. 왜 그럴까?
특성의 개수를 너무 많이 늘려서 훈련 세트에 지나치게 과대 적합된 결과라고 볼 수 있다. 다시 생각하면 과대 적합을 없애는 방법중 하나는 특성의 개수를 줄이는 것임을 알 수 있다.
규제 (Regularization)
규제는 머신러닝 모델이 과대적합되지 않도록 만들어준다. 선형 회귀 모델의 경우 특성에 곱해지는 계수(기울기)의 크기를 작게 만드는 일이다. 조심해야할 점은 특성의 스케일이 정규화되지 않으면 여기에 곱해지는 계수 값도 차이가 나게 된다는 것이다. 규제를 적용하기 전에 정규화를 해줘야한다. 저번 챕터에서는 np.mean()와 np.std()함수를 사용하여 평균과 표준편차를 구한 뒤, 데이터에서 평균을 뺀 값을 표준편차로 나눠서 정규화를 했다. 이번엔 사이킷런에서 제공하는 StandardScaler 클래스를 사용해보자.
훈련 세트를 훈련시키고 학습시킨 변환기를 사용해서 학습 세트와 훈련 세트를 변환하자.
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly) #꼭 훈련 세트로 학습한 변환기를 사용해 테스트 세트까지 변환해야한다.
train_scaled = ss.transform(train_poly) #표준점수로 변환한 값
test_scaled = ss.transform(test_poly)
* 훈련 세트에서 학습한 평균과 표준편차는 StandardScaler 클래스 객체의 mean_, scale_ 속성에 저장된다. 특성마다 계산하기때문에 55개의 평균과 표준편차가 들어있다.
선형 회귀 모델에 규제를 추가한 모델을 릿지(Ridge)와 라쏘(Lasso)라고 부른다. 릿지는 계수를 제곱한 값을 기준으로 규제를 적용하고, 라쏘는 계수의 절댓값을 기준으로 규제를 적용한다. 라쏘는 또한 규제를 할 때 계수를 0으로 만들 수도 있다. 일반적으로 릿지를 선호한다고 한다.
릿지 회귀
train_scaled 데이터로 모델을 훈련해보자.
from sklearn.linear_model import Ridge
ridge = Ridge()
ridge.fit(train_scaled, train_target)
선형 회귀에서 거의 완벽했던 점수가 약간 낮아졌다. 테스트 세트 점수를 확인해보자.
테스트 세트 점수가 55개의 특성을 사용했음에도 훈련 세트에 지나치게 과대적합되지 않고 정상적으로 돌아왔다.
릿지와 라쏘 모델을 사용할 때 alpha 매개변수로 규제의 양을 임의로 조절할 수 있다. alpha 값이 크면 규제 강도가 세지므로 계수 값을 더 줄이고 조금 더 과소 적합이 되도록 유도하고 작으면 반대로 선형 회귀 모델과 유사해져 과대적합될 가능성이 커진다.
* alpha값은 릿지 모델이 학습하는 값이 아니라 사전에 지정해야 하는 값이다. 이렇게 모델이 학습할 수 없고 사람이 알려줘야하는 파라미터를 하이퍼파라미터(Hyperparameter)라고 한다. 사이킷런과 같은 머신 러닝 라이브러리에서 클래스와 메서드의 매개변수로 표현된다.
적절한 alpha 값을 찾는 방법은 alpha 값에 따른 R² 값의 그래프를 그려 보는 것이다. 훈련 세트와 테스트 세트의 점수가 가장 가까운 지점이 최적의 alpha 값이 된다.
import matplotlib.pyplot as plt
train_score = []
test_score = []
alpha_list = [0.001, 0.01, 0.1, 1, 10, 100] #alpha 값을 바꾸면서 무엇이 적합한지 확인해보자
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))
plt.plot(np.log10(alpha_list),train_score)
plt.plot(np.log10(alpha_list),test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.show()
#alpha가 0.1일때 가장 적합하다.
alpha 값에 따른 훈련 세트와 테스트 세트의 R² 값을 보니 log(alpha)가 -1일 때 즉, alpha 값이 0.1일 때가 가장 적합한 것을 알 수 있다. 제일 왼쪽은 훈련 세트에만 너무 잘 맞는 과대 적합, 오른쪽은 훈련 세트와 테스트 세트 모두 점수가 낮아지는 과소 적합으로 가는 경향이 보인다.
ridge = Ridge(alpha = 0.1)
ridge.fit(train_scaled, train_target)
alpha를 0.1로 설정하고 점수를 확인해보면,
훈련 세트와 테스트 세트의 점수가 비슷하게 높고 과대적합과 과소적합사이에서 균형을 잘 이루고있는 것을 확인할 수 있다.
라쏘 회귀
라쏘도 릿지와 매우 비슷하다. Ridge 클래스를 Lasso 클래스로 바꾸는 것이 전부이다.
from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(train_scaled, train_target)
훈련 세트와 테스트 세트의 점수를 확인해보자.
릿지에서처럼 점수가 잘 나오는 것을 볼 수 있다. 최적의 alpha 값이 얼마인지 구해보자.
train_score = []
test_score = []
alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
for alpha in alpha_list:
lasso = Lasso(alpha=alpha, max_iter=10000)
lasso.fit(train_scaled, train_target)
train_score.append(lasso.score(train_scaled, train_target))
test_score.append(lasso.score(test_scaled, test_target))
plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R²')
plt.show()
최적의 alpha 값은 10이겠다. alpha에 10을 넣고 점수를 다시 계산해보자.
lasso = Lasso(alpha=10)
lasso.fit(train_scaled, train_target)
점수가 잘 나오는 것을 확인할 수 있다. 앞에서 라쏘 모델은 계수 값을 아예 0으로 만들 수 있다고 했는데, 계수가 0으로 만들어진 특성이 있나 확인해보자. 라쏘 모델의 계수는 coef_ 속성에 저장되어있다.
lasso.coef_가 0이면 1을 return하고 np.sum() 함수는 반환된 1을 세는 역할을 한다.
무려 40개나 계수가 0이 된 것을 확인할 수있다. 55개의 특성을 가지고 훈련을 시켰지만 정작 사용한 특성은 15개밖에 되지 않는다. 이런 특징 때문에 라쏘 모델을 유용한 특성만을 골라낼 때 사용한다.
'AI > 머신 러닝(ML)' 카테고리의 다른 글
결정 트리 (Decision Tree) (0) | 2021.08.16 |
---|---|
확률적 경사 하강법 (Stochastic Gradient Descent) (0) | 2021.08.14 |
로지스틱 회귀 (Logistic Regression) (0) | 2021.08.11 |
선형 회귀 (Linear Regression) (0) | 2021.08.09 |
K-최근접 이웃 회귀(K-Neighbors Regression) (0) | 2021.08.08 |