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


Archive»


 

'Tutorial'에 해당되는 글 38

  1. 2017.10.20 수학포기자를 위한 딥러닝과 텐서플로우의 이해 (10)
  2. 2017.09.20 오토인코더를 이용한 비정상 거래 검출 모델의 구현 #3 - 데이타 전처리 (1)
  3. 2017.09.11 오토 인코더를 이용한 비정상 거래 검출 모델의 구현 #1 (1)
  4. 2017.09.10 텐서플로우 하이레벨 API를 Estimator를 이용한 모델 정의 방법
  5. 2017.08.30 Tensorflow Object Detection API를 이용한 물체 인식 #3-얼굴은 학습시켜보자
  6. 2017.08.21 Tensorflow Object Detection API를 이용한 물체 인식 #2-동물 사진을 학습 시켜보자 (1)
  7. 2017.08.16 Tensorflow Object Detection API를 이용한 물체 인식 #1-설치와 사용하기
  8. 2017.08.10 텐서플로우 트레이닝 데이타 포맷인 *.tfrecord 파일 읽고 쓰기
  9. 2017.07.15 데이타 워크플로우 관리를 위한 Apache Airflow #1 - 소개
  10. 2017.06.22 얼굴 인식 모델을 만들어보자 #4 -클라우드를 이용하여 학습 시키기
  11. 2017.06.15 연예인 얼굴 인식 모델을 만들어보자 - #2. CNN 모델을 만들고 학습시켜 보자 (11)
  12. 2017.06.10 머신러닝 시스템 프로세스와 아키텍쳐 (6)
  13. 2017.04.03 텐서플로우의 세션,그래프 그리고 함수의 개념 (1)
  14. 2017.04.03 텐서플로우-배치 처리에 대해서 이해하자 (1)
  15. 2017.01.09 딥러닝을 이용한 숫자 이미지 인식 #2/2-예측 (10)
  16. 2017.01.09 딥러닝을 이용한 숫자 이미지 인식 #1/2-학습 (5)
  17. 2016.12.26 텐서플로우 #2 - 행렬과 텐서플로우 (3)
  18. 2016.12.09 텐서플로우-#1 자료형의 이해 (4)
  19. 2016.11.30 머신러닝의 과학습 / 오버피팅의 개념 (1)
  20. 2016.11.26 Docker Kubernetes의 UI
 


글은 제가 텐서플로우와 딥러닝을 공부하면서 블로그에 메모해놨던 내용을 모아놓은 글입니다.

혼자 공부하면서 어려웠던 점도 있었기 때문에, 저처럼 텐서플로우와 딥러닝을 공부하시는 분들께 도움이 되고자 자료를 공개합니다.

텐서플로우 초기버전부터 작성하였기 때문에, 다소 코드가 안맞는 부분이 있을 있으니 양해 부탁드리며, 글은 개인이 스터디용으로 자유롭게 사용하실 있으며, 단체나 기타 상용 목적으로 사용은 금지 됩니다.


머신러닝 이북-수포자를 위한 머신러닝.pdf.zip


혹시 이 교재로 공부하시다가 잘못된 부분을 수정하셨으면 다른분들을 위해서 친절하게 댓글을 달아주시면 감사하겠습니다.


그리고 오프라인 스터디 그룹을 진행하시는 분들을 위해서 지원을 해드립니다.

  • 발표용 프리젠테이션 파일
  • 실습 자료
  • 온라인 실습용 https://google.qwiklabs.com/catalog 토큰
스터디 지원을 위해서는 
1. https://www.facebook.com/groups/googlecloudkorea/ 구글 클라우드 사용자 그룹에 가입 하신후
2. https://www.meetup.com/GDG-Cloud-Korea 에 가입하신 후에, 스터디 모임을 매주 진행하실때 마다 밋업을 여시면 됩니다.
그후에, 저한테 페이스북으로 연락 주시면 https://www.facebook.com/terry.cho.7 제가 자료와 함께 실습 토큰 (무료 크레딧)을 제공해 드립니다.


저작자 표시 비영리
신고

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

#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()함수는 데이타를 가지고 오면서 순서를 바꾸기 때문에, 셔플링된 결과를 리턴하게 된다.


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


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


저작자 표시 비영리
신고

오토인코더를 이용한 비정상 거래 검출 모델의 구현 #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 의 내용을 참고하여 작성하였다.

Custom Estimator

Estimator의 스켈레톤 코드는 다음과 같다. 모델을 정의하는 함수는 학습을 할 feature와, label을 입력 받고, 모델의 모드 (학습, 테스트, 예측) 모드를 인자로 받아서 모드에 따라서 모델을 다르게 정의할 수 있다. 예를 들어 학습의 경우 드롭 아웃을 사용하지만 테스트 모드에서는 드롭 아웃을 사용하지 않는다.

def model_fn(features, labels, mode, params):
  # Logic to do the following:
  # 1. Configure the model via TensorFlow operations
  # 2. Define the loss function for training/evaluation
  # 3. Define the training operation/optimizer
  # 4. Generate predictions
  # 5. Return predictions/loss/train_op/eval_metric_ops in EstimatorSpec object
  return EstimatorSpec(mode, predictions, loss, train_op, eval_metric_ops)

입력 인자에 대한 설명

그러면 각 인자를 구체적으로 살펴보자

  • features : input_fn을 통해서 입력되는 feature로 dict 형태가 된다.

  • labels : input_fn을 통해서 입력되는 label 값으로 텐서 형태이고, predict (예측) 모드 일 경우에는 비어 있게 된다.

  • mode : 모드는 모델의 모드로, tf.estimator.ModeKeys 중 하나를 사용하게 된다.

    • tf.estimator.ModeKeys.TRAIN : 학습 모드로 Estimator의 train()을 호출하였을 경우 사용되는 모드이다.

    • tf.estimator.ModeKeys.EVAL : 테스트 모드로, evaluate() 함수를 호출하였을 경우 사용되는 모드이다.

    • tf.estimator.ModeKeys.PREDICT : 예측모드로,  predict() 함수를 호출하였을 경우에 사용되는 모드이다.  

  • param : 추가적으로 입력할 수 있는 패러미터로, dict 포맷을 가지고 있으며, 하이퍼 패러미터등을 이 변수를 통해서 넘겨 받는다.

Estimator 에서 하는 일

Estimator 를 구현할때, Estimator 내의 내용은 모델을 설정하고, 모델의 그래프를 그린 다음에, 모델에 대한 loss 함수를 정의하고, Optimizer를 정의하여 loss 값의 최소값을 찾는다. 그리고 prediction 값을 계산한다.


Estimator의 리턴값

Estimator에서 리턴하는 값은 tf.estimator.EstimatorSpec 객체를 리턴하는데, 이 객체는 다음과 같은 값을 갖는다.

  • mode : Estimator가 수행한 모드. 보통 입력값으로 받은 모드 값이 그대로 리턴된다.

  • prediction (PREDICT 모드에서만 사용됨) : PREDICT 모드에서 예측을 수행하였을 경우, 예측된 값을 dict 형태로 리턴한다.

  • loss (EVAL 또는, TRAIN 모드에서 사용됨) : 학습과 테스트중에 loss 값을 리턴한다.

  • train_op (트레이닝 모드에서만 필요함) : 한 스텝의 학습을 수행하기 위해서 호출하는 함수를 리턴한다. 보통 옵티마이져의  minimize()와 같은 함수가 사용된다.
           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)

  • eval_metrics_ops (optional) : EVAL (테스트) 모드에서 테스트를 위해서 사용된 인자들을 dict 형태로 리턴한다. tf.metrics에는 미리 정의된 일반적인 메트릭들이 정의되어 있는데, 예를 들어 accuracy 등이 이에 해당한다. 아래는 tf.metrics.accuracy를 이용하여 예측값 (predictions)과 라벨(labels)의 값을 계산하여, 메트릭으로 리턴하는 방법이다.

    eval_metric_ops = {
    "accuracy": tf.metrics.accuracy(labels, predictions) }

    만약 rmse를 evaluation metric으로 사용하고자 하면 다음과 같이 정의한다.
    eval_metric_ops = {
       "rmse": tf.metrics.root_mean_squared_error(
           tf.cast(labels, tf.float64), predictions)
    }

    만약에 별도의 메트릭을 정의하지 않으면, 디폴트로 loss 값만 EVAL 단계에서 계산되게 된다.

데이타 입력 처리

모델로의 데이타 입력은 Esitmator의 모델 함수로 입력되는 features 변수를 통해서 입력 된다.

features는 컬럼명으로된 키와, 컬럼 값으로 이루어진 dict 형태의 데이타 형으로, 뉴럴 네트워크 모델에 데이타를 입력하기 위해서는 이중에서 학습에 사용할 컬럼만을 추출하여, 입력 레이어에 넣어 줘야 한다.

이 features 에서 특정 컬럼만을 지정하여 추출한 후에, 그 컬럼의 값을 넣어주는 것은 tf.feature_column.input_layer 함수를 사용하면 된다.


예제를 보자

input_layer = tf.feature_column.input_layer(
 features=features, feature_columns=[age, height, weight])


위의 예제는 features 에서 age,height,weight 컬럼을 추출하여 input layer로 넣는 코드이다.

네트워크 정의

데이타를 읽었으면 이제 뉴럴네트워크를 구성해야 한다. 네트워크의 레이어는 tf.layers 로 간단하게 구현할 수 있다. tf.layer에는 풀링,드롭아웃,일반적인 뉴럴네트워크의 히든 레이어, 컨볼루셔널 네트워크들이 함수로 구현되어 있기 때문에 각 레이어를 하나의 함수로 간단하게 정의가 가능하다.


아래는 히든레이어를 구현하는 tf.layers.dense 함수이다.


tf.layers.dense( inputs, units, activation)


  • inputs는 앞의 레이어를 정의하고

  • units는 이 레이어에 크기를 정의하고

  • 마지막으로 activation은 sigmoid나,ReLu와 같은 Activation 함수를 정의한다.


다음 예제는 5개의 히든 레이어를 가지는 오토 인코더 네트워크를 정의한 예이다.

 input_layer = features['inputs'] # 784 pixels
   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)


5개의 히든 레이어는 각각 256,128,16,128,256 개의 노드를 가지고 있고, 각각 ReLu를 Activation 함수로 사용하였다.

그리고 마지막 output layer는 784개의 노드를 가지고 sigmoid 함수를 activation 함수로 사용하였다.

Loss 함수 정의

다음 모델에 대한 비용함수(loss/cost function)을 정의한다. 이 글을 읽을 수준이면 비용함수에 대해서 별도로 설명하지 않아도 되리라고 보는데, 비용함수는 예측값과 원래 라벨에 대한 차이의 합을 나타내는 것이 비용함수이다.


 # Connect the output layer to second hidden layer (no activation fn)

 output_layer = tf.layers.dense(second_hidden_layer, 1)
 # Reshape output layer to 1-dim Tensor to return predictions
 predictions = tf.reshape(output_layer, [-1])
 predictions_dict = {"ages": predictions}

 # Calculate loss using mean squared erro
 loss = tf.losses.mean_squared_error(labels, predictions)

코드를 보면, 최종 예측된 값은 predictions에 저장되고, 학습 데이타로 부터 받은 라벨 값은 labels에 저장된다. 이 차이를 계산할때, MSE (mean square error)를 사용하였다.

Training Op 정의

비용 함수가 적용되었으면, 이 비용함수의 값을 최적화 하는 것이 학습이기 때문에, 옵티마이저를 정의하고, 옵티마이저를 이용하여 비용함수의 최적화가 되도록 한다.

아래 코드는  Optimizer를 GradientDescentOptimizer로 정의하고, 이 옵티마이저를 이용하여 이용하여 loss 값을 최소화 하도록 하였다.

optimizer = tf.train.GradientDescentOptimizer(
   learning_rate=params["learning_rate"])

train_op = optimizer.minimize(
   loss=loss, global_step=tf.train.get_global_step())

전체 코드

그러면 위의 내용을 모두 합쳐서 model_fn으로 모아서 해보자.

def model_fn(features, labels, mode, params):
 """Model function for Estimator."""
 # Connect the first hidden layer to input layer
 # (features["x"]) with relu activation
 first_hidden_layer = tf.layers.dense(features["x"], 10, activation=tf.nn.relu)

 # Connect the second hidden layer to first hidden layer with relu
 second_hidden_layer = tf.layers.dense(
     first_hidden_layer, 10, activation=tf.nn.relu)

 # Connect the output layer to second hidden layer (no activation fn)
 output_layer = tf.layers.dense(second_hidden_layer, 1)


 # Reshape output layer to 1-dim Tensor to return predictions
 predictions = tf.reshape(output_layer, [-1])

 # Provide an estimator spec for `ModeKeys.PREDICT`.
 if mode == tf.estimator.ModeKeys.PREDICT:
   return tf.estimator.EstimatorSpec(
       mode=mode,
       predictions={"ages": predictions})

 # Calculate loss using mean squared error
 loss = tf.losses.mean_squared_error(labels, predictions)

 # Calculate root mean squared error as additional eval metric
 eval_metric_ops = {
     "rmse": tf.metrics.root_mean_squared_error(
         tf.cast(labels, tf.float64), predictions)
 }

 optimizer = tf.train.GradientDescentOptimizer(
  learning_rate=params["learning_rate"])

 train_op = optimizer.minimize(
     loss=loss, global_step=tf.train.get_global_step())

 # Provide an estimator spec for `ModeKeys.EVAL` and `ModeKeys.TRAIN` modes.

 return tf.estimator.EstimatorSpec(
     mode=mode,
     loss=loss,
     train_op=train_op,
     eval_metric_ops=eval_metric_ops)

데이타 입력

 first_hidden_layer = tf.layers.dense(features["x"], 10, activation=tf.nn.relu)

네트워크 정의

 # Connect the second hidden layer to first hidden layer with relu
 second_hidden_layer = tf.layers.dense(
     first_hidden_layer, 10, activation=tf.nn.relu)

 # Connect the output layer to second hidden layer (no activation fn)
 output_layer = tf.layers.dense(second_hidden_layer, 1)

first_hidden_layer의 입력값을 가지고 네트워크를 구성한다. 두번째 레이어는 first_hidden_layer를 입력값으로 하여, 10개의 노드를 가지고, ReLu를 activation 레이어로 가지도록 하였다.  

마지막 계층은 두번째 계층에서 나온 결과를 하나의 노드를 이용하여 합쳐서 activation 함수 없이 결과를 냈다.

 # Reshape output layer to 1-dim Tensor to return predictions
 predictions = tf.reshape(output_layer, [-1])

 # Provide an estimator spec for `ModeKeys.PREDICT`.
 if mode == tf.estimator.ModeKeys.PREDICT:
   return tf.estimator.EstimatorSpec(
       mode=mode,
       predictions={"ages": predictions})

예측 모드에서는 prediction 값을 리턴해야 하기 때문에, 먼저 예측값을 output_layer에서 나온 값으로, 행렬 차원을 변경하여 저장하고, 만약에 예측 모드 tf.estimator.ModeKeys.PREDICT일 경우 EstimatorSpec에 predction 값을 넣어서 리턴한다. 이때 dict 형태로 prediction 결과 이름을 age로 값을 predictions 값으로 채워서 리턴한다.

Loss 함수 정의

다음 비용 함수를 정의하고, 테스트 단계(EVAL)에서 사용할 evaluation metrics에 rmse를 테스트 기준으로 메트릭으로 정의한다.

 # Calculate loss using mean squared error
 loss = tf.losses.mean_squared_error(labels, predictions)

 # Calculate root mean squared error as additional eval metric
 eval_metric_ops = {
     "rmse": tf.metrics.root_mean_squared_error(
         tf.cast(labels, tf.float64), predictions)
 }

Training OP 정의

비용 함수를 정했으면, 비용 함수를 최적화 하기 위한 옵티마이져를 정의한다. 아래와 같이 GradientDescentOptimzer를 이용하여 loss 함수를 최적화 하도록 하였다.

 optimizer = tf.train.GradientDescentOptimizer(
  learning_rate=params["learning_rate"])

 train_op = optimizer.minimize(
     loss=loss, global_step=tf.train.get_global_step())

 # Provide an estimator spec for `ModeKeys.EVAL` and `ModeKeys.TRAIN` modes.

마지막으로, PREDICTION이 아니고, TRAIN,EVAL인 경우에는 EstimatorSpec을 다음과 같이 리턴한다.

Loss 함수와, Training Op를 정의하고 평가용 매트릭스를 정의하여 리턴한다.

 return tf.estimator.EstimatorSpec(
     mode=mode,
     loss=loss,
     train_op=train_op,
     eval_metric_ops=eval_metric_ops)

실행

그러면 완성된 Estimator를 사용해보자

train_input_fn = tf.estimator.inputs.numpy_input_fn(
   x={"x": np.array(training_set.data)},
   y=np.array(training_set.target),
   num_epochs=None,
   shuffle=True)

# Train

nn.train(input_fn=train_input_fn, steps=5000)

# Score accuracy

test_input_fn = tf.estimator.inputs.numpy_input_fn(
   x={"x": np.array(test_set.data)},
   y=np.array(test_set.target),
   num_epochs=1,
   shuffle=False)

ev = nn.evaluate(input_fn=test_input_fn)
print("Loss: %s" % ev["loss"])
print("Root Mean Squared Error: %s" % ev["rmse"])

각 코드를 보면

train_input_fn = tf.estimator.inputs.numpy_input_fn(
   x={"x": np.array(training_set.data)},
   y=np.array(training_set.target),
   num_epochs=None,
   shuffle=True)

를 이용하여 numpy 의 데이타로 input_fn 함수를 만들었다. training_set.data는 학습 데이타, training_set.target을 학습용 라벨로 설정하고, epoch는 무제한, 그리고 데이타는 셔플 하도록 하였다.

nn.train(input_fn=train_input_fn, steps=5000)

앞서 정의된 모델에 train_input_fn을 넣어서 총 5000 번 학습을 하도록 하였다.

학습이 끝난 모델을 테스트 해야 하는데, 같은 방법으로 test_input_fn을 정의하고

ev = nn.evaluate(input_fn=test_input_fn)

evaluate를 이용하여, 학습된 모델을 평가한다.

평가된 결과를 보기 위해서 loss 값과 rmse 값을 ev[‘loss’], ev[‘rmse’]로 출력하였다.

지금까지 Estimator를 만드는 방법에 대해서 알아보았다. 다음 글에서는 Auto Encoder 네트워크를 Estimator로 구현해보도록 하겠다.





