머신러닝 Reboot - 개념 잡기 : 경사 하강법 1 - 특성의 scale


새롭게 시작하는 머신러닝 학습은 기존에 진행하던 학습에서 장애가 되었던 용어와 공식에 대한 몰이해를 극복하고자
진행하려고 한다. 다시 말해 직관적으로는 이해가 가지만 논리적으로 설명할 수없는 개념을 논리적으로 설명 가능하도록
정리해보고자 하는 것이다.


따라서 전체적으로 연관성을 가지고 이어지는 내용이라기 보다는 단편적인 용어의 정의나 공식의 풀이를 중심으로 
하면서 관련 내용을 정리하는 방식으로 진행이 될 것이다. 


이렇게 정리할 대상은 주로 ’핸즈온 머신러닝’이라는 책을 읽으면서 이해가 안가는 부분들을 대상으로 풀이할 것이며
전체적인 순서 역시 ‘핸즈온 머신러닝’의 목차를 따를 것이다. 


들어가는 말


지난 시간에는 선형 회귀 분석의 비용함수로부터 가중치(𝜽 또는 W)의 최솟값을 한방에 알아낼 수 있는 정규방정식
대해 알아보았다. 미분 등 복잡한 계산이 필요 없고 학습률같은 하이퍼파라미터를 관리할 필요가 없으며 또 빠른 예측이
가능하다는 장점이 있지만 특성 수가 늘어남에 따라 속도가 많이 느려지는 단점이 있었다.


오늘은 정규방정식의 단점을 해결할 수 있는, 다시 말해 특성 수에 관계 없이 일정 수준의 성능을 보장해주는 
경사하강법애 대한 내용 중 특성의 스케일에 대해 알아보려고 한다. 


경사하강법은 대체로 특성의 스케일에 민감한 것으로 알려져 있으며 일반적으로 아래 그래프로 그 사실을 설명한다.


핸즈온 머신러닝 발췌핸즈온 머신러닝 발췌

오늘은 경사하강법이 특성에 민감하다는 것을 예제 코드를 통해 조금 더 직관적으로 설명을 하고자 한다. 
사실 논리적으로 증명을 하고싶었으나 역시 나의 실력으로는 역부족이었다. 이 내용과 위의 그래프를 이해하지
못하여 이 포스팅을 준비하는데 무려 3주가 걸겼다…ㅠ.ㅠ


게다가 내가 그간 얼마나 공부를 설렁설렁 했는 지 이번 기회에 알게 되었다. 그동안 나는 선형회귀의 비용함수와 
경사하강법을 동일시 하여 생각했던 것이다. 서로 다른 함수를 동일하다고 생각하고 분석하고 있었으니 답이
나올리가 있나…-.- 겨우 최근에야 경사하강법은 특정 함수(특히 convex 함수)의 최적값을 찾아낼 수 있는
일반적인 알고리즘이라는 말을 이해하게 되었다.


다시 말해 경사하강법은 선형회귀의 비용함수 뿐만 아니라 볼록(또는 오목)한 그래프가 그려지는 함수라면 어떤
함수이든 그 최저점을 찾아낼 수 있는 방법이라는 것이다.


지금부터 코드를 통해 이 내용을 간단히 살펴보자. 너무 간단해서 들어가는 말보다 본문이 짧을지도…-.-


Python 코드로 보는 경사 하강법


이 내용의 원본 소스 출처는 다음과 같다.


https://github.com/shuyangsun/Cost-Function-Graph


이 원본 소스 중 non-convex 함수들에 대한 내용은 제거 하고 convex 함수에 대한 내용만을 남겨 확인해보았다.


일반적으로 특성이 2개인 함수까지는 시각화(그래프로 표현)할 수 있다. 이 부분은 내가 처음 머신러닝을 공부한다고
정리를 하면서 다항로지스틱으로 넘어갈 때 꽤나 답답해 했던 부분이기도 하다. 특성이 2개인 경우까지는 시각화가
가능해서 직관적으로 이해를 할 수 있었는데 특성이 3개 이상 되니 복잡한 수식만으로는 도무지 이해가 가지 않는
것이었다…ㅠ.ㅠ


여전히 특성이 3개 이상인 경우는 이해가 힘들기 때문에 오늘은 특성이 2개인 케이스를 대상으로 설명을 해보겠다.


이 코드에서 사용할 함수는 비용함수는 아니고 f(a,b) = a^2 + b^2 이라는 함수이다. 이 함수가 표현하는 범위를
3차원 그래프로 그려보고 그 범위 안에서 경사하강법이 어떤 경로로 최저점을 찾아가는지 보여주는 것이 아래의
코드이다.


import numpy as np
import matplotlib.pyplot as plt
import math
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D

def f(a,b):
	return a**2 + b**2

먼저 필요한 라이브러리들을 import하고 경사하강법을 통해 최저점을 찾아낼 대상 함수 f를 정의했다.


def gradient_descent(theta0, iters, alpha):
	history = [theta0] # to store all thetas
	theta = theta0     # initial values for thetas
	# main loop by iterations:
	for i in range(iters):
		# gradient is [2x, 2y]:
		gradient = [2.0*x for x in theta] #함수 f(x,y)의 미분
		# update parameters:
		theta = [a - alpha*b for a,b in zip(theta, gradient)]
		history.append(theta)
	return history

history = gradient_descent(theta0 = [-1.8, 1.6], iters =30, alpha = 0.03)


