혼자 공부하는 머신러닝 + 딥러닝 03-3 특성 공학과 규제

손지호·2023년 7월 14일
0

다중 회귀

다중 회귀(multiple regression) : 여러 개의 특성을 사용한 선형 회귀

하지만 우리는 3차원 공간 이상을 그리거나 상상할 수 없다. 선형 회귀를 단순한 직선이나 평면으로 생각하여 성능이 무조건 낮다고 오해해선 안된다!! 특성 많은 고차원에서는 선형 회귀가 매우 복잡한 모델을 표현할 수 있다.
특성 공학(feature engineering) : 기존의 특성을 사용해 새로운 특성을 뽑아내는 작업. 여기서는 '농어 길이 x 농어 높이'를 새로운 특성으로 만드는 예시.

데이터 준비

판다스(pandas) : 유명한 데이터 라이브러리.
데이터프레임(dataframe) : 넘파이 배열처럼 다차원 배열 다룰 수 있지만 훨씬 더 많은 기능 제공, 넘파이 배열로도 쉽게 바꿀 수 있음.

import pandas as pd # 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]
    ...
    [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]
     )

다음으로 perch_full과 perch_weight를 훈련 세트와 테스트 세트로 나눈다.

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)

# 이 데이터 사용해 새로운 특성 만들기.

사이킷런의 변환기

사이킷런은 특성을 만들거나 정처리하기 위한 다양한 클래스를 제공한다. 사이킷런에서는 이런 클래스를 변환기(transfomer) 라고 부른다. 변환기 클래스는 fit(), transform() 메서드를 제공한다.
앞서 배운 LinearRegression 같은 사이킷런의 모델 클래스는 추정기(estimator)리고도 부른다.

우리가 사용할 변환기는 PolynomialFeatures 클래스. 이 클래스는 sklearnpreprocessing 패키지에 포함되어 있다.

from sklearn.preprocessing import PolynomialFEatures

poly = PolynomialFeatures()
poly.fit([[2, 3]])
print(poly.transofrm([[2, 3]]))
>>> [[1. 2. 3. 4. 6. 9.]]

+ 꼭 훈련(fit)해야 변환(transform)이 가능함!!

fit() 메서드는 새롭게 만들 특성 조합을 찾고 transform() 메서드는 실제 데이터를 변환. 변환기는 입력 데이터를 변환하는 데 타깃 데이터 필요 X.
PolynomialFeatures 클래스는 기ㅗㄴ적으로 각 특성을 제곱한 항 추가하고 특성끼리 서로 곱한 항 추가한다. 근데 1은 왜 추가 되었을까??
선형 방정식의 절편을 항상 값이 1인 특성과 곱해지는 계수라고 볼 수 있다. 사이킷런의 선형 모델은 자동으로 철현 추가하므로 굳이 이렇게 특성 만들 필요 없음!

# include_bias = False로 하면 절편 위한 항 제거되고 특성의 제곱과 특성끼리 ㅗㅂ한 항만 추가.
poly = PolynomialFeatures(include_bias = False)
poly.fit([[2,3]])
print(poly.transform([[2,3]])
>>> [[2.  3.  4.  6.  9.]]

이 방식을 train_input에 적용.

poly = PolynomialFeatures(include_bias=False)

poly.fit(train_input)
train_poly = poly.transform(train_input)

print(train_poly.shape)
>>> (42, 9)
# get_feature_name_out() 메서드 호출하면 9개의 특성이 각각 어떤 입력의 조합으로 만들어졌는지 알려줌.
poly.get_feature_names_out()
>>> array(['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2',
       'x2^2'], dtype=object)
       
# 테스트 세트를 변환하기
test_poly = poly.transform(test_input)

다중 회귀 모델 훈련하기

다중 회귀 모델을 훈련하는 것은 선형 회귀 모델을 훈련하는 것과 같다. 다만 여러 개의 특서을 사용하여 선형 회귀를 수행하는 것뿐. 먼저 사이킷런의 LinearRegression 클래스 임포트하고 앞에서 만든 train_poly 사용해 모델 훈련시켜보자!

from sklearn.linear_model import LinearRegression

lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
>>> 0.9903183436982125

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

# 테스트 점수도 확인
print(lr.score(test_poly, test_target))
>>> 0.9714559911594111

테스트 세트에 대한 점수는 높아지지 않았지만 농어의 길이만 사ㅛㅇ했을 때 있던 과소적합 문제는 나타나지 않음.
특성 더 많이 추가하면?? POlynomialFeatures 클래스의 degree 매개변수를 사용하여 필요한 과항의 최대 차수를 지정할 수 있다.

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

train_poly 배열의 열의 개수가 특성의 개수. 이 데이터를 사용해 선형 회귀 모델 다시 훈련.

lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
>>> 0.9999999999996433

거의 완벽한 점수!! 테스트 세트에 대한 점수 출력

print(lr.score(test_poly, test_target))
>>> -144.40579436844948

너무나도 큰 음수!!???
→ 특성의 개수 크게 늘리면 선형 모델은 아주 강력해진다. 훈련 세트에 대해 거의 완벽하게 학습할 수 있지만, 이런 모델은 훈련 세트에 너무 과대적합되므로 테스트 세트에서는 형편없는 점수 만든다.

이 문제를 해결하기 위해선 특성을 줄여야 한다. 과대적합 줄이는 또 다른 방법 배워보자!!


규제

규제(regularization) : 머신러닝 모델이 훈련 세트를 너무 과도하게 학습하지 못하도록 훼방하는 것. 즉, 모델이 훈련 세트에 과대적합되지 않도록 만드는 것.선형 회귀 모델의 경우 특성에 곱해지는 계수(또는 기울기)의 크기를 작게 만드는 일.

특성의 스케일이 정규화되지 않으면 곱해지는 계수 값도 차이 나게 된다. 일반적으로 선형 회귀 모델에 규제를 적용할 때 계수 값의 크기가 서로 많이 다르면 공정하게 제어되지 않을 것이다. 그래서 규제 적용하기 전에 먼저 정규화를 해야한다!! 사이킷런에서는 StandardScaler 클래스를 사용한다. (변환기의 하나.)

from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_poly)

