imports

import numpy as np
import matplotlib.pyplot as plt 
import tensorflow as tf 
import tensorflow.experimental.numpy as tnp 
tnp.experimental_enable_numpy_behavior()
import graphviz
def gv(s): return graphviz.Source('digraph G{ rankdir="LR"'+s + '; }')

piece-wise linear regression

The basic idea behind piecewise linear regression is that if the data follow different linear trends over different regions of the data then we should model the regression function in "pieces." The pieces can be connected or not connected. Here, we'll fit a model in which the pieces are connected.

model: $y_i=\begin{cases} x_i +0.3\epsilon_i & x\leq 0 \\ 3.5x_i +0.3\epsilon_i & x>0 \end{cases}$

np.random.seed(43052)
N=100
x = np.linspace(-1,1,N)
lamb = lambda x: x*1+np.random.normal()*0.3 if x<0 else x*3.5+np.random.normal()*0.3 
y= np.array(list(map(lamb,x)))
y
array([-0.88497385, -0.65454563, -0.61676249, -0.84702584, -0.84785569,
       -0.79220455, -1.3777105 , -1.27341781, -1.41643729, -1.26404671,
       -0.79590224, -0.78824395, -0.86064773, -0.52468679, -1.18247354,
       -0.29327295, -0.69373049, -0.90561768, -1.07554911, -0.7225404 ,
       -0.69867774, -0.34811037,  0.11188474, -1.05046296, -0.03840085,
       -0.38356861, -0.24299798, -0.58403161, -0.20344022, -0.13872303,
       -0.529586  , -0.27814478, -0.10852781, -0.38294596,  0.02669763,
       -0.23042603, -0.77720364, -0.34287396, -0.04512022, -0.30180793,
       -0.26711438, -0.51880349, -0.53939672, -0.32052379, -0.32080763,
        0.28917092,  0.18175206, -0.48988124, -0.08084459,  0.37706178,
        0.14478908,  0.07621827, -0.071864  ,  0.05143365,  0.33932009,
       -0.35071776,  0.87742867,  0.51370399,  0.34863976,  0.55855514,
        1.14196717,  0.86421076,  0.72957843,  0.57342304,  1.54803332,
        0.98840018,  1.11129366,  1.42410801,  1.44322465,  1.25926455,
        1.12940772,  1.46516829,  1.16365096,  1.45560853,  1.9530553 ,
        2.45940445,  1.52921129,  1.8606463 ,  1.86406718,  1.5866523 ,
        1.49033473,  2.35242686,  2.12246412,  2.41951931,  2.43615052,
        1.96024441,  2.65843789,  2.46854394,  2.76381882,  2.78547462,
        2.56568465,  3.15212157,  3.11482949,  3.17901774,  3.31268904,
        3.60977818,  3.40949166,  3.30306495,  3.74590922,  3.85610433])
plt.plot(x,y,'.')
[<matplotlib.lines.Line2D at 0x7f2035cff4c0>]

풀이1: 단순회귀모형

x= x.reshape(N,1)
y= y.reshape(N,1) 

보통 검색해보면 net 대신 model로 변수 name을 설정하고는 한다.

net = tf.keras.Sequential()
net.add(tf.keras.layers.Dense(1)) 
net.compile(optimizer=tf.optimizers.SGD(0.1),loss='mse')
net.fit(x,y,batch_size=N,epochs=1000,verbose=0) # numpy로 해도 돌아감
<keras.callbacks.History at 0x7f200c4918a0>

위 주석의 뜻은 $x,y$가 꼭 tensor 형태가 아니어도, numpy 형태여도 돌아간다는 뜻

net.weights
[<tf.Variable 'dense_1/kernel:0' shape=(1, 1) dtype=float32, numpy=array([[2.2616348]], dtype=float32)>,
 <tf.Variable 'dense_1/bias:0' shape=(1,) dtype=float32, numpy=array([0.6069048], dtype=float32)>]
_yhat1 = 2.2616348 * x + 0.6069048
_yhat2 =  net.predict(x)
plt.plot(x,_yhat1,'r')
plt.plot(x,_yhat2,'b')
[<matplotlib.lines.Line2D at 0x7f200c335cc0>]
yhat = net.predict(x)
plt.plot(x,y,'.')
plt.plot(x,yhat,'--')
[<matplotlib.lines.Line2D at 0x7f200c3e0dc0>]

- 실패: 이 모형은 epoch을 10억번 돌려도 실패할 모형임

  • 왜? 아키텍처 설계자체가 틀렸음
    • 실제 $y$ 들은 곡선인데,.. 예상치는 직선이고....
  • 꺾인 부분을 표현하기에는 아키텍처의 표현력이 너무 부족하다 -> under fit의 문제
    • 비선형 함수를 도입하면 해결할 수 있을 것 같다?

풀이2: 비선형 활성화 함수의 도입

- 여기에서 비선형 활성화 함수는 relu

- 네트워크를 아래와 같이 수정하자.

(수정 전) hat은 생략

gv('''
"x" -> "x*w,    bias=True"[label="*w"] ;
"x*w,    bias=True" -> "y"[label="indentity"] ''')
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> G x x x*w,    bias=True x*w,    bias=True x->x*w,    bias=True *w y y x*w,    bias=True->y indentity

항등함수를 취한 단순모형

(수정 후) hat은 생략

gv('''
"x" -> "x*w,    bias=True"[label="*w"] ;
"x*w,    bias=True" -> "y"[label="relu"] ''')
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> G x x x*w,    bias=True x*w,    bias=True x->x*w,    bias=True *w y y x*w,    bias=True->y relu

항등함수 대신에 Relu 함수를 취하자

  • 마지막에 $f(x)=x$ 라는 함수대신에 relu를 취하는 것으로 구조를 약간 변경
  • =
  • 활성화함수(acitivation function)를 indentity에서 relu로 변경

- relu함수란?

_x = np.linspace(-1,1,100)
_x
array([-1.        , -0.97979798, -0.95959596, -0.93939394, -0.91919192,
       -0.8989899 , -0.87878788, -0.85858586, -0.83838384, -0.81818182,
       -0.7979798 , -0.77777778, -0.75757576, -0.73737374, -0.71717172,
       -0.6969697 , -0.67676768, -0.65656566, -0.63636364, -0.61616162,
       -0.5959596 , -0.57575758, -0.55555556, -0.53535354, -0.51515152,
       -0.49494949, -0.47474747, -0.45454545, -0.43434343, -0.41414141,
       -0.39393939, -0.37373737, -0.35353535, -0.33333333, -0.31313131,
       -0.29292929, -0.27272727, -0.25252525, -0.23232323, -0.21212121,
       -0.19191919, -0.17171717, -0.15151515, -0.13131313, -0.11111111,
       -0.09090909, -0.07070707, -0.05050505, -0.03030303, -0.01010101,
        0.01010101,  0.03030303,  0.05050505,  0.07070707,  0.09090909,
        0.11111111,  0.13131313,  0.15151515,  0.17171717,  0.19191919,
        0.21212121,  0.23232323,  0.25252525,  0.27272727,  0.29292929,
        0.31313131,  0.33333333,  0.35353535,  0.37373737,  0.39393939,
        0.41414141,  0.43434343,  0.45454545,  0.47474747,  0.49494949,
        0.51515152,  0.53535354,  0.55555556,  0.57575758,  0.5959596 ,
        0.61616162,  0.63636364,  0.65656566,  0.67676768,  0.6969697 ,
        0.71717172,  0.73737374,  0.75757576,  0.77777778,  0.7979798 ,
        0.81818182,  0.83838384,  0.85858586,  0.87878788,  0.8989899 ,
        0.91919192,  0.93939394,  0.95959596,  0.97979798,  1.        ])