다음으로 경사하강법을 함수로 정의하고 호출하여 그 결과를 history에 저장을 한다. 경사하강법의 다음 스텝을 결정하는
일반식은 다음과 같으며 이를 python 코드로 구현한 것이다.




경사하강법을 구현한 함수는 파라미터로 특성의 초깃값과 반복 횟수 그리고 학습률을 전달받는다. 이 코드에서 초깃값은 각 특성의 max에 가까운 값으로 정했다.


# f(x,y) = x^2 + y^2 함수의 그래프 그리기 fig = plt.figure(figsize=(20, 15)) ax = fig.gca(projection='3d') #plt.hold(True) a = np.arange(-2, 2, 0.25) b = np.arange(-2, 2, 0.25) a, b = np.meshgrid(a, b) c = f(a,b) surf = ax.plot_surface(a, b, c, rstride=1, cstride=1, alpha=0.3, linewidth=0, antialiased=False, cmap='rainbow')



주석된 내용처럼 f 함수가 표현하는 함수의 범위를 3차원으로 그려주는 코드이다. 특성 a와 b 모두 -2부터 2 사이의 값을
가지며 0.25씩 증가 하도록 값을 주었다. 함수가 a^2 + b^2이기 때문에 최솟값 0부터 최댓값 8까지의 그릇 모양으로
그래프가 표시된다.


a = np.array([x[0] for x in history])
b = np.array([x[1] for x in history])
c = f(a,b)
ax.scatter(a, b, c, color="r"); 

print(c)

plt.xlabel('Feature A')
plt.ylabel('Feature B')

plt.axis('equal')

plt.show()


이제 마지막으로 이전에 그려진 그래프 내에서 경사하강법을 통해 산출한 위치를 표시해준다. 특성이 2개이기 때문에
각각의 특성에 경사하강법을 적용한 결과를 그래프에 그려보면 최종적으로 아래와 같은 그래프를 볼 수 있다.



이 때 a = np.arange(-2, 2, 0.25)의 범위를 a = np.arange(-10, 10, 0.25)로 늘리게 되면 그래프의 형태가
오목한 그릇 형태가 아닌 u자 모양으로 휘어진 판자의 형태가 된다.



이런 상황에서는 가중치의 초깃값이 커질 수 있고 초깃값이 커지면 최솟값을 찾는데 그만큼 더 시간이 오래 걸리게 되며 이는 곧 특성값의 스케일 차이가 크게 되면 경사하강법의 성능이 나빠지게 된다고 볼 수 있는 것이다. 또한 내가 제대로 
이해하고 있는지 모르겠으나 이 그래프 표현만 놓고 보면 단지 두 개의 특성간에 스케일의 차이가 있을 때 뿐만 아니라 
두 특성의 스케일이 동일하더라도 그 규모가 커지면(예를들어 a와 b의 범위가 모두 10인 경우와 모두 100인 경우)
이 때 역시 경사하강법의 성능이 나빠져 더 많은 횟수를 진행해야 최솟값에 가까워지게 된다.


정리


앞서도 말했지만 이 부분을 이해하기 위해 장장 3주 이상이 걸렸다. 그러다가 위의 python 코드를 발견했고 처음 코드를
실행해봤을 때는 ‘유레카’를 외쳤지만 지금 다시 찬찬히 살펴보는 과정에서는 또 수많은 의문들이 일어나면서 내가 
제대로 이해한 것이 맞는지 알 수 없게 되었다…ㅠ.ㅠ 일단 직관적으로 생각했을 때 작은 수를 계산할 때보다 큰 수를 
계산할 때 더 많은 자원이 필요한 것은 당연한 것이니 특성의 스케일이 크면 그만큼 연산이 오래 걸린다고 보면 될 것이나
역시 완전한 이해는 되지 못한다.


더 나은 해법을 찾다가 contour라는 등고선 형태의 그래프를 그리는 방법이 있다는 것을 알아냈고 이 그래프가 위에
언급한 핸즈온 머신러닝의 그래프와 유사해서 더 설명하기가 좋지 않을까 생각했으나 실제 코드에 적용하는 방법을
몰라 이번 포스팅에서는 다루지 못했다. 시간 날 때 다시 정리를 해봐야겠다.


다음 시간에는 경사하강법의 3가지 종류(배치 경사하강법, 확률적 경사하강법, 미니 배치 경사하강법)에 대해 간단하게
정리해보겠다.

블로그 이미지

마즈다

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

머신러닝을 위한 기초 수학

웬만하면 가급적 수학적인 지식은 상식적인 차원에서 알고 넘어가고자 했으나 아무래도 무리인 것 같다.
일단 기본적으로 필요한 수학적 지식을 그 때 그 때 정리하고 넘어가도록 하자. 오늘은 지난 글에서 정리한
두 개의 함수 중 선형회귀분석의 가설함수를 이해하는데 필요한 수학적 지식인 기울기와 절편에 대해 정리를 
해보고자 한다.


이 글은 어디까지나 나와 같이 수학적 지식이 모자라는 사람을 위한 것이며 따라서 수학적 증명같은 것은
없다 오로지 직관이 있을 뿐…-.-


직선의 기울기

기울기는 우리가 흔히 아는 바와 같이 직선이 얼마나 기울어져있는가 하는 정도이다. 일상 생활에서는 일반적으로
각도로 표시를 하지만 여기서는 수학적 계산이 필요하므로 수학적인 부분만 살펴보자. 아래 그림을 보자


