[핸즈온 머신러닝] 4강 모델학습 1편

2018. 7. 14. 08:37Python-이론/python-인공지능2

모델학습



4.1. 선형회귀 

선형회귀는 우리가 수학시간에 자주보는 y = w1*x1 + w2*x2 + w3*x3 +b 와 같은 형태로 나타낼 수 있는 함수이다. 글로 설명해보면 입력 특성의 가중치의 합과 편향(또는 절편)이라는 상수를 더해 예측을 만듭니다. 

그럼 이제 훈련을 어떻게 시키는지 알아 봅시다. 


보통 훈련 데이터들을 입력시켜 훈련시키고 검증데이터를 입력해서 검증을 했는데 얼마나 모델에 잘 적합했냐에 따라서 좋은 모델인지 판단하게 됩니다. 

그리고 모든데이터가 모델에 맞을 수 는 없습니다. 이런 데이터들은 모델과 오차라는 것을 갖는데요. 이 오차를 통해서 모델의 성능을 판단할 수 있습니다.  가장 널리 사용되는 성능 측정 지표는 평균 제곱근 오차(MSE) 입니다. 그러므로 더 좋은 성능을 내기 위해서는 이러한 오차를 최소화 하기위한 함수의 파라미터(혹은 특성, 가중치)를 찾는 것입니다. 


MSE 공식 





4.1.1 정규방정식 


정규방정식은 비용함수(MSE)를 최소화 할 수 있는 방법이다. 


정규방정식의 공식은 



코드로 표현해보겠습니다.


1. 임의로 선형 데이터를 만들어보겠습니다.

 

import numpy as np

X = 2 * np.random.rand(100, 1) #0~1사이의 난수 발생
Y = 4 + 3*X + np.random.randn(100, 1)#가우시안 표준 정규분표 발생


위의 코드는 y = 3x + 4 + 가우시안_노이즈와 같이 표현할 수 있다. 


가우시안_노이즈 = 정규분포를 가지는 잡음이며 쉽게 말해서 일반적인 잡음(갑자기 튀지 않는 잡음)이다. 


그럼 이제부터 w 값을 예측해보겠습니다.  

X_b = np.c_[np.ones((100, 1)), X]
theta_best = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(Y) #inv 역행렬을 구하고 .dot을 사용해 행렬 곱셈을 합니다. array([[3.98272712], [2.93414589]])


우리가 적용한 4 + 3x와 비슷하게 나왔지만 오차때문에 정확하진 않은 값입니다. 그럼 위의 코드를 통해 구한 파라미터를 통해 실제 값을 예측해봅시다.


 

X_new = np.array([[0], [2]])
X_new_b = np.c_[np.ones((2, 1)), X_new]
y_predict = X_new_b.dot(theta_best)
y_predict array([[3.98272712], [9.8510189 ]])


모델의 예측을 그래프로 만들어 보겠습니다. 


코드

import matplotlib.pyplot as plt 

plt.plot(X_new, y_predict, "r--")
plt.plot(X, Y, "b.")
plt.axis([0, 2, 0, 15])
plt.show()

그래프 



하지만 이와 같이 적어준 코드는 scikit learn에서 쉽게 사용할 수 있게 만들어 주었습니다. 따라서 우리가 코드를 적을 때는 간편히 사용할 수 있다. 


#파라미터 값 구하기 from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(X, Y)
lin_reg.intercept_,lin_reg.coef_ (array([3.98272712]), array([[2.93414589]])) #이전에 구했던 파라미터와 동일한 값이 나왔다.


#예측값 구하기 lin_reg.predict(X_new) array([[3.98272712], [9.8510189 ]])


앞서 수식을 직접 표현하여 만든 것 보다 sckitlearn에서 제공하는 함수를 사용하면 훨씬 편하게 사용할 수 있다. 


4.1.2 계산 복잡도


정규 방정식은 (n+1)*(n+1) 크기가 되는 값들간의 역행렬을 계산합니다. 계산복잡도는 일반적으로 O() ~ O()입니다. 하지만 다행인 것이 훈련 세트의 샘플 수에는 선형적으로 증가합니다. O(m)이 됩니다. 그리고 메모리 공간이 충분하다면 효율적으로 처리할 수 있습니다. 그리고 학습된 선형 회귀 모델은 예측이 매우 빠릅니다. 하지만 다시 말해서 샘플이 두배로 늘어나면 걸리는 시간도 두 배 증가합니다.  



4.2 경사 하강법 


경사하강법이란 여러 종류의 문제에서 최적의 해법을 찾을 수 있는 일반저긴 최적화 알고리즘이다. 비용 함수를 최소화하기위해 반복해서 파라미터를 조정해나가는 것입니다. 


첫 시작은 임의의 파라미터에서 시작합니다. 반복하며 비용함수가 감소되는 방향으로 진행하여 알고리즘이 최소값에 수렴할 때까지 점진적으로 향상시킵니다. 


