[머신러닝 코세라 강의] (2주차) "Cost Function & Gradient Descent" Machine Learning (by Andrew Ng)
2주차 Machine Learning (by Andrew Ng) 교수님의 Coursera 강의의 중요 내용을 요약하고, 관련 알고리즘을 직접 파이썬으로 생성해보았습니다.
관련 ipython 코드는 구글 colab 링크에 담아두었습니다.
가설: $h_{\theta} (x) = \theta_0 + \theta_1 x$ 에서 $\theta_0, \theta_1$ 을 선택합니다. 이 때, cost function 인 $J(\theta_0, \theta_1) = \frac{1}{m} \sum_{i=1}^m \Big( h_\theta (x_i )- y_i \Big)^2$ 을 최소화하는 $\theta_0, \theta_1$ 을 찾습니다.
$$\min_{\theta_0, \theta_1} \frac{1}{m} \sum_{i=1}^m \Big( h_\theta (x_i )- y_i \Big)^2$$
Contour plot 을 그려보기 위해 시뮬레이션을 해보겠습니다. 시뮬레이션 예시를 위해 $y = 2 + 3 \times x$ (즉, $\theta_0 = 2, \theta_1 = 3$) 인 $x$ 와 $y$ 를 뽑습니다.
import numpy as np
from scipy import stats as st
from scipy.stats import norm
mu = 10
sigma = 0.25
theta0 = 2
theta1 = 3
n = 10000
x = [0]*n
y = [0]*n
for s in range(n):
x[s]=np.random.normal(mu,sigma)
y[s]= theta0 + theta1 * x[s]
u = np.random.normal(0,0.1,n)
x = np.array(x)
y = np.array(y)
y = y+u
import matplotlib.pyplot as plt
plt.scatter(x,y)
model = np.polyfit(x, y, 1)
model
는 array([3.00186546, 1.98162399]) 를 출력합니다. 원하는 파라미터 값이 출력되었습니다. 다음으로, cost function 을 정의한 후에, $\theta_0$ 와 $\theta_1$ 의 범위에 대해서, cost function 의 값을 구하고, 이를 그래프로 나타냅니다.
def J(b,a) :
sum = 0
for s in range(n):
sum = sum + (b+x[s]*a-y[s])**2
return sum *1/(2*n)
pointsTheta0 = np.arange(1.95,2.05,0.01)
pointsTheta1 = np.arange(2.95,3.05,0.01)
axisTheta0, axisTheta1 = np.meshgrid(pointsTheta0, pointsTheta1)
Z = J(axisTheta0,axisTheta1)
fig = plt.figure(figsize = (12,10))
ax = plt.axes(projection='3d')
surf = ax.plot_surface(axisTheta0, axisTheta1, Z, cmap = plt.cm.cividis)
# Set axes label
ax.set_xlabel('theta0', labelpad=20)
ax.set_ylabel('theta1', labelpad=20)
ax.set_zlabel('J(theta0,theta1)', labelpad=20)
fig.colorbar(surf, shrink=0.5, aspect=8)
plt.show()
아래와 같은 그래프가 나오는데, $(\theta_0, \theta_1)$ 에 대한 cost function 의 값이 육안으로 확인할 때, $\theta_0$ 에 대해서는 차이가 잘 보이지 않습니다. $\theta_1$ 에 대해서는 차이가 보입니다.
등고선을 구현해봅니다.
v = np.linspace(0.004, 0.02, 10, endpoint=True)
# plt.contour(axisTheta0, axisTheta1, Z)
plt.contourf(axisTheta0, axisTheta1, Z, v, cmap=plt.cm.jet)
plt.colorbar();
위의 코드는 아래와 같은 등고선을 출력합니다. (등고선을 육안으로 확인할 때) $\theta_0$ 값은 1.97 ~ 2.03 사이에서 cost function 이 비슷하게 나오고, $\theta_1$ 값은 cost function 이 거의 동일합니다.
다음으로, gradient descent 알고리즘을 이용해서 $\theta$ 값들을 찾아보도록 하겠습니다. 아래 $\theta$ 값들이 수렴할 때 까지 반복합니다.
$$\theta_j := \theta_j - \alpha \frac{\partial}{\partial \theta_j} J(\theta_0, \theta_1)$$
for $j=0$ and $j=1$.
이 때, $\theta$ 값들의 업데이트를 동시에 합니다.
xmean=x.mean(dtype=float)
ymean=y.mean(dtype=float)
xstd=x.std(dtype=float)
ystd=y.std(dtype=float)
x.mean(),x.std(),y.mean(),y.std()
(x 평균, x 표준편차, y 평균, y 표준편차) = (10.00088584317288, 0.248813713074603, 32.000859006885335, 0.7533594924594834) 가 나옵니다.
x 와 y 값들을 normalize 를 먼저 해줍니다. x 와 y 의 단위가 다르면 $\theta_0$ 와 $\theta_1$ 을 동시에 찾을 때, error 값을 특정짓기가 어렵기 때문입니다.
x=(x-x.mean())/x.std()
y=(y-y.mean())/y.std()
x 와 y 값들을 normalize 해줍니다. 평균과 표준편차가 모두 1입니다.
다음으로, gradient descent algorithm 을 적용하기 위해서 $-\alpha \frac{\partial}{\partial \theta_j}J(\theta_0, \theta_1)$ 을 적용합니다.
$j=0: \frac{\partial}{\partial \theta_0}J(\theta_0, \theta_1)= \frac{1}{m} \sum_{i=1}^m \Big( h_\theta (x_i )- y_i \Big) $
$j=1: \frac{\partial}{\partial \theta_1}J(\theta_0, \theta_1)= \frac{1}{m} \sum_{i=1}^m \Big( h_\theta (x_i )- y_i \Big) x_i$
def update0(b,a) :
sum = 0
for s in range(n):
sum = sum + (b+x[s]*a-y[s])
return sum *1/(n)
def update1(b,a) :
sum = 0
for s in range(n):
sum = sum + (b+x[s]*a-y[s])*x[s]
return sum *1/(n)
다음으로, 위 함수를 바탕으로 gradient descent 알고리즘을 적용해서 $\theta$ 값들을 찾습니다.
predTheta0=4
predTheta1=4
prevTheta0=0
prevTheta1=0
im_predTheta0=1
im_predTheta1=1
alpha= 10**(-3)
error = 10**(-5)
while (abs(predTheta0-prevTheta0)>error or abs(predTheta1-prevTheta1)>error):
prevTheta0 = predTheta0
prevTheta1 = predTheta1
im_predTheta0=predTheta0-alpha*update0(predTheta0,predTheta1)
im_predTheta1=predTheta1-alpha*update1(predTheta0,predTheta1)
predTheta0=im_predTheta0
predTheta1=im_predTheta1
True: $y = \theta_1 x + \theta_0$
$$y' = a x' + b$$
where $y' = \frac{y-\mu_y}{\sigma_y}$ and $x'=\frac{x-\mu_x}{\sigma_x}$ (참값은 $y$ 와 $x$ 인데, $y'$ 과 $x'$ 은 참값을 normalize 했기 때문입니다. $a$ 와 $b$ 는 파이썬 코드에서 각각 predTheta1 과 predTheta1 입니다.).
Then,
$$ \frac{y-\mu_y}{\sigma_y} = a \times \Big( \frac{x-\mu_x}{\sigma_x} \Big) + b$$
Therefore,
$$ y = a \frac{\sigma_y}{\sigma_x} x + \Big( \mu_y - a \frac{\sigma_y}{\sigma_x} \mu_x + \sigma_y b \Big) $$
따라서, $\theta_1 = a \frac{\sigma_y}{\sigma_x} $ 이고, $\theta_0 = \mu_y - a \frac{\sigma_y}{\sigma_x} \mu_x + \sigma_y b$ 입니다. 이를 적용해서 계산해보면,
estTheta1=predTheta1*ystd/xstd
estTheta0=ymean-predTheta1*(ystd/xstd)*xmean+ystd*predTheta0
(estTheta0, estTheta1) = (1.7672025480851359, 3.0238499860834755) 가 나옵니다. 실제값에 제법 가까운 값이 나옵니다. 오차를 제어하려면 어떻게 해야하는 지는 더 고민해야할 것 같습니다 (error 를 더 작은 값으로하면 값이 더 잘 나오지 않을까 싶네요).
Reference
- Gradient Descent, the Learning Rate, and the importance of Feature Scaling
- 미디엄 Gradient descent method.