2018/11/25 - [Study/인공지능학습] - [머신러닝 reboot] 개념 잡기 : 경사 하강법 1 - 특성의 scale

2019/01/28 - [Study/인공지능학습] - [머신러닝 Reboot] - 개념잡기 - 경사 하강법 3 - 경사 하강법의 종류


머신러닝 Reboot - 개념 잡기 : 경사 하강법 2 - step 공식 이해하기


지난 시간에는 어설프게나마 경사 하강법이 왜 특성에 민감한지 그래프를 통해 알아보았다. 여전히 논리적으로 설명하기 
힘든 부분이 있어 아쉬움이 남지만 직관적으로 봤을 때도 경사 하강법을 수행하기 위해서는 일단 특성들의 스케일을 
맞추는 것이 좋다는 것은 알게 되었다.


오늘은 이어서 어떤식으로 다음 기울기를 찾아 움직이는지 그 과정을 공식을 통해 알아보자.
이번 정리는 오로지 “핸즈온 머신러닝”의 166쪽에 있는 ‘식 4-7 경사 하강법의 스텝’을 이해하기 위한 것이다.


선형 회귀 관련 공식 복습 - 가설 함수와 비용 함수


우선 복습 차원에서 선형 회귀의 가설함수와 비용 함수를 다시 한 번 보자. 여러 표현 방법이 있지만 여기서는 “핸즈온
머신러닝”에서 발췌한 내용으로 정리를 해보겠다.


먼저 가설함수를 보자. 간단한 식임에도 불구하고 다양한 표현이 존재하여 혼란을 주기 일쑤이다. 아래 표현들을 보면서
정리해보자.


선형 회귀 가설 함수그림 1


1번 같은 경우 단순 선형 회귀라고 생각하면 되겠다. 𝜭와 𝑥가 모두 스칼라인 경우인 것이다. 다시 말해 특성이 1개인
경우…


2번과 3번은 모두 다중 선형 회귀를 표현한 식이며 𝜭와 𝑿는 모두 벡터이다. 그런데 묘하게 표현이 다르다.
왜 다른지 차근차근 살펴보자.



기본적으로 벡터는 종벡터(𝑛 X 1) 형태를 취한다. 이 때 𝑛은 특성의 수이다. 우리가 이미 잘 알고 있듯이 이 식들은
가설 함수의 원래 형태인 아래의 형식을 벡터의 곱으로 표현한 것이다.


선형 회귀그림 2


이 식의 𝜭와 𝑥를 각각 벡터로 표시해보자. 벡터는 기본적으로 종벡터 형태를 취한다고 했으니 다음과 같이 표현할 수
있다(여기서 𝜭 의 0번째 요소는 편향을 의미하며 따라서 X의 0번째 요소는 항상 1이다).


그림 3


그림 3-1


그런데 𝜭와 𝑥 가 모두 𝑛 X 1벡터라고 한다면 (𝑛 X 1) ∙ (𝑛 X 1)이 되어 벡터(행렬)의 연산 법칙으로 인해 계산을 할 수 
없게 된다. 따라서 앞에 있는 𝜭를 전치행렬로 만들어 (1 X 𝑛) ∙ (𝑛 X 1)이 되게 함으로써 연산이 가능하게 만드는 것이다.
이 것이 바로 두 번째 식이다. 물론 전치행렬의 성질에 따라 다음과 같이 표현할 수도 있다.


선형 회귀 가설 함수그림 4


3번째 식은 2번째 식을 조금 더 확장한 것이라고 볼 수 있다. 2번이 식에서 𝑋는 𝑛개의 요소를 갖는 벡터였다.
이러한 식이 𝑚개, 즉 𝑛개의 특성을 갖는 샘플이 𝑚개가 있다고 보는 것이다. 따라서 이 때는 식의 결과 역시 
벡터가 되는 것이다. 즉, 3번의 식을 구성하는 각 요소는 다음의 의미가 있다(물론 이 때 편향을 생각하여
𝑋 행렬의 1열은 모두 1로 채워져야 한다).


그림 5


그림 6


그림 3


여기에서 식은 2가지로 표현이 가능하다 𝑋를 𝑛 X 𝑚 행렬로 만든다면 식은 2번의 식과 동일한 형태가 만들어질
것이다. 이렇게 본다면 2번의 식이 가장 일반적인 선형 회귀의 가설함수라고 볼 수 있을 것이다. 그리고 이 식을
선형 회귀의 비용 함수에 대입하게 되면 아래와 같은 비용 함수의 식이 만들어진다.


