선형회귀분석 - 문돌이 식으로 해석하기~ 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 값은 사전 데이터로 쉽게 범위를 지정할 수 있지만 학습 계수같은 경우 상당한 시행착오를 거쳐야
했다. 적절한 학습 계수를 찾는 공식이 또 따로 있다는데…-.- 그건 차차 배우기로 하자.


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

블로그 이미지

마즈다

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