Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

cb

[혼공머신] Chapter 03 - 회귀 알고리즘과 모델 규제 본문

ai - study

[혼공머신] Chapter 03 - 회귀 알고리즘과 모델 규제

10011001101 2024. 2. 5. 15:39

본 게시물은 <혼자서 공부하는 머신러닝+딥러닝>의 Chapter 03: 회귀 알고리즘과 모델 규제를 보고 정리한 글입니다. 원본 코드는 책의 저자인 박해선님의 깃허브 코드를 참고하시길 바랍니다.

 

GitHub - rickiepark/hg-mldl: <혼자 공부하는 머신러닝+딥러닝>의 코드 저장소입니다.

<혼자 공부하는 머신러닝+딥러닝>의 코드 저장소입니다. Contribute to rickiepark/hg-mldl development by creating an account on GitHub.

github.com


분류와 회귀

지도 학습 알고리즘은 크게 분류(classification)회귀(regression)으로 나뉜다. 분류는 말 그대로 샘플을 몇 개의 클래스 중 하나로 분류하는 문제이다. 이와 달리 회귀는 클래스 중 하나로 분류하는 것이 아니라, 임의의 어떤 숫자를 예측하는 문제이다. (두 변수 사이의 상관관계를 분석하는 방법을 회귀라 부른다.)

 

지난 챕터에서는 k-최근접 알고리즘을 분류에서 사용했었는데, 이는 회귀에서도 작동한다.  k-최근접 이웃 회귀의 과정은 k-최근접 이웃 분류 알고리즘 과정과 비슷하다. 회귀에서도 동일하게 예측하려는 샘플에 가장 가까운 k개의 샘플을 선택하는데, 이때 샘플의 타겟은 클래스가 아닌 임의의 수치를 가진다. 선택된 k개의 샘플의 타겟 수치들의 평균을 구하면 예측값은 특정 수치가 될 것이고, 이가 곧 샘플의 예측 타겟값이 된다.


k-최근접 이웃 회귀

농어의 데이터를 활용하여 회귀 문제를 풀어보자. 아래의 데이터는 농어의 길이 값을 input으로 받아 무게 값을 target 값으로 반환한다.

import numpy as np

perch_length = np.array(
    [8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0,
     21.0, 21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5,
     22.5, 22.7, 23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5,
     27.3, 27.5, 27.5, 27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0,
     36.5, 36.0, 37.0, 37.0, 39.0, 39.0, 39.0, 40.0, 40.0, 40.0,
     40.0, 42.0, 43.0, 43.0, 43.5, 44.0]
     )
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]
     )

 

이는 간단히 복사+붙여넣기만 해 주면 된다. 

 

데이터가 어떤 형태를 띠고 있는지 알아보기 위해, 산점도로 표현해 볼 것이다. x축은 길이, y축은 무게로 설정하고 scatter() 함수를 통해 그래프를 그릴 수 있다.

import matplotlib.pyplot as plt

plt.scatter(perch_length, perch_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

 

위의 코드를 모두 실행시키면, 아래와 같은 산점도를 확인할 수 있다. 이는 농어의 길이가 늘어남에 따라 무게 또한 늘어나는 것을 확인할 수 있다.

 

이제, 저번 챕터에서 배운 것과 같이 데이터를 훈련 세트와 테스트 세트로 나눠 준다. 사이킷런에서 사용할 훈련 세트는 2차원 배열이어야 하므로 reshape() 메소드를 통해 배열의 크기까지 변환해 준다.

# 라이브러리 임포트
from sklearn.model_selection import train_test_split

# 데이터셋 분할
train_input, test_input, train_target, test_target = train_test_split(
    perch_length, perch_weight, random_state=42)
    
# 배열의 크기 확인
print(train_input.shape, test_input.shape)

# 크기 변환
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)

 

reshape(-1, 1)과 같이 사용하면 배열의 전체 원소 개수를 매번 외우지 않아도 되기 떄문에 편리하다.

 

k-최근접 이웃 회귀 알고리즘은 KNeighborsRegressor를 통해 쉽게 구현이 가능하다. 이 클래스는 KNeighborsClassifier와 사용법이 매우 비슷하기 때문에 아래 코드를 따라 실행해 보면 쉽게 이해할 수 있을 것이다.