선형 회귀 비용 함수그림 7


하지만 𝑋를 𝑚 X 𝑛 행렬로 만든다면 3번의 식이 된다. 이 3번의 식은 곧이어 설명할 경사 하강법의 step을
계산하는 공식에 등장하게 된다.


배치 경사 하강법


경사 하강법은 가중치 𝜭의 변화에 따라 비용 함수의 결과가 얼마나 바뀌는지를 확인하는 연속되는 과정이고
이를 알기 위해서는 비용 함수를 𝜭에 대해 미분해야 한다. 위에 언급한 그림7의 비용 함수를 𝜭에 대해 미분하면
다음과 같은 식을 얻을 수 있다(이 과정에서도 변형이 있는데 식 맨 앞의 2/m에서 2를 없애기 위해 미리 비용 함수에
1/2를 곱하는 경우도 있다. 이런 경우 2/m이 아닌 1/m이 된다).


선형 회귀 비용 함수의 편도함수그림 8


우리는 수알못이니 이 과정을 잠깐 설명하면 우선 미분의 성질 중 다음 성질을 알아야 한다. 바로 미분의 연쇄법칙이다.


 (f(g(x)))'=f'(g(x))g'(x)


미분의 연쇄법칙을 적용해보자면 선형 회귀 비용 함수는 다음과 같이 구성되어있다.


미분의 연쇄법칙그림 9


따라서 차례차례 미분을 해보면 다음과 같이 풀이될 수 있다.


미분의 연쇄법칙그림 10


이와 같이 선형 회귀의 비용 함수에서 𝜭에 대해 미분한 도함수는 그림8의 식이 되는 것이다. 이 도함수는 곧 비용 함수의
기울기를 의미하므로 경사 하강법은 이 도함수의 변화를 이용여 최솟값을 찾는 과정이고, 이는 초깃값으로 주어진 𝜭0에서 
학습률과 비용 함수의 도함수를 곱한 값을 빼서 다음 𝜭1를 구하고 다시 이 𝜭1에서 학습률과 비용 함수의 도함수를 곱한 
값을 빼서 𝜭2를 구하는 식으로 이 과정을 반복해 나가는 것이다.


이 과정에서 비용 함수의 도함수를 그대로 사용하는 경우도 있지만 식의 단순화를 위해 이 비용 함수의 도함수의 변화량을
행렬식으로 만들어 한방에 처리하는 방법도 있다. 이 것은 얼마전 포스팅한 정규방정식 관련 글에서 언급했듯이 𝚺는 
행렬로 변환 가능하다는 것으로 설명할 수 있다.


비용 함수의 도함수를 풀어보면 다음과 같다.


선형 회귀 비용 함수의 도함수그림 11


여기서 괄호 안에 있는 각 요소의 점곱(∙)을 기준으로 앞뒤로 분리를 하면 각각 다음과 같은 종벡터를 만들 수 있다.


그림 12


그림 13



각각의 종벡터는 m X 1 형태의 종벡터로 그대로는 곱셈이 성립하지 않으므로 𝑋가 요소인 종벡터를 전치시켜서
1 X m 형태의 횡벡터를 만들어 곱하면 동일한 식이 된다.


그림 14


그림 15


이제 마지막으로 𝜭가 포함된 종벡터를 풀이해보자. 이 종벡터는 다시 아래와 같이 나눠볼 수 있다.


그림 16


여기서 다시 뺄셈 식의 앞부분을 생각해보면 𝑋(i)는 특성 수만큼의 요소를 갖는 벡터들이다. 즉 m행 n열의 행렬이
되는 것이다.


그림 17


하지만 이렇게 되면 𝜭T는 1 X n의 벡터이고 𝑋는 m X n의 행렬이 되어 곱셈식이 성립되지 않는다. 따라서 𝜭T를
다시 전치시켜 n X 1의 종벡터를 만든 후 𝑋 뒤에 곱하면 m X n 행렬과 n X 1 벡터의 곱이 성립된다. 이렇게하여
최종적으로 정리된 선형 회귀 비용 함수를 𝜭에 대해 미분한 도함수의 변화량은 다음과 같이 표현할 수 있다.


그림 18


그리고 경사 하강법의 STEP을 구하는 공식은 아래와 같다.


경사 하강법의 step 계산 공식그림 19


정리