높이를 조절할 수 있는 유아용 미끄럼틀을 생각해보자. 직관적으로 보아 A 미끄럼틀이 B 미끄럼들보다 가파르다.
즉 기울기가 크다. 역으로 B는 A 보단 완만하다. 조금 더 수학적으로 살펴보면 기울기가 클 경우에는 아래 평면인
x의 길이보다 높이인 y이가 크고 반대로 기울기가 작을 경우에는 x의 길이가 y의 길이보다 크다. 즉, 기울기라는
것은 수평의 선 x와 높이 y의 관게에 의해서 결정되며 이 관계에 의하면 바로 위에 본 것처럼 y가 커지면 기울기도
커지고 y가 작아지면 기울기는 작아지는 관계다. 즉 기울기를 m이라고 하면 이 기울기는 다음과 같이 수학적으로
표현할 수 있다.


x는 분모이고 y는 분자이기 때문에 x가 작아질수록 혹은 y가 커질수록 기울기 m은 커지고 그 반대가 되면 기울기는
작아진다. 여기서 또 한가지! 아무리 수포자라도 양 변에 같은 값을 곱하면 여전히 양 변의 값은 같다는 것 정도는
알 것이다…-.- 양 변에 분자인 x를 곱하면 수식은 다음과 같이 바뀐다.


어렴풋이, 하지만 쉽게 잊혀지지 않을만큼 기억나는 1차 방정식의 공식이다. 이제 그림도 수학적으로 바꿔보자.
미끄럼틀이 아닌 x축과 y축으로 이루어진 평면 좌표가 있을 때 이 좌표상의 한 직선(파란색 직선)의 기울기는
y의 변동량을 x의 변동량으로 나눈 값 즉, (y2 - y1) / (x2 - x1)이 되는 것이다.

절편

위에 설명한 기본 1차함수의 경우 x가 0이면 곧 값도 0이된다. 즉, 이 직선은 좌표 평면에서 항상 x축과 y축이
만나는 원점을 지나는 직선이 되는 것이다. 하지만 때로는 원점을 지나지 않는 직선도 있을 것이다. 


현실의 예를 들어보자. 마침 최근 넷마블의 100억 인센티브가 이슈이니 회사의 총 이익과 직원의 총 급여의 관계를 
그래프로 나타낸다고 생각해보자. 총 급여는 기본급 + 인센티브다. 회사의 총 이익을 x, 직원의 총 급여를 y로 본다면
x가 증가함에 따라 직원의 총 급여는 증가할 것이다. 하지만 회사의 이익이 0이 되더라도 직원의 총 급여는 0이 되지 
않는다. 회사가 공식적으로 파산을하지 않는 한 기본급은 지급을 해야 하므로 매출이 0이더라도 y축은 0이 아닌
기본급을 표시하는 위치에 있을 것이다. 그러다가 이익이 -로 돌아서면 어느 순간 직원의 급여를 지급하지 못하게
되어 결국은 x축인 직원의 총 급여도 0이 될 것이다.


이렇게 평면 좌표상의 어떤 직선이 원점을 지나지 않는 경우 이 직선이 y축과 만나는 점을 y절편, x축과 만나는 점을
x절편이라고 한다. 


이 것은 다시 말하면 기본적인 1차 방정식에서 x에 0을 대입했을 때의 y의 값은 y절편, y에 0을 대입했을 때의 
x의 값은 x 절편으로 볼 수 있는 것이다. 그래서 1차 방정식은 다음과 같이 확장된다.


절편은 다른 의미로 같은 기울기의 직선이 상하 또는 좌우로 얼마나 움직였는지를 나타내주기도 한다.


선형회귀분석 가설함수 복습

우선 선형회귀분석의 가설함수를 다시 한 번 보자.


이전에 알아본 내용과 오늘 학습한 내용으로 알 수 있듯이 서로 관계가 있는 두 변수 x(총 이익)와 y(총 급여)를 
분석해보고 이 두 변수 사이의 관계를 밝혀내기 위한 기법이 선형회귀분석이다. 학습을 위해 만들어낸 데이터가
아닌 현실의 데이터라면 웬만해서는 완전한 직선으로 나타나는 경우는 거의 없을 것이다. 따라서 그러한 현실
데이터를 가장 근접하게 표현해줄 수 있는 직선 즉, 1차 방정식을 찾아내는 것이 바로 선형회귀분석의 목적이다.


우리가 학교에서 수학을 배울 때는 x 또는 y의 값을 구하는 것이 보통이었다. 하지만 현실의 상황을 분석하는
경우 x와 y는 이미 알고 있는 데이터들이다. 이런 상황에서 x와 y가 만나는 많은 점들 중 가장 많은 수와 근접한
직선을 찾아내는 것이 목적인 바 결국 이 것은 직선의 기울기를 알아내는 것이라 할 수 있다. 결국 위의 공식에서
x와 y는 이미 우리가 알고 있는 값이고 우리가 찾아야 할 것은 W인 것이다.


