[Generative] Python으로 FID 구하기

2024. 10. 15. 22:50Developers 공간 [Shorts]/Vision & Audio

728x90
반응형
<분류>
A. 수단
- OS/Platform/Tool : Linux, Kubernetes(k8s), Docker, AWS
- Package Manager : node.js, yarn, brew, 
- Compiler/Transpillar : React, Nvcc, gcc/g++, Babel, Flutter

- Module Bundler  : React, Webpack, Parcel

B. 언어
- C/C++, python, Javacsript, Typescript, Go-Lang, CUDA, Dart, HTML/CSS

C. 라이브러리 및 프레임워크 및 SDK
- OpenCV, OpenCL, FastAPI, PyTorch, Tensorflow, Nsight

 


1. What? (현상)

 

FID(Frechet Inception Distance)생성모델이 생성한 이미지들의 feature다른 Test Set의 feature를 비교하는 방법으로, 생성된 이미지의 QualityDiversity를 측정하는 방법입니다.

 

아래 식과 같이 구하며 Test Set 이미지 집합 $T$와 G로 생성한 이미지 집합 $I_G$일 때, $\mu$는 mean, $\Sigma$는 covariance를 의미합니다.

$$FID(T,I_G)=\underbrace{\|\mu_T-\mu_{I_G}\|^2_2}_{\text{Quality}}+\underbrace{Tr(\Sigma_T+\Sigma_{I_G}-2(\Sigma_T\Sigma_{I_G})^{\frac{1}{2}})}_{\text{Diversity}}$$

 

이번 글에서는 이를 구하는 코드에 대해 살펴보겠습니다.


2. Why? (원인)

  • X

3. How? (해결책)

먼저 원하는 embedding의 meanvariance를 아래 코드로 구해냅니다.

  • embd_list : 원하는 embedding들을 쌓아서 아래와 같은 차원으로 만들어 넣어줍니다.
    • shape : (원하는 embedding의 전체 개수, embedding의 차원)
  • mu : 해당 embedding의 mean를 의미합니다. 
    • shape : (embedding의 차원)
  • sigma : 해당 embedding의 covariance를 의미합니다. 
    • shape : (embedding의 차원, embedding의 차원)
import numpy as np

def calculate_embd_statistics(embd_lst):
    if isinstance(embd_lst, list):
        embd_lst = np.array(embd_lst)
    mu = np.mean(embd_lst, axis=0)
    sigma = np.cov(embd_lst, rowvar=False)
    return mu, sigma

 

위와 같이 얻은 두개의 embedding의 각 meanvariance를 통해 아래와 같은 코드로 FID를 구해냅니다.

  • mu1, mu2 : 생성된 샘플들(mu1)과 Reference 샘플들(mu2)의 embedding의 평균 
    • np.atleast_1d : input이 최소 1차원이 되도록 만들어줍니다.
    • shape : (embedding의 차원)
  • sigma1, sigma2 : 생성된 샘플들(sigma1)과 Reference 샘플들(sigma2)의 embedding의 covariance
    • np.atleast_2d :input이 최소 2차원이 되도록 만들어줍니다.  
    • shape : (embedding의 차원, embedding의 차원)
  • diff : mu1mu2의 차이
    • shape :  (embedding의 차원)

$$FID(T,I_G)=\underbrace{\|{\color{red}\mu_T-\mu_{I_G}}\|^2_2}_{\text{Quality}}+\underbrace{Tr(\Sigma_T+\Sigma_{I_G}-2(\Sigma_T\Sigma_{I_G})^{\frac{1}{2}})}_{\text{Diversity}}$$

  • tr_covmean : sigma1sigma2의 곱의 square root를 구한 후 diagonal의 합을 얻어냅니다.
    • linalg.sqrtm : matrix의 square root를 구해냅니다. 각 element별 square root가 아니라 $A=B^2$일 때 A matrix를 받아 B matrix를 구해냅니다.
    • np.trace : 2D matrix인 경우, 0차원과 1차원에 대해 diagonal의 합을 return합니다. 
    • shape : (embedding의 차원, embedding의 차원)

$$FID(T,I_G)=\underbrace{\|\mu_T-\mu_{I_G}\|^2_2}_{\text{Quality}}+\underbrace{{\color{red}Tr(}\Sigma_T+\Sigma_{I_G}-2{\color{red}(\Sigma_T\Sigma_{I_G})^{\frac{1}{2}}}{\color{red})}}_{\text{Diversity}}$$

import numpy as np
from scipy import linalg

def calculate_frechet_distance(mu1, sigma1, mu2, sigma2, eps=1e-6):


    mu1 = np.atleast_1d(mu1)
    mu2 = np.atleast_1d(mu2)

    sigma1 = np.atleast_2d(sigma1)
    sigma2 = np.atleast_2d(sigma2)

    assert mu1.shape == mu2.shape, \
        'Training and test mean vectors have different lengths'
    assert sigma1.shape == sigma2.shape, \
        'Training and test covariances have different dimensions'

    diff = mu1 - mu2

    # product might be almost singular
    covmean, _ = linalg.sqrtm(sigma1.dot(sigma2), disp=False)
    if not np.isfinite(covmean).all():
        msg = ('fid calculation produces singular product; '
            'adding %s to diagonal of cov estimates') % eps
        print(msg)
        offset = np.eye(sigma1.shape[0]) * eps
        covmean = linalg.sqrtm((sigma1 + offset).dot(sigma2 + offset))

    # numerical error might give slight imaginary component
    if np.iscomplexobj(covmean):
        if not np.allclose(np.diagonal(covmean).imag, 0, atol=1e-3):
            m = np.max(np.abs(covmean.imag))
            raise ValueError('Imaginary component {}'.format(m))
        covmean = covmean.real

    tr_covmean = np.trace(covmean)

    return (diff.dot(diff)+np.trace(sigma1)+np.trace(sigma2)-2*tr_covmean)

 

위 함수를 보면, matrix의 square root를 얻어내는 과정에서 infinity가 발생하는 경우, 위에서 사용할 sigma에 작은 값을 더해 다시 구해내는 과정이 추가됩니다.

  • np.isfinite() : 각 element가 $\infty$인지 확인해 array를 얻습니다.
    • np.all() : 모든 element가 True인지 확인합니다.
    • np.eye() : 2D 단위행렬을 생성합니다.

또한 당연히 matrix의 square root를 얻어내는 과정에서 complex값이 생기는 경우, 결과에서 실수부만 활용합니다.

  • np.iscomplexobj() : element 중 complex type이 있는지 확인합니다.
    • np.allclose() : 두개의 array를 element마다 비교해 tolerance 한에서 같다고 볼 수 있는지 확인합니다.
    • np.diagonal() : 2D array인 경우, diagonal 값의 집합 array를 얻습니다.
    • np.imag() : complex 값의 imaginary(허수부)를 얻어냅니다.
    • np.abs() : 절대값을 얻습니다.
    • np.max() : 최대값을 얻습니다.
    • np.real() : complex 값의 real(실수부)를 얻어냅니다.

https://github.com/Stability-AI/stable-audio-metrics/blob/main/src/openl3_fd.py

Adapted from: https://github.com/mseitzer/pytorch-fid/blob/master/src/pytorch_fid/fid_score.py
Adapted from: https://github.com/gudgud96/frechet-audio-distance/blob/main/frechet_audio_distance/fad.py

 

 

728x90
반응형