EunGyeongKim

지도학습 : 회귀 (Regression)_1차원 데이터, 직선 (1) 본문

ML & DL/딥러닝

지도학습 : 회귀 (Regression)_1차원 데이터, 직선 (1)

EunGyeongKim 2023. 2. 20. 19:54

지도학습은 회귀(Regression)분류(Classification)로 나눌수 있음. 

회귀 : 입력에 대해 연속적인 값을 대응시키는 문제. 

분류 : 입력에 대해 순서가 없는 클래스(라벨)을 대응시키는 문제


1차원 입력 직선 모델

연습 데이터 만들어주기

random함수를 이용하여 나이와 몸무게의 인공데이터 만들기

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

#create data
np.random.seed(seed=1)
x_min = 4
x_max = 30
x_n = 16
x = 5+25 * np.random.rand(x_n)
prm_c = [170, 108, 0.2] # 생성 매개변수
t = prm_c[0] - prm_c[1] * np.exp(-prm_c[2]*x) + 4 *np.random.randn(x_n)

plt.figure(figsize = (4,4))
plt.plot(x, t, marker='o', linestyle ='None', markeredgecolor='black', 
         color = 'cornflowerblue')
plt.xlim(x_min, x_max)
plt.grid(True)
plt.show()

직선모델

상기 그래프를 보면 데이터가 고르지 않기 때문에, 새로운 나이 데이터에 키를 정확히 맞추기는 불가능함.

그러므로 적당한 오차를 허용하여 데이터에 직선을 그어야 함.

직선의 방정식은

$$y(x) = w_0 + w_1$$

로 나타낼 수 있음

기울기를 나타내는 w0과, 절편을 나타내는 w1에 적당한 값을 넣으면 다양한 위치와 기울기의 직선을 만들 수 있음.

 

제곱 오차 함수 (평균제곱오차 : MSE, Mean Square Error)

'데이터에 부합하도록' 오차 J 를 정의하자

$$J = \frac{1}{N}\sum_{n=0}^{N-1}(y_n - t_n)^2$$

여기서 Jn은 직선모델에 Xn  넣었을 때의 출력을 나타냄.

$$ y_n = y(x_n) = w_0x_n + w_1 $$

J는 평균제곱오차로, 직선과 데이터 점의 차의 제곱의 평균. 교과서에 따라서 N으로 나누지 않는 제곱 합계 오차(sum of squared error)를 사용하시만, 어느경우든 도출되는 결론은 동일함.

데이터가 직선상에 나란히 있지 않기 때문에 J가 완전히 0이 되지는 않음.

from mpl_toolkits.mplot3d import Axes3D

# mse 함수
def mes_line(x, t, w):
    y = w[0] * x + w[1]
    mse = np.mean((y-t)**2)
    return mse

# calculation
xn = 100 # 등고선 표시 해상도
w0_range=[-25, 25]
w1_range=[120, 170]
x0 = np.linspace(w0_range[0], w0_range[1], xn)
x1 = np.linspace(w1_range[0], w1_range[1], xn)
xx0, xx1 = np.meshgrid(x0, x1)
j = np.zeros((len(x0), len(x1)))

for i0 in range(xn):
    for i1 in range(xn):
        j[i1, i0] = mes_line(x, t, (x0[i0], x1[i1]))

#graph
plt.figure(figsize = (9.5, 4))
plt.subplots_adjust(wspace = 0.5)

ax = plt.subplot(1, 2, 1, projection='3d')
ax.plot_surface(xx0, xx1, j, rstride=10, cstride=10, alpha=0.3, color='blue', edgecolor='black')
ax.set_xticks([-20, 0, 20])
ax.set_yticks([120, 140, 160])
ax.view_init(20, -60)

plt.subplot(1,2,2)
cont = plt.contour(xx0, xx1, j, 30, colors='black', levels=[100, 1000, 10000, 100000])
cont.clabel(fmt = '%1.0f', fontsize =8)
plt.grid(True)
plt.show()

 

