본문 바로가기

머신러닝 & 딥러닝

[혼자 공부하는 머신러닝] 03-03 특성공학과 규제(다중회귀, 변환기, 릿지&라쏘 회귀)

<알아두어야 하는 키워드>

다중회귀(multiple regression): 여러개의 특성을 활용한 선형회귀를 의미 (기존 선형 회귀는 2개의 특성만 사용함)

# 2개일때의 선형회귀 방정식 (무게)= a*(길이)+b

# 여러개일때의 선형회귀 방정식 (무게) = a*(길이) + b*(너비) + c*(두께) + d   --> 고려하는 특성이 늘어난 형태(고차원 문제로 발전함)

# 특성이 많으면 선형회귀는 강력한성능을 발휘함

 

특성 공학(Feature engineering): 기존의 특성을 사용해 새로운 특성을 뽑아내는 작업(서로 다른 특성을 곱해서 추가한다던가, 한 특성을 제곱한 수치를 추가한다던가 등 특성 개수 뻥튀기 가능), 특성의 조합

 

판다스(Pandas): 데이터 분석 라이브러리, 데이터프레임(DataFrame) 구조를 사용(넘파이 배열과 비슷하게 다차원 배열을 다루지만 더 많은 기능, DF는 넘파이 배열로도 쉽게 바꿀수 있음)

 

변환기(Transformer): 사이킷런에서 주로 제공하며, 특성을 만들거나 전처리(스케일 맞추기) 하기 위한 다양한 클래스 제공. (특성공학에서 특성개수 뻥튀기 할때 사용함) 

 

규제(Regularization): 머신러닝 모델이 훈련세트를 과도하게 학습하지 못하게 훼방을 놓음(Overfitting 방지), 선형회귀 모델의 경우 특성에 곱해지는 계수(기울기)의 크기를 작게 만드는 것이다.

 

다중회귀 할때도 특성의 정규화 작업을 해야 하는걸 잊지 말자(sklearn에 Standard Scaler 쓰면 됨)

 

변환기를 쓸때는 항상 훈련세트에서 학습시킨걸 그대로 테스트셋에도 가져다 쓰자(훈련세트는 데이터를 가장 잘 나타내는 대표라고 항상 가정 한다.)

 

릿지(ridge)와 라쏘(lasso): 선형회귀 모델에 규제를 추가한 모델, 둘이 규제를 가하는 방법이 다름

-릿지는 계수를 제곱한 값을 기준으로 규제를 정함(일반적으로 더 많이 씀 라쏘보다)

-라쏘는 계수의 절댓값을 기준으로 규제를 적용함

# 둘다 계수의 크기를 줄일수 있지만 라쏘는 0으로 만들수도 있음

# alpha 값을 조절하여 규제의 강도를 조절할수 있음(알파가 작으면 규제가 느슨, 커지면 규제강도 증가)

 

최적의 alpha값이란?: 알파 값에 대한 결정계수값(score함수 써서 계산) 그래프를 그려보면 된다. 

훈련셋과 테스트셋의 점수가 가장 가까운 지점이 최적의 알파값(규제강도)가 된다.

라소회귀에서 규제강도를 변경해가면서 R^2를 비교

 

하이퍼파라미터: 머신러닝 알고리즘이 학습하지 않는 파라미터, 사람이 사전에 지정해야 한다. 릿지와 라쏘의 규제 강도 alpha 파라미터도 여기에 속함

 

 

 

 

<유용한 코드>

# csv 파일을 읽어서 데이터프레임으로 변환, 이후 넘파이 배열로 다시 바꾸기
import pandas as pd

df = pd.read_csv('https://bit.ly/perch_csv_data')    # csv 파일 읽어오기
perch_full = df.to_numpy()    # 데이터프레임을 넘파이로 변환
print(perch_full)    # length, height, width가 출력됨

#skiprows: 건너뛸 행의 개수 지정
#nrows: 읽을 행의 개수를 지정

# 특성 공학 실행 (interaction_only= True로 하면 거듭제곱하는 항은 제외되고 각 특성간의 곱만 추가 됨)
poly = PolynomialFeatures(include_bias=False)     # include_bias=False: 절편 1 삭제 하기(안중요 하기 때문)
poly.fit(train_input)
train_poly = poly.transform(train_input)    # 농어 특성을 증폭시키고 저장함
print(train_input.shape)    # (42, 3)    원본은 3개의 특성 가지고 있음
print(train_poly.shape)    # (42, 9)    변환기를 거친뒤 9개로 특성이 증폭됨


# 정규화 실행
from sklearn.preprocessing import StandardScaler    # 이것도 변환기라고 볼수 있음(과거에는 표준점수로 수동으로 맞춰줌)
ss = StandardScaler()
ss.fit(train_poly)

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

 

 

 