tf.nn.relu(_x)
<tf.Tensor: shape=(100,), dtype=float64, numpy=
array([0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.01010101, 0.03030303, 0.05050505, 0.07070707, 0.09090909,
       0.11111111, 0.13131313, 0.15151515, 0.17171717, 0.19191919,
       0.21212121, 0.23232323, 0.25252525, 0.27272727, 0.29292929,
       0.31313131, 0.33333333, 0.35353535, 0.37373737, 0.39393939,
       0.41414141, 0.43434343, 0.45454545, 0.47474747, 0.49494949,
       0.51515152, 0.53535354, 0.55555556, 0.57575758, 0.5959596 ,
       0.61616162, 0.63636364, 0.65656566, 0.67676768, 0.6969697 ,
       0.71717172, 0.73737374, 0.75757576, 0.77777778, 0.7979798 ,
       0.81818182, 0.83838384, 0.85858586, 0.87878788, 0.8989899 ,
       0.91919192, 0.93939394, 0.95959596, 0.97979798, 1.        ])>
plt.plot(_x,_x)
plt.plot(_x,tf.nn.relu(_x))
[<matplotlib.lines.Line2D at 0x7f200c218c40>]
  • 파란색 선을 주황색 선으로 바꿔주는 것이 Relu렐루함수임
  • $f(x)=\max(0,x)=\begin{cases} 0 & x\leq 0 \\ x & x>0 \end{cases}$

0보다 x가 작으면 0으로, 0보다 x가 크면 x로 표현하는 relu~

- 아키텍처: $\hat{y}_i=relu(\hat{w}_0+\hat{w}_1x_i)$, $relu(x)=\max(0,x)$

- 풀이시작

1단계

net2 = tf.keras.Sequential() 

2단계

tf.random.set_seed(43053)
l1 = tf.keras.layers.Dense(1, input_shape=(1,)) 
a1 = tf.keras.layers.Activation(tf.nn.relu) 
net2.add(l1)
net2.layers
[<keras.layers.core.dense.Dense at 0x7f200c256a10>]
id(l1),id(net2.layers[0]) # id가 같은 모습
(139775619459600, 139775619459600)
net2.add(a1)
net2.layers
[<keras.layers.core.dense.Dense at 0x7f200c256a10>,
 <keras.layers.core.activation.Activation at 0x7f200c395d50>]

$l1, a1$이 순서대로 들어간 모습 확인

id(a1),id(net2.layers[1]) # id가 같은 모습
(140114653909808, 140114653909808)

seed 값 설정하면 초기값 같은 값으로 설정되서 결과도 같겠지?

l1.get_weights()
[array([[0.41721308]], dtype=float32), array([0.], dtype=float32)]
net2.get_weights()
[array([[0.41721308]], dtype=float32), array([0.], dtype=float32)]

(네트워크 상황 확인)

l1.weights
[<tf.Variable 'dense_2/kernel:0' shape=(1, 1) dtype=float32, numpy=array([[0.41721308]], dtype=float32)>,
 <tf.Variable 'dense_2/bias:0' shape=(1,) dtype=float32, numpy=array([0.], dtype=float32)>]

u1은 지금 위 그림에서 relu를 취하기 전 상태를 의미함.

u1= l1(x)
# 같은 결과
#u1= x@l1.weights[0] + l1.weights[1]

u1을 a1에 통과시킨다?!

v1= a1(u1)
# relu를 취하면, a1을 취하면 yhat 등장
#v1= tf.nn.relu(u1) 
plt.plot(x,x)
plt.plot(x,u1,'--r')
plt.plot(x,v1,'--b')
[<matplotlib.lines.Line2D at 0x7f6efc317d60>]

3단계

net2.compile(optimizer=tf.optimizers.SGD(0.1),loss='mse')

option있잖아 optimizer 하는, 근데 안 쓰는 이유는 0.01이 기본이라 학습률이 너무 작은 것 같아서

4단계

net2.fit(x,y,epochs=1000,verbose=0,batch_size=N)
<keras.callbacks.History at 0x7f6efc18d210>
net2=tf.keras.Sequential()
tf.random.set_seed(43053)
l1=tf.keras.layers.Dense(1,input_shape=(1,))
a1 = tf.keras.layers.Activation(tf.nn.Relu)
net2.add(l1)
net2.add(a1)
net2.compile(optimizers=tf.keras.optimizers.SGD(0.1),loss='mse')
net2.fit(x,y,epochs=1000,verbose=0,batch_size=100)

- result

$\hat{y}$를 구하는 다양한 방법

yhat = tf.nn.relu(x@l1.weights[0] + l1.weights[1]) 
yhat = net2.predict(x)
yhat = net2(x)
yhat = a1(l1(x))
yhat = net2.layers[1](net2.layers[0](x))
net2.layers[1](net2.layers[0](x))
  • 이것도 통과한다는 개념이 되다니..
  • layers[1]는 $a1$이 있었고 [0]에는 $l1$이 있었잖아
plt.plot(x,y,'.')
plt.plot(x,yhat,'--')
[<matplotlib.lines.Line2D at 0x7f6efc040b80>]

- discussion

  • 이것 역시 수백억번 에폭eopcs을 반복해도 이 이상 적합이 힘들다 $\to$ 모형의 표현력이 떨어진다.
  • 해결책: 주황색점선이 2개 있다면 어떨까?
    • 그대신 반대모양으로!

풀이3: 노드수추가 + 레이어추가

목표: 2개의 주황색 점선을 만들자.

1단계

net3 = tf.keras.Sequential()

2단계

tf.random.set_seed(43053)
l1 = tf.keras.layers.Dense(2,input_shape=(1,))
a1 = tf.keras.layers.Activation(tf.nn.relu)

선 두 개 만들 거니까 dense output을 2로 주자

net3.add(l1)
net3.add(a1) 
net3.layers
[<keras.layers.core.dense.Dense at 0x7f6efc205a80>,
 <keras.layers.core.activation.Activation at 0x7f6efc2c9e10>]

(네트워크 상황 확인)

l1(x).shape
# l1(x) : (100,1) -> (100,2) 
TensorShape([100, 2])

출력 차원 2 넣어줬으니까 100,2가 나오는 거지

plt.plot(x,x)
plt.plot(x,l1(x),'--')
[<matplotlib.lines.Line2D at 0x7f6efc0a1ba0>,
 <matplotlib.lines.Line2D at 0x7f6efc0a1de0>]
plt.plot(x,x)
plt.plot(x,a1(l1(x)),'--')
[<matplotlib.lines.Line2D at 0x7f6efc10e770>,
 <matplotlib.lines.Line2D at 0x7f6efc10ea10>]

- 이 상태에서는 yhat이 안나온다. 왜?

  • 차원이 안맞음. a1(l1(x))의 차원은 (N,2)인데 최종적인 yhat의 차원은 (N,1)이어야 함.
    • 차원이 어찌저찌 맞다고 쳐도 relu를 통과하면 항상 yhat > 0 임(양수만 나와).
    • 따라서 음수값을 가지는 y는 0으로 밖에 맞출 수 없음.

- 해결책: $a1(l1(x))$에 연속으로( = Sequential하게!)통과 후 또 다른 레이어를 설계! $(N,2) \to (N,1)$ 이 되도록!

  • yhat= bias + weight1 * a1(l1(x))[0] + weight2 * a1(l1(x))[1]

- 즉, a1(l1(x)) 를 새로운 입력으로 해석하고 출력을 만들어주는 선형모형을 다시태우면 된다.

  • 입력차원: 2
  • 출력차원: 1
