특성공학과 규제

Yoon1013·2023년 7월 14일
0
post-thumbnail

👥 다중 회귀(multiple regression)

이전 장에서는 농어의 길이를 이용하여 농어의 무게를 선형 회귀를 통해 예측했다. 이번 장에서는 농어의 길이 뿐만 아니라 농어의 높이와 두께 속성도 함께 이용하여 더 높은 성능의 농어의 무게 예측 모델을 만들고자 한다.
이처럼 여러개의 특성을 사용한 선형 회귀를 다중회귀(multiple regression)이라고 한다.
이전 장에서 1개의 특성을 사용했을 때 모델이 직선을 학습했던 것처럼 2개의 특성을 사용하면 평면을, 3개의 특성을 사용하면 공간을 학습한다.
여러개의 특성을 사용할 수록 더 높은 차원의 고차원 모델을 만들 수 있어 높은 성능을 기대할 수 있으나 그만큼 계산량이 많아지고 비용이 늘어나기 때문에 적절히 특성 개수를 조절해야 한다.

❓문제 정의

농어의 길이, 높이, 두께 데이터를 이용하여 다중회귀 모델로 농어의 무게를 예측하자.

🗃️ 데이터 준비

판다스(pandas) 라이브러리

판다스 라이브러리는 데이터분석에서 데이터 처리를 위해 주로 사용하는 라이브러리시리즈(Series), 데이터프레임(Data Frame) 등의 자료구조를 다룬다.
데이터프레임은 인덱스, 열, 값을 가지는 자료구조로 다차원 배열을 다룰 수 있다.
numpy 배열로 변환이 쉽고 csv 파일을 쉽게 읽고 쓸 수 있다.

# pandas 사용을 위해 pandas를 임포트 해야한다
import pandas as pd
df = pd.read_csv('https://bit.ly/perch_csv_data')
df.head(5)


head(n) 메서드를 써서 데이터가 잘 불러왔는지 확인해볼 수 있다.

perch_full = df.to_numpy()
print(perch_full)


to_numpy() 메서드를 이용하면 넘파이 배열로 바꿀 수 있다.
넘파이 배열은 데이터프레임과 다르게 열이름과 인덱스 출력이 없다!

타깃 데이터는 농어의 무게이므로 넘파이 배열로 만든다.

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


numpy 배열의 길이를 확인할 때엔 len(배열) 함수 복습!

훈련 세트와 테스트 세트를 나눠준다.

# 테스트세트와 훈련 세트 나누기
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)
print(train_input[:5])


훈련세트에 농어의 길이 뿐만 아니라 높이, 두께까지 3 속성이 모두 들어 있는 것을 확인할 수 있다.

🔄 특성공학과 사이킷런의 변환기

특성 공학(feature engineering)

이전 장에서 농어의 길이의 제곱항을 추가했던 것처럼 특성들끼리의 곱한 항을 추가하여 새로운 항을 만드려고 한다.
이처럼 기존 특성을 활용하여 새로운 특성을 만들어내는 작업을 특성 공학이라고 한다.

사이킷런의 변환기(transformer)

이전 장에서처럼 직접 계산한 항을 만들어 연결할 수도 있지만 사이킷런은 이런 일을 쉽게 할 수 있도록 다양한 클래스를 제공한다.
변환기는 특성 공학 뿐만 아니라 정규화 등 전처리를 위한 사이킷런 클래스를 의미한다.
사이킷런의 모델이 공통적으로 fit(), score(), predict() 메서드를 쓰는 것처럼 사이킷런의 변환기 또한 공통적으로 같은 메서드를 사용한다.
사이킷 런 패키지 안의 클래스를 사용할 때 input 데이터는 이차원 배열 형태여야함을 잊지 말자!

  • fit(): 변환할 기준을 찾는 메서드
  • transform(): fit()을 통해 찾은 기준으로 실제로 데이터를 변환시키는 메서드

변환기를 사용하여 특성 수를 늘려보자.

from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures()
poly.fit([[2,3]])
print(poly.transform([[2,3]]))


예상한 출력은 [2. 3. 4. 6. 9] 인데 1이 추가되었다?!

우리는 우리가 사용하려는 특성을 (농어 길이, 농어 높이, 종어 두께) 총 3개라고 생각하여 아래와 같은 식을 만드려고 했을 것이다. (특성 공학으로 항을 추가하지 않는다면 말이다.)

