본문 바로가기
TIL - 외/빅데이터

[머신러닝] 회귀 알고리즘 및 실습

by chaemj97 2023. 4. 13.
728x90
  • 회귀 : 임의의 수치를 예측하는 문제, 타깃값도 임의의 수치

 

K-최근접 이웃 회귀

  • k-최근접 이웃 알고리즘을 사용해 회귀 문제를 푼다. 가장 가까운 이웃 샘플을 찾고 이 샘플들의 타깃값을 평균하여 예측으로 삼는다.
    • 새로운 샘플이 훈련 세트의 범위를 벗어나면 엉뚱한 값을 예측할 수 있다.
  • kneighbors() 메서드를 사용하면 가장 가까운 이웃까지의 거리와 이웃 샘플의 인덱스를 얻을 수 있다.

결정 계수

  • 대표적인 회귀 문제의 성능 측정 도구, 1에 가까울수록 좋고, 0에 가깝다면 성능이 나쁜 모델
  • 타깃의 평균 정도를 예측하는 수준이라면 0에 가까워지고, 예측이 타깃에 아주 가까워지면 1에 가까운 값이 된다.

선형회귀 (Linear regression)

  • 특성과 타깃 사이의 관계를 가장 잘 나타내는 선형 방정식을 찾는다. 특성이 하나면 직선 방정식
  • 선형 회귀가 찾은 특성과 타깃 사이의 관계는 선형 방정식의 계수 또는 가중치에 저장
    • 객체.coef_, 객체.intercept_

 

과대적합 (Overfitting)

  • 모델의 훈련 세트 성능이 테스트 세트 성능보다 훨씬 높을 때 일어난다.
  • 모델이 훈련 세트에 너무 집착해서 데이터에 내재된 거시적인 패턴을 감지하지 못한다

 

과소적합 (Underfiting)

  • 훈련 세트와 테스트 세트 성능이 모두 동일하게 낮거나 테스트 세트 성능이 오히려 더 높을 때 일어난다.
  • 모델이 너무 단순하여 훈련 세트에 적절히 훈련되지 않은 경우
  • 더 복잡한 모델을 사용해 훈련 세트에 잘 맞는 모델을 만들어야 한다.

 

💡 머신러닝 모델은 주기적으로 훈련해야 한다.
시간과 환경이 변화하면서 데이터도 바뀌기 때문에 주기적으로 새로운 훈련 데이터로 모델을 다시 훈련해야 한다.

K-최근접 이웃 회귀 실습

  • 농어 데이터 56개 (길이, 무게)
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]
     )
  • 데이터 분포 확인
import matplotlib.pyplot as plt

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

농어의 길이가 커짐에 따라 무게도 늘어난다.

  • 훈련,테스트 세트 나누기
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) # (42,) (14,)

# 사이킷런에 사용할 훈련 세트는 2차원 배열이어야 한다.
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)
print(train_input.shape, test_input.shape) # (42, 1) (14, 1)
  • 결정 계수 구하기
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor()
# k-최근접 이웃 회귀 모델을 훈련합니다
knr.fit(train_input, train_target)

# 테스트 세트에 대한 결정 계수
knr.score(test_input, test_target) # 0.9928

# 훈련 세트에 대한 결정 계수
knr.score(train_input, train_target) # 0.9699

과소 적합되었다 -> 모델을 조금 더 복잡하게 만들기 -> k의 개수 줄이기

# 이웃의 갯수를 3으로 설정합니다
knr.n_neighbors = 3
# 모델을 다시 훈련합니다
knr.fit(train_input, train_target)

# 학습 데이터에 대한 결정 계수
print(knr.score(train_input, train_target)) # 0.9805

# 테스트 데이터에 대한 결정 계수
print(knr.score(test_input, test_target)) # 0.9746

두개 비슷해졌다.

  • mean absolute error(MAE)
from sklearn.metrics import mean_absolute_error

# 테스트 세트에 대한 예측을 만듭니다
test_prediction = knr.predict(test_input)
# 테스트 세트에 대한 평균 절댓값 오차를 계산합니다
mae = mean_absolute_error(test_target, test_prediction)
print(mae) 
# k가 5일 때 : 19.16 -> 예측이 평균적으로 19g 정도 타깃값과 다르다
# k가 3일 때 : 35.42