# 라이브러리 임포트
from sklearn.neighbors import KNeighborsRegressor

# 객체 생성
knr = KNeighborsRegressor()

# 모델 훈련
knr.fit(train_input, train_target)

# 모델 평가
knr.score(test_input, test_target)

 

위의 코드를 똑같이 실행하면 최종적으로 0.9928094061010639라는 정확도가 나온다. 

 

분류의 경우에는 테스트 세트에 있는 샘플 중 정답을 맞힌 개수의 비율을 정확도로 가졌다. 그러나 회귀에서는 정확한 숫자를 맞히기 거의 불가능하기 때문에 정답을 맞힌 개수를 정확도로 갖기에는 한계가 존재한다.


 

결정계수(R^2)

따라서 회귀의 경우에는 결정계수(coefficient of determination)을 통해 정확도를 평가한다. 결정계수는 간단히 R^2이라고도 부른다. 

 

결정계수는 다음과 같은 식으로 계산된다.

$$ R^2 = 1 - (타깃-예측)^2/(타깃-평균)^2 $$

 

사이킷런에서는 이외에도 다양한 측정 도구를 제공한다. sklearn.metrics 패키지의 mean_absolute_error는 타깃와 예측값 사이의 절댓값 오차를 평균하여 반환해 준다. 구현 코드는 아래와 같다.

# 라이브러리 임포트
from sklearn.metrics import mean_absolute_error

# 테스트 세트에 대한 예측
test_prediction = knr.predict(test_input)

# 절댓값 오차 계산
mae = mean_absolute_error(test_target, test_prediction)
print(mae)

 

이 코드를 실행하면 19.157142857142862라는 값을 얻을 수 있을 것이다. 이는 결과에서 예측이 평균적으로 19g정도 타깃과 다르다는 것을 의미한다.

 


과대적합 vs 과소적합

우리는 이때까지 훈련 세트를 통해 모델을 훈련시키고, 테스트 세트를 통해 모델을 평가하였다. 만약 여기서, 훈련 세트를 통해 모델을 평가한다면 R^2은 어떤 값을 가질까?

print(knr.score(train_input, train_target))

 

 

이를 실행하면 0.969882328099255라는 값을 얻을 수 있다. 앞서 테스트 세트에 대해 모델을 평가했을 때에는 0.9928094061010639라는 정확도를 가진 것을 확인할 수 있다. 이는 과연 어떤 의미인지 살펴보자.

 

모델을 훈련 세트에 기반하여 훈련시키면 훈련 세트에 잘 맞는 모델이 만들어진다. 그렇기 때문에 훈련 세트를 통해 모델을 평가한다면, 테스트 세트로 모델을 평가한 것보다 점수가 조금 더 높게 나온다. 

 

이 과정에서, 훈련 세트에서 점수가 굉장히 좋았는데 테스트 세트에 대해서는 터무니 없이 낮은 점수를 보이는 경우를 훈련 세트에 대한 과대적합(overfitting)이라고 한다. 훈련 세트에 대해서만 아주 잘 예측하고, 새로운 샘플이 들어왔을 때는 예측을 제대로 하지 못한다고 볼 수 있다.

 

반대로 훈련 세트보다 테스트 세트에 대해 점수가 높거나 두 점수가 모두 낮은 경우에는 모델이 훈련 세트에 과소적합(underfitting)되었다고 말한다. 모델이 너무 단순해서 훈련 세트에 대해 적절히 훈련되지 않았다고 볼 수 있다.

 

그렇다면 앞서 테스트 세트보다 훈련 세트에 대해 모델 평가 점수가 더 낮게 나왔다는 것은, 모델이 훈련 세트에 대해 과소적합되었다는 것으로 이해할 수 있다.

 

이는 모델이 너무 단순해서 적절히 훈련되지 않은 문제이므로, 모델을 조금 더 복잡하게 만들면 과소적합 문제를 해결할 수 있다. k-최근접 이웃 알고리즘에서 모델을 더 복잡하게 만드는 방법은 이웃의 개수인 k를 줄여 훈련 세트의 국지적인 패턴에 민감해지게 만드는 방법이 있다.

 