저작자 표시 비영리
신고

Object Detection API를 이용하여 커스텀 데이타 학습하기

얼굴인식 모델 만들기


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


이번글에서는 Tensorflow Object Detection API를 이용하여 직접 이미지를 인식할 수 있는 방법에 대해서 알아보자. 이미 가지고 있는 데이타를 가지고 다양한 상품에 대한 인식이나, 사람 얼굴에 대한 인식 모델을 머신러닝에 대한 전문적인 지식 없이도 손쉽게 만들 수 있다.


Object Detection API 설치

Object Detection API 설치는 http://bcho.tistory.com/1193http://bcho.tistory.com/1192 에서 이미 다뤘기 때문에 별도로 언급하지 않는다.

학습용 데이타 데이타 생성 및 준비

Object Detection API를 학습 시키기 위해서는 http://bcho.tistory.com/1193 예제와 같이 TFRecord 형태로 학습용 파일과 테스트용 파일이 필요하다. TFRecord 파일 포맷에 대한 설명은 http://bcho.tistory.com/1190 를 참고하면 된다.


이미지 파일을 TFRecord로 컨버팅하는 전체 소스 코드는 https://github.com/bwcho75/objectdetection/blob/master/custom/create_face_data.py 를 참고하기 바란다.

구글 클라우드 VISION API를 이용하여,얼굴이 있는지 여부를 파악하고, 얼굴 각도가 너무 많이 틀어진 경우에는 필터링 해낸후에,  얼굴의 위치 좌표를 추출하여 TFRecord 파일에 쓰는 흐름이다.

VISION API를 사용하기 때문에 반드시 서비스 어카운트 (Service Account/JSON 파일)를 구글 클라우드 콘솔에서 만들어서 설치하고 실행하기 바란다.


사용 방법은

python create_face_data.py {이미지 소스 디렉토리} {이미지 아웃풋 디렉토리} {TFRECORD 파일명}


형태로 사용하면 된다.

예) python ./custom/create_face_data.py /Users/terrycho/trainingdata_source /Users/terrycho/trainingdata_out


{이미지 소스 디렉토리} 구조는 다음과 같다.

{이미지 소스 디렉토리}/{라벨1}

{이미지 소스 디렉토리}/{라벨2}

{이미지 소스 디렉토리}/{라벨3}

:

예를 들어

/Users/terrycho/trainingdata_source/Alba

/Users/terrycho/trainingdata_source/Jessica

/Users/terrycho/trainingdata_source/Victoria

:

이런식이 된다.



명령을 실행하면, {이미지 아웃풋 디렉토리} 아래

  • 학습 파일은 face_training.record

  • 테스트 파일은 face_evaluation.record

  • 라벨맵은 face_label_map.pbtxt

로 생성된다. 이 세가지 파일이 Object Detection API를 이용한 학습에 필요하고 부가적으로 생성되는  csv 파일이 있는데

  • all_files.csv : 소스 디렉토리에 있는 모든 이미지 파일 목록

  • filtered_files.csv : 각 이미지명과, 라벨, 얼굴 위치 좌표 (사각형), 이미지 전체 폭과 높이

  • converted_result_files.csv : filtered_files에 있는 이미지중, 얼굴의 각도등이 이상한 이미지를 제외하고 학습과 테스트용 데이타 파일에 들어간 이미지 목록으로, 이미지 파일명, 라벨 (텍스트), 라벨 (숫자), 얼굴 좌표 (사각형) 을 저장한다.


여기서 사용한 코드는 간단한 테스트용 코드로, 싱글 쓰레드에 싱글 프로세스 모델로 대규모의 이미지를 처리하기에는 적절하지 않기 때문에, 운영환경으로 올리려면, Apache Beam등 분산 프레임웍을 이용하여 병렬 처리를 하는 것을 권장한다. http://bcho.tistory.com/1177 를 참고하기 바란다.


여기서는 학습하고자 하는 이미지의 바운드리(사각형 경계)를 추출하는 것을 VISION API를 이용해서 자동으로 했지만, 일반적인 경우는 이미지에서 각 경계를 수동으로 추출해서 학습데이타로 생성해야 한다




이런 용도로 사용되는 툴은 https://medium.com/towards-data-science/how-to-train-your-own-object-detector-with-tensorflows-object-detector-api-bec72ecfe1d9 문서에 따르면 FastAnnotationTool이나 ImageMagick 과 같은 툴을 추천하고 있다.



이렇게 학습용 파일을 생성하였으면 다음 과정은 앞의  http://bcho.tistory.com/1193 에서 언급한 절차와 크게 다르지 않다.

체크포인트 업로드

학습 데이타가 준비 되었으면 학습을 위한 준비를 하는데, 트랜스퍼 러닝 (Transfer learning)을 위해서 기존의 학습된 체크포인트 데이타를 다운 받아서 이를 기반으로 학습을 한다.

Tensorflow Object Detection API는 경량이고 단순한 모델에서 부터 정확도가 비교적 높은 복잡한 모델까지 지원하고 있지만, 복잡도가 높다고 해서 정확도가 꼭 높지는 않을 수 있다. 복잡한 모델일 수 록 학습 데이타가 충분해야 하기 때문에, 학습하고자 하는 데이타의 양과 클래스의 종류에 따라서 적절한 모델을 선택하기를 권장한다.


여기서는 faster_rcnn_inception_resnet_v2 모델을 이용했기 때문에 아래와 같이 해당 모델의 체크포인트 데이타를 다운로드 받는다.


curl -O http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_11_06_2017.tar.gz


파일의 압축을 푼 다음 체크 포인트 파일을 학습 데이타용 Google Cloud Storage (GCS) 버킷으로 업로드 한다.

gsutil cp faster_rcnn_inception_resnet_v2_atrous_coco_11_06_2017/model.ckpt.* gs://${YOUR_GCS_BUCKET}/data/





설정 파일 편집 및 업로드

다음 학습에 사용할 모델의 설정을 해야 하는데,  object_detection/samples/configs/ 디렉토리에 각 모델별 설정 파일이 들어 있으며, 여기서는 faster_rcnn_inception_resnet_v2_atrous_pets.config 파일을 사용한다.


이 파일에서 수정해야 하는 부분은 다음과 같다.

클래스의 수

