본문 바로가기
Python/ML

[머신러닝] 릿지, 라쏘(농어 무게 예측3)

by JooRi 2023. 1. 31.
728x90
SMALL
* 교재: 혼자 공부하는 머신러닝+딥러닝 (hanbit.co.kr)

 

* 문제: 릿지와 라쏘 회귀로 과대적합을 해결한다.

여러 개의 특성을 사용한 다중 회귀 모델의 과대적합을 경험해 보고 이를 해결하기 위해 릿지와 라쏘 모델을 훈련한다.

 

* 문제 해결 과정

1. 판다스를 사용하여 농어 데이터 준비하기

2. 훈련 세트와 테스트 세트 만들기

3. 사이킷런 변환기로 특성 변환하기

4. 다중 회귀 모델 훈련 및 평가하기

5. 규제

6. 릿지 회귀

7. 라쏘 회귀

 

 

1. 판다스를 사용하여 농어 데이터 준비하기

농어의 특성을 길이, 높이, 두께로 3개를 사용할 것이 때문에 데이터를 준비하기 번거롭다. 따라서 판다스를 사용하면 인터넷에서 데이터를 바로 다운로드하여 사용할 수 있다. 판다스는 데이터 분석 라이브러리이다.

 

 

농어 56마리의 길이 리스트를 준비해 보자.

 

import pandas as pd  # pd는 판다스의 별칭으로 사용됨
df = pd.read_csv('http://bit.ly/perch_csv_data')  # 농어 데이터 내려받기
perch_full = df.to_numpy()  # 넘파이 배열로 변환
print(perch_full)

 

[[ 8.4 2.11 1.41]

 [13.7 3.53 2. ]

...

 [44. 12.49 7.6 ]]

 

read_csv()는 csv 파일을 인터넷에서 읽어 판다스 데이터프레임으로 변환하는 함수이다. read_csv() 함수에 주소를 넣으면 판다스에서 파일을 읽을 수 있다.

CSV 파일은 판다스 데이터프레임을 만들기 위해 많이 사용하는 파일로, 콤마로 나누어져 있는 텍스트 파일이다.

to_numpy() 메서드는 넘파이 배열로 변환하는 메서드이다.

판다스로 농어 데이터를 인터넷에서 내려받아 데이터프레임(판다스의 핵심 데이터 구조)에 저장한 후 to_numpy() 메서드를 사용하여 넘파이 배열로 변환하였다.

 

 

농어 56마리의 무게 리스트를 준비해 보자.

 

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

 

타깃 데이터는 이전과 동일하게 넘파이를 임포트 하여 넘파이 배열로 만들었다.

농어의 길이가 특성이고 무게가 타깃이다.

 

 

2. 훈련 세트와 테스트 세트를 만들기

 

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)

 

사이킷런의 train_test_split() 함수를 임포트 한 후 perch_full과 perch_weight을 훈련 세트와 테스트 세트로 나누었고, 랜덤 시드(radom_state)는 42로 지정하였다.

train_test_split() 함수는 전달되는 리스트나 배열을 비율에 맞게 훈련 세트와 테스트 세트로 나눠주며, 자체적으로 랜덤 시드를 지정할 수 있는 random_state 매개변수가 있다.

 

 

3. 사이킷런 변환기로 특성 변환하기

사이킷런은 특성을 만들거나 전처리하기 위한 다양한 클래스를 제공한다. 사이킷런에서 이런 클래스를 변환기라고 한다.

 

 

먼저 훈련 세트의 특성을 변환해 보자.

 

from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(include_bias=False)  # 객체 생성
poly.fit(train_input)  # 새롭게 만들 특성 조합 찾기
train_poly = poly.transform(train_input)  # 훈련 세트의 데이터 변환
print(train_poly.shape)  # 출력: (42, 9)

 

PolynomialFeatures 클래스의 객체로 poly를 생성하였고, train_input을 변환한 데이터를 train_poly에 저장하였다.

배열의 크기 (42, 9)가 출력되었으므로 9개의 특성이 만들어졌다는 것이다.

PolynomialFeatures 클래스는 sklearn.preprocessing 패키지에 포함되어 있다.

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

변환기는 입력 데이터를 변환하는 데 타깃 데이터가 필요하지 않으므로 fit() 메서드에는 입력 데이터만 전달해도 된다.

 

 