net3.layers
[<keras.layers.core.dense.Dense at 0x7fe71010c370>,
 <keras.layers.core.activation.Activation at 0x7fe7103b1960>]
tf.random.set_seed(43053) 
l2 = tf.keras.layers.Dense(1, input_shape=(2,))

출력 차원 1로 지정해준 모습

net3.add(l2) 
net3.layers
[<keras.layers.core.dense.Dense at 0x7fe71010c370>,
 <keras.layers.core.activation.Activation at 0x7fe7103b1960>,
 <keras.layers.core.dense.Dense at 0x7fe71c66e620>]

dense가 하나 더 추가된 모습

net3.summary()
Model: "sequential_6"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_7 (Dense)             (None, 2)                 4         
                                                                 
 activation_5 (Activation)   (None, 2)                 0         
                                                                 
 dense_8 (Dense)             (None, 1)                 3         
                                                                 
=================================================================
Total params: 7
Trainable params: 7
Non-trainable params: 0
_________________________________________________________________

- 추정해야할 파라메터parameter 수가 4,0,3으로 나온다.

  • 추정할 total parameter는 7개

- 수식표현: $X \to X@W^{(1)}+b^{(1)} \to relu(X@W^{(1)}+b^{(1)}) \to relu(X@W^{(1)}+b^{(1)})@W^{(2)}+b^{(2)}=yhat$

  • (1), (2) 의 의미는 첫번째 변수, 두번째 변수 이 뜻!, 즉 구분을 위해
  • $X$: (N,1)
  • $W^{(1)}$: (1,2) ==> 파라메터 2개 추정
  • $b^{(1)}$: (2,) ==> 파라메터 2개가 추가 // 여기까지 추정할 파라메터는 4개
  • $W^{(2)}$: (2,1) ==> 파라메터 2개 추정 ($N \times 1$ 이 된다)
  • $b^{(2)}$: (1,) ==> 파라메터 1개(bias)가 추가 // 따라서 3개

- 참고: 추정할 파라메터 수가 많다 = 복잡한 모형이다.

  • 초거대AI: 추정할 파라메터 수가 엄청 많은..
net3.weights  # l1 + l2, 값 같은 이유는 seed 같게 잡아서
[<tf.Variable 'dense_7/kernel:0' shape=(1, 2) dtype=float32, numpy=array([[ 0.34065306, -0.7533803 ]], dtype=float32)>,
 <tf.Variable 'dense_7/bias:0' shape=(2,) dtype=float32, numpy=array([0., 0.], dtype=float32)>,
 <tf.Variable 'dense_8/kernel:0' shape=(2, 1) dtype=float32, numpy=
 array([[ 0.34065306],
        [-0.7533803 ]], dtype=float32)>,
 <tf.Variable 'dense_8/bias:0' shape=(1,) dtype=float32, numpy=array([0.], dtype=float32)>]
l1.weights
[<tf.Variable 'dense_7/kernel:0' shape=(1, 2) dtype=float32, numpy=array([[ 0.34065306, -0.7533803 ]], dtype=float32)>,
 <tf.Variable 'dense_7/bias:0' shape=(2,) dtype=float32, numpy=array([0., 0.], dtype=float32)>]

$(1,2)$는 위에서 $W^{(1)}$의 dimension

$(2,)$는 위에서 $b^{(1)}$의 bias term

을 의미한다.

l2.weights
[<tf.Variable 'dense_8/kernel:0' shape=(2, 1) dtype=float32, numpy=
 array([[ 0.34065306],
        [-0.7533803 ]], dtype=float32)>,
 <tf.Variable 'dense_8/bias:0' shape=(1,) dtype=float32, numpy=array([0.], dtype=float32)>]

$(2,1)$은 위에서 $W^{(2)}$의 dimension

$(1,)$는 위에서 $b^{(2)}$의 bias term

을 의미한다.

- 좀 더 간단한 수식표현: $X \to (u_1 \to v_1) \to (u_2 \to v_2) = yhat$

  • $u_1= X@W^{(1)}+b^{(1)}$
  • $v_1= relu(u_1)$
  • $u_2= v_1@W^{(2)}+b^{(2)}$
  • $v_2= indentity(u_2):=yhat$

$X$가 들어와, 선형변환의 결과 $u_1$이 만들어져, 활성화 함수의 Relu 함수 취하면 $v_1$이 만들어지지? 여기서 또 선형 변환 취하면 $u_2$가 취해지고 활성화 함수인 항등 함수 위하면 $v_2$가 만들어져서 결국은 $\hat{y}$이 완성된다.

gv('''
subgraph cluster_1{
    style=filled;
    color=lightgrey;
    "X" 
    label = "Layer 0"
}
subgraph cluster_2{
    style=filled;
    color=lightgrey;
    "X" -> "u1[:,0]"[label="*W1[0,0]"]
    "X" -> "u1[:,1]"[label="*W1[0,1]"]
    "u1[:,0]" -> "v1[:,0]"[label="relu"]
    "u1[:,1]" -> "v1[:,1]"[label="relu"]
    label = "Layer 1"
}
subgraph cluster_3{
    style=filled;
    color=lightgrey;
    "v1[:,0]" -> "yhat"[label="*W2[0,0]"]
    "v1[:,1]" -> "yhat"[label="*W2[1,0]"]
    label = "Layer 2"
}
''')
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> G cluster_1 Layer 0 cluster_2 Layer 1 cluster_3 Layer 2 X X u1[:,0] u1[:,0] X->u1[:,0] *W1[0,0] u1[:,1] u1[:,1] X->u1[:,1] *W1[0,1] v1[:,0] v1[:,0] u1[:,0]->v1[:,0] relu v1[:,1] v1[:,1] u1[:,1]->v1[:,1] relu yhat yhat v1[:,0]->yhat *W2[0,0] v1[:,1]->yhat *W2[1,0]

gv('''
subgraph cluster_1{
    style=filled;
    color=lightgrey;
    "X" 
    label = "Layer 0"
}
subgraph cluster_2{
    style=filled;
    color=lightgrey;
    "X" -> "node1"
    "X" -> "node2"
    label = "Layer 1: relu"
}
subgraph cluster_3{
    style=filled;
    color=lightgrey;
    "node1" -> "yhat"
    "node2" -> "yhat"
    label = "Layer 2"
}
''')
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> G cluster_1 Layer 0 cluster_2 Layer 1: relu cluster_3 Layer 2 X X node1 node1 X->node1 node2 node2 X->node2 yhat yhat node1->yhat node2->yhat

3단계

net3.compile(loss='mse',optimizer=tf.optimizers.SGD(0.1))

4단계

net3.fit(x,y,epochs=1000,verbose=0, batch_size=N) 
<keras.callbacks.History at 0x7fe71c5032b0>

- 결과확인

net3.weights
[<tf.Variable 'dense_7/kernel:0' shape=(1, 2) dtype=float32, numpy=array([[ 1.6352799 , -0.85507524]], dtype=float32)>,
 <tf.Variable 'dense_7/bias:0' shape=(2,) dtype=float32, numpy=array([-0.08284465,  0.85552216], dtype=float32)>,
 <tf.Variable 'dense_8/kernel:0' shape=(2, 1) dtype=float32, numpy=
 array([[ 1.6328746],
        [-1.2001747]], dtype=float32)>,
 <tf.Variable 'dense_8/bias:0' shape=(1,) dtype=float32, numpy=array([1.0253307], dtype=float32)>]
plt.plot(x,y,'.') 
plt.plot(x,net3(x),'--')
[<matplotlib.lines.Line2D at 0x7fe71c5000a0>]

- 분석

