머신러닝 모델 훈련
머신러닝 모델이 어떻게 작동하는지 잘 이해하고 있으면 적절한 모델, 올바른 훈련 알고리즘, 작업에 맞는 좋은 하이퍼 파라미터를 빠르게 찾을 수 있다. 또한 디버깅이나 에러를 효율적으로 분석하는 데 도움이 된다.
모델을 훈련시킨다는 것은 모델이 훈련 세트에 가장 잘 맞도록 모델 파라미터를 설정하는 것이며, 이를 위해 먼저 모델이 훈련 데이터에 얼마나 잘 맞는지 측정해야 한다.
이 글에서 언급하는 대부분의 주제는 신경망을 이해하고 구축하고 훈련시키는 데 필수이다.
[알아볼 개념]
- 선형 회귀
- 다항 회귀 - 학습 곡선
- 규제가 있는 선형 모델(릿지, 라쏘, 엘라스틱넷, 조기 종료)
- 로지스틱 회귀, 소프트맥스 회귀
선형 회귀는 닫힌 형태의 방정식을 사용하여 비용 함수를 최소화하는 파라미터를 계산하거나, 경사 하강법과 경사 하강법의 변형인 방식들을 사용하여 모델 파라미터를 조금씩 바꾸면서 비용 함수를 훈련 세트에 대해 최소화시킬 수 있다.
다항 회귀는 비선형 데이터셋에서 훈련시킬 수 있는 조금 더 복잡한 모델인데, 이 모델은 선형 회귀보다 파라미터가 많아서 훈련 데이터에 과대적합되기 더 쉽다.
따라서 학습 곡선을 사용해 모델이 과대적합되는지 감지해볼 수 있고, 그다음으로 훈련 세트의 과대적합을 줄일 수 있는 규제 기법을 적용해볼 수도 있다.
또한, 분류 작업에는 로지스틱 회귀와 소프트맥스 회귀가 널리 사용된다.
선형 회귀
선형 회귀를 훈련 시키는 방법
- 닫힌 형태의 방정식을 사용하여 훈련 세트에 가장 잘 맞는 모델 파라미터(훈련 세트에 대해 비용 함수를 최소화하는 파라미터)를 직접 계산
- 경사 하강법(GD)이라 불리는 반복적인 최적화 방식을 사용하여 모델 파라미터를 조금씩 바꾸면서 비용 함수를 훈련 세트에 대해 최소화시킨다.
- 경사 하강법의 변형인 배치 경사 하강법, 미니 배치 경사 하강법, 확률적 경사 하강법(SGD) 도 알아두면 좋다
회귀에 가장 널리 사용되는 성능 측정 지표는 평균 제곱근 오차 (RMSE)로, 선형 회귀 모델을 훈련시키려면 RMSE를 최소화하는 파라미터를 찾아야 한다. 실제로는 RMSE 보다 평균 제곱 오차(MSE)를 최소화하는 것이 같은 결과를 내면서 더 간단하다.
[정규 방정식]
정규 방정식: 비용 함수를 최소화하는 파라미터 값을 찾기 위한 결과를 바로 얻을 수 있는 수학 공식
먼저 선형처럼 보이는 데이터를 생성한 후
import matplotlib.pyplot as plt
import numpy as np
# 션형 데이터 생성
np.random.seed(42) #동일하게 재현되도록 하기 위해 지정
m = 100 #샘플 개수
X = 2 * np.random.rand(m, 1) # 열 벡터
y = 4 + 3 * X + np.random.rand(m, 1) # 열 벡터
# 그래프 생성
plt.figure(figsize=(6, 4))
plt.plot(X, y, "b.")
plt.xlabel("$x_1$")
plt.ylabel("$y$", rotation=0)
plt.axis([0, 2, 0, 15])
plt.grid()
plt.show()
정규 방정식을 사용해본다.
넘파이의 선형대수 모듈(np.linalg)에 있는 inv() 함수를 사용해 역행렬을 계산하고 dot() 메서드를 사용해 행렬 곱셈을 한다
from sklearn.preprocessing import add_dummy_feature
X_b = add_dummy_feature(X) # 각 샘플에 x0 = 1 추가
theta_best = np.linalg.inv(X_b.T @ X_b) @ X_b.T @ y
# 정규방정식으로 계산한 값 확인
theta_best
계산한 값을 사용해 예측 수행
X_new = np.array([[0], [2]])
X_new_b = add_dummy_feature(X_new) # 각 샘플에 x0 = 1 추가
y_predict = X_new_b @ theta_best
y_predict
모델의 예측을 그래프로 표현
import matplotlib.pyplot as plt
plt.figure(figsize=(6, 4))
plt.plot(X_new, y_predict, "r-", label="Predictions")
plt.plot(X, y, "b.")
plt.xlabel("$x_1$")
plt.ylabel("$y$", rotation=0)
plt.axis([0, 2, 0, 15])
plt.grid()
plt.legend(loc="upper left")
plt.show()
[사이킷런으로 선형 회귀 수행하기]
사이킷런은 특성의 가중치(coef_)와 편향(intercept_)을 분리하여 저장한다.
LinearRegression 클래스는 scipy.linalg.lstsq() 함수를 기반으로 한다.
또한 np.linalg.pinv() 함수를 사용해 유사역행렬을 직접 구할 수 있는데, 유사역행렬 자체는 특잇값 분해(SVD)라 부르는 표준 행렬 분해 기법을 사용해 계산된다.
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X, y)
lin_reg.intercept_, lin_reg.coef_
lin_reg.predict(X_new)
theta_best_svd, residuals, rank, s = np.linalg.lstsq(X_b, y, rcond=1e-6)
theta_best_svd
np.linalg.pinv(X_b) @ y
[경사 하강법]
경사 하강법은 여러 종류의 문제에서 최적의 해법을 찾을 수 있는 일반적인 최적화 알고리즘이다. 경사 하강법의 기본 아이디어는 비용 함수를 최소화하기 위해 반복해서 파라미터를 조정해가는 것이다.
◆ 배치 경사 하강법
매 스텝에서 훈련 데이터 전체를 사용해 X에 대해 계산한다. 이런 이유로 매우 큰 훈련 세트에서는 아주 느리나, 경사 하강법은 특성 수에 민감하지 않는다. 수십만 개의 특성에서 선형 회귀를 훈련시키려면 정규 방정식이나 SVD 분해보다 경사 하강법을 사용하는 편이 훨씬 빠르다.
eta = 0.1 # 학습률
n_epochs = 1000 # epoch: 훈련 세트를 한 번 반복하는 것
m = len(X_b) # 샘플 개수
np.random.seed(42)
theta = np.random.randn(2, 1) # 모델 파라미터를 랜덤하게 초기화
for epoch in range(n_epochs):
gradients = 2 / m * X_b.T @ (X_b @ theta - y)
theta = theta - eta * gradients
theta
세 가지 학습률을 사용하여 경사 하강법의 첫 20스텝을 시각화 해 본다
import matplotlib as mpl
def plot_gradient_descent(theta, eta):
m = len(X_b)
plt.plot(X, y, "b.")
n_epochs = 1000
n_shown = 20
theta_path = []
for epoch in range(n_epochs):
if epoch < n_shown:
y_predict = X_new_b @ theta
color = mpl.colors.rgb2hex(plt.cm.OrRd(epoch / n_shown + 0.15))
plt.plot(X_new, y_predict, linestyle="solid", color=color)
gradients = 2 / m * X_b.T @ (X_b @ theta - y)
theta = theta - eta * gradients
theta_path.append(theta)
plt.xlabel("$x_1$")
plt.axis([0, 2, 0, 15])
plt.grid()
plt.title(fr"$\eta = {eta}$")
return theta_path
np.random.seed(42)
theta = np.random.randn(2, 1) # 랜덤 초기화
plt.figure(figsize=(10, 4))
plt.subplot(131)
plot_gradient_descent(theta, eta=0.02)
plt.ylabel("$y$", rotation=0)
plt.subplot(132)
theta_path_bgd = plot_gradient_descent(theta, eta=0.1)
plt.gca().axes.yaxis.set_ticklabels([])
plt.subplot(133)
plt.gca().axes.yaxis.set_ticklabels([])
plot_gradient_descent(theta, eta=0.5)
plt.show()
- 왼쪽: 학습률이 너무 낮다. 알고리즘은 최적점에 도달하겠지만 시간이 오래 걸린다
- 가운데: 학습률이 적당해 보인다. 반복 몇 번만에 최적점에 수렴
- 오른쪽: 학습률이 너무 높다. 알고리즘이 스텝마다 최적점에서 점점 더 멀어져 발산한다
적절한 학습률을 찾기 위해 그리드 서치를 사용하나, 그리드 서치에서 수렴하느 ㄴ데 너무 오래 걸리는 모델이 제외될 수 있도록 반복 횟수를 제한해야 한다.
[확률적 경사 하강법]
확률적 경사하강법은 배치 경사 하강법과는 반대로 매 스텝에서 한 개의 샘플을 랜덤으로 선택하고 그 하나의 새믈에 대한 그레이디언트를 계산한다. 따라서 알고리즘이 훨씬 더 빠르고 매우 큰 훈련 세트도 훈련시킬 수 있다.
n_epochs = 50
t0, t1 = 5, 50 # 학습 스케줄 하이퍼파라미터
def learning_schedule(t):
return t0 / (t + t1)
np.random.seed(42)
theta = np.random.randn(2, 1) # 랜덤 초기화
for epoch in range(n_epochs):
for iteration in range(m):
random_index = np.random.randint(m)
xi = X_b[random_index:random_index+1]
yi = y[random_index:random_index+1]
gradients = 2 * xi.T @ (xi @ theta - yi) # SGD의 경우 m으로 나누지 않음
eta = learning_schedule(epoch * m + iteration)
theta = theta - eta * gradients
theta
▶ 배치 경사 하강법은 전체 훈련 세트에 대해 1,000번 반복하는 동안 이 코드는 훈련 세트에서 50번만 반복하고도 매우 좋은 값에 도달했다.
사이킷런에서 SGD 방식으로 선형 회귀를 사용하려면 기본값으로 제곱 오차 비용 함수를 최적화하는 SGDRegressor 클래스를 사용한다.
from sklearn.linear_model import SGDRegressor
sgd_reg = SGDRegressor(max_iter=1000, tol=1e-5, penalty=None, eta0=0.01,
n_iter_no_change=100, random_state=42)
sgd_reg.fit(X, y.ravel()) # fit()이 1D 타깃을 기대하기 때문에 y.ravel()
sgd_reg.intercept_, sgd_reg.coef_
[미니배치 경사 하강법]
각 스텝에서 미니배치라 부르는 임의 작은 샘플 세트에 대해 그레이디언트를 계산한다.
아래는 세 가지 경사 하강법 알고리즘이 훈련 과정 동안 파라미터 공간에서 움직인 경로이다.
모두 최솟값 근처에 도달했지만 배치 경사 하강법은 실제로 최솟값에서 멈춘 반면 확률적 경사 하강법과 미니배치 하강법은 근처에서 맴돌고 있다.
배치 경사 하강법의 경우 매 스텝에서 많은 시간이 소요되며 확률적 경사 하강법과 미니배치 경사 하강법의 경우에도 적절한 학습 스케줄을 사용하면 최솟값에 도달할 수 있다.
[선형 회귀를 사용한 알고리즘 비교]
알고리즘 | m이 클 때 | 외부 메모리 학습 지원 |
n이 클때 | 하이퍼 파라미터 수 |
스케일 조정 필요 |
사이킷런 |
정규 방정식 | 빠름 | N | 느림 | 0 | N | N/A |
SVD | 빠름 | N | 느림 | 0 | N | LinearRegression |
배치 경사 하강법 | 느림 | N | 빠름 | 2 | Y | N/A |
확률적 경사 하강법 | 빠름 | Y | 빠름 | >= 2 | Y | SGDRegression |
미니배치 경사 하강법 | 빠름 | Y | 빠름 | >= 2 | Y | N/A |
다항회귀
주어진 데이터가 단순한 직선보다 복잡한 형태일 경우 각 특성의 거듭제곱을 새로운 특성으로 추가하고, 이 확장된 특성을 포함한 데이터셋에 선형 모델을 훈련시키면 된다. 이런 기법을 다항 회귀라고 한다.
# 간단한 2차 방정식에 약간의 잡음을 추가한 비선형 데이터 생성
np.random.seed(42)
m = 100
X = 6 * np.random.rand(m, 1) - 3
y = 0.5 * X ** 2 + X + 2 + np.random.randn(m, 1)
# 그래프
plt.figure(figsize=(6, 4))
plt.plot(X, y, "b.")
plt.xlabel("$x_1$")
plt.ylabel("$y$", rotation=0)
plt.axis([-3, 3, 0, 10])
plt.grid()
plt.show()
사이킷런의 PolynomialFeatures를 사용해 훈련 세트에 있는 각 특성을 제곱(2차 다항)하여 새로운 특성으로 추가한다.
from sklearn.preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly_features.fit_transform(X)
X[0]
X_poly는 원래 특성 X와 이 특성의 제곱을 포함한다
X_poly[0]
확장된 훈련 데이터에 LinearRegression 적용하고 시각화
lin_reg = LinearRegression()
lin_reg.fit(X_poly, y)
lin_reg.intercept_, lin_reg.coef_
X_new = np.linspace(-3, 3, 100).reshape(100, 1)
X_new_poly = poly_features.transform(X_new)
y_new = lin_reg.predict(X_new_poly)
plt.figure(figsize=(6, 4))
plt.plot(X, y, "b.")
plt.plot(X_new, y_new, "r-", linewidth=2, label="Predictions")
plt.xlabel("$x_1$")
plt.ylabel("$y$", rotation=0)
plt.legend(loc="upper left")
plt.axis([-3, 3, 0, 10])
plt.grid()
plt.show()
PolynomialFeatures가 주어진 차수까지 특성 간의 모든 교차항을 추가하기 때문에 특성이 여러 개일 때 다항 회귀는 특성 사이의 관계를 찾을 수 있다.
다항 회귀는 선형 회귀보다 파라미터가 많아서 훈련 데이터에 과대적합되기 더 쉽기 때문에 학습 곡선을 사용해 모델이 과대적합되는지 감지해볼 수 있다.
다음 내용
[출처]
핸즈온 머신러닝
'[파이썬 Projects] > <파이썬 머신러닝>' 카테고리의 다른 글
[머신러닝] 서포트 벡터 머신(SVM) (1) | 2024.11.14 |
---|---|
[머신러닝] 모델 훈련 - 2 (3) | 2024.11.13 |
[머신러닝] 분류: MNIST 데이터셋 실습 - 2 (0) | 2024.11.09 |
[머신러닝] 분류: MNIST 데이터셋 실습 - 1 (0) | 2024.11.09 |
[머신러닝] 캘리포니아 주택 가격 프로젝트-3 (0) | 2024.11.08 |