경사하강법에서 가장 중요한 파라미터는 학습률입니다. 학습률은 각 스텝의 크기를 의미합니다.  학습률이 적으면 수렴하기 위해 반복을 너무 많이 진행해야 하므로 시간이 오래걸립니다. 하지만 학습률이 너무 크면 파라미터가 최소가 될때를 넘어서서 까지 진행하게 됩니다.


보통 예를 들기 위해 2차 함수 그래프를 예를 많이 들지만 모든 비용함수가 이와 같이 매끈한 그릇과 같지는 않습니다. 패인 곳이 있을 수도 있고 평지 가 있을 수도 있죠.


예를 들어 


무작위 로 시작하기 때문에 오른쪽으로 시작하면 지역 최소값에 수렴하게 되어 전역 최솟값에 가지 못하는 문제가 발생할 수 있습니다. 


하디만 다행히 MSE 비용함수는 볼록함수이기 때문에 지역 최솟값이 없고 하나의 전역 최솟값만 있습니다. 


(볼록함수 그래프의 두점을 이었을때 곡선이 항상 밑에 있으면 볼록함수이다.) 


4.2.1 배치 경사 하강법 


경사하강법을 구현하려면 각 모델 파라미터가 조금 변경될 때 비용 함수가 얼마나 바뀌는지 계산해야 합니다. 이를 편도함수라고 합니다. 



mse공식을 세타j에 대해 편미분하면 미분 공식에 따라 지수가 곱셈으로 따라 내려오고 괄호 안의 편미분이 추가로 곱해집니다.  이러한 방법은 데이터 전체를 사용합니다. 데이터 전체를 사용하므로 배치 경사 하강법이라고 합니다. 데이터가 크면 많이 느려지지만 특성의 수에는 그다지 민감하지 않아서 특성이 많은 데이터에 유리하게 쓰일 수 있습니다. 


그리고 각 단계의 스텝은 


이제 코드를 통해서 적절한 파라미터를 구해보겠습니다.

 

eta = 0.1 #학습률 
n_iterations = 1000
m = 100

theta = np.random.randn(2,1)

for iteration in range(n_iterations):
gradients = 2/m * X_b.T.dot(X_b.dot(theta)-Y)
theta = theta - eta *gradients
theta array([[3.92786835], [3.02440433]])

선형회귀 할때 지정해준 4와 3에 비슷하게 나왔습니다. 


하지만 이런 생각이 들 수 가 있습니다. 적절한 학습률을 어떻게 구해야 할까요. 쉬운 방법으로는 높은 학습률로 학습하다가 어느 오차 이하가됐을 때 알고리즘을 멈추게하는 것입니다. 


4.2.2 확률적 경사하강법 


배지 경사하강법의 가장 큰 문제점은 모든 데이터를 사용해서 정확하겠지만 훈련세트가 커지면 매우 느려지게 됩니다.  이와 반대로 확률적 경사하강법은 매 스탭에서 딱 한개의 샘플을 무작위로 선택하고 하나의 샘플에 대한 그래디언트를 계산압니다. 매 반복에서 매우적은 데이터만 처리하기 때문에 훨씬 빠릅니다. 따라서 매우 큰 훈련 데이터도 훈련시킬 수 있습니다. 


하지만 확률적이기 때문에 배치 경사 하강법보다 훨씬 불안정 합니다. 비용 함수가 최솟값에 다다를 때까지 부드럽게 감수하지 않고 위아래로 요동치면서 평균적으로 감소합니다. 다다를 떄까지 계속 요동칠 것 입니다. 하지만 무작위 적으로 값이 계속 움직이기 때문에 지역 최솟값을 벗어나고 전역 최솟값을 찾을 가능성이 높습니다.  하지만 전역 최솟값에 다다르기는 힘듭니다. 따라서 실제로는 처음 시작할때 학습률을 매우 높게 지정하고 갈 수록 학습률을 낮추어 주면 효과적으로 구할 수 있습니다. 


이러한 방법을 코드로 표현해보겠습니다. 


직접 코드를 작성

n_epochs = 50
t0, t1 = 5, 50

def learning_schedule(t):
return t0/(t+t1)

theta = np.random.randn(2, 1)

for epoch in range(n_epochs):
for i 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.dot(xi.dot(theta)-yi)
eta = learning_schedule(epoch * m + i)
theta = theta - eta *gradients
theta (array([3.93282178]), array([3.01973026]))


sckit learn에서 제공하는 함수를 사용


from sklearn.linear_model import SGDRegressor 
sgd_reg = SGDRegressor(max_iter=50, penalty=None, eta0=0.1) #epoch, penalty, 학습률
sgd_reg.fit(X, Y.ravel())
sgd_reg.intercept_, sgd_reg.coef_ (array([3.93282178]), array([3.01973026]))


4.2.3 미니배치 경사 하강법


전체 데이터나 한개의 데이터를 이용하는 것이 아니라 미니 배치라고 불리는 임의의 작은 샘플 세트에 대해 그레디언트를 계산합니다. 어느 정도 크게하면 알고리즘은 파라미터 고간에서 덜 불규칙하게 움직이고 최솟값에 더 가까이 도달하게 될 것입니다. 


