데이터 & AI/혼자공부하는 머시런닝 딥러닝

혼공 머신러닝 딥러닝 CH03-3

뭉실뭉실뜬구름 2023. 5. 25. 21:48
728x90
 

특성 공학과 규제

여러 특성을 사용한 다중 회귀에 대해 배우고 사이킷런의 여러 도구를 사용해 봅니다. 복잡한 모델의 과대적합을 막기 위한 릿지와 라쏘 회귀를 배운다,

  • 이번에는 길이 말고도 높이와 두께 데이터를 모두 이용해서 문제를 해결하겠다.
  • 사이킷런의 PolynomialFeatures 클래스를 사용하겠다.

다중 회귀

여러 개의 특성을 사용한 선형 회귀를 다중 회귀라고 부른다.

특성이 2개면 타깃값과 함께 3차원 공간을 형성하고 선형 회귀 방적식 '타깃 = a X 특성1 + b X 특성2 + 절편'은 평면이 된다.

  • 이 예제에서는 농어의 길이 높이 두께도 함께 사용한다. 또한 이전 절에서처럼 3개의 특성을 각각 제곱하여 추가한다. 거기다가 각 특성을 서로 곱해서 또 다른 특성을 만들겠다.
  • 즉 농어 길이 X 농어 높이를 새로운 특성으로 만든다는 거다.

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


데이터 준비

  • 판다스를 사용해 농어 데이터를 인터넷에서 내려받아 데이터프레임에 저장하겠다.
  • 그 다음 넘파이 배열로 변환하여 선형 회귀 모델을 훈련해 보겠다.
  • read_csv() 함수로 데이터 프레임을 만든 다음 to_numpy()메서드를 사용하겠다.
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 ]]
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)
 
 

사이킷런의 변환기

사이킷런은 특성을 만들거나 전처리하기 위한 다양한 클래스를 제공한다. 사이킷런에서는 이런 클래스를 변환기라고 부른다. 사이킷런의 모델 클래스에 일관된 fit(),score(),predict() 메서드가 있는 것처럼 변환기 클래스는 모두 fit(),transform() 메서드를 제공한다.

  • 우리가 사용할 변환기는 *** PolynomialFeatures *** 클래스이다. 이클래스는 sklearn.preprocessing 패키지에 포함되어있다.
from sklearn.preprocessing import PolynomialFeatures

 

  • 2개의 특성 2와 3으로 이루어진 샘플하나를 적용하겠다. 앞서 이야기한 것처럼 이 클래스의 객체를 만든 다음 fit(),transform() 메서드를 차례대로 호출한다.
  • ? transform 전에 꼭 fit을 하는 이유 >> 훈련(fit)을 해야 변환(transform)이 가능하다. 사이킷런의 일관된 api 때문에 두 단계로 나뉘어져 있다. 두 메서드를 하나로 붙인 fit_transform메서드도 있다.
 
 
poly = PolynomialFeatures()
poly.fit([[2,3]])
print(poly.transform([[2,3]]))
>>[[1. 2. 3. 4. 6. 9.]]

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

변환기는 타깃 데이터가 필요없어 입력데이터만 전달하면 된다.

PolynomialFeatures 클래스는 기본적으로 각 특성을 제곱한 항을 추가하고 특성끼리 서로 곱한 항을 추가한다. 2와3을 각기 제곱한 4와9가 추가되고, 2와3을 곱한 6이 추가된다. 그렇다면 1은 왜???

  • *무게 = a X 길이 + b X 높이 + c X 두께 + d x 1*

사실 선형 방정식의 절편을 항상 값이 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)
  • 9개의 특성이 만들어졌는데 어떤 입력의 조합으로 만들어졌는지 궁금행
poly.get_feature_names_out()
>>array(['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2',
       'x2^2'], dtype=object)
  • x0은 첫 번째 특성을 의미하며, x0^2는 첫 번째 특성의 제곱 등등을 의미함
 

test_poly = poly.transform(test_input) 
print(test_poly.shape) 
poly.get_feature_names_out()
>>(14, 9)
array(['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2',
       'x2^2'], dtype=object)
 
 

다중 회귀 모델 훈련하기

다중 회귀 모델을 훈련하는 것은 선형 회귀 모델을 훈련하는 것과 같다. 다만 여러 개의 특성을 사용하여 선형 회귀를 수행하는 것뿐이다.

  • 먼저 사이킷런의 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
  • 테스트 세트에 대한 점수는 높아지지 않았다. 그래도 과소적합 문제는 해결된거 같다.
  • 만약 특성을 더 추가하면? 3제곱 4제곱합을 넣는거다.
  • PolynimialFeatures 클래스의 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)
  • 만들어진 특성의 개수가 무려려려려ㅕ55개
  • 이걸 이용해 선형 회귀 모델을 다시 훈련하면
 
lr.fit(train_poly,train_target) 
print(lr.score(train_poly,train_target))
>>0.9999999999996433
  • 옼ㅋㅋㅋ
 
print(lr.score(test_poly,test_target))
>>-144.40579436844948
  • 앵? ㅋㅋㅋㅋ 특성의 개수를 너무 많이 늘려서 훈련세트에 과대 적합이 된거 같아...
  • 과대적합을 줄이기 위해 특성을 줄이면 되는데 이 방법 말고 다른 방법이 있다고 한다!!!

