6. 특성 공학과 규제

1. 들어가며

  • 이전에 다항회귀로 농어의 무게를 어느정도 예측이 가능했지만, 여전히 훈련 세트보다 테스트 세트의 점수가 더 높은 문제가 있었다.
  • 선형회귀는 특성이 많을수록 효과가 좋다. 기존에는 1개의 특성을 사용하는 직선형 모델이였다. 2개의 특성을 사용하게되면 평면을 학습한다. 이처럼 고차원에서의 선형회귀는 매우 복잡한 모델을 표현할 수 있다.
  • 기존의 특성을 사용해 새로운 특성을 뽑아내는 작업을 특성공학이라고 부릅니다.
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
import matplotlib.pyplot as plt
root = 'https://bit.ly/perch_csv_data'
df = pd.read_csv(root)
perch_full = df.to_numpy()
print(perch_full[:5])
[[ 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]]
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)
  • 사이킷런은 특성을 만들거나 전처리하기 위한 다양한 클래스를 제공한다. 이런 클래스를 변환기라고 부른다. 사이킷런의 모델 클래스에 일관된 fit(), score(), predict()메서드가 있는 것처럼 변환기 클래스는 fit(), transform()메서드를 제공한다.

2. 특성공학

poly = PolynomialFeatures(include_bias = False)
poly.fit([[2, 3]])
print(poly.transform([[2,3]]))
[[2. 3. 4. 6. 9.]]
  • 특성이 아주 많이 만들어졌다. PolynomialFeatures 클래스는 기본적으로 각 특성을 제곱한 항을 추가하고 특성끼리 서로 곱한 항을 추가한다. 이후 include_bias = False를 추가해 1을 반환해준다.
  • 이를통해 절편을 위한 항이 제거되고, 특성의 제곱과 특성끼리 곱한 항만 추가되었다.
poly.fit(train_input)
train_poly = poly.transform(train_input)
print(train_poly.shape)
(42, 9)
  • 9개의 특성은 가지는 훈련셋을 만들었다. 해당 특성이 어떻게 만들어 졌는지 알아보고 싶을 경우 get_feature_names() 메서드를 호출하면 9개의 특성이 어떻게 만들어졌는지 알 수 있다.
poly.get_feature_names()
['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2', 'x2^2']
  • ‘x0’는 첫번째 특성을 의미하고 ‘x0^2’는 첫 번째 특성의 제곱, ‘x0 x1’은 첫 번째 특성과 두 번째 특성의 곱을 나타내는 형식으로 만들어졌다.
  • 밑에서 테스트 세트도 변환해 준다.
test_poly = poly.transform(test_input)
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
0.9903183436982124
0.9714559911594134
  • 특성이 늘어나면서 점수가 아주 높아졌다. 농어의 길이 뿐 아니라 높이와 두께 모두 사용했고, 각 특성들을 제곱하거나 곱해서 특성을 추가했다.
  • 이로써 과소적합의 문제를 해결했다.
  • 이를 통해 특성이 늘어나면 선형 회귀 능력은 매우 강하다는 것을 알 수 있다.
  • 만약 특성을 더 많이 추가하면 어떻게 될까? 최대 차수를 5제곱까지 특성을 만들어 출력해보자.
poly = PolynomialFeatures(degree=5, include_bias = False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)
print(train_poly.shape)
(42, 55)
  • 이를 통해 만들어진 특성이 무려 55개 나 된다. 이를 이용해 다시 선형회귀를 훈련시켜보도록 한다.
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
0.9999999999991097
-144.40579242684848
  • 결과를 살펴보면 훈련세트에 대한 점수는 아주 좋다. 하지만 반대로 테스트세트의 점수는 아주 큰 음수가 나온다.
  • 이를 통해 알 수 있는것들은 다음과 같다.
    1. 특성의 개수를 늘리면 선형 모델은 아주 강력해 진다.
    2. 훈련세트에 대해 거의 완벽하게 학습할 수 있다.
    3. 하지만 과대적합의 문제가 발생해 테스트 세트에서는 점수가 잘 나오지 않는다.
  • 문제를 해결하기 위해서는 특성을 다시 줄이는 방법이 있다. 하지만 이외에도 또 다른 방법이 존재한다.

3. 규제

  • 규제는 머신러닝 모델이 훈련 세트를 너무 과도하게 학습하지 못하도록 훼방하는 것을 말한다.
  • 선형 회귀 모델에서는 특성에 곱해지는 계수(or 기울기)의 크기를 작게 만드는 것을 말한다.
  • 앞서 55개의 특성으로 훈련한 선형 회귀 모델의 계수를 규제하여 훈련 세트의 점수를 낮추고 대신 테스트 세트의 점수를 높여보도록 하겠다.
  • 여기서 일단 스케일을 고려해 보아야 한다. 스케일이 정규화 되지 않으면 곱해지는 계수 값도 차이가 나기 때문이다. 계수 값의 크기가 서로 다르면 공정하게 제어되기 힘들기 때문이다.