매개변수 구하기(경사 하강법)

J(오차)가 가장 작아지는 w0와 w1을 구하는 방법중 가장 간단한 방법은 경사하강법임

경사하강법의 w0와 w1에 대한 J의 지형을 이미지로 알아보자

초기 위치로 적당한 w0와 w1을 결정한다. 이것은 J 지형위의 한 지점에만 대응됨. 이 점에서의 기울기를 확인하고 J가 가장 감소하는 방향으로 w0와 w1를 조금만 바꿈. 이 절차를 여러번 반복하여 최종적으로 J가 가장 작아지는 '그릇의 바닥'인 w0와 w1에 도착할 수 있음.

언덕 위쪽 방향은 J를 w0과 w1로 편미분한 벡터로 표시됨

$$Hill Upper Side = [\frac{\partial J}{\partial w_0} \frac{\partial J}{\partial w_1}]^T$$

위 hil upper side를 J의 기울기로 부르며

$$\bigtriangledown wJ $$

로 나타냄

J를 최소화 하려면 J의 기울기의 반대방향

$$  - \bigtriangledown_wJ = [\frac{\partial J}{\partial w_0} \frac{\partial J}{\partial w_1}]^T$$

으로 진행하면 좋음. 

w의 갱신방법(학습법칙)을 행렬 표기로 나타내면 다음과 같음

$$w(t+1) = w(t) - \alpha \bigtriangledown _wJ|_{w(t)}$$

일반적으로 편미분 wJ는 w의 함수임. 이 w에 현재의 w의 값 w(t)를 대입한 값을

$$\bigtriangledown wJ | w(t)$$

이라고 표시함. 이 벡터가 지금 있는 지점 w(t)의 기울기를 나타내게 됨. a는 학습율이라고 불리는 양수값을 취하는 매개변수로, w 갱신의 폭을 조절함. 큰 편이 갱신이 커지지만, 수습이 어려워지므로 적당한 값을 선정하는게 좋음.

학습법칙을 성분표기로 나타내면 다음과 같음.

$$w_0(t+1) = w_0(t) -\alpha\frac{\partial J}{\partial w_0}|_{w_0(t), w_1(t)}$$

$$w_1(t+1) = w_1(t) -\alpha\frac{\partial J}{\partial w_1}|_{w_0(t), w_1(t)}$$

구체적으로 상기 편미분을 계산해 보자. 오차 J의 yn부분을 직선(w0xn+w1)으로 변경하면 다음 식과 같음

$$J = \frac{1}{N}\sum_{n=0}^{N-1}(y_n)-t_n^2 = \frac{1}{N}\sum_{n=0}^{N-1}(w_0x_n + w_1-t_n)^2$$

그리고 학습법칙 성분표기 식 1의 w0에 대한 편미분 부분을 연쇄법칙으로 계산하면 다음과 같은 식을 얻음

$$\frac{\partial J}{\partial w_0} = \frac{2}{N}\sum_{n=0}^{N-1}(w_0x_n + w_1 - t_n)x_n = \frac{2}{N}\sum_{n=0}^{N-1}(y_n - t_n)x_n$$

상기 식을 보기 쉽도록 w0xn+w1을 yn으로 되돌림. 같은 방법으로 오차식 J를 w1로 편미분하면 다음 식과 같음

$$\frac{\partial J}{\partial w_1} = \frac{2}{N}\sum_{n=0}^{N-1}(w_0x_n + w_1 - t_n) = \frac{2}{N}\sum_{n=0}^{N-1}(y_n - t_n)$$

따라서 학습법칙을 성분표기로 나타낸 식은 다음과 같음

$$w_0(t+1) = w_0(t) - \alpha \frac{2}{N}\sum_{n=0}^{N-1}(y_n - t_n)x_n$$

$$w_1(t+1) = w_1(t) - \alpha \frac{2}{N}\sum_{n=0}^{N-1}(y_n - t_n)$$

