얼마전에 Deep Learning 중 Convolutional Neural Network (CNN)에 대한 강의를 연다는 소식을 메일로 받게 되었습니다. 그런데 강의 내용 계획을 보니 Caffe 설치 방법이랑 Caffe 예제 실습이더군요. 처음에는 Caffe home page에만 가봐도 자세히 써져 있는 내용을 왜 강의를 할까 생각했습니다. 그러나 의외로 Caffe 사용방법에 대한 내용을 궁금해 하시는 분들이 있을 것 같아서 제가 해본 것 중 Caffe를 이용하여 CNN custom model 만드는 방법을 이 글에서는 소개하려고 합니다. 이 글에서는 DNN과 CNN에 대한 설명들은 안하고 정말 그냥 custom model 만드는 방법 중 한가지만 소개하려고 합니다. 뭐 CNN에 대한 내용이나, Neural Network에 대한 내용은 여기 저기 자세히 설명해 놓으신 블로그나 논문이 많으니 읽어보시기 바랍니다.



1. Training 시킬 데이터를 수집하자!


    먼저, CNN을 학습시키기 위해서는 많은 양의 image data가 필요합니다. 사실 이 작업 육체적으로나 정신적으로나 어마어마하게 힘듭니다. Custom model을 만들 필요가 없이 그저 실습을 해보실 생각이라면 ImageNet에서 데이터를 다운받는 것을 추천합니다. (저는 3년 전에 가입해서 기억이 가물가물 합니다만, 제 기억상으로는 연구소 mail이나 학교 mail로 연구용으로 사용한다는 것을 인증하면 바로 다운 받을 수 있었던 것 같습니다. image net data를 사용할 경우 data의 크기가 매우 크니 HDD 용량이 넉넉하지 않으시다면 용량을 확보하시고 다운 받으시기 바랍니다. 해외서버라서 좀 느릴 수도 있습니다.)

먼저 데이터를 모으는 방법은 크게 다음과 같이 2가지가 있습니다.


1. 직접 찍는다.

2. 웹에서 다운로드를 받는다.


    사실 위의 두 방법은 너무 당연한 이야기죠. 개인적으로 제가 custom model을 만들때는 직접 찍은 데이터를 적절하게 섞어줘야 인식할때 원하는 결과가 나왔습니다. 예를 들어 제가 다니는 학교 문은 웹상에 올라와 있는 일반적인 문들과는 조금 생김새가 다르기 때문에 웹에서 다운받은 데이터로만 하다가는 학습이 잘 안 될 수도 있습니다. (제가 다니는 학교 문은 image net data로 학습시킨 모델로 classification을 진행하면 금고로 나옵니다. 사실 image net data에는 1000개의 class만 있기 때문에 우리가 원하는 class가 없을 수도 있습니다. 예를들어 ILSVRC2012 data set에서 door 종류는 sliding door밖에 없고, elevator도 없죠. 우리가 custom model을 만드는 이유 중 하나이기도 합니다. 하지만 atm과 cat, vending machine같은 class는 있기 때문에 만약 training 시키고자 하는 class가 image net data set에 있다면 적절히 활용하는 것이 image data를 수집하는 과정에서 발생하는 stress를 줄여 줄 수 있을 것 같습니다.) 

    직접 image를 찍는 것도 힘든 일이지만 웹에서 다운로드 받는 것도 많은 시간이 듭니다. MNIST 데이터 셋은 60000장의 image data를 training set으로 사용하고 Image Net은 class당 1300장의 데이터를 사용 하죠. 한 class 당 1000장만 수집하고 class가 10개만 되도 10000장의 image를 수집해야 합니다. 때문에 저 같은 경우는 python을 이용하여 crawler를 만들어서 image를 수집했습니다. crawler를 이용해 image를 수집하니 연관없는 사진들도 많이 수집되었습니다. 몇몇 분들은 image에 존재하는 pixel값을 이용하여 필터링을 하실 수도 있겠지만, 사실 pixel값을 이용해서 필터링을 해도 연관없는 사진들은 여전히 많습니다. 이러한 연관없는 사진을 없애는 일은 사람이 직접 할 수 밖에 없습니다. 완벽하게 연관없는 사진을 코드로 필터링 할 수 있다면 training set을 이용하여 CNN을 학습시킬 이유도 없죠^^

    다음은 제가 image를 수집하기 위해 작성한 간단한 python code입니다. 정말 image 수집만을 목적으로 했기 때문에 매우 간단하게 만들었습니다. 아마 직접 작성하시는게 더 좋을 겁니다. 하지만 그냥 빨리 custom data set을 수집하고, 실습을 하고 싶으신 분들이 사용하거나, 참조하실 분들은 참조하시라고 올립니다.


