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


Archive»


딥러닝 - 컨볼루셔널 네트워크를 이용한 이미지 인식의 개념


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


이번 글에서는 딥러닝 중에서 이미지 인식에 많이 사용되는 컨볼루셔널 뉴럴 네트워크 (Convolutional neural network) 이하 CNN에 대해서 알아보도록 하자.


이 글을 읽기에 앞서서 머신러닝에 대한 기본 개념이 없는 경우는 다음 글들을 참고하기 바란다.



CNN은 전통적인 뉴럴 네트워크 앞에 여러 계층의 컨볼루셔널 계층을 붙인 모양이 되는데, 그 이유는 다음과 같다. CNN은 앞의 컨볼루셔널 계층을 통해서 입력 받은 이미지에 대한 특징(Feature)를 추출하게 되고, 이렇게 추출된 특징을 기반으로 기존의 뉴럴 네트워크를 이용하여 분류를 해내게 된다.




컨볼루셔널 레이어  (Convolutional Layer)

컨볼루셔널 레이어는 앞에서 설명 했듯이 입력 데이타로 부터 특징을 추출하는 역할을 한다.

컨볼루셔널 레이어는 특징을 추출하는 기능을 하는 필터(Filter)와, 이 필터의 값을 비선형 값으로 바꾸어 주는 액티베이션 함수(Activiation 함수)로 이루어진다.

그럼 각 부분의 개념과 원리에 대해서 살펴보도록 하자.


<그림 Filter와 Activation 함수로 이루어진 Convolutional 계층>

필터 (Filter)

필터 개념 이해

필터는 그 특징이 데이타에 있는지 없는지를 검출해주는 함수이다. 예를 들어 아래와 같이 곡선을 검출해주는 필터가 있다고 하자.



필터는 구현에서는 위의 그림 좌측 처럼 행렬로 정의가 된다.

입력 받은 이미지 역시 행렬로 변환이 되는데, 아래 그림을 보자.


쥐 그림에서 좌측 상단의 이미지 부분을 잘라내서 필터를 적용하는 결과이다.

잘라낸 이미지와, 필터를 곱하면

과 같이 결과 값이 매우 큰 값이 나온다.

만약에 아래 그림처럼 쥐 그림에서 곡선이 없는 부분에 같은 필터를 적용해보면


결과 값이 0에 수렴하게 나온다.


즉 필터는 입력받은 데이타에서 그 특성을 가지고 있으면 결과 값이 큰값이 나오고, 특성을 가지고 있지 않으면 결과 값이 0에 가까운 값이 나오게 되서 데이타가 그 특성을 가지고 있는지 없는지 여부를 알 수 있게 해준다.

다중 필터의 적용

입력값에는 여러가지 특징이 있기 때문에 하나의 필터가 아닌 여러개의 다중 필터를 같이 적용하게 된다.

다음과 같이 |,+,- 모양을 가지고 있는 데이타가 있다고 하자


각 데이타가 |와 - 의 패턴(특징을) 가지고 있는지를 파악하기 위해서 먼저 | (세로) 필터를 적용해보면 다음과 같은 결과가 나온다.


(맨앞의 상자는 필터이다.) 두번째 상자부터 원본 이미지에 세로선(|) 이 없는 경우 결과 이미지에 출력이 없고, 세로선이 있는 경우에는 결과 이미지에 세로 선이 있는 것을 확인할 수 있다.

마찬가지로 가로선(-) 특징이 있는지 가로 선을 추출하는 필터를 적용해보면 다음과 같은 결과를 얻을 수 있다.



이렇게 각기 다른 특징을 추출하는 필터를 조합하여 네트워크에 적용하면, 원본 데이타가 어떤 형태의 특징을 가지고 있는지 없는지를 판단해 낼 수 있다. 다음은 하나의 입력 데이타에 앞서 적용한 세로와 가로선에 대한 필터를 동시에 적용한 네트워크의 모양이다.



Stride

그러면 이 필터를 어떻게 원본 이미지에 적용할까? 큰 사진 전체에 하나의 큰 필터 하나만을 적용할까?

아래 그림을 보자, 5x5 원본 이미지가 있을때, 3x3인 필터를 좌측 상단에서 부터 왼쪽으로 한칸씩 그 다음 한줄을 내려서 또 왼쪽으로 한칸씩 적용해서 특징을 추출해낸다.

오른쪽 Convolved Feature 행렬이 바로 원본 이미지에 3x3 필터를 적용하여 얻어낸 결과 이다.



이렇게 필터를 적용 하는 간격 (여기서는 우측으로 한칸씩 그리고 아래로 한칸씩 적용하였다.) 값을 Stride라고 하고, 필터를 적용해서 얻어낸 결과를 Feature map 또는 activation map 이라고 한다.

Padding

앞에서 원본 데이타에 필터를 적용한 내용을 보면 필터를 적용한 후의 결과값은 필터 적용전 보다 작아졌다. 5x5 원본 이미지가 3x3의 1 stride 값을 가지고 적용되었을때, 결과 값은 3x3으로 크기가 작아졌다.

그런데, CNN 네트워크는 하나의 필터 레이어가 아니라 여러 단계에 걸쳐서 계속 필터를 연속적으로 적용하여 특징을 추출하는 것을 최적화 해나가는데, 필터 적용 후 결과 값이 작아지게 되면 처음에 비해서 특징이 많이 유실 될 수 가 있다. 필터를 거쳐감에 따라서 특징이 유실되는 것을 기대했다면 문제가 없겠지만, 아직까지 충분히 특징이 추출되기 전에, 결과 값이 작아지면 특징이 유실된다. 이를 방지 하기 위한 방법으로 padding 이라는 기법이 있는데, padding은 결과 값이 작아지는 것을 방지하기 위해서 입력값 주위로 0 값을 넣어서 입력 값의 크기를 인위적으로 키워서, 결과값이 작아지는 것을 방지 하는 기법이다.


다음 그림을 보자, 32x32x3 입력값이 있을때, 5x5x3 필터를 적용 시키면 결과값 (feature map)의 크기는 28x28x3 이 된다. 이렇게 사이즈가 작아지는 것을 원하지 않았다면 padding을 적용하는데, input 계층 주위로 0을 둘러 싸서, 결과 값이 작아지고 (피쳐가 소실 되는것)을 막는다

32x32x3 입력값 주위로 2 두깨로 0을 둘러싸주면 36x36x3 이 되고 5x5x3 필터 적용하더라도, 결과값 은 32x32x3으로 유지된다.


< 그림, 32x32x3 데이타에 폭이 2인 padding을 적용한 예 >


패딩은 결과 값을 작아지는 것을 막아서 특징이 유실되는 것을 막는 것 뿐 아니라, 오버피팅도 방지하게 되는데, 원본 데이타에 0 값을 넣어서 원래의 특징을 희석 시켜 버리고, 이것을 기반으로 머신러닝 모델이 트레이닝 값에만 정확하게 맞아 들어가는 오버피팅 현상을 방지한다.


오버 피팅에 대해서는 별도의 다른 글을 통해서 설명한다.

필터는 어떻게 만드는 것일까?

그렇다면 CNN에서 사용되는 이런 필터는 어떻게 만드는 것일까? CNN의 신박한 기능이 바로 여기에 있는데, 이 필터는 데이타를 넣고 학습을 시키면, 자동으로 학습 데이타에서 학습을 통해서 특징을 인식하고 필터를 만들어 낸다.

Activation function

필터들을 통해서 Feature map이 추출되었으면, 이 Feature map에 Activation function을 적용하게 된다.

Activation function의 개념을 설명하면, 위의 쥐 그림에서 곡선값의 특징이 들어가 있는지 안들어가 있는지의 필터를 통해서 추출한 값이 들어가 있는 예에서는 6000, 안 들어가 있는 예에서는 0 으로 나왔다.

이 값이 정량적인 값으로 나오기 때문에, 그 특징이 “있다 없다”의 비선형 값으로 바꿔 주는 과정이 필요한데, 이 것이 바로 Activation 함수이다.