한편 단순히 기울기만을 가지고 찾는다는 것은 불가능한 것이 기울기가 같은 직선은 무수히 많기 때문에 단순히
기울기만 같다고 해서 데이터를 가장 잘 표현해준다고 할 수는 없는 것이다. 직선을 위나 아래로 움직이면서
x와 y가 만나는 점들과 가장 오차가 적은 위치를 찾아야 할 것이다. 이 것이 바로 공식의 b에 해당하는 값이고
한쪽으로 치우쳤다는 의미에서 편향이라는 용어로도 사용된다.


마지막으로 정리하자면 선형회귀분석을 학습시킨다는 의미는 x 변수들과 y 변수들의 어느정도 비율로 관련이 
있는지(기울기)를 알아내고 그렇게 알아낸 기울기의 직선과 실제 데이터와의 오차를 절편을 이용해서 보정하는 
작업이라 할 수 있겠다.

블로그 이미지

마즈다

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

선형회귀분석과 관련된 2가지 함수의 의미 정리

이 글은 홍콩 과기대의 김성훈 교수님의 유튜브 강좌를 보고 제 나름대로 조금 더 이해를 하려고 풀어서
정리해본 글입니다.


  • 선형회귀분석 가설함수(Hypothesis function)

선형회귀분석의 기본적인 가설 함수 (Hypothesis function). 기존 데이터를 가장 잘 표현하는 
직선을 결정하는 함수이며 이 직선은 W와 b를 변화시켜가면서 찾을 수 있다. 바로 이 최적의 W와 b를
찾는 것이 선형회귀분석의 목적이다. 그리고 이렇게 찾아진 W와 b를 함수에 대입하게 되면 기존의
데이터가 아닌 새로운 x값이 나타났을 때 그 새로운 x에 대한 y값을 예측할 수 있게 되는 것이다.



이 과정에서 가장 중요한 것은 기울기를 나타내는 W값이다. 즉, 전체적인 데이터가 그래프 상에서
어떤 모양(어떤 기울기의 직선형 그래프)과 가장 유사한가 하는 것을 확인하는 일이며 b는 그렇게
예측된 직선과 실제 데이터간의 오차를 보정해준다고 생각하면 될 것이다.

W의 역할b의 역할



  • 선형회귀분석 비용함수(cost function)



선형회귀분석에서 가장 적절한 W와 b의 값을 찾는 과정에서 변화하는 W와 b의 값을 검증하는 함수.
검증은 아주 상식적이며 단순하다. 우리가 예측할 직선상의 y값(가설함수의 결과인 y값)과 실제 
좌표상의 y값 (우리가 이미 알고있는 실제의y의 값)의 오차가 작으면 작을수록 좋은 값인 것이다.
그래서 우선 H(x(i)) - y(i)라는 공식이 도출된다(H(x(i))는 가설 함수의 결과를 의미한다). 
즉, 이 비용 함수는 ‘오차’에 대한 함수인 것이다.



그런데 이 값을 그냥 사용한 것이 아니라 제곱을 했다. 이 것은 그냥 사용하게 될 경우 이 값은
음수와 양수로 그 부호가 달라진다. 위 그림에서 가설함수인 빨간 선 상의 H(x(i))값에서 a의 y값을
뺄 경우에는 그 결과가 음수가 나오고 b의 값을 뺄 경우에는 양수가 나온다. 각각의 데이터에 대해
이렇게 계산을 한 후 평균을 구하기 위해 합산을 하게 되면 양수와 음수가 상쇄되어 계산에 어려움이
생길 것이다. 따라서 제곱을 함으로써 양의 정수로 만들어주는 것이다. 여기까지 만들어진 공식이
(H(x(i)) - y(i))^2이다. 그리고 이렇게 제곱을 할 경우 결과 값이 크면 클수록 제곱 값은 
기하급수적으로 커지기 때문에 일종이 패널티 역할을 하게 되는 것이다.

이후 공식은 중고등 수학책을 잠시 열어보면 쉽게 알 수 있다. 시그마(∑) 기호는 밑에 있는 i가 1부터
특정 수인 m까지 늘어나는 동안 기호 우측에 있는 계산식을 모두 더하라는 의미이고 수식의 가장
앞의 1/m은 m개의 계산 결과를 더한 것을 다시 m으로 나눈 것이니 바로 평균의 의미가 되는 것이다. 
즉, 이 cost 함수는 예측값과 실제 값의 차(오차)를 제곱한 모든 값의 평균이 되는 것이다.

이렇게 구한 cost 함수의 식을 그래프로 그려보면 다음과 같은 곡선이 만들어진다. 즉, 이 함수는 제곱에
대한 함수이기 때문에 H(x(i)) - y(i)의 절대값이 크면 클수록 cost가 커지고 절대값이 작을 수록 cost가
0에 가까워지는 U자 형태의 그래프로 표현된다.



블로그 이미지

마즈다

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


선형회귀분석 - 문돌이 식으로 해석하기~ 2


일단 지난 시간에 주저리주저리 별거 없이 지껄이다가 글이 너무 길어졌다.
오늘은 Jupyter에서 직접 코드를 수정하면서 ‘텐서플로우 첫걸음’에 실린 선형회귀분석 예제를
조금 더 상세하게 살펴보도록 하겠다. 일단 원래의 소스는 이전 글에서 Jupyter에 업로드 하는
방법을 설명하였으니 그대로 따라하면 볼 수 있을 것이다. 여기서는 데이터를 바꿔서 사용할 예정이다.


데이터 적용하기


