아롱이 탐험대

얼굴 나이 인식기 개발 - 1 data preprocess (1) (Using EfficientNet with Pytorch) 본문

Project/pytorch

얼굴 나이 인식기 개발 - 1 data preprocess (1) (Using EfficientNet with Pytorch)

ys_cs17 2020. 9. 8. 13:31
반응형

1. Motivation

이번 시간에는 pytorch를 사용하여 facial age dataset으로 학습시킨  facial age classifier를 개발해보자.

이제는 kaggle에 도전해볼 실력이 된 것 같아 facial age dataset classifier라는 재미있어 보이는 과제를 수행하기 위해 시작하였다.

(kaggle 원본 링크: (https://www.kaggle.com/frabbisw/facial-age

 

2. Process

우선 해당 problem은 classification이기 때문에 object detection보다는 난이도가 비교적 원활하다.

하지만 얼굴은 사람에 따라 노안이거나 동안인 사람도 있고, 인종, 피부 등에 따라서도 많이 엇갈리는 어려움이 존재한다.

처음에는 1살, 2살,... , 70살과 같이 여러 개의 label로 나누어진 facial face dataset의 label을 그대로 사용하여 ResNet을 통해 학습을 시켜보았지만 ResNet의 성능도 그리 좋지 않았고, 무엇보다 label의 개수 (약 70개)에 비해 데이터가 매우 적어 약 30%의 정확도로 학습이 제대로 진행되지 않았다.

 

따라서 더욱 많은 데이터 셋을 구하고자 구글링을 진행하였다.

그 결과 https://drive.google.com/drive/folders/1dZhJNGmdoO1La6MOJRfUYxDZwIa-hXNX를 통해 라벨링이 되어있는 약 67000 개의 데이터를 얻게 되었다. 우리의 프로젝트는 위 드라이브에 있는 데이터를 기준으로 진행할 것이다.

 

인공 지능을 포함한 빅데이터 등 수많은 데이터를 요구하는 프로젝트에서 data preprocess는 가장 시간이 많이 걸리고, 매우 귀찮은 작업이다.

처음부터 프로젝트를 진행하는 경우에는 데이터 셋을 직접 촬영한 뒤, 이 데이터들을 직접 라벨링을 일일이 진행해야 한다.  labeling을 진행하는 과정은 이전 게시물을 참고하길 바란다. (https://ys-cs17.tistory.com/27)

 

우리의 프로젝트는 다행이도 labeling이 되어있는 다량의 데이터들을 발견하였고, 분류 문제는 비교적 데이터 라벨링이 어렵지 않다.  대부분의 classification model들의 labeling format은 아래 이미지와 같이 라벨에 해당하는 폴더명에 이미지 파일을 삽입하면 된다.

 

facial age dataset (kaggle)

하지만 아래 이미지와 같이 label format이 파일명에 포함되어 있는 data도 존재한다.

 

 

따라서 우리는 다른 3개의 data (combined_faces, facial-age, UTKFace)를 통합된 format으로 변환시킨 후 전처리를 진행해야 한다.

나는 비교적 더 편한 전자의 폴더 방식 labeling을 진행하였다.

 

3. Directory change Codes

전체 코드 https://github.com/yunseokddi/pytorch_dev/blob/master/facial_age_classifier/EfficientNet_ver/data_preprocess/preprocess.py

 

(1) __init__

수많은 시행착오가 발생할 것을 대비해 data preprocess 코드는 재사용성을 위해 class로 제작하였다.

import os
import shutil
import argparse


class file_preprocess:
    def __init__(self, src_folder, dst_folder):
        self.src_folder = src_folder
        self.dst_folder = dst_folder
        self.file_list = os.listdir(src_folder)

해당  class에서 사용되는 모듈은 os, shutil, argparse가 있고, 각각 폴더 내 파일 관리, 파일 이동 및 복제 관리, 터미널 입력값 관리를 해주는 모듈들이다.

 

class의 인자 값으로 시작되는 폴더, 목적이 되는 폴더를 지정한 후, file_list 변수에는 시작되는 폴더의 file list 들을 list 형태로 참조한다.

 

(2) create_folder

    def create_folder(self):
        for file in self.file_list:
            if not (os.path.isdir(self.dst_folder+ file.split('_')[0])):
                os.makedirs(os.path.join(self.dst_folder +file.split('_')[0]))

        print('Age file create finish')

이제부터는 class의 method에 대해 정의를 해보자.

우선은 폴더를 만드는 함수이다. 우리는 데이터 전처리를 하기 위해 labeling을 진행한다. 해당 label에 기준이 되는 것은 위에 말한 바와 같이 folder name이다.

따라서 우리는 모든 데이터에 대해 label이 되는 folder를 생성해야 하고, 이는 해당 코드를 통해 진행한다.

우선 위에서 class 생성 시 시작 경로에 있는 폴더의  file을 담은 list를 file_list라는 인스턴스로 저장을 하였다.

이를 for문을 통해 file name을 split('_')로 분리한 뒤 첫 번째 분리 요소를 가지고 폴더를 생성한다.

 

combined_faces data

쉽게 설명을 하면 위 이미지의 아이는 1살이고, 이미지의 이름은 1_1.jpg이다. 우리는 위 이미지를 labeling 하기 위해서는 1이라는 폴더가 필요하다. 물론 하나하나 직접 만들 수 있지만, 나이의 폭이 1~100살까지 존재하기 때문에 전부 다 마우스로 만들기는 너무 귀찮다.

따라서 combined_faces에 있는 모든 이미지 파일들의 이름을 split('_')을 통해 분리한다.

분리하게 되면 위 이미지 기준으로 1, 1.jpg라는 list로 변환이 된다. 우리는 이 중에서 1만 필요하기 때문에 0번째 요소를 선택하여  폴더를 생성하면 된다.

폴더를 생성하기 위해 if문을 사용하여 만약 해당 이름의 폴더 (./dst_folder/나이)가 존재하지 않다면 os.makedirs를 사용해 폴더를 생성하도록 코드를 구성하였다.

 여기서 os.path.join은 파일의 경로를 윈도, 맥, 우분투에 알맞게 자동으로 변경을 해준다.

INPUT: python3 preprocess.py --src_path ./data/combined_faces --dst_path ./data/ --create_folder
OUTPUT: Age file create finish

코드 실행 결과

 

(3) move_file_by_split_age

    def move_file_by_split_age(self):
        for file in self.file_list:
            shutil.move(self.src_folder + '/' + str(file), self.dst_folder + '/' + file.split('_')[0] + '/' + str(file))

        print('File move finish for {} files'.format(len(self.file_list)))
        shutil.rmtree(self.src_folder)

label에 해당하는 모든 폴더를 생성을 했으니 이제는 이미지 파일들을 각 폴더에 옮길 차례다.

아까와 같이 for문을 통해 self.file_list에 있는 file를 순차적으로 접근하고 split을 통해 해당 나이에 해당하는 폴더로 shutil.move를 통해 이동시킨다.

모든 파일을 이동시킨 후 shutil.rmtree를 통해 이동이 다돼서 파일이 없는 폴더를 삭제한다.

 

INPUT: python3 preprocess.py --src_path ./data/combined_faces --dst_path ./data --move_files    
OUTPUT: File move finish for 33486 files

INPUT: python3 preprocess.py --src_path ./data/UTKFace --dst_path ./data --move_files 
OUTPUT: File move finish for 23708 files

실행 결과 파일들이 잘 옮겨진 것을 확인할 수 있다.

 

 

(4) change_names && facial_age_data_move

지금까지 변경하였던 데이터들과 다르게 facial_age data는 format 형식이 다름으로 다른 방식을 통해 data를 변경해야 한다.

 

facial-age dataset

위 이미지와 같은 형식으로 되어있고, 해당 폴더 안에 각 label에 맞는 data가 있다.

  def change_names(self):
        for filename in self.file_list:
            os.rename(self.src_folder + '/' + filename, self.dst_folder + '/' + filename.lstrip('0'))

        print('facial age file change finish')

우선 다루기 좋게 파일명에 있는 왼쪽 0을 없애주자. 왼쪽 0을 없앨 때는 lstrip('0')을 통해 왼쪽 기준으로 이어저 있는 0들을 없애준다.

INPUT: python3 preprocess.py --src_path ./data/facial-age --dst_path ./data/facial-age --change_names    
OUTPUT: facial age file change finish

변경 후

 

    def facial_age_data_move(self):
        for i in range(1, 91):
            path = self.src_folder + '/' + str(i)

            for pack in os.walk(path):
                for f in pack[2]:
                    shutil.move(path+'/'+f, self.dst_folder+'/'+str(i)+'/'+f)

        print('facial age files move finish')
        shutil.rmtree(self.src_folder)

90살이 넘어가는 data들은 수량이 적어서 모두 삭제하였다. 따라서 for문을 1~90까지만 돌려 path를 변경시켜 준다.

이제 해당 메서드를 통해 폴더 명들을 변경한 facial-age dataset을 우리의 labeling dataset으로 이동시켜 준다.

os.walk()를 통해 디렉터리와 하위 디렉터리 안에 들어있는 파일들을 다음과 같이 탐색할 수 있다.

또한 pack에는 총인자가 3개가 존재한다.

 

0: dir과 files가 있는 path

1: root 아래에 있는 폴더들

2: root 아래에 있는 파일들

 

우리는 root 아래에 있는 파일들을 옮기는 문제라 2번째 idx만 필요하다.

따라서  shutil.move를 통해 파일들을 옮긴다.

INPUT: python3 preprocess.py --src_path ./data/facial-age --dst_path ./data --facial_age_data_move
OUTPUT: facial age files move finish

 

 

 

 

 

 

반응형
Comments