아롱이 탐험대

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

Project/pytorch

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

ys_cs17 2020. 9. 10. 16:11
반응형

Review

이전 시간에는 데이터 수집과 디렉터리 변경 및 labeling에 대해 알아보았다.

이번 시간에는 matplotlib을 활용하여 그래프화 및 나머지 전처리에 대해 진행하겠다.

1편: https://ys-cs17.tistory.com/28

 

1. make_graph.py Code

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

(1)  __init__

class analysis_data:
    def __init__(self, root_dir, start_age, end_age):
        self.root_dir = root_dir
        self.start_age = start_age
        self.end_age = end_age + 1
        self.x = [i for i in range(self.start_age, self.end_age)]

우선 class initialize 함수에 대해 살펴보자.

 

__init__ 함수 parameter

1. root_dir: dir의 root가 되는 경로

2. start_age: 나이가 시작되는 라벨 (우리의 데이터는 1살부터 시작)

3. end_age: 나이가 끝나는 라벨 (우리의 데이터는 90까지)

 

이 함수에서 end_age는 for문 인덱싱 때문에 +1을 해주었다.

self.x는 나중에 사용할 matplotlib plot의 x축이 된다.

 

(2)  get_file_len_list

    def get_file_len_list(self):
        folder_len = self.end_age - self.start_age + 1
        count = 0
        file_num = []

        for i in range(folder_len):
            path = self.root_dir + '/' + str(i)

            for pack in os.walk(path):
                for f in pack[2]:
                    count += 1

                file_num.append(count)
                count = 0

        return file_num

get_file_len_list 함수는 이전 시간에 구현하였던 facial_age_data_move와 비슷한 함수이다.

각 label에 존재하는 file list를 추출한 후, 각 sub directory에 존재하는 파일의 개수를 counting 하여 file_num이라는 변수에 list 형태로 저장한다.

 

(3)  get_avg_len && get_file_len

    def get_avg_len(self, file_num):
        avg = 0

        for num in file_num:
            avg += num

        avg /= len(file_num)

        return avg

다음은 각 label에 해당하는 데이터의 개수에 대한 평균값을 구해주는 함수이다.

for 문을 통해 avg값을 구해준다.

 

    def get_file_len(self, file_num):
        return len(file_num)

그다음은 file의 총개수를  return 시켜주는 함수이다.

 

(3)  draw_file_len_plt && draw_avg_len_plt && imshow_plot

    def draw_file_len_plot(self, file_num):
        plt.plot(self.x, file_num)
        plt.xlabel('age')
        plt.ylabel('data num')

    def draw_avg_len_plt(self, avg):
        avg_y = [avg for i in range(self.start_age, self.end_age)]
        plt.plot(self.x, avg_y)

    def imshow_plot(self):
        plt.title('Facial age data')
        plt.show()

해당 코드들은 matplotlib로 그래프를 그리는 코드이다. plt.plot으로 실질적인 데이터를 list 형태로 넣어 주게 된다.

또한 plt.show()로 함수 호출 전까지 그렸던 그래프들을 출력할 수 있다.

 

INPUT: python3 make_graph.py --root_dir ./data --start_age 1 --end_age 90 --draw_plot --draw_avg_plot

OUTPUT

파란색이 우리의 데이터이고, 주황색은 평균 개수이다.

결과 값을 보다시피 데이터의 편차가 매우 크다. 따라서 편차를 줄이기 전 각 label을 각 세대 초중반, 후반으로 merge 시키는 방법이 좋을 것 같아 merge를 진행하였다.

 

(4) merge_folder (preprocess.py)

다시 이전 preprocess.py 코드로 돌아와서 merge code를 구상해보자.

    def merge_folder(self):
        for i in range(1,91,5):
            if i == 1:
                i = 0
            for j in range(i+1, i+5):
                sub_path = self.src_folder+'/'+str(j)

                for files in os.listdir(sub_path):
                    shutil.move(self.src_folder+'/'+str(j)+'/'+ files, self.src_folder+'/'+str(i)+'/'+files)
                    shutil.rmtree(self.src_folder+'/'+str(i)+'/')

        print('merge finish')

우선 merge를 시키기 전 첫 번째 디렉터리인 1을 0으로 변경해주고 코드를 실행시켜야 한다.

코드를 보면 i+1부터 i+4까지 for문이 동작함으로 예를 들어 i가 10이라면 11부터 14까지는 10으로 병합이 된다.

