강의영상

imports

import tensorflow as tf 
import numpy as np
import matplotlib.pyplot as plt 
import tensorflow.experimental.numpy as tnp 
tnp.experimental_enable_numpy_behavior() 

최적화문제

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

그리드서치의 문제점

- 비판1: [-10,10]이외에 해가 존재하면?

  • 이 예제의 경우는 운좋게 [-10,10]에서 해가 존재했음
  • 하지만 임의의 고정된 $x,y$에 대하여 $loss(\beta)=(x\beta-y)^2$ 의 형태의 해가 항상 [-10,10]에서 존재한다는 보장은 없음
  • 해결책: 더 넓게 많은 범위를 탐색하자?

- 비판2: 효율적이지 않음

  • 알고리즘을 요약하면 결국 -10부터 10까지 작은 간격으로 조금씩 이동하며 loss를 조사하는 것이 grid search의 아이디어
  • $\to$ 생각해보니까 $\beta=2$인 순간 $loss=(\frac{1}{2}\beta-1)^2=0$이 되어서 이것보다 작은 최소값은 존재하지 않는다(제곱은 항상 양수이어야 하므로)
  • $\to$ 따라서 $\beta=2$ 이후로는 탐색할 필요가 없다

방법2: gradient descent

알고리즘

(1) 임의의 초기값을 선정하고 loss를 계산한다.

  • $\beta=-5 \to loss(-5)=(-5/2-1)^2=12.25$
(-5/2-1)**2
12.25

(2) 임의의 초기값에서 좌우로 약간씩 이동해보고 loss를 계산한다.

  • 왼쪽으로 이동: $\beta=-5.01,\quad loss(5.01)=12.285025$
  • 오른쪽으로 이동: $\beta=-4.99, \quad loss(-4.99)=12.215025 $
(-5.01 /2 -1)**2
12.285025
(-4.99 /2 -1)**2
12.215025

(3) (2)의 결과를 보고 어느쪽으로 이동하는것이 유리한지 따져보고 유리한 방향으로 이동한다.

  • $\beta=-4.99$ 로 이동

(4) (2)-(3) 의 과정을 반복한다. 왼쪽/오른쪽 모두 가봐도 유리한 지점이 없다면 알고리즘을 멈춘다.

잠깐 알고리즘 감상

- 알고리즘이 멈추는 지점은 $\beta=2$이다. 왜냐하면 이경우 왼쪽으로 가도, 오른쪽으로 가도 현재 손실함수값보다 크기 때문.

- 이 알고리즘은 $loss=(x\beta-y)^2$의 꼴에서 $[-10,10]$ 이외의 지점에 해가 존재하여도 적절하게 해를 찾을 것.

- 또한 비효율적으로 $\beta=2$ 이후에도 탐색을 반복하지 않는다.

- 알고리즘해석

(2)의 의미: 미분을 하라는 뜻

(3)의 의미: update

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

- 미분계수의 의미

  • 미분계수가 양수이다: 왼쪽으로 이동 = 빼기 0.01
  • 미분계수가 음수이다: 오른쪽으로 이동 = 더하기 0.01

- 수식화

$$\beta_{next} = \begin{cases} \beta_{old} - 0.01 & loss'(\beta_{old})>0 \\ \beta_{old} + 0.01 & loss'(\beta_{old})<0 \end{cases}$$

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

- 동일하게 0.01씩 이동하는게 맞을까?

_beta = np.linspace(-10,5)
plt.plot(_beta,(_beta/2-1)**2)
[<matplotlib.lines.Line2D at 0x7f396c6c43d0>]

- 위의 그림에서 $\beta=-10$ 일 경우의 접선의 기울기는 $-6$이고 $\beta=-4$ 일때 접선의 기울기는 $-3$이다.

$\because loss = (0.5\beta-1)^2 \to loss' = 0.5\beta-1$

  • $\beta=-10$에서 0.01만큼 이동했다면 $\beta=-4$에서 0.005만큼 이동해야함

- 아이디어를 수식화하자!

$$\beta_{next} \leftarrow \beta_{old} -\alpha \left[\frac{\partial}{\partial \beta} loss(\beta)\right]_{\beta=\beta_{old}}$$

  • 아까 수식이랑 좀 다르다? 달라보이지만 $\beta_{old}$를 이동시켜 $\beta_{next}$를 만든다는 개념은 같음
  • $\alpha>0$
  • $\alpha$의 의미: 한번 업데이트할때 움직이는 보폭
  • $\alpha=\frac{0.01}{6}$ 로 만약 설정하면 $\beta=-10$일때 오른쪽으로 0.01움직임

(개선한 알고리즘을 이용한 풀이)

iter1: beta=-10 출발

beta = tf.Variable(-10.0)
2022-03-28 22:10:20.721327: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:939] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
with tf.GradientTape(persistent=True) as tape: 
    loss = (beta/2-1)**2 
tape.gradient(loss,beta)
<tf.Tensor: shape=(), dtype=float32, numpy=-6.0>
alpha=0.01/6 
beta.assign_sub(alpha*tape.gradient(loss,beta))
<tf.Variable 'UnreadVariable' shape=() dtype=float32, numpy=-9.99>

iter2 beta=-9.99

beta
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=-9.99>
with tf.GradientTape(persistent=True) as tape: 
    loss = (beta/2-1)**2 
beta.assign_sub(alpha*tape.gradient(loss,beta))
<tf.Variable 'UnreadVariable' shape=() dtype=float32, numpy=-9.980008>
  • 왜 tf.Variable의 메소드에 assign_add, assign_sub 정도만 있는지?
  • persistenct도 왜 디폴트가 False인지?

