📌 목차

1. DDPM (Denoising Diffusion Probabilistic Models)
2. DDPM with U-Net
3. DDPM Sampling과  DDIM
4. 요약

 

🧐  preview: 

Diffusion은 GAN과 함께 10년 넘게 Image Generation방법 중 영향력이 큰 생성모델링 기법이다. (GAN의 최대 유행: 2017~2020)
Diffusion모델은 여러 Benchmark Dataset에서 기존 최신 GAN보다 성능이 뛰어난데,
특히 txt2img의 OpenAI DALL∙E, Google Imagen등에서 많이 사용된다.

Diffusion모델의 핵심기반 아이디어는 VAE, EBM과 비슷한 점이 존재한다.
Contrastive Diffusion(= Score 함수) 사용 대신, 
Log분포의 Gradient를 직접 추정해 모델을 학습하는 EBM분야인 "Score-based Generative Model"에서 매우 중요하다.

NCSN(Noise Conditional Score Network)라는 모델이 원시 data에 여러 scale의 noise를 적용,
Data밀도가 낮은 영역에서도 잘 작동하였다.


2020년, Denoising Diffusion Probabilistic Models라는 논문이 발표되면서
앞선 Diffusion모델과 Score-based모델사이 깊은 연관성을 밝혀냈고
GAN에 필적할만한 Diffusion모델인 DDPM(Denoising Diffusion Probabilistic Model)을 train시켰다.

 

 

 

 

 

 


1.  DDPM (Denoising Diffusion Probabilistic Models)

Forward Diffusion

img x0를 많은 step(ex. T=1000)동안 점진적으로 손상시켜
최종적으로 "Standard Gaussian Noise"와 구별불가능하게 만든다 가정하자.

img xt-1에 분산 βt를 갖는 소량의 Gaussian Noise를 추가.
새로운 img xt를 생성하는 함수 q를 정의하자.
이 q를 적용하면, 아래처럼 점진적으로 잡음이 커지는 img sequence (x0, ... , xT)를 생성할 수 있다.

이때, update과정은 아래와 같이 수학적으로 표기가능하다.




Reparameterization Trick

🤔 q를 t번 적용하지 않고 이미지 x0+Noise인 xt바로 Skip가능하다면 유용하지 않을까?
위 식의 2번째 줄은 2개의 Gaussian분포를 더해 새로운 Gaussian분포를 하나 얻을 수 있다:
따라서 원본 img x0에서 정방향 diffusion과정의 어느단계로든 건너뛸 수 있게 되었다.
또한, 기존의 βt 대신, āt값을 사용해 diffusion schedule을 정의할 수 있다.
āt: signal(= 원본 x0)로 인한 분산
1-āt: noise(ε)로 인한 분산

∴ Foward Diffusion과정 q는 아래와 같다:




Diffusion Schedule

추가적으로 각 time step마다 다른 β를 자유로이 선택가능하다.
즉, β 나 ā 값이 t에 따라 변하는 방식을 "Diffusion Schedule"이라 한다.

[Ho et al;2020]에서는 Linear Diffusion Schedule을 선택했다.
위 논문에서는 β1=0.0001부터 , βT=0.02까지 선형적으로 증가한다.

이후 논문에서는 Cosine Diffusion Schedule이 도입되었다.
이때, 코사인 스케줄은 ā를 아래와 같이 정의한다:
def linear_beta_schedule(timesteps):
    scale = 1000 / timesteps
    beta_start = scale * 0.0001
    beta_end = scale * 0.02
    return torch.linspace(beta_start, beta_end, timesteps, dtype = torch.float64)

def cosine_beta_schedule(timesteps, s = 0.008):
    """
    https://openreview.net/forum?id=-NEXDKk8gZ
    """
    steps = timesteps + 1
    t = torch.linspace(0, timesteps, steps, dtype = torch.float64) / timesteps
    alphas_cumprod = torch.cos((t + s) / (1 + s) * math.pi * 0.5) ** 2
    alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
    betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
    return torch.clip(betas, 0, 0.999)

위 그림을 보면 Cosine Diffusion Schedule이 더 느리게 상승했음을 확인가능하다.
즉, img에 noise를 점진적으로 추가해 train효율성 및 생성품질을 향상한다.


Reverse Diffusion

noise추가과정을 되돌리는 신경망 pθ(xt-1|xt)를 구축하자.
이때, 신경망 pθ(xt-1|xt)는 q(xt-1|xt)의 역방향 분포를 근사화하는 신경망이다.