앞서 k-최근접 이웃 알고리즘의 기본 k 값이 5라고 했었으므로, 이를 3으로 낮춰보자.

# 이웃의 갯수를 3으로 설정
knr.n_neighbors = 3

# 모델을 다시 훈련
knr.fit(train_input, train_target)

# 훈련 세트에 대해 모델 평가
print(knr.score(train_input, train_target))

# 테스트 세트에 대해 모델 평가
print(knr.score(test_input, test_target))

 

이를 실행시키면 훈련 세트에 대해서는 약 0.98의 정확도를, 테스트 세트에 대해서는 약 0.97의 정확도를 반환한다.

 

두 점수의 차이가 크지 않으므로 모델은 과대적합되지도, 테스트 세트의 점수가 훈련 세트의 점수보다 낮으므로 과소적합되지도 않은 것을 확인할 수 있다.


선형 회귀

선형 회귀(linear regression)는 널리 사용되는 대표적인 회귀 알고리즘이다. 이는 데이터 샘플들의 경향을 가장 잘 나타내는 선을 찾아내는 머신러닝 알고리즘이다.

 

위의 사진에서 1, 2, 3번 직선 중 어떤 직선이 가장 그래프의 경향성을 잘 나타낼까? 이는 어렵지 않게 3번이라고 답할 수 있다. 3번과 같은 직선을 찾아내는 과정을 선형 회귀라 부르고, 사이킷런의 LinearRegression 클래스를 통해 간단히 구현 가능하다.

 

사이킷런의 모델 클래스들은 훈련, 평가, 예측 메소드 이름이 모두 동일하기 때문에 아래와 같이 실행시켜 줄 수 있다.

# 라이브러리 임포트
from sklearn.linear_model import LinearRegression

# 객체 생성
lr = LinearRegression()

# 선형 회귀 모델 훈련
lr.fit(train_input, train_target)

# 50cm 농어에 대한 예측
print(lr.predict([[50]]))

 

이를 실행시키면 [1241.83860323]이라는 값이 출력된다. LinearRegression 클래스가 찾은 직선은 y=ax+b의 형태를 가지는데, 농어 데이터에 가장 잘 맞는 a, b에 따라 반환되는 값이 1241.83860323이기 때문이다. LinearRegression 클래스가 찾은 a와 b는 아래의 속성을 통해 확인할 수 있다.

print(lr.coef_, lr.intercept_)

 

 

이제, 아래의 코드를 사용하여 농어의 길이 15에서 50까지를 직선으로 그려보자.

# 훈련 세트의 산점도를 그린다
plt.scatter(train_input, train_target)

# 15에서 50까지 1차 방정식 그래프를 그린다
plt.plot([15, 50], [15*lr.coef_+lr.intercept_, 50*lr.coef_+lr.intercept_])

# 50cm 농어 데이터
plt.scatter(50, 1241.8, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

 

LinearRegression이 찾은 직선은 다음과 같이 표현될 수 있고, 이 직선을 통해 훈련 세트의 범위를 벗어난 농어의 무게도 예측할 수 있을 것이다. 하지만 그래프의 왼쪽 아래를 보면 이상한 점을 발견할 수 있을 것이다.


다항 회귀

LinearRegression을 통해 찾아낸 직선대로 예측을 실시하면, 농어의 무게가 음수가 되는 일이 발생한다. 농어의 산점도를 엄밀히 말하자면 이는 일직선이 아니라, 왼쪽 위로 조금 구부러진 곡선이기 때문에 직선으로는 산점도의 경향을 완벽히 담아내기 어렵다.

위와 같은 2차 방정식의 그래프를 그리려면 길이를 제곱한 항이 훈련 세트에 추가되어야 한다. 이는 저번 챕터에서 사용했던 column_stack() 함수를 사용해 간단히 추가할 수 있다.

train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))

 

이는 train_input, test_input에 있는 모든 원소를 제곱하여 새로운 특성을 만들어 준 것과 같다. 원래 특성인 길이를 제곱하여 왼쪽 열에 추가했기 때문에  훈련 세트와 테스트 세트 모두 열이 2개로 늘어났다는 것을 알 수 있다.

 