규제

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

  • 우선 규제를 적용하기 전에 정규화를 진행해야한다. 계수값의 크기가 서로 많이 다르면 공정하게 제어되지 않기 떄문이다.
  • 사이킷런에서 제공하는 StandardScaler 클래스를 사용하겠다.
from sklearn.preprocessing import StandardScaler
ss = StandardScaler() 
ss.fit(train_poly) 
train_scaled = ss.transform(train_poly) 
test_scaled = ss.transform(test_poly) 
# 훈련 세트에서 학습한 평균과 표준편차는 StandardScaler 클래스 객체의 mean_, scale_ 속성에 저장된다. 특성마다 계산하므로 55개의 평균과 표준 편차가 들어 있다.
 
  • 이제 표준점수로 변환한 train_scaled와 test_scaled가 준비되었다.

선형 회귀 모델에 규제를 추가한 모델을 릿지와 라쏘라고 부른다. 두 모델은 규제를 가하는 방법이 다르다. 릿지는 계수를 제곱한 값을 기준으로 규제를 적용하고, 라쏘는 계수의 절댓값을 기준으로 규제를 적용한다. 일반적으로 릿지를 조금 더 선호한다. 두 알고리즘 모두 계수의 크기를 줄이지만 라쏘는 아예 0으로 만들 수도 있다.


릿지 회귀

  • 앞서 준비한 train_scaled 데이터로 릿지 모델을 훈련해 보죠
 

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

>>0.9896101671037343
0.9790693977615387
  • 테스트 세트 점수가 정상으로 돌아왔다.

릿지와 라쏘 모델을 사용할 때 규제의 양을 임의로 조절할 수 있다.

모델 객체를 만들 때 alpha 매개변수로 규제의 강도를 조절한다.

alpha 값이 크면 규제 강도가 세지므로 계수 값을 더 줄이고 조금 더 과소 적합되도록 유도한더.

  • alpha 값은 사전에 우리가 지정해야 하는 값이다. 이처럼 머신러닝 모델이 학습할 수 없고 사람이 알려줘야 하는 파라미터를 하이퍼파라미터라고 부른다. 사이킷런과 같은 머신러닝 라이브러리에서 하이퍼파라미터는 클래스와 메서드의 매개변수로 표현된다.
  • 적잘한 alpha 값을 찾는 한 가지 방법은 alpha 값에 대한 결정계수 값의 그래프를 그려 보는 것이다. 훈련 세트와 테스트 세트의 점수가 가장 가까운 지점이 최적의 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))
  • 이제 그래프를 그려보겠다.
  • 근데 alpha 값을 0.001부터 10배씩 늘려서 이대로 그래프를 그리면 그래프 왼쪽이 너무 촘촘해진다.
  • alpha_list에 있는 6개의 값을 동일한 간격으로 나타내기 위해 로그 함수로 바꾸어 지수로 표현하겠다. 즉 0.001은 -3,0.01은 -2가 되는 식이다.
# 넘파이 로그 함수는 np.lot()와 np.log10()이 있는데 전자는 자연 상수 e를 밑으로 하는 자연로그이며 후자는 10을 밑으로 하는 상용로그이다.
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()
  • 가장 좋은 곳은 -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)) 
print(lasso.score(test_scaled,test_target))

>>0.989789897208096
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=1000) 
	lasso.fit(train_scaled,train_target) 
	train_score.append(lasso.score(train_scaled,train_target)) 
	test_score.append(lasso.score(test_scaled,test_target))
/usr/local/lib/python3.10/dist-packages/sklearn/linear_model/_coordinate_descent.py:631: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations, check the scale of the features or consider increasing regularisation. Duality gap: 2.336e+04, tolerance: 5.183e+02
  model = cd_fast.enet_coordinate_descent(
/usr/local/lib/python3.10/dist-packages/sklearn/linear_model/_coordinate_descent.py:631: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations, check the scale of the features or consider increasing regularisation. Duality gap: 2.025e+04, tolerance: 5.183e+02
  model = cd_fast.enet_coordinate_descent(
/usr/local/lib/python3.10/dist-packages/sklearn/linear_model/_coordinate_descent.py:631: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations, check the scale of the features or consider increasing regularisation. Duality gap: 8.062e+02, tolerance: 5.183e+02
  model = cd_fast.enet_coordinate_descent(
  • 그 다음 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()
  • 최적의 alpha는 1이다. 이 값으로 다시 훈련하면
 
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
# np.sum()함수는 배열을 모두 더한 값을 반환한다. 넘파이 배열에 비교 연산자를 사용했을 때 각 원소는 True 또는 False이다. # np.sum() 함수는 True를 1 False를 0으로 인식하여 덧셈을 한다.
  • 결론적으로 40개가 0이니까 55개의 특성을 주입했지만 라쏘 모델이 사용한 특성은 15개 밖에 되지 않는다.
  • 이러한 특성 때문에 라쏘 모델을 유용한 특성을 골라내는 용도로도 사용할 수 있다.
 
728x90