[AITech][Final Project][P stage] 20220602 - 최종 프로젝트 14일차

3 minute read


최종 프로젝트 14일차

지난 목표였던 배치 처리 시 성능이 변하는 문제를 해결하였다. 배치 처리로부터 오는 모델의 동작이 달라지는 부분들이 있어서, 이를 완벽히 동일하게 맞추지는 못하고 배치 처리 환경 하에서 최적의 파라미터 세팅을 찾아주었다.

단순하게 생각했을 때는 배치 처리라는 것이 모델에게 한 번에 몇 개의 input을 주느냐이고, 이것이 결과에 영향을 주면 안된다고 생각했지만 서칭을 해보며 결과가 달라질 수 있다는 것을 알았다.

  • Dropout, BatchNorm 등 배치 크기에 영향을 받는 layer들(model.eval())
  • Training 과정에서 모델이 학습한 배치 크기와 Inference 과정에서 모델에게 주어지는 배치 크기가 다르면 결과값이 조금 달라진다.

크게는 위 두 사항을 발견했는데, 이외에 다른 요인들도 있을 수 있을 것이다.


배치 처리 시 성능 문제를 해결한 후, 1차 clustering 결과를 merge하는 코드를 작성하였다.

사용자에게 영상의 등장인물들을 제공하기 위해서는, 한 명의 인물이 여러 cluster로 분류되는 것보다 여러 명의 인물이 하나의 cluster로 분류되는 것이 더 큰 문제일 것이다. 현재 프로젝트에서는 각 cluster마다 가장 질이 좋은 한 장의 이미지를 사용자에게 출력할 것이기 때문에, 한 cluster 내에 여러 인물이 있는 것은 문제가 된다.

따라서 후자의 경우를 거의 없애기 위해 similarity 값(높을수록 엄격하게, 더 비슷해야 같은 cluster로 분류)을 조금 높게 주었다. 다만 이 경우에 한 인물이 여러 cluster로 분류되는 경우가 생기기 때문에, 이를 어느정도 합쳐주기 위해 cluster merging 코드를 작성한 것이다.

확인 결과 merging이 어느정도 유의미한 모습을 보였다.

아래는 팀 노션에 공유된 내용이다.


Cluster merging 방법

Cluster 내의 인물들의 face feature, cloth feature를 평균하여 해당 cluster의 face feature, cloth feature를 구합니다.

해당 방법을 이용하면 기존 clustering에 사용하던 256-d feature vector를 앞 뒤 128-d씩 잘라서 바로 사용하면 돼서, 추가적인 feature 저장이나 계산 과정이 필요 없음.

이 값들을 실험을 통하여 같은 인물 간 최대 distance는 어느정도인지, 다른 인물 간 최소 distance는 어느정도인지를 관찰합니다.

실험을 통하여 구한 값들에서 최대한 robust하게 threshold 값을 설정하였습니다.

  • 이때 robust하다는 것은 동일한 인물이 동일 cluster로 merge되도록 하되, 다른 인물이 동일 cluster로 merge되는 경우는 아예 없게 하는 것을 말합니다.
  • Merging 시 face feature만을 사용할 때는 FACE_THRESHOLD_HARD(0.18), cloth feature(0.12)만을 사용할 때는 CLOTH_THRESHOLD_HARD 사용
  • 두 feature를 함께 사용하여 merge를 시도할 때는 FACE_THRESHOLD_SOFT(0.19), CLOTH_THRESHOLD_SOFT(0.15) 사용
  • merging process를 여러 번 반복하는 iteration 설정 가능하게 함


코드