학습법칙이 구체적으로 나타났으므로, 프로그램으로 구현해보자

#평균제곱 오차의 기울기
def dmse_line(x, t, w):
    y = w[0]*x+w[1]
    d_w0 = 2*np.mean((y-t)*x)
    d_w1 = 2*np.mean(y-t)
    return d_w0, d_w1

# 경사하강법
def fit_line_num(x, t):
    w_init = [10.0, 165.0]
    alpha = 0.001 # learning rate
    i_max = 100000 # max iter 
    eps = 0.1 # 반복 종료 기울기의 절대값의 한계
    w_i = np.zeros([i_max, 2])
    w_i[0, :] = w_init
    for i in range(1, i_max):
        dmse = dmse_line(x, t, w_i[i-1])
        w_i[i, 0] = w_i[i-1, 0]-alpha * dmse[0]
        w_i[i, 1] = w_i[i-1, 1]-alpha * dmse[1]
        if max(np.absolute(dmse)) < eps:
            # 종료판정. 
            break
    w0 = w_i[i,0]
    w1 = w_i[i,1]
    w_i = w_i[:i, :]
    return w0, w1, dmse, w_i

# main
plt.figure(figsize=(4,4))
xn = 100 #등고선 해상도
w0_range = [-25, 25]
w1_range = [120, 170]
w0 = np.linspace(w0_range[0], w0_range[1], xn)
w1 = np.linspace(w1_range[0], w1_range[1], xn)
xx0, xx1 = np.meshgrid(x0, x1)
j = np.zeros((len(x0), len(x1)))
for i0 in range(xn):
    for i1 in range(xn):
        j[i1, i0] = mes_line(x, t, (x0[i0], x1[i1]))
cont = plt.contour(xx0, xx1, j, 30, colors='black', levels=(100, 1000, 10000, 100000))
cont.clabel(fmt='%1.0f', fontsize = 8)
plt.grid(True)

# 경사하강법 호출
W0, W1, dMSE, W_history = fit_line_num(x, t)

# result
print('반복횟수 {0}'.format(W_history.shape[0]))
print('W=[{0: .6f}. {1:6f}]'.format(dMSE[0], dMSE[1]))
print('MSE={0:.6f}'.format(mes_line(x, t, [W0, W1])))
plt.plot(W_history[:,0], W_history[:,1], '.-', color = 'gray', markersize = 10, markeredgecolor='cornflowerblue')
plt.show()

평균제곱오차 J의 등고선 위에 w의 갱신의 모습을 푸른선으로 나타냄

처음에는 가파른 계곡으로 진행해 골짜기에 정착하면, 계곡의 중앙부근에서 천천히 나아가서 기울기가 거의 없어지는 지점에 도달하는것을 알 수 있음.

구한 W0, W1이 정말 데이터의 절편과 기울기가 되는지 확인. 

def show_line(w):
    xb = np.linspace(x_min, x_max, 100)
    y = w[0] * xb + w[1]
    plt.plot(xb, y, color=(.5, .5, .5), linewidth=4)

plt.figure(figsize=(4,4))
W = np.array([W0, W1])
mse = mes_line(x, t, W)
print('w0={0:.3f}, w1 = {1:.3f}'.format(W0, W1))
print('SD = {0:.3f} cm'.format(np.sqrt(mse)))
show_line(W)
plt.plot(x, t, marker='o', linestyle = 'None', color='cornflowerblue', markeredgecolor='black')
plt.xlim(x_min, x_max)
plt.grid(True)
plt.show()

직선과 데이터의 차이는 대략 7.00cm 정도 됨.


전체코드(https://github.com/EunGyeongKim/Machine-learning/blob/main/LinearRegression_Gradient_descent.py)

 

GitHub - EunGyeongKim/Machine-learning

Contribute to EunGyeongKim/Machine-learning development by creating an account on GitHub.

github.com

Comments