plt.plot(x,y,'.') 
plt.plot(x,l1(x),'--')
[<matplotlib.lines.Line2D at 0x7fe673f9d330>,
 <matplotlib.lines.Line2D at 0x7fe673f9d570>]
plt.plot(x,y,'.') 
plt.plot(x,a1(l1(x)),'--')
[<matplotlib.lines.Line2D at 0x7fe673e04ee0>,
 <matplotlib.lines.Line2D at 0x7fe673e051b0>]
plt.plot(x,y,'.') 
plt.plot(x,l2(a1(l1(x))),'--')
[<matplotlib.lines.Line2D at 0x7fe673e7ca00>]

- 마지막 2개의 그림을 분석

l2.weights
[<tf.Variable 'dense_8/kernel:0' shape=(2, 1) dtype=float32, numpy=
 array([[ 1.6328746],
        [-1.2001747]], dtype=float32)>,
 <tf.Variable 'dense_8/bias:0' shape=(1,) dtype=float32, numpy=array([1.0253307], dtype=float32)>]
fig, (ax1,ax2,ax3) = plt.subplots(1,3) 
fig.set_figwidth(12) 
ax1.plot(x,y,'.')
ax1.plot(x,a1(l1(x))[:,0],'--r')
ax1.plot(x,a1(l1(x))[:,1],'--b')
ax2.plot(x,y,'.')
ax2.plot(x,a1(l1(x))[:,0]*1.6328746,'--r') # 위의 weights 결과를 보고 대입해보자
ax2.plot(x,a1(l1(x))[:,1]*(-1.2001747)+1.0253307,'--b')
ax3.plot(x,y,'.')
ax3.plot(x,a1(l1(x))[:,0]*1.6328746+a1(l1(x))[:,1]*(-1.2001747)+1.0253307,'--')
[<matplotlib.lines.Line2D at 0x7fe5c5fee7d0>]

풀이3의 실패

tf.random.set_seed(43054) 
## 1단계
net3 = tf.keras.Sequential() 
## 2단계
net3.add(tf.keras.layers.Dense(2))
net3.add(tf.keras.layers.Activation('relu')) 
net3.add(tf.keras.layers.Dense(1))
## 3단계 
net3.compile(optimizer=tf.optimizers.SGD(0.1),loss='mse')
## 4단계 
net3.fit(x,y,epochs=1000,verbose=0,batch_size=N)
<keras.callbacks.History at 0x7fe5c5e726b0>
plt.plot(x,y,'.')
plt.plot(x,net3(x),'--')
[<matplotlib.lines.Line2D at 0x7fe5c5eebdc0>]

- 엥? 에폭이 부족한가?

net3.fit(x,y,epochs=10000,verbose=0,batch_size=N)
plt.plot(x,y,'.')
plt.plot(x,net3(x),'--')
[<matplotlib.lines.Line2D at 0x7fe5c45e3f40>]

- 실패분석

l1,a1,l2 = net3.layers
l2.weights
[<tf.Variable 'dense_10/kernel:0' shape=(2, 1) dtype=float32, numpy=
 array([[0.65121335],
        [1.8592643 ]], dtype=float32)>,
 <tf.Variable 'dense_10/bias:0' shape=(1,) dtype=float32, numpy=array([-0.60076195], dtype=float32)>]
fig, (ax1,ax2,ax3,ax4) = plt.subplots(1,4) 
fig.set_figwidth(16) 
ax1.plot(x,y,'.')
ax1.plot(x,l1(x)[:,0],'--r')
ax1.plot(x,l1(x)[:,1],'--b')
ax2.plot(x,y,'.')
ax2.plot(x,a1(l1(x))[:,0],'--r')
ax2.plot(x,a1(l1(x))[:,1],'--b')
ax3.plot(x,y,'.')
ax3.plot(x,a1(l1(x))[:,0]*0.65121335,'--r')
ax3.plot(x,a1(l1(x))[:,1]*(1.8592643)+(-0.60076195),'--b')
ax4.plot(x,y,'.')
ax4.plot(x,a1(l1(x))[:,0]*0.65121335+a1(l1(x))[:,1]*(1.8592643)+(-0.60076195),'--')
[<matplotlib.lines.Line2D at 0x7fe5c5def4c0>]
  • 보니까 빨간색 선이 하는 역할을 없음
  • 그런데 생각해보니까 이 상황에서는 빨간색 선이 할 수 있는 일이 별로 없음
  • 왜? 지금은 나름 파란색 선에 의해서 최적화가 된 상태임 $\to$ 빨간색 선이 뭔가 하려고하면 최적화된 상태가 깨질 수 있음 (loss 증가)
    • 빨간색 선이 파란색 선을 건들이지 않고 변하기가 쉽지 않음
  • 즉, 이 상황 자체가 나름 최적회된 상태이다. 이러한 현상을 "global minimum을 찾지 못하고 local minimum에 빠졌다"라고 표현한다.

확인:

net3.weights
[<tf.Variable 'dense_9/kernel:0' shape=(1, 2) dtype=float32, numpy=array([[-0.03077251,  1.8713338 ]], dtype=float32)>,
 <tf.Variable 'dense_9/bias:0' shape=(2,) dtype=float32, numpy=array([-0.04834982,  0.3259186 ], dtype=float32)>,
 <tf.Variable 'dense_10/kernel:0' shape=(2, 1) dtype=float32, numpy=
 array([[0.65121335],
        [1.8592643 ]], dtype=float32)>,
 <tf.Variable 'dense_10/bias:0' shape=(1,) dtype=float32, numpy=array([-0.60076195], dtype=float32)>]
W1= tf.Variable(tnp.array([[-0.03077251,  1.8713338 ]]))
b1= tf.Variable(tnp.array([-0.04834982,  0.3259186 ]))
W2= tf.Variable(tnp.array([[0.65121335],[1.8592643 ]]))
b2= tf.Variable(tnp.array([-0.60076195])) 
with tf.GradientTape() as tape: 
    u = tf.constant(x) @ W1 + b1 
    v = tf.nn.relu(u) 
    yhat = v@W2 + b2 
    loss = tf.losses.mse(y,yhat) 
tape.gradient(loss,[W1,b1,W2,b2])
[<tf.Tensor: shape=(1, 2), dtype=float64, numpy=array([[ 0.00000000e+00, -4.77330119e-05]])>,
 <tf.Tensor: shape=(2,), dtype=float64, numpy=array([0.0000000e+00, 3.1478608e-06])>,
 <tf.Tensor: shape=(2, 1), dtype=float64, numpy=
 array([[ 0.00000000e+00],
        [-4.74910706e-05]])>,
 <tf.Tensor: shape=(1,), dtype=float64, numpy=array([-2.43031263e-05])>]

예상대로 계수값이 거의 다 0이다. $\to$ 나름의 최적값이다. local minimum이다.

풀이4: 노드수를 더 추가한다면?

- 노드수를 더 추가해보면 어떻게 될까? (주황색 점선이 더 여러개 있다면?)

