이 튜토리얼은 Caffe 와 Python을 사용하여 딥러닝으로 개와 고양이 구분하기[1]   글로부터 이어집니다.


4. 합성곱 신경망(CNN)을 사용하여 개와 고양이 식별자 만들기


이번에는 합성곱 신경망을 사용하여 개와 고양이를 구분하는 방법을 알아봅시다. 이 과제를 위해 Kaggle에 있는 개 대 고양이 대회(Dogs vs. Cats competition)에 있는 데이터셋을 사용할 것입니다. 신경망을 구현하기 위해 Caffe라 불리는 프레임웍 그리고 몇가지 파이썬 코드를 사용합니다.


4.1 개와 고양이 데이터 가져오기


먼저 대회 페이지로부터 2가지의 데이터셋을 다운로드 받아야 합니다. train.zip과 test1.zip 파일이 그것입니다. train.zip파일 안에는 개와 고양이로 라벨링된 이미지들이 있고 신경망을 훈련하는데 사용할 것입니다. test1.zip파일에는 분류되지 않은 개와 고양이 사진들이 섞여있으므로 훈련된 모델을 사용하여 식별해야 합니다. 우리의 예측 모델이 얼마나 잘 작동하는지 확인하기 위해서 Kaggle에 업로드하면 됩니다.


4.2 머신 설정


합성곱 신경망을 훈련시키기 위해서는 매우 강력한 GPU머신이 필요합니다.

이 튜토리얼을 진행하기 위해 사용한 시스템은 다음과 같습니다.

16기가 메모리

CPU: Intel® Core™ i7-4720HQ CPU @ 2.60GHz x 8

GPU: GeForce GTX 980M/PCIe/SSE2 + 3기가 메모리

OS: Ubuntu 16.04

그래픽 드라이버, Caffe 그리고 Opencv 를 설치하는 방법은 이전에 올렸던 Ubuntu 16.04 에 Caffe 설치하기(GPU with CUDA) 를 참조하기 바랍니다.


이제 이 튜토리얼을 수행하기 위해 작성된 코드들을 다운로드 하기 위해 git 명령어로 다운로드를 합니다.

git clone https://github.com/kyuhyong/deep_learning_caffe.git


이 Repsitory는 다음과 같에 세개의 폴더로 구성되어 있습니다.

  • caffe_models :  우리가 사용할 Caffe모델이 선언되어 있는 파일이 들어있습니다.

  • input: 데이터셋, 평가용 데이터등의 자료들을 담고 있는 폴더입니다. 처음에는 비어있습니다.

  • pycode: 데이터 처리, 트레이닝 결과를 수행할 파이썬 코드를 담고있습니다.


4.3 Caffe 둘러보기


Caffe는 버클리 비젼, 학습 센터(Berkeley Vision and Learning Center:BVLC)에서 만든 딥러닝 프레임웍입니다. 이것은 C++로 작성되었으며 파이썬과 매트랩 바인딩을 포함하고 있습니다.

Caffe를 사용하여 합성곱망을 훈련하기 위해서는 다음 4개의 단계를 거칩니다.


  • 1단계 - 데이타 준비: 이미지를 정리하고 적절한 포맷으로 저장하여 Caffe에서 사용하기 위한 준비를 합니다. 파이썬 스크립트를 통해 이미지 처리와 저장을 수행할 수 있습니다. Caffe에서 직접 이미지를 읽고 처리하는데 많은 시간이 소요되므로 Image의 요소들을 DB와 같이 만들어주는 LMDB파일을 만들게 됩니다.

  • 2단계 - 모델 정의: 이 단계에서는 합성곱 신경망(CNN)에 대한 정의, 파라미터와 설정값들을 포함하는 .prototxt 파일을 사용합니다.
  • 3단계 - Solver 정의: Solver는 모델 최적화를 담당합니다.  Solver에 관련된 파라미터들은 역시 .prototxt 파일로 되어있습니다.

  • 4단계 - 모델 훈련: 터미널에서 Caffe 명령어를 실행하여 모델을 훈련시킵니다. 모델이 훈련되면 .caffemodel 형식의 훈련된 모델이 생성됩니다.


훈련 단계를 거치면 .caffemodel을 사용하여 처음보는(Unseen) 이미지들에 대한 예측을 수행합니다.