예전에 로지스틱 회귀 ( http://bcho.tistory.com/1142 )에서 설명하였던 시그모이드(sigmoid) 함수가 이 Activation 함수에 해당한다.

간단하게 짚고 넘어가면, 결과 값을 참/거짓 으로 나타내는 것이 아니라, 참에 가까워면 0.5~1사이에서 1에 가까운 값을 거짓에 가까우면 0~0.5 사이의 값으로 리턴하는 것이다.


<그림. Sigmoid 함수>

뉴럴 네트워크나 CNN (CNN도 뉴럴 네트워크이다.) 이 Activation 함수로 이 sigmoid 함수는 잘 사용하지 않고, 아래 그림과 같은 ReLu 함수를 주요 사용한다.




<그림. ReLu 함수>

이 함수를 이용하는 이유는 뉴럴 네트워크에서 신경망이 깊어질 수 록 학습이 어렵기 때문에, 전체 레이어를 한번 계산한 후, 그 계산 값을 재 활용하여 다시 계산하는 Back propagation이라는 방법을 사용하는데, sigmoid 함수를 activation 함수로 사용할 경우, 레이어가 깊어지면 이 Back propagation이 제대로 작동을 하지 않기 때문에,(값을 뒤에서 앞으로 전달할때 희석이 되는 현상. 이를 Gradient Vanishing 이라고 한다.) ReLu라는 함수를 사용한다.

풀링 (Sub sampling or Pooling)

이렇게 컨볼루셔날 레이어를 거쳐서 추출된 특징들은 필요에 따라서 서브 샘플링 (sub sampling)이라는 과정을 거친다.


컨볼루셔널 계층을 통해서 어느정도 특징이 추출 되었으면, 이 모든 특징을 가지고 판단을 할 필요가 없다.

쉽게 예를 들면, 우리가 고해상도 사진을 보고 물체를 판별할 수 있지만, 작은 사진을 가지고도 그 사진의 내용이 어떤 사진인지 판단할 수 있는 원리이다.


그래서, 추출된 Activation map을 인위로 줄이는 작업을 하는데, 이 작업을 sub sampling 도는 pooling 이라고 한다. Sub sampling은 여러가지 방법이 있는데, max pooling, average pooling, L2-norm pooling 등이 있고, 그중에서 max pooling 이라는 기법이 많이 사용된다.


Max pooling (맥스 풀링)

맥스 풀링은 Activation map을 MxN의 크기로 잘라낸 후, 그 안에서 가장 큰 값을 뽑아내는 방법이다.

아래 그림을 보면 4x4 Activation map에서 2x2 맥스 풀링 필터를 stride를 2로 하여 2칸씩 이동하면서 맥스 풀링을 한 예인데, 좌측 상단에서는 6이 가장 큰 값이기 때문에 6을 뽑아내고, 우측 상단에는 2,4,7,8 중 8 이 가장 크기 때문에 8을 뽑아 내었다.


맥스 풀링은 특징의 값이 큰 값이 다른 특징들을 대표한다는 개념을 기반으로 하고 있다.

(주의 풀링은 액티베이션 함수 마다 매번 적용하는 것이 아니라, 데이타의 크기를 줄이고 싶을때 선택적으로 사용하는 것이다.)


이런 sampling 을 통해서 얻을 수 있는 장점은 다음과 같다.

  • 전체 데이타의 사이즈가 줄어들기 때문에 연산에 들어가는 컴퓨팅 리소스가 적어지고

  • 데이타의 크기를 줄이면서 소실이 발생하기 때문에, 오버피팅을 방지할 수 있다.


컨볼루셔널 레이어

이렇게 컨볼루셔널 필터와 액티베이션 함수 (ReLU) 그리고 풀링 레이어를 반복적으로 조합하여 특징을 추출한다.

아래 그림을 보면 여러개의 컨볼루셔널 필터(CONV)와 액티베이션 함수 (RELU)와 풀링 (POOL) 사용된것을 볼 수 있다.


Fully connected Layer

컨볼루셔널 계층에서 특징이 추출이 되었으면 이 추출된 특징 값을 기존의 뉴럴 네트워크 (인공 신경 지능망)에 넣어서 분류를 한다.

그래서 CNN의 최종 네트워크 모양은 다음과 같이 된다.



<그림. CNN 네트워크의 모양>

Softmax 함수

Fully connected network (일반적인 뉴럴 네트워크)에 대해서는 이미 알고 있겠지만, 위의 그림에서 Softmax 함수가 가장 마지막에 표현되었기 때문에, 다시 한번 짚고 넘어가자.

Softmax도 앞에서 언급한 sigmoid나 ReLu와 같은 액티베이션 함수의 일종이다.


Sigmoid 함수가 이산 분류 (결과값에 따라 참 또는 거짓을 나타내는) 함수라면, Softmax 는 여러개의 분류를 가질 수 있는 함수이다. 아래 그림이 Softmax 함수의 그림이다.




이것이 의미하는 바는 다음과 같다. P3(x)는 특징(feature) x에 대해서 P3일 확률, P1(x)는 특징 x 에 대해서 P1인 확률이다.

Pn 값은 항상 0~1.0의 범위를 가지며,  P1+P2+...+Pn = 1이 된다.


예를 들어서 사람을 넣었을때, 설현일 확률 0.9, 지현인 확율 0.1 식으로 표시가 되는 것이다.

Dropout 계층

위 CNN 그래프에서 특이한 점중 하나는 Fully connected 네트워크와 Softmax 함수 중간에 Dropout layer (드롭아웃) 라는 계층이 있는 것을 볼 수 있다.


드롭 아웃은 오버피팅(over-fit)을 막기 위한 방법으로 뉴럴 네트워크가 학습중일때, 랜덤하게 뉴런을 꺼서 학습을 방해함으로써, 학습이 학습용 데이타에 치우치는 현상을 막아준다.



<그림. 드롭 아웃을 적용한 네트워크 >

그림 출처 : https://leonardoaraujosantos.gitbooks.io/artificial-inteligence/content/dropout_layer.html


일반적으로 CNN에서는 이 드롭아웃 레이어를 Fully connected network 뒤에 놓지만, 상황에 따라서는 max pooling 계층 뒤에 놓기도 한다.


다음은 드롭아웃을 적용하고 학습시킨 모델과 드롭 아웃을 적용하지 않은 모델 사이의 예측 정확도를 비교한 결과 이다.



<그림. 드룹아웃을 적용한 경우와 적용하지 않고 학습한 경우, 에러율의 차이 >

이렇게 복잡한데 어떻게 구현을 하나요?

대략적인 개념은 이해를 했다. 그렇다면 구현을 어떻게 해야 할까? 앞에서 설명을 할때, softmax 나 뉴런에 대한 세부 알고리즘 ReLu 등과 같은 알고리즘에 대한 수학적인 공식을 설명하지 않았다. 그렇다면 이걸 하나하나 공부해야 할까?


아니다. 작년에 구글에서 머신러닝용 프로그래밍 프레임워크로 텐서 플로우라는 것을 발표했다.

이 텐서 플로우는 (http://www.tensorflow.org)는 이런 머신 러닝에 특화된 프레임웍으로, 머신러닝에 필요한 대부분의 함수들을 이미 구현하여 제공한다.

실제로 CNN을 구현한 코드를 보자. 이 코드는 홍콩 과학기술 대학교의 김성훈 교수님의 강의를 김성훈님이란 분이 텐서 플로우 코드로 구현하여 공유해놓은 코드중 CNN 구현 예제이다. https://github.com/FuZer/Study_TensorFlow/blob/master/08%20-%20CNN/CNN.py




첫번째 줄을 보면, tf.nn.conv2d 라는 함수를 사용하였는데, 이 함수는 컨볼루셔널 필터를 적용한 함수 이다. 처음 X는 입력값이며, 두번째 w 값은 필터 값을 각각 행렬로 정의 한다. 그 다음 strides 값을 정의해주고, 마지막으로 padding 인자를 통해서  padding 사이즈를 정한다.

컨볼루셔널 필터를 적용한 후 액티베이션 함수로 tf.nn.relu를 이용하여 ReLu 함수를 적용한 것을 볼 수 있다.

다음으로는 tf.nn.max_pool 함수를 이용하여, max pooling을 적용하고 마지막으로 tf.nn.dropout 함수를 이용하여 dropout을 적용하였다.


전문적인 수학 지식이 없이도, 이미 잘 추상화된 텐서플로우 함수를 이용하면, 기본적인 개념만 가지고도 머신러닝 알고리즘 구현이 가능하다.


텐서 플로우를 공부하는 방법은 여러가지가 있겠지만, 유투브에서 이찬우님이 강의 하고 계신 텐서 플로우 강의를 듣는 것을 추천한다. 한글이고 설명이 매우 쉽다. 그리고 매주 일요일에 생방송을 하는데, 궁금한것도 물어볼 수 있다.

https://www.youtube.com/channel/UCRyIQSBvSybbaNY_JCyg_vA


그리고 텐서플로우 사이트의 튜토리얼도 상당히 잘되어 있는데, https://www.tensorflow.org/versions/r0.12/tutorials/index.html 를 보면 되고 한글화도 잘 진행되고 있다. 한글화된 문서는 https://tensorflowkorea.gitbooks.io/tensorflow-kr/content/ 에서 찾을 수 있다.

구현은 할 수 있겠는데, 그러면 이 모델은 어떻게 만드나요?

그럼 텐서플로우를 이용하여 모델을 구현할 수 있다는 것은 알았는데, 그렇다면 모델은 어떻게 만들까? 정확도를 높이려면 수십 계층의 뉴럴 네트워크를 설계해야 하고, max pooling  함수의 위치와 padding등 여러가지를 고려해야 하는데, 과연 이게 가능할까?


물론 전문적인 지식을 가진 데이타 과학자라면 이런 모델을 직접 설계하고 구현하고 테스트 하는게 맞겠지만, 이런 모델들은 이미 다양한 모델이 만들어져서 공개 되어 있다.


그중에서 CNN 모델은 매년 이미지넷 (http://www.image-net.org/) 이라는데서 추최하는 ILSVRC (Large Scale Visual Recognition Competition) 이라는 대회에서, 주최측이 제시하는 그림을 누가 잘 인식하는지를 겨루는 대회이다.



<그림. 이미지넷 대회에 사용되는 이미지들 일부>


이 대회에서는 천만장의 이미지를 학습하여, 15만장의 이미지를 인식하는 정답률을 겨루게 된다. 매년 알고리즘이 향상되는데, 딥러닝이 주목 받은 계기가된 AlexNet은 12년도 우승으로, 8개의 계층으로 16.4%의 에러율을 내었고, 14년에는 19개 계층을 가진 VGG 알고리즘이 7.3%의 오차율을 기록하였고, 14년에는 구글넷이 22개의 레이어로 6.7%의 오차율을 기록하였다. 그리고 최근에는 마이크로소프트의 152개의 레이어로 ResNet이 3.57%의 오차율을 기록하였다. (참고로 인간의 평균 오류율은 5% 내외이다.)

현재는 ResNet을 가장 많이 참고해서 사용하고 있고, 쉽게 사용하려면 VGG 모델을 사용하고 있다.




결론

머신러닝과 딥러닝에 대해서 공부를 하면서 이게 더이상 수학자나 과학자만의 영역이 아니라 개발자도 들어갈 수 있는 영역이라는 것을 알 수 있었고, 많은 딥러닝과 머신러닝 강의가 복잡한 수학 공식으로 설명이 되지만, 이건 아무래도 설명하는 사람이 수학쪽에 배경을 두고 있기 때문 일것이고, 요즘은 텐서플로우 프레임웍을 사용하면 복잡한 수학적인 지식이 없이 기본적인 머신러닝에 대한 이해만을 가지고도 머신러닝 알고리즘을 개발 및 서비스에 적용이 가능한 시대가 되었다고 본다.


그림 출처 및 참고 문서



저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

머신러닝의 과학습 / 오버피팅의 개념


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


머신 러닝을 공부하다보면 자주 나오는 용어 중에 하나가 오버피팅 (Overfitting)이다.

과학습이라고도 하는데, 그렇다면 오버 피팅은 무엇일까?


머신 러닝을 보면 결과적으로 입력 받은 데이타를 놓고, 데이타를 분류 (Classification) 하거나 또는 데이타에 인접한 그래프를 그리는 (Regression) , “선을 그리는 작업이다.”

그러면 선을 얼마나 잘 그리느냐가 머신 러닝 모델의 정확도와 연관이 되는데, 다음과 같이 붉은 선의 샘플 데이타를 받아서, 파란선을 만들어내는 모델을 만들었다면 잘 만들어진 모델이다. (기대하는)


언더 피팅


만약에 학습 데이타가 모자라거나 학습이 제대로 되지 않아서, 트레이닝 데이타에 가깝게 가지 못한 경우에는 다음과 같이 그래프가 트레이닝 데이타에서 많이 떨어진것을 볼 수 있는데, 이를 언더 피팅 (under fitting)이라고 한다.



오버 피팅

오버 피팅은 반대의 경우로, 다음 그림과 같이 트레이닝 데이타에 그래프가 너무 정확히 맞아 들어갈때 발생한다.


샘플 데이타에 너무 정확하게 학습이 되었기 때문에, 샘플데이타를 가지고 판단을 하면 100%에 가까운 정확도를 보이지만 다른 데이타를 넣게 되면, 정확도가 급격하게 떨어지는 문제이ㅏㄷ.

오버피팅의 해결

이런 오버피팅 문제를 해결하는 방법으로는 여러가지가 있는데 대표적인 방법으로는

  • 충분히 많은 학습 데이타를 넣거나

  • 피쳐의 수를 줄이거나

  • Regularization (정규화)를 이용하는 방법이 있다.



그림 출처 : 출처 : https://kousikk.wordpress.com/2014/11/20/problem-of-overfitting-in-machine-learning/




저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

딥러닝의 개념과 유례

빅데이타/머신러닝 | 2016.11.27 20:37 | Posted by 조대협


딥러닝의 역사와 기본 개념

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

인경 신경망 알고리즘의 기본 개념


알파고나 머신러닝에서 많이 언급되는 알고리즘은 단연 딥러닝이다.

이 딥러닝은 머신러닝의 하나의 종류로 인공 신경망 알고리즘의 새로운 이름이다.


인공 신경망은 사람의 두뇌가 여러개의 뉴론으로 연결되서 복잡한 연산을 수행한다는데서 영감을 받아서, 머신러닝의 연산을 여러개의 간단한 노드를 뉴론 처럼 상호 연결해서 복잡한 연산을 하겠다는 아이디어이다.


<출처 : http://webspace.ship.edu/cgboer/theneuron.html >


이 뉴런의 구조를 조금 더 단순하게 표현해보면 다음과 같은 모양이 된다.


뉴런은 돌기를 통해서 여러 신경 자극 (예를 들어 피부에서 촉각)을 입력 받고, 이를 세포체가 인지하여 신호로 변환해준다. 즉 신경 자극을 입력 받아서 신호라는 결과로 변환해주는 과정을 거치는데,


이를 컴퓨터로 형상화 해보면 다음과 같은 형태가 된다.


뉴런의 돌기처럼 외부에서 입력값 X1,X2,X3를 읽어드리고, 이 입력값들은 돌기를 거치면서 인식되어 각각 W1*X1, W2*X2, W3*X3로 변환이 되어 세포체에 도착하여 여러 돌기에서 들어온 값은 (W1*X1+W2*X2+W3*X3)+b 값으로 취합된다.

이렇게 취합된 값은 세포체내에서 인지를 위해서 어떤 함수 f(x)를 거치게 되고, 이 값이 일정 값을 넘게 되면, Y에 1이라는 신호를 주고, 일정값을 넘지 않으면 0이라는 값을 준다.


즉 뉴런을 본떠서 입력값 X1...n에 대해서, 출력값  Y가 0 또는 1이 되는 알고리즘을 만든것이다.

Perceptron


이를 수식을 사용하여 한번 더 단순화를 시켜보면

X를 행렬이라고 하고,  X = [X1,X2,X3] 라고 하자.

그리고 역시 이에 대응되는 행렬 W를 정의하고 W=[W1,W2,W3] 라고 하면


<뉴런을 본떠서 만든 Perceptron>


입력 X를 받아서 W를 곱한 후에, 함수 f(x)를 거쳐서 0 또는 1의 결과를 내는 Y를 낸다.

즉 입력 X를 받아서 참(1)인지 거짓(0) 인지를 판별해주는 계산 유닛을 Perceptron이라고 한다.


이 Perceptron은 결국 W*X+b인 선을 그려서 이 선을 기준으로 1 또는 0을 판단하는 알고리즘이다.

예를 들어서 동물의 크기 (X1)와 동물의 복종도 (X2)라는 값을 가지고, 개인지 고양이인지를 구별하는 Perceptron이 있을때,

W*X+b로 그래프를 그려보면 (X=[X1,X2], W=[W1,W2] 다음과 같은 직선이 되고, 이 직선 윗부분이면 개, 아랫 부분이면 고양이 식으로 분류가 가능하다.



이 Perceptron은 입력에 따라서 Y를 1,0으로 분류해주는 알고리즘으로 앞에서 설명한 로지스틱 회귀 알고리즘을 사용할 수 있는데, 이때 로지스틱 회귀에서 사용한 함수 f(x)는 sigmoid 함수를 사용하였기 때문에, 여기서는 f(x)를 이 sigmoid 함수를 사용했다. 이 함수 f(x)를 Activation function이라고 한다. 이 Activation function은 중요하니 반드시 기억해놓기 바란다.


( 참고. 손쉬운 이해를 위해서 로지스틱 회귀와 유사하게 sigmoid 함수를 사용했지만,  sigmoid 함수이외에 다양한 함수를 Activation 함수로 사용할 수 있으며, 요즘은 sigmoid 함수의 정확도가 다른 Activation function에 비해 떨어지기 때문에, ReLu와 같은 다른 Activation function을 사용한다. 이 Activation function)에 대해서는 나중에 설명하겠다.)


Perceptron의 XOR 문제

그런데 이 Perceptron는 결정적인 문제를 가지고 있는데, 직선을 그려서 AND,OR 문제를 해결할 수 는 있지만,  XOR 문제를 풀어낼 수 가 없다는 것이다.


다음과 같은 Perceptron이 있을때


다음 그림 처럼 AND나 OR 문제는 직선을 그려서 해결이 가능하다.


그러나 다음과 같은 XOR 문제는 WX+b의 그래프로 해결이 가능할까?



<그림 XOR 문제>



하나의 선을 긋는 Perceptron으로는 이 문제의 해결이 불가능하다.


MLP (Multi Layer Perceptron) 다중 계층 퍼셉트론의 등장

이렇게 단일 Perceptron으로 XOR 문제를 풀 수 없음을 증명되었는데, 1969년에 Marvin Minsky 교수가, 이 문제를 해결 하는 방법으로 Perceptron을 다중으로 겹치면 이 문제를 해결할 수 있음을 증명하였다.



<그림 Multi Layer Perceptron의 개념도>


그런데, 이 MLP 역시 다른 문제를 가지고 있는데, MLP에서 학습을 통해서 구하고자 하는 것은 최적의 W와 b의 값을 찾아내는 것인데, 레이어가 복잡해질 수 록, 연산이 복잡해져서 현실적으로 이 W와 b의 값을 구하는 것이 불가능 하다는 것을 Marvin Minsky 교수가 증명 하였다.

Back Propagation 을 이용한 MLP 문제 해결

이런 문제를 해결 하기 위해서 Back propagation이라는 알고리즘이 도입되었는데, 기본 개념은

뉴럴 네트워크를 순방향으로 한번 연산을 한 다음에, 그 결과 값을 가지고, 뉴럴 네트워크를 역방향 (backward)로 계산하면서 값을 구한다는 개념이다.


Backpropagation의 개념에 대해서는 다음글에서 자세하게 설명하도록 한다.


Back Propagation 문제와 ReLu를 이용한 해결

그러나 이 Back Propagation 역시 문제를 가지고 있었는데, 뉴럴 네트워크가 깊어질 수 록 Backpropagation이 제대로 안된다.

즉 순방향(foward)한 결과를 역방향(backward)로 반영하면서 계산을 해야 하는데, 레이어가 깊을 수 록 뒤에 있는 값이 앞으로 전달이 되지 않는 문제 이다. 이를 Vanishing Gradient 문제라고 하는데, 그림으로 개념을 표현해보면 다음과 같다.

뒤에서 계산한 값이 앞의 레이어로 전달이 잘 되지 않는 것을 표현하기 위해서 흐리게 네트워크를 표현하였다.



이는 ReLu라는 activation function (앞에서는 sigmoid 함수를 사용했다.)으로 해결이 되었다.


뉴럴 네트워크의 초기값 문제

이 문제를 캐나다 CIFAR 연구소의 Hinton 교수님이 “뉴럴네트워크는 학습을 할때 초기값을 잘 주면 학습이 가능하다" 라는 것을 증명하면서 깊은 레이어를 가진 뉴럴 네트워크의 사용이 가능하게 된다.

이때 소개된 알고리즘이 초기값을 계산할 수 있는 RBM (Restricted Boltzmann Machine)이라는 알고리즘으로 이 알고리즘을 적용한 뉴럴 네트워크는 특히 머신러닝 알고리즘을 테스트 하는 ImageNet에서 CNN (Convolutional Neural Network)가 독보적인 성능을 내면서 뉴럴 네트워크가 주목 받기 시작하였다.


딥러닝

딥러닝이라는 어원은 새로운 알고리즘이나 개념을 이야기 하는 것이 아니고, 뉴럴 네트워크가 새롭게 주목을 받기 시작하면서 Hinton 교수님 등이 뉴럴네트워크에 대한 리브랜딩의 의미로 뉴럴 네트워크를 새로운 이름 “딥러닝"으로 부르기 시작하면서 시작 되었다.


추가

뉴럴네트워크와 딥러닝의 대략적인 개념과 역사에 대해서 알아보았다.

이 글에서는 뉴럴 네트워크에 대한 대략적인 개념만을 설명하고 있는데, 주로 언급되는 단어를 중심으로 기억하기를 바란다.

  • Perceptron

  • MLP (Multi Layer Perceptron)

  • Back propagation

  • ReLu

  • RBM


이외에도, Drop Out, Mini Batch, Ensemble 과 같은 개념이 있는데, 이 개념은 추후에 다시 설명하고, 딥러닝에서 이미지 인식에 많이 사용되는 CNN (Convolutional Neural Network)을 나중에 소개하도록 하겠다.



저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

Docker Kubernetes의 UI

클라우드 컴퓨팅 & NoSQL/google cloud | 2016.11.26 23:38 | Posted by 조대협

Docker Kubernetes UI


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


오늘 도커 밋업에서 Kubernetes 발표가 있어서, 발표전에 데모를 준비하다 보니, 구글 클라우드의 Kubernetes 서비스인 GKE (Google Container Engine)에서 Kubernetes UI를 지원하는 것을 확인했다.


Google Container Service (GKE)


GKE는 구글 클라우드의 도커 클라우드 서비스이다. 도커 컨테이너를 관리해주는 서비스로는 Apache mesos, Docker Swarm 그리고 구글의 Kuberenetes 가 있는데, GKE는 이 Kuberentes 기반의 클라우드 컨테이너 서비스이다.


대부분의 이런 컨테이너 관리 서비스는 아직 개발중으로 운영에 적용하기에는 많은 부가적인 기능이 필요한데, 사용자 계정 인증이나, 로깅등이 필요하기 때문에, 운영환경에 적용하기는 아직 쉽지 않은데, GKE 서비스는 운영 환경에서 도커 서비스를 할 수 있도록 충분한 완성도를 제공한다. 이미 Pocketmon go 서비스도 이미 GKE를 사용하고 있다.


Kubernetes UI


예전에 Kubernetes를 테스트할 때 단점은 아직 모든 관리와 모니터링을 대부분 CLI로 해야 하기 때문에 사용성이 떨어지는데, 이번 GKE에서는 웹 UI 콘솔을 제공한다.


구글 GKE 콘솔에서 Kuberentes 클러스터를 선택하며 우측에 Connect 버튼이 나오는데, 


이 버튼을 누르면, Kubernetes 웹 UI를 띄울 수 있는 명령어가 출력된다.

아래와 같이 나온 명령어를 커맨드 창에서 실행시키고 htt://localhost:8001/ui 에 접속하면 Kubernetes 웹 콘솔을 볼 수 있다. 


Kubernetes 의 웹콘솔은 다음과 같은 모양이다.



Kubernetes의 주요 컴포넌트인 Pods, Service, Replication Controller , Nodes 등의 상태 모니터링은 물론이고, 배포 역시 이 웹 콘솔에서 가능하다.


예를 들어  gcr.io/terrycho-sandbox/hello-node:v1 컨테이너 이미지를 가지고, Pod 를 생성하고, Service를 정의해서 배포를 하려면 다음과 같은 명령을 이용해야 한다.


1. hello-node 라는 pod를 생성한다. 

% kubectl run hello-node --image=gcr.io/terrycho-sandbox/hello-node:v1 --port=8080


2. 생성된 pod를 service를 정의해서 expose 한다.

kubectl expose deployment hello-node --type="LoadBalancer"


이런 설정들을 CLI로 하면 익숙해지면 쉽지만 익숙해지기전까지는 번거로운데,

아래 그림과 같이, 간단하게 웹 UI에서 Pod와 서비스들을 한번에 정의할 수 있다.





배포가 완료된 후에는 각 Pod의 상황이나, Pod를 호스팅하고 있는 Nodes 들의 상황등 다양한 정보를 매우 쉽게 모니터링이 가능하다. (cf. CLI를 이용할 경우 CLI 명령어를 잘 알아야 가능하다.)


GKE에 대한 튜토리얼은 https://cloud.google.com/container-engine/docs/tutorials  에 있는데,

추천하는 튜토리얼은

가장 간단한 튜토리얼 node.js 웹앱을 배포하는  http://kubernetes.io/docs/hellonode/

와 WordPress와 MySQL을 배포하는 https://cloud.google.com/container-engine/docs/tutorials/persistent-disk/

을 추천한다.


도커가 아직까지 운영 환경에 사례가 국내에 많지 않고, GKE도 GUI 가 없어서 그다지 지켜보지 않았는데, 다시 파볼만한 정도의 완성도가 된듯.


참고로 테스트를 해보니 VM을 3개 만들어놓고 컨테이너를 7개인가 배포했는데, VM은 3개로 유지된다. 즉 하나의 VM에 여러개의 컨테이너가 배포되는 형태인데, 작은 서비스들이 많은 경우에는 자원 사용 효율이 좋을듯. 이런 관점에서 봤는때는 VM 기반의 서비스보다 컨테이너 서비스를 쓰는 장점이 확실히 보이는듯 하다




저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

파이어베이스를 이용한 유니티 게임 로그 분석


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

모바일 로그 분석

일반적으로 모바일 로그 분석은 클라우드 기반의 무료 솔루션을 이용하다가 자체 구축으로 가는 경우가 많다.

클라우드 기반의 무료 로그 분석 솔루션으로는 구글 애널러틱스, 야후의 플러리, 트위터의 패브릭 그리고 구글의 파이어베이스 등이 있다.

이런 무료 로그 분석 솔루션들을 사용이 매우 간편하고, 핵심 지표를 쉽게 뽑아 줄 수 있으며, 별도의 운영이 필요 없다는 장점을 가지고 있다.

그러나 이런 클라우드 기반의 무료 솔루션의 경우에는 요약된 정보들만 볼 수 있고 또한 내가 원하는 지표를 마음대로 지정을 할 수 없기 때문에, 어느정도 서비스가 성장하고 팀의 여력이 되면 별도의 로그 수집 및 분석 솔루션을 만드는 것이 일반적이다.

오픈 소스 기반의 분석 솔루션

오픈 소스를 조합해서 모바일 로그 수집 시스템을 만들면 대략 다음과 같은 모양이 된다.


API 서버에서 로그를 수집해서 카프카등의 큐를 통해서 로그를 모으고, 실시간은 스파크 스트리밍, 배치는 하둡이나 스파크 스트리밍 프레임웍을 이용합니다. 대쉬 보드는 만드는 곳도 있지만, 주피터 노트북이나 제플린 노트북과 같은 노트북을 이용한다.

요즘은 데이타 저장 및 분석에 ELK (Elastic Search + Logstash + Kibana)와 같은 솔루션도 많이 사용하고 있다.


그런데 이런 오픈 소스 솔루션 기반으로 로그 분석 시스템을 개발하면 몇가지 문제가 발생한다.

  • 개발에 드는 노력
    이런 오픈소스 스택으로 시스템을 개발하려면, 이 프레임웍에 대해서 잘 아는 전문가가 필요합다. 일반적인 스타트업에서는 구하기도 힘들고, 기업이 어느정도 규모가 되더라도 빅데이타 관련 기술을 다룰 줄 아는 엔지니어는 여전히 귀한 엔지니어이고, 이런 엔지니어들이 있다하더라도, 시스템 설계및 구현에는 수개월의 기간이 소요 되게 된다.

  • 시스템 구매와 운영
    다음 문제는 모바일 데이타는 양이 많기 때문에, 위에서 언급한 빅데이타 관련 오픈 소스를 사용하게 되는데, 이러한 시스템은 하드웨어 자원이 수십에서 수백대가 필요하거니와, 이를 설치하고 운영하는 것 역시 쉽지 않다.
    로그를 수집하고 분석하는 로직을 만들어야 하는 엔지니어들이 정작 데이타 분석 보다는 시스템 운영과 유지보수에 많은 시간을 낭비해야 한다는 문제가 발생한다.
    규모가 작은 스타트업이나 엔지니어링 능력이 되지 않는 기업들은 이런 빅데이타 분석은 엄두도 내지 못하는 상황이 되고, 디테일한 데이타 분석을 하지 못하게 되니 자연히 경쟁력이 떨어지게 될 수 있다.

  • 연산 시간
    그리고 수집 수백대의 서버를 가지고 있다하더라도, 데이타 연산 시간은 수십분에서 수시간이 소요된다. 특히 데이타 분석 서버들이 분석을 하고 있을때는 다른 분석을 하고 싶은 사람들은 연산이 끝날때 까지 기다려야 하고, 수시간을 들여서 연산한 결과라도 연산이 잘못되었으면 다시 로직을 수정해서 수시간 동안 다시 연산을 해야 한다.
    비지니스 조직 입장에서는 지표 분석 결과를 얻는데, 수시간이 걸리니 의사 결정의 민첩성이 떨어지게 된다.

클라우드 기반의 분석 솔루션

근래에 이런 빅데이타 분석이 클라우드 컴퓨팅 기술과 만나면서 한번의 큰 변화를 겪게 되는데, 흔히들 빅데이타의 민주화라고 이야기 한다.  빅데이타 분석이 클라우드 컴퓨팅과 만나면서 겪은 큰 변화는 다음과 같다 .

클라우드 스케일의 연산

먼저 스케일이 달라집니다. 클라우드의 대용량 자원을 이용하여, 연산을 하기 때문에, 훨씬 더 빠른 연산을 저 비용에 할 수 있다.

예를 들어 구글의 빅쿼리의 경우에는 1000억개의 문자열(ROW)를  Regular expression을 이용하여 스트링 Like 검색을 하고 이를 group by 로 그룹핑하여 연산 하는 쿼리를 수행할때


“8600개의 CPU, 3600개의 디스크, 350GB의 네트워크 대역폭"


이 사용이 되고, 쿼리 수행 시간은 약 20~30초, 클라우드 사용 비용은 20$ (2만원) 정도가 소요 된다.

오픈 소스 기반으로 왠만한 규모로는 동시에 단일 연산으로 이렇게 수천개의 CPU를 같이 돌릴 수 있는 인프라를 사내에 가지고 있기도 힘들뿐 더러, 이만한 리소스를 20$라는 저렴한 비용에 사용하기란 거의 불가능에 가깝다.

이런 빠른 연산으로 인해서, 현업에서는 연산 결과를 기다리지 않고 바로바로 볼 수 있고, 비용 역시 저렴하기 때문에, 어느정도 자금력과 개발력이 있는 기업이 아니더라도 고성능의 빅데이타 분석 시스템 구현이 가능하게 된다.

NoOPS

다음 장점으로는 운영이 필요 없다는 것인데, 앞에서도 설명했듯이, 오픈 소스를 이용해서 빅데이타 분석 시스템을 직접 구축한 경우에는 시스템 인스톨과, 구성, 그리고 운영에 많은 시간이 소요 되는데, 클라우드 기반의 빅데이타 솔루션은 설정과 운영을 클라우드 서비스 제공자가 대행을 하기 때문에, 엔지니어링 팀은 별도의 설정과 유지보수 없이 본연의 역할인 데이타 분석에만 집중할 수 있게 된다. (아마 직접 하둡이나 스파크 클러스터를 운영해본 사람이라면 이 의미를 잘 이해하리라 본다.)


이렇게 클라우드가 빅데이타 영역에 도입되면서 이제는 빅데이타 분석이 뛰어난 엔지니어링 지식과 자금력이 없더라도 단시간내에 저비용으로 효율적인 데이타 분석이 가능하게 되었기 때문에, 이를 빅데이타의 민주화라고 부른다.

파이어베이스 애널러틱스

파이어베이스는 얼마전에 구글이 인수해서 클라우드 서비스 형태로 제공하고 있는 통합 모바일 개발 프레임웍이다. 웹은 지원하지 않고 모바일만 지원하는 형태의 프레임웍이며, 리얼타임 데이타 베이스, 광고 네트워크 통합, 푸쉬 서비스, 사용자 개인 인증 서비스등 여러가지 기능을 가지고 있는데, 그 중에서, 파이어베이스 애널러틱스는 모바일 빅데이타 분석에 최적화된 시스템이다.

빅쿼리와 파이어베이스의 조합

게임 체인저

파이어베이스는 모바일 데이타 분석에서 거의 게임 체인저라고 할만한 기술인데, 기존의 클라우드 기반의 모바일 데이타 분석 솔루션은 가장 큰 문제점이, 개발자가 정의한 로그 이벤트 (커스텀 로그)를 수집할 수 없다는 문제와  그리고 수집한 원본 데이타를 볼 수 없기 때문에, 원하는 지표를 마음대로 수집하고 분석하는 것이 불가능했다.

그런데 파이어베이스 애널러틱스는 이 두가지 기능을 지원하기 시작하였다.

커스텀 이벤트 정의를 통해서 개발자가 원하는 로그를 손쉽게 정의해서 수집이 가능하고, 또한 수집한 로그는 모두 구글의 빅데이타 저장 및 분석 플랫폼인 빅쿼리에 저장되고 바로 분석이 가능하다.

빅쿼리

파이어베이스 애널러틱스의 데이타는 빅쿼리에 저장이 되는데, 앞에서 예를 든것과 같이, 빅쿼리는 한번 연산에 수천개의 CPU와 디스크를 사용하여, 하둡이나 스파크에서 수시간이 걸리는 연산을 불과 수십초만에 처리가 가능하다.

빅쿼리의 또 다른 장점중의 하나는 이런 연산 속도 뿐만 아니라 RDBMS와는 다르게 JSON과 같이 트리형 (계층 구조를 가지는) 데이타형을 그대로 저장하고 쿼리가 가능하다는 것이다.


빅쿼리에 대한 자세한 설명은

를 참고하기 바란다.

파이어베이스 기반의 로그 분석

파이어베이스 애널러틱스는 뒤로는 빅쿼리 연동을 통해서 모든 원본 데이타의 수집과 분석을 지원하고 앞으로는 파이어베이스 에이전트를 모바일 디바이스에 탑재 하는 방식으로 최소한의 코드 개발로 모바일 앱으로 부터 모든 데이타를 수집할 수 있다.  파이어베이스 애널러틱스는 안드로이드와 iOS 플랫폼을 지원한다.

게임 프레임웍 지원

반가운 소식중의 하나는 파이어베이스 애널러틱스가 이제 유니티3D나, 언리얼(C++) 과 같은 게임 엔진을 지원한다. 현재 두 플랫폼에 대한 지원은 베타로 공개되어 있다.

코드 예제

그러면 파이어베이스 애널러틱스를 이용해서 로그를 수집하는 코드는 어떻게 삽입을 할까? 안드로이드와 유니티 3D의 예를 들어서 보자.

안드로이드 예제 코드

상세한 코드는 http://bcho.tistory.com/1131 를 참고하기 바란다.

코드 부분을 발췌해서 보면 다음과 같다.


//생략

:


import com.google.firebase.analytics.FirebaseAnalytics;


public class MainActivity extends AppCompatActivity {


 // add firebase analytics object

 private FirebaseAnalytics mFirebaseAnalytics;


   public void onSendEvent(View view){

     // 중간 생략

     Bundle bundle = new Bundle();

     bundle.putString(FirebaseAnalytics.Param.ITEM_ID, contentsId);

     bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, contentsName);

     bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, contentsCategory);

     mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);


 }

}