gv('''
subgraph cluster_1{
    style=filled;
    color=lightgrey;
    "X" 
    label = "Layer 0"
}
subgraph cluster_2{
    style=filled;
    color=lightgrey;
    "X" -> "node1"
    "X" -> "node2"
    "X" -> "..."
    "X" -> "node512"
    label = "Layer 1: relu"
}
subgraph cluster_3{
    style=filled;
    color=lightgrey;
    "node1" -> "yhat"
    "node2" -> "yhat"
    "..." -> "yhat"
    "node512" -> "yhat"
    label = "Layer 2"
}
''')
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> G cluster_1 Layer 0 cluster_2 Layer 1: relu cluster_3 Layer 2 X X node1 node1 X->node1 node2 node2 X->node2 ... ... X->... node512 node512 X->node512 yhat yhat node1->yhat node2->yhat ...->yhat node512->yhat
tf.random.set_seed(43056) # seed 바꾸면서 해봐~
net4= tf.keras.Sequential()
net4.add(tf.keras.layers.Dense(512,activation='relu')) # 이렇게 해도됩니다. 
net4.add(tf.keras.layers.Dense(1))         
net4.compile(loss='mse',optimizer=tf.optimizers.SGD(0.1)) 
net4.fit(x,y,epochs=1000,verbose=0,batch_size=N) 
<keras.callbacks.History at 0x7f6ef47553f0>
plt.plot(x,y,'.')
plt.plot(x,net4(x),'--')
[<matplotlib.lines.Line2D at 0x7f6ef460ac20>]
  • 잘된다..
  • 한 두 개의 노드가 역할을 못해도 다른노드들이 잘 보완해주는듯!
net4.summary()
Model: "sequential_4"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_4 (Dense)             (100, 512)                1024      
                                                                 
 dense_5 (Dense)             (100, 1)                  513       
                                                                 
=================================================================
Total params: 1,537
Trainable params: 1,537
Non-trainable params: 0
_________________________________________________________________

- 노드수가 많으면 무조건 좋다? $\to$ 대부분 나쁘지 않음. 그런데 종종 맞추지 말아야할것도 맞춤.. (overfit)

np.random.seed(43052)
N=100 
_x = np.linspace(0,1,N).reshape(N,1) 
_y = np.random.normal(loc=0,scale=0.001,size=(N,1))
plt.plot(_x,_y)
[<matplotlib.lines.Line2D at 0x7f6ef46567a0>]
tf.random.set_seed(43052) 
net4 = tf.keras.Sequential()
net4.add(tf.keras.layers.Dense(512,activation='relu'))
net4.add(tf.keras.layers.Dense(1))
net4.compile(loss='mse',optimizer=tf.optimizers.SGD(0.5))
net4.fit(_x,_y,epochs=1000,verbose=0,batch_size=N)
<keras.callbacks.History at 0x7f6ef46ae590>
plt.plot(_x,_y)
plt.plot(_x,net4(_x),'--')
[<matplotlib.lines.Line2D at 0x7f6ef45539d0>]
  • 이 예제는 추후 다시 공부할 예정
    • 랜덤을 맞추는게 이상한 거지..
    • error term을 쫓아가면서 맞추려는 현상 $\to$ overfit

여기까지가 심층신경망, 인공지능 학습의 시작

Logistic regression

motive

- 현실에서 이런 경우가 많음

  • $x$가 커질수록 (혹은 작아질수록) 성공확률이 올라간다.

- 이러한 모형은 아래와 같이 설계할 수 있음 <-- 외우세요!!

  • $y_i \sim Ber(\pi_i)$, where $\pi_i=\frac{\exp(w_0+w_1x_i)}{1+\exp(w_0+w_1x_i)}$

    • 1이 나올 확률이(성공 확률)은 $\pi_i$
    • $w_0$은 지장이 별로 없는데,
    • $x$가 커질수록
      • $w_1$가 양수일때, $\pi_i = \frac{\infty}{1+\infty} \approx 1$ 성공확률이 커진다.
      • $w_1$가 음수일 때, $\pi_i = \frac{0}{1+0} \approx 0$ 성공확률이 작아진다.
    • $x$가 작아질수록
      • $w_1$가 양수일때, $\pi_i = \frac{0}{1+0} \approx 0$ 성공확률이 작아진다.
      • $w_1$가 음수일 때, $\pi_i = \frac{\infty}{1+\infty} \approx 1$ 성공확률이 커진다.
  • $\hat{y}_i =\frac{\exp(\hat{w}_0+\hat{w}_1x_i)}{1+\exp(\hat{w}_0+\hat{w}_1x_i)} = \frac{1}{1+exp(-\hat{w}_0-\hat{w}_1x_i)}$

    • 각 분자/분모에 $\exp(\hat{w}_0+\hat{w}_1x_i)$ 곱한 거
  • $loss=-\frac{1}{n}\sum_{i=1}^{n}\big(y_i\log(\hat{y}_i)+(1-y_i)\log(1-\hat{y}_i)\big)$

- 위와 같은 손실함수를 BCEloss라고 부른다. (BCE는 Binary Cross Entropy의 약자)

왜 mse를 쓰지 않고 bceloss 를 쓸까?


Likelihood (function) for Bernoulli Distribution

Loglikelihood = $\log$ (likelihood) $$l(\pi | y) = log(L(\pi | y))$$

$f(Y = y;\pi) = \pi^{y} (1-\pi)^{1-y} , y \in \{ 0,1 \}$

$L(\pi |y) = \prod^{n}_{i=1} f(y_i;\pi), y_i \in \{ 0,1 \} ,i=1,\dots, n$

$l(\pi | y) = \log(L(\pi|y)) = \log(\prod^{n}_{i=1} f(y_i;\pi)) = \sum^{n}_{i=1} \log(f(y_i;\pi)) = \sum^{n}_{i=1} \log(\pi^{y_{i}} (1-\pi)^{1-y_i}) = \sum^{n}_{i=1}(y_i \log(\pi) + (1-y_i) \log(1-\pi))$


예제

N = 2000 
x = tnp.linspace(-1,1,N).reshape(N,1)
w0 = -1 
w1 = 5 
u = w0 + x*w1 
#v = tf.constant(np.exp(u)/(1+np.exp(u))) # v=πi 
v = tf.nn.sigmoid(u) # 위 함수가 구현되어 있는 sigmoid
y = tf.constant(np.random.binomial(1,v),dtype=tf.float64) # constant로 잡은 이유, y가 0,1로만 나와서 infty될까봐
plt.plot(x,y,'.',alpha=0.02)
plt.plot(x,v,'--r')
[<matplotlib.lines.Line2D at 0x7f1f9c271b70>]

- 이 아키텍처(yhat을 얻어내는 과정)를 다어어그램으로 나타내면 아래와 같다.

$x \to u \overset{Sigmoid}\to v = \hat{y}$

gv('''
subgraph cluster_1{
    style=filled;
    color=lightgrey;
    "x" 
    label = "Layer 0"
}
subgraph cluster_2{
    style=filled;
    color=lightgrey;
    "x" -> "x*w, bias=True"[label="*w"]
    "x*w, bias=True" -> "yhat"[label="sigmoid"]
    label = "Layer 1"
}
''')
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> G cluster_1 Layer 0 cluster_2 Layer 1 x x x*w, bias=True x*w, bias=True x->x*w, bias=True *w yhat yhat x*w, bias=True->yhat sigmoid

- 또는 간단하게 아래와 같이 쓸 수 있다.

gv('''
subgraph cluster_1{
    style=filled;
    color=lightgrey;
    x
    label = "Layer 0"
}
subgraph cluster_2{
    style=filled;
    color=lightgrey;
    x -> "node1=yhat"
    label = "Layer 1: sigmoid"
}
''')
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> G cluster_1 Layer 0 cluster_2 Layer 1: sigmoid x x node1=yhat node1=yhat x->node1=yhat

- 케라스를 이용하여 적합을 해보면

  • $loss=-\frac{1}{n}\sum_{i=1}^{n}\big(y_i\log(\hat{y}_i)+(1-y_i)\log(1-\hat{y}_i)\big)$