4.4 데이터 준비


다운로드한 train.zip 과 test1.zip 파일을 git으로 다운로드한 repository의 input 폴더에 복사합니다. 복사가 완료되면 다음 명령어를 사용하여 파일을 모두 풉니다.


cd ~/deep_learning_caffe/input/ unzip train.zip unzip test1.zip rm *.zip


이제 pycode 폴더로 이동하여 create_train_lmdb.py 를 실행하여 train 과 validation LMDB파일을 생성합니다.


cd ../pycode

python create_train_lmdb.py ~/deep_learning_caffe/input/


create_train_lmdb.py 스크립트는 다음을 수행합니다.

  • input/train 폴더에 있는 이미지들을 읽고 모든 이미지에 대한 히스토그램 평준화(Equalization)를 수행합니다. Histogram equalization은 이미지의 Contrast(채도)를 조절하는 기술입니다.

  •  모든 이미지들을 227 x 227 사이즈로 변경합니다.
  •  훈련 데이터들을 2가지 세트로 나눕니다. 전체의 5/6은 훈련을 위한 데이터로 분류해서 train_lmdb를 만들고 나머지 1/6은 평가를 위한 데이터로 분류하여 validation_lmdb를 만듭니다. 훈련 세트는 모델을 훈련시키는데 사용되고 평가용 세트는 모델의 정확성을 측정하는데 사용됩니다.
  •  이렇게 만들어진 2개의 LMDB 데이터베이스를 train_lmdb와 validation_lmdb로 저장하여 각각 훈련과 평가용으로 사용합니다.


아래는 이 코드에서 가장 중요한 부분을 설명합니다.

def transform_img(img, img_width=IMAGE_WIDTH, img_height=IMAGE_HEIGHT):

    #Histogram Equalization
    img[:, :, 0] = cv2.equalizeHist(img[:, :, 0])
    img[:, :, 1] = cv2.equalizeHist(img[:, :, 1])
    img[:, :, 2] = cv2.equalizeHist(img[:, :, 2])

    #Image Resizing
    img = cv2.resize(img, (img_width, img_height), interpolation = cv2.INTER_CUBIC)

    return img

transform_img는 컬러 이미지를 입력받아서 3개 컬러 채널에 대해 히스토그램 평준화(Equalization)를 수행하고 이미지의 사이즈를 변경합니다.



make_datum은 이미지를 받아서 그것의 라벨을 붙여서 이미지와 라벨을 포함하는 Datum 객체로 반환합니다.

def make_datum(img, label):
    #image is numpy.ndarray format. BGR instead of RGB
    return caffe_pb2.Datum(
        channels=3,
        width=IMAGE_WIDTH,
        height=IMAGE_HEIGHT,
        label=label,
        data=np.rollaxis(img, 2).tostring()) 

다음 코드는 훈련 데이터의 5/6을 lmdb에 넣는 과정입니다.

 
with in_db.begin(write=True) as in_txn:
    for in_idx, img_path in enumerate(train_data):
	if in_idx % 6 == 0:		# 5/6 of train data used for train
		continue
	img = cv2.imread(img_path, cv2.IMREAD_COLOR)
	img = transform_img(img, img_width=IMAGE_WIDTH, img_height=IMAGE_HEIGHT)
	if 'cat' in img_path:
	    label = 0
	else:
	    label = 1
	datum = make_datum(img, label)
	in_txn.put('{:0>5d}'.format(in_idx), datum.SerializeToString())
	print '{:d} of {:d}|'.format(in_idx,dataNum) + os.path.basename(os.path.dirname(img_path)) + "/"+ os.path.basename(img_path) + ' as {:d}'.format(label),"          \r",
in_db.close()



훈련 데이터의 평균값 이미지(Mean image)생성하기


아래 명령어를 사용하여 훈련 데이터의 평균값 이미지를 생성합니다. 모든 특징점 픽셀들이 평균 제로(zero mean)을 보장하기 위해 각각의 입력 이미지로부터 평균값 이미지를 빼게 됩니다. 이 과정은 Supervised 머신러닝에서 공통적으로 적용되는 전처리 단계입니다.


cd ~/caffe/build/tools ./compute_image_mean -backend=lmdb ~/deep_learning_caffe/input/train_lmdb/ ~/deep_learning_caffe/input/mean.binaryproto