전체 코드는 아래와 같다

#%% 농어 데이터csv 읽어오기
import pandas as pd
import numpy as np

df = pd.read_csv('https://bit.ly/perch_csv_data')    # csv 파일 읽어오기
perch_full = df.to_numpy()    # 데이터프레임을 넘파이로 변환
print(perch_full)    # length, height, width




#%% 데이터셋 준비, 트레인셋 테스트셋 분리하기
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]]))     # 기억하기: 특성엔지니어링 진행하기전에 fit 하는거 잊지 말기 ([[1. 2. 3. 4. 6. 9.]] 여기서 1은 절편임, 절편은 중요한건 아님)


# 실제 농어 특성 변환 하기
poly = PolynomialFeatures(include_bias=False)     # include_bias=False: 절편 1 삭제 하기(안중요 하기 때문)
poly.fit(train_input)
train_poly = poly.transform(train_input)    # 농어 특성을 증폭시키고 저장함
print(train_input.shape)    # (42, 3)    원본은 3개의 특성 가지고 있음
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)

# 학습인풋 변환기 그대로 테스트인풋 특성 엔지니어링 해주기
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))   # 0.9903183436982124
print(lr.score(test_poly, test_target))    # 0.9714559911594145

# 특성이 9개일때 매우 높은 정확도를 보이는걸 확인, 한번 특성의 개수를 샘플의 개수 이상으로 더 증폭시켜보자
poly = PolynomialFeatures(degree=5, include_bias=False)     # degree=5이면? a^5, a^3*b^2 이런 경우도 가능하게 한다는 뜻, 더 많은 조합의 경우(default = 2임)
poly.fit(train_input)
poly.get_feature_names_out()    # 'x0^3 x1^2', 'x0^3 x1 x2', 'x0^3 x2^2', 'x0^2 x1^3' 이런식으로 됨

train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)

print(train_input.shape)    # (42, 3)
print(train_poly.shape)    #  (42, 55)    55개로 증폭 됨

# 성능평가를 해보자(결정계수) --> 완전 과대 적합 발생! 규제(regularization)을 통해 과하게 학습하는걸 방해해보자
#print(lr.score(train_poly, train_target))   # 0.9999999999996176
#print(lr.score(test_poly, test_target))    # -144.40585108215134




# %% 규제를 시작하기전 각 특성들의 정규화(scale 맞추기) 실행하기
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)

# 규제를 통해서 특성의 개수가 55개로 늘어나도 과대적합이 발생하지 않음
print(ridge.score(train_scaled, train_target))    # 0.9896101671037343
print(ridge.score(test_scaled, test_target))    # 0.9790693977615383




# %% 릿지 모델에서 최적의 규제강도 alpha를 찾는다. (train,test의 score가 가장 가까운 지점이 최적의 alpha 값이다.)
# 알파값을 변경해가면서 결정계수 그래프를 그려보자

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

alpha_list = np.log10(np.array(alpha_list))
plt.plot(alpha_list, train_score)
plt.plot(alpha_list, test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.savefig('Ridge_bestAlpha.png')    # 알파가 -1, 0.1 일때 최적의 규제강도를 보인다.(두 결정계수가 가장 가까운 부분이 최적)




#%% 최적의 alpha 값을 찾았으니, 이걸 바탕으로 릿지모델을 구축한다.
ridge = Ridge(alpha=0.1)
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))    # 0.9903815817570366
print(ridge.score(test_scaled, test_target))    # 0.9827976465386937




#%% 라쏘 회귀 구현(규제의 기준이 계수의 절대값에 해당)
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))

alpha_list = np.log10(np.array(alpha_list))
plt.plot(alpha_list, train_score)
plt.plot(alpha_list, test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.savefig('lasso_bestAlpha.png') 

# 위의 라쏘모델 생성시 오류메시지가 뜰수도 있음, 최적의 계수를 찾기 위해서 반복적 계산을 수행하는데
# 지정한 반복 횟수가 부족할때 이런 경고가 발생함, 반복횟수를 늘리기 위해서 max_iter=10000 을 옵션으로 넣어줌




# %% 알파가 10일때 최적의 규제강도임을 파악함
lasso = Lasso(alpha=10, max_iter=10000)
lasso.fit(train_scaled, train_target)

print(lasso.score(train_scaled, train_target))    # 0.9887624603020236
print(lasso.score(test_scaled, test_target))    # 0.9830309645308443
print(np.sum(lasso.coef_ == 0))    # 48: 라소가 계수를 0으로 만들어 버린 것의 개수를 의미함