tf.random.set_seed(43052)
net = tf.keras.Sequential() 
net.add(tf.keras.layers.Dense(1,activation='sigmoid'))
bceloss_fn = lambda y,yhat: -tf.reduce_mean(y*tnp.log(yhat) + (1-y)*tnp.log(1-yhat))
net.compile(loss=bceloss_fn, optimizer=tf.optimizers.SGD(0.1))
net.fit(x,y,epochs=1000,verbose=0,batch_size=N) 
<keras.callbacks.History at 0x7f1f9c15cb20>

tf.reduce_mean(y*tnp.log(yhat) + (1-y)*tnp.log(1-yhat))=tf.reduce_sum(y*tnp.log(yhat) + (1-y)*tnp.log(1-yhat))/N 같지~

net.weights
[<tf.Variable 'dense/kernel:0' shape=(1, 1) dtype=float32, numpy=array([[4.2182126]], dtype=float32)>,
 <tf.Variable 'dense/bias:0' shape=(1,) dtype=float32, numpy=array([-0.87155575], dtype=float32)>]

초기값은 $w_0 = -1, w_1 = 5$ 였다

plt.plot(x,y,'.',alpha=0.1)
plt.plot(x,v,'--r')
plt.plot(x,net(x),'--b')
[<matplotlib.lines.Line2D at 0x7f27c0084730>]

MSE loss?

- mse loss를 쓰면 왜 안되는지?

tf.random.set_seed(43052)
net = tf.keras.Sequential() 
net.add(tf.keras.layers.Dense(1,activation='sigmoid'))
mseloss_fn = lambda y,yhat: tf.reduce_mean((y-yhat)**2)
net.compile(loss=mseloss_fn, optimizer=tf.optimizers.SGD(0.1))
net.fit(x,y,epochs=1000,verbose=0,batch_size=N) 
<keras.callbacks.History at 0x7f6ef018d6c0>
plt.plot(x,y,'.',alpha=0.1)
plt.plot(x,v,'--r')
plt.plot(x,net(x),'--b')
[<matplotlib.lines.Line2D at 0x7f6e785595d0>]
  • 일단 BCE loss와 비교해보니까 동일 초기값, 동일 epochs에서 MSE loss의 적합이 별로임

MSE loss vs BCE loss

- MSEloss, BCEloss의 시각화

w0, w1 = np.meshgrid(np.arange(-10,3,0.2), np.arange(-1,10,0.2), indexing='ij')
w0, w1 = w0.reshape(-1), w1.reshape(-1)

def loss_fn1(w0,w1):
    u = w0+w1*x 
    yhat = np.exp(u)/(np.exp(u)+1)
    return mseloss_fn(y,yhat) 

def loss_fn2(w0,w1):
    u = w0+w1*x 
    yhat = np.exp(u)/(np.exp(u)+1)
    return bceloss_fn(y,yhat) 

loss1 = list(map(loss_fn1,w0,w1))
loss2 = list(map(loss_fn2,w0,w1))
fig = plt.figure()
fig.set_figwidth(9)
fig.set_figheight(9)
ax1=fig.add_subplot(1,2,1,projection='3d')
ax2=fig.add_subplot(1,2,2,projection='3d')
ax1.elev=15
ax2.elev=15
ax1.azim=75
ax2.azim=75
ax1.scatter(w0,w1,loss1,s=0.1)
ax2.scatter(w0,w1,loss2,s=0.1) 
<mpl_toolkits.mplot3d.art3d.Path3DCollection at 0x7f6e784a7070>
  • 왼쪽곡면(MSEloss)보다 오른쪽곡면(BCEloss)이 좀 더 예쁘게 생김 $\to$ 오른쪽 곡면에서 더 학습이 잘 될 것 같음
    • 왼쪽 곡면에서 평면같은 부분이 보이기도 한다.

학습과정 시각화예시1

- 파라메터학습과정 시각화 // 옵티마이저: SGD, 초기값: $(w_0,w_1) = (-3.0,-1.0) $

(1) 데이터정리

$(2000,1) + (2000,1) \to (2000,2)$ 따라서 axis=1

X = tf.concat([tf.ones(N,dtype=tf.float64).reshape(N,1),x],axis=1)
X
<tf.Tensor: shape=(2000, 2), dtype=float64, numpy=
array([[ 1.       , -1.       ],
       [ 1.       , -0.9989995],
       [ 1.       , -0.997999 ],
       ...,
       [ 1.       ,  0.997999 ],
       [ 1.       ,  0.9989995],
       [ 1.       ,  1.       ]])>

(2) 1ter돌려봄

일단 input_dim 안 넣고 알아서 계산하게끔 코드 쓰기

net_mse = tf.keras.Sequential()
net_mse.add(tf.keras.layers.Dense(1,use_bias=False,activation='sigmoid')) 
net_mse.compile(optimizer=tf.optimizers.SGD(0.1),loss=mseloss_fn) 
net_mse.fit(X,y,epochs=1,batch_size=N)
1/1 [==============================] - 0s 103ms/step - loss: 0.1625
<keras.callbacks.History at 0x7f6e78287700>
net_bce = tf.keras.Sequential()
net_bce.add(tf.keras.layers.Dense(1,use_bias=False,activation='sigmoid')) 
net_bce.compile(optimizer=tf.optimizers.SGD(0.1),loss=bceloss_fn) 
net_bce.fit(X,y,epochs=1,batch_size=N)
1/1 [==============================] - 0s 112ms/step - loss: 0.9031
<keras.callbacks.History at 0x7f6e7836e4d0>
net_mse.get_weights(), net_bce.get_weights()
([array([[-0.8390354],
         [ 1.1133838]], dtype=float32)],
 [array([[ 0.66451424],
         [-0.30300772]], dtype=float32)])

초기값 지정

net_mse.set_weights([tnp.array([[-3.0 ],[ -1.0]],dtype=tf.float32)])
net_bce.set_weights([tnp.array([[-3.0 ],[ -1.0]],dtype=tf.float32)])
net_mse.get_weights(), net_bce.get_weights()
([array([[-3.],
         [-1.]], dtype=float32)],
 [array([[-3.],
         [-1.]], dtype=float32)])

(4) 학습과정기록: 15에폭마다 기록

What_mse = tnp.array([[-3.0 ],[ -1.0]],dtype=tf.float32)
What_bce = tnp.array([[-3.0 ],[ -1.0]],dtype=tf.float32)
net_mse.weights,net_bce.weights
([<tf.Variable 'dense_14/kernel:0' shape=(2, 1) dtype=float32, numpy=
  array([[-1.6284649 ],
         [ 0.15085724]], dtype=float32)>],
 [<tf.Variable 'dense_15/kernel:0' shape=(2, 1) dtype=float32, numpy=
  array([[-0.7483891],
         [ 3.1480474]], dtype=float32)>])
net_mse.weights[0],net_bce.weights[0] # 여기에 저장되어 있겠다.
(<tf.Variable 'dense_14/kernel:0' shape=(2, 1) dtype=float32, numpy=
 array([[-1.6284649 ],
        [ 0.15085724]], dtype=float32)>,
 <tf.Variable 'dense_15/kernel:0' shape=(2, 1) dtype=float32, numpy=
 array([[-0.7483891],
        [ 3.1480474]], dtype=float32)>)

밑의 결과들의 저장되어 있는 $\uparrow$

for k in range(29): 
    net_mse.fit(X,y,epochs=15,batch_size=N,verbose=0)
    net_bce.fit(X,y,epochs=15,batch_size=N,verbose=0)
    What_mse = tf.concat([What_mse,net_mse.weights[0]],axis=1) 
    What_bce = tf.concat([What_bce,net_bce.weights[0]],axis=1) 
What_mse

