이번글에서는 Tensorflow Object Detection API를 이용하여 직접 이미지를 인식할 수 있는 방법에 대해서 알아보자. 이미 가지고 있는 데이타를 가지고 다양한 상품에 대한 인식이나, 사람 얼굴에 대한 인식 모델을 머신러닝에 대한 전문적인 지식 없이도 손쉽게 만들 수 있다.
로 생성된다. 이 세가지 파일이 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를 이용해서 자동으로 했지만, 일반적인 경우는 이미지에서 각 경계를 수동으로 추출해서 학습데이타로 생성해야 한다
학습 데이타가 준비 되었으면 학습을 위한 준비를 하는데, 트랜스퍼 러닝 (Transfer learning)을 위해서 기존의 학습된 체크포인트 데이타를 다운 받아서 이를 기반으로 학습을 한다.
Tensorflow Object Detection API는 경량이고 단순한 모델에서 부터 정확도가 비교적 높은 복잡한 모델까지 지원하고 있지만, 복잡도가 높다고 해서 정확도가 꼭 높지는 않을 수 있다. 복잡한 모델일 수 록 학습 데이타가 충분해야 하기 때문에, 학습하고자 하는 데이타의 양과 클래스의 종류에 따라서 적절한 모델을 선택하기를 권장한다.
여기서는 faster_rcnn_inception_resnet_v2 모델을 이용했기 때문에 아래와 같이 해당 모델의 체크포인트 데이타를 다운로드 받는다.
다음 학습에 사용할 모델의 설정을 해야 하는데, object_detection/samples/configs/ 디렉토리에 각 모델별 설정 파일이 들어 있으며, 여기서는 faster_rcnn_inception_resnet_v2_atrous_pets.config 파일을 사용한다.
이 파일에서 수정해야 하는 부분은 다음과 같다.
클래스의 수
클래스 수를 정의한다. 이 예제에서는 총 5개의 클래스로 분류를 하기 때문에 아래와 같이 5로 변경하였다.
학습이 진행되면 텐서보드를 이용하여 학습 진행 상황을 모니터링할 수 있고, 또한 테스트 트레이닝을 수행하여, 모델에 대한 테스트를 동시 진행할 수 있다. http://bcho.tistory.com/1193 와 방법이 동일하니 참고하기 바란다.
학습을 시작하면 텐서보드를 통해서, Loss 값이 수렴하는 것을 확인할 수 있다.
결과
학습이 끝나면 텐서보드에서 테스트된 결과를 볼 수 있다. 이 예제의 경우 모델을 가장 복잡한 모델을 사용했는데 반하여, 총 5개의 클래스에 대해서 클래스당 약 40개정도의 학습 데이타를 사용했는데, 상대적으로 정확도가 낮았다. 실 서비스에서는 더 많은 데이타를 사용하기를 권장한다.
활용
학습된 모델을 활용하는 방법은 학습된 모델을 export 한후에, (Export 하는 방법은 http://bcho.tistory.com/1193 참고) export 된 모델을 로딩하여, 코드에서 불러서 사용하면 된다.
이번 글에서는 이 애완동물 데이타를 다운 받아서, Object Detection API에 학습 시키는 것까지 진행을 한다.
데이타를 다운로드 받은 후, Object Detection API에 학습 시키기 위해서, 데이타 포맷을 TFRecord 형태로 변환한 후, 학습을 하는 과정을 설명한다.
주의할점 : 이 튜토리얼은 총 37개의 클래스 약 7000장의 이미지를 학습시키는데, 17시간 이상이 소요되며, 구글 클라우 CloudML의 텐서플로우 클러스터에서 분산 러닝을 하도록 설명하고 있는데, 많은 비용이 들 수 있다. 전체 흐름과 과정을 이해하기 위해서는 17시간을 풀 트레이닝 시키지 말고 학습 횟수를 줄이거나 아니면 중간에서 학습을 멈춰서 비용이 많이 나오지 않도록 하는 것을 권장한다.
※ 맥이기 때문에, 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에 테스트용 데이타를 생성해준다.
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 버킷에 올리는 명령이다.
학습에 사용할 데이타와 체크포인트등을 업로드 했으면, 다음 텐서플로우 코드를 패키징 해야 한다. 이 글에서는 학습을 로컬 머신이 아니라 구글 클라우드의 텐서플로우 메니지드 서비스인 CloudML을 사용하는데, 이를 위해서는 텐서플로우코드와 코드에서 사용하는 파이썬 라이브러리들을 패키징해서 올려야 한다.
Object Detection API 모델 디렉토리에서 다음 명령어를 실행하면, model 디렉토리와 model/slim 디렉토리에 있는 텐서플로우 코드 및 관련 라이브러리를 같이 패키징하게된다.
명령을 실행하고 나면 패키징된 파일들은 dist/object_detection-0.1.tar.gz 와 slim/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
각 서버의 스펙은 상세 스펙은 나와있지 않고, 상대값으로 정의되어 있는데 대략 내용이 다음과 같다.
학습을 시작하고 나면 CloudML 콘솔에서 실행중인 Job을 볼 수 있고, Job을 클릭하면 자원의 사용 현황을 볼 수 있다. (CPU와 메모리 사용량)
학습을 시작한 후에, 학습된 모델을 Evaluate할 수 있는데, Object Detection API에서는 학습 말고 Evaluation 모델을 별도로 나눠서, 잡을 나눠서 수행하도록 하였다. 학습중에 생성되는 체크포인트 파일을 읽어서 Evaluation을 하는 형태이다.
다음을 Evaluation을 실행하는 명령어인데, 위의 학습 작업이 시작한 후에, 한시간 정도 후부터 실행해도 실행 상태를 볼 수 있다.
학습이 진행중에도, Evaluation을 시작했으면, Tensorboard를 이용하여 학습 진행 상황을 모니터링 할 수 있다. 학습 진행 데이타가 gs://${YOUR_GCS_BUCKET} 에 저장되기 때문에, 이 버킷에 있는 데이타를 Tensorboard로 모니터링 하면 된다.
실행 방법은 먼저 GCS 에 접속이 가능하도록 auth 정보를 설정하고, Tensorboard에 로그 파일 경로를
그래프를 만드는데, build_model에 의해서 나온 예측 결과에 softmax 함수를 적용한다. 학습시에는 softmax 함수의 비용이 크기 때문에 적용하지 않았지만, 예측에서는 결과를 쉽게 알아보기 위해서 softmax 함수를 적용한다. Softmax 함수는 카테고리 별로 확률을 보여줄때 전체 값을 1.0으로 해서 보여주는것인데, 만약에 Jolie,Sulyun,Victora 3개의 카테코리가 있을때 각각의 확률이 70%,20%,10%이면 Softmax를 적용한 결과는 [0.7,0.2,0.1] 식으로 출력해준다.
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
다음 텐서플로우 세션을 초기화 하고,
saver = tf.train.Saver()
saver.restore(sess, 'face_recog')
마지막으로 Saver의 restore 함수를 이용하여 ‘face_recog’라는 이름으로 저장된 학습 결과를 리스토어 한다. (앞의 예제에서, 학습이 완료된 모델을 ‘face_recog’라는 이름으로 저장하였다.)
예측하기
로딩 된 모델을 가지고 예측을 하는 방법은 다음과 같다. 이미지 파일을 읽은 후에, 구글 클라우드 VISION API를 이용하여, 얼굴의 위치를 추출한후, 얼굴 이미지만 크롭핑을 한후에, 크롭된 이미지를 텐서플로우 데이타형으로 바꾼후에, 앞서 로딩한 모델에 입력하여 예측된 결과를 받게 된다.
얼굴 영역 추출하기
먼저 vision API로 얼굴 영역을 추출하는 부분이다. 앞의 이미지 전처리에 사용된 부분과 다르지 않다.
import google.auth
import io
import os
from oauth2client.client import GoogleCredentials
from google.cloud import vision
from PIL import Image
from PIL import ImageDraw
FLAGS.image_size = 96
# set service account file into OS environment value
tfimages 에 이미지를 넣어서 모델에 넣고 prediction 값을 리턴 받는다. dropout은 사용하지 않기 때문에, keep_prob을 1.0으로 한다.
나온 결과를 가지고 Jessica, Jolie,Nicole Kidman, Sulhyun, Victoria Beckam 일 확률을 각각 출력한다.
전체 코드는 https://github.com/bwcho75/facerecognition/blob/master/2.%2BFace%2BRecognition%2BPrediction%2BTest.ipynb
다음은 설현 사진을 가지고 예측을 한 결과 이다.
이 코드는 학습된 모델을 기반으로 얼굴을 인식이 가능하기는 하지만 실제 운영 환경에 적용하기에는 부족하다. 파이썬 모델 코드를 그대로 옮겼기 때문에, 성능도 상대적으로 떨어지고, 실제 운영에서는 모델을 업그레이드 배포 할 수 있고, 여러 서버를 이용하여 스케일링도 지원해야 한다.
그래서 텐서플로우에서는 Tensorflow Serving 이라는 예측 서비스 엔진을 제공하고 구글 클라우에서는 Tensorflow Serving의 매니지드 서비스인, CloudML 서비스를 제공한다.
앞의 두 글이 로컬 환경에서 학습과 예측을 진행했는데, 다음 글에서는 상용 서비스에 올릴 수 있는 수준으로 학습과 예측을 할 수 있는 방법에 대해서 알아보도록 하겠다.
본 예제는 텐서플로우 1.1과 파이썬 2.7 그리고 Jupyter 노트북 환경 및 구글 클라우드를 사용하여 개발되었다.
준비된 데이타
학습에 사용한 데이타는 96x96 사이즈의 얼굴 이미지로, 총 5명의 사진(안젤리나 졸리, 니콜키드만, 제시카 알바, 빅토리아 베컴,설현)을 이용하였으며, 인당 학습 데이타 40장 테스트 데이타 10장으로 총 250장의 얼굴 이미지를 사용하였다.
사전 데이타를 준비할때, 정면 얼굴을 사용하였으며, 얼굴 각도 변화 폭이 최대한 적은 이미지를 사용하였다. (참고 : https://www.slideshare.net/Byungwook/ss-76098082 ) 만약에 이 모델로 학습이 제대로 되지 않는다면 학습에 사용된 데이타가 적절하지 않은것이기 때문에 데이타를 정재해서 학습하기를 권장한다.
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 등을 이용하여 이러한 기능을 제공한다.
마지막으로 read_data_batch() 함수 부분이다.get_input_queue에서 읽은 큐를 가지고 read_data함수에 넣어서 이미지 데이타와 라벨을 읽어서 리턴하는 값을 받아서 일정 단위로 (배치) 묶어서 리턴하는 함수이다. 중요한 부분이 데이타를 뻥튀기 하는 부분이 있다.
이 모델에서 학습 데이타가 클래스당 40개 밖에 되지 않기 때문에 학습데이타가 부족하다. 그래서 여기서 사용한 방법은 read_data에서 리턴된 이미지 데이타에 대해서 tf.image.random_xx 함수를 이용하여 좌우를 바꾸거나, brightness,contrast,hue,saturation 함수를 이용하여 매번 색을 바꿔서 리턴하도록 하였다.
그리고 마지막 부분에 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 을 사용한다.
다음 Weight 값 W_conv2 와 Bias 값 b2를 지정한후에, 간단하게 tf.nn.conv2d 함수를 이용하면 2차원의 Convolutional 네트워크를 정의해준다. 다음 결과가 나오면 이 결과를 액티베이션 함수인 relu 함수에 넣은 후에, 마지막으로 max pooling 을 이용하여 결과를 뽑아낸다.
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 함수는 연산 비용이 큰 함수이기 때문에 일반적으로 학습 단계에서는 적용하지 않는다.
이제 각 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
먼저 학습용 모델에 넣기 위한 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))
학습용 데이타가 아니라 검증용 데이타를 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
안녕하세요~ 올려주신 글 잘 참고하고 있습니다! 제가 이번에 모바일로 얼굴인식을 해보고 싶어서 올려주신 글 참고해서 만들어 보고 있는데요! 모바일에 최적화된 모델을 제작하기위해 optimize_for_inference 과정을 거쳐야 하는 걸로 알고 있습니다! 그 과정에서 입력이름과 아웃풋레이어이름을 인자로 실행해야 한다고 알고 있는데요~
tensorflow/bazel-bin/tensorflow/python/tools/optimize_for_inference \
--input=input_graph.pb \
--output=optimized_graph.pb \
--input_names=? \
--output_names=?
여기서 input_names와 output_names이 각각 image와 final_out이 맞을까요? ㅠㅠ
그리고 해당 코드를 실행했을때ㅠㅠ
tf.train.batch 부분에서
OutOfRangeError (see above for traceback): FIFOQueue '_3_batch/fifo_queue' is closed and has insufficient elements (requested 100, current size 0)
와 같이 에러가 발생하는데요 ㅠㅠ 어떻게 해야할까요? 제 데이터 셋은 classes=5 한 클래스당 사진은 50장입니다 ㅠㅠ
대협님 좋은글 잘보았습니다.
다만,
여쭙고 싶은 중요한 부분이 있는데요.
필터를 초기화 하는부분에서
2x2x3차원(rgb)을 갖는 필터를 30개를 생성해야한다고하면,
대협님 글대로 쓰면[2,2,3,30] 이렇게 생겻는데,
[2,2,3,30] 이면 3x30짜리 2개를쓰는 필터를 2개를 생성하라 라는 의미로 생각이됩니다.
실제로 돌려봐도 텐서가 그렇게나옵니다.
이부분은 중요한 문제인것같은데,, 어떻게 된것인지 알려주세요 ㅠ
감사합니다.
마이크로소프트 azure의 jupyter notebok으로 돌리며 train:validate = 7:3으로 조정, 18명 연예인에 한 연예인당 50장의 사진을 이용했습니다.
그런데 다음과 같이 나옵니다. 어떻게 해야 할까요?
OutOfRangeError: FIFOQueue '_23_batch_4/fifo_queue' is closed and has insufficient elements (requested 70, current size 0)
[[Node: batch_4 = QueueDequeueManyV2[component_types=[DT_UINT8, DT_INT32, DT_STRING], timeout_ms=-1, _device="/job:localhost/replica:0/task:0/cpu:0"](batch_4/fifo_queue, batch_4/n)]]
먼저 얼굴 인식 모델을 만들려면, 학습을 시킬 충분한 데이타가 있어야 한다. 사람 얼굴을 일일이 구할 수 도 없고, 구글이나 네이버에서 일일이 저장할 수 도 없기 때문에, 공개된 데이타셋을 활용하였는데, PubFig (Public Figures Face Database - http://www.cs.columbia.edu/CAVE/databases/pubfig/) 를 사용하였다.
이 데이타셋에는 약 200명에 대한 58,000여장의 이미지를 저장하고 있는데, 이 중의 일부만을 사용하였다.
를 실행하면 ./data 디렉토리에 이미지를 다운로드 받아서 사람 이름별 폴더에 저장해준다.
evals_urls.txt에는 위에서 언급한 dev_urls.txt 형태의 데이타가 들어간다.
사람 종류가 너무 많으면 데이타를 정재하는 작업이 어렵고, (왜 어려운지는 뒤에 나옴) 학습 시간이 많이 걸리기 때문에, 약 47명의 데이타를 다운로드 받아서 작업하였다.
학습 데이타 준비에 있어서 경험
쓰레기 데이타 골라내기
데이타를 다운받고 나니, 아뿔사!! PubFig 데이타셋이 오래되어서 없는 이미지도 있고 학습에 적절하지 않은 이미지도 있다.
주로 학습에 적절하지 않은 데이타는 한 사진에 두사람 이상의 얼굴이 있거나, 이미지가 사라져서 위의 우측 그림처럼, 이미지가 없는 형태로 나오는 경우인데, 이러한 데이타는 어쩔 수 없이 눈으로 한장한장 다 걸러내야만 했는데, 이런 간단한 데이타 필터링 처리는 Google Cloud Vision API를 이용하여, 얼굴이 하나만 있는 사진만을 사용하도록 하여 필터링을 하였다.
학습 데이타의 분포
처음에 학습을 시작할때, 분류별로 데이타의 수를 다르게 하였다. 어렵게 모은 데이타를 버리기가 싫어서 모두 다 넣고 학습 시켰는데, 그랬더니 학습이 쏠리는 현상이 발생하였다.
예를 들어 안젤리나 졸리 300장, 브래드피트 100장, 제시카 알바 100장 이런식으로 학습을 시켰더니, 이미지 예측에서 안젤리나 졸리로 예측하는 경우가 많아졌다. 그래서 학습을 시킬때는 데이타수가 작은 쪽으로 맞춰서 각 클래스당 학습 데이타수가 같도록 하였다. 즉 위의 데이타의 경우에는 안젤리나 졸리 100장, 브래드피트 100장, 제시카 알바 100장식으로 데이타 수를 같게 해야했다.
라벨은 숫자로
라벨의 가독성을 높이기 위해서 라벨을 영문 이름으로 사용했는데, CNN 알고리즘에서 최종 분류를 하는 알고리즘은 softmax 로 그 결과 값을 0,1,2…,N식으로 라벨을 사용하기 때문에, 정수형으로 변환을 해줘야 하는데, 텐서 플로우 코드에서는 이게 그리 쉽지않았다. 그래서 차라리 처음 부터 학습 데이타를 만들때는 라벨을 정수형으로 만드는것이 더 효과적이다
얼굴 각도, 표정,메이크업, 선글라스 도 중요하다
CNN 알고리즘을 마법처럼 생각해서였을까? 데이타만 있다면 어떻게든 학습이 될 줄 알았다. 그러나 얼굴의 각도가 많이 다르거나 표정이 심하게 차이가 난 경우에는 다른 사람으로 인식이 되기 때문에 가능하면 비슷한 표정에 비슷한 각도의 사진으로 학습 시키는 것이 정확도를 높일 수 있다.
얼굴 각도의 경우 구글 클라우드 VISION API를 이용하면 각도를 추출할 수 있기 때문에 20도 이상 차이가 나는 사진은 필터링 하였고, 표정 부분도 VISION API를 이용하면 감정도를 분석할 수 있기 때문에 필터링이 가능하다. (아래서 설명하는 코드에서는 감정도 분석 부분은 적용하지 않았다)
또한 선글라스를 쓴 경우에도 다른 사람으로 인식할 수 있기 때문에 VISION API에서 물체 인식 기능을 이용하여 선글라스가 검출된 경우에는 학습 데이타에서 제거하였다.
이외에도 헤어스타일이나 메이크업이 심하게 차이가 나는 경우에는 다른 사람으로 인식되는 확률이 높기 때문에 이런 데이타도 가급적이면 필터링을 하는것이 좋다.
웹 크라울링의 문제점
데이타를 쉽게 수집하려고 웹 크라울러를 이용해서 구글 이미지 검색에서 이미지를 수집해봤지만, 정확도는 매우 낮게 나왔다.
웹크라울러로 수집한 데이타는, 앞에서 언급한 쓰레기 데이타들이 너무 많다. 메이크업, 표정, 얼굴 각도, 두명 이상 있는 사진들이 많았고, 거기에 더해서 그 사람이 아닌 사람의 얼굴 사진까지 같이 수집이 되는 경우가 많았다.
웹 크라울링을 이용한 학습 데이타 수집은 적어도 얼굴 인식용 데이타 수집에 있어서는 좋은 방법은 아닌것 같다. 혹여나 웹크라울러를 사용하더라도 반드시 수동으로 직접 데이타를 검증하는 것이 좋다.
학습 데이타의 양도 중요하지만 질도 매우 중요하다
아이돌 그룹인 EXO와 레드벨벳의 사진을 웹 크라울러를 이용해서 수집한 후에 학습을 시켜보았다. 사람당 약 200장의 데이타로 8개 클래스 정도를 테스트해봤는데 정확도가 10%가 나오지를 않았다.
대신 데이타를 학습에 좋은 데이타를 일일이 눈으로 확인하여 클래스당 30장 정도를 수집해서 학습 시킨 결과 60% 정도의 정확도를 얻을 수 있었다. 양도 중요하지만 학습 데이타의 질적인 면도 중요하다.
중복데이타 처리 문제
데이타를 수집해본 결과, 중복되는 데이타가 생각보다 많았다. 중복 데이타를 걸러내기 위해서 파일의 MD5 해쉬 값을 추출해낸 후 이를 비교해서 중복되는 파일을 제거하였는데, 어느정도 효과를 볼 수 있었지만, 아래 이미지와 같이 같은 이미지지만, 편집이나 리사이즈가 된 이미지의 경우에는 다른 파일로 인식되서 중복 체크에서 검출되지 않았다.
연예인 얼굴 인식은 어렵다
얼굴 인식 예제를 만들면서 재미를 위해서 한국 연예인 얼굴을 수집하여 학습에 사용했는데, 제대로 된 학습 데이타를 구하기가 매우 어려웠다. 앞에서 언급한데로 메이크업이나 표정 변화가 너무 심했고, 어렸을때나 나이먹었을때의 차이등이 심했다. 간단한 공부용으로 사용하기에는 좋은 데이타는 아닌것 같다.
그러면 학습에 좋은 데이타는?
그러면 얼굴 인식 학습에 좋은 데이타는 무엇일까? 테스트를 하면서 내린 자체적인 결론은 정면 프로필 사진류가 제일 좋다. 특히 스튜디오에서 찍은 사진은 같은 조명에 같은 메이크업과 헤어스타일로 찍은 경우가 많기 때문에 학습에 적절하다. 또는 동영상의 경우에는 프레임을 잘라내면 유사한 표정과 유사한 각도, 조명등에 대한 데이타를 많이 얻을 수 있기 때문에 좋은 데이타 된다.
얼굴 추출하기
그러면 앞의 내용을 바탕으로 해서, 적절한 학습용 얼굴 이미지를 추출하는 프로그램을 만들어보자
포토샵으로 일일이 할 수 없기 때문에 얼굴 영역을 인식하는 API를 사용하기로한다. OPEN CV와 같은 오픈소스 라이브러리를 사용할 수 도 있지만 구글의 VISION API의 경우 얼굴 영역을 아주 잘 잘라내어주고, 얼굴의 각도나 표정을 인식해서 필터링 하는 기능까지 코드 수십줄만 가지고도 구현이 가능했기 때문에, VISION API를 사용하였다. https://cloud.google.com/vision/
VISION API ENABLE 하기
VISION API를 사용하기 위해서는 해당 구글 클라우드 프로젝트에서 VISION API를 사용하도록 ENABLE 해줘야 한다.
VISION API를 ENABLE하기 위해서는 아래 화면과 같이 구글 클라우드 콘솔 > API Manager 들어간후
+ENABLE API를 클릭하여 아래 그림과 같이 Vision API를 클릭하여 ENABLE 시켜준다.
SERVICE ACCOUNT 키 만들기
다음으로 이 VISION API를 호출하기 위해서는 API 토큰이 필요한데, SERVICE ACCOUNT 라는 JSON 파일을 다운 받아서 사용한다.
구글 클라우드 콘솔에서 API Manager로 들어간후 Credentials 메뉴에서 Create creadential 메뉴를 선택한후, Service account key 메뉴를 선택한다
다음 Create Service Account key를 만들도록 하고, accountname과 id와 같은 정보를 넣는다. 이때 중요한것이 이 키가 가지고 있는 사용자 권한을 설정해야 하는데, 편의상 모든 권한을 가지고 있는 Project Owner 권한으로 키를 생성한다.
(주의. 실제 운영환경에서 전체 권한을 가지는 키는 보안상의 위험하기 때문에 특정 서비스에 대한 접근 권한만을 가지도록 지정하여 Service account를 생성하기를 권장한다.)
Service account key가 생성이 되면, json 파일 형태로 다운로드가 된다.
이미지에서 md5 해쉬를 추출한후에, 이 해쉬를 이용하여 학습 데이타로 사용된 파일들의 해쉬와 비교한다. 만약에 중복되는 것이 없으면 이 해쉬를 리스트에 추가하고 다음 과정을 수행한다.
VISION API를 이용하여, 얼굴 영역을 추출하는데, 위의 코드에서 처럼 image_file을 읽은후에, batch_request라는 문자열을 만든다. JSON 형태의 문자열이 되는데, 이때 image라는 항목에 이미지 데이타를 base64 인코딩 방식으로 인코딩해서 전송한다. 그리고 VISION API는 얼굴인식뿐 아니라 사물 인식, 라벨인식등 여러가지 기능이 있기 때문에 그중에서 타입을 ‘FACE_DETECTION’으로 정의하여 얼굴 영역만 인식하도록 한다.
request를 만들었으면, VISION API로 요청을 보내면 응답이 오는데, 이중에서 response 엘리먼트의 첫번째 인자 ( [‘responses’][0] )은 첫번째 얼굴은 뜻하는데, 여기서 [‘faceAnnotation’]을 하면 얼굴에 대한 정보만을 얻을 수 있다. 이중에서 [‘fdBoundingPoly’] 값이 얼굴 영역을 나타내는 사각형이다. 이 갑ㄱㅅ을 읽어서 left,top,right,bottom 값에 세팅한 후 리턴한다.
그리고 얼굴의 각도 (상하좌우옆)를 추출하여, 얼국 각도가 각각 20도 이상 더 돌아간 경우에는 학습 데이타로 사용하지 않고 필터링을 해냈다.
다음은 각도를 추출하고 필터링을 하는 부분이다.
roll_angle = face[0]['rollAngle']
pan_angle = face[0]['panAngle']
tilt_angle = face[0]['tiltAngle']
angle = [roll_angle,pan_angle,tilt_angle]
# check angle
# if face skew angle is greater than > 20, it will skip the data
if abs(roll_angle) > MAX_ROLL or abs(pan_angle) > MAX_PAN or abs(tilt_angle) > MAX_TILT:
print('[Error] %s: face skew angle is big' % image_file)
return None
VISION API에서 추가로 “FACE DETECTION” 뿐만 아니라 “LABEL_DETECTION” 을 같이 수행했는데 이유는 선글라스를 쓰고 있는 사진을 필터링하기 위해서 사용하였다. 아래는 선글라스 있는 사진을 검출하는 코드이다.
# check sunglasses
for l in label:
if 'sunglasses' in l['description']:
print('[Error] %s: sunglass is detected' % image_file)
return None
얼굴 잘라내고 리사이즈 하기
앞의 detect_face에서 필터링하고 찾아낸 얼굴 영역을 가지고 그 부분만 전체 사진에서 잘라내고, 잘라낸 얼굴을 학습에 적합하도록 같은 크기 (96x96)으로 리사이즈 한다.
이런 이미지 처리를 위해서 PIL (Python Imaging Library - http://www.pythonware.com/products/pil/)를 사용하였다.
def crop_face(self,image_file,rect,outputfile):
try:
fd = io.open(image_file,'rb')
image = Image.open(fd)
crop = image.crop(rect)
im = crop.resize(IMAGE_SIZE,Image.ANTIALIAS)
im.save(outputfile,"JPEG")
fd.close()
print('[Info] %s: Crop face %s and write it to file : %s' %(image_file,rect,outputfile) )
image_file을 인자로 받아서 , rect 에 정의된 사각형 영역 만큼 crop를 해서 잘라내고, resize 함수를 이용하여 크기를 96x96으로 조정한후 (참고 IMAGE_SIZE = 96,96 로 정의되어 있다.) outputfile 경로에 저장하게 된다.
실행을 해서 정재된 데이타는 다음과 같다.
생각해볼만한점들
이 코드는 간단한 토이 프로그램이기 때문에 간단하게 작성했지만 실제 운영환경에 적용하기 위해서는 몇가지 고려해야 할 사항이 있다.
먼저, 이 코드는 싱글 쓰레드로 돌기 때문에 속도가 상대적으로 느리다 그래서 멀티 쓰레드로 코드를 수정할 필요가 있으며, 만약에 수백만장의 사진을 정재하기 위해서는 한대의 서버로 되지 않기 때문에, 원본 데이타를 여러 서버로 나눠서 처리할 수 있는 분산 처리 구조가 고려되어야 한다.
또한, VISION API로 사진을 전송할때는 BASE64 인코딩된 구조로 서버에 이미지를 직접 전송하기 때문에, 자칫 이미지 사이즈들이 크면 네트워크 대역폭을 많이 잡아먹을 수 있기 때문에 가능하다면 식별이 가능한 크기에서 리사이즈를 한 후에, 서버로 전송하는 것이 좋다. 실제로 필요한 얼굴 크기는 96x96 픽셀이기 때문에 필요없이 1000만화소 고화질의 사진들을 전송해서 네트워크 비용을 낭비하지 않기를 바란다.
다음은 이렇게 정재한 파일들을 텐서플로우에서 읽어서 실제로 학습하는 모델을 만들어보겠다.
위의 코드를 멀티 프로세스&멀티쓰레드로 돌리는 아키텍쳐와 코드는 http://bcho.tistory.com/1177 글을 참고하기 바란다.
안녕하세요. 이미지 정제 및 cnn 학습을 공부하고 있는 학생입니다!
google vision api를 이용해 사물을 인식하는 부분을 공부하다가 블로그를 찾게 되었습니다.
얼굴인식에서 사물인식으로 코드를 조금 수정해서 사용하고 싶은데, 생각보다 제 실력이 부족하다보니 어렵네요...
사물인식을 할때 label detection으로 하는걸로 알고 있는데, 이 부분만 수정하고, 코드를 돌리면 되나요?
를 이용하여 x에 image를 넣고, 그리고 dropout 비율을 0%로 하기 위해서 keep_prob를 1.0 (100%)로 한다. (예측이기 때문에 당연히 dropout은 필요하지 않다.)
이렇게 하면 이 이미지가 어떤 숫자인지에 대한 확률이 p에 저장된다.
그래프로 표현
그러면 이 p의 값을 찍어 보자
fig = plt.figure(figsize=(4,2))
pred = p_val[0]
subplot = fig.add_subplot(1,1,1)
subplot.set_xticks(range(10))
subplot.set_xlim(-0.5,9.5)
subplot.set_ylim(0,1)
subplot.bar(range(10), pred, align='center')
plt.show()
그래프를 이용하여 0~9 까지의 숫자 (가로축)일 확률을 0.0~1.0 까지 (세로축)으로 출력하게 된다.
다음은 위에서 입력한 숫자 “4”를 인식한 결과이다.
(보너스) 첫번째 컨볼루셔널 계층 결과 출력
컨볼루셔널 네트워크를 학습시키다 보면 종종 컨볼루셔널 계층을 통과하여 추출된 특징 이미지들이 어떤 모양을 가지고 있는지를 확인하고 싶을때가 있다. 그래서 각 필터를 통과한 값을 이미지로 출력하여 확인하고는 하는데, 여기서는 이렇게 각 필터를 통과하여 인식된 특징이 어떤 모양인지를 출력하는 방법을 소개한다.
아래는 우리가 만든 네트워크 중에서 첫번째 컨볼루셔널 필터를 통과한 결과 h_conv1과, 그리고 이 결과에 bias 값을 더하고 활성화 함수인 Relu를 적용한 결과를 출력하는 예제이다.
첫번째 필터는 총 32개로 구성되어 있기 때문에, 32개의 결과값을 imshow 함수를 이용하여 흑백으로 출력하였다.
다음은 bias와 Relu를 통과한 값인 h_conv_cutoff를 출력하는 예제이다. 위의 코드와 동일하며 subplot.imgshow에서 전달해주는 인자만 conv1_vals → cutoff1_vals로 변경되었다.
코드
fig = plt.figure(figsize=(16,4))
for f in range(num_filters1):
subplot = fig.add_subplot(4, 16, f+1)
subplot.set_xticks([])
subplot.set_yticks([])
subplot.imshow(cutoff1_vals[0,:,:,f],
cmap=plt.cm.gray_r, interpolation='nearest')
plt.show()
출력 결과는 다음과 같다
이제까지 컨볼루셔널 네트워크를 이용한 이미지 인식을 텐서플로우로 구현하는 방법을 MNIST(필기체 숫자 데이타)를 이용하여 구현하였다.
실제로 이미지를 인식하려면 전체적인 흐름은 같지만, 이미지를 전/후처리 해내야 하고 또한 한대의 머신이 아닌 여러대의 머신과 GPU와 같은 하드웨어 장비를 사용한다. 다음 글에서는 MNIST가 아니라 실제 칼라 이미지를 인식하는 방법에 대해서 데이타 전처리에서 부터 서비스까지 전체 과정에 대해서 설명하도록 하겠다.
댓글을 달아 주세요