지난 글에서도 밝혔듯이 데이터를 찾는다는 것이 결코 만만한 일이 아니었다. 그나마 찾은 데이터도
이게 적절한 데이터가 맞는지조차 모를 지경이다…ㅠ.ㅠ 일단 데이터 중 변경된 부분은 ‘서울시 시민
행복지수 통계’ 데이터가 10점 만점을 기준으로 한 점수 데이터인데 이 것을 백분률 데이터인
‘서울시 소득대비 부채현황 통계’  데이터와 맞추기 위해 백분률 데이터로 바꾸었다. 이렇게 바꾸면
두 데이터간에 상관관계가 없음(…-.-)을 명확하게 알 수 있다.


일단 Jupyter는 물론 Python 자체도 왕초보 신세이기에 파일로부터 직접 데이터를 읽어오는 방법은
모르겠고 그냥 무식하게 vectors_set에 하드코딩으로 데이터를 입력했다. 그리고 기존에 난수를
통해 데이터를 생성한 for문은 모두 주석처리 하였다. 그리고 이 새 데이터로 만든 그래프는 아래
그림과 같다. 참고로 수정 후 실행을 위해서는 상단 툴바의 >| 모양의 버튼을 클릭하면 된다. 그리고
plt에서 오류가 발생하므로 다음 코드를 plt 사용 전에 추가해주자.


import matplotlib.pyplot as plt


딱 봐도 두 데이터 간에 아무런 관계가 없음을 알 수 있다. 소득 대비 부채가 많건 적건 행복할 사람은
행복하고 아닌 사람은 아닌 것이다…-.- 그나저나 밑바닥의 점 2개는 뭔지…-.-


하지만 이 글을 통해 내가 정리하고 싶은 것은 이러한 과정에서 어느 곳에 어떤 값을 사용해야 제대로
분석이 되는가 하는 것을 밝히는 것이다.


본격적으로 텐서플로우 가동!!!


선형회귀 분석을 하기 위해 아래 이미지와 같은 과정을 거친다. 우선은 그러려니 하고 봐두자.


그리고 코드는 수정하지 말고 앞서 설명한대로 >| 버튼을 눌러 각 코드를 run 시켜보도록 하자.
아…마지막의 ‘In [9] :’라고 되어있는 부분의 소스 코드 중 plt.xlim와 plt.ylim는 적절한 범위로
수정을 해주자. 그냥 처음 이미지에서와 같은 범위로 주면 될 것이다.


.
.
plt.xlim(15,60)
plt.ylim(50,62)
.
.


그리고 결과를 보면 다음과 같다.


뭔가 좀 이상하다…원래 예제에는 그래프와 함께 파란 선이 그려졌었는데…어디갔나???


이제 코드를 하나하나 보면서 문제의 원인을 찾아보도록 하자.


처음 코드이다.


import tensorflow as tf


설명할 것도 없이 텐서플로우 라이브러리를 import하여 tf라는 이름으로 사용하겠다는 것이다.
다음은…


W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = W * x_data + b


이제 슬슬 설명할 것들이 생긴다.
W와 b는 이전 글에서 언급한대로 y = W * x + b 방정식에서의 그놈들 맞다. 값을 변경해가면서
최적의 값을 찾아야 하므로 범위를 지정하고 있다. 여기 사용된 텐서플로우 API들을 간단히 설명하면


Variable : 이름에서 짐작할 수 있듯이 텐서플로우에서 사용할 변수를 만든다.
random_uniform : 특정 형태의 배열을 만든다. 첫 번째 파라미터는 배열의 차수(여기서는 [1]이므로
1차원 배열이다)이고 두 번째와 세 번째는 이 배열에 들어갈 값의 범위이다. 즉 -1.0에서 1.0 
사이의 임의의 실수가 배열에 들어가는 것이다.
zeros : 역시 배열을 만드는데 이름에서도 알 수 있듯이 0으로 채워진 배열을 만든다. 0으로 채우니까
당연히 범위를 지정하는 파라미터는 필요없고 배열의 차원을 정하는 파라미터만 받는다. 


즉, W는 -1.0 ~ 1.0 사이의 임의의 실수를 바꾸어가면서 그리고 b는 0값을 유지하면서 가장 그래프와
일치하는 값을 찾아내는 것이다.


다음 코드를 보자


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


여기서부터는 살짝 어려워진다. 일단 선형회귀분석에서 최적의 값을 찾기 위한 알고리즘을 알아야 한다.
일단 API를 살펴보고 그 이름으로부터 미루어 짐작해보자.


square : 제곱을 해주는 함수
reduce_mean : 파라미터로 받은 배열의 평균을 구하는 함수
tran : 최적화를 위한 학습을 담당하는 객체
GradientDescentOptimizer : 경사하강법이라는 학습 알고리즘을 실행하는 함수. 파라미터는 학습
계수로 변경할 값(여기서는 W)을 얼마나 정밀하게 변경할 것인지를 결정하게 된다. 예를들어
이 값이 작으면 0.01씩 빼면서 변경하고 이 값이 크면 1씩 빼면서 변경하는 식이다.
minimize : 이름 그대로 최소값을 가져오는 함수


자 이제 정리를 해보면 loss는 y(모델 방정식을 통해 만들어낸 새로운 값)와 y_data(그래프상의
원래 y값)간의 오차를 제곱한 후 그 평균을 가져온다. 그리고 경사하강법이라는 알고리즘을 수행하는
optimizer를 만들고 이 optimizer를 통해 loss의 최소값을 가져오는 것이다. 즉, 추측한 값과
원래 값의 오차를 제곱한 후 그 평균 중 가장 작은 값을 가져오는 방식으로 학습을 진행하는 것이다.