이제 새롭게 만들어진 데이터 train_poly를 사용해 선형 회귀 모델을 다시 훈련하면, 이 모델은 2차 방정식의 매개변수인 a, b, c를 잘 찾아낼 수 있을 것이다.

# 객체 생성
lr = LinearRegression()

# train_poly에 대해 훈련
lr.fit(train_poly, train_target)

# 새로운 데이터 예측
print(lr.predict([[50**2, 50]]))

# a, b, c 확인
print(lr.coef_, lr.intercept_)

 

위의 코드를 실행하면 아래와 같은 값이 반환되는 것을 볼 수 있을 것이다.

[  1.01433211 -21.55792498] 116.0502107827827

 

그 말은 즉, 이 모델이 다음와 같은 그래프를 학습했다는 것이고, 무게를 길이 제곱과 길이의 선형 관계로 표현할 수 있다는 것을 의미한다.

$$ 무게 = 1.01 * 길이^2 - 21.6 * 길이 + 116.05 $$

 

이를 산점도를 표현해 보자.

# 구간별 직선을 그리기 위해 15에서 49까지 정수 배열을 만든다
point = np.arange(15, 50)

# 훈련 세트의 산점도를 그린다
plt.scatter(train_input, train_target)

# 15에서 49까지 2차 방정식 그래프를 그린다
plt.plot(point, 1.01*point**2 - 21.6*point + 116.05)

# 50cm 농어 데이터
plt.scatter([50], [1574], marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

 

 

이 모델은 직선을 사용한 단순 선형 회귀 모델모다 데이터를 더 잘 표현하는 그래프를 그린다. lr.score() 함수를 이용하여 확인해 보면, 훈련 세트와 테스트 세트의 성능도 단순 선형 회귀보다 나은 성능을 보이는 것을 알 수 있다.


특성 공학과 규제

여러 개의 특성을 사용한 선형 회귀를 다중 회귀(multiple regression)라고 부른다. 1개의 특성을 사용할 때 선형 회귀 모델은 직선을 학습하고, 2개의 특성을 사용하면 선형 회귀는 평면을 학습한다.

우리는 3차원 이상의 공간을 그리거나 상상할 수 없지만, 선형 회귀 모델은 특성이 많은 고차원에서도 매우 복잡한 모델을 표현할 수 있다. 우리는 특성을 제곱하여 추가했던 것처럼, 각 특성을 서로 곱해서 또 다른 특성을 만드는 것과 같이 데이터를 생성해낼 수 있다. 이처럼 기존의 특성을 사용해 새로운 특성을 뽑아내는 작업을 톡성 공학(feature engineering)이라고 한다.

 

데이터 분석 라이브러리인 판다스(pandas)는 인터넷에서 바로 데이터를 다운로드하여 사용하기 편리한 기능들을 제공한다. 데이터프레임(dataframe)은 판다스의 핵심 데이터 구조로, 다차원 배열을 다루면서 넘파이보다 훨씬 더 많은 기능을 제공해 준다.

 

https:/bit.ly/ perch_csv_data 에 접속하면, 농어 데이터의 전체 파일을 볼 수 있다. 이는 콤마로 나누어진 텍스트 파일, CSV 파일의 형태를 가진다. 따라서 우리는 판다스의 read_csv() 함수에 주소를 넣어 데이터프레임을 만들어 준 다음, to_numpy() 메서드를 활용해 넘파이 배열로 바꿔줄 것이다.

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)

 

 

사이킷런에서는 특성을 만들거나 전처리하기 위해 다양한 클래스를 제공한다. 이는 변환기(transformer)라고 불리는데, 우리는 여기서 PolynomialFeatures 클래스를 사용하여 특성 공학을 시행해 줄 것이다.

poly = PolynomialFeatures(include_bias=False)

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

 

fit() 함수를 통해 변환기를 훈련시켜 주고, transform() 함수를 통해 변환을 시행한다. 이는 사이킷런의 일관된 api 때문에 두 단계로 나누어져 있지만, fit_transform 메소드를 통해 한 번에 적용할 수도 있다.

 

아래의 함수를 통해서는 특성들이 각자 어떤 입력의 조합으로 만들어졌는지도 알 수 있다.

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

 

여기서 특성을 더 많이 추가하기 위해서는 PolynomialFeatures의 degree 매개변수를 사용하여 고차항의 최대 차수를 지정해 줄 수 있다.


