STUDY

[AI] 특성공학과 규제

Dalseung 2022. 11. 6. 14:27

개요

이전에서는 하나의 특성을 사용하여 선형 회귀 모델을 훈련시켰다. 1개의 특성을 사용했을 때 선형회귀모델이 학습하는 것은 직선이다. 2개의 특성을 사용하면 평면을 학습한다.

여러 개의 특성을 사용한 선형회귀를 다중회귀라 부른다. 특성이 많으면 많을 수록 복잡한 모델이 된다. 그러나 3차원 공간을 뛰어넘는 3개의 특성을 가진 상태, 그 이상을 그릴 수 없다. 그래서 있는 특성을 이용해서 새로운 특성을 만드는데,

이렇게 기존의 특성을 이용해서 새로운 특성을 뽑아내는 작업을 특성공학이라고 한다.

특성공학

데이터준비

이전과 달리 농어의 특성이 길이, 높이, 두께 3개로 늘어났다.

pandas를 이용해서 농어 데이터를 인터넷에서 내려받아 데이터프레임에 저장하겠다.

그 이후에 넘파이배열로 전환시켜주자

pandas를 이용하여 read_csv()함수로 데이터프레임을 만든 다음 to_numpy() 메서드를 사용하여 넘파이 배열로 바꾼다.

import pandas as pd
df = pd.read_csv('perch_full.csv') # 농어의 길이 높이 무께까지 넣음
perch_full = df.to_numpy()
print(perch_full)
# 길이   높이   두께
[[ 8.4   2.11  1.41]
      .
      .
      .
[44.   12.49  7.6 ]]
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]
     )
# train_input, test_input, train_target, test_target 나누기
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)

사이킷런의 변환기

from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures()
poly.fit([[2, 3]])
print(poly.transform([[2,3]])) # [[1. 2. 3. 4. 6. 9.]]

사이킷런에서는 특성을 만들거나 전처리하기 위한 다양한 클래스를 제공하는데, 이러한 클래스를 변환기라고 부른다. 우리가 사용할 변환기는 PolynomialFeatures 클래스이다.

변환기는 입력데이터를 변환하는데 타깃데이터가 필요하지 않다.

fit() 메서드는 새롭게 만들 특성조합을 찾고 transform()메서드는 실제로 데이터를 변환한다.

fit()메서드 호출 시 타깃데이터는 필요 없다.

따라서 모델 클래스와는 다르게 fit()메서드에 입력데이터만 전달했다.
여기서 2개의 특성(원소)를 가진 샘플[2,3]이 6개의 특성을 가진 샘플[1,2,3,4,6,9]로 바뀌었다.

1은 왜 나왔을까?

기존 샘플 2와 3을 제곱한 4와 9를 추가, 2와 3을 곱한 6 추가, 1은 무게 = a길이 + b높이 + c두께 + d1으로 선형방정식의 절편을 항상 값이 1인 특성과 곱해지는 계수이므로 현재 특성은 (길이,높이,두께,1)이라고 할 수 있다. 이런 기능을 없애려면 include_bias=False을 해주면 된다.

poly = PolynomialFeatures(include_bias=False) #사이킷런의 선형모델은 자동으로 절편을 추가하므로 굳이 이렇게 특성을 만들필요 없다.
poly.fit(train_input)
train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)
print(train_poly.shape) # (42, 9)
print(poly.get_feature_names_out()) # 0: 첫번째 데이터, 
(42, 9)

['x0' 'x1' 'x2' 'x0^2' 'x0 x1' 'x0 x2' 'x1^2' 'x1 x2' 'x2^2']
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target)) # 0.9903183436982126

매우 높은 점수가 나왔다.

농어의 길이 뿐 아니라 높이와 두께를 모두 사용했고 각 특성을 제곱하거나 서로 곱해서 다항 특성을 더욱 추가 했다.

특성이 늘어나면 선형 회귀의 능력은 매우 강하다는 것을 알 수 있다.

따라서 과소적합문제가 해결되었다.

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)

lr.fit(train_poly,train_target)
print(lr.score(train_poly,train_target)) # 0.9999999999997232
print(lr.score(test_poly,test_target)) # -144.40564483377855

특성의 개수를 크게 늘렸기 때문에 훈련세트에 대해 거의 완벽하게 학습하여 테스트세트에는 형편없는 점수를 만든다.

따라서 과대적합이 일어났다.

이를 해결할 수 있는 것이 규제이다.

규제

규제

머신러닝 모델이 훈련세트를 너무 과도하게 학습하지 못하도록 훼방하는 것을 말한다.

즉, 모델이 과대적합이 되지 않도록 만드는 것이다.

선형회귀모델에서는 상수함수가 될 수록 평균에 가까워지는 것이고 이는 기울기가 작아진다. 따라서 기울기를 작게 하거나 특성에 곱해지는 계수의 크기를 작게 만드는 것이 규제이다.