그러나 사이킷 런이 생각하는 특성은 다음과 같다.

위의 식으로 보면 특성은 (농어 길이, 농어 높이, 농어 두께, 1)이 된다.
따라서 위의 출력에서도 특성에 1이 추가된 것이다.
우리는 이렇게 1이라는 특성이 필요 없으므로 include_bias=False로 지정하여 해당 특성을 제거해 주도록 하겠다.

poly = PolynomialFeatures(include_bias=False)
poly.fit([[2,3]])
print(poly.transform([[2,3]]))


출력이 예상과 같아졌다.

어떻게 변환기를 사용하는지 알았으니 이제 변환기를 사용하여 특성 수를 늘려주기로 하자.

#이미 만든 poly 객체는 [[2,3]]에 대한 조합을 학습하고 있으므로 객체를 다시 만들어준다!
poly = PolynomialFeatures(include_bias=False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
print(train_poly[:5])


새로운 항이 잘 추가된 것을 볼 수 있다.

print(train_poly.shape)


이제 input 데이터는 9개의 특성을 가지고 있는 42개 샘플이 되었다.

각각의 특성이 어떤 조합으로 만들어진 특성인지 확인하는 방법: get_feature_names_out()

poly.get_feature_names_out()


train_input 데이터의 열이 (길이, 높이, 두께) 순이었으므로 x0: 길이, x1: 높이, x2: 두께이다.

이제 같은 객체를 사용하여 테스트 셋도 변환해준다.
테스트셋에 대하여 새로운 객체를 만들어줘도 조합에는 변화가 없겠지만 훈련 세트를 기준으로 테스트 셋 또한 변환하는 것이 논리에 맞으므로 훈련 세트로 학습된 객체로 테스트 셋을 변환하는 것이 좋다!

test_poly = poly.transform(test_input)
print(test_poly[:5])


테스트 셋 또한 잘 변환되었다.

🤖 다중회귀 모델 훈련

위에서 설명했듯 다중 회귀는 여러개의 특성을 사용한 선형회귀이므로 앞장과 같은 LinearRegression 객체를 사용하면 된다.

from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))


훈련 세트에 대한 모델의 정확도가 앞 장의 모델보다 올라간 것을 확인할 수 있다.

print(lr.score(test_poly, test_target))


앞장의 과소적합 문제 또한 해결된 것을 알 수 있다.

특성 수를 계속 늘리면 계속 모델의 성능이 좋아질까?

특성의 개수가 많아졌더니 모델의 성능 또한 좋아졌다.
그렇다면 특성을 계속해서 늘릴 수록 더 좋은 모델을 만들 수 있는 것 아닐까?
degree 매개변수를 사용하면 고차항의 최대 차수를 지정할 수 있다.

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)


특성의 개수가 55개가 되었다!

poly.get_feature_names_out()


degree에서 지정한대로 최대 5제곱 항까지 있는 것을 확인할 수 있다.

위 데이터를 가지고 다시 모델 훈련을 진행해보자.

lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))


엄청난 과대적합이 일어난 것을 볼 수 있다...!
훈련 세트의 샘플이 42개인데 학습할 수 있는 특성을 55개를 주었으므로 훈련 세트에 대해 거의 완벽하게 학습을 할 수 있다.
그러나 우리의 목표는 훈련 세트에 대한 완벽한 학습이 아닌 일반적인 패턴을 학습시키는 일이다.

🚫 규제(regularization)

이전 포스팅에서 과대적합에 대해 설명하면서 모델의 복잡도가 큰 경우 규제를 통해 과대적합을 해결할 수 있다고 설명한 바 있다.
선형 회귀의 경우 계수(coefficient)를 작게 만들어 모델이 보다 보편적인 패턴을 학습하도록 만들어주는 것을 규제라고 한다.

정규화

규제를 적용할 때에도 공정하게 규제가 되게 하기 위해 정규화를 해주어야 한다!
정규화를 할 때에는 훈련 세트와 같은 기준으로 테스트 세트를 정규화 해야한다는 것을 잊으면 안된다.

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly)
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)
print(train_scaled[0])
print(test_scaled[0])


정규화가 되었는지 확인해본다.

🤖 규제 모델 사용하기

선형 회귀에서 규제는 계수의 크기를 줄이는 것이라고 설명했는데 계수 비교를 위해 앞서 만든 모델의 계수를 확인해보자.

