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


Archive»


 

'튜토리얼'에 해당되는 글 121

  1. 2018.01.01 Apache Beam (Dataflow)를 이용하여, 이미지 파일을 tfrecord로 컨버팅 하기
  2. 2017.12.11 구글 스택드라이버를 이용한 애플리케이션 로그 모니터링
  3. 2017.11.13 차원 감소(Dimension reduction) 와 PCA 분석
  4. 2017.10.18 분류모델 (Classification)의 성능 평가 (1)
  5. 2017.10.13 클러스터링 #3 - DBSCAN (밀도 기반 클러스터링)
  6. 2017.10.11 클러스터링 #2 - Hierarchical clustering (계층 분석)
  7. 2017.10.09 클러스터링 #1 - KMeans
  8. 2017.09.27 오토인코더를 이용한 비정상 거래 검출 모델의 구현 #4 - 오토인코더 기반의 신용카드 이상거래 검출코드와 분석 결과 (1)
  9. 2017.09.20 오토인코더를 이용한 비정상 거래 검출 모델의 구현 #3 - 데이타 전처리 (1)
  10. 2017.09.18 오토인코더를 이용한 비정상 거래 검출 모델의 구현 #2 - MNIST 오토 인코더 샘플 (1)
  11. 2017.09.11 오토 인코더를 이용한 비정상 거래 검출 모델의 구현 #1 (1)
  12. 2017.09.10 텐서플로우 하이레벨 API를 Estimator를 이용한 모델 정의 방법
  13. 2017.08.30 Tensorflow Object Detection API를 이용한 물체 인식 #3-얼굴은 학습시켜보자
  14. 2017.08.21 Tensorflow Object Detection API를 이용한 물체 인식 #2-동물 사진을 학습 시켜보자 (1)
  15. 2017.08.15 얼굴 인식 모델을 만들어보자 #6 - CloudML을 이용하여 예측하기
  16. 2017.08.10 텐서플로우 트레이닝 데이타 포맷인 *.tfrecord 파일 읽고 쓰기 (1)
  17. 2017.07.31 얼굴 인식 모델을 만들어보자 #5-학습된 모델을 Export 하기 (1)
  18. 2017.07.20 Wide and deep network 모델 활용하기
  19. 2017.07.15 데이타 워크플로우 관리를 위한 Apache Airflow #1 - 소개
  20. 2017.06.15 연예인 얼굴 인식 모델을 만들어보자 - #2. CNN 모델을 만들고 학습시켜 보자 (11)
 

Apache Beam (Dataflow)를 이용하여, 이미지 파일을 tfrecord로 컨버팅 하기


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



개요

텐서플로우 학습에 있어서 데이타 포맷은 학습의 성능을 결정 짓는 중요한 요인중의 하나이다. 특히 이미지 파일의 경우 이미지 목록과 이미지 파일이 분리되어 있어서 텐서플로우에서 학습시 이미지 목록을 읽으면서, 거기에 있는 이미지 파일을 매번 읽어야 하기 때문에, 코딩이 다소 지저분해지고,IO 성능이 떨어질 수 있다