여전히 수학은 어렵다. 나름 치환과 간략화에 주의하면서 각종 공식을 이리 저리 변형시켜가면서 이해하려고 하지만
깔끔하게 정리되지 않는 것은 어쩔 수가 없다. 일단 오늘의 소득이라면 행렬을 횡벡터를 요소로 갖는 종벡터로 생각
하면 조금 더 쉽게 이해되는 경우가 있다는 것 정도…


오늘의 주된 내용은 “핸즈온 머신러닝”의 166쪽에 있는 ‘식 4-7 경사 하강법의 스텝’에 대한 풀이였는데 사실 책을
보면 여전히 이해되지 않는 부분이 있다. 165쪽에 있는 식 4-5 비용 함수의 편도함수 식에서 j의 의미를 잘 모르겠다.
얼핏 봤을 때 특성의 수를 의미할 것 같은데…그리고 괄호 안의 x와 괄호 밖의 x가 다르게 표기된 부분도 잘 이해가
안간다. 이렇게 기호 하나가 추가되는 것만으로도 풀이가 안드로메다로 향하는 것을 보면 아직도 한참 멀었다…ㅠ.ㅠ


일단 내가 정리한 식도 얼추 앞뒤가 맞아 들어가는 것 같으니 우선 오늘의 정리는 마무리 하고 다음 포스팅에서는
여기서 정리한 식을 바탕으로 코드를 통해 배치 경사 하강법, 확률적 경사 하강법, 미니 배치 경사 하강법에 대해
알아보도록 하겠다.


피곤하다…ㅠ.ㅠ

블로그 이미지

마즈다

이미 마흔을 넘어섰지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^




텐서플로우를 이용한 다중 선형 회귀

단순 선형 회귀가 쉽기에 다중 선형 회귀도 쉬운 줄 알았다...하지만 결코 쉽지 않다...ㅠ.ㅠ 물론 독립변수만 늘어났을 뿐 대부분의 식을 그대로 사용해도 되므로 

그냥 그렇게만 알고 넘어가면 이보다 쉬운 것도 없다. 하지만 수학적 사고방식이 모자란 문돌이에게는 변수가 하나 늘어난다는 것은 천지가 개벽하는 변화다.


예를들어 단순 선형 회귀는 그 결과를 그래프를 통해서 시각적으로 쉽게 확인이 가능했다. 그런데 다중 선형 회귀는 도대체 시각적으로 어떻게 표현해야 할지를 모르겠다. 

그나마 독립변수가 2개인 경우는 3차원그래프로 설명을 해놓은 곳이 많아 그러려니 했는데 3개 이상부터는 도대체 어찌 할 수 있는지 할 수는 있는 것인지...ㅠ.ㅠ


일단은 cost 함수로 성공 여부를 가늠하면서 한 번 진행해보기로 했다.


일단 사용한 데이터는 아래 문서의 16쪽에 있는 예제의 데이터이다.

 https://ita.kaist.ac.kr/data/board/ITAMATLAB09_02.pdf


단순 선형 회귀에서 확장

# '텐서플로우 첫걸음'의 예제에서 구현된 변수. 여기서는 직접 회귀 분석에 사용되지는 않고 산점도 행렬을 그리는데만 사용됨
# num_points = 1000
vectors_set = [[2.0, 3.0, 3.2],[1.3, 1.1, 3.0],[2.4, 3.5, 3.6],[1.5, 2.5, 2.6],[0.6, 1.9, 0.6],[2.0, 2.8, 3.5],
               [1.0, 1.3, 2.1], [2.0, 3.3, 3.4], [1.3, 2.0, 2.8], [0.9, 1.0, 2.3]
               ]

#vectors_set2 = [[36.2, 206.2, 1.0, 32.0],[39.0, 218.6, 4.0, 39.0],[81.7, 264.6, 4.0, 41.0],[39.0, 330.5, 2.0, 33.0],
#               [68.3, 334.7, 3.0, 37.0],[106.3, 365.6, 4.0, 31.0],[123.9, 379.3, 5.0, 35.0],[114.8, 456.4, 6.0, 29.0], 
#               [97.0, 502.7, 3.0, 27.0], [100.2, 531.0, 7.0, 36.0]
#               ]

최초에 "텐서플로우 첫걸음"의 예제 노트북으로 시작을 했기에 코드역시 기본적으로는 예제와 동일한 구조다.