기본적으로 gradle 빌드 스크립트에 파이어베이스 애널러틱스 모듈을 import 하고, FirebaseAnalytics 객체만 선언해주면 기본적인 사용자 로그 (앱 실행, 종료등), 일일 방문자, 동시 접속자, 접속 디바이스 종류, 사용자 연령과 성별들을 모두 수집해준다.

빌드 스크립트 수정 및 소스코드에 한줄의 코드만 추가해주면 된다.

다음으로, 각각의 이벤트를 추가하고자 한다면, 위와 같이 Bundle 객체를 정의해서, 넘기고자 하는 인자를 정의해주고 logEvent라는 메서드를 호출해주면 파이어베이스로 로그가 전달된다.

유니티 3D 예제 코드

유니티 3D에서 파이어베이스에 로그를 남기는 것도 다르지 않다.

다음 코드를 보자


       Firebase.Analytics.Parameter[] param = {

           new Firebase.Analytics.Parameter("sessionid", sessionid),

           new Firebase.Analytics.Parameter("score", (string)ApplicationModel.score.ToString())

       };

       Firebase.Analytics.FirebaseAnalytics.LogEvent(ApplicationModel.EVENT.END_SESSION, param);


Parameter라는 배열로, 파이어베이스에 남길 로그의 인자들을 정의한후에, LogEvent 메서드를 이용하여 이벤트 명과, 앞에서 정의된 인자들 (Parameter)를 남겨주면 로그는 자동으로 파이어베이스로 전달된다.