4.4 모델 정의


합성곱(CNN) 신경망을 사용하기로 결정하였으므로 확장자가 .prototxt 인 train_val 파일에 파라미터들을 정의해야합니다. Caffe는 Alexnet 혹은 Googlenet 과 같은 유명한 합성곱 신경망들을 제공합니다. 이 튜토리얼에서는 Alexnet을 살짝 변경한 blvc_reference_caffenet 을 사용할 것입니다. 

git으로 Repsitory를 클론하였다면 caffe_models/caffe_model_1/caffenet_train_val_1.prototxt 파일을 열면 됩니다..


그리고 다음 몇가지 파라미터들을 수정합니다.

  •  13, 24, 40 51번째 줄에서 myhome을 여러분의 홈 디렉토리로 변경합니다.
  •  우리가 만들 신경망은 개 또는 고양이 2개만을 구분하므로 신경망의 Output은 2개 입니다. 373번째 줄에서 num_output 을 2로 변경합니다.


caffe는 이렇게 정의된 신경망의 구조를 보다 이해하기 쉽게 도표화할 수 있는 파이썬 스크립트 도구를 제공합니다.



python ~/caffe/python/draw_net.py ~/deep_learning_caffe/caffe_models/caffe_model_1/caffenet_train_val_1.prototxt ~/deep_learning_caffe/caffe_models/caffe_model_1/caffe_model_1.png


 위 스크립트를 실행하면 모델의 구조가 caffe_models/caffe_model_1/caffe_model_1.png 이라는 파일로 만들어지게 됩니다.




4.5 Solver 정의


솔버는 모델 최적화를 담당합니다. 솔버의 파라미터들은 .prototxt 파일에 정의되어 있는데 이 파일은 deep_learning_caffe/caffe_models/caffe_model_1/solver_1.prototxt

에 존재합니다.


이 솔버는 모델의 정확성을 매 1,000 iteration마다 계산하게 됩니다. 최적화 과정은 최대 40,000회의 iteration을 수행하면서 매 5,000 iteration 마다 스냅샷을 저장하게 됩니다.(주, iteration을 많이 할수록 정확성이 증가하지만 연산 시간이 증가하게 되므로 적절한 횟수로 제한하는 것이 좋다. 사양이 낮다면 10,000회 정도로도 만족할만한 결과를 얻을 수 있다.)


base_lr, lr_policy, gamma, momentum 그리고  weight_decay등의 파라미터들이 모델의 수렴성을 개선하는 중요한 요소들입니다.

lr_policy는 "step", stepsize가 2500, base_lr가 0.001 그리고 gamma: 0.1로 설정하였습니다.

이렇게 설정하면 학습 속도를 0.001로 시작하여 매 2500 iteration 마다 10배씩 증가시키게 됩니다.


그리고 1번, 13번 줄의 파일 경로에서 /myhome 역시 각자의 홈 디렉토리로 설정합니다.

최적화 과정에는 다른 전략을 선택할 수 있습니다. 더 자세한 사항을 알고싶다면 다음 항목 solver documentation을 찾아보기 바랍니다.


4.6 모델 훈련


모델과 솔버에 대한 정의가 완료되면 이제 caffe 에서 제공하는 train 명령을 통해 훈련을 시작하게 됩니다.



cd ~/caffe/build/tools ./caffe train --solver ~/deep_learning_caffe/caffe_models/caffe_model_1/solver_1.prototxt 2>&1 | tee ~/deep_learning_caffe/caffe_models/caffe_model_1/model_1_train.log


이제 caffe는 데이터를 분석하면서 모델을 훈련시킵니다. 훈련에 걸리는 시간은 컴퓨터 사양과 max iteration 값에 따라 달라집니다. 

터미널 창을 보면서 매 5000 iteration 마다 accuracy 와 loss를 주시합니다. 그리고 이때 snap shot들을 caffe_model_1 폴더에 caffe_model_1_iter_10000.caffemodel. 같은 이름으로 저장하게 됩니다.


학습 곡선 그리기


학습 곡선은 매 iteration마다 훈련과 테스트 손실(Loss)율을 그래프로 나타낸 것입니다. 

이 그래프는 훈련/평가 손실과, 정확성을 시각화하는데 매우 유용합니다.