병합이 되는 코드는 shutil.move()로 구현하였다.

또한 병합 후 비어있는 디렉터리들을 shutil.rmtree로 제거해준다.

 

INPUT: python3 preprocess.py --src_path ./data --dst_path ./data --merge_files
OUTPUT: merge finish

result

(5) draw_bar

다시 make_graph.py로 돌아와 merge 한 디렉토리들을 그래프로 시각화해보자.

아까는 plot으로 진행을 하였지만, 개인적으로 plot보다는 bar가 더욱 시각적으로 보이기 때문에 merge한 디렉터리는 bar로 구현을 하겠다.

    def draw_bar(self, file_num):
        plt.title('Facial age data')
        plt.xlabel('age')
        plt.ylabel('data num')
        plt.xticks([i for i in range(self.start_age - 1, self.end_age, 5)])
        self.x = [i for i in range(self.start_age- 1, self.end_age, 5)]
        plt.bar(self.x, file_num)
        plt.show()

draw_bar도 plot과 그리는 방식이 비슷하다. 다른 점은 plt.plot에서 plt.bar로 바뀐 것뿐이다.

우리의 merged directories는 숫자가 5씩 증가하기 때문에 list의 3번째 parameter값을 5로 줌으로써 공차를 5씩 준다.

또한 plt.xticks를 사용하여 그래프 이미지의 찍히는 x 좌표 값들을 더욱 시각적으로 만들었다.

 

INPUT: python3 make_graph.py --root_dir ./data --start_age 1 --end_age 85 --draw_bar --print_avg
OUTPUT: avg is 3706

OUTPUT

merge전과 비교를 해보면 그나마 편차가 줄어든 모습을 볼 수 있다.

또한 argument 옵션으로 print_avg를 추가하여 output으로 평균값인 3706을 얻게 되었다.

 

위 그림을 보면 70 이후의 데이터의 양이 급격하게 낮아지는 모습을 볼 수 있다. 따라서 70부터 85까지 merge를 진행하였다.

해당 작업은 디렉터리가 별로 많지 않아 수작업으로 진행하였다.

 

다시 한번 merge 된 그래프를 봐보자

INPUT: python3 make_graph.py --root_dir ./data --start_age 1 --end_age 70 --draw_bar --print_avg
OUTPUT: avg is 4447

 평균값이 올라간 모습을 볼 수 있다.

 

(6) random_remove

 다음은 평균값에 알맞게 각 디렉터리의 file의 개수를 삭제를 해야 한다. 그 이유는 위 이미지와 같이 그대로 학습을 시키게 되면 0, 25에 해당하는 라벨들만 잘 학습이 되어 오버 피팅이 일어날 수도 있기 때문이다.

    def random_remove(self, num):
        random_files = random.sample(self.file_list, num)

        for file in random_files:
            os.remove(self.src_folder+'/'+str(file))

        print('{} random files removed'.format(num))

해당 함수를 살펴보면 random 모듈을 사용하여 인자 값으로 받은 num만큼 경로에 있는 파일을 지정한다.

그리고 for문을 통해 os.remove()를 사용하여 랜덤 한 파일들을 삭제한다.

INPUT: python3 preprocess.py --src_path ./data/20 --dst_path ./data --random_num 3000 --random_remove
OUTPUT: 3000 random files removed

 약 4000개의 데이터를 기준으로 삭제하였다.

result

결과를 bar로 표현을 해보면 처음보다 많이 좋아진 편차를 볼 수 있다.

 

2. train_val_split.py Code

다음은 train set과 validation set을 나누는 과정이다.

import os
import shutil
import random
import os.path

src_dir = '../data/class_15_data/train/70'
target_dir = '../data/class_15_data/val/70/'
src_files = (os.listdir(src_dir))

def valid_path(dir_path, filename):
       full_path = os.path.join(dir_path, filename)
       return os.path.isfile(full_path)


files = [os.path.join(src_dir, f) for f in src_files if valid_path(src_dir, f)]
choices = random.sample(files, 370)
for files in choices:
       shutil.move(files, target_dir)
print ('Finished!')

해당 경로를 변경을 한 후 아까와 마찬가지로 random의 값을 일정 수만큼 validation dataset으로 구분해주는 코드이다.

나는 약 10%의 비율로 train과 val를 나누었다.

반응형
Comments