파이어베이스 애널러틱스를 이용한 모바일 데이타 분석

그러면 파이어베이스를 이용하여 모바일 로그 분석을 어떻게 할 수 있는지 알아보자. 마침 유니티 3D가 얼마전 부터 베타로 지원이 되기 때문에, 간단한 게임을 이용한 로그 수집을 설명한다.

샘플 게임 설명

샘플에 사용한 게임은 간단한 RPG 형태의 게임으로 다음과 같이 구성된다.



시작 화면

시작화면에서는 로그 분석을 위해서, 사용자의 나이와 성별을 입력 받는다.


게임 화면

다음 게임이 시작되면, 화면을 터치하여 토끼 캐릭터를 이동 시키고, 돼지를 클릭하면 돼지를 공격한다.

돼지를 공격할때 마다 데미지는 돼지의 종류에 따라 일정 값 범위내에서 랜덤으로 판정되고, 생명 값이 남아있지 않으면 돼지가 죽게 된다.

맵내에 돼지는 7개가 유지되도록 되어 있으며, 돼지가 줄면, 돼지는 하늘에서 부터 떨어지게 되어 있다.

게임은 120초 동안 진행되며, 120초가 지나면 자동으로 종료된다.

종료 화면

게임이 종료되면 점수를 표시한다.

데이타  분석 지표 디자인

그러면 이 게임으로 어떻게 데이타를 분석할것인지에 대해서 고민해보자.

일일 접속 사용자나 사용자에 대한 사용 시간,횟수등은 파이어베이스 애널러틱스에서 기본적으로 수집이 되기 때문에, 조금 더 의미 있는 데이타를 수집해보도록 한다.

캐릭터 이동 히트맵

이 예제에서 다소 중점을 둔 부분중의 하나는 캐릭터 이동 히트맵이다.

게임에서 난이도 조정등에 사용할 수 있는 정보중의 하나가 NPC 캐릭터의 이동 동선과, 플레이어 캐릭터의 이동 동선이다. 주로 플레이어가 죽는 위치를 데드존 (Dead zone)이라고 하면, 이 데드존 위치를 찾아낼 수 있고, 이 데드존에서 플레이어와 NPC의 타입,레벨 등을 조사하여 난이도를 조정한다거나, 또는 AI(인공지능) 플레이어 캐릭터의 경우에는 이동 동선을 추적함으로써 맵 내에서 AI가 원하는 데로 잘 움직이는지를 추적해볼 수 있다.

아래는 데드존을 기반으로 캐릭터와 NPC의 레벨을 분석해놓은 예제이다.


<그림. 게임맵상에서 데드존의 플레이어와 NPC 캐릭터간의 레벨 분석 >


아래는 흥미로운 분석중의 한예인데, 게임맵에서, 각 위치별로 자주 발생하는 채팅 메세지를 표시한 내용이다.




<그림. 게임맵상에서 자주 사용되는 채팅 메세지 분석>


그림 출처 : http://www.cs.cornell.edu/courses/cs4152/2013sp/sessions/15-GameAnalytics.pdf


이런 시스템 역시 쉽게 개발이 가능한데, 파이어베이스 애널러틱스를 이용하여 채팅 로그를 수집한 후, 자연어 분석 API를 이용하면, 명사와 형용사등을 추출하여 자주 오가는 말들을 통계를 낼 수 있다.

http://bcho.tistory.com/1136 는 구글의 자연어 분석 API를 이용하여 트위터의 내용을 실시간으로 분석한 내용이다.

나이별  점수 분포

다음으로 일반적인 분석 시스템에서 수집되지 않는 커스텀 로그 분석 시나리오중 사용자 나이별 점수대를 분석해본다.

게임실행에서 종료까지 실행한 사용자

마지막으로 유용하게 사용되는 퍼널 분석의 예로 게임을 시작해서 종료할때까지의 도달율을 측정해봤다.

게임을 인스톨하고 시작한다음, 캐릭터를 움직이고, 캐릭터를 이용하여 공격을하고, 2분동안 플레이해서 게임을 종료한 사용자의 비율을 분석해본다.

로그 메세지 디자인

그러면 이러한 게임 로그를 분석하기 위해서 수집할 로그 메세지는 어떤 형태가 될지 디자인을 해보자.

로그 이벤트는 아래와 같이 7가지로 정의한다.

  • START_SESSION,END_SESSION 은 게임을 시작과 끝날때 발생하는 이벤트이다.

  • NPC_CREATE,NPC_MOVE,NPC_DIE 는 NPC(돼지)를 생성하고 이동하고, 그리고 죽었을때 각각 발생하는 이벤트이다. 이동은 이벤트의 수가 많기 때문에, 10초 단위로 수집하였다.

  • PLAYER_MOVE,PLAYER_ATTACK 은 플레이어 캐릭터의 이동과 NPC를 공격하는 이벤트를 수집한다.


각 이벤트를 플레이하는 판과 연결하기 위해서 각 플레이는 고유의 sessionid가 생성되서 게임이 시작될때부터 끝날때 까지 모든 이벤트에 저장된다.



Event name

Param

Key

Value

Type

Note


START_SESSION

This event is triggered when player press “START” button after submitting player’s age & gender

sessionid

Unique session Id for this play

String


age

Player’s age

String


sex

Player’s gender

String

true : man

false : woman

PLAYER_MOVE

It record location of player in game map periodically (every 2sec)

sessionid




Pos_X




Pox_Z




PLAYER_ATTACK

This event is occurred when player attack NPC.

sessionid

Unique session Id for this play



npc_id

Attacked NPC ID



type

Type of NPC



pos_X

NPC location X



pos_Z

NPC location Y



damage

Damage that NPC get in this attack



life

Left life for this NPC



NPC_CREATE

When new NPC is created, this event is logged.

sessionid

Unique session Id for this play



npc_id

Attacked NPC ID



type

Type of NPC



pos_X

NPC location X



pos_Y

NPC location Y



NPC_MOVE

Every 2sec for each NPC, it records the location of NPC.

sessionid

Unique session Id for this play



npc_id

Attacked NPC ID



type

Type of NPC



pos_X

NPC location X



pos_Y

NPC location Y



NPC_DIE

It is triggered when NPC is dead by attack

sessionid

Unique session Id for this play



npc_id

Attacked NPC ID



type

Type of NPC



pos_X

NPC location X



pos_Y

NPC location Y



END_SCENE

It is triggered when game stage(session) is over

sessionid

Unique session Id for this play



score

Score for this play




이렇게 정의된 로그는 파이어베이스 애널러틱스에 의해서 빅쿼리로 자동으로 저장되게 된다.

실시간 디버깅

이런 로깅을 삽입하면, 로그가 제대로 저장이 되는지 확인이 필요한데, 파이어베이스 애널러틱스는 특성상 로그 이벤트가 1000개가 쌓이거나 또는 컨버전 이벤트가 발생하거나 또는 1시간 주기로 로그를 서버에 전송하기 때문에 바로 올라오는 로그 메세지를 확인할 수 없다.

그래서 이번에 새로 소개되니 기능이 “DEBUG VIEW”라는 기능인데, 이 특정 디바이스에 디버깅 옵션을 지정하면, 실시간으로 올라오는 로그를 확인할 수 있다.

로그는 모바일앱에서 업로드한 후 약 10~20초 후에, 화면에 반영된다.



대쉬 보드를 이용한 지표 분석

대쉬 보드는 파이어 베이스 애널러틱스에서 기본으로 제공되는 지표로 모바일 서비스에 공통적으로 필요한 지표들을 분석하여 웹으로 출력해준다.

DAU/WAU/MAU 분석

가장 기본적인 지표로는 월간,주간,일간 방문자 수로를 그래프로 출력해준다.

평균 플레이 시간 분석

다음은 평균 플레이 시간으로, 사용자가 하루에 평균 얼마나 앱을 사용하였는지, 동시 접속자수 (Session)과,  한번 접속했을때 얼마나 오래 앱을 사용 하였는지 (Session duration)등을 분석하여 그래프로 출력해준다.


국가별 접속 내역 분석

다음은 국가별 접속 내용으로, 글로벌 서비스에는 필수로 필요한 분석 내용이다.


사용자 데모그래픽 정보 분석

사용자에 대한 데모 그래픽 정보 즉 성별과, 나이를 분석해주는데, 앱에 별도로 사용자 로그인 기능이 없거나, 사용자 정보를 추적하는 기능이 없더라도, 파이어베이스 애널러틱스는 여러군데에서 수집한 로그를 기반으로 사용자의 성별과 나이를 분석해 준다.



특정 이벤트에 대한 분석

다음은 특정 이벤트에 대한 분석이 가능하다. 게임에서 사용자가 스테이지를 넘어가는 이벤트등 파이어베이스에 정의된 이벤트 이외에도 사용자가 정의한 이벤트에 대한 분석이 가능하다.

또한 이벤트가 발생한 사용자에 대한 데모 그래픽 정보 (연령,성별,국가)를 같이 분석해서 해당 이벤트가 어떤 사용자 층에서 발생하였는지를 분석해 준다.


예를 들어 게임의 보너스 스테이지를 많이 클리어한 사용자의 통계만을 볼 수 있고, 그 보너스 스테이지를 클리어한 사용자의 나이,성별, 국가 정보등을 볼 수 있다.



게임 플레이 완료율에 대한 퍼널 분석

다음은 앞에서 데이타 분석 모델을 정의할때 정의한 문제로 사용자가 게임을 시작해서 플레이를 끝낸 사용자 까지를 퍼널(깔때기) 분석을 적용한 예이다.

해당 시간에 총 93번의 게임이 플레이 되었으며, 캐릭터까지는 이동하였으나, 공격을 하지 않은 플레이는 3번, 그리고 끝까지 게임 플레이를 끝낸 사용자는 총 62번으로 측정되었다.



이외에도 상품 구매에 대한(인앱)에 대한 분석이나, 디바이스 종류, 앱 버전, 그리고 어느 광고 네트워크에서 사용자가 인입되었는지 등의 분석등 다양한 분석이 가능한데, 대쉬보드의 자세한 지표에 대해서는 http://bcho.tistory.com/1132 를 참고하기 바란다.

노트북을 이용한 커스텀 로그 분석

앞에서는 파이어베이스에서 제공되는 로그와 분석 방법에 대해서만 분석을 진행하였다. 이번에는 커스텀 로그와 원본(raw)데이타를 이용한 데이타 분석에 대해서 알아보자.


모든 원본 데이타는 앞에서도 언급했듯이 구글의 빅쿼리에 저장되기 때문에, SQL 쿼리를 이용하여 자유롭게 데이타 분석이 가능하고 그래프로도 표현이 가능하다.

별도의 개발이 없이 자유롭게 쿼리를 실행하고 그래프로 표현할 수 있는 도구로는 노트북이 있는데, 빅쿼리는 주피터 노트북과 제플린이 지원된다. 주피처 노트북 오픈소스를 구글 클라우드에 맞춘 버전은 Google Cloud Datalab이라는 것이 있는데, 여기서는 데이타랩을 이용하여 분석하였다.

캐릭터 이동 히트맵 분석

앞에서 NPC_MOVE와 PLAYER_ATTACK을 이용하여, NPC의 이동 동선과, PLAYER가 공격을 한 위치를 수집하였다.

이를 히트맵으로 그려보면 다음과 같다.


좌측은 NPC가 주로 이동하는 경로이고 우측은 플레이어가 NPC를 주로 공격한 위치로, 많이 간곳일 수록 진하게 칠해진다.

NPC 캐릭터는 전체 맵에 걸쳐서 이동을 하는 것을 볼 수 있고, 주로 우측 나무 근처를 많이 움직이는 것을 볼 수 있다. 오른쪽 사용자가 공격한 위치를 보면 주로 중앙에 모여 있기 때문에 우측 나무 근처로 움직인 NPC는 생존 확률이 높았을 것으로 생각해볼 수 있다.

그리고 NPC 이동 맵에서 중간중간에 진하게 보이는 점은 NPC 가 생성되는 위치이기 때문에, 이동이 많이 관측되었다.

연령별 플레이 점수 분석

