혼자 공부하는 머신러닝 + 딥러닝

혼자 공부하는 머신러닝 + 딥러닝 Day 6

루바의 여정 2022. 9. 8. 00:43

지난시간...

지난시간에는 선형회귀에 대해서 알아보았다. 회귀는 훈련 데이터 범위 밖의 데이터도 예측을 할 수 있다. 하면서 샬라샬라

2차항까지 넣어보았는데 과소적합이 약간 남아있었다.

 

이번 시간에는 여러 개의 특성을 사용한 선형 회귀인 다중 회귀를 사용하여 모델을 훈련해보자.

1개의 특성을 사용할 때 선형 회귀 모델은 직선을 학습했다. 2개의 특성을 사용하면 선형 회귀 모델은 평면을 학습한다.

특성이 2개이면 타깃값과 함께 3차원을 형성한다. 선형 회귀 방정식인 아래와 같다.

$$ 타깃 = a \times 특성_1 + b \times 특성_2 + 절편$$

특성이 3개 이상인 경우 우리는 3차원 공간 이상을 그리거나 상상할 수 없기

기존 특성을 사용해 새로운 특성을 뽑아내는 작업을 특성 공학이라고 한다.

데이터 준비

판다스(pandas)는 유명한 데이터 분석 라이브러리로 데이터프레임이 핵심 데이터 구조이다. 판다스를 사용해 데이터를 사용해보자.

import pandas as pd ## pd는 관례적으로 사용하는 판다스의 별칭
df = pd.read_csv('데이터가 존재하는 주소')
perch_full = df.to_numpy()

read_csv() 함수로 데이터 프레임을 만든 후 to_numpy() 메소드를 사용해서 넘파이 배열로 변환했다.

 

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(), transform() 메소드를 제공한다.

sklearn.preprocessing 패키지에 포함된 PolynomialFeatures 클래스를 사용해보자.

예를 들어 2개의 특성 2와 3으로 이루어진 샘플 하나를 적용해보자. fit(), transform() 메소드를 차례대로 호출하자.

poly = PolynomialFeatures()
poly.fit([[2,3]])
print(poly.transform([[2,3]]))

## 출력값
[[1. 2. 3. 4. 6. 9.]]

fit() 메소드는 새롭게 만들 특성 조합을 찾는다. 또한 모델 클래스와 다르게 타깃 데이터가 필요하지 않다.

transform() 메소드는 실제로 데이터를 변환한다. 출력값을 보면 6개의 특성을 가진 샘플로 변환되었다.

PolynomialFeatures 클래스는 기본적으로 각 특성을 제곱한 항과 특성끼리 서로 곱한 항을 추가한다. 1은 왜 추가되었을까?

$$ 무게 = a \times 길이 + b \times 높이 + c \times 두께 + d \times 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에 적용해보고 변환한 데이터를 train_poly에 저장하고 배열의 크기를 확인해보자.

poly = PolynomialFeatures(include_bias = False)

poly.fit(train_input)

train_poly = poly.transform(train_input)

print(train_poly.shape)

## 출력값
(42, 9)

9개의 특성이 만들어 진 것을 알 수 있다.

PolynomialFeatures 클래스는 get_feature_names() 메소드를 호출해 특성들이 어떤 입력의 조합으로 만들어졌는지 알려준다.

poly.get_feature_names()

## 출력값
['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2', 'x2^2']

이제 테스트 세트를 변환하자.

test_poly = poly.transform(test_input)

 

다중 회귀 모델 훈련하기

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

LinearRegression 클래스를 import 하고 train_poly를 사용해 모델을 훈련하고 점수를 출력해보자.

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

## 훈련 세트 출력값
0.9903183436982124
## 테스트 세트 출력값
0.9714559911594134

훈련 세트는 높은 점수가 나왔다. 특성이 늘어나면 선형 회귀의 능력은 매우 강하다는 것을 알 수 있다. 반면 테스테 세트에 대한 점수는 높아지지 않았다. 다행히 과소적합 문제는 더이상 나타나지 않았다.

 

이번에는 특성을 더 추가해서 모델을 훈련해보자.

PolynomialFeatures 클래스의 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))
print(lr.score(test_poly, test_target))

## 훈련 세트 출력값
0.9999999999991097
## 테스트 세트 출력값
-144.40579242684848

훈련 세트는 거의 완벽한 점수이지만 테스트 세트는 아주 큰 음수이다. 이러한 이유는 특성의 갯수가 많을수록 선형 모델은 아주 강력해지므로 훈련 세트에 대해 거의 완벽하게 학습할 수 있기 때문이다. 하지만 훈련 세트에 과대적합되므로 테스트 세트에서는 형편없는 점수를 만든다. 이를 해결하기 위해 다시 특성을 줄여보자.