테스트 세트의 특성을 변환해 보자.

 

test_poly = poly.transform(test_input)

 

 

4. 다중 회귀 모델 훈련 및 평가하기

다중 회귀는 여러 개의 특성을 사용한 선형 회귀이다. 즉 다중 회귀 모델을 훈련하는 것은 선형 회귀 모델을 훈련하는 것과 같은 의미이다.

 

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.9714559911594134

 

농어의 길이뿐만이 아니라 높이와 두께를 모두 사용했더니 훈련 세트의 점수가 아주 높게 나왔다.

테스트 세트의 점수는 아주 높아지진 않았지만 농어의 길이만 사용했을 때의 과소적합 문제는 더 이상 나타나지 않는다.

따라서 특성이 늘어나면 선형 회귀의 능력이 강해진다는 것을 알 수 있다.

 

 

특성을 더 추가해 보자.

 

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)

 

PolynomialFeatures 클래스의 degree 매개변수에 필요한 고차항의 최대 차수를 지정할 수 있다.

degree 매개변수에 5를 지정하여 5 제곱까지의 특성을 만들었더니 (42, 55)이 출력되어 총 55개의 특성이 만들어졌다.

train_poly 배열의 열의 개수가 특성의 개수이다.

 

 

특성을 추가한 데이터로 선형 회귀 모델을 훈련해 보자.

 

lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))  # 훈련 세트 점수 출력: 0.9999999999991097
print(lr.score(test_poly, test_target))  # 테스트 세트 점수 출력: -144.40579242684848

 

훈련 세트의 점수는 아주 좋게 나왔지만 테스트 세트의 점수는 음수가 나왔다. 너무 과대적합되었다...

 

 

5. 규제

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

선형 회귀 모델에 규제를 적용할 때 계수 값이 서로 많이 다르면 공정하게 제어되지 않기 때문에 규제를 적용하기 전에 스케일을 정규화해야 한다.

 

 

train_scaled와 test_scaled를 표준점수로 변환하여 정규화해 보자.

 

from sklearn.preprocessing import StandardScaler  
ss = StandardScaler()  # 객체 생성
ss.fit(train_poly)  # 훈련
train_scaled = ss.transform(train_poly)  # 훈련 세트 변환
test_scaled = ss.transform(test_poly)  # 태스트 세트 변환

 

평균과 표준편차를 직접 구해 특성을 표준점수로 바꾸지 않고 사이킷런 변환기 중 하나인  StandardScaler 클래스를 사용하였다.

StandardScaler 클래스의 객체 ss를 초기화한 후 PolynomialFeatures 클래스로 만든 train_poly를 사용해 ss 객체를 훈련하였고, 변환기로 훈련 세트와 테스트 세트를 변환하였다.

이제 표준 점수로 변환한 train_scaled와 test_scaled 준비가 끝났다.

 

 

6. 릿지 회귀

선형 회귀 모델에 규제를 추가한 모델을 릿지와 라쏘라고 하며, 두 알고리즘 모두 사이킷런에서 제공한다.

릿지는 계수를 제곱한 값을 기준으로 규제를 적용하고, 라쏘는 계수의 절댓값을 기준으로 규제를 적용한다.

두 알고리즘 모두 계수의 크기를 줄이지만 라쏘는 아예 0으로 만들 수도 있다.

 

 

릿지 모델을 훈련해 보자.

 

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.9790693977615391

 

훈련 세트의 점수는 조금 낮아졌지만 테스트 세트의 점수가 음수로 나왔던 것이 해결되었다!

많은 특성을 사용했음에도 훈련 세트에 너무 과대적합되지 않아 테스트 세트에서도 좋은 성능을 내고 있다.

 

 

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

모델 객체를 만들 때 alpha 매개변수로 규제의 강도를 조절하면 된다. 적절한 alpha 값을 찾는 방법 중 하나는 alpha 값에 대한 R^2 값의 그래프를 그려본 후 훈련 세트와 테스트 세트의 점수가 가장 가까운 지점이 최적의 apha 값이 된다.

alpha 값처럼 머신러닝 모델이 학습할 수 없고 사람이 알려줘야 하는 파라미터를 하이퍼파라미터라고 한다.

먼저 alpha 값을 바꿀 때마다 점수를 저장할 리스트를 만들어보자.

 

import matplotlib.pyplot as plt