텐서플로우에서는 이러한 학습 데이타를 쉽게 읽을 수 있도록 tfrecord (http://bcho.tistory.com/1190)라는 파일 포맷을 지원한다.


이 글에서는 이미지 데이타를 읽어서 tfrecord 로 컨버팅하는 방법을 설명하며, 분산 데이타 처리 프레임웍인 오픈소스 Apache Beam을 기준으로 설명하나, tfrecord 변환 부분은 Apache Beam과 의존성이 없이 사용이 가능하기 때문에, 필요한 부분만 참고해도 된다. 이 Apache Beam을 구글의 Apache Beam 런타임 (매니지드 서비스)인 구글 클라우드의 Dataflow를 이용하여, 클러스터를 이용하여 빠르게 데이타를 처리하는 방법에 대해서 알아보도록 한다.


전체 코드는 https://github.com/bwcho75/cifar-10/blob/master/pre-processing/4.%20Convert%20Pickle%20file%20to%20TFRecord%20by%20using%20Apache%20Beam.ipynb 에 있다.


이 코드는 CIFAR-10 이미지 데이타를 Apache Beam 오픈 소스를 이용하여, 텐서플로우 학습용 데이타 포맷인  tfrecord 형태로 변환 해주는 코드이다.


Apache Beam은 데이타 처리를 위한 프레임웍으로, 구글 클라우드 상에서 실행하거나 또는 개인 PC나 Spark 클러스터상 여러 환경에서 실행이 가능하며, 구글 클라우드 상에서 실행할 경우 오토스케일링이나 그래프 최적화 기능등으로 최적화된 성능을 낼 수 있다.


CIFAR-10 데이타 셋은 32x32 PNG 이미지 60,000개로 구성된 데이타 셋으로 해당 코드 실행시 최적화가 되지 않은 상태에서 약 16분 정도의 처리 시간이 소요된다. 이 중 6분 정도는 Apache Beam 코드를 구글 클라우드로 업로드 하는데 소요되는 시간이고 실제 처리시간은 10분정도가 소요된다. 전처리 과정에 Apache Beam을 사용하기 전에 고려해야 할 요소는 다음과 같다.

  • 데이타가 아주 많아서 전처리 시간이 수시간 이상 소요될 경우 Apache Beam + Google Cloud를 고려하여 여러 머신에서 동시에 처리하여 빠른 시간내에 수행되도록 할 수 있다.

  • 데이타가 그다지 많지 않고 싱글 머신에서 멀티 쓰레드로 처리를 원할 경우에는 Apache Beam으로 멀티 쓰레드 기반의 병렬 처리를 하는 방안을 고려할 수 있다. 이 경우 클라우드에 대한 의존성을 줄일 수 있다.

  • 다른 대안으로는 Spark/Hadoop 등의 오픈소스를 사용하여, On Prem에서 여러 머신을 이용하여 전처리 하는 방안을 고려할 수 있다.

여기서는 아주 많은 대량의 이미지 데이타에 대한 처리를 하는 것을 시나리오로 가정하였다.

전처리 파이프라인

Apache Beam을 이용한 데이타 전처리 파이프라인의 구조는 다음과 같다.

이미지 파일 준비

CIFAR-10 데이타셋 원본은 이미지 파일 형태가 아니라 PICKLE이라는 파일 포맷으로 되어 있기 때문에,  실제 개발 환경에서는 원본데이타가 이미지인것으로 가정하기 위해서 https://github.com/bwcho75/cifar-10/tree/master/pre-processing 의 1~2번 코드를 통해서 Pickle 파일을 이미지 파일로 변경하고, *.csv 파일에 {파일명},{레이블} 형태로 인덱스 데이타를 생성하였다.

생성된 이미지 파일과 *.csv 파일은 gsutil 명령어를 이용하여 Google Cloud Storage (aka GCS)에 업로드 하였다. 업로드 명령은 https://github.com/bwcho75/cifar-10/blob/master/pre-processing/2.%20Convert%20CIFAR-10%20Pickle%20files%20to%20image%20file.ipynb 에 설명되어 있다.


전처리 파이프라인의 구조

Apache Beam으로 구현된 파이프라인의 구조는 다음과 같다.


1. TextIO의 ReadFromText로 CSV 파일에서 한 라인 단위로 문자열을 읽는다.

2. parseLine에서 라인을 ,로 구분하여 filename과 label을 추출한다.

3. readImage 에서 filename을 가지고, 이미지 파일을 읽어서, binary array 형태로 변환한다.

4. TFExampleFromImageDoFn에서 이미지 바이너리와 label을 가지고 TFRecord 데이타형인 TFExample 형태로 변환한다.

5. 마지막으로 TFRecordIOWriter를 통해서 TFExample을 *.tfrecord 파일에 쓴다.

코드 주요 부분 설명

환경 설정 부분

이 코드는 구글 클라우드와 로컬 환경 양쪽에서 모두 실행이 가능하도록 구현되었다.

SRC_DIR_DEV는 로컬환경에서 이미지와 CSV 파일이 위치한 위치이고, DES_DIR_DEV는 로컬환경에서 tfrecord 파일이 써지는 위치이다.

구글 클라우드에서 실행할 경우 파일 저장소를  GCS (Google Cloud Storage)를 사용한다. DES_BUCKET은 GCS 버킷 이름이다. 코드 실행전에 반드시 구글 클라우드 콘솔에서 GCS 버킷을 생성하기 바란다.  SRC_DIR_PRD와 DES_DIR_PRD는 GCS 버킷내의 각각 image,csv 파일의 경로와 tfrecord 파일이 써질 경로 이다. 이 경로에 맞춰서 구글 클라우드 콘솔에서 디렉토리를 먼저 생성해 놓기를 바란다.




PROJECT는 구글 클라우드 프로젝트 명이고, 마지막으로 DEV_MODE가 True이면 로컬에서 수행이되고 False이면 구글 클라우드에서 실행하도록 하는 환경 변수이다.

의존성 설정 부분

로컬에서 실행할 경우필요한  파이썬 라이브러리가 이미 설치되어야 있어야 한다.

만약에 구글 클라우드에서 실행할 경우 이 Apache Beam 코드가 사용하는 파이썬 모듈을 명시적으로 정의해놔야 한다. 클라우드에서 실행시에는 Apache Beam 코드만 업로드가 되기 때문에(의존성 라이브러리를 같이 업로드 하는 방법도 있는데, 이는 추후에 설명한다.), 의존성 라이브는 구글 클라우드에서 Dataflow 실행시 자동으로 설치할 수 있도록 할 수 있는데, 이를 위해서는 requirements.txt 파일에 사용하는 파이썬 모듈들을 정의해줘야 한다. 다음은 requirements.txt에 의존성이 있는 파이썬 모듈등을 정의하고 저장하는 부분이다.


Apache Beam 코드

Apache Beam의 코드 부분은 크게 복잡하지 않기 때문에 주요 부분만 설명하도록 한다.

Service account 설정

Apache Beam 코드를 구글 클라우드에서 실행하기 위해서는 코드 실행에 대한 권한을 줘야 한다. 구글 클라우드에서는 사용자가 아니라 애플리케이션에 권한을 부여하는 방법이 있는데, Service account라는 것을 사용한다. Service account는 json 파일로 실행 가능한 권한을 정의하고 있다.

Service account 파일을 생성하는 방법은 http://bcho.tistory.com/1166 를 참고하기 바란다.

Service account 파일이 생성되었으면, 이 파일을 적용해야 하는데 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 Service account  파일의 경로를 정의해주면 된다. 파이썬 환경에서 환경 변수를 설정하는 방법은 os.envorin[‘환경변수명']에 환경 변수 값을 지정해주면 된다.

Jobname 설정

구글 클라우드에서 Apache Beam 코드를 실행하면, 하나의 실행이 하나의 Job으로 생성되는데, 이 Job을 구별하기 위해서 Job 마다 ID 를 설정할 수 있다. 아래는 Job ID를 ‘cifar-10’+시간 형태로 지정하는 부분이다


환경 설정

Apache Beam 코드를 구글 클라우드에서 실행하기 위해서는 몇가지 환경을 지정해줘야 한다.


  • staging_location은 클라우드 상에서 실행시 Apache Beam 코드등이 저장되는 위치이다. GCS 버킷 아래 /staging이라는 디렉토리로 지정했는데, 실행 전에 반드시 버킷아래 디렉토리를 생성하기 바란다.

  • temp_location은 기타 실행중 필요한 파일이 저장되는 위치이다. 실행 전에 반드시 버킷아래 디렉토리를 생성하기 바란다.

  • zone은 dataflow worker가 실행되는 존으로 여기서는 asia-northeast1-c  (일본 리전의 c 존)으로 지정하였다.


DEV_MODE 에 따른 환경 설정

로컬 환경이나 클라우드 환경에서 실행이냐에 따라서 환경 변수 설정이 다소 달라져야 한다.


디렉토리 경로를 바꿔서 지정해야 하고, 중요한것은 RUNNER인데, 로컬에서 실행하기 위해서는 DirectRunner를 구글 클라우드 DataFlow 서비스를 사용하기 위해서는 DataflowRunner를 사용하면 된다.


readImage 부분

Read Image는 이미지 파일을 읽어서 byte[] 로 리턴하는 부분인데, 로컬 환경이냐, 클라우드 환경이냐에 따라서 동작 방식이 다소 다르다.

클라우드 환경에서는 이미지 파일이 GCS에 저장되어 있기 때문에 파이썬의 일반 파일 open 명령등을 사용할 수 없다.

그래서 클라우드 환경에서 동작할 경우에는 GCS에서 파일을 읽어서 Worker의 로컬 디스크에 복사를 해놓고 이미지를 읽어서 byte[]로 변환한 후에, 해당 파일을 지우는 방식을 사용한다.


아래 코드에서 보면 DEV_MODE가 False 인경우 GCS에서 파일을 읽어서 로컬에 저장하는 코드가 있다.


storageClient는 GCS 클라이언트이고 bucket 을 얻어온후, bucket에서 파일을 get_blob 명령어를 이용하여 경로를 저장하여 blob.download_to_file을 이용하여 로컬 파일에 저장하였다.

실행

코드 작성이 끝났으면 실행을 한다. 실행 상태는 구글 클라우드 콘솔의 Dataflow  메뉴에서 확인이 가능하다.

아래와 같이 실행중인 그리고 실행이 끝난 Job 리스트들이 출력된다.




코드 실행중에, 파이프라인 실행 상황 디테일을 Job 을 선택하면 볼 수 있다.


여기서 주목할만한 점은 우측 그래프인데, 우측 그래프는 Worker의 수를 나타낸다. 초기에 1대로 시작했다가 오토 스케일링에 의해서 9대 까지 증가한것을 볼 수 있다.

처음 실행이었기 때문에 적정한 인스턴스수를 몰랐기 때문에 디폴트로 1로 시작하고 오토스케일링을 하도록 했지만, 어느정도 테스트를 한후에 적정 인스턴수를 알면 오토 스케일링을 기다릴 필요없이 디폴트 인스턴스 수를 알면 처음부터 그 수만큼 인스턴스 수로 시작하도록 하면 실행 시간을 줄일 수 있다.

만약에 파이프라인 실행시 에러가 나면 우측 상단에 LOGS 버튼을 누르면 상세 로그를 볼 수 있다.


아래 그림은 파이프라인 실행이 실패한 예에서 STACK TRACES를 통해서 에러 내용을 확인하는 화면이다.



해당 로그를 클릭하면 Stack Driver (구글의 모니터링 툴)의 Error Reporting 시스템 화면으로 이동하게 된다.

여기서 디테일한 로그를 볼 수 있다.

아래 화면을 보면 ReadImage 단계에서 file_path라는 변수명을 찾을 수 없어서 나는 에러를 확인할 수 있다.


TFRecord 파일 검증

파이프라인 실행이 끝나면, GCS 버킷에 tfrecord 파일이 생성된것을 확인할 수 있다.


해당 파일을 클릭하면 다운로드 받을 수 있다.

노트북 아래 코드 부분이 TFRecord를 읽어서 확인하는 부분이다. 노트북에서 tfrecord 파일의 경로를 다운로드 받은 경로로 변경하고 실행을 하면 파일이 제대로 읽히는 지 확인할 수 있다.


파일 경로 부분은 코드상에서 다음과 같다.



정상적으로 실행이 된 경우, 다음과 같이 tfrecord에서 읽은 이미지와 라벨값이 출력됨을 확인할 수 있다.


라벨 값은 Label 줄에 values 부분에 출력된다. 위의 그림에서는 순서대로 라벨 값이 4와 2가 된다.



구글 스택드라이버를 이용한 애플리케이션 로그 모니터링

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

스택드라이버 소개

스택드라이버는 구글 클라우드에서 서비스로 제공되는 시스템 로그 및 모니터링 시스템이다. CPU,메모리사용량과 같은 하드웨어에 대한 정보에서 부터 웹서버나 OS와 같은 미들웨어 및 애플리케이션 로그를 수집, 검색 및 분석할 수 있으며, 여러 오픈 소스 (MongoDB, CouchDB, Redis - https://cloud.google.com/monitoring/agent/plugins/ )등에 대한 모니터링도 가능하다.

구글 클라우드 뿐 아니라, AWS에 대한 모니터링을 통합으로 지원하는 등, 상당히 많은 기능을 가지고 있다.

이 글에서는 스택드라이버를 이용하여 애플리케이션 로그를 수집하고 이를 분석하는 방법에 대해서 설명하고자 한다.

자바를 기반으로 애플리케이션 로깅을 설명한다. 자바 애플리케이션에서 스택드라이버로 로그를 남기는 방법은 여러가지가 있으나, 일반적으로 자바 프로그래밍 언어에서 많이 사용하는 로깅 프레임웍은 SL4J 를 이용한 로깅과, 스택드라이버 SDK를 이용하여 JSON 형태로 로그를 저장하는 방법에 대해서 알아보도록 한다.

API 인증

스택 드라이버를 사용하기 위해서는 로그 API에 대한 인증이 필요하다. 인증에는 여러가지 방법이 있는다. 사용이 쉬운 방법을 설명한다.

로컬 환경 또는 타 환경에서 인증

로컬 개발환경이나 클라우드에서 인증을 하는 방법은 서비스 어카운트 (Service Account)를 사용하는 방법이 있다. 서비스 어카운트는 구글 클라우드 콘솔에서  IAM 메뉴에서 생성할 수 있다. 서비스 어카운트 메뉴를 아래와 같이 선택한 다음.


상단 메뉴에서 Create Service Account  버튼을 누르고 서비스 어카운트 생성한다.


서비스 어카운트에는 서비스 어카운트의 권한을 설정할 수 있는데, Project Owner로 설정하면 모든 권한을 다 가질 수 있고, 여기서는 로깅 권한만을 줄것이기 때문에, Logs Writer 권한만을 지정한다.


계정 생성을 하면 json 파일이 다운로드 된다.

이 파일은 환경 변수 GOOGLE_APPLICATION_CREDENTIALS 에 파일 경로를 지정해주면 된다.

예시 $ export GOOGLE_APPLICATION_CREDENTIALS=/Users/terrycho/keys/terrycho-sandbox-projectowner.json




구글 클라우드 VM 내에서 인증

구글 클라우드 VM내에서 자바 코드를 실행할 경우 VM 자체에 API 접근 권한을 부여할 수 있다. 보통 운영환경에서는 이 방법이 권장된다.

아래와 같이 VM 생성시 “Identity and API access” 에서 API 접근 권한을 주면 된다. Set access for each API를 써서 Logging write 권한만을 줄 수 있고, 아니면 Allow full access to all Cloud APIs 를 이용해서 전체 API에 대한 권한을 줄 수 도 있다.




SL4J를 이용한 로깅

sl4j를 이용한 로깅은, 기존의 sl4j 로거를 그대로 사용하기 때문에 코드 변환이 거의 없고, 단지 maven 에서 라이브러리 의존성을 스택드라이버 로거로만 변경해주면 되기 때문에 별도의 학습이 필요없고 사용법이 단순하다는 장점이 있다. sl4j 로깅은 단순하다.

의존성 추가

먼저  pom.xml 에 아래와 같은 의존성을 추가 한다.

<dependency>
<groupId>com.google.cloud</groupId>       <artifactId>google-cloud-logging-logback</artifactId> <version>0.30.0-alpha</version>
</dependency>

logback.xml

다음 필요에 따라서 sl4j에 대한 설정을 위해서 logback.xml 을 추가 설정할 수 있다. 여기서 로깅 레벨등을 지정할 수 있으나, sl4j에 대한 내용이기 때문에 별도로 설명하지는 않는다.

자주 실수 하는 부분이 logback.xml은 클래스 패스의 경로내에 들어가 있어야 하는데 다른 방법으로는 자바 옵션으로 -Dlogback.configurationFile 으로 logback.xml 경로를 설정하면 된다.



코드

코드를 보자

package com.google.example.stackdriver;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;



public class App {

 private static final Logger logger = LoggerFactory.getLogger(App.class);

 

 public static void main(String[] args) {

   logger.info("My Hello Log4j");

 }

}


코드는 간단하다. logger를 선언한 후에, .info, .error, .warning 등의 메서드로 텍스트 문자열을 남기면 된다.


자바 로거 연동은 sl4j이외에도 java.util.logging 도 연동이 가능하다. 자세한 내용은 https://cloud.google.com/logging/docs/setup/java 를 참고하기 바란다.

Logger를 이용한 로깅

sl4j는 사용이 간편한 반면에 텍스트 문자열로 로깅이 되기 때문에, 구조화된 정보 (JSON)이나 여러 필드를 가지는 로그를 남기기가 쉽지 않다는 단점을 가지고 있다. 스택드라이버 전용 SDK를 사용하면, JSON등 다양한 포맷으로 로그를 쉽게 남길 수 있다. (sl4j의 경우에도 LoggingEnahncer를 사용하면 가능하기는 하다)


전체 코드는 다음과 같다.


package com.google.example.stackdriver;

import com.google.cloud.MonitoredResource;

import com.google.cloud.logging.LogEntry;

import com.google.cloud.logging.Logging;

import com.google.cloud.logging.LoggingOptions;

import com.google.cloud.logging.Payload.JsonPayload;

import com.google.cloud.logging.Payload.StringPayload;

import com.google.cloud.logging.Severity;

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;


public class LogWithLabel {

 //https://cloud.google.com/logging/docs/reference/libraries

 final static String LOG_NAME="terry-tutorial";

 /** Expects a new or existing Stackdriver log name as the first argument.*/

 public static void main(String... args) throws Exception {


   // Instantiates a client

   Logging logging = LoggingOptions.getDefaultInstance().getService();


   // The data to write to the log

   String text = "Hello, world!";

   Map<String, Object> jsonMap = new HashMap<String, Object>();

   jsonMap.put("elapsedtime", 11);

   

   for(int i=0;i<1000;i++){

    jsonMap.put("count", i);

   LogEntry entry

    //= LogEntry.newBuilder(StringPayload.of(text))

    // 한페이로드만 사용이 가능함. 오버라이드됨.

    = LogEntry.newBuilder(JsonPayload.of(jsonMap))

.setSeverity(Severity.ERROR)

       .setLogName(LOG_NAME)

       .setResource(MonitoredResource.newBuilder("global").build())

       .addLabel("instancename", "instance-1")

       .build();

   // Writes the log entry asynchronously

   logging.write(Collections.singleton(entry));

   }


   System.out.printf("Logged: %s%n", text);

 }

}


먼저 Logging 객체를 가지고 와야 한다. 별도의 설정 없이 다음과 같이 설정하면 되고, 프로젝트 및 인증은 앞에서 설정한 Service Account 파일의 정보를 그대로 사용한다.

Logging logging = LoggingOptions.getDefaultInstance().getService();


이 예제는 JSON 포맷으로 데이타를 저장하는 방법인데, 단순하게 1 레이어의 JSON을 저장하도록 하였다. Map을 이용하여 jsonMap을 정의하고, put을 이용하여 key, value 값을 저장한다.


   String text = "Hello, world!";

   Map<String, Object> jsonMap = new HashMap<String, Object>();

   jsonMap.put("elapsedtime", 11);


다음 로그를 저장하기 위해서는 LogEntry 객체를 이용해야 하는데, LogEntry는 LogEntry.newBuilder(PayLoad)를 이용하여 생성한다. Text 로그를 저장하는 TextPayLoad를 사용하거나 다른 페이로드도 있지만 여기서는 JsonPayLoad를 사용하였다.

LogEntry.newBuilder(JsonPayload.of(jsonMap))


다음 로그 Serverity (INFO,ERROR,WARNING)는 setServerity로 정할 수 있다. 스택 드라이버 로그는 정보 구조에서 계층 구조를 가질 수 있는데, 다음과 같은 개념을 가지고 있다.

리소스

리소스는 이 로그가 어떤 자원에 속하는지를 정의한다. 예를 들어, VM, 빅쿼리와 같이 어떤 인프라에 속하는지를 정의할 수 있는데, 애플리케이션의 경우 일반적으로 “global” 리소스로 정의한다.

리소스 명은 setResource메서드를 이용해서 지정이 가능하다.

라벨

다음 로그에 라벨을 달 수 있다. 예를 들어 이 리소스가 VM인데, 어떤 VM인지 식별을 하기 위해서 키를 name, 값을 인스턴스명 등으로 지정할 수 있다. 또는 개발/운영 환경인지를 구별하기 위해서 env 라는 키를 이용해서 환경에 따라 값을 dev,qa,prod 등으로 달 수 있다. 하나의 로그에는 여러개의 라벨을 붙이는 것이 가능하다. 라벨은 키,밸류 형태로 .addLabel(키,값)으로 추가가 가능하다.

로그 이름

로그 이름은 로그를 그룹핑할 수 있는데, 애플리케이션 종류등으로 그룹핑을할 수 있다. 이 로그는 사용자 로그, 게임 로그 등으로 그룹핑이 가능하다. 그룹 명을 setLogName으로 지정이 가능하다.


아래는 리소스를 global, 로그 이름을 LOG_NAME, 라벨에 instancename을 키로, instance-1이라는 값을 지정한 코드 예제이다.

       .setLogName(LOG_NAME)

       .setResource(MonitoredResource.newBuilder("global").build())

       .addLabel("instancename", "instance-1")

로그 확인

로그는 구글 클라우드 콘솔에서 STACKDRIVER > Logs 항목에서 확인이 가능하다.


위 그림과 같이 메뉴로 진입한 후에, 로그를 볼 수 있다.


리스트 박스에서 첫번째 박스는 리소스를 선택하는 화면으로 애플리케이션 로그는 앞의 예제에서 리소스를 global로 선택하였기 때문에, global을 선택한다. 그리고 두번째는 로그 이름을 고르는 화면인데, 앞에 예제에서 terry-tutorial로 로그 이름을 지정하였기 때문에 terry-tutorial을 선택한다.

다음 위의 화면에서 버튼을 누르면 실시간으로 로그를 볼 수 있는데, 통상 1분이내의 딜레이가 소요된다고 보면 된다.

로그에서 각 항목을 펼쳐보면 디테일을 볼 수 있다. 아래는 하나의 디테일인데, 중요한 부분은 timestamp에서 시간이 기록되고, serverity에 에러 레벨이 기록된다. 그리고 앞에서 지정한 Json PayLoad가 jsonPayLoad 라는 항목으로 들어간다.  라벨은 labels라는 항목에 키/밸류 형식으로 지정이 되는 것을 볼 수 있다.


로그 검색 및 필터링

스택드라이버의 강력한 기능중 하나가 로그에 대한 검색과 필터링인데, 스택 드라이버 콘솔 상단 화면에서 필터링(검색) 조건을 넣으면 각 필드 값에 따라서 다양한 형태로 로그 검색이 가능하다.


이 조건은 resource가 global이고, 그중에서 jsonPayload.count 가 900 보다 큰 로그만을 추출하는 방법이다. (Advanced filter를 사용하엿음)

표현식이 어렵지 않으니, https://cloud.google.com/logging/docs/view/advanced_filters 를 참고하면 손쉽게 로그 검색이 가능하다.

EXPORT

스택 드라이버의 다른 장점 중의 하나는 저장된 로그를 다른 시스템으로 EXPORT할 수 있는데, 크게 다음 3가지로 EXPORT가 가능하다.

  • GCS (파일) : Google Cloud Storage에 파일로 로그를 저장이 가능하다.

  • Pub/Sub (실시간 스트리밍) : 실시간으로 로그를 Pub/Sub 큐로 저장이 가능하다. Pub/Sub 뒤에 컨슈머를 둬서 다양한 처리가 가능하고 (알럿등) Apache Beam (Dataflow)연동을 통해서 실시간으로 로그를 분석 하는 것이 가능하다

  • BigQuery (데이타 베이스) : 실시간으로 데이타를 대용량 데이타 베이스는 빅쿼리에 저장하여 다양한 쿼리 및 시각화가 가능하다.


로그 EXPORT는 상단 메뉴의 CREATE EXPORT 버튼을 이용하면 EXPORT 정의가 가능하다.


이때 흥미로운 점은 로그 EXPORT시 필터에 조건을 걸어놓으면, 필터에 맞는 조건에 있는 로그만 EXPORT가 된다. 즉 로그 레벨이 CRITICAL한 로그만 Pub/Sub으로 로깅해서 알럿을 보내는 것과 같은 작업이 가능하게 된다.

빅쿼리로 EXPORT

그럼 그중에서 빅쿼리로 로그를 EXPORT하는 방법에 대해서 알아보기로 한다.

빅쿼리로 EXPORT하기 위해서는 CREATE EXPORT를 누른 후에, 로그 SINK 명을 지정하고 데이타셋을 지정해야 하는데, 데이타셋을 새로 생성하면 된다.


이 예제에서는 필터를 추가하여 label에서 instancename이 “instance-1”인 로그만 빅쿼리로 저장하도록 EXPORT 설정을 하였다.


http://bigquery.google.com에 들어가면 앞에 지정한 이름으로 데이타셋이 생긴것을 확인할 수 있고, 테이블명은 앞에서 지정한 로그명인 terry_tutorial 로 지정된것을 확인할 수 있다.

다음은 로그 시간과, JsonPay로드의 elapsedtime과, count 값을 조회하는 쿼리와 결과 이다.



쿼리 결과




데이타 스튜디오를 이용한 로그 시각화

이렇게 빅쿼리에 저장된 데이타는 구글 데이타 스튜디오를 이용하여 손쉽게 시각화가 가능하다.

https://datastudio.google.com에 접속한 후에, Start New Report에서 Blank Report 만들기를 선택한다.

새로운 리포트 화면이 나오면 우측 하단의

를 선택하여 빅쿼리 테이블과 연결을 한다.


좌측 커넥터를 선택하는 화면에서 BigQuery를 선택한후


MY PROJECT에서 내 프로젝트를 고르고, 데이타셋과 테이블은 선택한다.


다음으로 상단의 CONNECT 버튼을 눌러서 테이블을 연결한다. 또는 프로젝트를 선택하는 대신 CUSTOM QUERY를 누르면, 직접 SQL을 써서 특정 필드만 조회할 수 있다.


여기서는 전체 테이블을 불러오는 것으로 진행하도록 한다.

다음 화면에서는 필드 선택 및 제거, 그리고 타입 설정등이 가능하다.


적절하게 사용할 필드를 선택하고, 타입을 지정한후, 우측 상단의 ADD TO REPORT를 선택한다.

타임 스탬프는 일반적으로 일단위로 컨버팅 되기 때문에, 세밀한 로그를 원하면 분단위 등으로 변경하거나 커스텀 쿼리를 이용해서 초단위 값으로 컨버팅하기를 권장한다.

다음 메뉴에서 그래프나 표를 선택하여 적절하게 그리고, X 축은 Deminsion에 설정한다. 아래는 Dimension을 timestamp로 선택하고, Y축은 Metric 값으로 jsonPayload.count를 준 예이다.



혹시 테이블을 그린후에 데이타가 나오지 않는 경우가 있는데, 이 경우는 대부분 DataStudio의 Time zone과 빅쿼리에 저장된 Time이 맞지 않아서, 쿼리 범위에서 제외되는 경우인데, 이 경우는 그래프의 Property에서 날짜 범위를 다음과 같이 조정해주면 된다.



이외에도 다양한 기능이 있는데, 다음 문서들을 참고하기 바란다.




차원 감소와 PCA 분석

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

차원 감소 (Dimension reduction)

데이타를 분석할때 피쳐가 많으면 데이타 분석이 어렵고, 특히 3개 이상 (3차원)의 피쳐가 존재할 경우 시각화가 어려워진다. 머신러닝의 경우에 학습용 데이타의 피쳐가 많으면, 연산량이 많아지고, 특히 학습을 위해서 더 많은 데이타가 필요해진다. 이렇게 피쳐가 많음 으로써 발생하는 문제를 차원의 저주 (Dimension Curse)라고 이야기 하는데, 이 차원의 수를 줄이는 방법을 Dimension reduction / 차원 감소 방법이라고 한다.

차원 수를 줄인 다는 것은 다른 말로는 피쳐의 수를 줄인다는 말과 같고, 앞에서 언급한 바와 같이 데이타 분석에서는 차원을 줄여서 시각화를 가능하게 해서 데이타 분석을 용이하게 할 수 있다. 데이타 분석에 있어서 여전히 사람의 눈과 직관을 통한 분석은 중요한데, 3차원이 넘어가는 데이타는 시각화가 불가능하다. 그래서 차원을 줄여서 데이타의 특성을 파악할 필요가 있고, 또한 머신러닝에 있어서 학습 데이타의 수를 줄이고, 학습에 필요한 컴퓨팅 파워를 절약하기 위해서 차원 감소는 유용한 기법이 된다.

차원 감소 방식

차원을 감소 시키는 피쳐 선택 (Feature Selection)과 피쳐 추출 (Feature extraction) 두 가지 방식이 있다. 피쳐 선택의 경우는 여러개의 피쳐중에서 데이타의 특성을 가장 잘 나타내는 주요 필드 몇개만을 선택하여 대표 피쳐로 선택하는 방법이다.

예를 들어 [7,1,2],[100,1,3],[92,1,5] 가 있을때, 이 세개의 행렬에서 각 첫번째 열과 세번째 열이 그 변화 폭이 가장 크기 때문에, 첫번째와 세번째 열만을 대표 피쳐로 사용하여 다음과 같이 선택한다. [7,2],[100,3],[92,5] 이렇게 원래 피쳐에서 부분 집합만을 선택하는 방식을 피쳐 선택 방법이라고 한다.


다음은 피쳐 추출 (Feature extraction) 방식이 있는데, 이건 원본 데이타와 전혀 다른 형태의 데이타를 추출해낸다. 예를 들어 [7,1,2] 를 일정 공식에 의해서 [3,4] 등으로 변환하여 특성을 표현하는 방법인데, 이렇게는 이해가 약간 어려우니 PCA 기반의 피쳐 추출 방법에 대해서 알아보도록 하자


PCA

PCA 분석

다음과 같은 데이타가 있다고 하자.


PCA 분석에서는 데이타의 변화의 폭이 가장 큰 축을 정하고, 그 다음 그와 직교하는 축을 구한다


그리고 데이타의 중심점에 축을 위치 시켜서 0,0을 중심으로 데이타가 양쪽으로 균등하게 퍼지도록 분포를 시켜서 축을 뒤틀어서 아래와 같이 원래의 데이타를 변화 시킨다.


이렇게 PCA 분석을 하면, 데이타의 중심축을 0,0으로 위치 시킬 수 있고, 가장 데이타의 변화의 폭이 큰 순으로 X,Y축등을 지정하여 데이타를 볼 수 있다.

PCA를 이용한 차원의 감소

그러면 PCA 분석을 이용하여 차원을 어떻게 감소 시키는가?

PCA 분석을 하더라도 단순히 축을 틀어버린것이기 때문에 차원의 수는 줄어들지 않는다. PCA 분석을 하면, 각 피쳐 (축) 별로, 값의 변화도 (Variance : 해당 축의 값이 얼마나 크게 변하는가)를 볼 수 있는데, 다음은 PCA Variance 값의 예제이다.



그래프에서 보는것과 같이 0번 피쳐의 경우 Variance가 매우 심하고, 1,2는 상대적으로 많이 약한것을 볼 수 있다. 그래서 0만 피쳐로 사용하거나 또는 0,1만 피쳐로 사용하더라도 데이타 특징의 대부분을 나타낼 수 있다.

앞의 예제 데이타에서 2차원 데이타를  PCA 분석을 해서 첫번째 피쳐가 Variance가 가장 높다고 했을 때 이를 변환하면 다음과 같이 PCA  변환된 데이타의 X축의 값만을 사용하도록 해서 2차원을 1차원으로 줄일 수 있다.




물론 차원을 줄이면, 원래 데이타가 가지고 있는 특징이 다소 사라지는 단점이 있지만, 전체적인 데이타의 특성을 파악하는 대세에는 큰 영향이 없기 때문에, 더 장점이 많다.

Sklearn을 이용한 PCA 분석과 차원 감소

그러면 이를 파이썬 sklearn 라이브러리로 구현해보자. 여기서 사용할 데이타는 IRIS 데이타를 샘플 데이타로 사용하였다. 이 예제에서는 3차원인 IRIS 데이타를 PCA 분석을 통해서 2차원으로 줄여보도록 하겠다.

원본 데이타를 생성하고 시각화 하는 코드는 다음과 같다.


from sklearn import datasets
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import pandas as pd
iris = datasets.load_iris()

labels = pd.DataFrame(iris.target)
labels.columns=['labels']
data = pd.DataFrame(iris.data,columns=['Sepal length','Sepal width','Petal length','Petal width'])

fig = plt.figure( figsize=(6,6))
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)
ax.scatter(data['Sepal length'],data['Sepal width'],data['Petal length'],c=labels,alpha=0.5)
ax.set_xlabel('Sepal lenth')
ax.set_ylabel('Sepal width')
ax.set_zlabel('Petal length')
plt.show()




PCA 분석을 통해서, 각 피쳐별 Variance를 분석하는 코드는 다음과 같다.


from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
import matplotlib.pyplot as plt

# Create scaler: scaler
scaler = StandardScaler()

# Create a PCA instance: pca
pca = PCA()

# Create pipeline: pipeline
pipeline = make_pipeline(scaler,pca)

# Fit the pipeline to 'samples'
pipeline.fit(data)

features = range(pca.n_components_)
plt.bar(features, pca.explained_variance_)
plt.xlabel('PCA feature')
plt.ylabel('variance')
plt.xticks(features)
plt.show()


분석을 해보면 PCA 분석에 의해서 변환된 피쳐 0,1의 Variation이 큰것을 확인할 수 있다.



그래서 PCA 변환된 피쳐중 0,1번 피쳐만 사용해서 시각화를 해보면 다음과 같다.


from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

model = PCA(n_components=2)
pca_features = model.fit_transform(data)

xf = pca_features[:,0]
yf = pca_features[:,1]
plt.scatter(xf,yf,c=labels);
plt.show();


그림처럼 2차원으로 줄여도 IRIS 군집화의 특성이 어느정도 남아 있는 것을 확인할 수 있다.


다음은 1차원으로 줄여서 시각화를 한 예인데


from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

model = PCA(n_components=1)
pca_features = model.fit_transform(data)

xf = pca_features[:,0]
yf = len(xf)*[0]
plt.scatter(xf,yf,c=labels);
plt.show();



2차원에 비해서 -1~4사이에 분포된 두개의 클래스 (녹색과 노랑색)이 다소 겹치는 부분이 있지만, 전체적으로 봤을때 1차원으로 변환해도 어느정도 분류 특성을 유지하고 있는 것을 볼 수 있다.


이런 중첩 현상을 줄여주는 차원 감소 기법으로는 t-SNE라는 방법이 있는데, 이는 다음글에서 설명하도록 하겠다.



Classification & Clustering 모델 평가


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


클러스터링과 분류 모델에 대한 성능 평가 방법은 데이타에 라벨이 있는가 없는가에 따라서 방법이 나뉘어 진다. 사실 클러스터링은 라벨이 없는 데이타에 주로 사용을 하고, 라벨이 있는 경우에는 분류 모델을 사용한다. 클러스터링 모델에 대한 평가는 라벨이 없는 상태에서 클러스터의 응집도등을 평가하는데 대부분 그 정확도가 그리 높지 않기 때문에, 도메인 지식을 가지고 있는 전문가에 의한 휴리스틱한 방식의 평가 방식이 대부분이다.


분류 모델(Classification) 에 대한 모델 평가

라벨이 있는 경우에는 분류 모델에 대한 모델 평가 방법을 사용한다.

Confusion matrix

이진 분류 문제에서 암의 양성과 음성 데이타를 가지고 있는 데이타 가 있다고 하자


만약 모델의 정확도가 100%이면, 양성과 음성 데이타를 100% 잘 구분할것이다. 아래 그림과 같이, 양성으로 분 예측된 영역을 Positive prediction, 음성으로 분리된 영역을 Negative prediction 이라고 한다.


그런데 실제 세계에서는 정확도 100% 모델은 매우 드물고 실제로는 아래 그림과 같이 예측이 되는 경우가 많다.


양성과 음성 데이타가 각각 잘못되는 경우가 있다.

  • 양성인데, 양성으로 제대로 검출된것은 True Positive (TP)

  • 음성인데 음성으로 제대로 검출된것은 True Negative (TN)

  • 양성인데 음성으로 잘못 검출된것은 False Negative (FN)

  • 음성인데 양성으로 잘못 검출된것은 False Positive (FP)


라고 하고 그림으로 표현하면 다음과 같은 그림이 된다.


보통 이를 표로 표시하는데, 다음과 같이 표현이 된다.




P = TP + FN

N = FP + TN


그러면 이 지표를 가지고 무엇을 하느냐? 이 값을 기반으로 다음과 같은 지표들을 계산하여 모델 평가에 사용한다.

Accuracy

가장 대표적으로 사용되는 지표로 전체 데이타중에서, 제대로 분류된 데이타의 비율로


ACC = (TP + TN)  / (전체 데이타 수 = P + N)


모델이 얼마나 정확하게 분류를 하는지를 나타낸다.


Error Rate

Error Rate는 Accuracy 와 반대로, 전체 데이타 중에서 잘못 분류한 비율을 나타낸다


ERR = (FN+FP) / (전체 데이타수 = P+N)


Sensitivity (Recall or True positive Rate)

민감도라고도 하는데, Sensitive 또는  Recall이라고도 하는데, 원래 Positive 데이타 수에서 Positive로 분류된 수를 이야기 한다. 에를 들어 원본 데이타에 암 양성이 100개 있었는데, 모델에 있어서 90개가 분류되었으면, Sensitive Rate = 0.9 가된다.


SN = (TP) / P


모델이 얼마나 정확하게 Positive 값을 찾느냐를 나타낸다.

Recall (as opposed to precision) is not so much about answering questions correctly but more about answering all questions that have answer "true" with the answer "true". So if we simply always answer "true", we have 100% recall.


Precision

Precision (정밀성)은 Positive로 예측한 내용 중에, 실제 Positive의 비율을 뜻한다.


PREC = TP / (TP+FP)


Precision is about being precise. In common English, being precise means: if you give an answer, the answer will very likely be correct. So even if you answered only one question, and you answered this question correctly, you are 100% precise.


Specificity (True negative rate)

Specificity 값은 Negative 로 판단한것중에, 실제 Negative 값의 비율이다.


SP = TN / TN+FP


False Positive rate

원래는 Positive 값인데, 잘못해서 Negative로 판단한 비율로


FPR = FP / N


이 된다. 예를 들어 게임에서 어뷰징 사용자를 검출했을때 정확도도 중요하겠지만, FPR 값이 높으면, 정상 사용자를 비정상 사용자로 검출하는 경우가 많다는 의미가 된다. 어뷰징 사용자에 대해서는 계정 정지등 패널티를 주게 되는데, 모델이 아무리 어뷰징 사용자를 잘 찾아낸다 하더라도 FPR 값이 높게 되면, 정상적인 사용자를 어뷰징 사용자로 판단하여 선의의 사용자가 징계를 받게 되서, 전체적인 게임 충성도에 문제가 생길 수 있다. (어뷰징 사용자를 많이 찾아내는 것보다, 정상 사용자가 징계를 받게 되는 경우가 비지니스에 크리티컬 할때) 이런 경우에 FPR 값을 레퍼런스 할 수 있다.



그러면, Confusion Matrix를 통해서 계산된 결과를 가지고 모델을 어떻게 평가를 할까? 앞에서 나온 지표중에서 일반적으로 Accuracy 지표가 많이 사용되고, 그외에, ROC , Precision Recall Plot, F-Score 등이 많이 사용되는데 각각에 대해서 알아보자

ROC (Receiver Operating Characteristics)

ROC 그래프는 가로축을 FP Rate (Specificity) 값의 비율로 하고 세로축을 TP Rate (Sensitive) 로 하여 시각화 한 그래프이다.


  • Specificity = TN / TN+FP

  • Sensitive (Recall) = (TP) / P




보통 다음과 같은 그래프가 되고



(출처 : http://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html )


그래프가 위로 갈 수록 좋은 모델이고, 적어도 Y=X 그래프보다 위에 있어야 어느정도 쓸모 있는 모델로 볼 수 있다. 아래 그래프는 3개로 결과를 분류하는 모델에 대한 ROC 그래프 이다.


(출처 : http://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html )


ROC 그래프가 class 0, class 2, class 1 순서로 높은것을 볼 수 있다. 즉 이 모델은 class 0 을 제일 잘 분류하고 그 다음은 2,1 순서로 잘 분류 한다는 의미가 된다.

ROC는 그래프이기 때문에, 모델을 정확도를 하나의 숫자로 나타내기 어려워서 AUC (Area Under Curve) 라는 값을 사용하는데, ROC AUC값은 ROC 그래프의 면적이 된다. 최대값은 1이 된다. 위의 그래프를 보면 모델 0,2,1의 AUC값은 0.91, 0.79, 0.60 이 된다.

Precision Recall Plot

Precision Recall Plot (이하 PR 그래프)의 경우도 ROC 와 유사한데, 주로 데이타 라벨의 분포가 심하게 불균등 할때 사용한데, 예를 들어 이상 거래 검출 시나리오의 경우 정상 거래의 비율이 비정상 거래에 비해서 압도적으로 많기 때문에 (98%, 2%) 이런 경우에는 ROC 그래프보다 PR 그래프가 분석에 더 유리하다.


PR 그래프는 X 축을 Recall 값을, Y축을 Precision 값을 사용한다.


  • Sensitive (Recall) = (TP) / P

  • Precision = TP / (TP+FP)



다음은 이진 분류 (binary classification)의 PR 그래프의 예이다. 그래프가 위쪽으로 갈수록 정확도가 높은 모델이고, ROC와 마찬가지로 PR 그래프의 AUC (면적)값을 이용하여 모델의 정확도를 평가할 수 있다.



(출처 : http://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html)


그러면 모델이 쓸만한 모델인지 아닌지는 어떤 기준을 사용할까? ROC 그래프의 경우에는 Y=X 그래프를 기준으로 그래프 윗쪽에 있는 경우 쓸만한 모델로 판단을 했는데, PR 그래프의 경우 Base line이라는 것을 사용한다.


Base line = P / (P+N) 으로 정하는데, P는 데이타에서 Positive 레이블의 수, N 은 전체 데이타의 수이다. 예를 들어 암 데이타에서 암 양성이 300개 이고, 전체 데이타가 700이면 Base line은 300/(700+300) = 0.3 이 된다.  


위의 PR 그래프에 Base line을 적용하여 모델이 좋고 나쁜 영역을 판단하는 그림이다.

아래 그림은 두 모델을 비교한 PR 그래프인데, 두 모델 다 베이스라인을 넘어서 쓸만한 모델이기는 하지만, 모델 A가 B모델보다 확연하게 위에 위치하고 있기 때문에, A 모델이 좋다고 이야기할 수 있다.


(출처 : https://classeval.wordpress.com/introduction/introduction-to-the-precision-recall-plot/)

F-Score

모델의 성능을 하나의 수로 표현할때, ROC나 PR 그래프의 AUC를 사용하면 되지만, AUC를 계산하려면 여러 Throughput에 대해서 Precision, Recall, Specificity 값을 측정해야 한다.

그렇다면 Throughput을 이미 알고 있거나 또는 다양한 Throughput에 대해서 어떤 Throughput이 좋은지를 하나의 수로 모델의 성능을 평가하려면 어떻게 해야할까? 이를 위해서 사용하는 것이 F-Score 라는 값이 있다.


When measuring how well you're doing, it's often useful to have a single number to describe your performance

When measuring how well you're doing, it's often useful to have a single number to describe your performance. We could define that number to be, for instance, the mean of your precision and your recall. This is exactly what the F1-score is.

https://www.quora.com/What-is-an-intuitive-explanation-of-F-score

F Score에 대한 계산은 다음 공식을 이용한다. 큰 의미상으로 보자면 Precision과 Recall에 대한 평균인데, 그냥 평균을 내면, 값의 외곡 현상이 생기기 때문에, 가중치를 주는 평균이라고 이해하면 된다.


특히 β가 1인 경우 (즉 F1)를 F1 Score라고 하고, 모델의 성능 평가 지표로 많이 사용한다.


참고 문서


DBSCAN (밀도 기반 클러스터링)


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

기본 개념

이번에는 클러스터링 알고리즘중 밀도 방식의 클러스터링을 사용하는 DBSCAN(Density-based spatial clustering of applications with noise) 에 대해서 알아보도록 한다.

앞에서 설명한 K Means나 Hierarchical 클러스터링의 경우 군집간의 거리를 이용하여 클러스터링을 하는 방법인데, 밀도 기반의 클러스터링은 점이 세밀하게 몰려 있어서 밀도가 높은 부분을 클러스터링 하는 방식이다.

쉽게 설명하면, 어느점을 기준으로 반경 x내에 점이 n개 이상 있으면 하나의 군집으로 인식하는 방식이다.


그러면 조금 더 구체적인 개념과 용어를 이해해보자

먼저 점 p가 있다고 할때, 점 p에서 부터 거리 e (epsilon)내에 점이 m(minPts) 개 있으면 하나의 군집으로 인식한다고 하자. 이 조건 즉 거리 e 내에 점 m개를 가지고 있는 점 p를 core point (중심점) 이라고 한다.

DBSCAN 알고리즘을 사용하려면 기준점 부터의 거리 epsilon값과, 이 반경내에 있는 점의 수 minPts를 인자로 전달해야 한다.


아래 그림에서 minPts = 4 라고 하면, 파란점 P를 중심으로 반경 epsilon 내에 점이 4개 이상 있으면 하나의 군집으로 판단할 수 있는데, 아래 그림은 점이 5개가 있기 때문에 하나의 군집으로 판단이 되고, P는 core point가 된다.



아래 그림에서 회색점 P2의 경우 점 P2를 기반으로 epsilon 반경내의 점이 3개 이기 때문에, minPts=4에 미치지 못하기 때문에, 군집의 중심이 되는 core point는 되지 못하지만, 앞의 점 P를 core point로 하는 군집에는 속하기 때문에 이를 boder point (경계점)이라고 한다.



아래 그림에서 P3는 epsilon 반경내에 점 4개를 가지고 있기 때문에 core point가 된다.



그런데 P3를 중심으로 하는 반경내에 다른 core point P가 포함이 되어 있는데, 이 경우 core point P와  P3는 연결되어 있다고 하고 하나의 군집으로 묶이게 된다.


마지막으로 아래 그림의 P4는 어떤 점을 중심으로 하더라도 minPts=4를 만족하는 범위에 포함이 되지 않는다. 즉 어느 군집에도 속하지 않는 outlier가 되는데, 이를 noise point라고 한다.


이를 모두 정리해보면 다음과 같은 그림이 나온다.


정리해서 이야기 하면, 점을 중심으로 epsilon 반경내에 minPts 이상수의 점이 있으면 그 점을 중심으로 군집이 되고 그 점을 core point라고 한다. Core point 가 서로 다른 core point의 군집의 일부가 되면 그 군집을 서로 연결되어 있다고 하고 하나의 군집으로 연결을 한다.

군집에는 속하지만, 스스로 core point가 안되는 점을 border point라고 하고, 주로 클러스터의 외곽을 이루는 점이 된다.

그리고 어느 클러스터에도 속하지 않는 점은 Noise point가 된다.

장점

DBSCAN 알고리즘의 장점은

  • K Means와 같이 클러스터의 수를 정하지 않아도 되며,

  • 클러스터의 밀도에 따라서 클러스터를 서로 연결하기 때문에 기하학적인 모양을 갖는 군집도 잘 찾을 수 있으며


    기하학적인 구조를 군집화한 예 (출처 : https://en.wikipedia.org/wiki/DBSCAN )

  • Noise point를 통하여, outlier 검출이 가능하다.

예제 코드

코드의 내용은 앞과 거의 유사하다.


model = DBSCAN(eps=0.3,min_samples=6)


모델 부분만 DBSCAN으로 바꿔 주고, epsilon 값은 eps에 minPts값은 min_samples 인자로 넘겨주면 된다. 이 예제에서는 각각 0.3 과 6을 주었다.


전체 코드를 보면 다음과 같다.


import pandas as pd
iris = datasets.load_iris()

labels = pd.DataFrame(iris.target)
labels.columns=['labels']
data = pd.DataFrame(iris.data)
data.columns=['Sepal length','Sepal width','Petal length','Petal width']
data = pd.concat([data,labels],axis=1)

data.head()



IRIS 데이타를 DataFrame으로 로딩 한 다음, 학습에 사용할 피쳐를 다음과 같이 feature 변수에 저장한다.


feature = data[ ['Sepal length','Sepal width','Petal length','Petal width']]
feature.head()


다음은 모델을 선언하고, 데이타를 넣어서 학습을 시킨다.


from sklearn.cluster import DBSCAN
import matplotlib.pyplot  as plt
import seaborn as sns

# create model and prediction
model = DBSCAN(min_samples=6)
predict = pd.DataFrame(model.fit_predict(feature))
predict.columns=['predict']

# concatenate labels to df as a new column
r = pd.concat([feature,predict],axis=1)


다음은 모델을 선언하고, 데이타를 넣어서 학습을 시킨다.

학습이 끝난 결과를 다음과 같이 3차원 그래프로 시각화 해보자. 아래 시각화는 3차원인데, 학습은 4차원으로 하였다. 그래서 다소 오류가 있어 보일 수 있다. 다차원 데이타를 시각화 하기위해서는 PCA나 t-SNE와 같은 차원 감소 (dimensional reduction) 기법을 사용해야 하는데,  이는 다음 글에서 다루도록한다.


from mpl_toolkits.mplot3d import Axes3D
# scatter plot
fig = plt.figure( figsize=(6,6))
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)
ax.scatter(r['Sepal length'],r['Sepal width'],r['Petal length'],c=r['predict'],alpha=0.5)
ax.set_xlabel('Sepal lenth')
ax.set_ylabel('Sepal width')
ax.set_zlabel('Petal length')
plt.show()







마지막으로 Cross tabulazation 을 이용하여 모델을 검증해보면 다음과 같은 결과를 얻을 수 있다.

ct = pd.crosstab(data['labels'],r['predict'])
print (ct)



이 코드에 대한 전체 내용은 https://github.com/bwcho75/dataanalyticsandML/blob/master/Clustering/5.%20DBSCANClustering-IRIS%204%20feature-Copy1.ipynb 에서 확인할 수 있다.

Hierarchical clustering을 이용한 데이타 군집화


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


Hierarchical clustering (한글 : 계층적 군집 분석) 은 비슷한 군집끼리 묶어 가면서 최종 적으로는 하나의 케이스가 될때까지 군집을 묶는 클러스터링 알고리즘이다.

군집간의 거리를 기반으로 클러스터링을 하는 알고리즘이며, K Means와는 다르게 군집의 수를 미리 정해주지 않아도 된다. 참고로 이 글에서 사용된 예제 코드는 https://github.com/bwcho75/dataanalyticsandML/blob/master/Clustering/3.%20Hierarchical%20clustering-IRIS%204%20feature.ipynb 에 저장되어 있다.


예를 들어서 설명해보자

“진돗개,세퍼드,요크셔테리어,푸들, 물소, 젖소" 를 계층적 군집 분석을 하게 되면

첫번째는 중형견, 소형견, 소와 같은 군집으로 3개의 군집으로 묶일 수 있다.


이를 한번 더 군집화 하게 되면 [진돗개,셰퍼드] 와 [요크셔테리어,푸들] 군집은 하나의 군집(개)로 묶일 수 있다.


마지막으로 한번 더 군집화를 하게 되면 전체가 한군집(동물)으로 묶이게 된다.


이렇게 단계별로 계층을 따라가면서 군집을 하는 것을 계층적 군집 분석이라고 한다.

계층적 군집 분석은 Dendrogram이라는 그래프를 이용하면 손쉽게 시각화 할 수 있다.





계층형 군집화에 대한 좀 더 상세한 개념은 https://www.slideshare.net/pierluca.lanzi/dmtm-lecture-12-hierarchical-clustering?qid=94d8b25a-8cfa-421c-9ed5-03c0b33c29fb&v=&b=&from_search=1 를 보면 잘 나와 있다.


skLearn을 이용한 계층 분석 모델 구현

개념을 잡았으면 실제로 계층 분석 모델을 구현해보자.

데이타는 K Means에서 사용했던 IRIS 데이타를 똑같이 사용한다.

이번에는 4개의 피쳐를 이용해서 사용한다.


from sklearn import datasets
import pandas as pd
iris = datasets.load_iris()

labels = pd.DataFrame(iris.target)
labels.columns=['labels']
data = pd.DataFrame(iris.data)
data.columns=['Sepal length','Sepal width','Petal length','Petal width']
data = pd.concat([data,labels],axis=1)


다음은 IRIS 데이타를 이용하여 dendrogram을 그려보자

# Perform the necessary imports
from scipy.cluster.hierarchy import linkage, dendrogram
import matplotlib.pyplot as plt

# Calculate the linkage: mergings
mergings = linkage(data,method='complete')

# Plot the dendrogram, using varieties as labels
plt.figure(figsize=(40,20))
dendrogram(mergings,
          labels = labels.as_matrix(columns=['labels']),
          leaf_rotation=90,
          leaf_font_size=20,
)
plt.show()


먼저 linkage 함수를 import 한 다음 linkage 함수에 data를 넘겨주면 Hierarchical clustering을 수행한다. 이때 method=’complete’로 정했는데, 이 부분은 뒤에서 설명한다.

Hierarchical clustering 한 결과를 dendrogram 함수를 이용하여 dendrogram 그래프를 표현해 보면 다음과 같이 출력된다.




계층 분석 방식

앞의 코드에서, linkage 함수에서 method 를 사용했다. 이에 대해서 알아보자.

Hierachical clustering의 기본 원리는 두 클러스터 사이의 거리를 측정해서 거리가 가까운 클러스터끼리 묶는 방식이다.  그러면 두 클러스터의 거리를 측정할때 어디를 기준점으로 할것인가를 결정해야 하는데 다음 그림을 보자.



출처 : https://www.multid.se/genex/onlinehelp/hs515.htm


앞의 코드에서 사용한 complete linkage 방식은 두 클러스터상에서 가장 먼 거리를 이용해서 측정하는 방식이고 반대로  single linkage 방식은 두 클러스터에서 가장 가까운 거리를 사용하는 방식이다.

average linkage 방식은 각 클러스터내의 각 점에서 다른 클러스터내의 모든 점사이의 거리에 대한 평균을 사용하는 방식이다.


이 linkage 방식에 따라서 군집이 되는 모양이 다르기 때문에, 데이타의 분포에 따라서 적절한 linkage  방식을 변화 시켜가면서 적용해가는 것이 좋다.


계층 분석을 통한 군집의 결정

계층 분석은 최종적으로 1개의 군집으로 모든 데이타를 클러스터링 하는데, 그렇다면 n개의 군집으로 나누려면 어떻게 해야 하는가?

아래 dendrogram을 보자 y축이 각 클러스터간의 거리를 나타내는데, 위로 올라갈 수 록 클러스터가 병합되는 것을 볼 수 있다.




즉 적정 y 값에서 클러스터링을 멈추면 n개의 군집 까지만 클러스터링이 되는데, 위의 그림은 y 값을 3에서 클러스터링을 멈춰서 총 3개의 클러스터로 구분을 한 결과이다.


이렇게 계층형 분석에서 sklearn을 사용할 경우 fcluster 함수를 이용하면, 특정 y값에서 클러스터링을 멈출 수 있다. 다음 코드를 보자.


from scipy.cluster.hierarchy import fcluster

predict = pd.DataFrame(fcluster(mergings,3,criterion='distance'))
predict.columns=['predict']
ct = pd.crosstab(predict['predict'],labels['labels'])
print(ct)


앞의 코드에서 계층형 클러스터링을 한 mergings 변수를 fcluster 함수에 전달하고 두번째 인자에 y의 임계값을 3으로 지정하였다. Predict 컬럼에는 원본 입력데이타에 대한 예측 결과 (어느 클러스터에 속해있는지를 0,1,2로 입력 데이타의 수만큼 리턴한다.)를 리턴한다.


이를 원본 데이타의 라벨인 labels[‘label’]값과 Cross tabulation 분석을 해보았다.




세로축이 예측 결과, 가로측이 원래 값이다.

원래 label이 0인 데이타와 1인 데이타는 각각 잘 분류가 되었고, 2인 데이타는 34개만 정확하게 분류가 되었고 16개는 원본 레이블이 1인 데이타로 분류가 되었다.


지금까지 Hierachical clustering model에 대해서 알아보았다. K Means와 같은 군집화 모델이라도 내부 알고리즘에 따라서 군집화 결과가 다르기 때문에, 샘플 데이타의 분포를 보고 적절한 클러스터링 모델을 고르는 것이 필요하다. 다행이 sklearn의 경우 복잡한 수식 이해 없이도 간단한 라이브러리 형태로 다양한 클러스터링 모델 사용할 수 있도록 해놨기 때문에, 여러 모델을 적용해가면서 적정한 데이타 분류 방식을 찾아보는 것이 어떨까 한다.




클러스터링 #1 - KMeans

빅데이타/머신러닝 | 2017.10.09 22:41 | Posted by 조대협

클러스터링과 KMeans를 이용한 데이타의 군집화

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

클러스터링 문제

클러스터링은 특성이 비슷한 데이타 끼리 묶어주는 머신러닝 기법이다. 비슷한 뉴스나 사용 패턴이 유사한 사용자를 묶어 주는것과 같은 패턴 인지나, 데이타 압축등에 널리 사용되는 학습 방법이다.

클러스터링은 라벨링 되어 있지 않은 데이타를 묶는 경우가 일반적이기 때문에 비지도학습 (Unsupervised learning) 학습 방법이 사용된다.


클러스터링 알고리즘은 KMeans, DBSCAN, Hierarchical clustering, Spectral Clustering 등 여러가지 기법이 있으며, 알고르즘의 특성에 따라 속도나 클러스터링 성능에 차이가 있기 때문에, 데이타의 모양에 따라서 적절한 클러스터링 알고리즘을 선택하는 것이 중요하다. 다음은 sklearn에 나와 있는 각 클러스터링 알고리즘의 성능에 대한 비교표이다.



출처 : http://scikit-learn.org/stable/auto_examples/cluster/plot_cluster_comparison.html#sphx-glr-auto-examples-cluster-plot-cluster-comparison-py


이 글에서는 클러스터링 알고리즘 중에서 간단하게 사용할 수 있는 KMeans와 Hierachical Clustering 알고리즘을 파이썬 sklearn 라이브러리를 이용하여 설명한다.


KMeans

KMeans 클러스터링 알고리즘은 n개의 중심점을 찍은 후에, 이 중심점에서 각 점간의 거리의 합이 가장 최소화가 되는 중심점 n의 위치를 찾고, 이 중심점에서 가까운 점들을 중심점을 기준으로 묶는 클러스터링 알고리즘이다.

아래 그림을 보면 3개의 군집이 존재하는 것을 볼 수 있다. 각 군집별로 중심점이 찍혀 있는데, 이 중심점의 위치를 움직여 가면서 각 군집의 데이타와 중심점의 거리가 가장 작은 중심점을 찾는 것이다.



이 중심점은 결국 각 군집의 데이타의 평균값을 위치로 가지게 되는데, 이런 이유로 Means(평균) 값 알고리즘이라고 한다.


IRIS 데이타를 이용한 KMeans Clustering

그러면 파이썬 sklearn 라이브러리를 이용하여 IRIS 데이타를 KMeans 알고리즘을 이용하여 클러스터링 해보자

Iris 데이타는 붓꽃의 데이타를 머신러닝 학습용으로 잘 정리해놓은 테스트 데이타 셋으로 꽃잎(Petal)의 크기와 꽃받침(Petal)의 크기에 따라 Iris 꽃의 종류를 분리해놓았다.

이 Iris 데이타는 sklearn 라이브러리 안에 샘플 데이타로 제공되고 있다. 이 데이타셋에는 세가지 붓꽃의 종류별로 50장, 총 150장의 데이타를 샘플로 제공한다.



출처 : https://www.google.co.kr/url?sa=i&rct=j&q=&esrc=s&source=images&cd=&ved=0ahUKEwi0u5aAxePWAhXCNpQKHbTlAWwQjRwIBw&url=https%3A%2F%2Fwww.datacamp.com%2Fcommunity%2Ftutorials%2Fkeras-r-deep-learning&psig=AOvVaw2LZqoz0__VGKTODVDAbJnu&ust=1507638255303298


전체 소스 코드는 https://github.com/bwcho75/dataanalyticsandML/blob/master/Clustering/1.%20KMeans%20clustering-IRIS%202%20feature.ipynb 에 있다.


먼저 Iris 데이타를 로딩해보자


데이타를 로딩한 후에, 이 예제에서는 두개의 속성만 사용해서 분류하기로 해보자.  “Sepal length”와 “Sepal width” 컬럼 두개만 추출하여 학습용 feature라는 데이타 프레임으로 학습용 데이타를 만든다. Iris 데이타는 skearn.datasets에 들어있고 이를 로딩하려면 iris = datasets.load_iris()를 하면 로딩이 된다.

데이타는 로딩된 iris 데이타의 iris.data 필드에 들어가 있고, label은 iris.labels 컬럼에 들어가 있다.


from sklearn import datasets

import pandas as pd

iris = datasets.load_iris()


labels = pd.DataFrame(iris.target)

labels.columns=['labels']

data = pd.DataFrame(iris.data)

data.columns=['Sepal length','Sepal width','Petal length','Petal width']

data = pd.concat([data,labels],axis=1)

feature = data[ ['Sepal length','Sepal width']]

feature.head()




다음 K Means 라이브러리를 이용하여 학습을 시켜보자.


from sklearn.cluster import KMeans

import matplotlib.pyplot  as plt

import seaborn as sns


# create model and prediction

model = KMeans(n_clusters=3,algorithm='auto')

model.fit(feature)

predict = pd.DataFrame(model.predict(feature))

predict.columns=['predict']


sklearn.cluster에서 KMeans 라이브러리를 import 한후에, KMeans 객체를 생성하여 model에 저장한다. 이때 3개의 클러스터로 데이타를 군집화할것이기 때문에, 인자로 n_clusters=3으로 클러스터의 수를 정해준다.

model.fit(학습데이타)를 실행하면 학습 데이타를 이용하여 클러스터링을 위한 학습을 시작하고 학습 데이타에 맞는 중심점 3개를 추출해낸다. 이 학습이 된 모델을 가지고 model.predict(데이타) 를 수행하면 데이타를 학습된 모델에 맞춰서 군집화를 해서 어느 클러스터로 군집화가 되었는지 라벨을 리턴해준다.


클러스터링시, 클러스터의 라벨은 자동으로 0,1,2로 지정되는데, 이 순서는 학습을 할때 마다 임의로 변경이 될 수 있다.  클러스터링 된 라벨과 Sepal length, Sepal width를 하나의 데이타 프레임 r에  저장해서 출력해보자


# concatenate labels to df as a new column

r = pd.concat([feature,predict],axis=1)

시각화

K Means를 이용해서 클러스터링된 데이타를 Scatter plot을 이용해서 시각화 해보자


plt.scatter(r['Sepal length'],r['Sepal width'],c=r['predict'],alpha=0.5)


Scatter plot을 이용하여 클러스터링된 데이타를 그리고, 각 클러스터링 된 데이타를 라벨 (0,1,2)에 따라 색을 다르게 표시한다.

그리고 각 클러스터의 중심점을 붉은 색으로 점을 찍어서 나타내자.

클러스터별 중심점은 model.clsuter_centers 값에 저장이 된다. 중심점을 읽어서 center_x, center_y에 에 각 클러스터의 중심점 좌표를 저장하고 출력하자


centers = pd.DataFrame(model.cluster_centers_,columns=['Sepal length','Sepal width'])

center_x = centers['Sepal length']

center_y = centers['Sepal width']

plt.scatter(center_x,center_y,s=50,marker='D',c='r')

plt.show()


그래프로  출력된 결과는 다음과 같다.




데이타 스케일링를 통한 학습 데이타 정재

학습 데이타의 각 속성의 값이 범위가 크게 차이가 나면 머신러닝 학습이 잘 안되는 경우가 있는데, 예를 들어 속성 A의 범위가 1~1000이고, 속성 B의 범위가 1~10이면, 학습이 제대로 되지 않을 수 있다. 그래서 각 속성의 값의 범위를 동일하게 맞추는 것을 스케일링 (Feature scaling)이라고 한다


그림 좌측은 스케일링전의 원본 데이타, 우측은 데이타는 모든 속성을 0~1 사이로 조정한 결과이다. .

( 데이타 스케일링 대한 내용은 http://bcho.tistory.com/tag/data%20frame 참고 )



여러가지 알고리즘이 있는데 여기서 사용하는 스케일링 방법은 속성의 모든 값을 0~1 사이로 만들어주는 StandardScaling 방법을 사용한다.


즉 학습이 되기전에 데이타를 StandardScaler를 이용하여 스케일링을 조정한 후에, 스케일된 데이타를 KMeans 모델에 넣어서 학습 시키는 방법으로 두 단계를 거치는데, 이렇게 여러 단계를 거쳐서 데이타가 정재되고 학습되는 것을 파이프라인이라고 하고, sklearn.pipeline을 이용하여 손쉽게 구현이 가능하다.

아래 코드를 보자


from sklearn.pipeline import make_pipeline

from sklearn.preprocessing import StandardScaler

from sklearn.cluster import KMeans


scaler = StandardScaler()

model = KMeans(n_clusters=3)

pipeline = make_pipeline(scaler,model)


먼저 StandardScaler 객체 scaler를 만든 후, KMeans 모델 객체를 model로 선언한다. 다음에 make_pipeline 메서드를 이용하여 scaler 아 kmeans 모델을 순차로 실행하도록 파이프라인을 만든다.


pipeline.fit(feature)

predict = pd.DataFrame(pipeline.predict(feature))


다음 pipeline.fit과 .predict 메서드를 이용하여 모델을 학습 시키고 예측을 수행한다.

위의 iris 예제의 경우 스케일링을 적용하더라도 크게 모델의 정확도가 향상된것을 확인할 수 없는데, 이유는 Sepal length의 범위가 4~8, Sepal width의 범위가 2~5로 각 범위의 편차가 크지 않기 때문에 스케일링이 효과가 없다.

Inertia value를 이용한 적정 군집수 판단

K Means를 수행하기전에는 클러스터의 개수를 명시적으로 지정해줘야 한다. 데이타를 2개로 군집화할것인지, 3개로 할것인지등을 정해야 하는데, 몇개의 클러스터의 수가 가장 적절할지는 어떻게 결정할 수 있을까? Inertia value 라는 값을 보면 적정 클러스터 수를 선택할 수 있는 힌트를 얻을 수 있는데, Inertia value는 군집화가된 후에, 각 중심점에서 군집의 데이타간의 거리를 합산한것이으로 군집의 응집도를 나타내는 값이다, 이 값이 작을 수록 응집도가 높게 군집화가 잘되었다고 평가할 수 있다.


이 inertia value는 KMeans 모델이 학습된 후에, model.inertia_ 값으로 뽑아 볼 수 있다.

다음은 iris 데이타를 가지고 1~6개의 클러스터로 클러스터링을 했을때, 각 클러스터 개수별로 inertia value를 출력해보는 코드이다.


ks = range(1,10)

inertias = []


for k in ks:

   model = KMeans(n_clusters=k)

   model.fit(feature)

   inertias.append(model.inertia_)

   

# Plot ks vs inertias

plt.plot(ks, inertias, '-o')

plt.xlabel('number of clusters, k')

plt.ylabel('inertia')

plt.xticks(ks)

plt.show()


다음은 출력된 그래프이나. Inertia 값이 급격하게 하강해서 3~5사이에서는 변화의 폭이 크지 않은 것을 볼 수 있다.


이 값을 보면, iris 데이타는 3~5개의 클러스터로 분류하는 것이 적절하다고 판단할 수 있다.

크로스 테이블 체크를 이용한 모델 판단

클러스터링 모델을 검증하는 방법이 inertia 값을 사용하는 방법도 있지만 학습용 데이타가 라벨링이 되어 있는 경우에는 Cross tabulation (교차 분석)를 통해서 모델을 검증할 수 있다.

Cross tabulation 은 Pandas 라이브러리의 .crosstab 함수를 이용하면 쉽게 수행이 가능하다.


ct = pd.crosstab(data['labels'],r['predict'])

print (ct)


다음은 iris 모델에 대한 교차 분석 결과 인데



새로 축이 원본 데이타의 라벨링 된 값을 나타내고 가로가 KMeans로 인해서 클러스터링 된 결과이다.

원래 라벨 값이 0 인 값이  KMeans 에서 클러스터링 된 결과 predict값을 보면, 2 로 결과가 나온것이 50개이다. 즉 49개는 제대로 분류했다는 이야기지만, label이 1로 된 데이타는 38 제대로 분리되고 12개는 잘못 분리된것을 볼 수 있다. 그리고 마지막으로 label이 2인 데이타는 35개가 제대로 분리되고 15개는 제대로 분리되지 않았음을 볼 수 있다.

KMeans 알고리즘의 문제점

K Means 알고리즘은 사용이 편하고 속도가 비교적 빠른 알고리즘인데 비해서 몇가지 문제점을 가지고 있다. 먼저 클러스터의 수를 정해줘야 하고, 결정적으로 K Means에서는 중심점을 측정할때 처음에 랜덤으로 중심점의 위치를 찾기 때문에,  잘못하면, 중심점과 점간의 거리가 Global optimum 인 최소 값을 찾는 게 아니라 중심점이 Local optimum 에 에 수렴하여 잘못된 분류를 할 수 있다는 취약점을 가지고 있다.



출처 : http://www.cenaero.be/Page.asp?docid=27087&langue=EN


다음 글에서는 비지도 학습 기반의 클러스터링 알고리즘중의 하나인 Hierachical Clustering 알고리즘에 대해서 소개해보도록 하겠다. Hierarchical Clustering은 이름에서도 알 수 있듯이 각 클러스터가 유사한 특징을 가지고 있는 여러 계층으로 되어 있을 때 효과적으로 사용할 수 있으며, 클러스터의 수 n을 정의하지 않고도 사용이 가능하다.



오토인코더를 이용한 비정상 거래 검출 모델 구현 #4

신용카드 이상 거래 감지 코드


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


구현코드


전체 모델 코드는 https://github.com/bwcho75/tensorflowML/blob/master/autoencoder/creditcard_fraud_detection/3.model.ipynb 에 있다.


코드는 http://bcho.tistory.com/1198 에 설명한 MNIST 데이타를 이용한 오토인코더 모델과 다르지 않다. 차이는 데이타 피딩을 784개의 피쳐에서 28개의 피쳐로만 변환하였고, 데이타를 MNIST 데이타셋에서 CSV에서 읽는 부분만 변경이 되었기 때문에 쉽게 이해할 수 있으리라 본다.


학습 및 예측 결과

모델을 만들고 학습을 한후에, 이상 거래를 검출해봤다. 학습은

creditcard_validation.csv에 총 57108개의 거래로그가 저장되어 있었고, 그중에, 246개가 비정상 거래였다.

네트워크는 28,20,10,7,10,20,28 형태의 네트워크를 사용하였다.

입출력 값의 차이가 큰것을 기준으로 이 값이 어느 임계치 수준 이상이면 비정상 거래로 검출하도록 하고 실험을 해본 결과

다음과 같은 결과를 얻었다.


임계치

검출된 비정상 거래수

정상거래인데 비정상 거래로 검출된 거래

1.1

112

1

1.0

114

5

0.9

117

7

0.8

124

22


대략 검출 비율은 112~120 개 내외로 / 246개 중에서 50%가 안된다.

검출된 거래가 이상 거래인지 아닌지 여부는 대략 90% 이상이 된다.


결론

네트워크를 튜닝하고나 학습 시키는 피쳐를 변형 시키면 예상하건데, 50% 보다 높은 70~80%의 이상 거래는 검출할 수 있을 것으로 보인다.


그러나 이번 케이스의 경우는 비정상 거래가 레이블링이 되어 있었기 때문에 이런 실험이 가능했지만, 일반적인 이상 거래 검출의 경우에는 레이블링되어 있는 비정상 거래를 얻기 힘들다. 그래서 오토인코더를 통해서 전체 데이타를 학습 시킨후에, 각 트렌젝션이나 그룹별(사용자나 쇼핑몰의 경우 판매자등)로 오토인코더를 통해서 VALIDATION을 한후, 입출력값의 차이가 큰것의 경우에는 비정상 거래일 가능성이 매우 높기 때문에, 입출력값이 차이가 큰것 부터 데이타 탐색을 통하여 이상 거래 패턴을 찾아내고, 이를 통해서 임계치를 조정하여, 이상거래를 지속적으로 검출할 수 있도록 한후에, 이상 거래에 대한 데이타가 어느정도 수집되면 DNN등의 지도 학습 모델을 구축하여 이상 거래를 자동으로 검출할 수 있는 시스템으로 전환하는 단계를 거치는 방법이 더 현실적인 방법이 아닐까 한다.


오토 인코더를 이용한 신용카드 비정상 거래 검출 

#3 학습 데이타 전처리


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




앞의 글들 (http://bcho.tistory.com/1198 http://bcho.tistory.com/1197 ) 에서 신용카드 이상 검출을 하기 위한 데이타에 대한 분석과, 오토 인코더에 대한 기본 원리 그리고 오토 인코더에 대한 샘플 코드를 살펴보았다.


이제 실제 모델을 만들기에 앞서 신용카드 거래 데이타를 학습에 적절하도록 전처리를 하도록한다.

데이타양이 그리 크지 않기 때문에, 데이타 전처리는 파이썬 데이타 라이브러리인 pandas dataframe을 사용하였다. 여기서 사용된 전처리 코드는 https://github.com/bwcho75/tensorflowML/blob/master/autoencoder/creditcard_fraud_detection/2.data_normalization.ipynb 에 공개되어 있다.


데이타 전처리 과정

신용카드 거래 데이타를 머신러닝 학습의 검증과 테스트에 적절하도록 다음과 같은 절차를 통하여 데이타를 전처리하여 CSV 파일로 저장하였다.

데이타 정규화

학습 데이타에 여러가지 피쳐를 사용하는데, 예를 들어 피쳐 V1의 범위가 -10000~10000이고, 피쳐 V2의 범위가 10~20 이라면, 각 피쳐의 범위가 차이가 매우 크기 때문에, 경사 하강법등을 이용할때, 학습 시간이 더디거나 또는 제대로 학습이 되지 않을 수 있다. 자세한 내용은 김성훈 교수님의 모두를 위한 딥러닝 강좌중 정규화 부분  https://www.youtube.com/watch?v=1jPjVoDV_uo&feature=youtu.be 을 참고하기 바란다.

그래서 피쳐의 범위를 보정(정규화)하여 학습을 돕는 과정을 데이타 정규화라고 하는데, 정규화에는 여러가지 방법이 있다. 여기서 사용한 방법은 Fearture scaling이라는 방법으로, 모든 피쳐의 값들을 0~1사이로 변환하는 방법이다. 위에서 언급한 V1은 -10000~10000의 범위가 0~1사이로 사상되는 것이고, V2도 10~20의 범위가 0~1사이로 사상된다.

공식은 아래와 같은데



참고 https://en.wikipedia.org/wiki/Normalization_(statistics)


정규화된 값은 = (원본값 - 피쳐의 최소값) / (피쳐의 최대값 - 피쳐의 최소값)


으로 계산한다.

앞의 V1값에서 0의 경우는 (0 - (-10000)) / (10000 - (-10000)) = 0.5 로 사상이 되는것이다.


그러면 신용카드 데이타에서 V1~V28 컬럼을 Feature scaling을 위해서 정규화를 하려면

df_csv = pd.read_csv('./data/creditcard.csv')

CSV에서 원본 데이타를 읽는다.

읽어드린 데이타의 일부를 보면 다음과 같다.


df_csv 는 데이타의 원본값을 나타내고,  df_csv.min() 각 컬럼의 최소값, df_csv.max()는 각 컬럼의 최대값을 나타낸다. 이 값들을 이용하여 위의 Feature Scaling 공식으로 구현하면 아래와 같이 된다


df_norm = (df_csv - df_csv.min() ) / (df_csv.max() - df_csv.min() )


이렇게 정규화된 값을 출력해보면 다음과 같다.




V1 컬럼의 -1.359807이 정규화후에 0.935192 로 변경된것을 확인할 수 있고 다른 필드들도 변경된것을 확인할 수 있다.

데이타 분할

전체 데이타를 정규화 하였으면 데이타를 학습용, 검증용, 테스트용 데이타로 나눠야 하는데, 오토 인코더의 원리는 정상적인 데이타를 학습 시킨후에, 데이타를 넣어서 오토인코더가 학습되어 있는 정상적인 패턴과 얼마나 다른가를 비교하는 것이기 때문에 학습 데이타에는 이상거래를 제외하고 정상적인 거래만으로 학습을 한다.

이를 위해서 먼저 데이타를 정상과 비정상 데이타셋 두가지로 분리한다.

아래 코드는 Class=1이면 비정상, Class=0이면 정상인 데이타로 분리가 되는데, 정상 데이타는 df_norm_nonfraud에 저장하고, 비정상 데이타는 df_norm_fraud에 저장하는 코드이다.

# split normalized data by label
df_norm_fraud=df_norm[ df_norm.Class==1.0] #fraud
df_norm_nonfraud=df_norm[ df_norm.Class==0.0] #non_fraud


정상 데이타를 60:20:20 비율로 학습용, 테스트용, 검증용으로 나누고, 비정상 데이타는 학습에는 사용되지 않고 테스트용 및 검증용에만 사용되기 때문에, 테스트용 및 검증용으로 50:50 비율로 나눈다.


# split non_fraudfor 60%,20%,20% (training,validation,test)
df_norm_nonfraud_train,df_norm_nonfraud_validate,df_norm_nonfraud_test = \
   np.split(df_norm_nonfraud,[int(.6*len(df_norm_nonfraud)),int(.8*len(df_norm_nonfraud))])


numpy의 split 함수를 쓰면 쉽게 데이타를 분할 할 수 있다. [int(.6*len(df_norm_nonfraud)),int(.8*len(df_norm_nonfraud))] 가 데이타를 분할하는 구간을 정의하는데,  데이타 프레임의 60%, 80% 구간을 데이타 분할 구간으로 하면 0~60%, 60~80%, 80~100% 구간 3가지로 나누어서 데이타를 분할하여 리턴한다. 같은 방식으로 아래와 같이 비정상 거래 데이타도 50% 구간을 기준으로 하여 두 덩어리로 데이타를 나눠서 리턴한다.


# split fraud data to 50%,50% (validation and test)
df_norm_fraud_validate,df_norm_fraud_test = \
   np.split(df_norm_fraud,[int(0.5*len(df_norm_fraud))])

데이타 합치기

다음 이렇게 나눠진 데이타를 테스트용 데이타는 정상과 비정상 거래 데이타를 합치고, 검증용 데이타 역시 정상과 비정상 거래를 합쳐서 각각 테스트용, 검증용 데이타셋을 만들어 낸다.

두개의 데이타 프레임을 합치는 것은 아래와 같이 .append() 메서드를 이용하면 된다.


df_train = df_norm_nonfraud_train.sample(frac=1)
df_validate = df_norm_nonfraud_validate.append(df_norm_fraud_validate).sample(frac=1)
df_test = df_norm_nonfraud_test.append(df_norm_fraud_test).sample(frac=1)

셔플링

데이타를 합치게 되면, 테스트용과 검증용 데이타 파일에서 처음에는 정상데이타가 나오다가 뒷부분에 비정상 데이타가 나오는 형태가 되기 때문에 테스트 결과가 올바르지 않을 수 있는 가능성이 있다. 그래서, 순서를 무작위로 섞는 셔플링(Shuffling) 작업을 수행한다.

셔플링은 위의 코드에서 .sample(frac=1)에 의해서 수행되는데, .sample은 해당 데이타 프레임에서 샘플 데이타를 추출하는 명령으로 frac은 샘플링 비율을 정의한다 1이면 100%로, 전체 데이타를 가져오겠다는 이야기 인데, sample()함수는 데이타를 가지고 오면서 순서를 바꾸기 때문에, 셔플링된 결과를 리턴하게 된다.


전체 파이프라인을 정리해서 도식화 해보면 다음과 같다.


다음글에서는 이렇게 정재된 데이타를 가지고 학습할 오토인코더 모델을 구현해보도록 한다.


오토인코더를 이용한 비정상 거래 검출 모델의 구현 #2

MNIST 오토인코더 샘플


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


신용카드 이상 거래 감지 시스템 구현에 앞서서, 먼저 오토인코더에 대한 이해를 하기 위해서 오토 인코더를 구현해보자. 오토 인코더 샘플 구현은 MNIST 데이타를 이용하여 학습하고 복원하는 코드를 만들어 보겠다.


이 코드의 원본은 Etsuji Nakai 님의 https://github.com/enakai00/autoencoder_example 코드를 사용하였다.


데이타 전처리

이 예제에서는 텐서플로우에 포함된 MNIST 데이타 tensorflow.contrib.learn.python.learn.datasets    tfrecord 로 변경해서 사용한다.TFRecord에 대한 설명은 http://bcho.tistory.com/1190 를 참고하기 바란다.

MNIST 데이타를 TFRecord로 변경하는 코드는 https://github.com/bwcho75/tensorflowML/blob/master/LAB5-Create-MNIST-TFRecord-Data.ipynb 에 있다. 이 코드를 실행하면, ./data/train.tfrecord ./data/test.tfrecords 에 학습 및 테스트 데이타 파일이 생성된다. 이 파일들을 아래서 만들 모델이 들어가 있는 디렉토리 아래 /data 디렉토리로 옮겨놓자.

학습 코드 구현

학습에 사용되는 모델은 텐서플로우 하이레벨 API인 tf.layers와 Estimator를 이용해서 구현한다.

하이레벨 API를 사용하는 이유는 http://bcho.tistory.com/1195 http://bcho.tistory.com/1196 에서도 설명했듯이 구현이 상대적으로 쉬울뿐더러, 분산 학습이 가능하기 때문이다.


전체 코드는 hhttps://github.com/bwcho75/tensorflowML/blob/master/LAB5-Autoencoder-MNIST-Estimator.ipynb 에 공유되어 있다.

데이타 입력부

데이타 입력 부분은 tfrecord 파일을 읽어서, 파일 큐를 생성해서 input_fn 을 생성하는 부분이다. 이렇게 생성된 input_fn 함수는 Estimator 를 통해서, 학습과 테스트(검증) 데이타로 피딩되게 된다.


데이타 입력 부분은 read_and_decode함수와 input_fn 함수로 구현되어 있는데, 각각을 살펴보자

def read_and_decode(filename_queue):
   reader = tf.TFRecordReader()
   _,serialized_example = reader.read(filename_queue)
   
   features = tf.parse_single_example(
       serialized_example,
       features={
           'image_raw':tf.FixedLenFeature([],tf.string),
           'label':tf.FixedLenFeature([],tf.int64),
       })
   
   image = tf.decode_raw(features['image_raw'],tf.uint8)
   image.set_shape([784]) #image shape is (784,)
   image = tf.cast(image,tf.float32)*(1.0/255)
   label = tf.cast(features['label'],tf.int32)
   
   return image,label


read_and_decode 함수는 filename_queue에서, 파일을 읽어서 순서대로 TFRecoderReader를 읽어서 파싱한후에, image_raw이름으로 된 피쳐와,  label로 된 피쳐를 읽어서 각각 image와 label 이라는 텐서에 저장한다.

image는 차원을 맞추기 위해서 set_shape를 이용하여 1차원으로 784의 길이를 가진 텐서로 변환하고, 학습에 적절하도록 데이타를 regulization 을 하기 위해서, 1.0/255 를 곱해줘서 1~255값의 칼라값을 0~1사이의 값으로 변환한다.

그리고 label값은 0~9를 나타내는 숫자 라벨이기 때문에, tf.int32로 형 변환을 한다.

변환이 끝난 image와 label 텐서를 리턴한다.


def input_fn(filename,batch_size=100):
   filename_queue = tf.train.string_input_producer([filename])
   
   image,label = read_and_decode(filename_queue)
   images,labels = tf.train.batch(
       [image,label],batch_size=batch_size,
       capacity=1000+3*batch_size)
   #images : (100,784), labels : (100,1)
   
   return {'inputs':images},labels

Input_fn 함수는 실제로 Estimator에 값을 피딩하는 함수로, 입력 받은 filename으로 파일이름 큐를 만들어서 read_and_decode 함수에 전달 한 후, image와 label 값을 리턴받는다.

리턴 받은 값을 바로 리턴하지 않고 배치 학습을 위해서 tf.train.batch를 이용하여 배치 사이즈(batch_size)만큼 묶어서 리턴한다.

모델 구현부

데이타 입력 부분이 완성되었으면, 데이타를 읽어서 학습 하는 부분을 살펴보자.


모델 구현

아래는 모델을 구현한 autoecndoer_model_fn 함수이다.

Custom Estimator를 구현하기 위해서 사용한 구조이다.


def autoencoder_model_fn(features,labels,mode):
   input_layer = features['inputs']
   dense1 = tf.layers.dense(inputs=input_layer,units=256,activation=tf.nn.relu)
   dense2 = tf.layers.dense(inputs=dense1,units=128,activation=tf.nn.relu)
   dense3 = tf.layers.dense(inputs=dense2,units=16,activation=tf.nn.relu)
   dense4 = tf.layers.dense(inputs=dense3,units=128,activation=tf.nn.relu)
   dense5 = tf.layers.dense(inputs=dense4,units=256,activation=tf.nn.relu)
   output_layer = tf.layers.dense(inputs=dense5,units=784,activation=tf.nn.sigmoid)
   
   #training and evaluation mode
   if mode in (Modes.TRAIN,Modes.EVAL):
       global_step = tf.contrib.framework.get_or_create_global_step()
       label_indices = tf.cast(labels,tf.int32)
       loss = tf.reduce_sum(tf.square(output_layer - input_layer))
       tf.summary.scalar('OptimizeLoss',loss)

       if mode == Modes.TRAIN:
           optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
           train_op = optimizer.minimize(loss,global_step=global_step)
           return tf.estimator.EstimatorSpec(mode,loss = loss, train_op = train_op)
       if mode == Modes.EVAL:
           eval_metric_ops = None
           return tf.estimator.EstimatorSpec(
               mode,loss=loss,eval_metric_ops = eval_metric_ops)
       
   # prediction mode
   if mode == Modes.PREDICT:
       predictions={
           'outputs':output_layer
       }
       export_outputs={
           'outputs':tf.estimator.export.PredictOutput(predictions)
       }
       return tf.estimator.EstimatorSpec(
           mode,predictions=predictions,export_outputs=export_outputs) #이부분 코드 상세 조사할것


오토인코더 네트워크를 구현하기 위한 코드는 다음 부분으로 복잡하지 않다

   input_layer = features['inputs']
   dense1 = tf.layers.dense(inputs=input_layer,units=256,activation=tf.nn.relu)
   dense2 = tf.layers.dense(inputs=dense1,units=128,activation=tf.nn.relu)
   dense3 = tf.layers.dense(inputs=dense2,units=16,activation=tf.nn.relu)
   dense4 = tf.layers.dense(inputs=dense3,units=128,activation=tf.nn.relu)
   dense5 = tf.layers.dense(inputs=dense4,units=256,activation=tf.nn.relu)
   output_layer = tf.layers.dense(inputs=dense5,units=784,activation=tf.nn.sigmoid)


input_fn에서 피딩 받은 데이타를 input_layer로 받아서, 각 256,128,16,128,,256의 노드로 되어 있는  5개의 네트워크를 통과한 후에, 최종적으로 784의 아웃풋과  sigmoid 함수를 활성화(activation function)으로 가지는 output layer를 거쳐서 나온다.


다음 모델의 모드 즉 학습, 평가, 그리고 예측 모드에 따라서 loss 함수나 train_op 등이 다르게 정해진다.

  #training and evaluation mode
   if mode in (Modes.TRAIN,Modes.EVAL):
       global_step = tf.contrib.framework.get_or_create_global_step()
       label_indices = tf.cast(labels,tf.int32)
       loss = tf.reduce_sum(tf.square(output_layer - input_layer))
       tf.summary.scalar('OptimizeLoss',loss)


학습과 테스트 모드일 경우, global_step을 정하고, loss 함수를 정의한다.

학습 모드일 경우에는 아래와 같이 옵티마이저를 정하고,이 옵티마이저를 이용하여 loss 값을 최적화 하도록 하는 train_op를 정의해서 EstimatorSpec을 만들어서 리턴하였다.


      if mode == Modes.TRAIN:
           optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
           train_op = optimizer.minimize(loss,global_step=global_step)
           return tf.estimator.EstimatorSpec(mode,loss = loss, train_op = train_op)


테스트 모드 일 경우에는 옵티마이즈할 필요가 없기 때문에, 옵티마이져를 정의하지 않고 loss 값을 리턴하고, 평가를 위한 Evalutaion metrics를 정해서 리턴한다. 아래 코드는 별도로 evaluation metrics 를 정의하지 않고, 디폴트 메트릭스를 사용하였다.


      if mode == Modes.EVAL:
           eval_metric_ops = None
           return tf.estimator.EstimatorSpec(
               mode,loss=loss,eval_metric_ops = eval_metric_ops)


예측 모드일 경우에는 loss 값이나 optimizer 등의 정의가 필요 없고, output값을 어떤 값을 내보낼지만 정의하면 되고, 예측 모델 (prediction model)을 프로토콜 버퍼 포맷으로 export 할때의 구조를 정의하기 위해서 export_outpus 부분만 아래와 같이 정의해주면 된다.


  # prediction mode
   if mode == Modes.PREDICT:
       predictions={
           'outputs':output_layer
       }
       export_outputs={
           'outputs':tf.estimator.export.PredictOutput(predictions)
       }
       return tf.estimator.EstimatorSpec(
           mode,predictions=predictions,export_outputs=export_outputs)

Estimator 생성

모델에 대한 정의가 끝났으면, Estimator를 생성하는데, Estimator 정의는 아래와 같이 앞에서 정의한 모델인 autoencoder_model_fn을 정의해주고

def build_estimator(model_dir):
   return tf.estimator.Estimator(
       model_fn = autoencoder_model_fn,
       model_dir = model_dir,
       config=tf.contrib.learn.RunConfig(save_checkpoints_secs=180))


실험 (Experiment) 구현

앞에서 구현된 Estimator를 이용하여, 학습과 테스트를 진행할 수 있는데, 직접 Estimator를 불러사용하는 방법 이외에 Experiment 라는 클래스를 사용하면, 이 부분을 단순화 할 수 있다.

Experiment에는 사용하고자 하는  Estimator와 학습과 테스트용 데이타 셋, 그리고 export 전략 및, 학습,테스트 스탭을 넣어주면 자동으로 Estimator를 이용하여 학습과 테스트를 진행해준다.

아래는 Experiment 를 구현한 예이다.


def generate_experiment_fn(data_dir,
                         train_batch_size = 100,
                         eval_batch_size = 100,
                         train_steps = 1000,
                         eval_steps = 1,
                         **experiment_args):
   def _experiment_fn(output_dir):
       return Experiment(
           build_estimator(output_dir),
           train_input_fn=get_input_fn('./data/train.tfrecords',batch_size=train_batch_size),
           eval_input_fn=get_input_fn('./data/test.tfrecords',batch_size=eval_batch_size),
           export_strategies = [saved_model_export_utils.make_export_strategy(
               serving_input_fn,
               default_output_alternative_key=None,
               exports_to_keep=1)
           ],
           train_steps = train_steps,
           eval_steps = eval_steps,
           **experiment_args
       )
   return _experiment_fn



learn_runner.run(
   generate_experiment_fn(
       data_dir='./data/',
       train_steps=2000),
   OUTDIR)


대략 50,000 스탭까지 학습을 진행하면 loss 값 500 정도로 수렴 되는 것을 확인할 수 있다.

검증 코드 구현

검증 코드는 MNIST 데이타에서 테스트용 데이타를 로딩하여 테스트 이미지를 앞에서 학습된 이미지로 인코딩했다가 디코딩 하는 예제이다. 입력 이미지와 출력 이미지가 비슷할 수 록 제대로 학습된것이라고 볼수 있다.

Export 된 모듈 로딩

아래 코드는 앞의 학습과정에서 Export 된 학습된 모델을 로딩하여 새롭게 그래프를 로딩 하는 코드이다.


#reset graph
tf.reset_default_graph()

export_dir = OUTDIR+'/export/Servo/'
timestamp = os.listdir(export_dir)[0]
export_dir = export_dir + timestamp
print(export_dir)

sess = tf.Session()
meta_graph = tf.saved_model.loader.load(sess,[tf.saved_model.tag_constants.SERVING],export_dir)
model_signature = meta_graph.signature_def['serving_default']
input_signature = model_signature.inputs
output_signature = model_signature.outputs

print(input_signature.keys())
print(output_signature.keys())


tf.reset_default_graph()를 이용하여, 그래프를 리셋 한후, tf.save_model.loader.load()를 이용하여 export_dir에서 Export 된 파일을 읽어서 로딩한다.

다음 입력값과 출력값의 텐서 이름을 알기 위해서 model_signature.input과 output 시그니쳐를 읽어낸후 각각 keys()를 이용하여 입력과 출력 텐서 이름을 출력하였다.

이 텐서 이름은 로딩된 그래프에 입력을 넣고 출력 값을 뽑을 때 사용하게 된다.

테스트 코드 구현

학습된 모델이 로딩 되었으면 로딩된 모델을 이용하여 MNIST 테스트 데이타를 오토 인코더에 넣어서 예측을 진행 해본다.


from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
images, labels = mnist.test.images, mnist.test.labels

feed_dict = {sess.graph.get_tensor_by_name(input_signature['inputs'].name): mnist.test.images[:10]}
output = sess.graph.get_tensor_by_name(output_signature['outputs'].name)
results = sess.run(output, feed_dict=feed_dict)

fig = plt.figure(figsize=(4,15))
for i in range(10):
       subplot = fig.add_subplot(10,2,i*2+1)
       subplot.set_xticks([])
       subplot.set_yticks([])
       subplot.imshow(images[i].reshape((28,28)), vmin=0, vmax=1,
                      cmap=plt.cm.gray_r, interpolation="nearest")
       
       subplot = fig.add_subplot(10,2,i*2+2)
       subplot.set_xticks([])
       subplot.set_yticks([])
       subplot.imshow(results[i].reshape((28,28)), vmin=0, vmax=1,
                      cmap=plt.cm.gray_r, interpolation="nearest")

plt.show()


feed_dict = {sess.graph.get_tensor_by_name(input_signature['inputs'].name): mnist.test.images[:10]} 부분은 입력 데이타를 정의하는 부분으로, 앞에 모델 로딩시 사용했던 것과 같이 입력 텐서의 이름을 얻기 위해서 input_signature의 이름을 얻은 후, 그래프에서 그 이름으로 텐서를 가지고 온다. 그 이후, 가져온 텐서에 mnist 테스트 데이타셋에서 이미지 부분을 0~9 개를 피딩한다.


출력 값도 마찬가지로 output_signature에서 output 텐서 이름을 가지고 온후에, get_tensor_by_name 으로 해당 텐서를 가지고 온후에, output 변수에 저장한다.


마지막으로 sess.run을 통해서 feed_dict 값을 피딩하고, output 텐서를 리턴하여, 결과를 results로 리턴한다.

나머지는 리턴된 10개의 prediction result를 matplotlib를 이용하여 시각화 한 결과이다.

아래 결과와 같이 입력값과 출력값이 거의 유사하게 복원되었음을 확인할 수 있다.



테스트 코드를 웹으로 구현

테스트를 위해서 MNIST 데이타를 입력하는 것 말고, HTML 화면을 이용하여 직접 마우스로 숫자를 그래서 입력할 수 있도록 해보자


코드 구조 자체는 위의 예제와 같기 때문에 별도로 설명하지 않는다.



위의 그림과 같이 HTML 입력 박스에 마우스로 그림을 그리면 아래 그림과 같이 입력값과 함께 복원된 이미지를 보여 준다.

웹을 이용하여 숫자와 알파벳을 입력해서 입력과 결과값을 구분해본 결과, 영문이던 숫자이던 입출력 차이가 영문이나 숫자가 크게 차이가 나지 않아서, 변별력이 크지 않았다.



트레이닝 스탭이 이 50,000 스텝 정도면 loss값이 500 근처로 수렴을 하였는데, 1,000,000 스텝을 학습 시켜서 MNIST 데이타에 대한 기억 효과를 극대화 하려고 했지만 큰 효과가 없었다.

여러가지 원인이 있겠지만, HTML에서 손으로 이미지를 인식 받는 만큼, 글자의 위치나 크기에 따라서 loss 값이 크게 차이가 나는 결과를 보였다.  이 부분은 컨볼루셔널 필터 (Convolution Filter)를 사용하면 해결이 가능할것 같으나 적용은 하지 않았다.




또한 학습에 사용된 데이타는 0~255 의 흑백 값이지만, 위의 예제에서 웹을 통해 입력받은 값은 흑/백 (0 or 255)인 값이기 때문에 눈으로 보기에는 비슷하지만 실제로는 많이 다른 값이다.


또는 학습 데이타가 모자르거나 또는 네트워크 사이즈가 작았을 것으로 생각하는데, 그 부분은 별도로 테스트 하지 않았다.

신용 카드 데이타의 경우 손으로 그리는 그림이 아니기 때문에, 이런 문제는 없을 것으로 생각 하는데, 만약 문제가 된다면 네트워크 사이즈를 조정해보는 방안으로 진행할 예정이다.


다음 글에서는 신용 카드 데이타를 가지고 오토 인코더를 이용하여 비정상 거래를 검출하기 위해서 학습을 우하여 데이타 전처리를 하는 부분에 대해서 알아보도록 하겠다.


전체 코드 디렉토리가 변경되었습니다.

오토인코더를 이용한 비정상 거래 검출 모델의 구현 #1

신용카드 거래 데이타 분석


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


이미지 인식 모델은 만들어봤고, 아무래도 실제로 짜봐야 하는지라 좋은 시나리오를 고민하고 있는데, 추천 시스템도 좋지만, 이상 거래 감지에 대해 접할 기회가 있어서 이상 거래 감지 (Fraud Detection System)  시스템을 만들어 보기로 하였다


데이타셋

샘플 데이타를 구해야 하는데, 마침 kaggle.com 에 크레딧 카드 이상거래 감지용 데이타가 있었다.

https://www.kaggle.com/dalpozz/creditcardfraud 에서 데이타를 다운 받을 수 있다.




CSV 형태로 되어 있으며, 2013년 유럽 카드사의 실 데이타 이다. 2일간의 데이타 이고, 총 284,807건의 트렌젝션 로그중에, 492건이 비정상 데이타이고, 데이타 분포는 비정상 데이타가 0.172%로 심하게 불균형적이다.


전체 31개의 컬럼중, 첫번째 컬럼은 시간,30번째 컬럼은 비정상 거래 유무 (1이면 비정상, 0이면 정상) 그리고 마지막 31번째 컬럼은 결재 금액을 나타낸다 2~29번째 컬럼이 특징 데이타 인데, V1~V28로 표현되고 데이타 컬럼명은 보안을 이유로 모두 삭제 되었다.


데이타 분석

어떤 컬럼들을 피쳐로 정할것인가를 결정하기 위해서 데이타 분석을 시작한다.

데이타 분석 방법은  https://www.kaggle.com/currie32/predicting-fraud-with-tensorflow 를 참고하였다.


시간대별 트렌젝션양을 분석해보면 별다른 상관 관계를 찾을 수 없다.


트렌젝션 금액별로 비교를 한 그림이다.


위의 비정상 데이타를 보면, 작은 금액에서 비정상 거래가 많이 일어난것을 볼 수 있지만, 정상 거래군과 비교를 해서 다른 특징을 찾아낼 수 없다.


다음은 트랜젝션 금액을 기준으로 V1~V28 피쳐를 비교 분석해봤다.


붉은 점은 비정상, 파란점이 정상 거래이고, 가로축이 금액, 새로축이 V1 값이다. 이런 방법으로 V1~V8에 대한 그래프를 그려봤으나, 비정상 거래가 항상 정상거래의 부분집합형으로 별다른 특이점을 찾아낼 수 없었다.


다음으로 V1~V28 각 컬럼간의 값 분포를 히스토 그램으로 표현한 결과이다.

아래는 V2 피쳐의 값을 히스토그램으로 표현한 결과로 파란색이 정상, 붉은 색이 비정상 거래인데, 히스토그램이 차이가 나는 것을 확인할 수 있다.


V4 피쳐 역시 아래 그림과 같이 차이가 있는 것을 볼 수 있다.


V22 피쳐의 경우에는 정상과 비정상 거래의 패턴이 거의 유사하여 변별력이 없는것을 볼 수 있다.



이런식으로, V1~V28중에 비정상과 정상거래에 차이를 보이는 피쳐들만 선정한다.

위의 그래프들은 생성하는 코드는 https://github.com/bwcho75/tensorflowML/blob/master/autoencoder/Credit%20card%20fraud%20detection%20(Data%20Analytics).ipynb 에 있다.


모델 선택

정상거래와 비정상 거래가 라벨링이 되어 있기 때문에, 로지스틱 회귀나 일반적인 뉴럴네트워크를 사용해도 되지만, 비정상 거래 검출 로직의 경우 비정상 거래를 분별해서 라벨링한 데이타를 구하기가 매우 어렵다.

그래서 라벨된 데이타를 전제로 하는 지도학습보다 비지도학습 알고리즘을 선택하기로 한다.


비지도 학습 모델 중에서 오토 인코더라는 모델을 사용할 예정이다.

오토인코더 (AutoEncoder)

오토 인코더는 딥네트워크 기반의 비지도 학습 모델로, 뉴럴네트워크 두개를 뒤집어서 붙여놓은 형태이다.





<그림 출처 : https://deeplearning4j.org/deepautoencoder >

앞에 있는 뉴럴네트워크는 인코더, 뒤에 붙은 네트워크는 디코더가 된다.

인코더를 통해서 입력 데이타에 대한 특징을 추출해내고, 이 결과를 가지고 뉴럴 네트워크를 역으로 붙여서 원본 데이타를 생성해낸다.




이 과정에서 입력과 출력값이 최대한 같아지도록 튜닝함으로써, Feature를 잘 추출할 수 있게 하는것이 오토 인코더의 원리이다.


비정상 거래 검출에 있어서 이를 활용하는 방법은 학습이 되지 않은 데이타의 경우 디코더에 의해 복원이 제대로 되지 않고 원본 데이타와 비교했을때 차이값이 크기 때문에, 정상 거래로 학습된 모델은 비정상 거래가 들어왔을때 결과값이 입력값보다 많이 다를것이라는 것을 가정한다.


그러면 입력값 대비 출력값이 얼마나 다르면 비정상 거래로 판단할것인가에 대한 임계치 설정이 필요한데, 이는 실제 데이타를 통한 설정이나 또는 통계상의 데이타에 의존할 수 밖에 없다. 예를 들어 전체 신용카드 거래의 0.1%가 비정상 거래라는 것을 가정하면, 입력 값들 중에서 출력값과 차이가 큰 순서대로 데이타를 봤을때 상위 0.1%만을 비정상 거래로 판단한다.


또는 비지도 학습이기 때문에, 나온 데이타로 정상/비정상을 판단하기 보다는 비정상 거래일 가능성을 염두해놓고, 그 거래들을 비정상 거래일 것이라고 예측하고 이 비정상 거래 후보에 대해서 실제 확인이나 다른 지표에 대한 심층 분석을 통해서 비정상 거래를 판별한다.


이러한 과정을 거쳐서 비정상 거래가 판별이 되면, 비정상 거래에 대한 데이타를 라벨링하고 이를 통해서 다음 모델 학습시 임계치 값을 설정하거나 다른 지도 학습 알고리즘으로 변경하는 방법등을 고민해볼 수 있다.


다음글에서는 실제로 오토인코더 모델을 텐서플로우를 이용해서 구현해보겠다.


텐서플로우 하이레벨 API Estimator를 이용한 모델 정의 방법


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


텐서플로우의 하이레벨 API를 이용하기 위해서는 Estimator 를 사용하는데, Estimator 는 Predefined model 도 있지만, 직접 모델을 구현할 수 있다. 하이레벨 API와 Estimator에 대한 설명은 http://bcho.tistory.com/1195 글을 참고하기 바란다.


이 문서는 Custom Estimator를 이용하여 Estimator를 구현하는 방법에 대해서 설명하고 있으며, 대부분 https://www.tensorflow.org/extend/estimators 의 내용을 참고하여 작성하였다.