<tf.Tensor: shape=(2, 30), dtype=float32, numpy=
array([[-3.        , -1.5267563 , -1.4210105 , -1.313109  , -1.2056502 ,
        -1.1016837 , -1.004222  , -0.9156975 , -0.8375983 , -0.7704069 ,
        -0.7137851 , -0.66686356, -0.6285117 , -0.59753513, -0.5727944 ,
        -0.5532646 , -0.53805643, -0.52641535, -0.51770973, -0.511416  ,
        -0.5071025 , -0.5044136 , -0.5030572 , -0.50279343, -0.5034251 ,
        -0.50478995, -0.50675386, -0.50920665, -0.51205695, -0.5152289 ],
       [-1.        ,  0.24055327,  0.33582243,  0.43574232,  0.53882664,
         0.64314604,  0.7466175 ,  0.84736055,  0.9439684 ,  1.0356027 ,
         1.1219288 ,  1.2029756 ,  1.2789918 ,  1.3503349 ,  1.4174005 ,
         1.4805793 ,  1.5402385 ,  1.5967109 ,  1.6502953 ,  1.7012557 ,
         1.7498262 ,  1.7962129 ,  1.8405987 ,  1.8831452 ,  1.923996  ,
         1.9632788 ,  2.001108  ,  2.0375865 ,  2.0728061 ,  2.1068492 ]],
      dtype=float32)>
What_bce

<tf.Tensor: shape=(2, 30), dtype=float32, numpy=
array([[-3.        , -0.75769883, -0.7667865 , -0.7756514 , -0.78429496,
        -0.79272   , -0.8009305 , -0.80893093, -0.8167265 , -0.82432246,
        -0.8317245 , -0.8389384 , -0.8459697 , -0.852824  , -0.85950696,
        -0.866024  , -0.87238044, -0.87858135, -0.8846317 , -0.8905363 ,
        -0.8962999 , -0.9019269 , -0.90742147, -0.9127879 , -0.91803026,
        -0.92315227, -0.9281577 , -0.93305   , -0.93783253, -0.9425088 ],
       [-1.        ,  3.1962004 ,  3.2425287 ,  3.2871456 ,  3.3301535 ,
         3.3716452 ,  3.411707  ,  3.4504173 ,  3.4878485 ,  3.5240674 ,
         3.559135  ,  3.5931091 ,  3.6260426 ,  3.657985  ,  3.6889825 ,
         3.719077  ,  3.7483094 ,  3.7767167 ,  3.8043337 ,  3.8311942 ,
         3.8573287 ,  3.8827665 ,  3.907535  ,  3.9316604 ,  3.955167  ,
         3.978078  ,  4.000415  ,  4.022198  ,  4.04345   ,  4.0641866 ]],
      dtype=float32)>

(5) 시각화

from matplotlib import animation
plt.rcParams["animation.html"] = "jshtml"
fig = plt.figure()
fig.set_figwidth(6)
fig.set_figheight(6)
fig.suptitle("SGD, Winit=(-3,-1)")
ax1=fig.add_subplot(2,2,1,projection='3d')
ax2=fig.add_subplot(2,2,2,projection='3d')
ax1.elev=15;ax2.elev=15;ax1.azim=75;ax2.azim=75
ax3=fig.add_subplot(2,2,3)
ax4=fig.add_subplot(2,2,4)

ax1.scatter(w0,w1,loss1,s=0.1);ax1.scatter(-1,5,loss_fn1(-1,5),color='red',marker='*',s=200)
ax2.scatter(w0,w1,loss2,s=0.1);ax2.scatter(-1,5,loss_fn2(-1,5),color='red',marker='*',s=200)

ax3.plot(x,y,','); ax3.plot(x,v,'--r'); 
line3, = ax3.plot(x,1/(1+np.exp(-X@What_mse[:,0])),'--b')
ax4.plot(x,y,','); ax4.plot(x,v,'--r')
line4, = ax4.plot(x,1/(1+np.exp(-X@What_bce[:,0])),'--b')

def animate(i):
    _w0_mse,_w1_mse = What_mse[:,i]
    _w0_bce,_w1_bce = What_bce[:,i]
    ax1.scatter(_w0_mse, _w1_mse, loss_fn1(_w0_mse, _w1_mse),color='gray')
    ax2.scatter(_w0_bce, _w1_bce, loss_fn2(_w0_bce, _w1_bce),color='gray')
    line3.set_ydata(1/(1+np.exp(-X@What_mse[:,i])))
    line4.set_ydata(1/(1+np.exp(-X@What_bce[:,i])))

ani = animation.FuncAnimation(fig, animate, frames=30)
plt.close()
ani
</input>

학습과정 시각화예시2

- 파라메터학습과정 시각화 // 옵티마이저: Adam, 초기값: $(w_0,w_1) = (-3.0,-1.0) $

(1) 데이터정리

X = tf.concat([tf.ones(N,dtype=tf.float64).reshape(N,1),x],axis=1)
X
<tf.Tensor: shape=(2000, 2), dtype=float64, numpy=
array([[ 1.       , -1.       ],
       [ 1.       , -0.9989995],
       [ 1.       , -0.997999 ],
       ...,
       [ 1.       ,  0.997999 ],
       [ 1.       ,  0.9989995],
       [ 1.       ,  1.       ]])>

(2) 1ter돌려봄

net_mse = tf.keras.Sequential()
net_mse.add(tf.keras.layers.Dense(1,use_bias=False,activation='sigmoid')) 
net_mse.compile(optimizer=tf.optimizers.Adam(0.1),loss=mseloss_fn) 
net_mse.fit(X,y,epochs=1,batch_size=N)
1/1 [==============================] - 0s 106ms/step - loss: 0.1604
<keras.callbacks.History at 0x7f6e5a547160>
net_bce = tf.keras.Sequential()
net_bce.add(tf.keras.layers.Dense(1,use_bias=False,activation='sigmoid')) 
net_bce.compile(optimizer=tf.optimizers.Adam(0.1),loss=bceloss_fn) 
net_bce.fit(X,y,epochs=1,batch_size=N)
1/1 [==============================] - 0s 115ms/step - loss: 0.6128
<keras.callbacks.History at 0x7f6e5a31af20>
net_mse.get_weights(), net_bce.get_weights()
([array([[-0.95365924],
         [ 1.4536816 ]], dtype=float32)],
 [array([[0.3367687 ],
         [0.98789144]], dtype=float32)])
net_mse.set_weights([tnp.array([[-3.0 ],[ -1.0]],dtype=tf.float32)])
net_bce.set_weights([tnp.array([[-3.0 ],[ -1.0]],dtype=tf.float32)])
net_mse.get_weights(), net_bce.get_weights()
([array([[-3.],
         [-1.]], dtype=float32)],
 [array([[-3.],
         [-1.]], dtype=float32)])

(4) 학습과정기록: 15에폭마다 기록

What_mse = tnp.array([[-3.0 ],[ -1.0]],dtype=tf.float32)
What_bce = tnp.array([[-3.0 ],[ -1.0]],dtype=tf.float32)
for k in range(29): 
    net_mse.fit(X,y,epochs=15,batch_size=N,verbose=0)
    net_bce.fit(X,y,epochs=15,batch_size=N,verbose=0)
    What_mse = tf.concat([What_mse,net_mse.weights[0]],axis=1) 
    What_bce = tf.concat([What_bce,net_bce.weights[0]],axis=1) 

(5) 시각화

from matplotlib import animation
plt.rcParams["animation.html"] = "jshtml"
fig = plt.figure()
fig.set_figwidth(6)
fig.set_figheight(6)
fig.suptitle("Adam, Winit=(-3,-1)")
ax1=fig.add_subplot(2,2,1,projection='3d')
ax2=fig.add_subplot(2,2,2,projection='3d')
ax1.elev=15;ax2.elev=15;ax1.azim=75;ax2.azim=75
ax3=fig.add_subplot(2,2,3)
ax4=fig.add_subplot(2,2,4)