다음으로 플레이어 연령별 점수대를 보면, 최고 점수는 30대가 기록하였고, 대략 4900점대인데 반해서, 전체적인 평균 점수는 40대가 높은 것을 볼 수 있다. (이 데이타는 연령별로 수집된 데이타의 양이 그리 많지 않기 때문에 정확하지는 않다. 어디까지나 분석 예제용으로만 이해하기 바란다.)



분석에 사용된 코드는 아래에 있다. 이 코드는 데모용이고 최적화가 되어있지 않기 때문에, 운영 환경에서는 반드시 최적화를 해서 사용하기 바란다.


https://github.com/bwcho75/bigquery/blob/master/GameData/Game%20Data%20Demo.ipynb


참고로, 모든 데이타 분석은 주로 파이썬을 이용하였는데, 근래에 빅데이타 분석용 언어로 파이썬이 많이 사용되기 때문에, 파이썬을 공부해놓으면 좀 더 쉽게 데이타 분석이 가능하다. 또한 파이썬으로 데이타를 분석할때 많이 쓰이는 프레임웍으로는 팬다스 (pandas)와 넘파이 (numpy)가 있는데, 이 둘 역시 같이 익혀놓는것이 좋다.

파이어베이스 노티피케이션 서비스를 통한 이벤트 기반의 푸쉬 타게팅

파이어베이스 애널러틱스와 연계해서 유용하게 사용할 수 있는 기능은 파이어베이스 노티피케이션 이라는 서비스가 있다.


파이어 베이스 노티피케이션 서비스는 파이어베이스에서 제공되는 웹 콘솔을 이용하여 관리자가 모바일 서비스에 손쉽게 푸쉬 메세지를 보낼 수 있는 서비스이다.

푸쉬 타게팅을 위한 별도의 서버 시스템을 개발하지 않고도 마케팅이나 기획자등 비 개발인력이 타게팅된 푸쉬 메세지를 손쉽게 보낼 수 있게 디자인된 서비스인데, 특히 파이어 베이스 애널러틱스와 연계가 되면 세세한 타게팅이 가능하다.


이벤트 로그 기반의 타케팅

푸쉬 타겟을 정할때, 파이어베이스 애널러틱스에서 수집한 이벤트를 조건으로 해서 푸쉬를 타게팅할 수 있다.

예를 들어

  • 게임 스테이지 3 이상을 클리어한 플레이어한 푸쉬를 보낸다.

  • NPC를 10,000개 이상 죽인 플레이어에게 푸쉬를 보낸다.

  • 아이템을 100개이상 구매한 사용자에게 푸쉬를 보낸다.

와 같이 서비스에서 수집된 이벤트에 따라서 다양한 조건을 정의할 수 있다.



<그림. 파이어베이스 노티피케이션에서 특정 사용자 층을 타게팅 해서 보내는 화면 >


이런 타게팅은 파이어베이스 애널러틱스에서 Audience로 사용자 군을 정의한 후에, (로그 이벤트 조건이나 사용자 이벤트 조건 등), 이 조건에 타겟해서 푸쉬를 파이어베이스 노티피케이션 서비스에서 정의한다.

사용자 정보 기반의 타게팅

서비스의 로그 이벤트 정보뿐 아니라, 사용자에 대해서도 푸쉬 타게팅이 가능한데, 특정 성별이나 나이에 대해 푸쉬를 보내거나, 특정 단말을 사용하는 사용자, 특정 국가에 있는 사용자등 다양한 사용자 관련 정보로 푸쉬를 보낼 수 있다.

사용자 정보 역시 앞의 이벤트 로그 정보처럼 개발자가 커스텀 필드를 추가하여 사용자 정보를 로그에 수집할 수 있다.


스케쥴링

이런 타게팅 푸쉬는 바로 웹에서 보낼 수 도 있지만, 특정 시간에 맞춰서 미리 예약을 해놓는 것도 가능하다.  




비용 정책 분석

파이어베이스 애널러틱스에서 원본 데이타를 수집 및 분석 하려면 빅쿼리를 연동해야 하는데, 빅쿼리 연동은 파이어베이스의 무료 플랜으로는 사용이 불가능하다. Blaze 플랜으로 업그레이드 해야 하는데, Blaze 플랜은 사용한 만큼 비용을 내는 정책으로 다른 서비스를 사용하지 않고, 파이어베이스 애널러틱스와 빅쿼리 연동만을 사용할 경우에는 파이어베이스에 추가로 과금되는 금액은 없다. (0원이다.)

단 빅쿼리에 대한 저장 가격과 쿼리 비용은 과금이 되는데,  빅쿼리 저장 가격은 GB당 월 0.02$ 이고, 90일동안 테이블의 데이타가 변하지 않으면 자동으로 0.01$로 50%가 할인된다.

그리고 쿼리당 비용을 받는데, 쿼리는 GB 스캔당 0.005$가 과금된다.


자세한 가격 정책 및, 파이어베이스 애널러틱스에 대한 데이타 구조는 http://bcho.tistory.com/1133 를 참고하기 바란다.

저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

첫번째 게임을 만들어 보다

IT 이야기 | 2016.11.01 09:24 | Posted by 조대협

첫번째 게임을 만들어보다.


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


요즘 게임 개발이 워낙 인기 있는 분야이고, 유니티로 게임을 개발하기가 워낙 쉽다고 해서, 언젠가 한번 게임을 개발해봐야하겠다는 막연한 생각만 가지고 있었는데, 게임 로그 수집 및 분석을 테스트할 기회가 되서, 아예 게임을 만들어보기로 하였다.


유니티 개발환경의 편리함


대략 10월10일 부터 시작해서, 개발이 끝난게 대략 10월 말 경이니까는 2~3주 정도 걸린것 같다.

유니티 개발이 처음이라서, 책부터 사보고 남는 시간 짬짬이 개발을 했는데, 아마 집중해서 했으면 일주정도면 되지 않았을까 한다.


유니티로 게임을 개발하면서, 먼저 느낀것은 개발 환경이 참 잘 짜야져 있다는 것이다. 객체를 정의한 후, 각 객체에 객체를 컨트롤하기 위한 코드를 짜서, 객체에 드래그앤 드롭을 하면 코드가 적용되고, 각 객체를 Prefab이라는 형태로 쉽게 라이브러리 하여 다시 불러 쓸 수 가 있다. 또한 충돌 처리나, 지형에 따른 이동 처리등이 아주 쉽게 가능하다. 대학교때 다이렉트X를 이용해서 게임을 개발할때를 생각하면 정말 많이 쉬어졌다.




게임을 만들기 위한 사운드나 그래픽 에셋, 그리고 각종 특수 효과도 유니티 에셋 스토어에서 어렵지 않게 구할 수 있었다.


유니티는 멀티 플랫폼을 지원하는데, 같은 코드로 빌드 환경을 바꿔서 윈도우/맥 환경에서 부터 안드로이드,IOS용 게임 까지 쉽게 빌드가 가능하다.(거의 신세계와 같은 느낌)


전체적인 느낌은 게임 뿐 아니라, 일반 프로그래밍 환경이 궁극적으로 가야하는 환경이 아닌가 싶다. 멀티 플랫폼 지원, 에셋 스토어, 그리고 객체에 코드를 넣는 부분은 객체지향형의 개념을 가장 잘 구현한 케이스가 아닌가 싶다. (나중에 개발 플랫폼을 만든다면, 이 개념을 차용하고 싶다.)

유니티 개발 생태계

유니티로 개발하면서 재미있었던 점 중의 하나가 개발자 생태계가 아주 발전되어 있다. 왠만한 질문은 구글링을 하면 유니티 커뮤니티에 답이 대부분 있고, 유니티 개발환경이 아무래도 화면 위주다 보니, 글로 쓴 문서 보다는 동영상이 따라하기 훨씬 편리했는데, 유니티 튜토리얼 관련 컨텐츠가 유투브에 아주 풍부하였다.

또한 유니티에서 제공하는 튜토리얼 문서 역시 쉽고 체계적이었다.

아마 이렇게 유니티가 유행하게 된 요인중 하나는 플랫폼이 쉬울뿐 아니라, 개발자 생태계를 잘 가꾼 유니티 회사에 있지 않나 싶다.


게임 개발의 어려움


막상 게임을 개발해보니, 프로그래밍 자체 보다는 캐릭터의 배치, 시나리오의 작성, 난이도 조절, UX 등이 더 큰 어려움으로 다가왔다. 아무래도 사용자의 재미를 위주로 하는 애플리케이션이다 보니 코딩 이외에도 신경을 써야할 부분이 매우 많았다.


결론은

그래서 결론은, 게임 개발은 아무나 하는 것이 아니라는 것… 지금도 게임 개발하고 계시는 게임 개발자 분들 존경합니다.

유니티의 개발환경은 에셋 스토어와, 객체 지향형 개념등 한 단계 앞서 있는 개념으로 배울게 많다는 것. 그리고 개발 플랫폼이 메이져가 되기 위해서는 생태계 형성이 중요하고, 동영상 컨텐츠가 중요하다는 것 등이었다.






저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

수포자를 위한 딥러닝

#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) 값을 이용하여 예측을 수행한다.


참고





저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

수포자를 위한 딥러닝


#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)에 대해서 알아보도록 한다.


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

수포자를 위한 딥러닝


#2 - 선형회귀분석을 통한 머신러닝의 기본 개념 이해


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


Linear Regression을 통한 머신 러닝의 개념 이해

거리에 따른 택시 요금 문제

머신러닝이란 무엇일까? 개념 이해를 돕기 위해서 선형 회귀 (Linear Regression)이라는 머신러닝 모델을 보자. 먼저 선형 회귀 (Linear regression)이 무엇인지 부터 이해를 해야 하는데, 쉽게 설명하자면 결과값 (output value)이 있고 그 결과값을 결정할 것이라고 추정되는 입력값 (input value)과 결과 값의 연관관계를 찾는 것이고 이를 선형 관계를 통해 찾는 방법이 선형 회귀 (Linear regression)이다.

예를 들어서 설명해보자, 택시 요금을 예로 들어보자,택시 요금은 물론 막히냐 마냐에 따라 편차가 있지만, 대부분 거리에 비례해서 요금이 부과된다. 그래서 결과값 (요금)과 입력값 (거리)의 관계를 찾아야 한다.


거리별 요금을 그래프로 나타내보면 대략 다음과 같은 분포를 띄게 된다

원본 데이타의 거리를 x_data 그리고, 그 거리에서 측정된 택시 요금을 y_origin 이라고 하자.



가설 (Hypothesis) 정의

거리와 요금이 서로 비례하기 때문에, 거리(x_data)와 요금(y_data)간의 상관 관계는 다음과 같이 일차 방정식과 형태의 그래프를 그리게 된다고 가정하자. W (Weight)는 그래프의 각도, b는 bias를 뜻한다

y_data = Wx_data + b

이 일차 방정식 형태로 대충 1차원 그래프를 그려보자 같은 형태로 아래와 같이 그래프를 그려봤다.


그래프를 그려보니 그래프의 각이 안맞는것 같다. 그래프의 각도와 높이를 보정해보자


그래프를 보정했지만 또 안 맞는 것 같다. 그렇다면 최적의 그래프의 각도 W와, 높이 B는 어떻게 찾아야 하는 것일까?

코스트(비용) 함수

우리가 구하고자 하는 그래프는 실제 값에서 그래프의 값까지 차이가 가장 작은 값을 구하고자 하는 것이다. 아래 그림을 보자, 아래와 같이 y_data=Wx_data +b와 같은 그래프를 그렸다고 하자.


원래 값에서 우리가 예측한 값의 차이는 

(원래값과 계산된 값의 차이) = 측정값 - 그래프의 값

인데, 차이를 d라고 하자. 그리고 그래프에 의해서 계산된 값은 y_data라고 하면 택시 거리 x_data 에서 원래 측정된 값을 y_orgin라고 해서 수식으로 나타내면,

d = y_data - y_origin

이 된다. 이때 측정값은 여러개가 있기 때문에 n이라고 하면  n번째 측정한 택시비와 산식에 의해서 예측된 값의 차이는 dn이 된다.


dn = y_data_n - y_origin_n


즉 우리가 구하고자 하는 값은 dn의 합이 최소가 되는 W와 b의 값을 구하고자 하는 것이다.

다르게 설명하면 실제 측정한값과, 예측한 값의 차이가 최소가 되는 W와 b를 구하고자 하는 것이다.

dn은 위의 그래프에서 처럼 그래프 위에도 있을 수 있지만 (이경우 dn은 양수), 그래프 아래에도 있을 수 있기 때문에, (이경우 dn은 음수). 합을 구하면, 예측 선에서의 실측값 까지의 거리의 합이 되지 않기 때문에, dn에 대한 절대값을 사용한다고 하자.

그리고 n이 측정에 따라 여러개가 될 수 있기 때문에, 평균을 사용하자.


( ABS(d1)+ABS(d2)+ABS(d3)+.....+ABS(dn)) ) / n


즉 우리가 구하고자 하는 W와 b는 위의 함수의 값이 최소가 되는 값을 구하면 된다.

이렇게 측정된 값에서 연산된 값간의 차이를 연산하는 함수를 비용 함수 또는 영어로 코스트 함수 (Cost function이라고 한다.


사람이 일일이 계산할 수 없이니 컴퓨터를 이용해서 W=0.1,0.2,0.3,.... b=0.1,0.2,0.3,..... 식으로 넣어보고 이 코스트 함수가 가장 최소화되는 W와 b의 값을 찾을 수 있다.

옵티마이져 (Optimizer)

코스트 함수의 최소값을 찾는 알고리즘을 옵티마이져(Optimizer)라고 하는데, 상황에 따라 여러 종류의 옵티마이져를 사용할 수 있다. 여기서는 경사 하강법 (Gradient Descent) 라는 옵티마이져에 대해서 소개하도록 하겠다.


경사 하강법

그러면 W와 b를 구할때 W와 b를 어떤식으로 증가 또는 감소 시켜서 코스트 함수의 최소값을 가장 효율적으로 찾아낼 수 있을까? 위에서 언급한것 처럼 W를 0.0에서 부터 ). 0.1씩 증가시켜나가고 b도 같이 0.0에서 부터 1씩 증가 시켜 나갈까? 무한한 컴퓨팅 자원을 이용하면 되기는 하겠지만, 이렇게 무식하게 계산하지는 않는다.

코스트 함수를 최적화 시킬 수 있는 여러가지 방법이 있지만, Linear regression의 경우에는 경사 하강법 (그레이언트 디센트 : Gradient descent)라는 방식을 사용한다. 경사하강법에 대해서는 자세하게 알필요는 없고 ”대략 이런 개념을 사용하는 구나” 하는 정도만 알면 된다.