해당 파이썬 스크립트는 pycode 폴더에 plot_learning_curve.py 라는 파일을 실행하면 됩니다.



python ~/deep_learning_caffe/pycode/plot_learning_curve.py ~/deep_learning_caffe/caffe_models/caffe_model_1/model_1_train.log ~/deep_learning_caffe/caffe_models/caffe_model_1/caffe_model_1_learning_curve.png



4.7 새로운 데이터에 대한 예측


이제 훈련된 모델이 생겼으므로 처음보는 데이터(test1.zip에 포함된 이미지들)에 대해서 예측을 수행할 수 있습니다. 이것은 pycode 폴더에 있는 make_predictions_1.py 스크립트를 수행하면 됩니다. 이 코드를 실행하기 위해서는 4개의 파일이 필요합니다.


1.  test 이미지 파일.

2. mean image : 4.4절에서 만든 평균값 이미지.

3. 모델 아키텍쳐 파일: train과정에서 만들어진 caffenet_deploy_1.prototxt 라는 파일을 사용합니다. 이 파일은 caffenet_train_val_1.prototxt 파일과 거의 유사하지만 몇가지가 변경되어 있습니다. 우리는 여기에서 data_layer 를 지우고 input 레이어를 추가한 다음 마지막 레이어의 형식을 SoftmaxWithLoss 에서 Softmax로 변경합니다.

4. 훈련된 모델 가중치: 이것은 training 단계에서 생성된 파일입니다. 우리는 caffe_model_1_iter_10000.caffemodel을 사용할 것입니다.


원래 스크립트에서는 위 파일들이 하드코딩 되어 있으나 일부 내용을 변경하여 make_predictions_1.py에 대한 입력 인자로 다음의 세가지를 넣도록 변경하였습니다.


 1. input 폴더의 경로

 2. caffe 모델의 경로

 3. test 파일이 있는 폴더의 경로


python ~/deep_learning_caffe/pycode/make_predictions_1.py ~/deep_learning_caffe/input/ ~/deep_learning_caffe/caffe_models/caffe_model_1/ ~/deep_learning_caffe/input/test1/

아래는 코드에서 중요한 부분을 발췌한 것입니다.

먼저 평균값 이미지(mean image)를 mean_array에 배열 형식으로 전달고 caffe 모델의 caffenet_deploy_1.prototxt 파일과, 모델 트레이닝으로 생성된 모델파일인 caffe_model_1_iter_10000.caffemodel 파일을 caffe의 Net에 전달합니다.


#Read mean image
with open(meanBinary) as f:
    mean_blob.ParseFromString(f.read())
mean_array = np.asarray(mean_blob.data, dtype=np.float32).reshape(
    (mean_blob.channels, mean_blob.height, mean_blob.width))
...
#Read model architecture and trained model's weights
net = caffe.Net(caffeProto,
                caffeModel,
                caffe.TEST)

#Define image transformers
transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
transformer.set_mean('data', mean_array)
transformer.set_transpose('data', (2,0,1))

다음은 새로운 이미지를 읽고 이미지를 모델의 입력에 맞게 처리한 다음 모델을 통과시켜 어떤 확률이 나오는지에 따라 분류하여 (0에 가까우면 고양이, 1에 가까우면 개) 저장합니다.

   
img = cv2.imread(img_path, cv2.IMREAD_COLOR)
    img = transform_img(img, img_width=IMAGE_WIDTH, img_height=IMAGE_HEIGHT)
    
    net.blobs['data'].data[...] = transformer.preprocess('data', img)
    out = net.forward()
    pred_probas = out['prob']

    test_ids = test_ids + [img_path.split('/')[-1][:-4]]
    preds = preds + [pred_probas.argmax()]
    argmax = pred_probas.argmax()
    if argmax == 1:
        shutil.copy2(img_path, dir1path)
    else:
        shutil.copy2(img_path, dir2path)


그리고 판별한 결과를 .cvs 파일에 저장하게 됩니다.

위 스크립트를 실행하게 되면 test폴더에 있는 이미지들이 클래스에 따라 분류되어 각각 predict폴더 안에 c1, c2폴더로 복사되어있음을 확인할 수 있습니다.


분류가 완료된 이미지 파일



2018/1/18에 최초 문서 발행됨

2018/1/22 일부 수정됨

+ Recent posts