쓰는 놈도 뭔소린지 모르니 그냥 그러려니하고 넘어가자. 어차피 중요한 내용은 마지막에 3줄 요약
할테니까…-.-


다음 코드를 보면…


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


이전까지의 코드는 연산에 필요한 데이터를 만든 것이고 실제 연산은 Session을 생성하여 이로부터
run을 실행할 때 비로소 연산이 시작되는 것이다. 즉, 위의 코드는 모든 변수를 초기화 하는 작업이다.


마지막 코드를 보면 다음과 같다


for step in range(8):
sess.run(train)
print(step, sess.run(W), sess.run(b), sess.run(loss))
산포도 그리기
plt.plot(x_data, y_data, 'ro')
직선 그리기
plt.plot(x_data, sess.run(W) * x_data + sess.run(b))
x, y 축 레이블링을 하고 각 축의 최대, 최소값 범위를 지정합니다.
plt.xlabel('x')
plt.xlim(15,60)
plt.ylim(50,62)
plt.ylabel('y')
plt.show()


여기서 비로소 학습을 실행하고 그 결과를 그래프로 표시하게 된다. plt를 사용한 부분은 모두 그래프를
그리기 위한 코드이고 실제 학습을 진행하는 것은 sess.run(train)이다. 이 학습을 총 8번 수행한다.


문제점 찾기


자…이제 대충은 흐름을 알것 같은데…도대체 왜 학습을 통해 나온 결과가 그래프에 표시되지 않는
것일까?


여기서 중요하게 생각해야 하는 부분은 아직까지는 컴퓨터가 모든 것을 스스로 알아서 처리하는 단계가
아니라는 점이다. 즉, 어느정도 사람이 도움을 주어야 한다. 그렇다면 어디에서 어떻게 도움을 주어야
할까?


우선 떠오르는 부분은 바로 W와 b의 예상값의 범위를 만드는 부분이다. 우리는 이미 그 이전에
원래의 데이터로 생성된 그래프의 형태를 알고 있다. 즉, 그 그래프에 그려진 데이터의 대부분을
관통하는 직선의 기울기와 y절편을 어느 정도는 파악할 수 있다는 것이다. 따라서 무작정 아무생각
없이 W와 b의 범위를 정하기 보다는 원래 그래프와 근사한 값으로 범위를 좁혀주는 것이 텐서플로우가
작업하기 수월할 것이다.


여기서 내가 사용한 데이터 분포를 보니 거의 수평에 가까운 직선이다. 즉 기울기는 0에 가깝고 y절편은
60에서 61 언저리다. 따라서 나는 코드를 이렇게 바꾸었다. 기울기가 0에 가까우므로 W는 0으로 
채워진 배열을 주었고 y절편인 b는 59~61 사이의 임의의 실수가 들어가도록 하였다.


W = tf.Variable(tf.zeros([1]))
b = tf.Variable(tf.random_uniform([1], 59.0, 61.0))
y = W * x_data + b


그리고 다시 한번 실행을 해보자


아…여전히 예측 값을 표현할 파란 선이 안보인다. 그런데 이상한 점 하나를 발견했다. 코드에서 print로
각 값들을 찍고 있는데 그 출력 결과를 한 번 보자. 좌측부터 각각 학습 수행 횟수, W값, b값, 그리고
오차의 제곱 평균의 최소 값이다. 특히 눈여겨봐야 할 것은 제대로 학습이 진행된다면 이 loss값은

적절한 범위 내에서 점점 작아져야 한다.


print(step, sess.run(W), sess.run(b), sess.run(loss))


출력 결과이다.


(0, array([ 33.58777618], dtype=float32), array([ 59.98347092], dtype=float32), 1446895.1)
(1, array([-43044.61328125], dtype=float32), array([-1115.60925293], dtype=float32), 2.3799283e+12)
(2, array([ 55205580.], dtype=float32), array([ 1506647.75], dtype=float32), 3.9146391e+18)
(3, array([ -7.08022108e+10], dtype=float32), array([ -1.93222899e+09], dtype=float32), 6.4390147e+24)
(4, array([  9.08052220e+13], dtype=float32), array([  2.47812089e+12], dtype=float32), 1.0591254e+31)
(5, array([ -1.16459456e+17], dtype=float32), array([ -3.17823849e+15], dtype=float32), inf)
(6, array([  1.49361495e+20], dtype=float32), array([  4.07615184e+18], dtype=float32), inf)
(7, array([ -1.91559007e+23], dtype=float32), array([ -5.22774354e+21], dtype=float32), inf)


loss 값이 지나치도록 급격하게 작아지고 있다. 그러다보니 W와 b값은 그래프에 표현될 범위를 벗어나고 만 것이다.


이 것을 조정해 줄 것이 바로 학습 계수이다(학습 횟수와는 다르다. for문을 통해 8번의 학습을 진행한 것은
학습 횟수이고 학습 계수는 학습할 값의 범위에 대한 정밀도라고 생각하면 되겠다).


그래서 이번에는 다음의 코드를 수정했다.


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