lr = LinearRegression()
lr.fit(train_scaled, train_target)
print(lr.coef_)


계수가 매우매우 큰 것을 알 수 있다!

릿지(Ridge) 회귀

릿지 회귀는 계수를 제곱한 값을 기준으로 규제를 적용하는 모델이다.
일반적으로 뒤에 설명할 라쏘 모델 보다 선호되는 방식으로 사용 방법은 LinearRegression과 같다.

from sklearn.linear_model import Ridge

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


훈련 세트에 대한 점수가 조금 낮아진 것을 확인할 수 있다.

print(ridge.score(test_scaled, test_target))


테스트 세트 점수 또한 음수에서 돌아온 것을 확인할 수 있으며 과대적합되지 않은 것을 확인할 수 있다.

print(ridge.coef_)


계수의 크기가 훨씬 작아진 것을 확인할 수 있다

규제의 강도를 조절할 때에는 alpha 매개변수를 사용하여 조절한다.
alpha 값이 커지면 규제의 강도가 세지므로 계수 값이 더 작아진다.
alpha값은 우리가 직접 조정해야 하는 하이퍼파라미터로 적당한 alpha 값을 찾아야 한다.
alpha 값에 따라 R² 값의 변화를 그래프로 그려 최적의 R² 값을 찾아보자.

import matplotlib.pyplot as plt

# alpha 값의 변화에 따라 점수를 저장할 빈 리스트 만들기
train_score = []
test_score = []

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))
print(train_score, test_score)


alpha 값을 변화시키면서 훈련 세트와 테스트 세트에 대한 R²값을 배열에 저장했다.

plt.plot(np.log10(alpha_list), train_score) #x축 간격을 비슷하게 만들어 주기 위해 로그 스케일로 그린다
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.show()


그래프를 보면 alpha 값이 0.01 부터는 테스트 세트의 점수가 급격하게 떨어지는 것을 볼 수 있으므로 과대적합 경향을 보인다.
반대로 alpha값이 10 이상이 되면 훈련세트의 점수도 떨어지는 과소적합 경향을 보인다.
따라서 가장 적절한 alpha 값은 테스트 세트의 점수가 가장 높고 훈련세트와의 점수 차이가 가장 작은 0.1이 적당하다고 볼 수 있다.

최적의 alpha 값을 찾았으니 다시 모델을 훈련하자.

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))


훈련 세트와 테스트 세트가 비슷하게 높고 좋은 성능을 보여주고 있다.

라쏘(Lasso) 회귀

릿지 모델이 계수의 제곱을 기준으로 규제를 적용한다면 라쏘 모델은 계수의 절댓값을 기준으로 규제를 적용한다.
사용 방법은 릿지 모델과 같다.

from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))


릿지 모델과 마찬가지로 훈련 세트의 점수가 약간 내려간 것을 확인할 수 있다.

print(lasso.score(test_scaled, test_target))


테스트 세트의 점수도 높아 과대적합 또한 해결한 것을 확인할 수 있다.

라쏘 모델 역시 alpha 매개변수를 이용해 규제 강도를 조절하며 적당한 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))

max_iter는 최대 반복 횟수를 지정하는 파라미터이다.

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 값이 작아지면 테스트 세트의 점수가 계속 감소하여 과대적합 경향을 보이고 있다는 것을 알 수 있다.
반대로 alpha 값이 100이 되면 훈련 세트 점수와 테스트 세트 점수가 동시에 급감하여 과소 적합 경향이 보인다.
따라서 가장 적절한 alpha 값은 훈련 세트 점수와 테스트 세트 점수의 차이가 작고 점수가 높은 alpha 값이 10일 때이다.

최적의 alpha 값을 찾았으니 다시 모델을 만들어보자.

lasso = Lasso(alpha=10)
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
print(lasso.score(test_scaled, test_target))


라쏘 모델 역시 과대적합을 잘 억제하는 것을 확인할 수 있다.

print(np.sum(lasso.coef_ == 0))


위의 코드를 통해 라쏘 모델이 예측에 사용하지 않은 특성의 개수를 파악할 수 있다.
라쏘 모델은 55개 중 15개 특성만 사용했다.
이를 통해 라쏘 모델은 유용한 특성을 골라내는 용도로 사용할 수 있다는 것을 알 수 있다.

📚 Reference

혼자 공부하는 머신러닝+딥러닝, 박해선, 한빛미디어

profile
Data Science & AI

0개의 댓글