4.2.4 전반적인 비교


4.3 다항 회귀 


다항회귀를 사용하면 비선형 데이터를 학습하는 데 선형 모델을 사용할 수 있습니다. 이렇게 사용하는 방법은 각 특성의 거듭제곱을 새로운 특성을 추가하는 방법입니다.  



m = 100
X = 6 * np.random.rand(m, 1) - 3
Y = 0.5 * X**2 + X + 2 + np.random.randn(m, 1)

비선형 모델 데이터를 만들었습니다. 하지만 이러한 형태는 당연히 선형모델에 맞지 않을 것 입니다. 따라서 데이터를 선형 모델에 맞게 바꾸어 주겠습니다. 


사이킷런의 PolynomialFeatures를 사용해 훈련데이터를 변환해보겠습니다. 


from sklearn.preprocessing import PolynomialFeatures

poly_features = PolynomialFeatures(degree=2, include_bias=False) #입력값 X를 다항식으로 변환합니다.
X_poly = poly_features.fit_transform(X)
X_poly[0]


lin_reg = LinearRegression()
lin_reg.fit(X_poly, Y)
lin_reg.intercept_, lin_reg.coef_ (array([1.96786077]), array([[1.06300165, 0.50328912]]))


다항식에 곱해준 것과 같이 비슷한 파라미터의 값이 나왔습니다. 


PolynomialFeatures가 주어진 차수까지 특성 간의 모든 교차항을 추가해주는 함수입니다. 


4.4 학습 곡선 


우리가 상상했을 때 고차 다항 회귀 그래프는 울긋불긋 하고 복잡할 것 입니다. 그렇다보면 과대적합이 발생한 확률이 올라갑니다. 반면에 데이터가 부족한 선형 모델은 과소적합니다. 보통은 2차 다항 회귀가 가장 일반화가 잘된 모델이 됩니다. 이렇듯이 우리가 모델을 만들었을 때 과대적합 한지 과소적합 한지 어떻게 알 수 있을 까요?? 2장에서는 교차 검증을 사용하여 훈련데이터에서는 성능이 좋지만 교차 검증 점수가 나쁘다면 모델이 과대적합 한지 알 수 있었습니다. 만약 양쪽에 좋지 않다면 과소적합입니다. 또 다른 방법은 학습곡선을 보는 것 입니다. 


그럼 그래프를 그려보겠습니다.



from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split

def plot_learning_curves(model, X, Y):
X_train, X_val, Y_train, Y_val = train_test_split(X, Y, test_size=0.2)
train_errors, val_errors = [], []
for m in range(1, len(X_train)):
model.fit(X_train[:m], Y_train[:m])
Y_train_predict = model.predict(X_train[:m])
Y_val_predict = model.predict(X_val)
train_errors.append(mean_squared_error(Y_train[:m], Y_train_predict))
val_errors.append(mean_squared_error(Y_val, Y_val_predict))#이 함수는 말그대로 평균 제곱 오차 회귀 손실이다.
plt.plot(np.sqrt(train_errors), "r+-", linewidth=2, label="practice")
plt.plot(np.sqrt(val_errors), "b-", linewidth=3, label="check")
plt.legend(loc='upper right')
plt.xlabel('practice set size ')
plt.ylabel('RMSE')
plt.show()

lin_reg = LinearRegression()
plot_learning_curves(lin_reg, X, Y)

이 함수는 입력된 데이터와 미리 만들어 놓은 예측된 데이터를 비교하여 오차를 만들어 낸다. 


만들어진 그래프 


훈련 데이터를 보면 그래프가 가리키는 값이 작으므로 훈련 세트에 적은 샘플이 있을때는 괜찮은 효과를 낼 것 입니다. 샘플이 추가 됨에 따라 노이즈도 있고 비선형이기 때문에 모델이 훈련 데이터를 완벽히 학습하는 것은 불가능합니다.  


검증 데이터는 처음에는 훈련된 데이터가 부족해서 오차가 엄청 높게 나옵니다. 하지만 데이터가 계속 훈련되면서 오차가 감소합니다. 그러나 점점 평평해지고 훈련세트와 가까워 집니다. 이것이 대표적인 과소적합의 예입니다. 


이제 같은 데이터에서 10차 다항 회귀 모델의 학습 곡선을 그려보겠습니다. 



from sklearn.pipeline import Pipeline

polynomial_regression = Pipeline([
("poly_features", PolynomialFeatures(degree=10, include_bias=False)),
("lin_reg", LinearRegression()),
])

plot_learning_curves(polynomial_regression, X, Y)

그래프


위의 그래프와의 차이점 

1. 훈련 데이터의 오차가 선형 회귀 모델보다 훨씬 낮습니다. 

2. 두고선 사이에 공간이 있습니다. 이말은 훈련 데이터에서의 모델 성능이 검증 데이터에서 보다 훨씬 낫다는 뜻이고, 

이는 과대적합 모델의 특징입니다.