이를 통해 N(0, I)에서 random noise sampling 후,
reverse diffusion과정을 여러번 적용해 새로운 img를생성할 수 있다:



Train Algorithm

[Reverse Diffusion과 VAE간 비교]
∙ 목표: 신경망으로 random noise를 의미있는 출력으로 변환하는 것.
∙ 차이점: 
  - VAE는 정방향(= img2noise)과정이 모델의 일부 (= 학습됨)
  - Diffusion: 이에 대한 parameter가 없이 진행.

그렇기에, VAE와 동일한 Loss function을 적용한다.

⭐️ 주의점:
Diffusion모델이 실제로 2개의 신경망복사본을 유지한다는 점.
경사하강법으로 train된 신경망과 이전 train step의 신경망 가중치의 EMA(지수이동평균)을 사용하는 또다른 EMA신경망이다.

 

 

 

 

 

 

 

 

 

 

 

 

 


2.  DDPM with U-Net

DDPM with UNet

앞서 신경망의 종류를 확인했다: img에 추가된 noise를 예측하는 신경망
이제, 이에 사용할 신경망 구조를 살펴보자.

Skip Connection을 통해 정보가 신경망 일부를 건너뛰고 후속층으로 signal을 흘러보낼 수 있다.
특히나 "출력이 입력크기와 같아야할 때, U-Net이 유용하다".

 

Sinusodial Embedding

[Transformer논문]에서 처음 소개된 사인파 임베딩을
[NeRF논문]처럼 기존 아이디어를 변형해 사용한다.

❗️핵심 아이디어:

신경망 후속층에서 사용가능하도록 noise분산값(= 스칼라값)을 더 복잡한 표현이 가능한 고차원 벡터로 변환한다.
NeRF논문에서는 문장에서 단어의 이산적인 위치를 벡터로 encoding하는 것이 아닌, 연속적인 값으로 확장했다:
보통, noise embedding길이의 절반이 되게 L=16으로 선택하고
주파수 f의 최대 scaling계수로 ln(1000) / (L-1) 을 택한다.
def sinusoidal_embedding(x):
    f = math.exp(
        torch.linspace(
            math.log(1.0),
            math.log(1000.0),
            16,
        )
    )
    angular_speeds = 2.0 * math.pi * f
    embeddings = torch.cat(
        [torch.sin(angular_speeds*x), torch.cos(angular_speeds*x)], dim=3
    )
    return embeddings​

Residual Block

Residual Block구조는 아래와 같다.
일부 Residual Block에서 Block의 출력과 channel수를 일치시켜야한다.
그렇기에 Skip Connection에 kernel_size=1인 Conv2D층을 추가한다.

Down Block  &  UpBlock


∙ Down Block  

Residual Block으로 channel수를 늘린다.
또한, img_size를 줄이려고 마지막에 AvgPooling층을 적용한다.
(UpBlock과의 Skip Connection을 위해 각 Residual Block에 list를 추가해야함.)


∙ UpBlock  

UpSampling2D를 진행한다. (보통 ConvTranspose나 Interpolation을 적용.)
연속된 UpBlock은 channel수를 줄이면서 DownBlock의 출력과 연결한다.
import torch
import torch.nn as nn
import torch.nn.functional as F

class SwishActivation(nn.Module):
    def forward(self, x):
        return x * torch.sigmoid(x)

class ResidualBlock(nn.Module):
    def __init__(self, width):
        super(ResidualBlock, self).__init__()
        self.width = width
        self.conv1 = nn.Conv2d(in_channels=width, out_channels=width, kernel_size=3, padding=1)
        self.bn1 = nn.BatchNorm2d(width)
        self.activation = SwishActivation()
        self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, kernel_size=3, padding=1)
        self.bn2 = nn.BatchNorm2d(width)

    def forward(self, x):
        input_width = x.size(1)
        if input_width == self.width:
            residual = x
        else:
            residual = nn.Conv2d(in_channels=input_width, out_channels=self.width, kernel_size=1)(x)
        x = self.bn1(x)
        x = self.activation(x)
        x = self.conv1(x)
        x = self.bn2(x)
        x = self.activation(x)
        x = self.conv2(x)
        x = x + residual
        return x

