블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

수포자를 위한 딥러닝

#4 - 로지스틱 회귀를 이용한 이항 분류 문제의 해결

조대협 (http://bcho.tistory.com)


1장에서 머신러닝의 종류는 결과값의 타입이 연속형인 Regression (회귀) 문제와, 몇가지 정해진 분류로 결과(이산형)가 나오는 Classification(분류) 문제가 있다고 하였다. 2,3장에 걸쳐서 회귀 문제에 대해서 알아보았고, 이번장에서는 로지스틱 회귀를 이용한 분류 문제에 대해서 알아보자.

이 글의 내용은 Sung.Kim 교수님의 “모두를 위한 딥러닝”(http://hunkim.github.io/ml/) 을 참고하였다. 여러 자료들을 찾아봤는데, 이 강의 처럼 쉽게 설명해놓은 강의는 없는것 같다.

분류 문제(Classification)의 정의

분류 문제란 학습된 모델을 가지고, 입력된 값을 미리 정해진 결과로 분류해주는 모델을 이야기 한다.

분류 결과가 참/거짓과 같이 두개만 있을때 이항 분류 분석, 두개 이상일때는 다항 분류 분석이라고 하는데, 이번장에서 살펴볼 로지스틱 회귀 분석은 분류된 결과가 두 가지만 있는 이항 분류 모델이다. (다항 분류 모델은 로지스틱 회귀에 이어서 소프트맥스 회귀 분석에서 설명하도록 하겠다.)


이항 분류의 대표적인 예는 다음과 같다.

  • 이메일 스팸과 정상 이메일 검출

  • 신용카드 거래에서 정상 거래와 이상 거래 검출

  • 게임에서 어뷰징 사용자와 정상 사용자 검출


등이 이항 분류의 예가 될 수 있다.


예를 들어 아래와 같은 데이타가 있다고 가정하자



붉은 동그라미로 표시된 데이타와, 녹색 세모로 표시된 데이타를 분류하고 싶을때, 아래와 같이 이항 분류 문제는 이를 분류할 수 있는 이상적인 직선 그래프를 찾는 것이다.


로지스틱스 회귀 분석 (Logistics Regression)

선형 회귀 분석 (Linear regression) 으로 분류 문제 접근하기

이항 분류 모델에 대한 예를 들어보자. 종양의 크기에 따라서 양성 종양(암)인지 음성 종양인지를 판별하는 문제가 있다고 하자. 아래 그림은 종양의 크기에 대한 양성과 음성 여부를 그래프로 나타낸 것인데, X축은 종양의 크기, Y축은 종양의 양성과 음성 여부를 나타낸다. 1이면 양성 0이면 음성이다.


이 문제를 선형 회귀 모델로 정의해서 그래프를 그려보면 다음과 같다.

y=W*x

와 같은 그래프가 그려지고 대략 아래 그림에서 보는것과 같이 y > 0.5 보다 크면 양성 암, y <0.5 보다 작으면 음성암으로 판단할 수 있다.


그런데, 만약에 새로운 트레이닝 데이타에서, 종양의 크기가 큰 데이타가 들어오면 어떻게 될까?

아래 그림을 보자, 예를 들어 새로운 트레이닝 데이타에 종양의 크기가 5인 경우 양성 암이라는 데이타가 새로 들어왔다고 하자


이 경우 앞에서 선형 회귀로 만든 그래프의 기울기가 새로 들어온 데이타를 포함하면 맞지 않기 때문에 선형 회귀로 재학습을 시키게 되면 다음과 같은 기울기(점선 그래프)로 변하게 된다.


이 경우에는 앞에서 암이 양성인 여부를 판단할때 사용한 y가 0.5라는 기준은 더이상 사용할 수 없게 되고, y가 0.2 일때를, 새 기준으로 잡아서, 암의 양성/음성 여부를 판단해야 한다.

그러면 새로운 데이타가 들어올때 마다 기준점을 다시 잡아야 하는것인가? 또한 그렇게 만든 모델로 예측을 한다면, 학습에 사용되지 않은 큰 데이타가 들어온다면 오류가 발생할 수 도 있다.

그래서, 선형 회귀 분석 모델(Linear regression) 은 이항 분류에 적절하지 않다. 그렇다면 어떤 모델이 적절할까 ?

참고



시그모이드(sigmoid) 함수

이런 형태의 이항 분류 분석에 적절한 함수로 시그모이드(sigmoid)함수라는 것이 있다. 그래프의 모양은 다음과 같다. S 자 형태의 그래프 모양으로 중심축 (x=0)을 중심으로 좌측은 0으로 수렴하고 우측은 1로 수렴한다.



이 시그모이드 함수에 앞의 데이타를 적용해보면 다음과 같은 형태가 된다.


그림과 같이 y축을 0.5를 기준으로 판단할때 y가 0.5 일때 x가 2 인데, x<2 인 부분은 y=0으로 음성, x>2인 부분은 y=1로 양성이 된다.

큰 데이타 (x=100)가 추가된다하더라도 시그모이드 함수는 그 값이 1로 수렴되기 때문에, 앞의 선형 회귀 분석의 경우 처럼 암의 양성/음성인 경우를 결정하는 y와 x값이 변화하지 않는다.

가설 (Hyphothesis)

그래서, 이 시그모이드 함수를 사용하여 가설을 정의할 수 있다.

가설은 아래와 같다

y = sigmoid(Wx + b)


결과 값 y 는 0 또는 1의 값을 갖는다. 시그모이드 함수를 수학 공식으로 표현하면, 아래와 같다. (그렇다는 것만 알아두고 외워서 쓰자)
1 / (1+ math.exp(-(W*x+b) )


디시전 바운드리(Decision boundary)


y = sigmoid (W*x + b) 을 가설 함수로 그려진 위의 그래프에서 W는 1, b는 -2로 그래프로 우측으로 두칸을 이동하였다.

W에 1,  b에 -2 를 대입해보면 y = sigmoid(1*x-2) 의 형태의 그래프인데, “x가 2를 기준으로 좌,우측이 양성암이냐 아니냐” 를 결정하기 때문에 때문에,

1*x-2 <0 이면,  y=0이 되고

1*x+2 >0 이면, y=1이 된다.

이를 일반화 해보면, 시그모이드(sigmoid) 함수 내에 들어가는

W*x+b < 0 이면,  y=0이 되고,

W*x+b > 0 이면,  y=1이 된다.

즉 로지스틱 회귀 분석은 위의 조건을 만족하는 W와 b의 값을 찾는 문제이다.

그리고  시그모이드 함수내의

z=W*x+b

그래프를 기준으로나눠서 y가 0또는 1이 되는 기준을 삼는데, 이 그래프를 기준으로 결정을 하기 때문에, 이를 디시전 바운드리 (Decision boundary) 라고 한다.


변수가 x하나가 아니라, x1,x2가 있는 문제를 살펴보자


이 문제에서 가설 함수 y = sigmoid (W1*x1 + W2*x2 + b)가 될것이고,

z=W1*x1+W2*x2+b 가 디시전 바운드리 함수가 되며, 위의 그래프상에서는 붉은선과 초록선을 나누는 직선이 되고 이것이 바로 디시전 바운드리가 된다.

코스트 함수 (비용함수/Cost function)

자 그러면 가설 함수를 정의 했으니 적정 W와 b값을 찾기 위해서 코스트 함수를 정의해보자.

다시 한번 앞에서 코스트 함수의 개념을 되집어 보면, 코스트 함수의 개념은 가설 함수에 의해서 예측된 값과 트레이닝을 위해서 입력된 값(실제값) 사이의 차이를 계산해주는 함수로, 예측된 값과 입력된값 들의 차이에 대한 평균 값을 구한다.

로지스틱 회귀에서 사용되는 코스트 함수는 다음과 같다.

cost_function =(1/n) * Sum(
                              -y_origin*log(sigmoid(Wx+b)) - (1-y_origin)*log(1-(sigmoid(Wx+b)))

                        )

  • n 의 트레이닝 데이타의 수

  • Y_origin  는 트레이닝에 사용된 x에 대한 입력값


의미를 설명하겠지만, 머리가 아프면 넘어가도 좋다. 그냥 가져다 쓰면 된다.


그러면 어떻게 저런 코스트 함수가 사용되었는지를 알아보자.

선형회귀분석(Linear regression)의 코스트 함수를 다시 한번 살펴보자

코스트 함수는 측정값과 가설에 의해서 예측된 값의 차이의 제곱 평균을 나타내는 함수였다.

선형 회귀 분석에서의 코스트 함수는 다음과 같았다.

Cost =  Sum( (y_data_n - y_origin_n) ^ 2) / n

그리고 이 함수를 그래프로 그려보면 다음과 같이 매끈한 그래프가 나왔다.


그래프의 모양이 매끈한 골짜기 모양이였기 때문에 경사 하강법(Gradient descent)을 사용할 수 있었다.

그러면 로지스틱 회귀 분석에도 기존의 코스트 함수를 이용하여 경사하강법을 적용할 수 있는지 보자

코스트 함수에서 y_data_n에 가설 함수를 대입 시켜 보면

Cost = Sum(  ( sigmoid(Wx + b) - y_origin) ^ 2) /n

Sigmoid 함수를 풀어서표현하면


Cost = Sum(  1 / (1+ math.exp(-(W*x+b) )- y_origin) ^ 2) /n

가 되는데, exp( -(W*x+b) ) (즉 e^(-Wx+b) ) 형태로 표현되기 때문에,e 가 들어간 코스트 함수의 그래프는 다음과 같은 형태를 띄게 된다.

경사 하강법은 그래프를 타고 내려가면서 가장 작은 값을 찾는 알고리즘인데, (물이 골짜기를 따라서 내려가듯이), 이 코스트 함수의 그래프는 작은 골짜기들이 모여서 큰 골짜기 형태를 만든 모양이 된다. 그래서 경사 하강법을 적용할 경우 아래 그림과 같이 코스트 함수의 최소값으로 수렴하지 않고, 중간에 작은 골짜기에서 수렴해 버린다.


그래서 로지스틱 회귀 분석에서는 경사 하강법을 사용하기 위해서 이 코스트 함수를 메끈한 형태로 만들 필요가 있고, 새로운 코스트 함수를 사용한다. “e” 때문에 이런형태의 그래프가 그려지는 건데, e를 상쇄할 수 있는 역치함수는 log 함수가 있다. 그래서 log 함수를 적용하여 메끈한 형태의 코스트 함수를 정의해보자.코스트 함수는 y=1일때와 y=0일때 나눠서 계산해야 한다. 각각의 함수를 보면 다음과 같다.


y=1
cost = (1/n) * sum (-log(가설) )
cost = (1/n) * sum (-log(sigmoid(Wx+b)) )


y=0
cost = (1/n) * sum( -log(1-가설) )
cost = (1/n) * sum( -log(1-sigmoid(Wx+b)) )


코스트 함수는 측정한 값과, 가설에 의해 예측된 값의 차이를 나타내는 함수로, 개별값이 작을 수록 적절한 모델이 된다. 그래서 측정값과 가설값이 같거나 유사할 수 록 코스트 함수의 결과값이 작게 나와야 한다.

먼저 y=1 (측정한 값이)일때를 보자,  

코스트 함수는 cost = (1/n) * sum (-log(sigmoid(Wx+b)) ) 이다.

전체 코스트 평균 말고, 개발 값에 대한 측정값과 예측값에 대한 차이는 이 함수에서 -log(sigmoid(Wx+b)) 이다. 시그모이드 함수 특성상 sigmoid(Wx+b)는 0~1까지의 범위를 가지기 때문에 -log(0~1)을 그래프로 그려보면 다음과 같다.


측정값이 1이기 때문에, 가설함수 (시그모이드 함수  sigmoid(Wx+b) )에 의한 결과가 1이면 예측이 잘된것이고, 1에서 멀어져서 0으로 갈수록 예측된 값과 측정된 값의 차이가 크다고 할 수 있는데, 위의 그래프에서 보면, 가설에 의해 계산한 결과(x축)가 1에 가까울 수록 코스트(y축)은 0으로 수렴하고, 가설에 의해 계산한 결과가 0에 수렴할 수 록, 코스트는 높아지는 것을 볼 수 있다. 즉 y=1에서는 가설이 1에 수렴해야 하기 때문에, 1에 가까워질 수록 코스트가 낮아지는 그래프를 띄게 된다.


y가 0일때도 마찬가지 원리인데 측정값이 0이기 때문에, 가설에 의한 결과값이 0이 되어야 한다. 0에서 멀어질 경우 코스트가 늘어나고 0에서 가까워질 경우 코스트가 줄어드는 형태의 비용 함수를 정의해야 한다.

마찬가지로 코스트 함수의 평균이 아닌 개별값을 보면, -log(1-sigmoid(Wx+b)) 이 되고 sigmoid(Wx+b)는 0..1의 범위이기 때문에, -log(1- (0..1) ) 이 된다. 단 1에서 0..1을 뺄셈을 했기 때문에, 그래프는 -log(1.0, 0.99,0.98,....) 형태로 y=1인 경우와 반대 모양의 그래프가 된다.



자 이제, y=1인 경우와 y=0인 경우에 대한 각각의 코스트 함수를 정의하였다. 이를 코딩으로 옮기려면 y=1인 경우와 0인 경우에 대해서 각각 다른 코스트 함수로 처리하기 위해서 if  문을 사용해야 하지만, 그러면 코딩이 복잡해지기 때문에 이를 하나의 식으로 간단히 할 수 있는 방법을 찾아보자

y=1
cost_y1 = (1/n) * sum (-log(sigmoid(Wx+b)) )


y=0

cost_y0 = (1/n) * sum( -log(1-sigmoid(Wx+b)) )


y=1일때는 y=0인 코스트 함수 cost_y0 가 0이 되고, y=0일때는 y=1의 코스트 함수 cost_y1가 0이 되면 된다.

즉 cost = y*cost_y1 + (1-y)cost_y0 형태가 되면,


y=1 이면, cost = 1*cost_y1 + (1-1)*cost_y0 = cost_y1이 되고

y=0 이면, cost = 0*cost_y1 +  (1-0)*cost_y0 = cost_y0 이 된다.


그래서 y=1인 코스트 함수와 y=0인 코스트 함수를 위의 식 cost_y1과  cost_y=0에 각각 대입해보면


cost = y*[ (1/n) * sum (-log(sigmoid(Wx+b)) )]  + (1-y)[(1/n) * sum( -log(1-sigmoid(Wx+b)) )]


으로 평균 함수인 (1/n)* sum을 앞으로 빼면

cost = (1/n)*Sum(

y*(-log(sigmoid(Wx+b)))

        + (1-y)(-log(1-sigmoid(Wx+b) ))

     )


가 된다.

수식을 따라가면서 이해하면 제일 좋겠지만, 코스트 함수가 저런 원리로 생성이 되는 구나 정도 이해하고, 그냥 가져도 쓰자. (왠지 주입식 교육 같은 느낌이 들기는 하지만)


옵티마이져 (Optimizer)

코스트 함수가 정해졌으면, 코스트를 최소화할 옵티마이져로 어떤 옵티마이져를 사용해야 할지 결정해야 한다.

앞에서 경사 하강법에 적절하도록 코스트 함수를 수정했기 때문에, 경사 하강법(Gradient descent) 알고리즘을 사용한다. 이 경사하강법은 텐서플로우 코딩에서는 간단하게

optimizer = tf.train.GradientDecentOptimizer(learningRate)

train = optimizer.minimize(cost_function)


정의하여 옵티마이져로 사용할 수 있다.

예측 (Prediction)

자 이렇게 해서, 로지스틱 회귀에 대한 학습을 끝내고, W와 b 값을 구했다고 하자. 그렇다면 이 학습된 모델을 가지고, 들어오는 데이타에 대한 분류는 어떻게 할것인가? (예측은 어떻게 할것인가)


가설 함수를 다시 생각해보면 가설함수는 sigmoid(Wx + b) 이다.

그래서 예측이 필요한 값 x가 들어왔을때, 가설함수에 의한 결과값이 y’ 이라고 하면, y’은 0..1 까지의 실수가 되고, y’은 0.75일 경우, x에 대한 결과가 1일 확률은 75%, 0일 확률은 (100-75%인) 25%가 된다.


앞에서 종양의 크기에 대한 양성암 문제를 다시 예를 들어보면, 종양의 크기 x=5이면, sigmoid(Wx+b)에 의해서 계산된 결과가 0.95라고 하면, 이 경우 양성 종양인 확률은 95%, 음성인 확률은 5% 이다.


정리

분류 문제는 회귀 분석과 함께 머신러닝에서 가장 대표적인 문제이고, 로지스틱 회귀 분석은 분류문제에 있어서 기본적이고 대표가 되는 모델이다. 수식이 많이 나와서 다소 복잡할 수 있겠지만 정리를 해보면 다음과 같다.


먼저 가설 함수 (Wx+b) 를 정의하고, 이 가설 함수에 사용된 변수 W와 b에 대한 적정값을 계산하기 위해서 코스트 함수를 정의한후, 이 코스트 함수를 이용한 W와 b를 구하기 위해서 옵티마이저를 정의해서 학습을 통해서 W와 b값을 구한다.


이 과정에서 복잡한 수학적인 설명이 있었지만, 다음과 같은 접근 방법이 좋지 않을까 한다.

  1. 로지스틱 회귀 분석은 결과가 참/거짓인 이항 분석 문제에 사용된다.

  2. 비용 함수가 있다는 것을 알고 로지스틱 회귀 분석용 비용 함수를 가져다 쓴다. 단 비용 함수가 낮을 수 록 학습이 정확하다는 의미 정도는 알아야 학습 도중에 비용 함수의 결과를 보고 학습의 정확도를 파악할 수 있다.

  3. 옵티마이져는 그레디언트 디센트 알고리즘을 사용한다. 그냥 가져다 쓴다.
    단 여기서 학습 속도(Learning Rate, 2장 선형 회귀 분석을 구현하는 문서에서도 설명하였음)의 의미를 파악하고,학습 프로그램을 돌릴때 이 패러미터를 조정하면서 사용한다.

  4. 나온 결과값 (W와 b) 값을 이용하여 예측을 수행한다.


참고





수포자를 위한 딥러닝


#3 - 텐서플로우로 선형회귀 학습을 구현해보자


조대협 (http://bcho.tistory.com)


앞에서 살펴본 선형 회귀(Linear regression) 머신 러닝 모델을 실제 프로그래밍 코드를 만들어서 학습을 시켜보자. 여러가지 언어를 사용할 수 있지만, 이 글에서는 텐서플로우를 기반으로 설명한다.

텐서플로우 개발 환경 셋업

텐서 플로우 개발 환경을 설정하는 방법은 여러가지가 있지만, 구글 클라우드의 데이타랩 (datalab)환경을 사용하기로 한다. 텐서플로우 환경을 설정하려면 파이썬 설치 및 연관된 수학 라이브러리를 설치해야 하는 등 설치가 까다롭기 때문에, 구글 클라우드에서 제공하는 파이썬 노트북 (Jupyter 노트북 : http://jupyter.org/ ) 이 패키징 된 도커 이미지를 사용하기로 한다.

파이썬 노트북은 일종의 위키나 연습장 같은 개념으로 연산등에 필요한 메모를 해가면서 텐서 플로우나 파이썬 코드도 적어넣고 실행도 할 수 있기 때문에 데이타 관련 작업을 하기 매우 편리하다.

또한 도커로 패키징된 데이타랩 환경은 로컬에서나 클라우드 등 아무곳에서나 실행할 수 있기 때문에 편리하고 별도의 과금이 되지 않기 때문에 편리하게 사용할 수 있다.

구글 클라우드 계정 및 프로젝트 생성

GCP 클라우드를 사용하기 위해서는 구글 계정에 가입한다. 기존에 gmail 계정이 있으면 gmail 계정을 사용하면 된다. http://www.google.com/cloud 로 가서, 좌측 상당에 Try it Free 버튼을 눌러서 구글 클라우드에 가입한다.





다음 콘솔에서 상단의 Google Cloud Platform 을 누르면 좌측에 메뉴가 나타나는데, 메뉴 중에서 “결제" 메뉴를 선택한후 결제 계정 추가를 통해서 개인 신용 카드 정보를 등록한다.



개인 신용 카드 정보를 등록해야 모든 서비스를 제한 없이 사용할 수 있다.  단 Trial의 경우 자동으로 한달간 300$의 비용을 사용할 수 있는 크레딧이 자동으로 등록되니, 이 범위를 넘지 않으면 자동으로 결제가 되는 일이 없으니 크게 걱정할 필요는 없다.

프로젝트 생성

계정 생성 및 결제 계정 세팅이 끝났으면 프로젝트를 생성한다.

프로젝트는 VM이나 네트워크 자원, SQL등 클라우드 내의 자원을 묶어서 관리하는 하나의 집합이다. 여러 사람이 하나의 클라우드를 사용할때 이렇게 프로젝트를 별도로 만들어서 별도로 과금을 하거나 각 시스템이나 팀별로 프로젝트를 나눠서 정의하면 관리하기가 용이하다.


화면 우측 상단에서 프로젝트 생성 메뉴를  선택하여 프로젝트를 생성한다.




프로젝트 생성 버튼을 누르면 아래와 같이 프로젝트 명을 입력 받는 창이 나온다. 여기에 프로젝트명을 넣으면 된다.

도커 설치

이 글에서는 로컬 맥북 환경에 데이타랩을 설치하는 방법을 설명한다.

데이타 랩은 앞에서 언급한것과 같이 구글 클라우드 플랫폼 상의 VM에 설치할 수 도 있고, 맥,윈도우 기반의 로컬 데스크탑에도 설치할 수 있다. 각 플랫폼별 설치 가이드는  https://cloud.google.com/datalab/docs/quickstarts/quickstart-local 를 참고하기 바란다. 이 문서에서는 맥 OS를 기반으로 설치하는 방법을 설명한다.


데이타 랩은 컨테이너 솔루션인 도커로 패키징이 되어 있다. 그래서 도커 런타임을 설치해야 한다.

https://www.docker.com/products/docker 에서 도커 런타임을 다운 받아서 설치한다.

도커 런타임을 설치하면 애플리케이션 목록에 다음과 같이 고래 모양의 도커 런타임 아이콘이 나오는 것을 확인할 수 있다.

하나 주의할점이라면 맥에서 예전의 도커 런타임은 오라클의 버추얼 박스를 이용했었으나, 제반 설정등이 복잡하기 때문에, 이미 오라클 버추얼 박스 기반의 도커 런타임을 설치했다면 이 기회에, 도커 런타임을 새로 설치하기를 권장한다.

다음으로 도커 사용을 도와주는 툴로 Kitematic 이라는 툴을 설치한다. (https://kitematic.com/) 이 툴은 도커 컨테이너에 관련한 명령을 내리거나 이미지를 손쉽게 관리할 수 있는 GUI 환경을 제공한다.


구글 클라우드 데이타 랩 설치

Kitematic의 설치가 끝났으면 데이타랩 컨테이너 이미지를 받아서 실행해보자, Kitematic 좌측 하단의 “Dokcer CLI” 버튼을 누르면, 도커 호스트 VM의 쉘 스크립트를 수행할 수 있는 터미널이 구동된다.



터미널에서 다음 명령어를 실행하자


docker run -it -p 8081:8080 -v "${HOME}:/content" \

 -e "PROJECT_ID=terrycho-firebase" \

 gcr.io/cloud-datalab/datalab:local


데이타랩은 8080 포트로 실행이 되고 있는데, 위에서 8081:8080은  도커 컨테이너안에서 8080으로 실행되고 있는 데이타 랩을 외부에서 8081로 접속을 하겠다고 정의하였고, PROJECT_ID는 데이타랩이 접속할 구글 클라우드 프로젝트의 ID를 적어주면 된다. 여기서는 terrycho-firebase를 사용하였다.

명령을 실행하면, 데이타랩 이미지가 다운로드 되고 실행이 될것이다.

실행이 된 다음에는 브라우져에서 http://localhost:8081로 접속하면 다음과 같이 데이타랩이 수행된 것을 볼 수 있다.


학습하기

이제 텐서 플로우 기반의 머신러닝을 위한 개발 환경 설정이 끝났다. 이제 선형 회귀 모델을 학습 시켜보자

테스트 데이타 만들기

학습을 하려면 데이타가 있어야 하는데, 여기서는 랜덤으로 데이타를 생성해내도록 하겠다. 다음은 데이타를 생성하는 텐서 플로우코드이다.

텐서 플로우 자체에 대한 설명과 문법은 나중에 기회가 되면 별도로 설명하도록 하겠다.


import numpy as np

num_points = 200

vectors_set = []

for i in xrange(num_points):

 x = np.random.normal(5,5)+15

 y =  x*1000+ (np.random.normal(0,3))*1000

 vectors_set.append([x,y])

 

x_data = [v[0] for v in vectors_set ]

y_data = [v[1] for v in vectors_set ]


for 루프에서 xrange로 200개의 샘플 데이타를 생성하도록 하였다.

x는 택시 주행거리로,  정규 분포를 따르는 난수를 생성하되 5를 중심으로 표준편차가 5인 데이타를 생성하도록 하였다. 그래프를 양수로 만들기 위해서 +15를 해주었다.

다음으로 y값은 택시비인데, 주행거리(x) * 1000 + 정규 분포를 따르는 난수로 중심값은 0, 그리고 표준편차를 3으로 따르는 난수를 생성한후, 이 값에 1000을 곱하였다.

x_data에는 x 값들을, 그리고 y_data에는 y값들을 배열형태로 저장하였다.


값들이 제대로 나왔는지 그래프를 그려서 확인해보자. 아래는 그래프를 그리는 코드이다.

Pyplot이라는 모듈을 이용하여 plot이라는 함수를 이용하여 그래프를 그렸다. Y축은 0~40000, X축은 0~35까지의 범위를 갖도록 하였다.

import matplotlib.pyplot as plt

plt.plot(x_data,y_data,'ro')

plt.ylim([0,40000])

plt.xlim([0,35])

plt.xlabel('x')

plt.ylabel('y')

plt.legend()

plt.show()

그려진 그래프의 모양은 다음과 같다.


학습 로직 구현 

이제 앞에서 생성한 데이타를 기반으로해서 선형 회귀 학습을 시작해보자. 코드는 다음과 같다.


import tensorflow as tf


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

b = tf.Variable(tf.zeros([1]))

y = W * x_data + b


loss = tf.reduce_mean(tf.square(y-y_data))

optimizer = tf.train.GradientDescentOptimizer(0.0015)

train = optimizer.minimize(loss)


init = tf.initialize_all_variables()

sess = tf.Session()

sess.run(init)


for step in xrange(10):

 sess.run(train)

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

 print(step,sess.run(loss))

 

 plt.plot(x_data,y_data,'ro')

 plt.plot(x_data,sess.run(W)*x_data + sess.run(b))

 plt.xlabel('x')

 plt.ylabel('y')

 plt.legend()

 plt.show()


W의 초기값은 random_uniform으로 생성을 한다. 초기값은 -1.0~1.0 사이의 값으로 생성하도록 하였다.

( radom_uniform 에서 첫번째 인자 [1]은 텐서의 차원을 설명하는데, 1은 1차원으로 배열과 같은 형태가 2는 2차원으로 행렬과 같은 형태, 3은 3차원 행렬 행태가 된다.)

다음 b는 tf.zeros([1])으로 정의했는데, 1차원 텐서로 값이 0이 된다. (zeros)

학습을 하고자 하는 공식 (가설은) y = W * x_data + b 이 된다.


다음으로 코스트 함수와 옵티마이져를 지정하는데, 코스트 함수는 앞 글에서 설명한것과 같이 

가설에 의해 계산된 값 y에서 측정값 y_data를 뺀후에, 이를 제곱하여 평균한 값이다. 코드로 옮기면 다음과 같다.

loss = tf.reduce_mean(tf.square(y-y_data))


코스트 함수에서 최소 값을 구하기 위해서 옵티마이저로 경사하강법 (Gradient descent) 알고리즘을 사용하기 때문에, 옵티마이저로 tf.train.GradientDescentOptimizer(0.0015) 과 같이 지정하였다. 인자로 들어가는 0.0015는 경사 하강법에서 학습 단계별로 움직이는 학습 속도를 정의하는 것으로 러닝 레이트 (Learning rate라고 한다)) 이 내용은 뒤에서 다시 자세하게 설명하겠다.

코스트 함수와 옵티마이져(Gradient descent)가 정의되었으면 트레이닝 모델에 적용한다.

train = optimizer.minimize(loss)

는 경사 하강법(Gradient descent) 을 이용하여 코스트 함수 (loss)가 최소가 되는 값을 찾으라는 이야기이다.


다음 코드에서는 for loop로 학습을 10번을 반복해가면서 학습을 하라는 이야기로,

for step in xrange(10):

 sess.run(train)

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

 print(step,sess.run(loss))


학습 단계별로, W,b값 그리고 loss의 값을 화면으로 출력하도록 하였다.

그리고 학습이 어떻게 되는지 그래프로 표현하기 위해서

 plt.plot(x_data,sess.run(W)*x_data + sess.run(b))

X_data를 가로축으로 하고, W*x_data + b의 값을 그래프로 출력하도록 하였다.


이렇게 해서 학습을 진행하면 다음과 같은 그래프가 순차적으로 출력되는 것을 확인할 수 있다.


그래프가 점점 데이타의 중앙에 수렴하면서 조정되는 것을 확인할 수 있다.

이렇게 해서 맨 마직막에 다음과 같은 결과가 출력된다.



W는 1018, b는 51 그리고 코스트의 값은 10272684.0이 됨을 확인할 수 있다.

이렇게 학습이 끝났고, 이제 거리에 따른 택시비는

(택시비) = 1018 * (거리) + 51로 

이 공식을 가지고 거리에 따른 택시비를 예측할 수 있다.


테스트에 사용한 모든 데이타는 링크를 참고하면 얻을 수 있다.

https://github.com/bwcho75/tensorflowML/blob/master/1.%20Linear%20Regression.ipynb

학습 속도(러닝 레이트 / Learning Rate) 조정하기 

앞의 예제에서 optimizer를  tf.train.GradientDescentOptimizer(0.0015) 에서 0.0015로 학습 속도를 지정하였다. 그렇다면 학습 속도란 무엇인가?


선형 회귀 분석의 알고리즘을 되 짚어보면, 가설에 의한 값과 원래값의 차이를 최소화 하는 값을 구하는 것이 이 알고리즘의 내용이고, 이를 코스트 함수를의 최소값을 구하는 것을 통해서 해결한다.

W의 값을 조정해 가면서 코스트의 값이 최소가 되는 값을 찾는데, 이때 경사 하강법 (Gradient descent)방법을 사용하고 경사의 방향에 따라서 W의 값을 조정하는데, 다음 W의 값이 되는 부분으로 이동하는 폭이 학습 속도 즉 러닝 레이트이다. (아래 그림)


이 예제에서는 학습 속도를 0.0015로 설정하고, 매번 학습 마다 W를 경사 방향으로 0.0015씩 움직이도록 하였다.  그러면 적정 학습 속도를 어떻게 구할까?

오퍼 슈팅 (Over shooting)

먼저 학습 속도가 크면 어떤일이 벌어지는지를 보자

학습 속도를 0.1로 주고 학습을 시키면 어떤 결과가 생길까?

W,b 그리고 cost 함수를 찍어보면 다음과 같은 결과가 나온다.

(0, array([ 86515.3671875], dtype=float32), array([ 4038.51806641], dtype=float32))
(0, 3.1747764e+12) ← cost
(1, array([-7322238.], dtype=float32), array([-341854.6875], dtype=float32))
(1, 2.3281766e+16)
(2, array([  6.27127488e+08], dtype=float32), array([ 29278710.], dtype=float32))
(2, 1.7073398e+20)
(3, array([ -5.37040691e+10], dtype=float32), array([ -2.50728218e+09], dtype=float32))
(3, 1.252057e+24)
(4, array([  4.59895629e+12], dtype=float32), array([  2.14711517e+11], dtype=float32))
(4, 9.1818105e+27)
(5, array([ -3.93832261e+14], dtype=float32), array([ -1.83868557e+13], dtype=float32))
(5, 6.7333667e+31)
(6, array([  3.37258807e+16], dtype=float32), array([  1.57456078e+15], dtype=float32))
(6, 4.9378326e+35)
(7, array([ -2.88812128e+18], dtype=float32), array([ -1.34837741e+17], dtype=float32))
(7, inf)
(8, array([  2.47324691e+20], dtype=float32), array([  1.15468523e+19], dtype=float32))
(8, inf)
(9, array([ -2.11796860e+22], dtype=float32), array([ -9.88816316e+20], dtype=float32))
(9, inf)

Cost 값이 3.1e+12,2.3e+16,1.7e+20 ... 오히려 커지다가 7,8,9에서는 inf(무한대)로 가버리는 것을 볼 수 있다.


그래프를 보면 다음과 같은 형태의 그래프가 나온다.


학습이 진행될 수 록, 코스트 함수의 결과 값이 작아지면서 수렴이 되어야 하는데,  그래프의 각이 서로 반대로 왔다갔다 하면서 발산을 하는 모습을 볼 수 있다.

코스트 함수의 그래프를 보고 생각해보면 그 원인을 알 수 있다.


학습 속도의 값이 크다 보니, 값이 아래 골짜기로 수렴하지 않고 오히려 반대편으로 넘어가면서 점점 오히려 그래프 바깥 방향으로 발산하면서, W값이 발산을 해서 결국은 무한대로 간다. 이를 오버 슈팅 문제라고 한다.

그래서, 학습 과정에서 코스트 값이 수렴하지 않고 점점 커지면서 inf(무한대)로 발산하게 되면, 학습 속도가 지나치게 큰것으로 판단할 수 있다.

스몰 러닝 레이트(Small Learning Rate)

반대로 학습 속도가 매우 작을때는 어떤일이 발생할까?

학습속도를 0.0001로 작게 설정을 해보자.


(0, array([ 86.40672302], dtype=float32), array([ 4.03895712], dtype=float32))
(0, 3.6995174e+08)
(1, array([ 165.43540955], dtype=float32), array([ 7.72794485], dtype=float32))
(1, 3.1007162e+08)
(2, array([ 237.61743164], dtype=float32), array([ 11.09728241], dtype=float32))
(2, 2.6011749e+08)
(3, array([ 303.54595947], dtype=float32), array([ 14.17466259], dtype=float32))
(3, 2.18444e+08)
(4, array([ 363.76275635], dtype=float32), array([ 16.98538017], dtype=float32))
(4, 1.8367851e+08)
(5, array([ 418.76269531], dtype=float32), array([ 19.55253601], dtype=float32))
(5, 1.5467589e+08)
(6, array([ 468.99768066], dtype=float32), array([ 21.89723206], dtype=float32))
(6, 1.304809e+08)
(7, array([ 514.8805542], dtype=float32), array([ 24.03874016], dtype=float32))
(7, 1.1029658e+08)
(8, array([ 556.78839111], dtype=float32), array([ 25.99466515], dtype=float32))
(8, 93458072.0)
(9, array([ 595.06555176], dtype=float32), array([ 27.78108406], dtype=float32))
(9, 79410816.0)

 

코스트값이 점점 작은 값으로 작아지는 것을 볼 수 있지만 계속 감소할 뿐 어떤 값에서 정체 되거나 수렴이 되는 형태가 아니다.

그래프로 표현해보면 아래 그래프와 같이 점점 입력 데이타에 그래프가 가까워 지는 것을 볼 수 있지만, 입력 데이타에 그래프가 겹쳐지기 전에 학습이 중지 됨을 알 수 있다.


이런 문제는 학습속도가 너무 작을 경우 아래 그림 처럼, 코스트 값의 최소 값에 도달하기전에, 학습이 끝나버리는 문제로 Small learning rate 라고 한다.




이 경우에는 학습 횟수를 느리거나 또는 학습 속도를 조절함으로써 해결이 가능하다.


다음글에서는 분류 문제의 대표적인 알고리즘인 로지스틱 회귀 (Logistic Regression)에 대해서 알아보도록 한다.