[STBDA] 04wk: tensorflow_경사하강법

Author

김보람

Published

June 19, 2023

해당 강의노트는 전북대학교 최규빈교수님 STBDA2022 자료임

imports

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
2023-06-19 15:29:42.043174: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
import tensorflow.experimental.numpy as tnp
tnp.experimental_enable_numpy_behavior()

미분

tf.GradientTape() 사용방법

- 예제9: 카페예제로 돌아오자.

x= tnp.array([20.1, 22.2, 22.7, 23.3, 24.4, 25.1, 26.2, 27.3, 28.4, 30.4])
tf.random.set_seed(43052)
epsilon=tf.random.normal([10])
y=10.2 + 2.2*x + epsilon
y
<tf.Tensor: shape=(10,), dtype=float64, numpy=
array([55.4183651 , 58.19427589, 61.23082496, 62.31255873, 63.1070028 ,
       63.69569103, 67.24704924, 71.4365008 , 73.10130336, 77.84988286])>
beta0 = tf.Variable(9.0)
beta1 = tf.Variable(2.0)
with tf.GradientTape(persistent=True) as tape:
    loss = sum((y-beta0-beta1*x)**2)
tape.gradient(loss,beta0)
<tf.Tensor: shape=(), dtype=float32, numpy=-126.78691>
tape.gradient(loss,beta1)
<tf.Tensor: shape=(), dtype=float32, numpy=-3208.8394>

- 예제10: 카페예제의 매트릭스 버전

X= tnp.array([1]*10 +[20.1, 22.2, 22.7, 23.3, 24.4, 25.1, 26.2, 27.3, 28.4, 30.4]).reshape(2,10).T
X
<tf.Tensor: shape=(10, 2), dtype=float64, numpy=
array([[ 1. , 20.1],
       [ 1. , 22.2],
       [ 1. , 22.7],
       [ 1. , 23.3],
       [ 1. , 24.4],
       [ 1. , 25.1],
       [ 1. , 26.2],
       [ 1. , 27.3],
       [ 1. , 28.4],
       [ 1. , 30.4]])>
beta= tnp.array([9.0,2.0]).reshape(2,1)
beta
<tf.Tensor: shape=(2, 1), dtype=float64, numpy=
array([[9.],
       [2.]])>
X@beta
<tf.Tensor: shape=(10, 1), dtype=float64, numpy=
array([[49.2],
       [53.4],
       [54.4],
       [55.6],
       [57.8],
       [59.2],
       [61.4],
       [63.6],
       [65.8],
       [69.8]])>
beta_true= tnp.array([10.2,2.2]).reshape(2,1)
y= X@beta_true+epsilon.reshape(10,1)
y
<tf.Tensor: shape=(10, 1), dtype=float64, numpy=
array([[55.4183651 ],
       [58.19427589],
       [61.23082496],
       [62.31255873],
       [63.1070028 ],
       [63.69569103],
       [67.24704924],
       [71.4365008 ],
       [73.10130336],
       [77.84988286]])>
with tf.GradientTape(persistent=True) as tape:
    tape.watch(beta)
    yhat= X@beta
    loss= (y-yhat).T @(y-yhat)
tape.gradient(loss,beta)
<tf.Tensor: shape=(2, 1), dtype=float64, numpy=
array([[ -126.78690956],
       [-3208.83947584]])>

- 이론적인 값을 확인하면

loss = (y-Xb)’(y-Xb) = -2X’y + 2X’Xb

-2*X.T @ y + 2*X.T@X@beta
<tf.Tensor: shape=(2, 1), dtype=float64, numpy=
array([[ -126.78690956],
       [-3208.83947584]])>

- 예제11: 위의 예제에서 이론적인 \(\boldsymbol{\beta}\)의 최적값을 찾아보고 (즉 \(\boldsymbol{\hat\beta}\)을 찾고) 그 지점에서 loss의 미분값(=접선의 기울기)를 구하라. 결과가 \(\bf{0}\)인지 확인하라. (단 \({\bf 0}\)은 길이가 2이고 각 원소가 0인 벡터)