실습

규제를 하기 전 정규화를 실시한다 전 처럼 평균에서 뺀 값을 표준편차로 나눠도 되지만 여기서는 편하게 StandardScaler클래스를 사용한다.

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

PolynomialFeatures 클래스로 만든 train_poly를 사용해 객체를 훈련하고 꼭 훈련세트로 학습한 변환기를 사용해 테스트 세트까지 변환해야한다.
이제 표준점수로 변환한 train_scaled와 test_scaled가 준비되었다.

선형회귀모델에 규제를 추가한 모델을 릿지(ridge)와 라쏘(lasso)이라 부른다. ridge는 선형모델의 계수를 작게 만들어 과대적합을 완화시킨다. 또한 계수를 제곱한 값을 기준으로 규제를 적용함 lasso는 계수값을 아예 0으로 만들 수 있다. 계수의 절댓값을 기준으로 규제를 적용한다.

Ridge 회귀

from sklearn.linear_model import Ridge
ridge = Ridge()
ridge.fit(train_scaled,train_target)
print(ridge.score(train_scaled, train_target)) # 0.9896101671037343
print(ridge.score(test_scaled, test_target)) # 0.9790693977615387

Ridge와 Lasso모델을 사용할 때 규제의 양을 임의로 지정할 수 있다.

이를 alpha매개변수로 조절이 가능한데, alpha값이 크면 규제강도가 쎄져 계수값을 줄이고 조금 더 과소적합되도록 유도한다.

alpha값이 작으면 계수를 줄이는 역할이 줄어들고 선형회귀모델과 비슷해져 과대적합이 될 가능성이 크다.

한번 적절한 alpha 모델을 찾아보자

적절한 alpha값을 찾는 방법은 alpha값에 대한 R^2그래프를 그려보는 것이다. 훈련세트와 테스트세트의 점수가 가장 가까운 지점이 최적의 alpha값이 된다.

import matplotlib.pyplot as plt
train_score = []
test_score = []

alpha값을 바꿀 때마다 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))

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

for문을 써서 alpha의 값이 변할 때마다 점수가 얼마나 변하는지를 볼 수 있도록 한다.

image

alpha가 적어질 수록 훈련세트의 점수가 너무 커서 테스트세트의 점수와 크게 멀어지는 모습을 볼 수 있는데 이것이 과대적합이고,
alpha가 커질 수록 테스트 세트 점수와 훈련세트점수가 같이 작아져서 과소적합이 일어난다.

최적은 alpha가 -1일 때로 이때는 10^-1이므로 1/10 = 0.1일 때이다.

이제 Ridge 모델을 학습시켜보자.

ridge = Ridge(alpha=0.1)
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target)) # 0.9903815817570367
print(ridge.score(test_scaled, test_target)) # 0.9827976465386928

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

Lasso 회귀

from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(train_scaled, train_target) 
print(lasso.score(train_scaled, train_target)) # 0.989789897208096
print(lasso.score(test_scaled,test_target)) # 0.9800593698421883

0.98로 점수는 좋다.

하지만 lasso도 ridge와 마찬가지로 alpha 매개변수로 규제의 강도를 조절 할 수 있다. 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))
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()

image

가장 최적의 값은 alpha가 1일 때로 즉, 10일 때이다.

위 그래프도 왼쪽으로 갈 수록 과대적합이, 오른쪽으로 갈수록 과소적합이 일어나고 있다.

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

모델이 잘 훈련됐다. 특성을 많이 사용했지만 릿지와 마찬가지로 라쏘모델이 과대적합을 잘 억제하고 테스트 세트의 성능을 크게 높였다.

lasso모델은 계수값을 아예 0으로 만들 수 있으므로 lasso모델의 계수는 coef_에 저장되어있다. 이중 0인것을 헤아려보자

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

np.sum()함수는 배열을 모두 더한 값을 반환한다. 넘파이 배열에 비교연산자를 사용했을 때, 각 원소는 True 또는 False가 된다. np.sum()함수는 True를 1로, False를 0으로 인식하여 덧셈을 할 수 있기 떄문에 마치 비교연산자에 맞는 원소 개수를 헤아리는 효과를 낸다.

55개의 특성을 모델에 주입했지만 모델이 사용한 특성은 15개 밖에 되지 않는다. 이러한 특징때문에 라쏘모델을 유용한 특성을 골라내는 용도로 사용할 수 있다.

이러한 규제를 적용한 선형모델을 사용해 농어의 무게를 잘 예측할 수 있게 되었다.

'STUDY' 카테고리의 다른 글

리눅스 네트워크 및 프로세스 관련 명령어  (6) 2024.11.14
vim 명령어 정리  (2) 2024.11.14
[AI] 선형회귀  (0) 2022.11.06
[AI] k-최근접 이웃 회귀  (1) 2022.11.06
[AI] 데이터 전처리  (0) 2022.11.06