ss = StandardScaler()
ss.fit(train_poly)
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)
  • StandardScaler 클래스의 객체 ss를 초기화한 후 PolynomialFeatures 클래스로 만든 train_poly를 사용해 훈련한다.(테스트 세트도 같이 해줘야 한다!!)
  • 선형 회귀 모델에 규제를 추가한 모델을 릿지, 라쏘라고 부른다.
  • 릿지는 계수를 제곱한 값을 기준으로 규제를 적용한다.
  • 라쏘는 계수의 절댓값을 기준으로 규제를 적용한다.
  • 일반적으로는 릿지를 더 선호하는 경향이 있다. 두 알고리즘 모두 계수의 크기를 줄이지만 라쏘는 아예 0을 만들 수도 있다.

1. 릿지 회귀

ridge = Ridge()
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))
print(ridge.score(test_scaled, test_target))
0.9896101671037343
0.9790693977615386
  • 선형 회귀에서 거의 완벽에 가까워던 점수가 조금 낮아졌다. 테스트 세트 점수도 정상으로 돌아왔다.
  • 무조건 많은 특성을 사용한다 해서 좋은 성능을 내는 것은 아니다.
  • 릿지와 라쏘 모델을 사용할 때 규제의 양을 임의로 조절할 수 있다. 모델 객체를 만들 때 alpha 매개변수로 규제의 강도를 조절한다. alpha값이 크면 규제 강도가 세져 계수 값을 줄이고, 과소적합되도록 유도한다. 반대로 값이 작으면 개수를 줄이는 역할이 줄고, 선형회귀 모델과 유사해지므로 과대적합될 가능성이 크다.
  • 적절할 alpha 값을 찾는 한 가지 방법은 alpha 값에 대한 결정계수값의 그래프를 그려보는 것이다.
  • 훈련세트와 테스트세트의 점수가 가장 가까운 지점이 최적의 alpha 값이 된다.
train_score = []
test_score = []
  • alpha값을 0.001에서 100까지 10배씩 늘려가며 릿지 회귀 모델을 훈련한 다음 훈련 세트와 테스트 세트의 점수를 파이썬 리스트에 저장한다.
alpha_list = [0.001, 0.01, 0.1, 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))
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()

output_33_0

  • 위는 훈련세트 그래프이며, 아래는 테스트세트 그래프이다. 가장 가깝고 테스트 세트 점수가 가장 높은 -1 즉 0.1이다.
  • alpha 값을 0.1로 해서 최종 모델을 훈련하자.
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.9903815817570366
0.9827976465386922
  • 이 모델은 훈련세트와 테스트세트의 점수가 비슷하게 모두 높고 과대적합과 과소적합 사이에서 균형을 맞추고 있다.

2. 라쏘 회귀

lasso = Lasso()
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
print(lasso.score(test_scaled, test_target))
0.989789897208096
0.9800593698421883
  • 릿지만큼 좋은 점수를 보여준다. 라쏘 모델도 매개변수를 이용해 규제의 강도를 조절할 수 있다.
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))
/Users/janghyeseong/Desktop/PR/PR2/lib/python3.9/site-packages/sklearn/linear_model/_coordinate_descent.py:530: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 18778.697957792876, tolerance: 518.2793833333334
  model = cd_fast.enet_coordinate_descent(
/Users/janghyeseong/Desktop/PR/PR2/lib/python3.9/site-packages/sklearn/linear_model/_coordinate_descent.py:530: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 12972.821345404844, tolerance: 518.2793833333334
  model = cd_fast.enet_coordinate_descent(
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()


output_41_0

  • 라쏘에서는 10이 가장 최적의 알파값이다.
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
  • 모델 훈련이 아주 적절하다. 특성을 많이 사용했지만 릿지와 마찬 가지로 라쏘 모델이 과대적합을 잘 억제하고 테스트 세트의 성능을 높여주었다.

4. 마치며

  • 이번 시간에는 특성공학을 통해 새로운 특성을 만들고 이를 통해 보다 많은 특성을 고려해 예측 정도를 올리는 법을 배웠다.
  • 이 과정에서 과대 과소 적합문제가 발생하고 이를 해결하기 위해 규제를 사용해 적절하게 특성을 조정하는 방법을 배웠다.
  • 이런 규제는 릿지회귀와 라쏘회귀가 존재하고, 각각의 방법을 알아보았다.
  • 어려워서 한번 더 읽어봐야겠다.

Categories:

Updated:

Leave a comment