y_data = [2.0, 1.3, 2.4, 1.5, 0.6, 2.0, 1.0, 2.0, 1.3, 0.9]
x1_data = [3.0, 1.1, 3.5, 2.5, 1.9, 2.8, 1.3, 3.3, 2.0, 1.0]
x2_data = [3.2, 3.0, 3.6, 2.6, 0.6, 3.5, 2.1, 3.4, 2.8, 2.3]
#x3_data = [v[3] for v in vectors_set]

참고 : 위에서 기존에 사용하던 vectors_set이라는 변수를 그대로 살려놓았는데 그 이유는 다중 회귀 분석을 시각화 하는 방법 중 산점도 행렬을 그리기 위해서다. 

다중 회귀를 구성하는 각 변수들의 관계를 볼 수 있는 산점도 행렬을 만들 수 있도록 데이터를 구조화 해주는 pandas라는 라이브러리를 찾아서 사용해 보았는데. 

이 pandas에서 데이터를 만들 때 조금 더 편하게 사용할 수 있다. 아래 코드를 수행했을 때 처음 나오는 그래프가 바로 산점도 행렬이다.

import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame(vectors_set)

pd.tools.plotting.scatter_matrix(df)
plt.tight_layout()
plt.show()

plt.plot(x1_data, y_data, 'ro')
plt.show()
plt.plot(x2_data, y_data, 'b+')
plt.show()
#plt.plot(x3_data, y_data, 'cs')
#plt.show()


그리고 다음과 같이 가설 함수를 만든다. 다중 회귀 분석의 가설함수는 단순 회귀 분석의 가설함수에서 추가되는 독립 변수에 대해 W * X를 추가로 더해주면 된다. 

즉 기존 단순 선형 회귀의 가설 함수가 y = W * x + b였다면 독립변수가 2개인 다중 회귀의 가설함수는 y = W1 * x1 + W2 * x2 + b의 형식이 되는 것이다. 이런 식으로 

독립변수가 늘어남에 따라 가설함수는 다음과 같이 표현할 수 있다.


위 예제 데이터는 독립변수가 2개이기 때문에 아래와 같이 가설 함수를 만들 수 있다.

import tensorflow as tf

W1 = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
W2 = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
#W3 = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
#b = tf.Variable(tf.random_uniform([1], 88.0, 89.0))
b = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
y = W1 * x1_data + W2 * x2_data + b

가설 함수 단순화 하기

위와 같은 진행은 다중 선형 회귀 가설 함수의 일반 공식에서 보는 바와 같이 독립변수가 늘어나는 만큼 공식이 한도 끝도 없이 늘어날 수가 있다. 그래서 이 공식을 조금 더 

간단하게 표현하기 위해 행렬의 곱을 이용하여 표현할 수 있다. 이 행렬 곱의 성질을 이용하면 b까지도 간단하게 정리할 수 있다. 아래 공식과 같이 표현이 가능한 것이다.

독립변수가 2개인 경우


독립변수가 3개인 경우


b까지 포함시킨 경우


위 방법을 이용하여 가설함수를 다시 구성하면 다음과 같이 간단하게 정리할 수 있게된다. 먼저 두 개의 독립변수를 하나의 벡터로 만들어보자

x_data = [[3.0, 1.1, 3.5, 2.5, 1.9, 2.8, 1.3, 3.3, 2.0, 1.0],
          [3.2, 3.0, 3.6, 2.6, 0.6, 3.5, 2.1, 3.4, 2.8, 2.3]]
y_data = [2.0, 1.3, 2.4, 1.5, 0.6, 2.0, 1.0, 2.0, 1.3, 0.9]

x1_data, x2_data 두개의 변수가 x_data 하나의 변수에 모두 포함되었다. y_data는 그냥 사용하면 된다. 다음은 W1과 W2도 하나로 합쳐보자

W = tf.Variable(tf.random_uniform([1, 2], -1.0, 1.0))
b = tf.Variable(tf.random_uniform([1], -1.0, 1.0))

바뀐 부분은 tf.random_uniform의 첫번째 파라미터가  [1]에서 [1, 2]로 바뀐 것이다. 즉, 하나의 행만 가지고 있던 배열 형태에서 1행 2열 형태의 벡터로 바꾸었다. 

아직까지는 b는 그대로 사용을 하자.


이제 행렬 곱셈을 해보자. 텐서플로우에서 행렬 곱셈을 해주는 함수는 matmul이다.

y = tf.matmul(W, x_data) + b

y = W1 * x1_data + W2 * x2_data + b라는 긴 공식이 y = tf.matmul(W, x_data) + b로 짧아졌다. 새삼 수학적 사고의 대단함을 느끼는 순간이었다.