#-*- coding: utf-8 -*-
from bs4 import BeautifulSoup
import requests
import urllib
import urllib2
import os
from os.path import os
import html5lib
import codecs

count = 28
index = 1
max_count = 1000 #number that you want to download

imageDataDir = "F:/image/"
#imageDataDir = "/home/san/image/" #linux directory
querySet = ['door','현관문','cat','fire extinguisher','tiger'] #query for searching


def get_soup(url,header):
    return BeautifulSoup(urllib2.urlopen(urllib2.Request(url,headers=header)))

if not os.path.isdir(imageDataDir):
    os.mkdir(imageDataDir)
    
for query in querySet:
    image_naming = query #image name
    query= query.split()
    tempDirName = '_'.join(query)
    query='+'.join(query)
    isQueryKorean = False

    targetDir = imageDataDir+tempDirName

    try:
        if not os.path.isdir(targetDir):
            os.mkdir(targetDir)
    except:
        isQueryKorean = True
        image_naming="temp"
        targetDir = imageDataDir+"temp"
        dirNum=0
        while(True):
            tempTargetDir = targetDir+str(dirNum)
            dirNum+=1
            if not os.path.isdir(tempTargetDir):
                targetDir = tempTargetDir
                os.mkdir(targetDir)
                break
        
    index = 1 #init index
    
    for i in range(max_count/count):
        url="http://www.bing.com/images/search?q="+query+"&first="+str(index)+"&count="+str(count)+"&FORM=HDRSC2"
        index = index+count
        print url #print query url

        page = urllib.urlopen(url).read()
        soup = BeautifulSoup(page, 'html5lib')

        for img_temp in soup.find_all("a", "thumb"):
            img = img_temp.get('href')
            try:
                print img
                raw_img = urllib2.urlopen(img).read()
                cntr = len([i for i in os.listdir(targetDir) if image_naming in i]) + 1
                print cntr
                f = open(targetDir+"/" + image_naming + "_"+ str(cntr)+".jpg", 'wb')
                f.write(raw_img)
                f.close()
            except:
                print("fail to download")

    제가 주석쓰는걸 귀찮아 하고 python이 주로 사용하는 언어가 아니라서 이상한 점이 있더라도 양해 부탁드립니다. 먼저 query url을 bing을 사용한 이유는 bing source code 분석 결과, bing이 thumbnail이 아닌 원본 image 다운로드가 편했기 때문입니다. 구글로 검색하시고 싶으신 분은 google source code 보고 download 받으시면 됩니다. 개인적으로 google은  thumbnail은 받기 쉬운데 원본 source image 받기가 힘들더군요. (google에서 원본 image 받는 방법 아시는 분은 알려주시면 감사하겠습니다. 뭐 들리는 이야기로는 구글에 돈내면 한달에 1000 query인가 검색할 수 있다고 합니다. 확실하진 않습니다. selenium, mechanize 제외) max_count 변수는 다운로드할 image의 갯수입니다. 하지만 실제로 저 만큼 받아지진 않습니다. 제가 올린 코드에서는 jpg format만 받게 해놔서 jpg가 아닌 이미지들은 안받아지거든요. (가끔식 jpg가 아닌 파일도 받아지긴 합니다.) 때문에 필요한 image 갯수보다 넉넉하게 설정하시는게 좋습니다.

    모아진 데이터를 보면 cat같은 데이터는 noise 없이 잘 모아지는 편이나, fire extinguisher 같은 경우에는 noise가 껴있는 경우가 있습니다. class에 따라서 제대로 된 데이터가 별로 없는 경우도 있습니다. (ex. door sign, elevator 등)

    데이터 필터링을 하시면 됩니다. 이것 추가하고 저건 지우고 하는 방식으로 데이터 셋을 변경해가며 실험하는 것도 좋을거 같습니다. 이게 또 데이터가 많다보면 하기 귀찮을 때가 있는데 그냥 대충하세요^^. CNN을 학습시킬때 목적에 따라 좋은 데이터가 있고 좋지 않은 데이터가 있을 수 있습니다. 저 같은 경우는 일상생활 중에 찍은 사진을 분류하기 위해 모델을 만들었으므로 위 그림에서 fire extinguisher_12.jpg 같은 경우에는 좋은 데이터였습니다. 왜냐하면 2~6번 image와 같이 배경이 흰색인 경우는 일상생활에서 별로 접하기 힘들기 때문이죠. 이 부분도 개인의 목적에 맞게 데이터를 선별하시면 됩니다. (Image Net data set들도 download해서 보시면 이건 좀 이상한데 싶은 이미지들 섞여 있습니다. 필터링 하다가 귀찮았나 보죠?) 저같은 경우는 이 데이터에 제가 직접 찍은 사진들도 추가했습니다.

    이제 train set과 valid set을 분류해 주세요. 필요할 경우 test set도 마련하면 좋지만 필수 사항은 아닙니다. 저같은 경우는 train set의 라벨링을 먼저 진행 한다음에 train set 중에 valid 용으로 사용할 이미지들을 따로 선별했습니다.