\(\beta\)의 최적값은 \((X'X)^{-1}X'y\)이다.

beta_optimal = tf.linalg.inv(X.T @ X) @ X.T  @y
with tf.GradientTape(persistent=True) as tape:
    tape.watch(beta_optimal)
    yhat= X@beta_optimal
    loss= (y-yhat).T @(y-yhat)
tape.gradient(loss,beta_optimal)
<tf.Tensor: shape=(2, 1), dtype=float64, numpy=
array([[-1.05160325e-12],
       [-2.64108735e-11]])>

- beta_true에서의 기울기도 계산해보자.

with tf.GradientTape(persistent=True) as tape:
    tape.watch(beta_true)
    yhat= X@beta_true
    loss= (y-yhat).T @(y-yhat)
tape.gradient(loss,beta_true)
<tf.Tensor: shape=(2, 1), dtype=float64, numpy=
array([[ -2.74690956],
       [-71.45947584]])>
  • 샘플사이즈가 커진다면 tape.gradient(loss,beta_true) \(\approx\) tape.gradient(loss,beta_optimal)
  • 샘플사이즈가 커진다면 beta_true \(\approx\) beta_optimal

경사하강법

최적화문제

- \(loss=(\frac{1}{2}\beta-1)^2\)를 최소하는 \(\beta\)를 컴퓨터를 활용하여 구하는 문제를 생각해보자. - 답은 \(\beta=2\)임을 알고 있다.

방법2: gradient descent

알고리즘!

  1. beta = -5 로 셋팅한다.
(-5/2-1)**2
12.25
  1. beta=-5 근처에서 조금씩 이동하여 loss를 조사해본다.
(-4.99/2-1)**2 ## 오른쪽으로 0.01 이동하고 loss조사
12.215025
(-5.01/2-1)**2 ## 왼쪽으로 0.01 이동하고 loss조사
12.285025
  1. (2)의 결과를 잘 해석하고 더 유리한 쪽으로 이동

  2. 위의 과정을 반복하고 왼쪽, 오른쪽 어느쪽으로 움직여도 이득이 없다면 멈춘다.

알고리즘 분석

- (2)-(3)의 과정은 beta=-5 에서 미분계수를 구하고 미분계수가 양수이면 왼쪽으로 움직이고 음수이면 오른쪽으로 움직인다고 해석가능. 아래그림을 보면 더 잘 이해가 된다.

plt.plot(beta,loss)

왼쪽/오른쪽중에 어디로 갈지 어떻게 판단하는 과정을 수식화?

- 아래와 같이 해석가능

  • 오른쪽으로 0.01 간다 = beta_old에 0.01을 더함. (if, 미분계수가 음수)
  • 왼쪽으로 0.01 간다. = beta_old에 0.01을 뺀다. (if, 미분계수가 양수)
- 그렇다면 $_{new} = \[\begin{cases} \beta_{old} + 0.01, & loss'(\beta_{old})< 0 \\ \beta_{old} - 0.01, & loss'(\beta_{old})> 0 \end{cases}\]

$

혹시 알고리즘을 좀 개선할수 있을까?

- 항상 0.01씩 움직여야 하는가?

plt.plot(beta,loss)

- \(\beta=-10\) 일 경우의 접선의 기울기? \(\beta=-4\) 일때 접선의 기울기?

  • \(\beta=-10\) => 기울기는 -6
  • \(\beta=-4\) => 기울기는 -3

- 실제로 6,3씩 이동할순 없으니 적당한 \(\alpha\) (예를들면 \(\alpha=0.01\)) 를 잡아서 곱한만큼 이동하자.

- 수식화하면

  • \(\beta_{new} = \beta_{old} - \alpha~ loss'(\beta_{old})\)
  • \(\beta_{new} = \beta_{old} - \alpha~ \left[\frac{\partial}{\partial \beta }loss(\beta)\right]_{\beta=\beta_{old}}\)

- \(\alpha\)의 의미 - \(\alpha\)가 크면 크게크게 움직이고 작으면 작게작게 움직인다. - \(\alpha>0\) 이어야 한다.

구현코드

- iter 1

\(\beta=-10\)이라고 하자.

beta = tf.Variable(-10.0)
with tf.GradientTape(persistent=True) as tape:
    loss = (beta/2-1)**2
tape.gradient(loss,beta)
<tf.Tensor: shape=(), dtype=float32, numpy=-6.0>

\(\beta = -10\) 에서 0.01만큼 움직이고 싶음

alpha= 0.01/6
alpha * tape.gradient(loss,beta)
<tf.Tensor: shape=(), dtype=float32, numpy=-0.01>
beta.assign_sub(alpha * tape.gradient(loss,beta))
<tf.Variable 'UnreadVariable' shape=() dtype=float32, numpy=-9.99>
beta
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=-9.99>

- iter2

with tf.GradientTape(persistent=True) as tape:
    loss = (beta/2-1)**2
beta.assign_sub(tape.gradient(loss,beta)*alpha)
<tf.Variable 'UnreadVariable' shape=() dtype=float32, numpy=-9.980008>

- for 문을 이용하자.

(강의용)

beta = tf.Variable(-10.0)
for k in range(10000):
    with tf.GradientTape(persistent=True) as tape:
        loss = (beta/2-1)**2
    beta.assign_sub(tape.gradient(loss,beta)*alpha)
beta
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=1.9971251>

(시도1)

beta = tf.Variable(-10.0)
for k in range(100):
    with tf.GradientTape(persistent=True) as tape:
        loss = (beta/2-1)**2
    beta.assign_sub(tape.gradient(loss,beta)*alpha)
beta
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=-9.040152>

(시도2)

beta = tf.Variable(-10.0)
for k in range(1000):
    with tf.GradientTape(persistent=True) as tape:
        loss = (beta/2-1)**2
    beta.assign_sub(tape.gradient(loss,beta)*alpha)
beta
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=-3.2133684>

- 너무 느린 것 같다? \(\to\) \(\alpha\)를 키워보자!

학습률

- 목표: \(\alpha\)에 따라서 수렴과정이 어떻게 달라지는 시각화해보자.

[시각화 코드 예비학습]
fig = plt.figure() # 도화지가 만들어지고 fig라는 이름을 붙인다.
<Figure size 640x480 with 0 Axes>
ax = fig.add_subplot() # fig는 ax라는 물체를 만든다.
id(fig.axes[0])
139963527032544
id(ax)
139963527032544
pnts, = ax.plot([1,2,3],[4,5,6],'or')
pnts
<matplotlib.lines.Line2D at 0x7f4bcc48baf0>
  • pnts뒤에 ‘콤마’ 붙임-> 튜플
pnts.get_xdata()
array([1, 2, 3])
pnts.get_ydata()
array([4, 5, 6])
fig

pnts.set_ydata([5,5,5])
pnts.get_ydata()
[5, 5, 5]
fig

- 응용

plt.rcParams["animation.html"]="jshtml"
from matplotlib import animation
def animate(i):
    if i%2 == 0:
        pnts.set_ydata([4,5,6])
    else:
        pnts.set_ydata([5,5,5])
ani = animation.FuncAnimation(fig,animate,frames=10)
ani

예비학습 끝

- beta_lst=[-10,-9,-8] 로 이동한다고 하자.

beta_lst = [-10,-9,-8]
loss_lst = [(-10/2-1)**2,(-9/2-1)**2,(-8/2-1)**2]
fig = plt.figure()
<Figure size 640x480 with 0 Axes>
ax= fig.add_subplot()
_beta = np.linspace(-15,19,100)
ax.plot(_beta,(_beta/2-1)**2)
fig

pnts, = ax.plot(beta_lst[0],loss_lst[0],'ro')
fig

def animate(i):
    pnts.set_xdata(beta_lst[:(i+1)])
    pnts.set_ydata(loss_lst[:(i+1)])
ani =animation.FuncAnimation(fig, animate, frames=3)
ani

- 최종아웃풋

beta = tf.Variable(-10.0)
alpha = 0.01/6
beta_lst=[]
loss_lst=[]
beta_lst.append(beta.numpy())
loss_lst.append((beta.numpy()/2-1)**2)
with tf.GradientTape(persistent=True) as tape:
    tape.watch(beta)
    loss = (beta/2-1)**2
beta.assign_sub(tape.gradient(loss,beta)*alpha)
<tf.Variable 'UnreadVariable' shape=() dtype=float32, numpy=-9.99>
beta_lst.append(beta.numpy())
loss_lst.append((beta.numpy()/2-1)**2)
beta_lst, loss_lst
([-10.0, -9.99], [36.0, 35.94002362785341])

- for

beta = tf.Variable(-10.0)
alpha = 0.01/6
beta_lst=[]
loss_lst=[]
beta_lst.append(beta.numpy())
loss_lst.append((beta.numpy()/2-1)**2)
for k in range(100):
    with tf.GradientTape(persistent=True) as tape:
        tape.watch(beta)
        loss = (beta/2-1)**2
    beta.assign_sub(tape.gradient(loss,beta)*alpha)
    beta_lst.append(beta.numpy())
    loss_lst.append((beta.numpy()/2-1)**2)
fig = plt.figure()
ax = fig.add_subplot()
ax.plot(_beta,(_beta/2-1)**2)
pnts, = ax.plot(beta_lst[0],loss_lst[0],'or')

ani = animation.FuncAnimation(fig,animate,frames=100)
ani

숙제

\(y=(x-1)^2\)를 최소화 하는 \(x\)를 경사하강법을 이용하여 찾아라. 수렴과정을 animation으로 시각화하라. - x의 초기값은 -3으로 설정한다. - 적당한 \(\alpha\)를 골라서 100번의 반복안에 수렴하도록 하라.