이렇게 하고나서야 겨우 파란 선형 그래프를 볼 수 있었다. 보다시피 엄청나게 작은 학습 계수를 주었다.
이렇게 하면 정밀도가 높아지는 대신 좁은 범위를 자주 왔다갔다 해야 하기 때문에 당연히 시간이 오래
걸린다. 물론 지금과 같은 적은 데이터에서는 별 차이를 못느끼지만.


결론


나의 문제는 늘 불필요하게 글이 길어지는 것이다…-.-
사실 내가 늘어놓은 많은 이야기들이 쓸모없는지도 모르겠다. 더 잘 설명해놓은 곳이 바닷가 모래알만큼이나
많을 것이다. 그럼에도 굳이 이 글을 적은 이유는 오로지 한가지 이유에서였다. 자신만의 데이터를 가지고
작업을 하는 것이 최종 목적일진대 적어도 그 과정에서 무엇을 어떻게 조작해야 하는지 정도는 알고 있어야
할 것이기 때문이다. 이 선형회귀분석에서는 W와 b에 대한 적절한 범위를 지정해주는 것, 그리고 적절한
범위의 학습 계수를 설정해 주는 것
이 매우 중요한 일이었다. 사용한 데이터가 영 쓸모 없는 것이 문제는
문제였지만…-.-


W나 b 값은 사전 데이터로 쉽게 범위를 지정할 수 있지만 학습 계수같은 경우 상당한 시행착오를 거쳐야
했다. 적절한 학습 계수를 찾는 공식이 또 따로 있다는데…-.- 그건 차차 배우기로 하자.


일단 선형회귀분석은 여기까지…

블로그 이미지

마즈다

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


선형회귀분석 - 문돌이 식으로 해석하기~

지난 번 Docker를 이용하여 텐서플로우를 설치해보았다. 일단 설치는 잘 되었고 부수적으로 Jupyter라는
웹 기반의 Python IDE도 알게 되었다. 그리고 일단 설치를 했으니 공부좀 해보자 하는 마음에 책을 한 권
e-Book으로 구매했다. “텐서플로우 첫걸음(조르디 토레스 지음, 박해선 옮김, 한빛미디어 출판)”이라는
책이었다. 0장 한국어판 서문과 1장 텐서플로우 기본 다지기를 어렵지 않게 넘기고 드디어 2장 에 다다르게


되었다. 2장의 내용은 텐서플로우를 이용하여 선형회귀분석을 하는 것이다.


선형회귀분석…선형…직진하다가…회귀…유턴하는건가…-.-?
아무튼 AI에 관심을 가지면서 많이 들어보던 용어인데 사실 수학 용어에 알러지 반응이 있어 이런 용어들이
나오면 무의식적으로 워프를 해버린다…-.- 그래도 본격적으로 공부를 해보기로 했으니 이제는 그러면
안될 것 같아 조금 자세히 들여다 보려는데…아무래도 텐서플로우 입문서이다보니 설명이 매우 간략하다.
그리고 뒤에서 말해주겠다고 약올리는 부분도 있고…


애초에 어려운 알고리즘에 대한 내용은 우선 피하고 텐서플로우 자체에 대해 알아보자고 계획을 잡았지만
이놈에 호기심이란…내가 만일 판도라였다면 제우스 면전에서 상자를 열어재꼈을 것이다…-.-


그래서…지난 1주일 내내 도대체 선형회귀분석이 뭔지 알아보느라 시간을 보냈다. 그 것을 이번 포스팅에
정리해보고자 한다. 물론 문돌이의 정리이기에 어디로 튈지는 장담 못한다…ㅠ.ㅠ


Jupyter부터…


지난 시간에 다음에는 Jupyter를 좀 알아보자고 했었는데…사실 Jupyter는 IDE 툴이라서 사용법 외에
특별히 설명할 만한 것이 없는 것 같다. 우선 Jupyter 공식 홈페이지와 사용법을 비교적 잘 설명한 블로그
하나를 링크하는 것으로 대신하고 본문 내용 중에 예제 업로드 하는 방법 정도 설명하겠다.


Jupyter 공식 홈페이지 : http://jupyter.org
옥수별님 블로그 : http://sams.epaiai.com/220763757405


예제 업로드 하기


우선 시간 절약을 위해 텐서플로우 첫걸음의 제 2장 예제를 Jupyter에 업로드 하도록 하겠다.
원 저자의 소스도 있지만 번역을 하신 박해선 님의 소스가 좀 더 깔끔하고 Jupyter Notebook
으로 설명도 곁들여져 있어 공부하기 좋다. 


소스 link : https://github.com/rickiepark/first-steps-with-tensorflow


아래와 같은 과정으로 Jupyter에 소스를 업로드 하자.


  1. Jupyter Notebook 목록 우측 상단에 있는 Upload 버튼을 클릭한다.


  2. 파일 선택 창이 나오면 소스코드를 다운로드 받은 위치로 이동하여 선형회귀분석 소스인

    chapter2_regression.py.ipynb를 선택하고 선택 버튼을 누른다.


  3. Notebook 목록에 방금 올린 소스 파일명이 가장 위에 보여지면 그 오른쪽의 파란색 Upload
    버튼을 한 번 더 눌러준다.


  4. 소스 파일의 업로드가 완료되면 아래 그림처럼 기존의 다른 Notebook들과 같은 형태로
    표시된다.


  5. chapter2_regression.py.ipynb를 클릭하면 드디어 소스를 조작할 수 있는 Notebook
    화면이 등장한다.