규제

그러나, 특성의 개수를 늘리면 선형 모델은 아주 강력해져 훈련 세트에 대해 과대적합될 수 있다는 문제를 가진다. 그렇기 때문에 우리는 머신러닝 모델이 훈련 세트를 너무 과도하게 학습하지 못하도록 규제(regularization)을 해 줄 필요가 있다. 선형 회귀 모델의 경우에는 특성에 곱해지는 계수의 크기를 작게 만드는 방법이 해당될 수 있다.

 

일반적으로 선형 회귀 모델에는 규제를 적용할 때, 계수 값의 크기가 다르면 공정하게 제어되지 않을 것이다. 그렇기 때문에 먼저 정규화를 해 줄 필요가 있다. 이번에는 사이킷런의 StandardScaler 클래스를 사용하여 데이터를 변환해 주자.

# 라이브러리 임포트
from sklearn.preprocessing import StandardScaler

# StandardScaler 객체 생성
ss = StandardScaler()

# 객체 훈련
ss.fit(train_poly)

# 표준 점수로 변환
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)

릿지 회귀

선형 회귀 모델에는 여러 방법으로 규제를 추가할 수 있는데, 먼저 릿지(Ridge) 규제에 대해 알아보자. 릿지는 계수를 제곱한 값을 기준으로 선형 회귀 모델에 규제를 적용한다. 이는 sklearn.linear_model 패키지 안에 구현되어 있기 때문에 손쉽게 사용이 가능하다.

 

이 또한 마찬가지로 사이킷런에서 제공하는 기능이므로, fit() 메소드를 사용하여 훈련해 줄 수 있다.

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

 

마지막 평가 함수까지 출력해 보면, 많은 특성을 사용했음에도 불구하고 훈련 세트에 과대적합되지 않아 테스트 세트에 대해서도 좋은 성능을 낸다는 것을 확인할 수 있다.

 

모델 객체를 만들 때 alpha 매개변수로 규제의 강도를 조절할 수도 있다. 적절한 alpha 값을 찾기 위해서는, 여러 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))
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의 값이 최적이 된다.

 

이렇게 구한 alpha 값을 토대로 최종 모델을 훈련해 주면, 균형적인 선형 회귀 모델을 만들 수 있을 것이다. 아래는 예시 코드이다.

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

라쏘 회귀

라쏘(Lasso) 회귀는 또 다른 규제 모델로, 계수의 절댓값을 기준으로 규제를 적용하는 방법이다. 일반적으로 라쏘 회귀보다는 릿지 회귀를 조금 더 선호하긴 한다. 두 알고리즘 모두 계수의 크기를 줄이는데, 라쏘는 아예 0으로 만들 수도 있다는 특징이 있다.

 

릿지 회귀와 마찬가지로 아래의 코드를 통해 모델을 훈련시켜 보자.

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

 

가장 적절한 alpha 값을 찾기 위해 위의 코드를 실행해 보면, 1에서 alpha의 값이 최적인 것을 알 수 있다. (alpha 값은 그래프를 통해 본인이 정하기 나름이다.)

 

마지막으로 아래의 코드를 실행해 보면, 모델이 잘 훈련된 것을 알아볼 수 있다.

lasso = Lasso(alpha=10)
lasso.fit(train_scaled, train_target)

print(lasso.score(train_scaled, train_target))
print(lasso.score(test_scaled, test_target))

 


마무리

이번 챕터에서는 머신러닝의 회귀 문제에 대해 다뤄 보면서 k-최근접 이웃 회귀 모델을 만들어 보았다. 그 과정에서 훈련 세트의 범위 밖에 있는 샘플까지 예측하기 위해 선형 회귀 문제를 풀어 보았고, 훈련 세트에 모델이 과대 적합되어 있는 문제를 해결하기 위해 릿지, 라쏘와 같은 여러 규제 방법을 적용해 보았다.

 

과대적합/규제와 같은 문제는 실제 머신러닝 프로젝트 과정에서 흔하게 보이는 문제이며, 앞으로도 활용되는 부분이 매우매우 많으므로 추가 공부를 위해 확실히 개념을 알아 두는 것이 중요하겠다.