클래스 수를 정의한다. 이 예제에서는 총 5개의 클래스로 분류를 하기 때문에 아래와 같이 5로 변경하였다.

 8 model {

 9   faster_rcnn {

10     num_classes: 5

11     image_resizer {

학습 데이타 파일 명 및 라벨명

학습에 사용할 학습데이타 파일 (tfrecord)와 라벨 파일명을 지정한다.

126 train_input_reader: {

127   tf_record_input_reader {

128     input_path: "gs://terrycho-facedetection/data/face_training.record"

129   }

130   label_map_path: "gs://terrycho-facedetection/data/face_label_map.pbtxt"

131 }


테스트 데이타 파일명 및 라벨 파일명

학습후 테스트에 사용할 테스트 파일 (tfrecord)과 라벨 파일명을 지정한다

140 eval_input_reader: {

141   tf_record_input_reader {

142     input_path: "gs://terrycho-facedetection/data/face_evaluation.record"

143   }

144   label_map_path: "gs://terrycho-facedetection/data/face_label_map.pbtxt"

145   shuffle: false

146   num_readers: 1


만약에 학습 횟수(스탭)을 조정하고 싶으면 num_steps 값을 조정한다. 디폴트 설정은 20만회인데, 여기서는 5만회로 수정하였다.

117   # never decay). Remove the below line to train indefinitely.
118   # num_steps: 200000
119   num_steps: 50000
120   data_augmentation_options {
121     random_horizontal_flip {
122     }


설정 파일 수정이 끝났으면 gsutil cp 명령을 이용하여 해당 파일을 GCS 버킷에 다음과 같이 업로드 한다.

gsutil cp object_detection/samples/configs/faster_rcnn_inception_resnet_v2_atrous_pets.config gs://${YOUR_GCS_BUCKET}/data/faster_rcnn_inception_resnet_v2_atrous_pets.config

코드 패키징

models/ 디렉토리에서 다음 명령을 수행하여, 모델 코드를 패키징한다.

python setup.py sdist

(cd slim && python setup.py sdist)



학습


gcloud ml-engine jobs submit training `whoami`_object_detection_`date +%s` \

   --job-dir=gs://${YOUR_GCS_BUCKET}/train \

   --packages dist/object_detection-0.1.tar.gz,slim/dist/slim-0.1.tar.gz \

   --module-name object_detection.train \

   --region asia-east1 \

   --config object_detection/samples/cloud/cloud.yml \

   -- \

   --train_dir=gs://${YOUR_GCS_BUCKET}/train \

   --pipeline_config_path=gs://${YOUR_GCS_BUCKET}/data/faster_rcnn_resnet101_pets.config

모니터링

학습이 진행되면 텐서보드를 이용하여 학습 진행 상황을 모니터링할 수 있고, 또한 테스트 트레이닝을 수행하여, 모델에 대한 테스트를 동시 진행할 수 있다. http://bcho.tistory.com/1193 와 방법이 동일하니 참고하기 바란다.


학습을 시작하면 텐서보드를 통해서, Loss 값이 수렴하는 것을 확인할 수 있다.



결과

학습이 끝나면 텐서보드에서 테스트된 결과를 볼 수 있다. 이 예제의 경우 모델을 가장 복잡한 모델을 사용했는데 반하여, 총 5개의 클래스에 대해서 클래스당 약 40개정도의 학습 데이타를 사용했는데, 상대적으로 정확도가 낮았다. 실 서비스에서는 더 많은 데이타를 사용하기를 권장한다.



활용

학습된 모델을 활용하는 방법은 학습된 모델을 export 한후에, (Export 하는 방법은  http://bcho.tistory.com/1193 참고) export 된 모델을 로딩하여, 코드에서 불러서 사용하면 된다.

http://bcho.tistory.com/1192 참고



저작자 표시 비영리
신고

Object Detection API에 애완동물 사진을 학습 시켜 보자


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


Object Detection API에 이번에는 애완동물 사진 데이타를 학습시켜 보도록 한다.

애완 동물 학습 데이타의 원본은  Oxford-IIIT Pets lives  http://www.robots.ox.ac.uk/~vgg/data/pets/ 에 있다. 약 37개의 클래스에, 클래스당 200개 정도의 이미지를 가지고 있다.



이번 글에서는 이 애완동물 데이타를 다운 받아서, Object Detection API에 학습 시키는 것까지 진행을 한다.

데이타를 다운로드 받은 후, Object Detection API에 학습 시키기 위해서, 데이타 포맷을 TFRecord 형태로 변환한 후, 학습을 하는 과정을 설명한다.


주의할점 : 이 튜토리얼은 총 37개의 클래스 약 7000장의 이미지를 학습시키는데, 17시간 이상이 소요되며, 구글 클라우 CloudML의 텐서플로우 클러스터에서 분산 러닝을 하도록 설명하고 있는데, 많은 비용이 들 수 있다. 전체 흐름과 과정을 이해하기 위해서는 17시간을 풀 트레이닝 시키지 말고 학습 횟수를 줄이거나 아니면 중간에서 학습을 멈춰서 비용이 많이 나오지 않도록 하는 것을 권장한다.

학습 데이타 다운로드 받기

%curl -O http://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz

%curl -O http://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz

※ 맥이기 때문에, curl -O 를 사용했는데, Linux의 경우에는 wget을 사용하면 된다.

파일을 다운로드 받았으면 압축을 풀어보자

  • images.tar.gz에는 애완동물의 학습용 이미지가 들어가 있다.

  • annotations.tar.gz 는 각 이미지에 대한 메타 데이타가 들어있다. 이미지 마다 나타난 동물의 종류, 사진상 동물의 위치 (박스)

TFRecord 파일 포맷으로  컨버팅 하기

압축을 푼 메타데이타와 이미지 파일을 이용해서 tfrecord 파일 형태로 컨버팅을 해야 한다. Tfrecord 내에는 이미지 바이너리, 이미지에 대한 정보 (이미지 크기, 인식할 물체의 위치, 라벨)등이 들어간다. 상세 데이타 포맷에 대해서는 다음글에서 설명하도록 한다.

이 데이타를 가지고 tfrecord 타입으로 컨버팅 하는 코드는 object_detection/create_pet_tf_record.py

에 이미 작성되어 있다. 아래 코드를 이용해서 실행해주면 자동으로 pet_train.record에 학습용 데이타를 pet_val.record에 테스트용 데이타를 생성해준다.


python object_detection/create_pet_tf_record.py \
   --label_map_path=object_detection/data/pet_label_map.pbtxt \
   --data_dir=`pwd` \
   --output_dir=`pwd`

학습 환경 준비하기

데이타가 준비되었으면 학습을 위한 환경을 준비해야 한다.

학습은 구글 클라우드 플랫폼의 CloudML을 사용한다. CloudML은 구글 클라우드 플랫폼의 Tensorflow managed 서비스로, Tensorflow 클러스터 설치나 운영 필요 없이 간단하게 명령어 만으로 여러대의 머신에서 학습을 가능하게 해준다.

CloudML을 사용하기 위해서는 몇가지 환경 설정을 해줘야 한다.

  • 먼저 학습용 데이타 (tfrecord)파일을 구글 클라우드 스토리지 (GCS)로 업로드 해야 한다.

  • Object Detection API에서 사물 인식에 사용된 모델의 체크 포인트를 업로드 해야 한다.

  • 클라우드에서 학습을 하기 때문에, 텐서플로우 코드를 패키징해서 업로드해야 한다.

학습 데이타 업로드 하기

데이타를 업로드하기전에, 구글 클라우드 콘솔에서 구글 클라우드 스토리지 버킷을 생성한다.

생성된 버킷명을 YOUR_GCS_BUCKET 환경 변수에 저장한다.

export YOUR_GCS_BUCKET=${YOUR_GCS_BUCKET}


다음 gsutil 유틸리티를 이용하여 YOUR_GCS_BUCKET 버킷으로 학습용 데이타와, 라벨맵 데이타를 업로드 한다.


gsutil cp pet_train.record gs://${YOUR_GCS_BUCKET}/data/pet_train.record
gsutil cp pet_val.record gs://${YOUR_GCS_BUCKET}/data/pet_val.record
gsutil cp object_detection/data/pet_label_map.pbtxt gs://${YOUR_GCS_BUCKET}/data/pet_label_map.pbtxt


학습된 모델 다운로드 받아서 업로드 하기

다음은 학습된 모델을 받아서, 그중에서 체크포인트를  GCS에 올린다.


curl -O http://storage.googleapis.com/download.tensorflow.org/models/object_detection/faster_rcnn_resnet101_coco_11_06_2017.tar.gz

tar -xvf faster_rcnn_resnet101_coco_11_06_2017.tar.gz
gsutil cp faster_rcnn_resnet101_coco_11_06_2017/model.ckpt.* gs://${YOUR_GCS_BUCKET}/data/


체크 포인트를 다운받아서 업로드 하는 이유는, 트랜스퍼 러닝 (Transfer Learning)을 하기 위함인데, 하나도 학습이 되지 않은 모델을 학습을 시키는데는 시간이 많이 들어간다. 트랜서퍼러닝은 이미 학습이 되어 있는 모델로 다른 데이타를 학습 시키는 방법인데, 사물을 인식하는 상태로 학습되어 있는 모델을 다른 물체 (여기서는 애완동물)를 학습하는데 사용하면 학습 시간을 많이 줄 일 수 있다. 이런 이유로, 사물 인식용으로 학습된 체크포인트를 로딩해서 이 체크포인트 부터 학습을 하기 위함이다.

설정 파일 변경하기

Object Detection API를 사용하기 위해서는 학습에 대한 설정 정보를 정의해야 한다.

이 설정 파일안에는 학습 데이타의 위치, 클래스의 수 및 각종 하이퍼 패러미터들이 정의되어 있다. 패러미터에 대한 자세한 설명은  https://github.com/tensorflow/models/blob/master/object_detection/g3doc/configuring_jobs.md를 참고하기 바란다. 이 예제에서는 설정 파일을 따로 만들지 않고 애완동물 사진 학습을 위해서 미리 정의되어 있는 템플릿 설정 파일을 이용하도록 한다.  설정 파일은 미리 정의된 모델에 따라 다른데, 여기서는 faster_rcnn_resnet101_pets 모델을 사용하기 때문에 object_detection/samples/configs/faster_rcnn_resnet101_pets.config 파일을 사용한다.


파일의 위치가 PATH_TO_BE_CONFIGURED 문자열로 정의되어 있는데, 이를 앞에서 만든 GCS 버킷명으로 변경해야 하기 때문에, 아래와 같이 sed 명령을 이용하여 해당 문자열을 변경하자


Linux : sed -i "s|PATH_TO_BE_CONFIGURED|"gs://${YOUR_GCS_BUCKET}"/data|g" object_detection/samples/configs/faster_rcnn_resnet101_pets.config


Max : sed -i ‘’ -e "s|PATH_TO_BE_CONFIGURED|"gs://${YOUR_GCS_BUCKET}"/data|g" object_detection/samples/configs/faster_rcnn_resnet101_pets.config


설정 파일 작성이 끝났으면 이를 GCS 버킷에 올린 후에, 학습시에 사용하도록 한다. 다음 명령어는 설정 파일을 GCS 버킷에 올리는 명령이다.

gsutil cp object_detection/samples/configs/faster_rcnn_resnet101_pets.config \
   gs://${YOUR_GCS_BUCKET}/data/faster_rcnn_resnet101_pets.config


텐서플로우 코드 패키징 및 업로드

학습에 사용할 데이타와 체크포인트등을 업로드 했으면, 다음 텐서플로우 코드를 패키징 해야 한다. 이 글에서는 학습을 로컬 머신이 아니라 구글 클라우드의 텐서플로우 메니지드 서비스인 CloudML을 사용하는데, 이를 위해서는 텐서플로우코드와 코드에서 사용하는 파이썬 라이브러리들을 패키징해서 올려야 한다.


Object Detection API 모델 디렉토리에서 다음 명령어를 실행하면, model 디렉토리와 model/slim 디렉토리에 있는 텐서플로우 코드 및 관련 라이브러리를 같이 패키징하게된다.


# From tensorflow/models/
python setup.py sdist
(cd slim && python setup.py sdist)


명령을 실행하고 나면 패키징된 파일들은 dist/object_detection-0.1.tar.gzslim/dist/slim-0.1.tar.gz 에 저장되게 된다.

학습하기

구글 CloudML을 이용하여 학습하기. 그러면 학습을 시작해보자. 학습은 200,000 스탭에 총 17시간 정도가 소요되며, 비용이 3000$ 이상이 소요되니, 비용이 넉넉하지 않다면, 학습을 중간에 중단 시키기를 권장한다. 테스트 목적이라면 약 10~20분 정도면 충분하지 않을까 한다. 아니면 앞의 config 파일에서 trainning step을 작게 낮춰서 실행하기 바란다.


# From tensorflow/models/
gcloud ml-engine jobs submit training `whoami`_object_detection_`date +%s` \
   --job-dir=gs://${YOUR_GCS_BUCKET}/train \
   --packages dist/object_detection-0.1.tar.gz,slim/dist/slim-0.1.tar.gz \
   --module-name object_detection.train \
   --region asia-east1 \
   --config object_detection/samples/cloud/cloud.yml \
   -- \
   --train_dir=gs://${YOUR_GCS_BUCKET}/train \
   --pipeline_config_path=gs://${YOUR_GCS_BUCKET}/data/faster_rcnn_resnet101_pets.config


학습을 시킬 텐서플로우 클러스터에 대한 정보는 object_detection/samples/cloud/cloud.yml 에 들어 있다. 내용을 보면,

trainingInput:

 runtimeVersion: "1.0"

 scaleTier: CUSTOM

 masterType: standard_gpu

 workerCount: 5

 workerType: standard_gpu

 parameterServerCount: 3

 parameterServerType: standard


scaleTier로 클러스터의 종류를 정의할 수 있는데, 서버 1대에서 부터 여러대의 클러스터까지 다양하게 적용이 가능하다. 여기서는 모델이 크기가 다소 크기 때문에, Custom으로 설정하였다.


역할

서버 타입

댓수

Master server

standard_gpu

1

Worker

standard_gpu

5

Parameter Server

standard

5


각 서버의 스펙은 상세 스펙은 나와있지 않고, 상대값으로 정의되어 있는데 대략 내용이 다음과 같다.



출처 https://cloud.google.com/ml-engine/docs/concepts/training-overview#machine_type_table




학습을 시작하고 나면 CloudML 콘솔에서 실행중인 Job을 볼 수 있고, Job을 클릭하면 자원의 사용 현황을 볼 수 있다. (CPU와 메모리 사용량)



학습을 시작한 후에, 학습된 모델을 Evaluate할 수 있는데, Object Detection API에서는 학습 말고 Evaluation 모델을 별도로 나눠서, 잡을 나눠서 수행하도록 하였다. 학습중에 생성되는 체크포인트 파일을 읽어서 Evaluation을 하는 형태이다.

다음을 Evaluation을 실행하는 명령어인데, 위의 학습 작업이 시작한 후에, 한시간 정도 후부터 실행해도 실행 상태를 볼 수 있다.


# From tensorflow/models/
gcloud ml-engine jobs submit training `whoami`_object_detection_eval_`date +%s` \
   --job-dir=gs://${YOUR_GCS_BUCKET}/train \
   --packages dist/object_detection-0.1.tar.gz,slim/dist/slim-0.1.tar.gz \
   --module-name object_detection.eval \
   --region asia-east1 \
   --scale-tier BASIC_GPU \
   -- \
   --checkpoint_dir=gs://${YOUR_GCS_BUCKET}/train \
   --eval_dir=gs://${YOUR_GCS_BUCKET}/eval \
  --pipeline_config_path=gs://${YOUR_GCS_BUCKET}/data/faster_rcnn_resnet101_pets.config


학습 진행 상황 확인하기

학습이 진행중에도, Evaluation을 시작했으면, Tensorboard를 이용하여 학습 진행 상황을 모니터링 할 수 있다. 학습 진행 데이타가 gs://${YOUR_GCS_BUCKET} 에 저장되기 때문에, 이 버킷에 있는 데이타를 Tensorboard로 모니터링 하면 된다.

실행 방법은 먼저 GCS 에 접속이 가능하도록 auth 정보를 설정하고, Tensorboard에 로그 파일 경로를

GCS 버킷으로 지정하면 된다.

gcloud auth application-default login
tensorboard --logdir=gs://${YOUR_GCS_BUCKET}


아래는 실제 실행 결과이다.



Evaluataion이 끝났으면, 테스트된 이미지도 IMAGES 탭에서 확인이 가능하다.



학습된 모델을 Export 하기

학습이 완료되었으면, 이 모델을 예측 (Prediction)에 사용하기 위해서 Export 할 수 있다. 이렇게 Export 된 이미지는 나중에 다시 로딩하여 예측(Prediction)코드에서 로딩을 하여 사용이 가능하다.

${YOUR_GCS_BUCKET}에 가면 체크 포인트 파일들이 저장되어 있는데, 이 체크 포인트를 이용하여 모델을 Export 한다.



GCS 버킷에서 Export 하고자 하는 Check Point 번호를 선택한 후에 Export 하면 된다, 여기서는 200006 Check Point를 Export 해보겠다.


${CHECKPOINT_NUMBER} 환경 변수를

export CHECKPOINT_NUMBER=200006

으로 설정한 다음에 다음 명령어를 실행한다.


# From tensorflow/models
gsutil cp gs://${YOUR_GCS_BUCKET}/train/model.ckpt-${CHECKPOINT_NUMBER}.* .
python object_detection/export_inference_graph.py \

   --input_type image_tensor \

   --pipeline_config_path object_detection/samples/configs/faster_rcnn_resnet101_pets.config \

   --trained_checkpoint_prefix model.ckpt-${CHECKPOINT_NUMBER} \

   --output_directory output_inference_graph.pb


명령을 실행하고 나면 output_inference_graph.pb 디렉토리에 모델이 Export 된것을 확인할 수 있다.

다음 글에서는 직접 자신의 사진 데이타만을 가지고 학습과 예측을 하는 방법에 대해서 알아보겠다.


참고 자료



저작자 표시 비영리
신고


Tensorflow Object Detection API


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


Tensorflow Object Detection API는, Tensorflow 를 이용하여 이미지를 인식할 수 있도록 개발된 모델로, 라이브러리 형태로 제공되며, 각기 다른 정확도와 속도를 가지고 있는 5개의 모델을 제공한다. 머신러닝이나 텐서플로우에 대한 개념이 거의 없더라도 라이브러리 형태로 손쉽게 사용할 수 있으며, 직접 사용자 데이타를 업로드해서 학습을 하여, 내 시나리오에 맞는 Object Detection System을 손쉽게 만들 수 있다.


Object Detection API를 설치하기 위해서는 텐서플로우 1.x 와 파이썬 2.7x 버전이 사전 설치되어 있어야 한다. 이 글에서는 파이썬 2.7.13과 텐서플로우 2.7.13 버전을 기준으로 하고, 맥에 설치하는 것을 기준으로 한다. 리눅스나 다른 플랫폼 설치는 원본 설치 문서 https://github.com/tensorflow/models/blob/master/object_detection/g3doc/installation.md 를 참고하기 바란다.


설치 및 테스팅

Protocol Buffer 설치

Object Detection API는 내부적으로 Protocol Buffer를 사용한다. MAC에서 Protocol Buffer를 설치 하는 방법은 https://github.com/google/protobuf/tree/master/pythonhttp://bcho.tistory.com/1182 를 참고하기 바란다.

설치가 되었는지를 확인하려면, 프롬프트 상에서 protoc 명령을 실행해보면 된다.

파이썬 라이브러리 설치

프로토콜 버퍼 설치가 끝났으면, 필요한 파이썬 라이브러리를 설치한다.

% pip install pillow

% pip install lxml

% pip install jupyter

% pip install matplotlib

Object Detection API 다운로드 및 설치

Object Detection API 설치는 간단하게, 라이브러리를 다운 받으면 된다. 설치할 디렉토리로 들어가서 git clone 명령어를 통해서, 라이브러리를 다운로드 받자

% git clone https://github.com/tensorflow/models

Protocol Buffer 컴파일

다음 프로토콜 버퍼를 사용하기 위해서 protoc로 proto 파일을 컴파일 한데, Object Detection API를 설치한 디렉토리에서 models 디렉토리로 들어간 후에, 다음 명령어를 수행한다.


protoc object_detection/protos/*.proto --python_out=.

PATH 조정하기

설치가 끝났으면 Object Detection API를 PATH와 파이썬 라이브러리 경로인 PYTHONPATH에 추가한다. 맥에서는 사용자 홈디렉토리의 .bash_profile 에 추가 하면되낟.

PYTHONPATH 환경 변수에 {Object Detection API 설치 디렉토리}/models/slim 디렉토리와 Object Detection API 설치 디렉토리}/models/models 디렉토리를 추가한다.

같은 디렉토리를 PATH에도 추가해준다.


export PYTHONPATH=$PYTHONPATH:/Users/terrycho/dev/workspace/objectdetection/models:/Users/terrycho/dev/workspace/objectdetection/models/slim

export PATH=$PATH:/Users/terrycho/dev/workspace/objectdetection/models:/Users/terrycho/dev/workspace/objectdetection/models/slim

테스팅

설치가 제대로 되었는지를 확인하기 위해서 {Object Detection API 설치 디렉토리}/models/ 디렉토리에서 다음 명령을 실행해보자


% python object_detection/builders/model_builder_test.py


문제 없이 실행이 되었으면 제대로 설치가 된것이다.


사용하기

설치가 끝났으면 실제로 사용해 보자, Object Detection API를 인스톨한 디렉토리 아래 models/object_detection/object_detection_tutorial.ipynb 에 테스트용 노트북 파일이 있다. 이 파일을 주피터 노트북 (http://jupyter.org/)을 이용하여 실행해보자.
(원본 코드 https://github.com/tensorflow/models/blob/master/object_detection/object_detection_tutorial.ipynb)


실행을 하면 결과로 아래와 같이 물체를 인식한 결과를 보여준다.




이 중에서 중요한 부분은 Model Preparation이라는 부분으로,

여기서 하는 일은 크게 아래 3가지와 같다.

  • Export 된 모델 다운로드

  • 다운로드된 모델 로딩

  • 라벨맵 로딩


Export 된 모델 다운로드

Object Detection API는 여러가지 종류의 미리 훈련된 모델을 가지고 있다.

모델 종류는 https://github.com/tensorflow/models/blob/master/object_detection/g3doc/detection_model_zoo.md 를 보면 되는데,  다음과 같은 모델들을 지원하고 있다.  COCO mAP가 높을 수 록 정확도가 높은 모델인데, 대신 예측에 걸리는 속도가 더 느리다.


Model name

Speed

COCO mAP

Outputs

ssd_mobilenet_v1_coco

fast

21

Boxes

ssd_inception_v2_coco

fast

24

Boxes

rfcn_resnet101_coco

medium

30

Boxes

faster_rcnn_resnet101_coco

medium

32

Boxes

faster_rcnn_inception_resnet_v2_atrous_coco

slow

37

Boxes


모델은 *.gz 형태로 다운로드가 되는데, 이 파일안에는 다음과 같은 내용들이 들어있다.

  • Check point (model.ckpt.data-00000-of-00001, model.ckpt.index, model.ckpt.meta)
    텐서플로우 학습 체크 포인트로, 나중에, 다른 데이타를 학습 시킬때 Transfer Learning을 이용할때, 텐서플로우 그래프에 이 체크포인트를 로딩하여, 그 체크포인트 당시의 상태로 학습 시켜놓을 수 있다. 이 예제에서는 사용하지 않지만, 다른 데이타를 이용하여 학습할때 사용한다.

  • 학습된 모델 그래프 (frozen_inference_graph.pb)
    학습이 완료된 그래프에 대한 내용을 Export 해놓은 파일이다. 이 예제에서는 이 모델 파일을 다시 로딩하여 Prediction을 수행한다.

  • Graph proto (pgrah.pbtxt)


기타 파일들

이외에도 기타 다른 파일들이 있는데, 다른 파일들은 이미 Object Detection API 안에 이미 다운로드 되어 있다.

  • 라벨맵
    라벨맵은 {Object Detection API 설치 디렉토리}/models/object_detection/data  디렉토리 안에 몇몇 샘플 모델에 대한 라벨맵이 저장되어 있다. 라벨맵은 모델에서 사용한 분류 클래스에 대한 정보로 name,id,display_name 식으로 정의되며, name은 텍스트 라벨, id는 라벨을 숫자로 표현한 값 (반드시 1부터 시작해야 한다.), display_name은 Prediction 결과를 원본 이미지에서 인식한 물체들을 박스처리해서 출력하는데 이때 박스에 어떤 물체인지 출력해주는 문자열에 들어가는 텍스트 이다.
    여기서 사용한 라벨맵은 mscoco_label_map.pbtxt 파일이 사용되었다.

  • 학습 CONFIG 파일
    모델 학습과 예측에 사용되는 각종 설정 정보를 저장한 파일로 위에서 미리 정의된 모델별로 각각 다른 설정 파일을 가지고 있으며 설정 파일의 위치는  {Object Detection API 설치 디렉토리}/models/object_detection/samples/configs 에 {모델명}.config 에 저장되어 있다.


다운로드된 모델과 라벨맵 로딩

위에서 많은 파일이 다운되고 언급되었지만 예측 (Prediction)에는 학습된 그래프 모델을 저장한 frozen_inference_graph.pb 파일과, 분류 라벨이 저장된 mscoco_label_map.pbtxt 두 개만 사용된다.


다음 코드 부분에서 모델을 다운 로드 받고, 모델 파일과 라벨 파일의 경로를 지정하였다.


# What model to download.

MODEL_NAME = 'ssd_mobilenet_v1_coco_11_06_2017'

MODEL_FILE = MODEL_NAME + '.tar.gz'

DOWNLOAD_BASE = 'http://download.tensorflow.org/models/object_detection/'


# Path to frozen detection graph. This is the actual model that is used for the object detection.

PATH_TO_CKPT = MODEL_NAME + '/frozen_inference_graph.pb'


# List of the strings that is used to add correct label for each box.

PATH_TO_LABELS = os.path.join('data', 'mscoco_label_map.pbtxt')


NUM_CLASSES = 90


그리고 마지막 부분에 분류 클래스의 수를 설정한다. 여기서는 90개의 클래스로 정의하였다.

만약에 모델을 바꾸고자 한다면 PATH_TO_CKPT를 다른 모델 파일로 경로만 변경해주면 된다.


다음으로  frozen_inference_graph.pb  로 부터 모델을 읽어서 그래프를 재생성하였다.


detection_graph = tf.Graph()

with detection_graph.as_default():

 od_graph_def = tf.GraphDef()

 with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid:

   serialized_graph = fid.read()

   od_graph_def.ParseFromString(serialized_graph)

   tf.import_graph_def(od_graph_def, name='')


나머지 부분은 이미지를 읽어서, 로딩된 모델을 이용하여 물체를 Detection 하는 코드이다.


여기까지 간단하게 Tensorflow Object Detection API를 설치 및 사용하는 방법에 대해서 알아보았다.

다음 글에서는 다른 데이타로 모델을 학습해서 예측하는 부분에 대해서 알아보도록 하겠다.


참고 자료





저작자 표시 비영리
신고

TFRecord


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


텐서플로우를 접하게 다 보면 필히 만나는 부분이 텐서플로우 학습 데이타 포맷인 TFRecord라는 파일 포맷이다. 마침 얼굴 인식 모델을 이번에는 텐서플로우에서 미리 개발되어 제공되는 물체 인식 API인 Tensorflow Object Detection API를 사용해서 얼굴을 학습시켜보려고 하니 데이타 포맷을 TFRecord 포맷으로 변경해야 한다. 그래서, TFRecord 파일을 만들어보고, 테스트를 위해서 데이타 내용도 직접 읽는 코드를 작성해보았다. (전체 코드는 https://github.com/bwcho75/objectdetection/tree/master/tfrecord 에 다.)

TFRecord 파일 포맷이란

TFRecord 파일은 텐서플로우의 학습 데이타 등을 저장하기 위한 바이너리 데이타 포맷으로, 구글의 Protocol Buffer 포맷으로 데이타를 파일에 Serialize 하여 저장한다.

CSV 파일에서와 같이 숫자나 텍스트 데이타를 읽을때는 크게 지장이 없지만, 이미지를 데이타를 읽을 경우 이미지는 JPEG나 PNG 형태의 파일로 저장되어 있고 이에 대한 메타 데이타와 라벨은 별도의 파일에 저장되어 있기 때문에, 학습 데이타를 읽을때 메타데이타나 라벨용 파일 하나만 읽는 것이 아니라 이미지 파일도 별도로 읽어야 하기 때문에, 코드가 복잡해진다.


또한 이미지를 JPG나 PNG 포맷으로 읽어서 매번 디코딩을 하게 되면, 그 성능이 저하되서 학습단계에서 데이타를 읽는 부분에서 많은 성능 저하가 발생한다.


이와 같이 성능과 개발의 편의성을 이유로 TFRecord 파일 포맷을 이용하는 것이 좋다.


그러면 간단한 예제를 통해서 TFRecord 파일을 쓰고 읽는 방법에 대해서 알아보도록 하자

본 예제는 http://warmspringwinds.github.io/tensorflow/tf-slim/2016/12/21/tfrecords-guide/ 글과 https://github.com/tensorflow/models/blob/master/object_detection/g3doc/using_your_own_dataset.md 글을 참고하였다.


TFRecord 파일 생성

TFRecord 파일 생성은 tf.train.Example에 Feature를 딕셔너리 형태로 정의한 후에, tf.train.Example 객체를 TFRecord 파일 포맷 Writer인 tf.python_io.TFRecordWriter를 통해서 파일로 저장하면 된다.


다음 코드를 보자, 이 코드는 Tensorflow Object Detection API를 자신의 데이타로 학습시키기 위해서 데이타를 TFRecord 형태로 변환하여 저장하는 코드의 내용이다.

이미지를 저장할때 사물의 위치를 사각형 좌표로 지정하고 저장한다.


def create_cat_tf_example(encoded_image_data):


 height = 1032

 width = 1200

 filename = 'example_cat.jpg'

 image_format = 'jpg'


 xmins = [322.0 / 1200.0]

 xmaxs = [1062.0 / 1200.0]

 ymins = [174.0 / 1032.0]

 ymaxs = [761.0 / 1032.0]

 classes_text = ['Cat']

 classes = [1]


 tf_example = tf.train.Example(features=tf.train.Features(feature={

     'image/height': dataset_util.int64_feature(height),

     'image/width': dataset_util.int64_feature(width),

     'image/filename': dataset_util.bytes_feature(filename),

     'image/source_id': dataset_util.bytes_feature(filename),

     'image/encoded': dataset_util.bytes_feature(encoded_image_data),

     'image/format': dataset_util.bytes_feature(image_format),

     'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),

     'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),

     'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),

     'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),

     'image/object/class/text': dataset_util.bytes_list_feature(classes_text),

     'image/object/class/label': dataset_util.int64_list_feature(classes),

 }))

 return tf_example


저장되는 내용은 이미지의 높이와 너비 (height,weight), 파일명 (filename), 인코딩 포맷 (format), 이미지 바이너리 (encoded), 이미지내에서 물체의 위치를 가르키는 사각형 위치 (xmin,ymin,xmax,ymax) 와 라벨 값등이 저장된다.


코드를 유심이 살표보면 생각보다 이해하기어렵지 않다.

tf.train.Example 객체를 만들고 이때 인자로 features에 TFRecord에 저장될 갚들의 목록을 딕셔너리 형태로 저장한다.

이때 저장되는 데이타의 이름과 값을 지정해야 하는데


'image/height': dataset_util.int64_feature(height),


를 보면 'image/height' 이 데이타의 이름이 되고, dataset_util.int64_feature(height), 가 height 값을 텐서플로우용 리스트형으로 변형하여 이를 학습용 피쳐 타입으로 변환하여 저장한다.

이 예제는 Object Detection API의 일부이기 때문에, dataset_util이라는 모듈을 사용했지만, 실제로 이 함수의 내부를 보면  tf.train.Feature(int64_list=tf.train.Int64List(value=values)) 로 구현이 되어 있다.


다음 이렇게 생성된 tf.train.Example 객체를 tf.python_io.TFRecordWriter 를 이용해서 다음과 같이 파일에 써주면 된다.

   writer = tf.python_io.TFRecordWriter(tfrecord_filename)

   writer.write(tf_example.SerializeToString())


다음은 코드 전체이다.


import tensorflow as tf

from PIL import Image

from object_detection.utils import dataset_util


def create_cat_tf_example(encoded_image_data):


 height = 1032

 width = 1200

 filename = 'example_cat.jpg'

 image_format = 'jpg'


 xmins = [322.0 / 1200.0]

 xmaxs = [1062.0 / 1200.0]

 ymins = [174.0 / 1032.0]

 ymaxs = [761.0 / 1032.0]

 classes_text = ['Cat']

 classes = [1]


 tf_example = tf.train.Example(features=tf.train.Features(feature={

     'image/height': dataset_util.int64_feature(height),

     'image/width': dataset_util.int64_feature(width),

     'image/filename': dataset_util.bytes_feature(filename),

     'image/source_id': dataset_util.bytes_feature(filename),

     'image/encoded': dataset_util.bytes_feature(encoded_image_data),

     'image/format': dataset_util.bytes_feature(image_format),

     'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),

     'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),

     'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),

     'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),

     'image/object/class/text': dataset_util.bytes_list_feature(classes_text),

     'image/object/class/label': dataset_util.int64_list_feature(classes),

 }))

 return tf_example


def read_imagebytes(imagefile):

   file = open(imagefile,'rb')

   bytes = file.read()


   return bytes

   

def main():

   print ('Converting example_cat.jpg to example_cat.tfrecord')

   tfrecord_filename = 'example_cat.tfrecord'

   bytes = read_imagebytes('example_cat.jpg')

   tf_example = create_cat_tf_example(bytes)


   writer = tf.python_io.TFRecordWriter(tfrecord_filename)

   writer.write(tf_example.SerializeToString())


main()


참고로 이 예제는 앞에서도 언급하였듯이 Object Detection API에 대한 의존성을 가지고 있기 때문에 일반적인 텐서플로우 개발환경에서는 실행이 되지 않는다. Tensorflow Object Detection API 를 인스톨해야 dataset_util 를 사용할 수 있기 때문에 Object Detection API 설치가 필요하다.

만약에 Object Detection API 설치 없이 TFRecord Writer를 짜보고 싶은 경우에는 http://warmspringwinds.github.io/tensorflow/tf-slim/2016/12/21/tfrecords-guide/ 문서에 예제가 간단하게 잘 정리되어 있으니 참고하기 바란다.

TFRecord에서 데이타 읽기

데이타를 읽는 방법도 크게 다르지 않다. 쓰는 순서의 반대라고 보면 되는데, TFReader를 통해서 Serialized 된 데이타를 읽고, 이를 Feature 목록을 넣어서 파싱한 후에, 파싱된 데이타셋에서 각 피쳐를 하나하나 읽으면 된다.


코드를 보자


def readRecord(filename_queue):

   reader = tf.TFRecordReader()

   _,serialized_example = reader.read(filename_queue)

   

   #'''

   keys_to_features = {

       'image/height': tf.FixedLenFeature((), tf.int64, 1),

       'image/width': tf.FixedLenFeature((), tf.int64, 1),

       'image/filename': tf.FixedLenFeature((), tf.string, default_value=''),

       #'image/key/sha256': tf.FixedLenFeature((), tf.string, default_value=''),

       'image/source_id': tf.FixedLenFeature((), tf.string, default_value=''),

       'image/encoded': tf.FixedLenFeature((), tf.string, default_value=''),

: (중략)

   }

   

   features = tf.parse_single_example(serialized_example,features= keys_to_features)

   

   height = tf.cast(features['image/height'],tf.int64)

   width = tf.cast(features['image/width'],tf.int64)

   filename = tf.cast(features['image/filename'],tf.string)

   source_id = tf.cast(features['image/source_id'],tf.string)

   encoded = tf.cast(features['image/encoded'],tf.string)

: (중략)

   return height,width,filename,source_id,encoded,image_format


TFRecoderReader를 이용하여 파일을 읽는데, 파일을 직접읽지 않고 filename_queue를 이용해서 읽는다. 코드 전체를 보면, 이 큐는

filename_queue = tf.train.string_input_producer([tfrecord_filename])

로, 파일 이름 목록을 가지고 리턴하는 string_input_producer를 사용하였다.

파일을 읽으면 데이타는 직렬화 된 상태로 리턴이 되어 serialized_example 에 저장된다.

이를 각 개별 피쳐로 디코딩 하기 위해서 피쳐 목록을 keys_to_features 딕셔너리에 저장한다.

이때, 각 피쳐의 이름과, 타입을 정의한다.

다음은 ‘image/height’ 피쳐를 정의하여 int 64 타입으로 읽어드리는 부분이다.


       'image/height': tf.FixedLenFeature((), tf.int64, 1),


피쳐 목록과, 데이타 타입은 TFRecord 파일을 쓸때 사용한 이름과 데이타 타입을 그대로 사용하면 된다.

피쳐 목록이 정의되었으면


   features = tf.parse_single_example(serialized_example,features= keys_to_features)


를 통해서 피쳐를 파싱해낸다. 파싱된 피쳐는 features에 딕셔너리 형태로 저장된다.

다음 리턴 받은 텐서를 각 변수에 저장한다. 이때 타입 캐스팅을 해서 저장한다. (이미 타입을 맞춰서 데이타를 꺼냈기 때문에, 별도의 캐스팅은 필요없지만 확실히 하기 위해서 캐스팅을 한다.)

다음은 ‘image/height’ 를 피쳐를 배열에서 뽑아서, int64 타입으로 변환하여 height 에 저장하는 부분이다.


   height = tf.cast(features['image/height'],tf.int64)


리턴값은 텐서가 되는데, 이 값을 출력하는 코드를 보자

당연히 텐서이기 때문에 Session 을 시작해야 하는데, 먼저 파일에서 데이타를 읽기 위해서 filename queue를 정의한다.

    filename_queue = tf.train.string_input_producer([tfrecord_filename])

다음 filename_queue를 인자로 넘겨서 height,source_id 등의 값을 *.tfrecord 파일에서 읽어서 텐서로 리턴 받는다.


    height,width,filename,source_id,encoded,image_format = readRecord(filename_queue)


다음 세션을 시작하고, readRecord 에서 리턴된 값을 받아온다.         vheight,vwidth,vfilename,vsource_id,vencoded,vimage_format = sess.run([height,width,filename,source_id,encoded,image_format])


전체 코드는 https://github.com/bwcho75/objectdetection/tree/master/tfrecord/reader.py 를 참고하기 바란다.


마지막으로 받은 값을 화면에 출력한다.



간단하게 tfRecord 파일 포맷으로 학습용 데이타를 쓰는 방법을 알아보았다. 텐서플로우 코드가 간단해 지고 성능에 도움이 되는 만큼 데이타 전처리 단계에서 가급적이면 학습 데이타를 tfrecord 타입으로 바꿔서 학습하는 것을 권장한다. (특히 이미지 데이타!!)


참고 자료


저작자 표시 비영리
신고

Apache airflow


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

배경

빅데이타 분석이나, 머신러닝 코드를 만들다 보면 필요한것중에 하나가 여러개의 태스크를 연결해서 수행해야 할 경우가 있다. 데이타 베이스의 ETL 작업과 비슷한 흐름이라고 보면 된다.


예를 들어 머신러닝의 학습 과정을 보면 데이타 전처리,학습,배포,예측과 같은 단계를 가지게 된다.


  • rawdata를 읽어서 preprocessing 단계를 거쳐서 학습에 적절한 training data로 변경하고,

  • 변경된 training data를 가지고 머신러닝 모델을 학습한후, 학습된 모델을 저장한다.

  • 학습된 모델을 가지고 예측을 해서 결과를 저장한다.


이렇게 머신러닝은 여러개의 단계를 거쳐서 수행이 되는데, 각 단계가 끝나면 다음 단계를 수행해야 한다. 단순하게 CRON+쉘로 순차적으로 수행하는 것등이 가능하지만, 에러가 났을때 재처리를 하거나 , 수행 결과에 따라 분기를 하는 등 조금 더 구조화된 도구가 필요하다.

데이타 워크 플로우 관리 도구

이런 요구 사항 때문에 여러가지 툴이 개발되었는데, 대표적인 도구로는 하둡 에코시스템에 우지(oozie ) 등이 있다.



<그림. Oozie eclipse 클라이언트 >


하둡의 여러 에코 시스템 솔루션들을 유기적으로 조합하기 위해서 개발된 도구로, 하둡 에코 시스템에 있는 여러가지 다양한 솔루션과 연동하기 위한 아답터를 가지고 있다.

이외에도 rundeck, luigi와 같은 유사한 솔루션들이 있다.

오늘 소개하고자 하고자하는 데이타 워크 플로우 관리도구는 아파치 오픈소스 airflow 이다. 원래 airbnb에서 개발된 도구로 현재 아파치 오픈소스에서 인큐베이터 단계에 있는 소프트웨어이다.


airflow를 소개하는 이유는 첫번째 파이썬 기반으로 태스크 코드를 작성할 수 있기 때문에, 데이타 분석이나 머신러닝을 개발하는 엔지니어들에게 익숙한 언어이고, 한대에서 동작하는게 아니라 여러 머신에 분산하여 수행 될 수 있는 장점을 가지고 있다.



<그림. Apache airflow 의 작업 그래프 구조 화면 >

airflow 시작하기

그러면 간단하게 airflow에 대한 개념과 사용법에 대해서 알아보자

airflow 설치

airflow는 실행되는 작업의 상태등을 저장하기 위해서 데이타 베이스 (MySQL이나 Postgres)등이 필요하며, 분산 환경을 위해서 여러대에 설치할 수 있다. 또한 로컬 환경에 sqlite와 함께 간단하게 설치할 수 있다. 여기서는 간단하게 개인 맥북환경에 로컬로 설치 및 실행하는 시나리오로 설명한다.


설치 방법은 매우 간단하다. 파이썬 2.7 환경에서 아래와 같이 간단학 “pip install airflow”만 실행해주면 된다.

%pip install airflow



airflow가 설치되었으면 데이타 베이스 설정을 해줘야 하는데, 이 튜토리얼에서는 개발 및 테스트를 위해서 sqlite를 사용한다. sqlite를 초기화 하기 위해서 다음과 같이 “airflow initdb” 명령을 실행한다.

% airflow initdb


자아 이제 설치가 끝나고 airflow를 사용할 준비가 되었다. 이제 airflow 웹콘솔을 기동해보자

“airflow webserver -p 8080” 을 실행하고 웹에 http://localhost:8080에 접근하면 airflow 콘솔을 볼 수 있다.

airflow 코드

airflow에서 워크플로우를 저장하기 위해서 몇가지 추상화된 개념을 사용한다.

Airflow DAG의 구조

DAG (Directed Acyclic Graph)

DAG는 하나의 워크 플로우라고 보면 된다. 위의 예제처럼, 머신러닝 이라는 DAG를 정의한다면, Preprocessing,Training,Prediction 워크플로우가 하나의 DAG가 된다.

Operator and Task

Operator는 DAG안에서 정의되는 작업 함수(함수임을 주의하자) 이야기 하는데, Pre processing, Training, Prediction 함수가 Operatorator 이다.

이 Operator 함수가 DAG 상에서 실제로 워크플로우 상에 정의되서 호출 되면 이것이 Task 이다.

객체지향 언어에서 Operator가 class 라면, Task는 object 라고 보면 된다.


이해가 잘안될 수 있는데, 코드를 보자


from airflow import DAG

from airflow.operators.bash_operator import BashOperator

from airflow.operators.dummy_operator import DummyOperator

from airflow.operators.python_operator import PythonOperator

from datetime import datetime,timedelta


dag = DAG('hello-airflow',description='Hello airflow DAG',

         schedule_interval = '*/5 0 * * *',

         start_date=datetime(2017,07,01),catchup=False)


def print_hello():

   return 'Hello Airflow'


python_task = PythonOperator(

                   task_id='python_operator',

                   python_callable = print_hello,

                   dag = dag)


bash_task = BashOperator(

       task_id='print_date',

       bash_command='date',

       dag=dag)


bash_task.set_downstream(python_task)


DAG 정의 부분을 보자. DAG 객체는 DAG에 대한 전체 컨택스를 저장 및 유지 관리한다.

DAG('hello-airflow',description='Hello airflow DAG', 에서 DAG를 이름을 ‘hello-airflow’로 정의하고 description에 설명을 적는다.

schedule_interval = '*/5 * * * *', 다음으로 이 DAG가 실행되는 주기를 정해야 하는데, cron 명령과 같은 노테이션으로 정의한다. 위 설정은 매 5분마다 실행되도록 하는 설정이다.

마지막으로, start_date=datetime(2017,07,01), ,DAG를 언제부터 시작할것인지 지정한다. DAG는 반드시 전역 변수로 지정한다. DAG안에서 다른 DAG를 부르는 sub DAG의 경우에는 지역 변수로 지정이 가능하다.


다음 task에 사용할 operator를 정의하는데, 파이썬 코드를 실행할 오퍼레이터인 PythonOperator와 쉘 커맨드를 실행할 BashOperator를 가지고 각각 파이썬 태스크 python_task와, 쉘 태스크 bash_task를 정의한다.


python_task = PythonOperator(

                   task_id='python_operator',

                   python_callable = print_hello,

                   dag = dag)


파이썬 태스크의 id는 “python_operator”라고 지정하였고, 실행시 print_hello를 호출하도록 하였다.

그리고 이 태스크는 DAG인 dag에 지정한다.


다음 쉘 태스크의 내용은 다음과 같다.

bash_task = BashOperator(

       task_id='print_date',

       bash_command='date',

       dag=dag)


print_data라는 이름으로 태스크를 정의하고, 쉘 명령어 date를 실행하도록 하였다.

등록

코드 작성이 끝나면 코드를 배포해보자. Dag 파일을 airflow에 등록해야 하는데, dag 파일을 저장하는 장소는 dags_folder 라는 변수로 $AIRFLOW_HOME/airflow.cfg 파일안에 정의 되어 있다. 디폴트 장소는 $AIRFLOW_HOME/dags/ 폴더이다. 위에서 작성한 코드를 해당 디렉토리에 복사하자

다음 dag이 제대로 등록되었는지를 확인한다. 커멘드 창에서 “airflow list_dags”라는 명령을 수행하면 현재 등록되어 있는 DAG 목록을 볼 수 있다. 아래 그림과 같이 hello-airflow dag가 등록된것을 확인할 수 있다.




hello-airflow dag안에 어떤 태스크들이 정의되어 있는지를 확인하려면 ‘airflow list_tasks hello-airflow’ 명령을 이용하면 hello-airflow 안에 등록된 태스크 목록을 출력해준다.


테스트

테스트를 하려면 태스크 단위로 테스트가 가능하다. airflow test {DAG ID} {태스크 ID} {실행날짜} 식으로 하면 된다.

, 예를 들어 print_date 태스크를 2017-07-01을 기준으로 실행하고자하면 airflow test hello-airflow print_date 2017-07-01

Hello-airflow DAG안에 print_date라는 태스크를 실행한다.



실행

DAG 코드 개발 등록과 테스트가 완료되었으면 이제 airflow scheduler 를 띄워준다. (일종의 데몬이다.) 스케쥴러는 DAG 코드에 정의된 스케쥴에 따라서 테스크를 실행해준다.

스케쥴러 실행은 간단하게 airflow scheduler 명령을 실행하면 된다.



스케쥴러가 실행되면, 각 DAG의 스케쥴에 따라서 자동으로 태스크들을 수행한다.


로그 모니터링

스케쥴러에 의해서 실행되는 DAG와 태스크들의 결과와 로그는 어떻게 모니터링 할까? airflow에 의해서 수행되는 태스크들은 $AIRFLOW_HOME/logs 디렉토리에 저장된다.

logs 디렉토리 아래에 각각 DAG 이름으로 저장이 되며, DAG 이름으로 된 디레토리안에는 태스크명으로 된 서브 디렉토리가 있고, 이 서브 디렉토리 아래에 시간대별 로그가 있다.

즉 hello-airflow DAG의 print_date 태스크에 대한 로그는 $AIRFLOW_HOME/logs/hello-airflow/print_date/{날짜및시간} 파일 명으로 저장된다.

웹 콘솔을 이용한 모니터링

airflow의 강력한 기능중의 하나는 웹 기반의 모니터링 콘솔을 제공한다. 뒤에서는 주요 웹 콘솔의 주요 기능에 대해서 알아보도록 한다.

Graph View

Graph View는 DAG의 구조를 그래프 형태로 보여주는 뷰이다.


복잡한 워크플로우의 경우 그 구조를 파악하는데 유용한다. 위의 그림은 앞서 만든 hello-airflow 에 대한 태스크간 그래프로 print_date를 호출한 후에, python_operator 태스크를 호출하는 것을 볼 수 있다.

Tree View


트리뷰를 보면, DAG의 구조를 트리 형태로 보여주고, DAG의 태스크가 각각 성공했는지 실패 했는지를 우측 그래프 처럼 표현해준다. 각 태스크를 로그를 보려면 각 태스크 실행 결과 그래프를 누르면 아래와 같이 세부 메뉴가 나온다.



여기서 View Log를 누르면 각 Task 별로 실행 당시의 로그를 볼 수 있다. 아래는 Python_Operator 태스크를 실행한 로그이다.



아래서 두번째 줄을 보면 Hello Airflow 라는 문자열을 리턴한것을 확인할 수 있다.


Task Duration

Task duration은 DAG에서 수행된 각 태스크의 수행 시간을 그래프 형태로 나타내준다.



어떤 태스크가 시간이 많이 걸리는지 그리고 수행시간이 매번 수행할때 마다 올바른지 (큰 변화가 없고 일정한지. 이건 매우 유용할듯) 등을 체크할 수 있다.

Task Tries


Task Tries 에서는 각 수행별로 각각의 태스크를 수행한 횟수를 그래프로 보여준다. 즉 재시도 (RETRY)횟수를 모니터링할 수 있다.


Gantt


Gantt 차트는 각 수행에 대해서 태스크들의 수행 순서에 따라서 소모된 시간과 함께 간트 차트로 표시해준다.

앞의 차트에서 이미 얻을 수 있는 뷰이지만, 각 태스크의 수행 순서와 태스크당 시간을 한꺼번에 보여주기 때문에 병목 구간 파악이 쉽다.


<그림 airflow gantt chart 그래프 예제 (출처 : https://www.agari.com/airflow-agari/) >


이미 링크드인의 Azkaban이나, 스포티파이의 Luigi, 하둡의 Oozie 등 여러가지 워크 플로우 관리 시스템이 있지만, 아직 인큐베이터 단계인 airflow를 주목하는 이유는 분산 환경 지원이 가능하고, 태스크에 대한 스크립트를 파이썬을 사용할 수 있기 때문에, 각종 빅데이타 분석 시스템이나 라이브러리 그리고 머신러닝 시스템과 연동이 쉽고, 파이썬 언어만 알면 쉽게  정교한 플로우 개발이 가능하기 때문에, ( XML등의 설정을 하지 않고도) 활용 가능성이 높다.

저작자 표시 비영리
신고

얼굴 인식 모델을 만들어보자 #4 클라우드를 이용하여 학습 시키기

(머신러닝 학습 및 예측 시스템의 운영환경화)


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

앞에서 모델을 만들고 학습도 다했다. 이제, 이 모델을 실제 운영 환경에서 운영할 수 있는 스케일로 포팅을 하고자 한다.


로컬 환경 대비 실제 운영 환경으로 확장할때 고려해야 하는 사항은


  • 대규모 학습 데이타를 저장할 수 있는 공간

  • 대규모 학습 데이타를 전처리하기 위한 병렬 처리 환경
    이 내용은 이미  http://bcho.tistory.com/1177에서 다루었다.

  • 대규모 학습 데이타를 빠르게 학습 시킬 수 있는 컴퓨팅 파워

  • 학습된 데이타를 이용한 대규모 예측 서비스를 할 수 있는 기능


위의 요건을 만족하면서 텐서플로우로 환경을 올리는 방법은 여러가지가 있지만, 클라우드를 선택하기로 한다.

이유는

  • 첫번째 모델 개발에 집중하고, 텐서플로우의 설치 및 운영 등에 신경쓰지 않도록 한다. 단순한 텐서플로우 설치뿐만 아니라 여러 장비를 동시에 이용하여 분산 학습을 하려면, 클러스터 구성 및 유지가 부담이 된다.

  • 클라우드 컴퓨팅 파워를 이용하여, 대규모 데이타에 대한 전처리를 수행하고 개개별 학습 속도를 높이는 것은 물론이고, 모델을 튜닝하여 동시에 여러 모델을 학습 시킬 수 있다.

  • 대용량 학습 데이타를 저장하기 위한 스토리지 인프라에 대한 구성 및 운영 비용을 절감한다.


즉 설정이나 운영은 클라우드에 맏겨 놓고, 클라우드의 무한한 자원과 컴퓨팅 파워를 이용하여 빠르게 모델을 학습하기 위함이다.

구글 클라우드


아무래도 일하는 성격상 구글 클라우드를 먼저 볼 수 밖에 없는데, 구글 클라우드에서는 텐서플로우의 매니지드 서비스인 CloudML을 제공한다.


CloudML은 별도의 설치나 환경 설정 없이 텐서플로우로 만든 모델을 학습 시키거나 학습된 결과로 예측을 하는 것이 가능하다. 주요 특징을 보면 다음과 같다.


  • 학습시에, 별도의 설정 없이 텐서플로우 클러스터 크기 조절이 가능하다. 싱글 머신에서 부터 GPU 머신 그리고 여러대의 클러스터 머신 사용이 가능하다

  • 하이퍼 패러미터 튜닝이 가능하다. DNN의 네트워크의 폭과 깊이도 하이퍼 패러미터로 지정할 수 있으며, CloudML은 이런 하이퍼패러미터의 최적값을 자동으로 찾아준다.

  • 예측 서비스에서는 Tensorflow Serv를 별도의 빌드할 필요 없이 미리 환경 설정이 다되어 있으며 (bazel 빌드의 끔직함을 겪어보신 분들은 이해하실듯) gRPC가 아닌 간단한 JSON 호출로 예측 (PREDICTION) 요청을 할 수 있다

  • 분당 과금이다. 이게 강력한 기능인데, 구글 클라우드는 기본적으로 분당 과금으로 CPU를 사용하던, GPU를 사용하던 정확히 사용한 만큼만 과금하기 때문에, 필요할때 필요한 만큼만 사용하면 된다. 일부 클라우드의 경우에는 시간당 과금을 사용하기 때문에, 8대의 GPU머신에서 1시간 5분을 학습하더라도 8대에 대해서 2시간 요금을 내야하기 때문에 상대적으로 비용 부담이 높다.

  • 가장 큰 메리트는 TPU (Tensorflow Processing Unit)을 지원한다는 것인데, 딥러닝 전용 GPU라고 생각하면 된다. 일반적인 CPU또는 GPU대비 15~30배 정도 빠른 성능을 제공한다.


    현재는 Close Alpha로 특정 사용자에게만 시범 서비스를 제공하고 있지만 곧 CloudML을 통해서 일반 사용자에게도 서비스가 제공될 예정이다.

CloudML을 이용하여 학습하기

코드 수정

CloudML에서 학습을 시키려면 약간의 코드를 수정해야 한다. 수정해야 하는 이유는 학습 데이타를 같이 올릴 수 없기 때문인데, 여기에는 두 가지 방법이 있다.


  • 학습 데이타를 GCS (Google Cloud Storage)에 올려놓은 후, 학습이 시작되기 전에 로컬 디렉토리로 복사해 오거나

  • 또는 학습 데이타를 바로 GCS로 부터 읽어오도록 할 수 있다.


첫번째 방법은 gsutil 이라는 GCS 명령어를 이용하여 학습 시작전에 GCS에서 학습 데이타를 카피해오면 되고,

두번째 방법은 학습 데이타의 파일명을 GCS 로 지정하면 된다.

예를 들어 텐서 플로우 코드에서 이미지 파일을 아래와 같이 로컬 경로에서 읽어왔다면

   image =  tf.image.decode_jpeg(tf.read_file(“/local/trainingdata/”+image_file),channels=FLAGS.image_color)


GCS에서 읽어오려면 GCS 경로로 바꿔 주면 된다. GCS 버킷명이 terrycho-training-data라고 하면

   image =  tf.image.decode_jpeg(tf.read_file(“gs://terrycho-training-data/trainingdata/”+image_file),channels=FLAGS.image_color)


첫번째 방법의 경우에는 데이타가 아주 많지 않고, 분산 학습이 아닌경우 매우 속도가 빠르다. 두번째 방법의 경우에는 데이타가 아주아주 많아서 분산 학습이 필요할때 사용한다. 아무래도 로컬 파일 억세스가 GCS 억세스 보다 빠르기 때문이다.


다음은 첫번째 방식으로 학습 데이타를 로컬에 복사해서 학습하는 방식의 코드이다.


https://github.com/bwcho75/facerecognition/blob/master/CloudML%20Version/face_recog_model/model_localfile.py

코드 내용은 앞서 만들 모델 코드와 다를것이 없고 단지 아래 부분과, 파일 경로 부분만 다르다

def gcs_copy(source, dest):

   print('Recursively copying from %s to %s' %

       (source, dest))

   subprocess.check_call(['gsutil', '-q', '-m', 'cp', '-R']

       + [source] + [dest]


gcs_copy 함수는 GCS의 source 경로에서 파일을 dest 경로로 복사해주는 명령이다.


def prepare_data():

   # load training and testing data index file into local

   gcs_copy( 'gs://'+DESTINATION_BUCKET+'/'+TRAINING_FILE,'.')

   gcs_copy( 'gs://'+DESTINATION_BUCKET+'/'+VALIDATION_FILE,'.')

   

   # loading training and testing images to local

   image_url = 'gs://'+DESTINATION_BUCKET+'/images/*'


   if not os.path.exists(FLAGS.local_image_dir):

        os.makedirs(FLAGS.local_image_dir)

   gcs_copy( image_url,FLAGS.local_image_dir)

   

prepare_data()    

main()


그리고 prepare_data를 이용해서, 학습과 테스트용 이미지 목록 파일을 복사하고, 이미지들도 로컬에 복사한다.

로컬에 데이타 복사가 끝나면 main()함수를 호출하여 모델을 정의하고 학습을 시작한다.



디렉토리 구조

코드를 수정하였으면, CloudML을 이용하여 학습을 하려면, 파일들을 패키징 해야 한다. 별 다를것은 없고


[작업 디렉토리]

  • __init__.py

  • {모델 파일명}.py


식으로 디렉토리를 구성하면 된다.

얼굴 학습 모델을 model_localfile.py라는 이름으로 저장하였다


명령어

이제 학습용 모델이 준비되었으면, 이 모델을 CloudML에 집어 넣으면 된다.

명령어가 다소 길기 때문에, 쉘 스크립트로 만들어놓거나 또는 파이썬 노트북에 노트 형식으로 만들어 놓으면 사용이 간편하다. 다음은 파이썬 노트북으로 만들어놓은 내용이다.


import google.auth

import os

import datetime


os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/Users/terrycho/keys/terrycho-ml.json"

job_name = 'preparefacedata'+ datetime.datetime.now().strftime('%y%m%d%H%M%S')


리모트로 구글 클라우드의 CloudML을 호출하기 때문에, GOOGLE_APPLICATION_CREDIENTIALS에 서비스 어카운트 파일을 지정한다.

그리고 CloudML에 학습을 실행하면, 각 학습은 JOB으로 등록되는데, 손쉽게 JOB을 찾아서 모니터링 하거나 중지할 수 있도록, JOB ID를 현재 시간으로 생성한다.



print job_name

# Job name whatever you want

JOB_NAME=job_name

# the directory of folder that include your source and init file

PACKAGE_PATH='/Users/terrycho/anaconda/work/face_recog/face_recog_model'

# format: folder_name.source_file_name

MODULE_NAME='face_recog_model.model_localfile'

# bucket you created

STAGING_BUCKET='gs://terrycho-face-recog-stage'

# I recommand "europe-west1" region because there are not enough GPUs in US region for you.....

REGION='us-east1'

# Default is CPU computation. set BASIC_GPU to use Tesla K80 !

SCALE_TIER='BASIC_GPU'


# Submit job with these settings

!gcloud ml-engine jobs submit training $JOB_NAME \

--package-path=$PACKAGE_PATH \

--module-name=$MODULE_NAME \

--staging-bucket=$STAGING_BUCKET \

--region=$REGION \

--scale-tier=$SCALE_TIER \


다음은 cloudml 명령어를 실행하면 된다. 각 인자를 보면

  • JOB_NAME은 학습 JOB의 이름이다.

  • package-path는 __init__.py와 학습 모델 및 관련 파일들이 있는 디렉토리가 된다.

  • module-name은 package-path안에 있는 학습 실행 파일이다.

  • staging-bucket은 CloudML에서 학습 코드를 올리는 임시 Google Cloud Storage로, Google Cloud Storage 만든 후에, 그 버킷 경로를 지정하면 된다.

  • region은 CloudML을 사용한 리전을 선택한다.

  • 마지막으로 scale-tier는 학습 머신의 사이즈를 지정한다.

스케일 티어

설명

BASIC

싱글 머신. CPU

BASIC_GPU

싱글 머신 + K80 GPU

STANDARD_1

분산 머신

PREMIUM_1

대규모 분산 머신

CUSTOM

사용자가 클러스터 크기를 마음대로 설정


일반적인 모델은 BASIC_GPU를 사용하면 되고, 모델이 분산 학습이 가능하도록 개발되었으면 STANDARD_1 이나 PREMIUM_1을 사용하면 된다.


이렇게 명령을 수행하면 모델코드가 CloudML로 전송되고, 전송된 코드는 CloudML에서 실행된다.

학습 모니터링

학습이 시작되면 JOB을 구글 클라우드 콘솔의 CloudML 메뉴에서 모니터링을 할 수 있다.




다음은 CloudML에서의 JOB 목록이다.  (진짜 없어 보인다…)




실행중인 JOB에서 STOP 버튼을 누르면 실행중인 JOB을 정지시킬 수도 있고, View Logs 버튼을 누르면, 학습 JOB에서 나오는 로그를 볼 수 있다. ( 텐서플로우 코드내에서 print로 찍은 내용들도 모두 여기 나온다.)




여기까지 간단하게나마 CloudML을 이용하여 모델을 학습하는 방법을 알아보았다.

본인의 경우 연예인 인식 모델을 MAC PRO 15” i7 (NO GPU)에서 학습한 경우 7000 스텝가지 약 8시간이 소요되었는데, CloudML의 BASIC_GPU를 사용하였을때는 10,000 스탭에 약 1시간 15분 정도 (GCS를 사용하지 않고 직접 파일을 로컬에 복사해놓고 돌린 경우) 가 소요되었다. (빠르다)


여기서 사용된 전체 코드는 https://github.com/bwcho75/facerecognition/tree/master/CloudML%20Version 에 있다.


  • model_gcs.py 는 학습데이타를 GCS에서 부터 읽으면서 학습하는 버전이고

  • model_localfile.py는 학습데이타를 로컬 디스크에 복사해놓고 학습하는 버전이다.


다음 글에서는 학습된 모델을 배포하여 실제로 예측을 실행할 수 있는 API를 개발해보도록 하겠다.

저작자 표시 비영리
신고

연예인 얼굴 인식 모델을 만들어보자

#2 CNN 모델을 만들고 학습 시켜보기

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

선행 학습 자료

이 글은 딥러닝 컨볼루셔널 네트워크 (이하 CNN)을 이용하여 사람의 얼굴을 인식하는 모델을 만드는 튜토리얼이다. 이 글을 이해하기 위해서는 머신러닝과 컨볼루셔널 네트워크등에 대한 사전 지식이 필요한데, 사전 지식이 부족한 사람은 아래 글을 먼저 읽어보기를 추천한다.

 

머신러닝의 개요 http://bcho.tistory.com/1140

머신러닝의 기본 원리는 http://bcho.tistory.com/1139

이산 분류의 원리에 대해서는 http://bcho.tistory.com/1142

인공 신경망에 대한 개념은 http://bcho.tistory.com/1147

컨볼루셔널 네트워크에 대한 개념 http://bcho.tistory.com/1149

학습용 데이타 전처리 http://bcho.tistory.com/1176

학습용 데이타 전처리를 스케일링 하기 http://bcho.tistory.com/1177

손글씨를 CNN을 이용하여 인식하는 모델 만들기 http://bcho.tistory.com/1156

손글씨 인식 CNN 모델을 이용하여 숫자 인식 하기 http://bcho.tistory.com/1157

환경

본 예제는 텐서플로우 1.1과 파이썬 2.7 그리고 Jupyter 노트북 환경 및 구글 클라우드를 사용하여 개발되었다.

준비된 데이타

학습에 사용한 데이타는 96x96 사이즈의 얼굴 이미지로, 총 5명의 사진(안젤리나 졸리, 니콜키드만, 제시카 알바, 빅토리아 베컴,설현)을 이용하였으며, 인당 학습 데이타 40장 테스트 데이타 10장으로 총 250장의 얼굴 이미지를 사용하였다.

사전 데이타를 준비할때, 정면 얼굴을 사용하였으며, 얼굴 각도 변화 폭이 최대한 적은 이미지를 사용하였다. (참고 : https://www.slideshare.net/Byungwook/ss-76098082 ) 만약에 이 모델로 학습이 제대로 되지 않는다면 학습에 사용된 데이타가 적절하지 않은것이기 때문에 데이타를 정재해서 학습하기를 권장한다.

데이타 수집 및 정재 과정에 대한 내용은 http://bcho.tistory.com/1177 를 참고하기 바란다.

 

컨볼루셔널 네트워크 모델

얼굴 인식을 위해서, 머신러닝 모델 중 이미지 인식에 탁월한 성능을 보이는 CNN 모델을 사용하였다. 테스트용 모델이기 때문에 모델은 복잡하지 않게 설계하였다.

 

학습과 예측에 사용되는 이미지는 96x96픽셀의 RGB 컬러 이미지를 사용하였다.

아래 그림과 같은 모델을 사용했는데, 총 4개의 Convolutional 계층과, 2개의 Fully connected 계층, 하나의 Dropout 계층을 사용하였다.


Convolutional 계층의 크기는 각각 16,32,64,128개를 사용하였고, 사용된 Convolutional 필터의 사이즈는 3x3 이다.

Fully connected 계층은 각각 512, 1024를 사용하였고 Dropout 계층에서는 Keep_prob값을 0.7로 둬서 30%의 뉴론이 drop out 되도록 하여 학습을 진행하였다.

 

학습 결과 5개의 카테고리에 대해서 총 200장의 이미지로 맥북 프로 i7 CPU 기준 7000 스텝정도의 학습을 진행한 결과 테스트 정확도 기준 90% 정도의 정확도를 얻을 수 있었다.

코드 설명

텐서플로우로 구현된 코드를 살펴보자

파일에서 데이타 읽기

먼저 학습 데이타를 읽어오는 부분이다.

학습과 테스트에서 읽어드리는 데이타의 포맷은 다음과 같다

 

/Users/terrycho/training_data_class5_40/validate/s1.jpg,Sulhyun,3

이미지 파일 경로, 사람 이름 , 숫자 라벨

 

파일에서 데이타를 읽어서 처리 하는 함수는 read_data_batch(), read_data(), get_input_queue()  세가지 함수가 사용된다.

  • get_input_queue() 함수는 CSV 파일을 한줄씩 읽어서, 파일 경로 및 숫자 라벨 두가지를 리턴할 수 있는 큐를 만들어서 리턴한다.

  • read_data() 함수는 get_input_queue()에서 리턴한 큐로 부터 데이타를 하나씩 읽어서 리턴한다.

  • read_batch_data()함수는 read_data() 함수를 이용하여, 데이타를 읽어서 일정 단위(배치)로 묶어서 리턴을 하고, 그 과정에서 이미지 데이타를 뻥튀기 하는 작업을 한다.

즉 호출 구조는 다음과 같다.

 

read_batch_data():

 → Queue = get_input_queue()

 → image,label = read_data(Queue)

 → image_data = 이미지 데이타 뻥튀기

Return image_data,label

 

실제 코드를 보자

get_input_queue

get_input_queue() 함수는 CSV 파일을 읽어서 image와 labels을 리턴하는 input queue를 만들어서 리턴하는 함수이다.

 

def get_input_queue(csv_file_name,num_epochs = None):

   train_images = []

   train_labels = []

   for line in open(csv_file_name,'r'):

       cols = re.split(',|\n',line)

       train_images.append(cols[0])

       # 3rd column is label and needs to be converted to int type

       train_labels.append(int(cols[2]) )

                           

   input_queue = tf.train.slice_input_producer([train_images,train_labels],

                                              num_epochs = num_epochs,shuffle = True)

   

   return input_queue

 

CSV 파일을 순차적으로 읽은 후에, train_images와 train_labels라는 배열에 넣은 다음 tf.train.slice_input_producer를 이용하여 큐를 만들어냈다. 이때 중요한 점은 shuffle=True라는 옵션을 준것인데, 만약에 이 옵션을 주지 않으면, 학습 데이타를 큐에서 읽을때 CSV에서 읽은 순차적으로 데이타를 리턴한다. 즉 현재 데이타 포맷은 Jessica Alba가 40개, Jolie 가 40개, Nicole Kidman이 40개 .. 식으로 순서대로 들어가 있기 때문에, Jessica Alba를 40개 리턴한 후 Jolie를 40개 리턴하는 식이 된다.  이럴 경우 Convolutional 네트워크가 Jessica Alba에 치우쳐지기 때문에 제대로 학습이 되지 않는다. Shuffle은 필수이다.

read_data()

input_queue에서 데이타를 읽는 부분인데 특이한 점은 input_queue에서 읽어드린 이미지 파일명의 파일을 읽어서 데이타 객체로 저장해야 한다. 텐서플로우에서는 tf.image.decode_jpeg, tf.image.decode_png 등을 이용하여 이러한 기능을 제공한다.

def read_data(input_queue):

   image_file = input_queue[0]

   label = input_queue[1]

   

   image =  tf.image.decode_jpeg(tf.read_file(image_file),channels=FLAGS.image_color)

   

   return image,label,image_file

read_data_batch()

마지막으로 read_data_batch() 함수 부분이다.get_input_queue에서 읽은 큐를 가지고 read_data함수에 넣어서 이미지 데이타와 라벨을 읽어서 리턴하는 값을 받아서 일정 단위로 (배치) 묶어서 리턴하는 함수이다. 중요한 부분이 데이타를 뻥튀기 하는 부분이 있다.

이 모델에서 학습 데이타가 클래스당 40개 밖에 되지 않기 때문에 학습데이타가 부족하다. 그래서 여기서 사용한 방법은 read_data에서 리턴된 이미지 데이타에 대해서 tf.image.random_xx 함수를 이용하여 좌우를 바꾸거나, brightness,contrast,hue,saturation 함수를 이용하여 매번 색을 바꿔서 리턴하도록 하였다.

 

def read_data_batch(csv_file_name,batch_size=FLAGS.batch_size):

   input_queue = get_input_queue(csv_file_name)

   image,label,file_name= read_data(input_queue)

   image = tf.reshape(image,[FLAGS.image_size,FLAGS.image_size,FLAGS.image_color])

   

   # random image

   image = tf.image.random_flip_left_right(image)

   image = tf.image.random_brightness(image,max_delta=0.5)

   image = tf.image.random_contrast(image,lower=0.2,upper=2.0)

   image = tf.image.random_hue(image,max_delta=0.08)

   image = tf.image.random_saturation(image,lower=0.2,upper=2.0)

   

   batch_image,batch_label,batch_file = tf.train.batch([image,label,file_name],batch_size=batch_size)

   #,enqueue_many=True)

   batch_file = tf.reshape(batch_file,[batch_size,1])

 

   batch_label_on_hot=tf.one_hot(tf.to_int64(batch_label),

       FLAGS.num_classes, on_value=1.0, off_value=0.0)

   return batch_image,batch_label_on_hot,batch_file

 

그리고 마지막 부분에 label을 tf.one_hot을 이용해서 변환한것을 볼 수 있는데, 입력된 label은 0,1,2,3,4 과 같은 단일 정수이다. 그런데, CNN에서 나오는 결과는 정수가 아니라 클래스가 5개인 (분류하는 사람이 5명이기 때문에) 행렬이다. 즉 Jessica Alba일 가능성이 90%이고, Jolie일 가능성이 10%이면 결과는 [0.9,0.1,0,0,0] 식으로 리턴이 되기 때문에, 입력된 라벨 0은 [1,0,0,0,0], 라벨 1은 [0,1,0,0,0] 라벨 2는 [0,0,1,0,0] 식으로 변환되어야 한다. tf.one_hot 이라는 함수가 이 기능을 수행해준다.

 

모델 코드

모델은 앞서 설명했듯이 4개의 Convolutional 계층과, 2개의 Fully connected 계층 그리고 Dropout 계층을 사용한다. 각각의 계층별로는 코드가 다르지 않고 인지만 다르니 하나씩 만 설명하도록 한다.

 

Convolutional 계층

아래 코드는 두번째 Convolutional 계층의 코드이다.

  • FLAGS.conv2_layer_size 는 이 Convolutional 계층의 뉴런의 수로 32개를 사용한다.

  • FLAGS.conv2_filter_size 는 필터 사이즈를 지정하는데, 3x3 을 사용한다.

  • FLAGS.stride2 = 1 는 필터의 이동 속도로 한칸씩 이동하도록 정의했다.

 

# convolutional network layer 2

def conv2(input_data):

   FLAGS.conv2_filter_size = 3

   FLAGS.conv2_layer_size = 32

   FLAGS.stride2 = 1

   

   with tf.name_scope('conv_2'):

       W_conv2 = tf.Variable(tf.truncated_normal(

                       [FLAGS.conv2_filter_size,FLAGS.conv2_filter_size,FLAGS.conv1_layer_size,FLAGS.conv2_layer_size],

                                             stddev=0.1))

       b2 = tf.Variable(tf.truncated_normal(

                       [FLAGS.conv2_layer_size],stddev=0.1))

       h_conv2 = tf.nn.conv2d(input_data,W_conv2,strides=[1,1,1,1],padding='SAME')

       h_conv2_relu = tf.nn.relu(tf.add(h_conv2,b2))

       h_conv2_maxpool = tf.nn.max_pool(h_conv2_relu

                                       ,ksize=[1,2,2,1]

                                       ,strides=[1,2,2,1],padding='SAME')

       

       

   return h_conv2_maxpool

 

다음 Weight 값 W_conv2 와 Bias 값 b2를 지정한후에, 간단하게 tf.nn.conv2d 함수를 이용하면 2차원의 Convolutional 네트워크를 정의해준다. 다음 결과가 나오면 이 결과를 액티베이션 함수인 relu 함수에 넣은 후에, 마지막으로 max pooling 을 이용하여 결과를 뽑아낸다.

 

각 값의 의미에 대해서는 http://bcho.tistory.com/1149 의 컨볼루셔널 네트워크 개념 글을 참고하기 바란다.

같은 방법으로 총 4개의 Convolutional 계층을 중첩한다.

 

Fully Connected 계층

앞서 정의한 4개의 Convolutional 계층을 통과하면 다음 두개의 Fully Connected 계층을 통과하게 되는데 모양은 다음과 같다.

  • FLAGS.fc1_layer_size = 512 를 통하여 Fully connected 계층의 뉴런 수를 512개로 지정하였다.

 

# fully connected layer 1

def fc1(input_data):

   input_layer_size = 6*6*FLAGS.conv4_layer_size

   FLAGS.fc1_layer_size = 512

   

   with tf.name_scope('fc_1'):

       # 앞에서 입력받은 다차원 텐서를 fcc에 넣기 위해서 1차원으로 피는 작업

       input_data_reshape = tf.reshape(input_data, [-1, input_layer_size])

       W_fc1 = tf.Variable(tf.truncated_normal([input_layer_size,FLAGS.fc1_layer_size],stddev=0.1))

       b_fc1 = tf.Variable(tf.truncated_normal(

                       [FLAGS.fc1_layer_size],stddev=0.1))

       h_fc1 = tf.add(tf.matmul(input_data_reshape,W_fc1) , b_fc1) # h_fc1 = input_data*W_fc1 + b_fc1

       h_fc1_relu = tf.nn.relu(h_fc1)

   

   return h_fc1_relu

 

Fully connected 계층은 단순하게 relu(W*x + b) 함수이기 때문에 이 함수를 위와 같이 그대로 적용하였다.

마지막 계층

Fully connected 계층을 거쳐 나온 데이타는 Dropout 계층을 거친후에, 5개의 카테고리에 대한 확률로 결과를 내기 위해서 final_out 계층을 거치게 되는데, 이 과정에서 softmax 함수를 사용해야 하나, 학습 과정에서는 별도로 softmax 함수를 사용하지 않는다. softmax는 나온 결과의 합이 1.0이 되도록 값을 변환해주는 것인데, 학습 과정에서는 5개의 결과 값이 어떤 값이 나오던 가장 큰 값에 해당하는 것이 예측된 값이기 때문에, 그 값과 입력된 라벨을 비교하면 되기 때문이다.

즉 예를 들어 Jessica Alba일 확률이 100%면 실제 예측에서는 [1,0,0,0,0] 식으로 결과가 나와야 되지만, 학습 중는 Jessica Alaba 로 예측이 되었다고만 알면 되기 때문에 결과가 [1292,-0.221,-0.221,-0.221] 식으로 나오더라도 최대값만 찾으면 되기 때문에 별도로 softmax 함수를 적용할 필요가 없다. Softmax 함수는 연산 비용이 큰 함수이기 때문에 일반적으로 학습 단계에서는 적용하지 않는다.

 

마지막 계층의 코드는 다음과 같다.

# final layer

def final_out(input_data):

 

   with tf.name_scope('final_out'):

       W_fo = tf.Variable(tf.truncated_normal([FLAGS.fc2_layer_size,FLAGS.num_classes],stddev=0.1))

       b_fo = tf.Variable(tf.truncated_normal(

                       [FLAGS.num_classes],stddev=0.1))

       h_fo = tf.add(tf.matmul(input_data,W_fo) , b_fo) # h_fc1 = input_data*W_fc1 + b_fc1

       

   # 최종 레이어에 softmax 함수는 적용하지 않았다.

       

   return h_fo

전체 네트워크 모델 정의

이제 각 CNN의 각 계층을 함수로 정의 하였으면 각 계층을 묶어 보도록 하자. 묶는 법은 간단하다 앞 계층에서 나온 계층을 순서대로 배열하고 앞에서 나온 결과를 뒤의 계층에 넣는 식으로 묶으면 된다.

 

# build cnn_graph

def build_model(images,keep_prob):

   # define CNN network graph

   # output shape will be (*,48,48,16)

   r_cnn1 = conv1(images) # convolutional layer 1

   print ("shape after cnn1 ",r_cnn1.get_shape())

   

   # output shape will be (*,24,24,32)

   r_cnn2 = conv2(r_cnn1) # convolutional layer 2

   print ("shape after cnn2 :",r_cnn2.get_shape() )

   

   # output shape will be (*,12,12,64)

   r_cnn3 = conv3(r_cnn2) # convolutional layer 3

   print ("shape after cnn3 :",r_cnn3.get_shape() )

 

   # output shape will be (*,6,6,128)

   r_cnn4 = conv4(r_cnn3) # convolutional layer 4

   print ("shape after cnn4 :",r_cnn4.get_shape() )

   

   # fully connected layer 1

   r_fc1 = fc1(r_cnn4)

   print ("shape after fc1 :",r_fc1.get_shape() )

 

   # fully connected layer2

   r_fc2 = fc2(r_fc1)

   print ("shape after fc2 :",r_fc2.get_shape() )

   

   ## drop out

   # 참고 http://stackoverflow.com/questions/34597316/why-input-is-scaled-in-tf-nn-dropout-in-tensorflow

   # 트레이닝시에는 keep_prob < 1.0 , Test 시에는 1.0으로 한다.

   r_dropout = tf.nn.dropout(r_fc2,keep_prob)

   print ("shape after dropout :",r_dropout.get_shape() )

   

   # final layer

   r_out = final_out(r_dropout)

   print ("shape after final layer :",r_out.get_shape() )

 

   return r_out

 

이 build_model 함수는 image 를 입력 값으로 받아서 어떤 카테고리에 속할지를 리턴하는 컨볼루셔널 네트워크이다.  중간에 Dropout 계층이 추가되어 있는데, tf.nn.dropout함수를 이용하면 간단하게 dropout 계층을 구현할 수 있다. r_fc2는 Dropout 계층 앞의 Fully Connected 계층에서 나온 값이고,  두번째 인자로 남긴 keep_prob는 Dropout 비율이다.

 

   r_dropout = tf.nn.dropout(r_fc2,keep_prob)

   print ("shape after dropout :",r_dropout.get_shape() )

 

모델 학습

데이타를 읽는 부분과 학습용 모델 정의가 끝났으면 실제로 학습을 시켜보자

 

def main(argv=None):

   

   # define placeholders for image data & label for traning dataset

   

   images = tf.placeholder(tf.float32,[None,FLAGS.image_size,FLAGS.image_size,FLAGS.image_color])

   labels = tf.placeholder(tf.int32,[None,FLAGS.num_classes])

   image_batch,label_batch,file_batch = read_data_batch(TRAINING_FILE)

 

먼저 학습용 모델에 넣기 위한 image 데이타를 읽어드릴 placeholder를 images로 정의하고, 다음으로 모델에 의해 계산된 결과와 비교하기 위해서 학습데이타에서 읽어드린 label 데이타를 저장하기 위한 placeholder를 labels로 정의한다. 다음 image_batch,label_batch,fle_batch 변수에 배치로 학습용 데이타를 읽어드린다. 그리고 dropout 계층에서 dropout 비율을 지정할 keep_prob를 place holder로 정의한다.

각 변수가 지정되었으면, build_model 함수를 호출하여, images 값과 keep_prob 값을 넘겨서 Convolutional 네트워크에 값을 넣도록 그래프를 정의하고 그 결과 값을 prediction으로 정의한다.

 

   keep_prob = tf.placeholder(tf.float32) # dropout ratio

   prediction = build_model(images,keep_prob)

   # define loss function

   loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=prediction,labels=labels))

   tf.summary.scalar('loss',loss)

 

   #define optimizer

   optimizer = tf.train.AdamOptimizer(FLAGS.learning_rate)

   train = optimizer.minimize(loss)

 

중간 중간에 학습 과정을 시각화 하기 위해서 tf.summary.scalar 함수를 이용하여 loss 값을 저장하였다.

 

그래프 생성이 완료 되었으면, 학습에서 계산할 비용 함수를 정의한다. 비용함수는 sofrmax cross entopy 함수를 이용하여, 모델에 의해서 예측된 값 prediction 과, 학습 파일에서 읽어드린 label 값을 비교하여 loss 값에 저장한다.

그리고 이 비용 최적화 함수를 위해서 옵티마이져를 AdamOptimizer를 정의하여, loss 값을 최적화 하도록 하였다.

 

학습용 모델 정의와, 비용 함수, 옵티마이저 정의가 끝났으면 학습 중간 중간 학습된 모델을 테스트하기 위한 Validation 관련 항목등을 정의한다.

 

   # for validation

   #with tf.name_scope("prediction"):

   validate_image_batch,validate_label_batch,validate_file_batch = read_data_batch(VALIDATION_FILE)

   label_max = tf.argmax(labels,1)

   pre_max = tf.argmax(prediction,1)

   correct_pred = tf.equal(tf.argmax(prediction,1),tf.argmax(labels,1))

   accuracy = tf.reduce_mean(tf.cast(correct_pred,tf.float32))

           

   tf.summary.scalar('accuracy',accuracy)

      

   startTime = datetime.now()

 

학습용 데이타가 아니라 검증용 데이타를 VALIDATION_FILE에서 읽어서 데이타를 validate_image_batch,validate_label_batch,validate_file_batch에 저장한다. 다음, 정확도 체크를 위해서 학습에서 예측된 라벨값과, 학습 데이타용 라벨값을 비교하여 같은지 틀린지를 비교하고, 이를 가지고 평균을 내서 정확도 (accuracy)로 사용한다.

 

학습용 모델과, 테스트용 데이타 등이 준비되었으면 이제 학습을 시작한다.

학습을 시직하기 전에, 학습된 모델을 저장하기 위해서 tf.train.Saver()를 지정한다. 그리고, 그래프로 loss와 accuracy등을 저장하기 위해서 Summary write를 저장한다.

다음 tf.global_variable_initializer()를 수행하여 변수를 초기화 하고, queue에서 데이타를 읽기 위해서 tf.train.Corrdinator를 선언하고 tf.start_queue_runners를 지정하여, queue 러너를 실행한다.

 

   #build the summary tensor based on the tF collection of Summaries

   summary = tf.summary.merge_all()

   

   with tf.Session(config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)) as sess:

       saver = tf.train.Saver() # create saver to store training model into file

       summary_writer = tf.summary.FileWriter(FLAGS.log_dir,sess.graph)

       

       init_op = tf.global_variables_initializer() # use this for tensorflow 0.12rc0

       coord = tf.train.Coordinator()

       threads = tf.train.start_queue_runners(sess=sess, coord=coord)

       sess.run(init_op)

 

변수 초기화와 세션이 준비되었기 때문에 이제 학습을 시작해보자. for 루프를 이용하여 총 10,000 스텝의 학습을 하도록 하였다.

 

       for i in range(10000):

           images_,labels_ = sess.run([image_batch,label_batch])

 

다음 image_batch와 label_batch에서 값을 읽어서 앞에서 정의한 모델에 넣고 train 그래프 (AdamOptimizer를 정의한)를 실행한다.

 

           sess.run(train,feed_dict={images:images_,labels:labels_,keep_prob:0.7})

 

이때 앞에서 읽은 images_와, labels_ 데이타를 피딩하고 keep_prob 값을 0.7로 하여 30% 정도의 값을 Dropout 시킨다.

 

다음 10 스텝 마다 학습 상태를 체크하도록 하였다.

           

           if i % 10 == 0:

               now = datetime.now()-startTime

               print('## time:',now,' steps:',i)         

               

               # print out training status

               rt = sess.run([label_max,pre_max,loss,accuracy],feed_dict={images:images_

                                                         , labels:labels_

                                                         , keep_prob:1.0})

               print ('Prediction loss:',rt[2],' accuracy:',rt[3])

위와 같이 loss 값과 accuracy 값을 받아서 출력하여 현재 모델의 비용 함수 값과 정확도를 측정하고

 

               # validation steps

               validate_images_,validate_labels_ = sess.run([validate_image_batch,validate_label_batch])

               rv = sess.run([label_max,pre_max,loss,accuracy],feed_dict={images:validate_images_

                                                         , labels:validate_labels_

                                                         , keep_prob:1.0})

               print ('Validation loss:',rv[2],' accuracy:',rv[3])

학습용 데이타가 아니라 위와 같이 테스트용 데이타를 피딩하여, 테스트용 데이타로 정확도를 검증한다. 이때 keep_prob를 1.0으로 해서 Dropout 없이 100% 네트워크를 활용한다.

 

               if(rv[3] > 0.9):

                   Break

 

만약에 테스트 정확도가 90% 이상이면 학습을 멈춘다. 그리고 아래와 같이 Summary

 

               # validation accuracy

               summary_str = sess.run(summary,feed_dict={images:validate_images_

                                                         , labels:validate_labels_

                                                         , keep_prob:1.0})

 

               summary_writer.add_summary(summary_str,i)

               summary_writer.flush()

 

마지막으로 다음과 같이 학습이 다된 모델을 saver.save를 이용하여 저장하고, 사용된 리소스들을 정리한다.

       saver.save(sess, 'face_recog') # save session

       coord.request_stop()

       coord.join(threads)

       print('finish')

   

main()

 

이렇게 학습을 끝내면 본인의 경우 약 7000 스텝에서 테스트 정확도 91%로 끝난것을 확인할 수 있다.

 

아래는 텐서보드를 이용하여 학습 과정을 시각화한 내용이다.

 


 

코드는 공개가 가능하지만 학습에 사용한 데이타는 저작권 문제로 공유가 불가능하다. 약 200장의 사진만 제대로 수집을 하면 되기 때문에 각자 수집을 해서 학습을 도전해보는 것을 권장한다. (더 많은 인물에 대한 시도를 해보는것도 좋겠다.)

정리 하며

혹시나 이 튜토리얼을 따라하면서 학습 데이타를 공개할 수 있는 분들이 있다면 다른 분들에게도 많은 도움이 될것이라고 생각한다. 가능하면 데이타가 공개되었으면 좋겠다.

전체 코드는 https://github.com/bwcho75/facerecognition/blob/master/1.%2BFace%2BRecognition%2BTraining.ipynb 에 있다.

그리고 직접 사진을 수집해보면, 데이타 수집 및 가공이 얼마나 어려운지 알 수 있기 때문에 직접 한번 시도해보는 것도 권장한다. 아래는 크롬브라우져 플러그인으로 구글 검색에서 나온 이미지를 싹 긁을 수 있는 플러그인이다. Bulk Download Images (ZIG)

https://www.youtube.com/watch?v=k5ioaelzEBM

 



이 플러그인을 이용하면 손쉽게 특정 인물의 데이타를 수집할 수 있다.

다음 글에서는 학습이 끝난 데이타를 이용해서 실제로 예측을 해보는 부분에 대해서 소개하도록 하겠다.

 

 

 

저작자 표시 비영리
신고


Machine Learning Pipeline


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

대부분 모델 개발과 알고리즘에 집중

머신러닝을 공부하고 나서는 주로 통계학이나, 모델 자체에 많은 공부를 하는 노력을 드렸었다. 선형대수나 미적분 그리고 방정식에 까지 기본으로 돌아가려고 노력을 했었고, 그 중간에 많은 한계에도 부딪혔지만, 김성훈 교수님의 모두를 위한 딥러닝 강의를 접하고 나서, 수학적인 지식도 중요하지만 수학적인 깊은 지식이 없어도 모델 자체를 이해하고 근래에 발전된 머신러닝 개발 프레임웍을 이용하면 모델 개발이 가능하다는 것을 깨달았다.

 

계속해서 모델을 공부하고, 머신러닝을 공부하는 분들을 관심있게 지켜보고 실제 머신러닝을 사용하는 업무들을 살펴보니 재미있는 점이 모두 모델 자체 개발에만 집중한다는 것이다. 커뮤니티에 올라오는 글의 대부분은 어떻게 모델을 구현하는지 어떤 알고리즘을 사용하는지에 대한 내용들이 많았고, 실 업무에 적용하는 분들을 보면 많은 곳들이 R을 이용하여 데이타를 분석하고 모델링을 하는데, 데이타를 CSV 파일 형태로 다운 받아서 정재하고 데이타를 분석하고 모델을 개발하는 곳이 많은 것을 보았다. 데이타의 수집 및 전처리 및 개발된 모델에 대한 서비스에 대해서는 상대적으로 많은 정보를 접하지 못했는데, 예상하기로 대부분 모델 개발에 집중하기 때문이 아닌가 싶다.

 

엔지니어 백그라운드를 가진 나로써는 CSV로 데이타를 끌어다가 정재하고 분석하는 것이 매우 불편해 보이고 이해가 되지 않았다. 빅데이타 분석 시스템에 바로 연결을 하면, CSV로 덤프 받고 업로드 하는 시간등에 대한 고민이 없을텐데.” 왜 그렇게 할까 ?”라는 의문이 계속 생기기 시작하였다.

미니 프로젝트를 시작하다

이런 의문을 가지던중 CNN 네트워크 모델에 대한 대략적인 학습이 끝나고, 실제로 적용하면서 경험을 쌓아보기로 하였다. 그래서 얼굴 인식 모델 개발을 시작하였다. CNN 모델이라는 마법을 사용하면 쉽게 개발이 될줄 알았던 프로젝트가 벌써 몇달이 되어 간다. 학습용 데이타를 구하고, 이를 학습에 적절하도록 전처리 하는 과정에서 많은 실수가 있었고, 그 과정에서 많은 재시도가 있었다.

 

(자세한 내용은 http://bcho.tistory.com/1174 , https://www.slideshare.net/Byungwook/ss-76098082 를 참조)

 

특히나 데이타 자체를 다시 처리해야 하는 일이 많았기 때문에, 데이타 전처리 코드를 지속적으로 개선하였고 개선된 코드를 이용하여 데이타를 지속적으로 다시 처리해서 데이타의 품질을 높여나갔는데, 처리 시간이 계속해서 많이 걸렸다.

자동화와 스케일링의 필요성

특히 이미지 전처리 부분은 사진에서 얼굴이 하나만 있는 사진을 골라내고 얼굴의 각도와 선글라스 유무등을 확인한후 사용 가능한 사진에서 얼굴을 크롭핑하고 학습용 크기로 리사이즈 하는 코드였는데 (자세한 내용 http://bcho.tistory.com/1176) 싱글 쓰레드로 만들다 보니 아무래도 시간이 많이 걸렸다. 실제 운영환경에서는 멀티 쓰레드 또는 멀티 서버를 이용하여 스케일링을 할 필요가 있다고 느꼈다.

 

또한 이미지 수집에서 부터 필터링, 그리고 학습 및 학습된 모델의 배포와 서비스 까지 이 전 과정을 순차적으로 진행을 하되 반복적인 작업이기 때문에 자동화할 필요성이 있다고 생각했다.

아이 체중 예측 모델을 통한 파이프라인에 대한 이해

그러던 중에 팀 동료로 부터 좋은 예제 하나를 전달 받게 되었다.

미국 아기들의 환경에 따른 출생 체중을 예측하는 간단한 선형 회귀 모델을 구현한 파이썬 노트북인데 (https://github.com/GoogleCloudPlatform/training-data-analyst/blob/master/blogs/babyweight/babyweight.ipynb) 하나의 노트북에 전체 단계를 모두 구현해놓았다.

 


 

데이타에 대한 분석을 통한 데이타 특성 추출, 추출된 특성을 통한 모델 개발, 모델 학습을 위한 데이타 전처리 그리고 학습 및 학습된 모델을 통한 예측 서비스 까지 모든 과정을 하나의 노트북에 구현해놓았다.

(시간이 있으면 꼭 보기를 강력 추천한다.)

 

흥미로운 점이 데이타 전처리를 Apache Beam이라는 데이타 처리 플랫폼을 썼고, 그 전처리 코드를 파이썬 노트북에 하나로 다 정리한것이다. (실제로 수행은 로컬에서도 가능하지만, 클라우드에서도 실행이 가능해서 충분한 스케일링을 지원한다.)

 

Apache Beam의 구글의 빅데이타 분석 프레임웍으로 Apache Spark 과 같은 프레임웍이라고 보면된다. Google Dataflow라는 이름으로 구글 클라우드에서 서비스가 되는데, Apache Beam이라는 오픈소스로 공개가 되었다. ( http://bcho.tistory.com/1123 http://bcho.tistory.com/1122 http://bcho.tistory.com/1124 )

 

아 이렇게 하는구나 하는 생각이 들었고, 그즘 실무에서 이와 같은 흐름으로 실제로 머신러닝을 수행하는 것을 볼 기회가 있었다.

데이타 전처리를 스케일링하다.

서비스가 가능한 수준의 전체 머신러닝 서비스 파이프라인을 만들어보고 싶어졌다. 마침 또 Apache Beam의 경우에는 예전에 Java 코드로 실시간 분석을 해본 경험이 있고 이번에 2.0 버전이 릴리즈 되서 이번에는 2.0에서 파이썬을 공부해보기로 하고 개발에 들어갔다.

 

특히 기존의 데이타 전처리 코드는 싱글 쓰레드로 돌기 때문에 스케일링에 문제가 있었지만, Apache Beam을 사용할 경우 멀티 쓰레드 뿐만 아니라 동시에 여러대의 머신에서 돌릴 수 있고 이러한 병렬성에 대해서는 크게 고민을 하지 않아도 Apache Beam이 이 기능을 다 제공해준다. 또한 이 데이타 전처리 코드를 돌릴 런타임도 별도 설치할 필요가 없이 커멘드 하나로 구글 클라우드에서 돌릴 수 가 있다. (직업 특성상 클라우드 자원을 비교적 자유롭게 사용할 수 있었다.)

 

Apache Beam으로 전처리 코드를 컨버팅 한결과 기존 싱글 쓰레드 파이썬 코드가 400~500장의 이미지 전처리에 1~2시간이 걸렸던 반면, 전환후에 대략 15~17분이면 끝낼 수 있었다. 전처리 중에는 서버의 대수가 1대에서 시작해서 부하가 많아지자 자동으로 5대까지 늘어났다. 이제는 아무리 많은 데이타가 들어오더라도 서버의 대수만 단순하게 늘리면 수분~수십분내에 수십,수만장의 데이타 처리가 가능하게 되었다.


<그림. Apache Beam 기반의 이미지 전처리 시스템 실행 화면 >

 

Apache Beam 기반의 이미지 전처리 코드는 https://github.com/bwcho75/facerecognition/blob/master/Preprocess%2Bface%2Brecognition%2Bdata%2Band%2Bgenerate%2Btraining%2Bdata.ipynb 에 공개해 놨다.

 

머신러닝 파이프라인 아키텍쳐와 프로세스

이번 과정을 통해서 머신러닝의 학습 및 예측 시스템 개발이 어느 정도 정형화된 프로세스화가 가능하고 시스템 역시 비슷한 패턴의 아키텍쳐를 사용할 수 있지 않을까 하는 생각이 들었고, 그 내용을 아래와 같이 정리한다.

파이프라인 개발 프로세스

지금까지 경험한 머신러닝 개발 프로세스는 다음과 같다.

 

  1. 데이타 분석
    먼저 머신러닝에 사용할 전체 데이타셋을 분석한다. 그래프도 그려보고 각 변수간의 연관 관계나 분포도를 분석하여, 학습에 사용할 변수를 정의하고 어떤 모델을 사용할지 판단한다.

  2. 모델 정의
    분석된 데이타를 기반으로 모델을 정의하고, 일부 데이타를 샘플링하여 모델을 돌려보고 유효한 모델인지를 체크한다. 모델이 유효하지 않다면 변수와 모델을 바꿔 가면서 최적의 모델을 찾는다.

  3. 데이타 추출 및 전처리
    유효한 모델이 개발이 되면, 일부 데이타가 아니라 전체 데이타를 가지고 학습을 한다. 전체 데이타를 추출해서 모델에 넣어서 학습을 하려면 데이타의 크기가 크면 매번 매뉴얼로 하기가 어렵기 때문에 데이타 추출 및 전처리 부분을 자동화 한다.   

  4. 전체 데이타를 이용한 반복 학습 및 튜닝
    모델 자체가 유효하다고 하더라도 전체 데이타를 가지고 학습 및 검증을 한것이 아니기 때문에 의외의 데이타가 나오거나 전처리에 의해서 필터링되지 않은 데이타가 있을 수 있기 때문에 지속적으로 데이타 추출 및 전처리 모듈을 수정해야 하고, 마찬가지로 모델 역시 정확도를 높이기 위해서 지속적으로 튜닝을 한다. 이 과정에서 전체 데이타를 다루기 때문에 모델 역시 성능을 위해서 분산형 구조로 개선되어야 한다.

  5. 모델 배포
    학습 모델이 완성되었으면 학습된 모델을 가지고 예측을 할 수 있는 시스템을 개발하고 이를 배포한다.

  6. 파이프라인 연결 및 자동화
    머신러닝의 모델은 위의 과정을 통해서 만들었지만, 데이타가 앞으로도 지속적으로 들어올 것이고 지속적인 개선이 필요하기 때문에 이 전과정을 자동화 한다. 이때 중요한것은 데이타 전처리, 학습, 튜닝, 배포등의 각 과정을 물 흐르듯이 연결하고 자동화를 해야 하는데 이렇게 데이타를 흐르는 길을 데이타 플로우라고 한다. (흔히 Luigi, Rundeck, Airflow와 같은 데이타플로우 오케스트레이션 툴을 이용한다)

 

전체적인 프로세스에 대해서 좋은 영상이 있어서 공유한다.


아키텍쳐

위의 프로세스를 기반으로한 머신러닝 파이프라인 아키텍쳐 는 다음과 같다.


 

 

Inputs

머신 러닝 파이프라인의 가장 처음단은 데이타를 수집하고 이 수집된 데이타를 저장하는 부분이다.

데이타 수집은 시간,일,주,월과 같이 주기적으로 데이타를 수집하는 배치 프로세싱과, 실시간으로 데이타를 수집하는 리얼타임 프로세싱 두가지로 나뉘어 진다. 이 두 파이프라인을 통해서 데이타 소스로 부터 데이타를 수집하고 필터링하고 정재하여, 데이타 레이크에 저장한다. 이 구조는 일반적인 빅데이타 분석 시스템의 구조와 유사하다. (참고 자료 http://bcho.tistory.com/984 http://bcho.tistory.com/671 )

 

개인적으로 머신러닝을 위해서 중요한 부분 중 하나는 데이타 레이크를 얼마나 잘 구축하느냐이다. 데이타 레이크는 모든 데이타가 모여 있는 곳으로 보통 데이타 레이크를 구축할때는 많은 데이타를 모으는 데만 집중하는데, 데이타를 잘 모으는 것은 기본이고 가장 중요한 점은 이 모여 있는 데이타에 대한 접근성을 제공하는 것이다.

 

무슨 이야기인가 하면, 보통 머신러닝 학습을 위해서 학습 데이타를 받거나 또는 데이타에 대한 연관성 분석등을 하기 위해서는 데이타 레이크에서 데이타를 꺼내오는데, 데이타 레이크를 개발 운영 하는 사람과 데이타를 분석하고 머신러닝 모델을 만드는 사람은 보통 다르기 때문에, 모델을 만드는 사람이 데이타 레이크를 운영하는 사람에게 “무슨 무슨 데이타를 뽑아서 CSV로 전달해 주세요.” 라고 이야기 하는 것이 보통이다. 그런데 이 과정이 번거롭기도 하고 시간이 많이 걸린다.

가장 이상적인 방법은 데이타를 분석하고 모델링 하는 사람이 데이타 레이크 운영팀에 부탁하지 않고서도 손쉽고 빠르게 데이타에 접근해서 데이타를 읽어오고 분석을 할 수 있어야 한다.

직업 특성상 구글의 빅쿼리를 많이 접하게 되는데, 빅쿼리는 대용량 데이타를 저장할 수 있을 뿐만 아니라 파이썬 노트북이나 R 스튜디오 플러그인을 통해서 바로 데이타를 불러와서 분석할 수 있다.  


<그림 INPUT 계층의 빅데이타 저장 분석 아키텍쳐>

Pre processing & Asset creation

Pre processing은 수집한 데이타를 학습 시스템에 넣기 위해서 적절한 데이타만 필터링하고 맞는 포맷으로 바꾸는 작업을 한다. 작은 모델이나 개발등에서는 샘플링된 데이타를 로컬에서 내려 받아서 R이나 numpy/pandas등으로 작업이 가능하지만, 데이타가 수테라에서 수백테라이상이 되는 빅데이타라면 로컬에서는 작업이 불가능하기 때문에, 데이타 전처리 컴포넌트를 만들어야 한다.

일반적으로 빅데이타 분석에서 사용되는 기술을 사용하면 되는데, 배치성 전처리는 하둡이나 스파크와 같은 기술이 보편적으로 사용되고 실시간 스트리밍 분석은 스파크 스트리밍등이 사용된다.


Train

학습은 전처리된 데이타를 시스템에 넣어서 모델을 학습 시키는 단계이다. 이 부분에서 생각해야 할점은 첫번째는 성능 두번째는 튜닝이다. 성능 부분에서는 GPU등을 이용하여 학습속도를 늘리고 여러대의 머신을 연결하여 학습을 할 수 있는 병렬성이 필요하다. 작은 모델의 경우에는 수시간에서 하루 이틀 정도 소요되겠지만 모델이 크면 한달 이상이 걸리기 때문에 고성능 하드웨어와 병렬 처리를 통해서 학습 시간을 줄이는 접근이 필요하다. 작은 모델의 경우에는 NVIDIA GPU를 데스크탑에 장착해놓고 로컬에서 돌리는 것이 가성비 적으로 유리하고, 큰 모델을 돌리거나 동시에 여러 모델을 학습하고자 할때는 클라우드를 사용하는 것이 절대 적으로 유리하다 특히 구글 클라우드의 경우에는  알파고에서 사용된 GPU의 다음 세대인 TPU (텐서플로우 전용 딥러닝 CPU)를 제공한다. https://cloud.google.com/tpu/ CPU나 GPU대비 최대 15~30배 정도의 성능 차이가 난다.

 

 

학습 단계에서는 세부 변수를 튜닝할 필요가 있는데, 예를 들어 학습 속도나 뉴럴 네트워크의 폭이나 깊이, 드롭 아웃의 수, 컨볼루셔널 필터의 크기등등이 있다. 이러한 변수들을 하이퍼 패러미터라고 하는데, 학습 과정에서 모델의 정확도를 높이기 위해서 이러한 변수들을 자동으로 튜닝할 수 있는 아키텍쳐를 가지는 것이 좋다.

 

텐서플로우등과 같은 머신러닝 전용 프레임웍을 사용하여 직접 모델을 구현하는 방법도 있지만, 모델의 난이도가 그리 높지 않다면 SparkML등과 같이 미리 구현된 모델의 런타임을 사용하는 방법도 있다.

Predict

Predict에서는 학습된 모델을 이용하여 예측 기능을 서비스 하는데, 텐서플로우에서는  Tensorflow Serv를 사용하면 되지만, Tensorflow Serv의 경우에는 bazel 빌드를 이용하여 환경을 구축해야 하고, 대규모 서비를 이용한 분산 환경 서비스를 따로 개발해야 한다. 거기다가 인터페이스가 gRPC이다. (귀찮다.)

구글 CloudML의 경우에는 별도의 빌드등도 필요 없고 텐서 플로우 모델만 배포하면 대규모 서비스를 할 수 있는 런타임이 바로 제공되고 무엇보다 gRPC 인터페이스뿐만 아니라 HTTP/REST 인터페이스를 제공한다. 만약에 Production에서 머신러닝 모델을 서비스하고자 한다면 구글 CloudML을 고려해보기를 권장한다.

Dataflow Orchestration

이 전과정을 서로 유기적으로 묶어 주는 것을 Dataflow Orchestration이라고 한다.

예를 들어 하루에 한번씩 이 파이프라인을 실행하도록 하고, 파이프라인에서 데이타 전처리 과정을 수행하고, 이 과정이 끝나면 자동으로 학습을 진행하고 학습 정확도가 정해진 수준을 넘으면 자동으로 학습된 모델은 서비스 시스템에 배포하는 이 일련의 과정을 자동으로 순차적으로 수행할 수 있도록 엮어 주는 과정이다.

airbnb에서 개발한 Airflow나 luigi 등의 솔루션을 사용하면 된다.

아직도 갈길은 멀다.

얼굴 인식이라는 간단한 모델을 개발하고 있지만, 전체를 자동화 하고, 클라우드 컴퓨팅을 통해서 학습 시간을 단축 시키고 예측 서비스를 할 수 있는 컴포넌트를 개발해야 하고, 향후에는 하이퍼 패러미터 튜닝을 자동으로 할 수 있는 수준까지 가보려고 한다. 그 후에는 GAN을 통한 얼굴 합성들도 도전하려고 하는데, node.js 공부하는데도 1~2년을 투자한후에나 조금이나마 이해할 수 있게 되었는데, 머신러닝을 시작한지 이제 대략 8개월 정도. 길게 보고 해야 하겠다.

 



 

 

저작자 표시 비영리
신고

텐서플로우의 세션,그래프 그리고 함수의 개념


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


그래프와 세션에 대한 개념이 헷갈려서, 좋은 샘플이 하나 만들어져서 공유합니다.

텐서 플로우의 기본 작동 원리는 세션 시작전에 그래프를 정의해놓고, 세션을 시작하면 그 그래프가 실행되는 원리인데, 그래서 이 개념이 일반적인 프로그래밍 개념과 상의하여 헷갈리는 경우가 많다


즉, 세션을 시작해놓고 함수를 호출하는 케이스들이 대표적인데

http://bcho.tistory.com/1170 코드를 재 사용해서 이해해보도록 하자


이 코드를 보면, tt = time * 10 을 세션 시작전에 정의해놨는데, 이 코드를 함수로 바꾸면 아래와 같은 형태가 된다.


변경전 코드

def main():

   

   print 'start session'

   #coornator 위에 코드가 있어야 한다

   #데이타를 집어 넣기 전에 미리 그래프가 만들어져 있어야 함.

   batch_year,batch_flight,batch_time = read_data_batch(TRAINING_FILE)

   year = tf.placeholder(tf.int32,[None,],name='year')

   flight = tf.placeholder(tf.string,[None,],name='flight')

   time = tf.placeholder(tf.int32,[None,],name='time')

   

   tt = time * 10

   summary = tf.summary.merge_all()

   with tf.Session() as sess:

       summary_writer = tf.summary.FileWriter(LOG_DIR,sess.graph)

       try:


           coord = tf.train.Coordinator()

           threads = tf.train.start_queue_runners(sess=sess, coord=coord)


           for i in range(5):

               y_,f_,t_ = sess.run([batch_year,batch_flight,batch_time])

               print sess.run(tt,feed_dict={time:t_})

               #summary_str = sess.run(summary,feed_dict=feed_dict)

               #summary_writer.add_summary(summary_str,i)

               summary_writer.flush()         


변경후 코드

def create_graph(times):

   tt = times * 10

   return tt


def main():

   

   print 'start session'

   #coornator 위에 코드가 있어야 한다

   #데이타를 집어 넣기 전에 미리 그래프가 만들어져 있어야 함.

   batch_year,batch_flight,batch_time = read_data_batch(TRAINING_FILE)

   year = tf.placeholder(tf.int32,[None,],name='year')

   flight = tf.placeholder(tf.string,[None,],name='flight')

   time = tf.placeholder(tf.int32,[None,],name='time')

   

   r = create_graph(time)

   

   summary = tf.summary.merge_all()

   with tf.Session() as sess:

       summary_writer = tf.summary.FileWriter(LOG_DIR,sess.graph)

       try:


           coord = tf.train.Coordinator()

           threads = tf.train.start_queue_runners(sess=sess, coord=coord)


           for i in range(5):

               y_,f_,t_ = sess.run([batch_year,batch_flight,batch_time])

               print sess.run(r,feed_dict={time:t_})

               #summary_str = sess.run(summary,feed_dict=feed_dict)

               #summary_writer.add_summary(summary_str,i)

               summary_writer.flush()


변경후 코드는 tt = times * 10 을 create_graph라는 함수로 뺐는데, session 시작전에 함수를 호출한다. 언뜻 보면 개념이 헷갈릴 수 있는데, time 이라는 변수는 텐서플로우의 placeholder로 값이 읽혀지는 시점이 queue_runner를 시작해야 값을 읽을 수 있는 준비 상태가 되고, 실제로 값을 큐에서 읽으려면 session을 실행하고 feed_dict를 이용하여 feeding을 해줘야 값이 채워지기 때문에, 일반적인 프로그램상으로는 session을 시작한 후에 함수를 호출해야할것 같이 생각이 되지만, 앞에서도 언급했듯이 텐서플로우에서 프로그래밍의 개념은 그래프를 다 만들어놓은 후 (데이타가 처리되는 흐름을 모두 정의해놓고) 그 다음 session을 실행하여 그래프에 데이타를 채워놓는 개념이기 때문에, session이 정의되기 전에 함수 호출등을 이용해서 그래프를 정의해야 한다.


저작자 표시 비영리
신고


텐서플로우 배치 처리


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


텐서플로우에서 파일에서 데이타를 읽은 후에, 배치처리로 placeholder에서 읽는  예제를 설명한다.

텐서의 shape 의 차원과 세션의 실행 시점등이 헷갈려서 시행착오가 많았기 때문에 글로 정리해놓는다.

큐와 파일처리에 대한 기본적인 내용은 아래글

  • http://bcho.tistory.com/1163

  • http://bcho.tistory.com/1165

를 참고하기 바란다.

데이타 포맷

읽어 드릴 데이타 포맷은 다음과 같다. 비행기 노선 정보에 대한 데이타로 “년도,항공사 코드, 편명"을 기록한 CSV 파일이다.

2014,VX,121

2014,WN,1873

2014,WN,2787

배치 처리 코드

이 데이타를 텐서 플로우에서 읽어서 배치로 place holder에 feeding 하는 코드 이다

먼저 read_data는 csv 파일에서 데이타를 읽어서 파싱을 한 후 각 컬럼을 year,flight,time 으로 리턴하는 함 수이다.

def read_data(file_name):

   try:

       csv_file = tf.train.string_input_producer([file_name],name='filename_queue')

       textReader = tf.TextLineReader()

       _,line = textReader.read(csv_file)

       year,flight,time = tf.decode_csv(line,record_defaults=[ [1900],[""],[0] ],field_delim=',')    

   except:

       print "Unexpected error:",sys.exc_info()[0]

       exit()

   return year,flight,time


string_input_producer를 통해서 파일명들을 큐잉해서 하나씩 읽는데,여기서는 편의상 하나의 파일만 읽도록 하였는데, 여러개의 파일을 병렬로 처리하고자 한다면, [file_name]  부분에 리스트 형으로 여러개의 파일 목록을 지정해주면 된다.

다음 각 파일을 TextReader를 이용하여 라인 단위로 읽은 후 decode_csv를 이용하여, “,”로 분리된 컬럼을 각각  읽어서 year,flight,time 에 저장하여 리턴하였다.


다음 함수는 read_data_batch 라는 함수인데, 앞에서 정의한 read_data 함수를 호출하여, 읽어드린 year,flight,time 을 배치로 묶어서 리턴하는 함수 이다.


def read_data_batch(file_name,batch_size=10):

   year,flight,time = read_data(file_name)

   batch_year,batch_flight,batch_time = tf.train.batch([year,flight,time],batch_size=batch_size)

   

   return  batch_year,batch_flight,batch_time


tf.train.batch 함수가 배치로 묶어서 리턴을 하는 함수인데, batch로 묶고자 하는 tensor 들을 인자로 준 다음에, batch_size (한번에 묶어서 리턴하고자 하는 텐서들의 개수)를 정해주면 된다.


위의 예제에서는 batch_size를 10으로 해줬기 때문에, batch_year = [ 1900,1901….,1909]  와 같은 형태로 10개의 년도를 하나의 텐서에 묶어서 리턴해준다.

즉 입력 텐서의 shape이  [x,y,z] 일 경우 tf.train.batch를 통한 출력은 [batch_size,x,y,z] 가 된다.(이 부분이 핵심)


메인 코드

자 이제 메인 코드를 보자

def main():

   

   print 'start session'

   #coornator 위에 코드가 있어야 한다

   #데이타를 집어 넣기 전에 미리 그래프가 만들어져 있어야 함.

   batch_year,batch_flight,batch_time = read_data_batch(TRAINING_FILE)

   year = tf.placeholder(tf.int32,[None,])

   flight = tf.placeholder(tf.string,[None,])

   time = tf.placeholder(tf.int32,[None,])

   

   tt = time * 10


tt = time * 10 이라는 공식을 실행하기 위해서 time 이라는 값을 읽어서 피딩하는 예제인데 먼저 read_data_batch를 이용하여 데이타를 읽는 그래프를 생성한다. 이때 주의해야할점은 이 함수를 수행한다고 해서, 바로 데이타를 읽기 시작하는 것이 아니라, 데이타의 흐름을 정의하는 그래프만 생성된다는 것을 주의하자


다음으로는 year,flight,time placeholder를 정의한다.

year,flight,time 은 0 차원의 scalar 텐서이지만, 값이 연속적으로 들어오기 때문에, [None, ] 로 정의한다.

즉  year = [1900,1901,1902,1903,.....] 형태이기 때문에 1차원 Vector 형태의 shape으로 [None, ] 로 정의한다.

Placeholder 들에 대한 정의가 끝났으면, 세션을 정의하고 데이타를 읽어드리기 위한 Queue runner를 수행한다. 앞의 과정까지 텐서 그래프를 다 그렸고, 이 그래프 값을 부어넣기 위해서, Queue runner 를 수행한 것이다.


   with tf.Session() as sess:

       try:


           coord = tf.train.Coordinator()

           threads = tf.train.start_queue_runners(sess=sess, coord=coord)


Queue runner를 실행하였기 때문에 데이타가 데이타 큐로 들어가기 시작하고, 이 큐에 들어간 데이타를 읽어드리기 위해서, 세션을 실행한다.

               y_,f_,t_ = sess.run([batch_year,batch_flight,batch_time])

               print sess.run(tt,feed_dict={time:t_})

세션을 실행하면, batch_year,batch_flight,batch_time 값을 읽어서 y_,f_,t_ 변수에 각각 집어 넣은 다음에, t_ 값을 tt 공식의 time 변수에 feeding 하여, 값을 계산한다.


모든 작업이 끝났으면 아래와 같이 Queue runner를 정지 시킨다.

           coord.request_stop()

           coord.join(threads)


다음은 앞에서 설명한 전체 코드이다.


import tensorflow as tf

import numpy as np

import sys


TRAINING_FILE = '/Users/terrycho/dev/data/flight.csv'


## read training data and label

def read_data(file_name):

   try:

       csv_file = tf.train.string_input_producer([file_name],name='filename_queue')

       textReader = tf.TextLineReader()

       _,line = textReader.read(csv_file)

       year,flight,time = tf.decode_csv(line,record_defaults=[ [1900],[""],[0] ],field_delim=',')    

   except:

       print "Unexpected error:",sys.exc_info()[0]

       exit()

   return year,flight,time


def read_data_batch(file_name,batch_size=10):

   year,flight,time = read_data(file_name)

   batch_year,batch_flight,batch_time = tf.train.batch([year,flight,time],batch_size=batch_size)

   

   return  batch_year,batch_flight,batch_time


def main():

   

   print 'start session'

   #coornator 위에 코드가 있어야 한다

   #데이타를 집어 넣기 전에 미리 그래프가 만들어져 있어야 함.

   batch_year,batch_flight,batch_time = read_data_batch(TRAINING_FILE)

   year = tf.placeholder(tf.int32,[None,])

   flight = tf.placeholder(tf.string,[None,])

   time = tf.placeholder(tf.int32,[None,])

   

   tt = time * 10


   with tf.Session() as sess:

       try:


           coord = tf.train.Coordinator()

           threads = tf.train.start_queue_runners(sess=sess, coord=coord)


           for i in range(10):

               y_,f_,t_ = sess.run([batch_year,batch_flight,batch_time])

               print sess.run(tt,feed_dict={time:t_})


           print 'stop batch'

           coord.request_stop()

           coord.join(threads)

       except:

           print "Unexpected error:", sys.exc_info()[0]


main()


다음은 실행결과이다.



저작자 표시 비영리
신고

딥러닝을 이용한 숫자 이미지 인식 #2/2


앞서 MNIST 데이타를 이용한 필기체 숫자를 인식하는 모델을 컨볼루셔널 네트워크 (CNN)을 이용하여 만들었다. 이번에는 이 모델을 이용해서 필기체 숫자 이미지를 인식하는 코드를 만들어 보자


조금 더 테스트를 쉽게 하기 위해서, 파이썬 주피터 노트북내에서 HTML 을 이용하여 마우스로 숫자를 그릴 수 있도록 하고, 그려진 이미지를 어떤 숫자인지 인식하도록 만들어 보겠다.



모델 로딩

먼저 앞의 예제에서 학습을한 모델을 로딩해보도록 하자.

이 코드는 주피터 노트북에서 작성할때, 모델을 학습 시키는 코드 (http://bcho.tistory.com/1156) 와 별도의 새노트북에서 구현을 하도록 한다.


코드

import tensorflow as tf

import numpy as np

import matplotlib.pyplot as plt

from tensorflow.examples.tutorials.mnist import input_data


#이미 그래프가 있을 경우 중복이 될 수 있기 때문에, 기존 그래프를 모두 리셋한다.

tf.reset_default_graph()


num_filters1 = 32


x = tf.placeholder(tf.float32, [None, 784])

x_image = tf.reshape(x, [-1,28,28,1])


#  layer 1

W_conv1 = tf.Variable(tf.truncated_normal([5,5,1,num_filters1],

                                         stddev=0.1))

h_conv1 = tf.nn.conv2d(x_image, W_conv1,

                      strides=[1,1,1,1], padding='SAME')


b_conv1 = tf.Variable(tf.constant(0.1, shape=[num_filters1]))

h_conv1_cutoff = tf.nn.relu(h_conv1 + b_conv1)


h_pool1 =tf.nn.max_pool(h_conv1_cutoff, ksize=[1,2,2,1],

                       strides=[1,2,2,1], padding='SAME')


num_filters2 = 64


# layer 2

W_conv2 = tf.Variable(

           tf.truncated_normal([5,5,num_filters1,num_filters2],

                               stddev=0.1))

h_conv2 = tf.nn.conv2d(h_pool1, W_conv2,

                      strides=[1,1,1,1], padding='SAME')


b_conv2 = tf.Variable(tf.constant(0.1, shape=[num_filters2]))

h_conv2