b까지 단순화 시키려면 다음과 같이 하면 된다.

import tensorflow as tf

x_data = [[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
          [3.0, 1.1, 3.5, 2.5, 1.9, 2.8, 1.3, 3.3, 2.0, 1.0],
          [3.2, 3.0, 3.6, 2.6, 0.6, 3.5, 2.1, 3.4, 2.8, 2.3]]
y_data = [2.0, 1.3, 2.4, 1.5, 0.6, 2.0, 1.0, 2.0, 1.3, 0.9]

W = tf.Variable(tf.random_uniform([1, 3], -1.0, 1.0))
y = tf.matmul(W, x_data)

정말 단순해졌다.

이후 진행은 단순회귀 분석과 동일하다. loss 함수를 정의하고 최적화 방법은 학습속도 0.01인 그래디언트 디센트를 사용한다.

loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)

그리고 모든 변수를 초기화하고 세션을 시작한다.

init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

일단 10000번 정도 반복을 하면서 1000번 째마다 현재 step과 W와 loss값을 출력하여 보았다. 행렬 곱을 이용하여 식을 단순화 하기 전에는 W1, W2, b의 값이 

각각 별개의 배열로 출력되었으나 식을 단순화 한 후에는 W가 하나의 벡터로 전체 값을 모두 표시해준다. 

for step in range(10000):
    sess.run(train)
    if step % 1000 == 0 :
        print(step, sess.run(W), sess.run(loss))
(0, array([[ 0.77657259,  0.304876  ,  0.05037546]], dtype=float32), 0.10678037)
(1000, array([[-0.0653717 ,  0.31436804,  0.32436851]], dtype=float32), 0.01154841)
(2000, array([[-0.24788393,  0.33180454,  0.3721334 ]], dtype=float32), 0.0072818799)
(3000, array([[-0.28713715,  0.33556959,  0.38239372]], dtype=float32), 0.0070845596)
(4000, array([[-0.29557922,  0.3363795 ,  0.38460028]], dtype=float32), 0.0070754252)
(5000, array([[-0.29739448,  0.33655375,  0.38507465]], dtype=float32), 0.0070749982)
(6000, array([[-0.2977854 ,  0.33659121,  0.38517687]], dtype=float32), 0.0070749833)
(7000, array([[-0.29786775,  0.33659837,  0.38519898]], dtype=float32), 0.007074981)
(8000, array([[-0.29788083,  0.33659986,  0.38520217]], dtype=float32), 0.0070749745)
(9000, array([[-0.29788083,  0.33659986,  0.38520217]], dtype=float32), 0.0070749745)

일단 결과는 위와 같이 나왔는데 이 결과는 비용함수의 결과가 상당히 낮은 값임에도 불구하고 예제 데이터가 있던 문서에서 정리한 결과 값과 상당한 차이를 보인다. 

왜 차이가 나는지 어떻게 이 차이를 줄이도록 조정할 수 있는지는 아직 잘 모르겠다.


문서 상의 결과값 : y(x1,x2) = -0.4503 + 0.3067 * x1 + 0.4589 * x2


정리

이번 포스팅 내용울 3줄 정리해보도록 하자.

  1. 다중 선형 회귀는 단순 선형 회귀에서 독립변수의 수만 늘어난 형태이다.
  2. 가설함수는 y = W1 * x1 + W2 * x2 + ... Wn * xn + b의 형태이다.
  3. 비용함수는 단순 회귀 분석과 동일하다.

적절한 값을 구한다는 목적만 생각한다면 달리 어려운 부분은 없다. 하지만 처음 단순 선형 회귀를 정리할 때 그래프를 보면서 변화를 비교했던 것이 이해를 돕는데 

도움이 되었기에 다중 선형 회귀에서도 그런 방법이 없을까 생각했는데 독립변수 2개까지는 3차원 그래프로 표현을 하는 내용들이 간혹 보였지만 독립변수가 3개 이상인 

경우에는 그래프로 표시 하는 내용을 전혀 찾을 수가 없었다. 일단 크게 중요한 부분은 아니기에 호기심을 누르고 그냥 넘어가도록 하자. 


전체 글은 Jupyter Notebook에서 작성되었으며 전체 Notebook 파일도 아래 첨부한다.

chapter2_multi_regression.py.ipynb


블로그 이미지

마즈다

이미 마흔을 넘어섰지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^