for문

(수업용)

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

(시도1)

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

(시도2)

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

학습률

- 목표: 아래의 학습과정을 시각화해보자.

beta = tf.Variable(-10.0) 
alpha=0.01/6 

for k in range(100): 
    with tf.GradientTape(persistent=True) as tape: 
        loss = (beta/2-1)**2 
    beta.assign_sub(alpha*tape.gradient(loss,beta))

beta
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=-9.040152>
[시각화코드 예비학습]

- 환경설정

plt.rcParams["animation.html"]="jshtml"
from matplotlib import animation 

- 도화지와 네모틀 생성

fig = plt.figure() # fig 는 도화지 
<Figure size 432x288 with 0 Axes>
ax = fig.add_subplot() # ax 네모틀
fig

- 도화지와 네모틀는 포함관계에 있음.

fig.axes
[<AxesSubplot:>]
id(fig.axes[0])
139884400738752
id(ax)
139884400738752

- 네모틀(ax)의 특수기능(=메소드)중에는 plot이 있음. 이것은 또 어떤 오브젝트를 생성함

pnts, = ax.plot([1,2,3],[3,4,5],'or') 
pnts
<matplotlib.lines.Line2D at 0x7f39600862f0>
fig

- pnts 오브젝트: x,y data를 변경해보자.

pnts.get_xdata()
array([1, 2, 3])
pnts.get_ydata()
array([3, 4, 5])
pnts.set_ydata([4,4,4])
pnts.get_ydata()
[4, 4, 4]
fig

- 에니매이션

def animate(i): 
    if i%2 == 0:
        pnts.set_ydata([3,4,5])
    else: 
        pnts.set_ydata([4,4,4])
ani=animation.FuncAnimation(fig,animate,frames=30)
ani
</input>

예비학습끝

- 다시 학습과정 시각화 문제로 돌아오자.

beta_lst = [-10.0,-9.00,-8.00] 
loss_lst = [(-10.0/2-1)**2,(-9.00/2-1)**2,(-8.00/2-1)**2]
fig = plt.figure() 
ax = fig.add_subplot() 
_beta= np.linspace(-15,19)
ax.plot(_beta,(_beta/2-1)**2) 
[<matplotlib.lines.Line2D at 0x7f3950174040>]
fig
pnts, = ax.plot(beta_lst[0],loss_lst[0],'ro')
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
</input>

- 학습과정을 beta_lst, loss_lst로 저장하자.

beta_lst = [] 
loss_lst = [] 
beta = tf.Variable(-10.0) 
alpha=0.01/6 
beta.numpy()
-10.0
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: 
        loss = (beta/2-1)**2 
    beta.assign_sub(alpha*tape.gradient(loss,beta))
    beta_lst.append(beta.numpy()) 
    loss_lst.append((beta.numpy()/2-1)**2) 
fig = plt.figure() # fig 는 도화지 
<Figure size 432x288 with 0 Axes>
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
</input>

- alpha를 조정한다!! ($\alpha=0.1$)

beta_lst = [] 
loss_lst = [] 
beta = tf.Variable(-10.0) 
alpha=0.1
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: 
        loss = (beta/2-1)**2 
    beta.assign_sub(alpha*tape.gradient(loss,beta))
    beta_lst.append(beta.numpy()) 
    loss_lst.append((beta.numpy()/2-1)**2) 
fig = plt.figure() # fig 는 도화지 
<Figure size 432x288 with 0 Axes>
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
</input>

- alpha를 더 크게하면? ($\alpha=1$)

beta_lst = [] 
loss_lst = [] 
beta = tf.Variable(-10.0) 
alpha=1
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: 
        loss = (beta/2-1)**2 
    beta.assign_sub(alpha*tape.gradient(loss,beta))
    beta_lst.append(beta.numpy()) 
    loss_lst.append((beta.numpy()/2-1)**2) 
fig = plt.figure() # fig 는 도화지 
<Figure size 432x288 with 0 Axes>
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
</input>

- alpha는 학습속도를 의미함. $\to$ 빨리배우는게 좋으니까 학습률이 크면 무조건 좋은거아닌가? $\to$ 아니에용

(예시1) 너무 큰 학습률의 비효율성 ($\alpha=3.9$)

beta_lst = [] 
loss_lst = [] 
beta = tf.Variable(-10.0) 
alpha=3.9
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: 
        loss = (beta/2-1)**2 
    beta.assign_sub(alpha*tape.gradient(loss,beta))
    beta_lst.append(beta.numpy()) 
    loss_lst.append((beta.numpy()/2-1)**2) 
fig = plt.figure() # fig 는 도화지 
<Figure size 432x288 with 0 Axes>
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
</input>

(예시2) 너무 큰 학습률의 위험성 ($\alpha=4.05$)

beta_lst = [] 
loss_lst = [] 
beta = tf.Variable(-10.0) 
alpha=4.05
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: 
        loss = (beta/2-1)**2 
    beta.assign_sub(alpha*tape.gradient(loss,beta))
    beta_lst.append(beta.numpy()) 
    loss_lst.append((beta.numpy()/2-1)**2) 
fig = plt.figure() # fig 는 도화지 
<Figure size 432x288 with 0 Axes>
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
</input>

숙제

경사하강법을 이용하여 $y=(x-1)^2$의 최소값을 구하고 이를 애니메이션으로 시각화하라. (100번정도에 수렴하도록 적당한 학습률을 설정할것)