def merge_clusters(cluster_dict, fingerprints, iteration=1, FACE_THRESHOLD_HARD=0.18, CLOTH_THRESHOLD_HARD=0.12, FACE_THRESHOLD_SOFT=0.19, CLOTH_THRESHOLD_SOFT=0.15) -> dict:
    '''
    parameters:
        clusters: calc.cluster() 의 return 값 (dict / key=cluster_size(int), value=clusters(2d-array))
        fingerprints: feature vector dictionary (key=filepath, value=feature vector)
        iteration: merge 반복 횟수
        FACE_THRESHOLD_HARD
        CLOTH_THRESHOLD_HARD
        FACE_THRESHOLD_SOFT
        CLOTH_THRESHOLD_SOFT
    return:
        merged_clusters: calc.cluster() 의 return 값과 동일한 형태 (dict / key=cluster_size(int), value=clusters(2d-array))
    '''

    for _ in range(iteration):
        cluster_list = sorted([[key, value] for key, value in cluster_dict.items()], key=lambda x:x[0], reverse=True)
        cluster_fingerprints = [] # [(face, cloth), ...]
        cluster_cnt = 0

				# 각 cluster의 face feature, cloth feature 값 계산
        for cluster_with_num in cluster_list:
            num, clusters = cluster_with_num
            for idx, cluster in enumerate(clusters):
                cluster_face_fingerprint = np.zeros((128,))
                cluster_cloth_fingerprint = np.zeros((128,))
                i = 0
                for person in cluster:
                    encoding = fingerprints[person]
                    face, cloth = encoding[:128], encoding[128:]
                    cluster_face_fingerprint += face
                    cluster_cloth_fingerprint += cloth
                    i += 1
                assert i > 0, 'cluster is empty!'
                cluster_face_fingerprint /= i
                cluster_cloth_fingerprint /= i

                cluster_fingerprints.append([(num, idx), (cluster_face_fingerprint, cluster_cloth_fingerprint)])
                cluster_cnt += 1

        merged = [] # 병합될 cluster
        merged_clusters = dict() # 병합이 완료된 새로운 cluster 딕셔너리

				# cluster 병합
        for i in range(cluster_cnt):
            if cluster_fingerprints[i][0] in merged:
                continue
            big_num, big_idx = cluster_fingerprints[i][0]
            person_list = cluster_dict[big_num][big_idx]
            merged_num = big_num
            for j in range(i+1, cluster_cnt):
                cluster_face_norm = round(np.linalg.norm(cluster_fingerprints[i][1][0] - cluster_fingerprints[j][1][0]),3)
                cluster_cloth_norm = round(np.linalg.norm(cluster_fingerprints[i][1][1] - cluster_fingerprints[j][1][1]),3)
                if cluster_face_norm < FACE_THRESHOLD_HARD or cluster_cloth_norm < CLOTH_THRESHOLD_HARD or \\
                    (cluster_face_norm < FACE_THRESHOLD_SOFT and cluster_cloth_norm < CLOTH_THRESHOLD_SOFT):
                    small_num, small_idx = cluster_fingerprints[j][0]
                    merged_num += small_num
                    person_list += cluster_dict[small_num][small_idx]
                    merged.append(cluster_fingerprints[j][0])
                    
            merged_clusters[merged_num] = merged_clusters.get(merged_num, [])
            merged_clusters[merged_num].append(person_list)

        cluster_dict = merged_clusters

    return merged_clusters


결과

[영상 1]

merge 전

image-20220603113804180

merge 후

image-20220603113815613

[영상 2]

merge 전

image-20220603113831773

merge 후

image-20220603113843739

[영상 3]

merge 전

image-20220603113854720

merge 후

image-20220603113905247

[영상 4]

merge 전

image-20220603113918899

merge 후

image-20220603113933127

[영상 5]

merge 전

image-20220603113948755

merge 후

image-20220603114003398

[영상 6]

merge 전

image-20220603114015214

merge 후

image-20220603114031929

완벽하게 합쳐줄 수는 없지만, merge 전후로 1~2 명 가량의 cluster가 합쳐져 의미있는 역할을 수행할 수 있는 것으로 보인다.



현재 진행 상황은 Clustering 부는 모듈화 만을 남겨두었으며, ‘쇼츠 생성 부’, ‘프론트-백엔드 부’도 윤곽이 잡혀가고 있다. (곧 있으면 ver1.0이 나올 듯?)


결론

  • 모델링 코드 모듈화
  • 흥미도 계산 연구



참고 자료

Categories: ,

Updated:

Leave a comment