ax1.scatter(w0,w1,loss1,s=0.1);ax1.scatter(-1,5,loss_fn1(-1,5),color='red',marker='*',s=200)
ax2.scatter(w0,w1,loss2,s=0.1);ax2.scatter(-1,5,loss_fn2(-1,5),color='red',marker='*',s=200)

ax3.plot(x,y,','); ax3.plot(x,v,'--r'); 
line3, = ax3.plot(x,1/(1+np.exp(-X@What_mse[:,0])),'--b')
ax4.plot(x,y,','); ax4.plot(x,v,'--r')
line4, = ax4.plot(x,1/(1+np.exp(-X@What_bce[:,0])),'--b')

def animate(i):
    _w0_mse,_w1_mse = What_mse[:,i]
    _w0_bce,_w1_bce = What_bce[:,i]
    ax1.scatter(_w0_mse, _w1_mse, loss_fn1(_w0_mse, _w1_mse),color='gray')
    ax2.scatter(_w0_bce, _w1_bce, loss_fn2(_w0_bce, _w1_bce),color='gray')
    line3.set_ydata(1/(1+np.exp(-X@What_mse[:,i])))
    line4.set_ydata(1/(1+np.exp(-X@What_bce[:,i])))

ani = animation.FuncAnimation(fig, animate, frames=30)
plt.close()
ani
</input>

경사하강법으로 적합시킬 때, 기울기를 개선하는 두 가지 방법

  1. $\frac{\partial loss}{\partial w_0}$ vs $\frac{\partial loss}{\partial w_1}$ 비교해서 기울기를 원하는 쪽으로 선택
  2. gradient를 누적해서 발전시키는 algorithm

$\to$ 이 두 가지 방법을 절충한 방법이 Adam

$\therefore$ Adam 쓰면 학습을 효율적으로 빨리 할 수 있다.

학습과정 시각화예시3

- 파라메터학습과정 시각화 // 옵티마이저: Adam, 초기값: $(w_0,w_1) = (-10.0,-1.0) $

(1) 데이터정리

X = tf.concat([tf.ones(N,dtype=tf.float64).reshape(N,1),x],axis=1)
X
<tf.Tensor: shape=(2000, 2), dtype=float64, numpy=
array([[ 1.       , -1.       ],
       [ 1.       , -0.9989995],
       [ 1.       , -0.997999 ],
       ...,
       [ 1.       ,  0.997999 ],
       [ 1.       ,  0.9989995],
       [ 1.       ,  1.       ]])>

(2) 1ter돌려봄

net_mse = tf.keras.Sequential()
net_mse.add(tf.keras.layers.Dense(1,use_bias=False,activation='sigmoid')) 
net_mse.compile(optimizer=tf.optimizers.Adam(0.1),loss=mseloss_fn) 
net_mse.fit(X,y,epochs=1,batch_size=N)
1/1 [==============================] - 0s 115ms/step - loss: 0.3962
<keras.callbacks.History at 0x7f6e543076a0>
net_bce = tf.keras.Sequential()
net_bce.add(tf.keras.layers.Dense(1,use_bias=False,activation='sigmoid')) 
net_bce.compile(optimizer=tf.optimizers.Adam(0.1),loss=bceloss_fn) 
net_bce.fit(X,y,epochs=1,batch_size=N)
1/1 [==============================] - 0s 120ms/step - loss: 0.9299
<keras.callbacks.History at 0x7f6e5446b250>
net_mse.get_weights(), net_bce.get_weights()
([array([[ 0.8862325 ],
         [-0.39477226]], dtype=float32)],
 [array([[ 0.08077933],
         [-0.770376  ]], dtype=float32)])
net_mse.set_weights([tnp.array([[-10.0 ],[ -1.0]],dtype=tf.float32)])
net_bce.set_weights([tnp.array([[-10.0 ],[ -1.0]],dtype=tf.float32)])
net_mse.get_weights(), net_bce.get_weights()
([array([[-10.],
         [ -1.]], dtype=float32)],
 [array([[-10.],
         [ -1.]], dtype=float32)])

(4) 학습과정기록: 15에폭마다 기록

What_mse = tnp.array([[-10.0 ],[ -1.0]],dtype=tf.float32)
What_bce = tnp.array([[-10.0 ],[ -1.0]],dtype=tf.float32)
for k in range(29): 
    net_mse.fit(X,y,epochs=15,batch_size=N,verbose=0)
    net_bce.fit(X,y,epochs=15,batch_size=N,verbose=0)
    What_mse = tf.concat([What_mse,net_mse.weights[0]],axis=1) 
    What_bce = tf.concat([What_bce,net_bce.weights[0]],axis=1) 

(5) 시각화

from matplotlib import animation
plt.rcParams["animation.html"] = "jshtml"
fig = plt.figure()
fig.set_figwidth(6)
fig.set_figheight(6)
fig.suptitle("Adam, Winit=(-10,-1)")
ax1=fig.add_subplot(2,2,1,projection='3d')
ax2=fig.add_subplot(2,2,2,projection='3d')
ax1.elev=15;ax2.elev=15;ax1.azim=75;ax2.azim=75
ax3=fig.add_subplot(2,2,3)
ax4=fig.add_subplot(2,2,4)

ax1.scatter(w0,w1,loss1,s=0.1);ax1.scatter(-1,5,loss_fn1(-1,5),color='red',marker='*',s=200)
ax2.scatter(w0,w1,loss2,s=0.1);ax2.scatter(-1,5,loss_fn2(-1,5),color='red',marker='*',s=200)

ax3.plot(x,y,','); ax3.plot(x,v,'--r'); 
line3, = ax3.plot(x,1/(1+np.exp(-X@What_mse[:,0])),'--b')
ax4.plot(x,y,','); ax4.plot(x,v,'--r')
line4, = ax4.plot(x,1/(1+np.exp(-X@What_bce[:,0])),'--b')

def animate(i):
    _w0_mse,_w1_mse = What_mse[:,i]
    _w0_bce,_w1_bce = What_bce[:,i]
    ax1.scatter(_w0_mse, _w1_mse, loss_fn1(_w0_mse, _w1_mse),color='gray')
    ax2.scatter(_w0_bce, _w1_bce, loss_fn2(_w0_bce, _w1_bce),color='gray')
    line3.set_ydata(1/(1+np.exp(-X@What_mse[:,i])))
    line4.set_ydata(1/(1+np.exp(-X@What_bce[:,i])))

ani = animation.FuncAnimation(fig, animate, frames=30)
plt.close()
ani
</input>
  • 아무리 아담이라고 해도 이건(초기값이 너무 안 좋으면) 힘듦

- discussion

  • mse_loss는 경우에 따라서 엄청 수렴속도가 느릴 수도 있음.
  • 근본적인 문제점: mse_loss일 경우 loss function의 곡면이 예쁘지 않음. (전문용어로 convex가 아니라고 말함)
    • convex, 위에서 곡선으로 나오는 부분, 예를 들어 이차 방정식에서 오목한 부분
  • 좋은 optimizer를 이용하면 mse_loss일 경우에도 수렴속도를 올릴 수 있음 (학습과정 시각화예시2). 그렇지만 이는 근본적인 해결책은 아님. (학습과정 시각화예시3)

- 요약: 왜 logistic regression에서 mse loss를 쓰면 안되는가?

  • mse loss를 사용하면 손실함수가 convex하지 않으니까!
  • 그리고 bce loss를 사용하면 손실함수가 convex하니까!