train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)

먼저 StandardScale 클래스의 객체 ss를 초기화한 후 PolynomialFeatures 클래스로 만든 trainpoly 사용해 객체 훈련한다. 강조하지만, 꼭 훈련 세트로 학습한 변호나기 사용해 테스트세트까지 변환해야 한다._
이제 표준점수 변환한 train_scaled와 test_scaled가 준비 되었다.

선형 회귀 모델에 규제를 추가한 모델을 릿지(ridge)라쏘(lasso) 라 부른다.
릿지는 계수를 제곱한 값을 기준으로 규제를 적용, 라쏘는 계수의 절댓값을 기준으로 규제를 적용한다. 일반적으로 릿지 조금 더 선호! 두 알고리즘 모두 계수의 크기를 줄이지만, 라쏘는 0으로 만들어버릴 수도 있음!


릿지 회귀

릿지, 라쏘 모두 sklearn.linear_model 패키지 안에 있다.

# train_scaled 데이터로 릿지 모델 훈련해보기
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

테스트 세트 점수가 정상으로 돌아왔다!
릿지와 라쏘 모델 사용할 때 규제의 양을 임의로 조절할 수 있다. 모델 개체 만들 때 alpha 매개변수로 규제의 강도 조절.
alpha 값이 크면 규제 강도 세지므로 계수 값더 줄이고 조금 더 과소적합되도록 유도한다.
반대로 alpha 값이 작으면 계수를 줄이는 역할이 줄어들고 선형 회귀 모델과 유사해지므로 과대적합될 가능성이 크다.

+ 사람이 직접 지정해야 하는 매개변수
하이퍼파라미터(hyperparameter) : 머신러닝 모델이 학습할 수 없고 사람이 알려줘야 하는 파라미터.

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

# 맷플롯립 임포트하고 alpha 값을 바꿀 때마다 score() 매서드 결과 저장할 리스트 생성
import matplotlib.pyplot as plt

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))
# 그래프 작성
# 0.001 부터 10배씩 했기 때문에 너무 촘촘해짐 → alpha_list에 있는 6개의 값 동일한 간격으로 나타내기 위해 로그 함수로 바꾸어 지수로 표현.
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 = 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.9903815817570367
	0.9827976465386928

훈련 세트, 테스트 세트 점수 비슷하고 과대적합과 과소적합에서 균형 이루고 있다!


라쏘 회귀

라쏘 모델 훈련하는 것은 릿지와 매우 비슷. Ridge 클래스를 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

테스트 세트의 점수도 릿지만큼 아주 좋음!! 라쏘 모델도 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))
# train_score와 test_score 리스트 사용해 그래프 작성. x축은 로그 스케일로 변환.
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()

파란색이 훈련 세트 그래프. 주황색이 테스트 세트 그래프. 이것도 왼쪽은 과대적합. 오른쪽으로 갈수록 훈련 세트와 테스트 세트의 점수가 좁혀지고 있다. 가장 오른쪽은 아주 크게 점수가 떨어진다. 이 지점은 분명 과소적합되는 모델일 것. 라쏘 모델에서 최적의 aplha 값은 1, 즉 10^1 = 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

모델 잘 훈련됨!!! 특성 많이 사용했지만, 릿지와 마찬가지로 라쏘 모델이 과대적합을 잘 억제하고 테스트 세트의 성능 크게 높였다. 앞에서 라쏘 모델은 계수 값을 아예 0으로 만들 수 있었다. 라쏘 모델의 계수는 coef_ 속성에 저장되어 있다.

# 0인 것 헤아려보기
print(np.sum(lasso.coef_ == 0))
>>> 40

55개의 특성을 사용했지만, 라쏘 모델이 사용한 특성은 15개 뿐. 이런 특징 때문에 라쏘 모델을 유용한 특성을 골라내는 용도로 사용할 수 있다.