선형회귀 (Linear regression) 실습

from sklearn.linear_model import LinearRegression

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

# 50cm 농어에 대한 예측
print(lr.predict([[50]])) # [1241.84] / 정답은 1500

예측과 정답의 차이가 크다.

  • 모델 파라미터 + 모델 산점도 + 회귀선
print(lr.coef_, lr.intercept_)
# [39.01714496] -709.0186449535477

# 훈련 세트의 산점도를 그립니다
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()

예측하고 싶은 데이터는 선형직선 위에 있다.

무게가 0 이하로 내려갈 수도 있다.

  • 결정 계수 확인
print(lr.score(train_input, train_target)) # 0.94
print(lr.score(test_input, test_target)) # 0.82

수치 둘다 작아서 과소 적합

 

다항 회귀

# 2차방정식을 그리기 위해 제곱한 항 추가
train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))

lr = LinearRegression()
lr.fit(train_poly, train_target)

# 예측
print(lr.predict([[50**2, 50]])) # [1573.98423528]

이전보다 예측이 더 잘 맞다

  • 모델 산점도 + 회귀선
# 구간별 직선을 그리기 위해 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()

단순 선형 회귀보다 더 나은 그래프다.

무게가 음수가 나올 수 없다.

  • 결정 계수
print(lr.score(train_poly, train_target)) # 0.97
print(lr.score(test_poly, test_target)) # 0.98

둘 다 상승이지만 과소적합이 조금 있는거 같다.


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

새로운 특성 만들기 - 변환기

  • fit을 해야 transform 가능 
  • fit() 메서드
    • 새롭게 만들 특성 조합 찾기
    • 입력 데이터만 전달
  • transform() 메서드
    • 실제로 데이터를 변환
from sklearn.preprocessing import PolynomialFeatures

# 연습
# degree는 최고 차수를 지정, 기본값 2
poly = PolynomialFeatures(degree=2)
poly.fit([[2, 3]])
print(poly.transform([[2, 3]])) # [[1. 2. 3. 4. 6. 9.]]

# 절편을 위한 항 제거
poly = PolynomialFeatures(include_bias=False)
poly.fit([[2, 3]])
print(poly.transform([[2, 3]])) # [[2. 3. 4. 6. 9.]]
# include_bias=False 없어도 사이킷런 모델은 자동으로 특성에 추가된 절편 항 무시
  • 변환
poly = PolynomialFeatures(include_bias=False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
print(train_poly.shape) # (42, 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.99
# 테스트 세트
print(lr.score(test_poly, test_target)) # 0.97

농어의 길이만 사용했을 때 나타났던 과소적합 문제 해결


규제

  • 모델이 훈련 세트에 과대적합되지 않도록 만드는 것
  • 선형 회귀 모델의 경우 특성에 곱해지는 계수의 크기를 작게 만드는 것
  • 규제 적용하기 전에 먼저 정규화
    • StandardScaler 클래스
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)) # 0.99
print(ridge.score(test_scaled, test_target)) #0.98
  • 규제의 양 임의로 조절
    • 모델 객체를 만들 때 alpha 매개변수로 규제의 강도 조절 가능
    • alpha 값이 크면 규제 강도가 세지므로 계수 값을 더 줄이고 조금 더 과소적합되도록 유도
    • alpha 기본값 1
import matplotlib.pyplot as plt

train_score = []
test_score = []

# alpha의 값을 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))
    
# 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 -> alpha = 0.1

 

라쏘 회귀

  • 계수의 절댓값을 기준으로 규제를 적용
  • 계수 값을 아예 0으로 만들 수 있다
    • 계수 값 : 객체.coef_
    • 유용한 특성을 골라내는 용도로 사용할 수 있다. == (객체.coef_ !=0) 특성들
from sklearn.linear_model import Lasso

lasso = Lasso()
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target)) # 0.99
print(lasso.score(test_scaled, test_target)) # 0.98
  • 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))
    
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 = 10

728x90
반응형

댓글