경사 하강법을 사용하기 위해서는 위의 코스트 함수를,측정값과 예측값의 절대값의 평균이 아니라 평균 제곱 오차라는 함수를 사용한다.

이 함수는 형식으로 정의되는데, 평균 제곱 오차 함수 (Mean square error function)이라고 한다.


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


풀어서 설명하면, n 번째의 원래데이타(y_origin_n)와 예측 데이타(y_data_n)의 차이를 제곱(^2)해서, 이 값을 n으로 나눈 평균 값이다.

즉 이 Cost가 최소가 되는 W와 b값을 구하면 된다.

편의상 W하나만을 가지고 설명해보자. 위의 그래프를 W와 b에 대한 상관 그래프로 그려보면 다음과 같은 함수 형태가 된다.


이 그래프에서 W에 대한 적정값에 대한 예측을 시작하는 점을 위의 그림에서 파란 점이라고 하면, 경사 하강법은 현재 W의 위치에 대해서, 경사가 아래로 되어 있는 부분으로 점을 움직이는 방법이다. 어느 방향으로 W를 움직이면 Cost 값이 작아지는지는 현재 W위치에서 비용 함수를 미분하면 된다. (고등학교 수학이 기억이 나지 않을 수 있겠지만 미분의 개념은 그래프에서 그 점에 대한 기울기를 구하는 것이다. )


이렇게, 경사를 따라서 아래로 내려가다 보면 Cost 함수가 최소화가 되는 W 값을 찾을 수 있다. 이렇게 경사를 따라서 하강 (내려가면서) 최소값을 찾는다고 하여 경사 하강법이라고 한다.  


학습

코스트 함수가 정의 되었으면 실제 데이타 x_data_n과 y_data_n을 넣어서 경사하강법에 의해서 코스트 함수가 최소가 되는 W와 b를 구한다. 이 작업은 W값을 변화시키면서 반복적으로 x_data_n로 계산을 하여, 실제 측정 데이타와 가설에 의해서 예측된 결과값에 대한 차이를 찾아내고 최적의 W와 b값을 찾아낸다.

예측

학습 과정에 의해서 최적의 W와 b를 찾았으면 이제, 이 값들을 이용해서 예측 해보자

학습에 의해서 찾아낸 W가 1600, b가 2000이라고 하면, 앞의 가설에서 정의한 함수는 Wx*b였기 때문에, 예측 함수는 


y = Wx +b

거리에 따른 택시비 = W*(거리) + b

거리에 따른 택시비 = 1600 * (거리) + 2000

이 되고, 이를 학습된 모델 이라고 한다.


이제 예측을 수행해보자, 거리가 10km일 때 택시비는 얼마일까? 공식에 따라

택시비 = 1600 * 10km + 2000

으로, 18000원이 된다.

머신 러닝의 순서

지금까지 택시 거리와 택시비에 대한 문제를 가지고 머신 러닝에 대한 기본 원리를 살펴보았다.

이를 요약해서 머신 러닝이란 것이 어떤 개념을 가지고 있는지 다시 정리해보자.


기본 개념은 데이타를 기반으로해서 어떤 가설 (공식)을 만들어 낸 다음, 그 가설에서 나온 값이 실제 측정값과의 차이(코스트 함수)가 최소한의 값을 가지도록 변수에 대한 값을 컴퓨터를 이용해서 찾은 후, 이 찾아진 값을 가지고 학습된 모델을 정의해서 예측을 수행 하는 것이다.  


학습 단계

즉 모델을 만들기 위해서, 실제 데이타를 수집하고, 이 수집된 데이타에서 어떤 특징(피쳐)를 가지고 예측을 할것인지 피쳐들을 정의한 다음에, 이 피쳐를 기반으로 예측을 한 가설을 정의하고, 이 가설을 기반으로 학습을 시킨다.


예측 단계

학습이 끝나면 모델 (함수)가 주어지고, 예측은 단순하게, 모델에 값을 넣으면, 학습된 모델에 의해서 결과값을 리턴해준다.


지금까지 Linear regression 분석을 통한 머신러닝의 원리에 대해서 간략하게 알아보았다. 다음 다음장에서는 이 모델을 어떻게 프로그래밍 언어를 이용하여 학습을 시키고 운영을 하는지에 대해서 알아보도록 하겠다.



Thanx to 