모델의 과대적합을 제어하기

선형 회귀 알고리즘 사용해 농어 무게 예측하는 모델 훈련. 훈련 세트에 과소적합되는 문제 발생. 이를 위해 농어의 길이뿐만 아니라 높이와 두께도 사용하여 다중 회귀 모델 훈련 시킴.
또한 다항 특성을 많이 추가하여 훈련 세트에서 거의 완벽에 가까운 점수 얻는 모델 훈련. 특성 많이 추가하면 선형 회귀는 매우 강력한 성능 낸다. 하지만 특성이 너무 많으면 선형 회귀 모델을 제약하기 위한 도구가 필요.
이 도구가 릿지와 라쏘. 사이킷런 사요해 다중 회귀 모델과 릿지, 라쏘 모델을 훈련. 규제 양 조절하기 위해 최적의 alpha 값 찾는 방법 알아봤다!

전체 코드 (출처 : https://bit.ly/hg-03-3)

# 데이터 준비
import pandas as pd

df = pd.read_csv('https://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
print(perch_full)

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)

# 사이킷런의 변환기
from sklearn.preprocessing import PolynomialFeatures

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

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

poly = PolynomialFeatures(include_bias=False)

poly.fit(train_input)
train_poly = poly.transform(train_input)

print(train_poly.shape)
poly.get_feature_names_out()
test_poly = poly.transform(test_input)

# 다중 회귀 모델 훈련하기
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))

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)

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

#규제
from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_poly)

train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)

# 릿지
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))

import matplotlib.pyplot as plt

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

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

# 라쏘
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))

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

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

정리

  • 다중 회귀는 여러 개의 특성을 사용하는 회귀 모델. 특성이 많으면 선형 모델은 강력한 성능을 발휘한다.
  • 특성 공학은 주어진 특성을 조합하여 새로운 특성을 만드는 일련의 작업 과정이다.
  • 릿지는 규제가 있는 선형 회귀 모델 중 하나이며 선형 모델의 계수를 작게 만들어 과대적합을 완화시킨다. 릿지는 비교적 효과가 좋아 널리 사용하는 규제 방법.
  • 라쏘는 또 다른 규제가 있는 선형 회귀 모델이다. 릿지와 달리 계수 값을 아예 0으로 만들 수 있다.
  • 하이퍼파라미터는 머신러닝 알고리즘이 학습하지 않는 파라미터이다. 이런 파라미터는 사람이 사전에 지정해야 한다. 대표적으로 릿지와 라쏘의 규제 강도 alpha 파라미터이다.

핵심 패키지와 함수

pandas

  • read_csv() : CSV 파일을 로컬 컴퓨터나 인터넷에서 일겅 판다스 데이터프레임으로 변환하는 함수. 매우 많은 매개변수 제공. 그중 자주 사용하는 것들은 아래와 같다.
    sep : CSV 파일의 구분자를 지정. 기본값은 '콤마(,)'
    header : 데이터프레임의 열 이름으로 사용할 CSV 파일의 행 번호를 지정. 기본적으로 첫 번째 행을 열 이름으로 사용.
    skiprows : 파일에서 읽기 전에 건너뛸 행의 개수 지정.
    nrows : 파일에서 읽을 행의 개수 지정.

scikit-learn

  • PolynomialFeatures : 주어진 특성 조합하여 새로운 특성 만듦.
    degree는 최고 차수 지정, 기본값은 2.
    interaction_only가 True이면 거듭제곱 항은 제외되고 특성 간의 곱셈 항만 추가. 기본값은 False.
    include_bias가 False이면 절편 위한 특성 추가 X. 기본값은 True.
  • Ridge : 규제가 있는 회귀 알고리즘인 릿지 회귀 모델을 훈련함.
    alpha 매개변수로 규제의 강도 조절. alpha 값이 클수록 규제가 세짐. 기본값은 1.
    solver 매개변수에 최적의 모델을 찾기 위한 방법 지정 가능. 기본값은 'auto', 데이터에 따라 자동으로 선택. (사이킷런 0.17 버전의 'sag'는 확률적 평균 경사 하강법 알고리즘으로 특성과 샘플 수 많을 때 성능 빠르고 좋음. 사이킷런 0.19 버전에는 'sag'의 개선 버전인 'saga' 추가됨.)
    random_state는 slover가 'sag'나 'saga'일 때 넘파이 난수 시드값 지정 가능.
  • Lasso : 규제가 있는 회귀 알고리즘인 라쏘 회귀 모델을 훈련. 이 클래스는 최적의 모델 찾기 위해 좌표축 따라 최적화 수행해가는 좌표 하강법(coordinate descent) 사용.
    alpha와 random_state 매개변수는 Ridge 클래스와 동일.
    max_iter는 알고리즘의 수행 반복 횟수를 지정. 기본값은 1000.
profile
초보 중의 초보. 열심히 하고자 하는 햄스터!

0개의 댓글