class DownBlock(nn.Module):
    def __init__(self, width, block_depth):
        super(DownBlock, self).__init__()
        self.width = width
        self.block_depth = block_depth
        self.res_blocks = nn.ModuleList([ResidualBlock(width) for _ in range(block_depth)])
        self.avg_pool = nn.AvgPool2d(kernel_size=2)

    def forward(self, x):
        x, skips = x
        for res_block in self.res_blocks:
            x = res_block(x)
            skips.append(x)
        x = self.avg_pool(x)
        return x

class UpBlock(nn.Module):
    def __init__(self, width, block_depth):
        super(UpBlock, self).__init__()
        self.width = width
        self.block_depth = block_depth
        self.up_sampling = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False)
        self.res_blocks = nn.ModuleList([ResidualBlock(width) for _ in range(block_depth)])

    def forward(self, x):
        x, skips = x
        x = self.up_sampling(x)
        for res_block in self.res_blocks:
            x = torch.cat([x, skips.pop()], dim=1)
            x = res_block(x)
        return x​

 

 

 

 

 

 

 

 

 

 

 


3. DDPM Sampling과  DDIM

DDPM을 이용한 Sampling

훈련된 모델에서 img를 sampling해야한다.
이를 위해 reverse diffusion과정이 필요하다.
즉, random noise에서 시작해 원본이 남을 때 까지 모델로 noise를 점진적으로 제거해야한다.

모델은 random noise추가과정의 마지막 time_step에서 추가된 noise뿐만아니라
trainset의 img에 추가된 noise총량을 예측하게 훈련된다.
(다만, 완전한 random noise에서 한번에 img예측은 불가능하다.)

그렇기에 두 단계를 거쳐 xt에서 xt-1로 이동한다.
Step 1. 모델의 noise예측을 사용, x0의 추정치 계산
Step 2. 예측 noise를 t-1 step까지만 다시 적용해 xt-1을 생성.
이 과정을 여러단계에 걸쳐 반복하면 조금씩 점진적으로 x0에 대한 추정치로 결국 돌아갈 수 있다..

아래 식은 이 과정을 수학적으로 보여준다:
위 식을 해석하면 다음과 같다:
첫번째 괄호: 신경망 ε(t)θ에 의해 예측된 noise로 계산된 추정이미지 x0
이후  t-1 signal비율인 √āt-1로 scale을 조정, 예측된 noise를 재적용.

두번째 괄호: t-1 noise 비율인 √1 - āt-1 - σ2t로 scale 조정.

세번째 괄호: 추가적인 Gaussian Noise를 더한다.



DDIM (Denoising Diffusion Implicit Model)

DDPM에서 모든 t에 대해 σt=0인 특수한 경우.
즉, DDIM을 사용하면 생성과정이 완전히 "결정론적(deterministic)"하다.
그렇기에 Random Noise가 같다면 항상 동일한 출력을 만든다.

이는 latent space의 sample과 pixel space에서
생성된 출력사이에 잘 정의된 mapping이 있음을 나타낸다.

 

 

 

 

 

 

 

 

4. 요약

DDPM이후 DDIM논문의 아이디어는 생성과정을 완전히 결정론적으로 만들었다.

DDPM은 2가지 과정으로 나뉜다.

① 정방향 Diffusion
 -
일련의 작은 단계로 train data에 noise를 추가.
이때, reparameterization trick으로 어느단계에 해당하는 noise_img라도 계산가능하다.
parameter선택 schedule 선택 또한 중요하다.

② 역방향 Diffusion

 - 추가된 noise를 예측하는 모델로 구성.
noise_img와 해당단계 noise비율이 주어지면 각 time step에서 noise를 예측하는 U-Net으로 구현된다.
UpBlock: img 크기를 늘리고 channel수를 줄임.
DownBlock: img 크기를 줄이고 channel수를 늘림.
noise비율은 사인파임베딩(sinusoidal embedding)으로 encoding

역방향 Diffusion 단계를 늘리면 속도는 느려지나 img생성품질은 향상된다.
또한, 두 img 사이를 보간하기 위해 latent space연산을 수행한다.

'Gain Study > Generation' 카테고리의 다른 글

[G]Part 3-2. Multi-Modal  (1) 2024.03.06
[G]Part 3-1. Advanced GAN  (0) 2024.03.04
[G]Part 2-5. Energy-based Model  (0) 2024.01.29
[G]Part 2-4. Normalizing Flows  (2) 2024.01.29
[G]Part 2-3. Auto Regressive Models  (0) 2024.01.26

+ Recent posts