train_score = []  # alpha 값을 바꿀 때마다 score() 메서드의 결과를 저장할 리스트  생성
test_score = []  # alpha 값을 바꿀 때마다 score() 메서드의 결과를 저장할 리스트  생성

 

 

맷플롯립을 임포트 한 후 alpha 값을 바꿀 때마다 score() 메서드의 결과를 저장할 리스트를 만들었다.

 

 

다음으로 alpha 값을 늘려가며 릿지 회귀 모델을 훈련하고 점수를 저장해 보자.

 

alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]  # alpha 값을 0.001에서 100까지 10배씩 늘려가기
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에서 100까지 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()

 

 

alpha 값을 0.001부터 100까지 10배씩 늘렸기 때문에 그대로 그래프를 그리면 왼쪽이 너무 촘촘해진다. 따라서 alpha_list에 있는 6개의 값을 동일한 간격으로 나타내기 위해 로그 함수로 바꾸어 지수로 표현하였다. 즉 0.001은 -3, 0.01은 -2이다.

파란색은 훈련 세트 그래프, 주황색은 테스트 세트 그래프이다.

그래프의 왼쪽을 보면 훈련 세트와 테스트 세트의 점수 차이가 아주 크다. 훈련 세트에는 잘 맞고 테스트 세트에는 형편없는 과대적합의 전형적인 모습이다. 반대로 오른쪽은 훈련 세트와 테스트 세트의 점수가 모두 낮아지는 과소적합의 모습을 하고 있다.

적절한 alpha 값은 두 그래프가 가장 가깝고 테스트 세트의 점수가 가장 높은 -1이다. 즉 10^-1 = 0.1이다.

 

 

alpha 값을 0.1로 하여 최종 모델을 훈련해 보자.

 

ridge = Ridge(alpha=0.1)  # alpha 값 0.1
ridge.fit(train_scaled, train_target)  # 훈련
print(ridge.score(train_scaled, train_target))  # 훈련 세트 점수 출력: 0.9903815817570365
print(ridge.score(test_scaled, test_target))  # 테스트 세트 점수 출력: 0.9827976465386884

 

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

 

 

7. 라쏘 회귀

이번에는 라쏘 모델을 훈련해 보자.

 

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 = []  # alpha 값을 바꿀 때마다 score() 메서드의 결과를 저장할 리스트  생성
test_score = []  #alpha 값을 바꿀 때마다 score() 메서드의 결과를 저장할 리스트 생성
alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]  # alpha 값을 0.001에서 100까지 10배씩 늘려가기
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))  # 테스트 세트 점수 저장

 

/usr/local/lib/python3.8/dist-packages/sklearn/linear_model/_coordinate_descent.py:647: 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: 1.878e+04, tolerance: 5.183e+02 model = cd_fast.enet_coordinate_descent( /usr/local/lib/python3.8/dist-packages/sklearn/linear_model/_coordinate_descent.py:647: 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: 1.297e+04, tolerance: 5.183e+02 model = cd_fast.enet_coordinate_descent(

 

ConvergenceWarning이라는 경고가 출력되었다.

사이킷런의 라쏘 모델은 최적의 계수를 찾기 위해 반복적인 계산을 수행하는데, 지정한 반복 횟수가 부족할 때 이런 경고가 발생한다. 이 반복 횟수를 충분히 늘리기 위해 max=iter 매개변수의 값을 10000으로 지정하였다.

 

 

이제 그래프를 그려보자.

 

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 = 10이다.

 

 

alpha 값을 10으로 하여 최종 모델을 훈련해 보자.

 

lasso = Lasso(alpha=10)  # 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

 

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

 

 

* 용어 정리

1. 판다스(pandas): 데이터 분석 라이브러리

2. 데이터프레임: 판다스의 핵심 데이터 구조

3. read_csv(): csv 파일을 인터넷에서 읽어 판다스 데이터프레임으로 변환하는 함수

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

5. 특성 공학(feature engineering): 특성을 조합하여 새로운 특성을 만드는 작업

6. 릿지: 규제가 있는 선형 회귀 모델

7. 라쏘: 규제가 있는 선형 회귀 모델

8. 하이퍼파라미터(hyperparameter): 머신러닝 알고리즘이 학습하지 않고 사람이 지정해야 하는 파라미터

 

 

 

 

 

728x90
LIST

댓글