빅데이터 분석 특강 (4주차) 3월 28일
import tensorflow as tf
import numpy as np
import tensorflow.experimental.numpy as tnp
import matplotlib.pyplot as plt
tf.config.list_physical_devices('GPU')
tnp.experimental_enable_numpy_behavior()
-
예제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
loss 정의하고 싶다.
beta0 = tf.Variable(9.0)
beta1 = tf.Variable(2.0)
차이
tf.Variable(3.0)
tf.Variable([3.0])
with tf.GradientTape(persistent=True) as tape:
loss = sum((y-beta0-beta1*x)**2)
tape.gradient(loss,beta0), tape.gradient(loss,beta1)
-
예제10: 카페예제의 매트릭스 버전
$\star$ 시험에 나와요 🍟
내 version...
X=tf.transpose(tf.concat([[[1.0]*10],[x]],0))
교수님 version..
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
beta = tnp.array([9.0,2.0]).reshape(2,1)
beta
같은 방법
beta = tnp.array([9.0],[2.0])
beta_true = tnp.array([10.0],[2.2])
X@beta
beta_true= tnp.array([10.2,2.2]).reshape(2,1)
y= X@beta_true+epsilon.reshape(10,1)
y
epsilon 정의 안 하고 하는 법
y = X@beta_true + tf.random.randn(10).reshape(10,1)
with tf.GradientTape(persistent=True) as tape:
tape.watch(beta)
yhat= X@beta
loss= (y-yhat).T @(y-yhat)
-
텐서플로우가 계산한 미분값
tape.gradient(loss,beta)
-
이론적인 값을 확인
$loss = -2\bf{X}^\top \bf{y} + 2\bf{X}^\top \bf{XB}$
-2*X.T @ y + 2*X.T@X@beta
-
예제11: 위의 예제에서 이론적인 $\boldsymbol{\beta}$의 최적값을 찾아보고 (즉 $\boldsymbol{\hat\beta}$을 찾고) 그 지점에서 loss의 미분값(=접선의 기울기)를 구하라. 결과가 $\bf{0}$인지 확인하라. (단 ${\bf 0}$은 길이가 2이고 각 원소가 0인 벡터)
$\star$ 시험에 나와요 🍟
이변량..loss를 미분해서 도함수를 구하고 베타를 대입..
$\beta$의 최적값은 $(X'X)^{-1}X'y$이다.
$\hat{\bf{B}} = (\bf{X}^\top \bf{X})^{-1}\bf{X}^\top \bf{y}$
beta_optimal = tf.linalg.inv(X.T @ X) @ X.T @y # 이보다 loss를 작게 만드는 beta는 없다.
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)
-
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)
-
샘플사이즈가 커진다면 tape.gradient(loss,beta_true)
값과 tape.gradient(loss,beta_optimal)
값이 비슷해진다.
-
$\therefore$ 샘플사이즈가 커진다면 beta_true
$\approx$ beta_optimal
과 같은 말
-
y를 최소화하는 x를 구해라 $\approx$ loss를 최소화하는 beta를 구해라
-
$loss=(\frac{1}{2}\beta-1)^2$를 최소하는 $\beta$를 컴퓨터를 활용하여 구하는 문제를 생각해보자.
-
답은 $\beta = 2$ 임을 알고 있다.
-
beta를 수없이 만들어내서 계산을 해보자
(1) beta = [-10.00,-9.99,...,10.00] 와 같은 리스트를 만든다.
(2) (1)의 리스트의 각 원소에 해당하는 loss(=$(\frac{1}{2}\beta - 1)^2$)를 구한다.
(3) (2)에서 구한 loss를 제일 작게 만드는 beta를 찾는다.
beta = np.linspace(-10,10,100)
loss = (beta/2 -1)**2
loss
-
tnp.argmin
tnp.argmin([1,2,-3,3,4])
tnp.argmin([1,2,3,-3,4])
작은 값의 위치를 리턴해주는 argmin
tnp.argmin(loss)
beta[59]
우리는 최적값이 2라는 것을 알고 있지, 잘 구했네~
정말 59번째일까?
(beta[59]/2-1)**2
(beta[60]/2-1)**2
뭐가 좋은지 어떻게 알아?
- loss 직접 계산해보면 되지~ 59번째가 loss값이 더 작잖아
- 이로써 우리는 loss 최적값의 위치에서의 beta가 가장 최적의 값이라고 결론내릴 수 있음
-
비판1: [-10,10]이외에 해가 존재하면?
- 이 예제의 경우는 운좋게 [-10,10]에서 해가 존재했음
- 하지만 임의의 고정된 $x,y$에 대하여 $loss(\beta)=(x\beta-y)^2$ 의 형태의 해가 항상 [-10,10]에서 존재한다는 보장은 없음
- 해결책: 더 넓게 많은 범위를 탐색하자?
- -100 ~ 100으로 범위 잡던가~ but, 완전한 해결은 하지 못해..
-
비판2: 효율적이지 않음
- 알고리즘을 요약하면 결국 -10부터 10까지 작은 간격으로 조금씩 이동하며 loss를 조사하는 것이 grid search의 아이디어
- $\to$ 생각해보니까 $\beta=2$인 순간 $loss=(\frac{1}{2}\beta-1)^2=0$이 되어서 이것보다 작은 최소값은 존재하지 않는다(제곱은 항상 양수이어야 하므로)
- $\to$ 따라서 $\beta=2$ 이후로는 탐색할 필요가 없다
(1) 임의의 초기값(beta = -5로)을 선정하고 loss를 계산한다.
- $\beta=-5 \to loss(-5)=(-5/2-1)^2=12.25$
(-5/2-1)**2
(2) 임의의 초기값(beta = -5)에서 좌우로 약간씩 이동해보고 loss를 계산한다.(beta = -5 주변에서 조금씩 움직여보면서 계산해보자)
- 왼쪽으로 이동: $\beta=-5.01,\quad loss(-5.01)= 12.285025$
- 오른쪽으로 이동: $\beta=-4.99, \quad loss(-4.99)= 12.215025$
즉 미분...
(-5.01/2-1)**2
(-4.99/2-1)**2
-4.99가 최적값이다!( 말 바꾸기)
조금씩 움직이면서 말 계속 바꿔...
(3) (2)의 결과를 보고 어느쪽으로 이동하는것이 유리한지 따져보고 유리한 방향으로
이동한다.
- $\beta=-4.99$ 로 이동
(4) (2)-(3) 의 과정을 반복한다. 왼쪽/오른쪽 모두 가봐도 유리한 지점이 없다면(이득이 없다면) 알고리즘을 멈춘다.
알고리즘 분석
-
(2)-(3)의 과정은 beta=-5에서 미분계수를 구하고 미분계수가 양수이면 왼쪽으로 움직이고, 미분계수가 음수이면 오른쪽으로 움직인다
고 해석 가능. 아래 그림을 보면 더 잘 이해가 된다.
plt.plot(beta,loss)
-
정지조건?
알고리즘이 멈추는 지점은 $\beta=2$이다. 왜냐하면 이경우 왼쪽으로 가도, 오른쪽으로 가도 현재 손실함수값보다 크기 때문
-
[-10,10] 이외에 해가 존재?
이 알고리즘은 $loss=(x \beta-y)^2$의 꼴에서 $[-10,10]$ 이외의 지점에 해가 존재하여도 적절하게 해를 찾을 것.
-
$\beta=2$를 찾았다면?
또한 비효율적으로 $\beta=2$ 이후에도 탐색을 반복하지 않는다
-
알고리즘해석
- (2)의 과정: 미분을 하라는 뜩
- (3)의 과정: update
-
아래와 같이 해석 가능
- 오른쪽으로 0.01 간다 = $\beta_{old}$에 0.01을 더함. (if, 미분계수가 음수)
- 왼쪽으로 0.01 간다 = $\beta_{old}$에 0.01을 뺌. (if, 미분계수가 양수)
-
수식화
$\beta_{new} = \begin{cases} \beta_{old} + 0.01, & loss'(\beta_{old}) <0 \\ \beta_{old} - 0.01,& loss'(\beta_{old})>0 \end{cases}$
-
왜 0.01씩? 항상 동일하게 0.01 씩 움직여야 할까?
plt.plot(beta,loss)
-
아이디어
보폭을 조정해보자, 최적값에 가까우면 보폭을 작게
-
$\beta=-10$ 일 경우의 접선의 기울기? $\beta=-4$ 일때 접선의 기울기?
- $\beta = 10$ $\rightarrow$ 기울기는 -6
(-10/2-1)
- $\beta = 4$ $\rightarrow$ 기울기는 -3
(-4/2-1)
$\therefore loss=(0.5 \beta−1)^2 \rightarrow loss ′ =0.5\beta−1$
- $β=−10$에서 $0.01$만큼 이동했다면 $\beta=−4$에서 $0.005$만큼 이동해야함
-
실제로 6,3씩 이동할 수는 없으니 적당한 $\alpha$를 잡아서 곱한만큼 이동하자
-
수식화
결국은 위 식과 같음, 단, 알파가 양수여야 함
- $\beta_{new} = \beta_{old} - \alpha loss' (\beta_{old})$
- $\beta_{new} = \beta_{old} - \alpha \big[ \frac{\partial}{\partial \alpha}loss(\beta) \big]_{\beta = \beta_{old}}$
위의 식에서 아래는 벡터버전 식임
-
식의 의미
- 아까 수식이랑 좀 다르다? 달라보이지만 $\beta_{old}$를 이동시켜 $\beta_{next}$를 만든다는 개념은 같음
- $\alpha$가 크면 큰 보폭으로 움직이고 작으면 작은 보폭으로 움직인다.
- $\alpha>0$ 이어야 한다.
- 음수면 loss를 최대화하는 식이 되어버림..log likelihood 에서 사용 가능하겠지..
- $\alpha$의 의미: 한번 업데이트할때 움직이는 보폭
- $\beta=-10$ 에서 $0.01$만큼 움직이기 위한 $\alpha$의 설정;
- $\alpha = \frac{0.01}{6}$
-
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)
직접 풀었던 위의 값이랑 같네!
$\beta = -10$ 에서 0.01 만큼 움직이고 싶다.
alpha = 0.01/6
alpha * tape.gradient(loss,beta)
beta
왜 tf.Variable의 메소드에 assign_add, assign_sub 정도만 있는지?
- assign 은 초기화하고 싶을떄, add와 sub만 필요해서 두 개 밖에..
beta.assign_sub(alpha * tape.gradient(loss,beta))
beta
-
iter 2:
with tf.GradientTape(persistent=True) as tape: # -9,99인 상태로 미분 다시하려고
loss = (beta/2-1)**2
beta.assign_sub(tape.gradient(loss,beta) * alpha) # 더 나은 베터가 나왔다.
persistenct도 왜 디폴트가 False인지?
- Vriable 기본?(관찰함)
- constant 관찰 안 함
-
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
(시도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
(시도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
-
너무 느린 것 같다? $\to$ $\alpha$ 를 키워보자
학습률
-
$\alpha$에 따라서 수련과정이 어떻게 달라지는지 시각화 해보자
모든 것을 객체화하는 것이 가능할까..
- 도화지와 네모틀 생성
fig = plt.figure() # 도화지가 만들어지고 fig라는 이름을 붙인다.
fig
ax = fig.add_subplot() # fig는 ax라는 네모틀(물체)를 만든다.
fig
type(fig.axes)
- 도화지와 네모틀는 포함관계에 있음.
id(fig.axes[0]) # 여기 저장되어 있군..
id(ax) # 똑같네..
pnts = ax.plot([1,2,3],[4,5,6],'or')
pnts # 리스트
- 네모틀(ax)의 특수기능(=메소드)중에는 plot이 있음. 이것은 또 어떤 오브젝트를 생성함
pnts, = ax.plot([1,2,3],[4,5,6],'or')
pnts
fig
a =1,
a+a
a,=[1]
a
- pnts 오브젝트: x,y data를 변경해보자.
pnts.get_xdata()
pnts.get_ydata()
pnts.set_ydata([5,5,5])
pnts.get_ydata()
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=30) # 도화지 전달, 어떻게 전달할지 룰 전달
ani
예비학습 끝
다시 학습과정 시각화 문제로 돌아오자.
-
beta_lst = [-10,-9,-8] 로 이동한다고 하자
beta_lst = [-10.0,-9.0,-8.0]
loss_lst = [(-10.0/2-1)**2, (-9.0/2-1)**2, (-8.0/2-1)**2]
fig = plt.figure()
_beta = np.linspace(-15,19,100)
ax = fig.add_subplot()
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.numpy() # 넘파이 화, 이전 시간 배운 거
beta_lst = []
loss_lst = []
beta.numpy()
beta_lst.append(beta.numpy())
loss_lst.append(((beta.numpy()/2-1)**2))
with tf.GradientTape(persistent=True) as tape:
tape.watch(beta) # variable로 잡아서 안 해도 되긴 하지만..
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)
beta_lst, loss_lst
-
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번의 반복안에 수렴하도록 하라.
fig = plt.figure()
_x = np.linspace(-8,10,100)
ax = fig.add_subplot()
ax.plot(_x,(_x-1)**2)
$\frac{d}{dx}y = 2(x-1)$
x = tf.Variable(-3.0)
alpha=0.1/(8)
x_lst=[]
y_lst=[]
x_lst.append(x.numpy())
y_lst.append((x.numpy()-1)**2)
for k in range(100):
with tf.GradientTape(persistent=True) as tape:
tape.watch(x)
y = (x-1)**2
x.assign_sub(tape.gradient(y,x) * alpha)
x_lst.append(x.numpy())
y_lst.append((x.numpy()-1)**2)
fig = plt.figure()
ax = fig.add_subplot()
ax.plot(_x,(_x-1)**2)
pnts, = ax.plot(x_lst[0], y_lst[0],'or')
def animate(i):
pnts.set_xdata(x_lst[:(i+1)])
pnts.set_ydata(y_lst[:(i+1)])
ani =animation.FuncAnimation(fig,animate,frames=100)
ani