이글은 딥러닝 전문가 김홍회 박사님(Ayden Kim - https://www.facebook.com/Ayden.Kim )이 검수해주셨습니다. 감사합니다.


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

수포자를 위한 딥러닝

#1 - 머신러닝의 개요

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

들어가기에 앞서서 

몇년전부터 빅데이타와 머신러닝이 유행하면서 이분야를 공부해야겠다고 생각을 하고 코세라의 Andrew.NG 교수님의 강의도 듣고, 통계학 책도 보고, 수학적인 지식이 부족해서 고등학교 수학 참고서도 봤지만, 도저히 답이 나오지 않는다. 머신 러닝에 사용되는 알고리즘은 복잡도가 높고 일반적인 수학 지식으로 이해조차 어려운데, 실제 운영 시스템에 적용할 수 있는 수준의 알고리즘은 석박사급의 전문가적인 지식이 아니면 쉽게 만들 수 없는 것으로 보였다. 예를 들어 인공지능망(뉴럴네트워크:Neural Network) 알고리즘에 대한 원리는 이해할 수 있지만, 실제로 서비스에 사용되는 알고르즘을 보니 보통 60~90개의 계층으로 이루어져 있는데, (그냥 복잡하다는 이야기로 이해하면 됨) 이런 복잡한 알고리즘을 수학 초보자인 내가 만든다는 것은 거의 불가능에 가까워 보였고, 이런것을 만들기 위해서 몇년의 시간을 투자해서 머신러닝 전문가로 커리어패스를 전환할 수 는 있겠지만 많은 시간과 노력이 드는데 반해서, 이미 나에게는 소프트웨어 개발과 백앤드 시스템이라는 전문분야가 있어싸.

그래도 조금씩 보다보니, 머신 러닝에서 소개되는 알고리즘은 주로 사용되는 것은 약 20개 내외였고, 이미 다 정형화 되어 있어서 그 알고리즘을 만들어내기보다는, 가져다 쓰기만 하면 될 것 같다는 느낌이 들었다. 아직 많이 보지는 못했지만, 실제로 머신 러닝 기반의 시스템들은 나와 있는 알고리즘을 코드로 옮겨서 운영 환경에 올리는 경우가 대부분이었다.

비유를 하자면 우리가 복잡한 해쉬 리스트나, 소팅 알고리즘을 모르고도 간단하게 프로그래밍 언어에 있는 라이브러리를 가져다 쓰는 것과 같은 원리라고나 할까? 그래서, 완벽하게 이해하고 만들기 보다는 기본적인 원리를 파악하고 이미 공개된 알고리즘과 특히 레퍼런스 코드를 가져다가 운영환경에다 쓸 수 있는 정도의 수준을 목표로 하기로 했다.

이제 아주 아주 초보적인 수준의 이해를 가지고, 구글의 텐서플로우 기반으로 머신러닝과 딥러닝을 공부하면서 내용을 공유하고자 한다. 글을 쓰는 나역시도 수포자이며 머신러닝에 대한 초보자이기 때문에, 설명이 부족할 수 도 있고, 틀린 내용이 있을 수 있음을 미리 알리고 시작한다. (틀린 내용은 알려주세요)

머신러닝

머신 러닝은 데이타를 기반으로 학습을 시켜서 몬가를 예측하게 만드는 기법이다.

통계학적으로는 추측 통계학 (Inferential statistics)에 해당하는 영역인데, 근래에 들어서 알파고와 같은 인공지능이나 자동 주행 자동차, 로봇 기술등을 기반으로 주목을 받고 있다.



<그림. 구글의 자동 주행 자동차>


간단한 활용 사례를 보면

  • 학습된 컴퓨터에 의한 이메일 스팸 필터링

  • 편지지의 우편번호 글자 인식

  • 쇼핑몰이나 케이블 TV의 추천 시스템

  • 자연어 인식

  • 자동차 자율 주행

등을 볼 수 있다.


이러한 시나리오는 지속적인 샘플 데이타를 수집 및 정제하고 지속적으로 알고리즘을 학습해나감에 따라서 최적의 알고리즘을 찾아나가도록 한다.

쇼핑몰의 추천 시스템의 경우 사용자의 구매 패턴을 군집화하여 유사한 패턴을 찾아냄으로써 적절한 상품을 추천하는데, 예를 들어 30대 남성/미혼/연수입 5000만원/차량 보유한 사용자가 카메라,배낭등을 구매했을 경우 여행 상품을 구매할 확률이 높다는 것을 학습하였을때, 이러한 패턴의 사용자에게 여행 상품을 추천해주는 것과 같은 답을 제공할 수 있다.

지도 학습과 비지도 학습

머신러닝은 학습 방법에 따라서 지도 학습 (Supervised Learning)과 비지도 학습 (Unsupervised Learning)으로 분류될 수 있다.

지도 학습 (Supervised Learning)



예를 들어 학생에게 곱셈을 학습 시킬때,

“2*3=6이고, 2*4=8이야, 그러면 2*5= 얼마일까? “

처럼 문제에 대한 정답을 주고 학습을 한 후, 나중에 문제를 줬을때 정답을 구하도록 하는 것이 지도 학습 (Supervised Learning)이다.

비지도 학습 (Unsupervised learning)

반대로 비지도 학습은 정답을 주지않고 문제로만 학습을 시키는 방식을 비지도 학습이라고 한다.

예를 들어 영화에 대한 종류를 학습 시키기 위해서, 연령,성별과 영화의 종류 (액션, 드라마, SF)를 학습 시켰을때, 이를 군집화 해보면 20대 남성은 액션 영화를 좋아하고 20대 여성은 드라마 영화를 좋아 하는 것과 같은 군집된 결과를 얻을 수 있고, 이를 기반으로 20대 남성이 좋아하는 영화의 종류는 유사한 군집의 결과인 ”액션 영화" 라는 답을 내게 되낟.


여기서 문제에 대한 답을 전문적인 용어로 이야기 하면 라벨된 데이타 (Labeled data)라고 한다.


머신러닝의 대표적인 문제 Regression과 Classification 문제

머신러닝을 이용해서 해결하는 문제의 타입은 크게 regression과 classification 문제 두가지로 대표가 된다.

Classification

Classification은 입력값에 대한 결과값이 연속적이지 않고 몇개의 종류로 딱딱 나눠서 끊어지는 결과가 나오는 것을 이야기 한다. 예를 들어 종양의 크기가 0.3cm 이상이고 20대이면, 암이 양성, 또는 종양의 크기가 0.2cm 이하이고 30대이면, 암이 음성과 같이 결과 값이 ”양성암/음성암"과 같이 두개의 결과를 갖는 것이 예가 된다.


<종양 크기에 따른, 암의 양성/음성 여부에 대한 그래프>

또 다른 예로는 사진을 업로드 했을때, 사진의 물체를 인식할때 ”이사진은 개이다.” “이사진은 고양이이다.” 처럼 특정 종류에 대한 결과값이 나오는 것 역시 Classification 문제로 볼 수 있다.


Regression

Regression 문제는 결과값이 연속성을 가지고 있을때 Regression 문제라고 한다. 즉 택시의 주행거리에 따른 요금과 같은 문제인데, 변수 택시 주행 거리에 대해서, 결과 택시 값이 기대 되는 경우로 변수와 결과값이 연속적으로 이루어 지는 경우를 말한다.


<그림. 주행 거리에 따른 택시비 >

머신 러닝과 딥러닝

이러한 머신 러닝의 분야중, 인공 지능망 (뉴럴 네트워크 / Artificial neural network)라는 기법이 있는데, 사람의 뇌의 구조를 분석하여, 사람 뇌의 모양이 여러개의 뉴런이 모여서 이루어진것 처럼, 머신 러닝의 학습 모델을 두뇌의 모양과 같이 여러개의 계산 노드를 여러 층으로 연결해서 만들어낸 모델이다.


<알파고에 사용된 뉴럴네트워크 구조>


이 모델은 기존에 다른 기법으로 풀지 못하였던 복잡한 문제를 풀어낼 수 있었지만, 계층을 깊게 하면 계산이 복잡하여 연산이 불가능하다는  이유로 그간 관심을 가지고 있지 못했다가

캐나다의 CIFAR (Canadian Institute for Advanced Research) 연구소에서 2006년에 Hinton 교수가 ”A fast learning algorithm for deep belifef nets” 논문을 발표하게 되는데,  이 논문을 통해서 뉴럴네트워크에 입력하는 초기값을 제대로 입력하면 여러 계층의 레이어에서도 연산이 가능하다는 것을 증명하였고,  2007년 Yosua Bengio 라는 분이 ”Greedy Layer-Wise training of deep network” 라는 논문에서 깊게 신경망을 구축하면 굉장히 복잡한 문제를 풀 수 있다는 것을 증명해냈다.


이때 부터 뉴럴네트워크가 다시 주목을 받기 시작했는데,  이때 뉴럴 네트워크라는 모델을 사람들에게 부정적인 인식이 있었기 때문에, 다시 이 뉴럴 네트워크를 딥러닝 (Deep learning)이라는 이름으로 다시 브랜딩을 하였다.

그 이후에 IMAGENET 챌린지라는 머신러닝에 대한 일종의 컨테스트가 있는데, 이 대회는 이미지를 입력하고 머신 러닝을 통해서 컴퓨터가 이미지의 물체등을 인식할 수 있게 하는 대회로, 머신 러닝 알고리즘의 정확도를 측정하는 대회이다. 이 대회에서 2012년   Hinton 교수님 랩에 있던 Alex 라는 박사 과정의 학생이 딥러닝 기반의 머신 러닝 알고리즘으로 혁신 적인 결과를 내었고 지금은 이 딥러닝이 머신 러닝의 큰 주류중의 하나로 자리잡게 되었다.


<이미지넷에서 사용되는 이미지>



저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

노트7의 소셜 반응을 분석해 보았다. 


#3 제플린 노트북을 이용한 상세 분석



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



데이타 스튜디오는 편리하게 사용할 수 있지만, 쿼리 사용등이 불가능하기 때문에, 원본 데이타를 이용한 상세 분석에는 어려움이 있다. 원본 데이타를 이용해서 상세 분석을 하려면 노트북 계열의 애플리케이션이 효과적인데, 빅쿼리를 연동할 수 있는 노트북으로는 이전에 소개한 주피터 노트북 기반의 데이타랩 (datalab)과, 스파크나 다른 빅데이타 솔루션과 함께 많이 사용되는 제플린 노트북(zeppelin.apache.org) 이 있다.


지난 글에서 데이타랩에 대한 연동 방법은 이미 소개하였으니, 이번에는 제플린을 통하여, 빅쿼리의 데이타를 분석해보도록 한다.


제플린 설치

제플린을 설치 하는 방법은 간단하다. Zeppelin.apache.org 에서, 설치 파일을 다운로드 받는다.

빅쿼리 연동 인터프리터는 제플린 버전 0.61 버전 이상에 포함되어 있기 때문에, 0.61 버전 이상을 다운로드 받는다.  이 때 모든 인터프리터가 포함된 버전을 다운 받아야 한다. (아니면 별도로 인터프리터를 설치해야 하는 번거로움이 따른다.)


다운 로드 받은 파일의 압축을 푼다. 다음으로 제플린 설치 디렉토리로 들어가서 다음 명령어를 수행한다.

% ./bin/zeppelin.sh

윈도우의 경우에는 %./bin/zeppelin.cmd 를 실행하면 된다.

자바 애플리케이션이기 때문에 별도의 설치 과정이 필요없고, 제플린 애플리케이션을 실행하기만 하면 된다.

제플린이 기동되었으면 브라우져에서 http://localhost:8080 으로 접속하면 다음과 같이 제플린 콘솔을 볼 수 있다.

노트북 생성

제플린 콘솔에 들어왔으면 초기화면에서 Create new note 라는 메뉴를 이용하여 새로운 노트북을 생성하자. 여기서는 편의상 “BQ 노트북" 이라는 이름으로 노트북을 생성하였다.


분석 쿼리 작성

이제 분석할 내용은 수집된 트윗의 명사들에 대해서, 시간 단위로 그룹핑을 한 다음에, 각 단어에 대해서 발생한 횟수를 카운트해서 보여주는 내용을 구현하려고 한다.

예를 들어서 9월20일에는 “유행" 이라는 단어가 200회 발생하였고, “패션" 이라는 단어가 100회 발생하였다. 라는 식으로 조회를 하려고 한다.


현재 테이블 구조는 다음과 같다.

Date (발생 시간)

Noun (명사)

count (발생 횟수)


SQL 문장을 작성해보자

select date,noun,sum(count) from 테이블명

group by date,noun


이렇게 쿼리를 하면, 시간대 별로, 명사와 그 명사의 발생 횟수를 리턴을 해주는데, 우리는 앞의 데이타 플로우 프로그램에서 30초 단위로 통계를 집계하도록 하였기 때문에, 30초 단위로 결과가 리턴된다. 우리가 원하는 결과는 30초 단위가 아니라 1시간 단위의 결과 이기 때문에, 다음과 같이 쿼리를 수정한다.


select  DATE(date) as ddate,HOUR(date) as dhour,noun,sum(count) from 테이블명

group by ddate,dhour,noun


DATE와 HOUR라는 함수를 사용하였는데, DATE는 타임 스탬프 형태의 컬럼에서 날짜만을 추출해주는 함수 이고, HOUR는 타임 스탬프 형태의 컬럼에서 시간만을 추출해주는 함수 이다.

이렇게 날짜와 시간만을 추출한 다음에, group by 절을 이용하여, 날짜와,시간 그리고 명사로 그룹핑을 하게 되면 우리가 원하는 것과 같이 각 날짜의 시간대별로 명사별 발생횟수 ( sum(count)) 값의 통계를 얻을 수 있다.


제플린에서 빅쿼리 명령을 수행하려면 다음과 같이 %bigquery.sql 이라고 첫줄에 선언을 한 다음에 SQL 문장을 수행하면 된다.




결과는 디폴트로 테이블 형태로 나오는데, 아래 아이콘 중에서 그래프 아이콘을 누르면 그래프 형태로 볼 수 가 있는데, 이 때 X,Y축의 변수를 지정할 수 있다.

아래 그림과 같이 Keys (X축을) ddate,dhour를 선택하고 Values(Y축)을 dhour SUM을 선택하면, 시간별 나타난 단어수를 볼 수 있다.



그런데 이 쿼리를 수행하면, 각 시간별로 발생한 명사 단어의 수가 매우 많기 때문에, 보기가 매우 어렵다.

그렇다면 시간대별로 발생한 명사중에서 각 시간대별로 많이 발생한 명사 5개씩만을 볼 수 없을까? 즉 group by를 전체 데이타 구간이 아니라, 각각 시간대 별로 계산을 해줄 수 는 없을까 하는 필요가 발생한다.

빅쿼리 파티셔닝

데이타를 구간 별로 나눠서 연산할 수 있는 기능으로 빅쿼리에는 파티션이라는 기능이 있다.

예를 들어서 group by를 전체 결과에 대해 그룹핑을 하는 것이 아니라, 앞에서 언급한 요건 처럼 일 단위로 짤라서 그룹핑을 하는 것이 가능하다.




파티션을 이용해서 할 수 있는 것은 파티션별로 합계나, 통계를 내거나, 파티션의 각 로우의 값의 백분율(%)나 또는 소팅한 순서등을 볼 수 있다. 여기서는, 시간으로 파티션을 나누고  파티션내에서 명사의 수가 많은 수 순서대로 소팅을 한후에, RANK라는 함수를 이용하여 그 파티션에서 그 명사가 몇번째로 많이 나타났는지를 출력하도록 해보겠다.


파티션의 사용법은 다음과 같다.

“파티션 함수 OVER (PARTITION BY 파티션을할 키 목록)”

여기서는 일/시간 별로 파티션을 나눈 후에, 그 순위별로 소팅을 할 것이기 때문에, 다음과 같은 식을 쓴다.

RANK() OVER (PARTITION BY ddate,dhour ORDER BY ncount DESC  ) as rank


이를 쿼리에 적용하면 다음과 같다.

   SELECT

       DATE(date) as ddate,HOUR(date) as dhour

       ,noun

       ,sum(count) as ncount

       , RANK() OVER (PARTITION BY ddate,dhour ORDER BY ncount DESC  ) as rank

   FROM [useful-hour-138023:twitter.noun]

   group by noun,ddate,dhour

   order by ddate,dhour,ncount desc


그러면 다음과 같이 일/날짜 파티션별로 많이 발생한 명사 순으로 발생횟수와 순위(rank)를 출력해준다.



그런데 쿼리를 돌려보면 알겠지만, 시간대별로 수집한 명사의 종류가 많기 때문에, 일자별 데이타가 매우 많다. 그래서 파티션별로 많이 등장하는 단어 5개만을 보려고 하면 rank <5 인것만 걸러내면 된다. 이는 중첩 쿼리를 이용해서 수행이 가능하다

다음은 이를 적용한 예이다.


SELECT ddate,dhour

   ,noun

   , rank

from (

   SELECT

       DATE(date) as ddate,HOUR(date) as dhour

       ,noun

       ,sum(count) as ncount

       , RANK() OVER (PARTITION BY ddate,dhour ORDER BY ncount DESC  ) as rank

   FROM [useful-hour-138023:twitter.noun]

   where noun != "note7" and noun != "samsung" and noun !="galaxy"

   group by noun,ddate,dhour

   order by ddate,dhour,ncount desc

   )

where rank < 6


이렇게 하면, 각 시간대별로 자주 등장하는 단어 6개만을 보여준다.


이 쿼리를 이용하여 데이타를 어떻게 분석하는지는 예전글 http://bcho.tistory.com/1136 을 참고하세요.


간단하게나마 트위터 피드에서 특정 키워드를 기반으로 하여, 명사와 형용사를 추출하여 소셜 반응을 분석하는 애플리케이션 개발과 데이타 분석 방법에 대해서 설명하였다.

아이폰7을 분석해보니, 명사 분석도 의미가 있었지만, 아이폰7에 대한 기대를 형용사 분석을 통해서도 많은 인사이트를 얻을 수 있었다. Awesome, excellent와 같은 기대치가 높은 형용사가 많이 검출되었고 bad, fuck 과 같은 부정적인 의미의 형용사는 다소 낮게 검출되었다. (아마 이즈음이 노트7 폭발로 인하여 반사 이익을 얻은게 아닐까 추정되는데.)


이외에도, 이모콘티만 추출하여 분석을 한다거나, 부사등을 통해서 분석을 하는 것도 가능하고, 구글 자연어 처리 API는 글을 통해서 사람의 감정을 분석해주는 기능도 있기 때문에 응용 분야는 훨씬 더 넓다고 볼 수 있다.

저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License


노트7의 소셜 반응을 분석해 보았다. 


#2 구현하기


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

지난번 글 http://bcho.tistory.com/1136에 이어서, 트위터를 통한 소셜 반응을 분석하는 시 스템을 구축하는 방법에 대해서 알아본다. 

시나리오 및 아키텍쳐

스트리밍 처리와 데이타 플로우에 대한 개념 이해가 끝났으면 이제 실제로 실시간 분석 애플리케이션을 만들어보자.

SNS를 이용한 마케팅 분석에서 대표적인 시나리오중 하나는 트위터 피드를 분석하여, 사람들의 반응을 분석하는 시나리오이다. 자주 언급 되는 단어나 형용사를 분석함으로써, 특정 제품이나 서비스에 대한 소셜 네트워크상의 바이럴 반응을 분석할 수 있는데, 여기서  구현하고자 하는 시나리오는 다음과 같다. 트위터 피드에서 특정 키워드로 트윗 문자열들을 수집한 후에, 구글의 자연어 분석 API를 통하여 트윗 문자열에서 명사와 형용사를 추출한다

추출한 명사와 형용사의 발생 횟수를 통계내어서 대쉬보드에 출력하는 시나리오이다.


이를 구현하기 위한 솔루션 아키텍쳐는 다음과 같다.


fluentd를 이용하여 트위터의 특정 키워드를 기반으로 트위터 피드를 수집하고, 수집된 피드들은 구글 클라우드의 큐 서비스인 Pub/Sub으로 전달된다. 전달된 데이타는 데이타 플로우에서 읽어서 필요한 데이타만 필터링한 후, 구글의 자연어 분석 API를 통해서 명사와 형용사를 분리한다.

분리된 명사와 형용사는 데이타플로우에서 30초 주기의 고정윈도우(Fixed Window) 단위로, 명사에서 발생한 단어의 수와, 형용사에서 발생한 단어의 수를 카운트 한 다음에, 빅쿼리에 명사 테이블과 형용사 테이블에 저장한다.

저장된 데이타는 구글의 리포팅 도구인 데이타 스튜디오를 통해서 그래프로 출력한다.


구현

그러면 위에서 설명한 아키텍쳐대로 시스템을 하나씩 구현해보자.

전체 예제 코드와 설정 파일은 https://github.com/bwcho75/googledataflow/tree/master/twitter 에서 받아볼 수 있다.

트위터 피드 수집 서버 설정

먼저 트위터에서 피드를 수집하기 위해서 fluentd 에이전트를 설정한다. 구글 컴퓨트 엔진에서 VM을 생성한 후에, 앞의 빅쿼리 예제에서 한것과 마찬가지로 fluentd 에이전트를 설치한다.

VM을 설치할때, 반드시 Cloud API access scopes를 full API access로 설정해야 하는데, 이 VM에서 fluentd를 통해서 수집한 피드를 Pub/Sub으로 전달할때, Pub/Sub API를 사용하기 때문이다.


Fluentd 가 설치되었으면 Pub/Sub으로 데이타를 전달하기 때문에,Fluentd pub/sub 에이전트를 추가설치 한다.

에이전트명은 “fluent-plugin-gcloud-pubsub”로

% sudo td-agent-gem install fluent-plugin-gcloud-pubsub

명령을 이용해서 설치한다.


에이전트 설치가 끝났으면 fluentd 에이전트 설정을 해야 한다.

다음은 트위터에서 “note7”에 관련된 피드를 읽어서 pub/sub 큐로 피드를 전송하는 fluentd 설정 예제이다.


<source>

 type twitter

 consumer_key        트위터 Consumer Key

 consumer_secret     트위터 Consumer Secrect

 oauth_token         트위터 Access Token

 oauth_token_secret  트위터 Access Token Secrect

 tag                 input.twitter.sampling  # Required

 timeline            sampling                # Required (tracking or sampling or location or userstream)

 keyword             note7

 output_format       nest                    # Optional (nest or flat or simple[default])

</source>

<match input.twitter.sampling>

 type gcloud_pubsub

 project 본인의 프로젝트명

 topic projects/본인의 프로젝트명/topics/twitter

 key 다운로드받은 구글 클라우드 억세스 토큰 JSON 파일

 flush_interval 10

 autocreate_topic false

</match>


Fluentd 설정이 끝났다.

Pub/Sub 큐 설정

다음으로는 fluentd 읽어드린 트위터 피드를 받아드를 Pub/Sub 큐를 생성한다.

큐 생성 방법에 대해서는 앞의 Pub/Sub 챕터를 참고하기 바란다. (http://bcho.tistory.com/1120)

큐 이름은 twitter라고 한다. 전체 큐 이름은 “projects/본인 프로젝트명/twitter” 가 된다.

데이타 플로우 프로젝트 생성

큐까지 데이타를 읽어드렸으면, 이 데이타를 처리할 데이타 플로우 파이프라인을 구현한다.

이클립스에서 데이타 플로우 파이프라인 프로젝트를 생성하자. 프로젝트 생성은 앞장의 “데이타 플로우 개발환경 설정" 부분을 참고하기 바란다. (http://bcho.tistory.com/1128)


프로젝트가 생성되었으면, 이 프로젝트에서 사용할 의존성 라이브러리들을 메이븐 (maven) 빌드 스크립트인 pom.xml에 추가해준다.

추가해야 하는 API는 JSON 파싱을 위한 javax.json-api와, javax.json 그리고 구글의 자연서 분석 API를 호출하기 위한 google-api-client와 google-api-service-language 모듈이다.


다음 코드 블럭을 <dependencies> 엘리먼트 아래 하부 엘리먼트로 추가해준다


   <dependency>

   <groupId>javax.json</groupId>

   <artifactId>javax.json-api</artifactId>

   <scope>provided</scope>

   <version>1.0</version>

</dependency>

<dependency>

   <groupId>org.glassfish</groupId>

   <artifactId>javax.json</artifactId>

   <version>1.0.4</version>

</dependency>

<!-- NL API dependency -->

<dependency>

     <groupId>com.google.apis</groupId>

     <artifactId>google-api-services-language</artifactId>

     <version>v1beta1-rev7-1.22.0</version>

   </dependency>

   <dependency>

     <groupId>com.google.api-client</groupId>

     <artifactId>google-api-client</artifactId>

     <version>1.22.0</version>

   </dependency>


데이타 플로우 코드 작성

전체 파이프라인 흐름

파이프라인 코드 작성에 앞서서 전체 파이프라인 흐름을 살펴보자

전체 흐름은 다음과 같다.


  1. Read From PubSub
    PubSub의 “twitter” 큐에서 JSON 형태의 트위터 메세지를 읽는다.

  2. Parse Twitter
    트위터 JSON 메세지를 파싱한 후, 전체 메세지에서 트윗 메세지를 저장하고 있는 “text” 필드와 언어셋을 정의하고 있는 “lang” 필드만 추출한다.
    자연어 분석 API가 아직 영어, 스페인어, 일본어만 지원하기 때문에, 이 예제에서는 영어로 트윗만 추출하도록 한다.

  3. NL Processing
    앞에서 추출한 트윗 메세지를 구글의 자연어 분석 API에 분석을 요청하여 명사와 형용사만 추출해낸다.

  4. 명사 처리 파이프라인
    다양한 처리 방식을 보여주기 위해서, 이 예제에서는 하나의 데이타 스트림을 분기 처리하여 두개의 데이타 파이프라인에서 처리하는 방식으로 구현하였다. 명사 처리 파이프라인은 다음과 같은 단계를 거친다.

    1. Noun Filter
      명사와 형용사 리스트로 들어온 데이타 중에서 명사만 필터링 한다.

    2. Window 적용
      고정 크기 윈도우 (Fixed Window) 30초를 적용하여, 30초 단위로 데이타를 분석하도록 한다.

    3. Count.PerElement
      명사 단어와, 각 단어별 발생횟 수를 30초 단위로 모아서 카운트 한다.

    4. Noun Formating
      카운트된 결과를 빅쿼리에 쓰도록, [윈도우 시작 시간,명사 단어, 발생횟수] 형태의 빅쿼리 ROW(행) 데이타 타입으로 포매팅 한다.

    5. Write Noun Count to BQ
      포매팅 된 데이타를 빅쿼리에 쓴다.

  5. 형용사 처리 파이프라인
    형용사를 처리하는 파이프라인도 내용은 명사를 처리한 파이프라인과 다르지 않고 동일하게 다음과 같은 순서를 따른다.

    1. Adj Filter

    2. Window 적용

    3. Count.PerElement

    4. Adj Formating

    5. Write Adj Count to BQ

빅쿼리 데이타 구조

빅쿼리에는 두개의 테이블에 데이타를 나눠서 저장하였다.

명사와 형용사 테이블로 각각의 테이블 명과 구조는 다음과 같다.


명사 테이블 : noun

필드명

데이타 타입

date

TIMESTAMP

noun

STRING

count

INTEGER


형용사 테이블 : adj

필드명

데이타 타입

date

TIMESTAMP

adj

STRING

count

INTEGER

자연어 분석 클래스 작성

전체 데이타 흐름과 저장 구조가 이해되었으면, 파이프라인 코드 작성에 앞서서 자연어 처리 API를 호출하는 로직을 만들어보자


우리가 사용할 API는 String으로 문자열을 주면 다음과 같이 NLAnalyzeVO 객체로 분석 결과를 리턴해주는 코드이다.


package com.terry.nl;


import java.util.ArrayList;

import java.util.List;


public class NLAnalyzeVO {

List<String> nouns = new ArrayList<String>();

List<String> adjs = new ArrayList<String>();

List<String> emoticons = new ArrayList<String>();

float sentimental;


public List<String> getNouns() {

return nouns;

}


public List<String> getAdjs() {

return adjs;

}


public List<String> getEmoticons() {

return emoticons;

}


public float getSentimental() {

return sentimental;

}


public void setSentimental(float sentimental) {

this.sentimental = sentimental;

}

public void addNouns(String n){

nouns.add(n);

}

public void addAdj(String a){

adjs.add(a);

}

public void addEmoticons(String e){

emoticons.add(e);

}

}

<NLAnalyzeVO.java>


분석 결과로는 List<String> 타입으로 명사들의 목록을 nouns 로, 형용사들의 목록을 adj로 리턴해준다. float형으로 sentimental 이라는 필드에는 입력된 문장의 감정도를 리턴하도록 되어 있다. 음수값일 때는 부정적, 양수값일 경우에는 긍정을 의미한다.

VO안에는 List<String> emoticons 라는 필드가 있는데, 이는 트위터 메세지 내의 이모티콘을 추출하여 저장하기 위한 필드인데, 이 예제에서는 사용하지 않으니 신경 쓰지 않아도 된다.


package com.terry.nl;


import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;

import com.google.api.client.http.HttpRequest;

import com.google.api.client.http.HttpRequestInitializer;

import com.google.api.client.json.JsonFactory;

import com.google.api.client.json.jackson2.JacksonFactory;

import com.google.api.services.language.v1beta1.CloudNaturalLanguageAPI;

import com.google.api.services.language.v1beta1.CloudNaturalLanguageAPI.Documents.AnnotateText;

import com.google.api.services.language.v1beta1.CloudNaturalLanguageAPIScopes;

import com.google.api.services.language.v1beta1.model.AnalyzeEntitiesRequest;

import com.google.api.services.language.v1beta1.model.AnalyzeEntitiesResponse;

import com.google.api.services.language.v1beta1.model.AnalyzeSentimentRequest;

import com.google.api.services.language.v1beta1.model.AnalyzeSentimentResponse;

import com.google.api.services.language.v1beta1.model.AnnotateTextRequest;

import com.google.api.services.language.v1beta1.model.AnnotateTextResponse;

import com.google.api.services.language.v1beta1.model.Document;

import com.google.api.services.language.v1beta1.model.Entity;

import com.google.api.services.language.v1beta1.model.Features;

import com.google.api.services.language.v1beta1.model.Sentiment;

import com.google.api.services.language.v1beta1.model.Token;


import java.io.IOException;

import java.io.PrintStream;

import java.security.GeneralSecurityException;

import java.util.List;

import java.util.Map;


/**

*

* Google Cloud NL API wrapper

*/



@SuppressWarnings("serial")

public class NLAnalyze {


public static NLAnalyze getInstance() throws IOException,GeneralSecurityException {


return new NLAnalyze(getLanguageService());

}


public NLAnalyzeVO analyze(String text) throws IOException, GeneralSecurityException{

Sentiment  s = analyzeSentiment(text);

List <Token> tokens = analyzeSyntax(text);

NLAnalyzeVO vo = new NLAnalyzeVO();


for(Token token:tokens){

String tag = token.getPartOfSpeech().getTag();

String word = token.getText().getContent();


if(tag.equals("NOUN")) vo.addNouns(word);

else if(tag.equals("ADJ")) vo.addAdj(word);

}


vo.setSentimental(s.getPolarity());


return vo;

}



/**

* Be sure to specify the name of your application. If the application name is {@code null} or

* blank, the application will log a warning. Suggested format is "MyCompany-ProductName/1.0".

*/

private static final String APPLICATION_NAME = "Google-LanguagAPISample/1.0";


/**

* Connects to the Natural Language API using Application Default Credentials.

*/

public static CloudNaturalLanguageAPI getLanguageService()

throws IOException, GeneralSecurityException {

GoogleCredential credential =

GoogleCredential.getApplicationDefault().createScoped(CloudNaturalLanguageAPIScopes.all());

JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();

return new CloudNaturalLanguageAPI.Builder(

GoogleNetHttpTransport.newTrustedTransport(),

jsonFactory, new HttpRequestInitializer() {

@Override

public void initialize(HttpRequest request) throws IOException {

credential.initialize(request);

}

})

.setApplicationName(APPLICATION_NAME)

.build();

}


private final CloudNaturalLanguageAPI languageApi;


/**

* Constructs a {@link Analyze} which connects to the Cloud Natural Language API.

*/

public NLAnalyze(CloudNaturalLanguageAPI languageApi) {

this.languageApi = languageApi;

}


public List<Token> analyzeSyntax(String text) throws IOException{

AnnotateTextRequest request =

new AnnotateTextRequest()

.setDocument(new Document().setContent(text).setType("PLAIN_TEXT"))

.setFeatures(new Features().setExtractSyntax(true))

.setEncodingType("UTF16");

AnnotateText analyze =

languageApi.documents().annotateText(request);


AnnotateTextResponse response = analyze.execute();


return response.getTokens();


}

/**

* Gets {@link Sentiment} from the string {@code text}.

*/

public Sentiment analyzeSentiment(String text) throws IOException {

AnalyzeSentimentRequest request =

new AnalyzeSentimentRequest()

.setDocument(new Document().setContent(text).setType("PLAIN_TEXT"));

CloudNaturalLanguageAPI.Documents.AnalyzeSentiment analyze =

languageApi.documents().analyzeSentiment(request);


AnalyzeSentimentResponse response = analyze.execute();

return response.getDocumentSentiment();

}


}


<NLAnalyze.java>


코드 상의 주요 부분을 살펴보자

public NLAnalyzeVO analyze(String text)

메서느가 주요 메서드로, 트윗 문자열을 text 인자로 넘겨주면 분석 결과를 NLAnalyzeVO로 리턴한다.

이 메서드 안에서는 두개의 메서드를 호출하는데, analyzeSentiment(text) 와, analyzeSyntax(text)

를 두개 호출한다.

analyzeSentiment(text) 메서드는 text 를 넣으면 float 타입으로 감정도인 Sentinetal 지수를 리턴한다.

analyzeSyntax(text)는 구문을 분석하여, 명사,형용사,접속사,조사 등과 단어간의 의존 관계등을 분석해서 리턴해주는데, Token 이라는 데이타 타입의 리스트 형태로 다음과 같이 리턴한다.

List <Token> tokens = analyzeSyntax(text);


여기서 단어의 형(명사,형용사)는 token에서 tag 라는 필드를 통해서 리턴되는데, 우리가 필요한것은 명사와 형용사만 필요하기 때문에, tag가 NOUN (명사)와 ADJ (형용사)로 된 단어만 추출해서 NLAnalyzeVO 객체에 넣어서 리턴한다. (태그의 종류는 https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/annotateText#Tag ) 를 참고하기 바란다.


중요

이 코드를 이용해서 구글 클라우드의 자연어 분석 API를 호출할때 그러면 API 인증은 어떻게 할까? 보통 구글 클라우드 콘솔에서 다운 받는 서비스 어카운트 키 (Service Account Key) JSON 파일을 사용하는데, 구글 자연어 분석 API를 호출하기 위해서도 서비스 어카운트 키가 필요하다.

이 키를 콘솔에서 다운로드 받은 후에, GOOGLE_APPLICATION_CREDENTIALS 라는 환경 변수에 서비스 어카운트 키의 경로를 지정해주면 된다.


예) export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your-project-credentials.json


자연어 분석 클래스를 다 만들었으면 테스트 코드를 만들어서 테스트를 해보자.

다음은 JUnit 4.X를 이용한 간단한 테스트 코드 이다.


package com.terry.nl.test;


import static org.junit.Assert.*;


import java.io.IOException;

import java.security.GeneralSecurityException;

import java.util.List;


import org.junit.Test;


import com.terry.nl.NLAnalyze;

import com.terry.nl.NLAnalyzeVO;


public class NLAnalyzeTest {


@Test

public void test() {

try {

NLAnalyze instance = NLAnalyze.getInstance();

String text="Larry Page, Google's co-founder, once described the 'perfect search engine' as something that 'understands exactly what you mean and gives you back exactly what you want.'";

NLAnalyzeVO vo = instance.analyze(text);

List<String> nouns = vo.getNouns();

List<String> adjs = vo.getAdjs();

System.out.println("### NOUNS");

for(String noun:nouns){

System.out.println(noun);

}

System.out.println("### ADJS");

for(String adj:adjs){

System.out.println(adj);

}

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

fail("API call error");

} catch (GeneralSecurityException e) {

// TODO Auto-generated catch block

e.printStackTrace();

fail("Security exception");

}

}


}


"Larry Page, Google's co-founder, once described the 'perfect search engine' as something that 'understands exactly what you mean and gives you back exactly what you want.'" 문자열을 분석하여,  명사와 형용사를 추출하여 다음과 같이 결과를 출력해준다.

### NOUNS

Larry

Page

Google

co-founder

search

engine

something

### ADJS

perfect

파이프라인 코드 작성

이제 메인 파이프라인 개발을 위한 준비가 다 되었다. 이제 TwitterPipeline 이라는 이름으로 파이프라인을 구현해보자. 전체 코드는 다음과 같다.

package com.terry.dataflow;


import java.io.IOException;

import java.io.StringReader;

import java.security.GeneralSecurityException;

import java.util.ArrayList;

import java.util.List;


import javax.json.Json;

import javax.json.JsonObject;

import javax.json.JsonReader;


import org.joda.time.DateTime;

import org.joda.time.Duration;

import org.joda.time.Instant;

import org.joda.time.format.DateTimeFormat;

import org.joda.time.format.DateTimeFormatter;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;


import com.google.api.services.bigquery.model.TableFieldSchema;

import com.google.api.services.bigquery.model.TableRow;

import com.google.api.services.bigquery.model.TableSchema;

import com.google.cloud.dataflow.sdk.Pipeline;

import com.google.cloud.dataflow.sdk.io.BigQueryIO;

import com.google.cloud.dataflow.sdk.io.PubsubIO;

import com.google.cloud.dataflow.sdk.options.PipelineOptionsFactory;

import com.google.cloud.dataflow.sdk.transforms.Count;

import com.google.cloud.dataflow.sdk.transforms.Create;

import com.google.cloud.dataflow.sdk.transforms.DoFn;

import com.google.cloud.dataflow.sdk.transforms.ParDo;

import com.google.cloud.dataflow.sdk.transforms.ParDo.Bound;

import com.google.cloud.dataflow.sdk.transforms.windowing.FixedWindows;

import com.google.cloud.dataflow.sdk.transforms.windowing.IntervalWindow;

import com.google.cloud.dataflow.sdk.transforms.windowing.Window;

import com.google.cloud.dataflow.sdk.values.KV;

import com.terry.nl.NLAnalyze;

import com.terry.nl.NLAnalyzeVO;


import com.google.cloud.dataflow.sdk.values.PCollection;


/**

* A starter example for writing Google Cloud Dataflow programs.

*

* <p>The example takes two strings, converts them to their upper-case

* representation and logs them.

*

* <p>To run this starter example locally using DirectPipelineRunner, just

* execute it without any additional parameters from your favorite development

* environment.

*

* <p>To run this starter example using managed resource in Google Cloud

* Platform, you should specify the following command-line options:

*   --project=<YOUR_PROJECT_ID>

*   --stagingLocation=<STAGING_LOCATION_IN_CLOUD_STORAGE>

*   --runner=BlockingDataflowPipelineRunner

*/

public class TwitterPipeline {

private static final Logger LOG = LoggerFactory.getLogger(TwitterPipeline.class);

private static final String NOWN_TABLE=

"useful-hour-138023:twitter.noun";

private static final String ADJ_TABLE=