규제

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

선형 회귀 모델에 규제를 적용할 때 계수 값의 크기가 서로 많이 다르면 공정하게 제어되지 않아 정규화를 진행한 후 규제를 적용해야한다.

사이킷런의 StandardScaler 클래스를 사용해서 정규화를 진행해보자. 이 클래스도 변환기의 하나이다.

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly)

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

이제 표준점수로 변환한 train_scaled, test_scaled가 준비되었다. mean_, scale_속성에 평균과 표준편차가 저장된다.

선형 회귀 모델에 규제를 추가한 모델을 릿지와 라쏘라고 부른다. 이제 두 모델을 알아보자.

릿지 회귀

릿지는 계수를 제곱한 값을 기준으로 규제를 적용한다. 일반적으로 라쏘보다 더 선호된다.

sklearn.linear_model 패키지 안에 존재하고 fit() 메소드로 훈련하고 score() 메소드로 평가한다. 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.9790693977615391

선형 회귀에 비해 훈련 세트 점수가 조금 낮아졌다. 테스트 세트의 점수가 다시 정상으로 돌아왔다. 모델 객체를 만들 때 alpha 매개변수로 규제의 강도를 조절할 수 있다. alpha 값이 크면 과소적합되도록 유도하고 값이 작으면 과대적합될 가능성이 크다.

적절한 alpha 값을 찾는 한 가지 방법은 alpha 값에 대한 결정계수($R^2$) 값의 그래프를 그려 보는 것이다. 훈련 세트와 테스트 세트의 점수가 가장 가까운 지점이 최적의 alpha 값이 된다.

단, alpha 값은 머신러닝 알고리즘이 학습하지 않는 파라미터 값이므로 사람이 직접 알려줘야한다. 이렇게 사람이 직접 알려줘야하는 파라미터를 하이퍼파라미터라고 한다.

import matplotlib.pyplot as plt

## score() 결과를 저장할 리스트
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))

score() 메소드의 결과를 저장할 리스트를 생성한 후 alpha 값을 늘려가면 릿지 모델을 훈련한 후 훈련 세트와 테스트 세트의 점수를 리스트에 저장한다.

이제 np.log10()을 사용해서 일정한 간격으로 그래프를 그려주자.

plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)

plt.xlabel('length')
plt.ylabel('weight')
plt.show()

파란색: 훈련 세트, 주황색: 테스트 세트

왼쪽으로 갈수록 테스트 세트보다 훈련 세트에 잘맞고 테스트 세트에는 형편 없는 과대적합을 나타낸다. 오른쪽으로 갈수록 훈련 세트와 테스트 세트 모두 점수가 낮아지는 과소적합의 모습을 보여준다. 가장 적절한 alpha값은 두 그래프가 가장 가깝고 테스트 세트의 점수가 가장 높은 -1, 즉 $10^-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.9903815817570365
## 테스트 세트 출력값
0.9827976465386884

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

 

라쏘 회귀

라쏘는 계수의 절댓값을 기준으로 규제를 적용한다. 라쏘는 계수의 크기를 0으로 만들 수도 있다.

라쏘는 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 값을 바꿔가며 훈련 세트와 테스트 세트에 대한 점수를 계산해보자.

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

train_score와 test_score 리스트를 사용해 그래프를 그리자.

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이다. 이 값으로 모델을 다시 훈련해보자.

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_ 속성에 저장되어 있다.

print(np.sum(lasso.coef_ == 0))

## 출력값
40

40개의 계수가 0인 것을 알 수 있다. 55개의 특성 중에서 라쏘 모델이 사용한 특성은 15개인 것이다. 이런 특징 때문에 라쏘 모델을 유용한 특성을 골라내는 용도로도 사용할 수 있다.

 

정리

1. 다중회귀: 여러 개의 특성을 사용하는 회귀 모델. 특성이 많으면 선형 모델은 강력한 성능을 발휘한다.

2. 특성공학: 주어진 특성을 조합하여 새로운 특성을 만드는 작업

3. 릿지: 규제가 있는 선형 모델. 선형 모델의 계수를 작게 만들어 과대적합을 완화시킨다. 널리 사용된다.

4. 라쏘: 규제가 있는 선형 모델. 계수 값을 0으로 만들 수도 있다.

5. 하이퍼파라미터: 머신러닝 알고리즘이 학습하지 않는 파라미터. 사람이 지정해야한다.