혼자 공부하는 머신러닝 + 딥러닝

[혼공머신] 6. Feature Engineering & Regularization

Seon_ 2021. 12. 17. 17:27
6.fish_regression3

데이터 준비

In [1]:
import pandas as pd 
df = pd.read_csv('https://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
print(perch_full)
[[ 8.4   2.11  1.41]
 [13.7   3.53  2.  ]
 [15.    3.82  2.43]
 [16.2   4.59  2.63]
 [17.4   4.59  2.94]
 [18.    5.22  3.32]
 [18.7   5.2   3.12]
 [19.    5.64  3.05]
 [19.6   5.14  3.04]
 [20.    5.08  2.77]
 [21.    5.69  3.56]
 [21.    5.92  3.31]
 [21.    5.69  3.67]
 [21.3   6.38  3.53]
 [22.    6.11  3.41]
 [22.    5.64  3.52]
 [22.    6.11  3.52]
 [22.    5.88  3.52]
 [22.    5.52  4.  ]
 [22.5   5.86  3.62]
 [22.5   6.79  3.62]
 [22.7   5.95  3.63]
 [23.    5.22  3.63]
 [23.5   6.28  3.72]
 [24.    7.29  3.72]
 [24.    6.38  3.82]
 [24.6   6.73  4.17]
 [25.    6.44  3.68]
 [25.6   6.56  4.24]
 [26.5   7.17  4.14]
 [27.3   8.32  5.14]
 [27.5   7.17  4.34]
 [27.5   7.05  4.34]
 [27.5   7.28  4.57]
 [28.    7.82  4.2 ]
 [28.7   7.59  4.64]
 [30.    7.62  4.77]
 [32.8  10.03  6.02]
 [34.5  10.26  6.39]
 [35.   11.49  7.8 ]
 [36.5  10.88  6.86]
 [36.   10.61  6.74]
 [37.   10.84  6.26]
 [37.   10.57  6.37]
 [39.   11.14  7.49]
 [39.   11.14  6.  ]
 [39.   12.43  7.35]
 [40.   11.93  7.11]
 [40.   11.73  7.22]
 [40.   12.38  7.46]
 [40.   11.14  6.63]
 [42.   12.8   6.87]
 [43.   11.93  7.28]
 [43.   12.51  7.42]
 [43.5  12.6   8.14]
 [44.   12.49  7.6 ]]
In [2]:
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])
In [3]:
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)

사이킷런의 변환기

변환기란 특성을 만들거나 전처리하기 위한 클래스를 일컫는 말이다.

In [4]:
from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures()
poly.fit([[2, 3]])
print(poly.transform([[2,3]]))
[[1. 2. 3. 4. 6. 9.]]
  • 2개의 특성을 가진 데이터가 6개의 특성을 가진 샘플로 바뀌었음.
  • PolynomialFeatures 클래스는 기본적으로 각 특성을 제곱한 항을 추가하고 특성끼리 서로 곱한 항을 추가함.
    • 1이 추가된 이유는, 상수항(y절편)에 곱해지는 고정 계수라고 생각하면 됨.
    • 그러나 사이킷런의 선형 모델은 자동으로 절편을 추가하므로 굳이 1을 포함할 필요가 없음.
    • include_bias = False로 지정하여 다시 특성을 변환해보자.
In [5]:
poly = PolynomialFeatures(include_bias=False)
poly.fit([[2, 3]])
print(poly.transform([[2,3]]))
[[2. 3. 4. 6. 9.]]
  • 잘 변환되었다.
    • 사실 include_bias=False로 지정하지 않아도 사이킷런 모델은 자동으로 특성에 추가된 절편 항을 무시한다.
    • (아니 그러면 왜 시켰냐고요)
In [6]:
poly = PolynomialFeatures(include_bias=False)

poly.fit(train_input)
train_poly = poly.transform(train_input)
In [7]:
print(train_poly.shape)
(42, 9)
In [8]:
poly.get_feature_names()
Out[8]:
['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2', 'x2^2']
  • get_feature_names() 메서드를 호출하면 9개의 특성이 각각 어떤 입력의 조합으로 만들어졌는지 알려준다.
In [9]:
# 테스트 세트도 같은 방식으로 변환한다.
test_poly = poly.transform(test_input)

다중 회귀 모델 학습시키기

In [10]:
from sklearn.linear_model import LinearRegression

lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
0.9903183436982124
  • 두께 데이터 및 특성 공학을 활용한 새로운 데이터들을 활용한 결과 스코어가 높아졌다.
In [11]:
print(lr.score(test_poly, test_target))
0.9714559911594155
  • 훈련 세트의 스코어가 테스트 세트의 스코어보다 높으므로 과소적합 문제는 해결했다.
    • 만약 더 많은 특성을 추가한다면?
    • degree 매개변수를 이용하면 최대차수를 지정할 수 있다.
In [12]:
poly = PolynomialFeatures(degree=5, include_bias=False)

poly.fit(train_input)
train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)
In [13]:
print(train_poly.shape)
(42, 55)

특성의 개수가 55개로 늘어났다.