2. Labeling을 하자!


    이렇게 데이터를 모았으면 이제 라벨링을 해야합니다. 솔직히 caffe에서는 file path명만 써주면 되서 그냥 listdir() 함수를 이용해서 train list 작성을 해도 됩니다만 저는 원본 파일은 놔두고 라벨링을 한 set을 따로 만들었습니다.


from os import listdir
from os.path import isfile, join
import os
from shutil import copyfile

dirSet = ['atm','door','door_sign','elevator','emergency_exit','extinguisher', 'outlet','trash_bin', 'vending_machine']
sourcePath = '/home/san/imageSet/'
destPath = '/home/san/caffe/imageSet/'

newDirName = 0

for targetDir in dirSet:
    onlyFiles = [f for f in listdir(sourcePath+targetDir) if isfile(join(sourcePath+targetDir),f))]

    newName = 0
    newTargetDir = destPath+'n'+str(newDirName)
    os.mkdir(newTargetDir)

    for name in onlyFiles:
        print ('copy '+sourcePath+targetDir+'/'+name)
        copyfile(sourcePath+targetDir+'/'+name,newTargetDir+'/'+str(newDirName)+'_'+str(newName)+'.JPEG')
        newName+=1
    newDirName+=1

    갑자기 linux로 바뀌었죠? 저는 그냥 이 작업부터 linux로 진행했습니다. (data 모으는 것은 오래 걸리기 때문에 linux로 하면 그 오랜 시간동안 카카오톡을 못하거든요^^) 일단 저는 위와 같이 python을 이용하여 labeling을 진행하였습니다. 라벨링을 하고 나서 val 폴더에 valid set을 선별하여 넣었습니다. 저 같은 경우는 그냥 30개 씩했는데 원하는 만큼씩 valid set을 선별하세요.

~/caffe/imageSet Directory의 모습

train set의 모습

valid set의 모습


라벨링을 하고 나면 train list가 적혀 있는 train.txt와 valid.txt를 작성해야 합니다. 저는 다음과 같이 작성하였습니다.

이것도 사람이 작성하기엔 힘들어서 있어서 code로 작성했습니다. 이 코드는 굳이 올릴 필요성을 못느끼겠네요. 파일 경로 옆에 써져있는 숫자들은 index입니다. 이 index는 몇번 output node로 해당 클래스를 학습시킬지 알려주는 것입니다. 저같은 경우는 n0 class(atm)는 0번, n1 class(door)는 1번으로 학습시켰습니다. 이것도 자신이 정하기 나름입니다. 하지만 아직 언급하진 않았지만 자신이 만든 CNN의 output node 숫자보다는 적어야 합니다. 또한 여러 클래스를 같은 index number로 학습시켜도 상관 없지만 그러면 그 클래스들 간에 구분이 되지 않으니 의미가 없겠죠? 

오늘 포스트는 여기까지입니다. 사실 저는 이렇게 학습 데이터를 모으는게 가장 힘들었습니다. 학습은 CNN구조를 바꾸거나, 여러가지 인자들을 변경한 다음 돌려놓고 30~40시간 동안 다른 일을 하면 되서 별로 안힘들었거든요. 어쨋든 다음 포스팅에서는 imagenet lmdb data만드는 것에서 부터 CNN training 시키는 것과 classification 하는 데까지 써보도록 하겠습니다.

+ Recent posts