선형회귀분석 간단 정리


우선 가장 단순하게 시작을 해보도록 하자. 우선 아래 2가지만 알고 가도록 하자.


  • 한 변수(y, 종속 변수)가 다른 변수(x, 독립 변수)에 의해 어떻게 영향을 받는지 분석한다. 
    즉, 두 변수간에 인과관계가 있을 때 유효하다. (예 : 소득 수준과 문화 생활에 지출하는 비용간의 관계)
  • 선형회귀 모델을 표현하는 함수는 y = W * x + b이다.


일단 종속 변수에 대해 독립 변수가 1개인 것을 단순 선형회귀라고 하며 오늘은 이 단순 선형회귀만
다룬다. 선형 회귀라는 이름에서 ‘선형’이 의미하는 것은 분석의 결과가 직선의 형태를 띠고 있기 때문이다.


(회귀라는 용어에 대해서는 다음 링크 참조 : 
http://terms.naver.com/entry.nhn?docId=1625383&cid=42251&categoryId=42262
)


이 것은 두 번째 적은 함수에서도 알 수 있다. 바로 W라는 기울기와 b라는 y절편을 갖는 1차방정식이다.
이는 좌표 평면상에서 직선을 표현하게 된다.


이러한 기본 지식을 가지고 선형회귀분석을 간단하게 요약 설명하면 과거의 데이터를 통해 선형회귀 모델
방정식을 가장 잘 표현하는 W값과 b값을 찾고 이렇게 찾은 W값과 b값을 방정식에 대입하게 되면 나중에
새로운 x가 발견되었을 때 이 방정식을 통해 y의 값을 추측해낼 수 있는 것이다. 또 다른 측면에서는 
y와 x가 관계가 있는지 없는지, 관계가 있다면 얼마나 있는지를 확인하는 목적에도 사용될 수 있다.


아래 우측 그림과 같은 그래프로 표현되는 데이터 집단이 있다면 이 그래프를 가장 잘 표현하는 직선은
오른쪽 그림의 파란 선이 될 것이다. 바로 이 파란 선을 만들 수 있는 W와 b의 값을 찾는 것이 1차 목적이다.
(아래 그림은 텐서플로우 첫걸음의 예제로부터 가져온 것이다.)


실제로 해보기


설명을 하고나서도 뭔소린지 당췌 모르겠다…ㅠ.ㅠ 게다가 예제에서는 아무런 의미도 없는 난수를
통해 생성한 데이터를 가지고 설명을 하니 왜 이런 짓거리를 해야 하는건가 하는 생각조차 든다.
그러니 실생활에서 접할 수 있는 데이터를 가지고 한 번 진행을 해보자


집고 넘어가자!
구글이 텐서플로우같은 훌륭한 라이브러리를 공개하는 것에 대해 매체에서 언급하는 내용 중 2가지만
짚어보자


  1. 알고리즘이나 툴 자체보다는 데이터가 더 중요하다.
  2. 세상에는 많은 업무 분야들이 있고 아무리 구글같은 회사라 하더라도 그 모든 분야를 모두 아우를
    수는 없다. 따라서 각 분야의 전문가들이 텐서플로우를 어떻게 이용하는지 알 수 있는 기회가 된다.


이 이야기가 선뜻 와 닿지 않았는데 이번에 ‘실생활에서 접할 수 있는 자료로 공부해보자’라고 생각하고
진행하는 과정에서 뼈저리게 느꼈다. 적절한 데이터를 찾는 작업이 결코 만만한 작업이 아니었다.
실제로 데이터 사이언티스트들이 이런 일을 하는지는 모르겠지만 분석이 필요한 데이터를 발굴하고
이 데이터사이의 연관성을 찾아내는 작업이 얼마나 어려운 일인지를 새삼 깨다았다.


실생활에서 접할 수 있는 데이터를 찾기 위해 우선 생각한 것이 공공데이터 포털이었다. 사실 일주일 동안
가장 시간을 많이 들인 것이 이 공공데이터 포탈에서 분석이 가능할만한 데이터, 인과관계가 있을 법한
데이터를 찾는 일이었다. 결과부터 말하면 결국 찾지 못했다. 독립 변수로 쓸 데이터와 종속 변수로 쓸
데이터를 찾아야 하는데 시계열의 범위가 맞지 않거나 아예 시계열 데이터가 없거나 한 것들이 많았다.
일단 ‘서울특별시 사회인식 통계’라는 항목에서 몇가지 자료를 추출했지만 겨우 시계열 범위가 일치할
뿐, 구별로 분산된 데이터인데다가 얼핏 봐도 하나의 독립 변수로는 설명이 되지 않는 그런 데이터였다.


그래서…엉망이나마 이번에 사용할 데이터는 아래 2개의 데이터인데 앞서 말한 바와 같이 구별로
측정된 데이터를 구분없이 하나로 통합해서 사용하였다.


‘서울시 소득대비 부채현황 통계’ 

서울시 소득대비 부채현황 통계.xls

‘서울시 시민행복지수 통계’

서울시 시민행복지수 통계_2003.xls

서울시 시민행복지수 통계_2004.xls

서울시 시민행복지수 통계_2005.xls

서울시 시민행복지수 통계_2006.xls


생각보다 글이 길어져 본격적인 분석은 다음 글에 이어서 하도록 하겠습니다~

블로그 이미지

마즈다

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