In [14]:
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
0.9999999999938144
In [15]:
print(lr.score(test_poly, test_target))
-144.40744532797535
  • 특성의 개수를 늘리자 훈련 세트에 아주 가까운 모델이 학습됬다.
    • 그러나 테스트 세트에서는 아주 큰 음수 값이 나온다.
    • 당연하게도, 과대적합됬기 때문이다.
    • 훈련 세트의 샘플 개수가 42개이므로 55개의 특성을 이용하면 완벽히 학습할 수 있는 것이 당연하다.
    • 규제(regulazation)과 정규화를 추가하여 overfitting을 막아보자.

규제

In [16]:
from sklearn.preprocessing import StandardScaler #정규화의 일종

# 정규화를 실시하고 학습시킨다.
ss = StandardScaler()
ss.fit(train_poly)

# fit이 되었으므로 transform을 실시하여 feature의 수를 늘린다.
# 꼭 훈련 세트로 학습한 변환기를 사용해 테스트 세트까지 변환해야 한다.
train_scaled = ss.transform(train_poly) 
test_scaled = ss.transform(test_poly)

릿지 회귀

릿지 회귀란 계수를 제곱한 값을 기준으로 선형 회귀 모델에 규제를 적용하는 것을 말한다.

In [17]:
from sklearn.linear_model import Ridge

ridge = Ridge()
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))
0.9896101671037343

선형 회귀에서 완벽에 가까웠던 스코어가 조금은 낮아졌다.

In [18]:
print(ridge.score(test_scaled, test_target))
0.9790693977615386
  • 테스트 세트 점수가 아주 큰 음수에서 정상으로 돌아왔다.
    • 많은 특성을 사용했음에도 불구하고 훈련 세트에 너무 과대적합되지 않아 테스트 세트에서도 좋은 성능을 보인다.
  • 릿지 모델에서 규제의 양을 조절할 수 있다.
    • alpha 매개변수를 사용하면 되는데 값이 크면 규제도 크다.
    • alpha값과 같이 모델이 학습하는 계수가 아니라 사용자가 직접 정해줘야 하는 계수를 하이퍼파라미터라고 한다.
    • 적절한 alpha 값을 찾는 방법은 alpha값에 대한 R2 값 그래프를 그려보는 것이다.
    • 이 때 훈련 세트와 테스트 세트의 점수가 가장 가까운 지점이 최적의 alpha값이 된다.
In [19]:
import matplotlib.pyplot as plt

train_score = []
test_score = []
In [20]:
alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
for alpha in alpha_list:
    # 릿지 모델 생성
    ridge = Ridge(alpha=alpha)

    # fitting
    ridge.fit(train_scaled, train_target)

    # 훈련 점수와 테스트 점수를 저장
    train_score.append(ridge.score(train_scaled, train_target))
    test_score.append(ridge.score(test_scaled, test_target))

alpha 값을 0.001부터 10배씩 늘렸기 때문에 로그 함수로 바꾸어 지수로 표현한다.

In [21]:
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()
  • 파란색 선이 훈련 세트 그래프, 주황색 선이 테스트 세트 그래프이다.
    • 왼쪽은 훈련 세트에는 잘맞고, 테스트 세트에는 잘 맞지 않는 overfitting의 양상을 띤다.
    • 오른쪽은 훈련 세트와 테스트 세트의 점수가 모두 낮아지는 과소적합의 모습을 보인다.
    • 적절한 alpha 값은 두 그래프가 가장 가깝고 점수가 가장 높은 -1, 즉 10^-1 = 0.1이다.
    • 따라서 alpha=0.1로 지정하여 최종 모델을 훈련시킬 것이다.
In [22]:
ridge = Ridge(alpha=0.1)
ridge.fit(train_scaled, train_target)

print(ridge.score(train_scaled, train_target))
print(ridge.score(test_scaled, test_target))
0.9903815817570368
0.9827976465386896

훈련 세트와 테스트 세트의 점수가 비슷하게 모두 높고 과대적합과 과소적합 사이에서 균형을 맞추고 있다.

라쏘 회귀

  • 라쏘 회귀는 계수의 절댓값을 기준으로 규제를 적용한다.
    • 주의해야 할 것은 라쏘의 경우 계수를 0으로 만들어버릴 수도 있다.
In [23]:
from sklearn.linear_model import Lasso

lasso = Lasso()
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
0.9897898972080961

라쏘 역시 과대적합을 잘 억제했다.

In [24]:
print(lasso.score(test_scaled, test_target))
0.9800593698421884
  • 라쏘의 테스트 세트의 점수 역시 릿지만큼 좋다.
    • 라쏘 역시 alpha 값을 조절할 수 있다.
In [25]:
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))
C:\Users\BouncyBear\Anaconda3\lib\site-packages\sklearn\linear_model\_coordinate_descent.py:476: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 18778.697957792032, tolerance: 518.2793833333334
  positive)
C:\Users\BouncyBear\Anaconda3\lib\site-packages\sklearn\linear_model\_coordinate_descent.py:476: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 12972.821345401393, tolerance: 518.2793833333334
  positive)
In [26]:
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값은 1, 즉 10^1=10이다.
In [27]:
lasso = Lasso(alpha=10)
lasso.fit(train_scaled, train_target)

print(lasso.score(train_scaled, train_target))
print(lasso.score(test_scaled, test_target))
0.9888067471131867
0.9824470598706695
In [1]:
from IPython.core.display import display, HTML

display(HTML("<style>.container { width:90% !important; }</style>"))