TOP
class="layout-aside-left paging-number">
본문 바로가기
[파이썬 Projects]/<파이썬 딥러닝, 신경망>

[딥러닝] 비지도 학습: 확산 모델

by 기록자_Recordian 2024. 12. 4.
728x90
반응형
오토인코더, GAN, 확산 모델
 

[딥러닝] 비지도 학습: 오토인코더, GAN, 확산 모델

이전 내용 오토인코더 오토인코더는 지도 방식을 사용하지 않고 잠재 표현 또는 입력 데이터의 밀집 표현(코딩)을 학습할 수 있는 인공 신경망이다. 코딩은 일반적으로 입력보다 훨씬 낮은 차

puppy-foot-it.tistory.com


확산 모델(Diffusion Model)

 

확산 모델은 데이터의 점진적 변환 과정을 통해 고품질의 샘플을 생성하는 확률적 모델이다. 특히, 잡음 제거 확산 모델(Denoising Diffusion Probabilistic Models, DDPM)은 딥러닝 기반의 이미지 생성 기술 중 눈에 띄는 접근법이다. 

 

1. 확산 모델 (Diffusion Models)

확산 모델은 데이터의 분포를 모델링하고 그 데이터를 생성하는 데 사용되는 모델이다. 특히, 모델이 데이터를 점진적으로 잡음(noise) 과정으로 변환하고, 반대로 잡음을 점진적으로 제거하여 원래 데이터로 복원한다. 이러한 모델은 정방향 과정과 역방향 과정이라는 두 가지 주요 과정으로 구성된다.

 

2. 잡음 제거 확산 모델 (DDPM)

DDPM은 확산 모델의 일종으로, 정방향 과정에서 데이터를 점진적으로 잡음으로 덮고 역방향 과정에서 점진적으로 잡음을 제거하여 데이터를 생성한다.

- 주요 용어 및 개념

  • 등방성 노이즈 (Isotropic Noise): 모든 방향으로 동일한 성질을 갖는 노이즈로, 이 개념은 노이즈를 추가할 때 측면적으로 중요하다.
  • 정방향 과정 (Forward Process): 데이터에 점진적으로 노이즈를 추가하는 과정이다. 이 과정은 데이터를 점진적으로 가우시안 분포로 변환한다.
  • 역방향 과정 (Reverse Process): 노이즈가 추가된 데이터를 점진적으로 원래 데이터로 복원하는 과정이다. 이 과정에서는 추가된 노이즈를 제거한다.

3. 잠재 확산 모델 (Latent Diffusion Models)

잠재 확산 모델은 확산 과정이 잠재 공간(latent space)에서 수행되는 방식이다. 이를 통해 고해상도 이미지를 보다 효율적으로 생성할 수 있다.

- 주요 특징

  • 잠재 공간 변환: 원래 이미지 공간 대신 더 작은 차원의 잠재 공간에서 데이터를 변환한다.
  • 효율성 향상: 잠재 공간에서의 조작은 계산 비용을 줄이고 학습 속도를 높일 수 있다.

- 구현 개요

  1. 잠재 공간으로 인코딩: 원래 이미지를 잠재 공간 벡터로 변환한다.
  2. 잠재 공간 내 확산 과정: 잠재 공간에서 노이즈를 추가하고 제거하는 확산 과정을 수행한다.
  3. 데코딩: 최종적으로 잠재 공간 벡터를 원래 이미지 공간으로 복원한다.

4. 요약 및 주요 이점

  • 잡음 제거 확산 모델 (DDPM): 데이터를 점진적으로 노이즈화하고 이를 역방향 과정으로 복원하여 고품질의 이미지를 생성한다.
  • 정방향 및 역방향 과정: 데이터의 노이즈화 및 노이즈 제거 과정을 통해 이미지 생성에서의 안정성과 성능을 확보한다.
  • 잠재 확산 모델: 잠재 공간에서의 확산 과정을 통해 고해상도 이미지 생성의 효율성을 높인다.

확산 모델에서 알파, 베타, 알파의 누적곱

 

잡음 제거 확산 모델(Denoising Diffusion Probabilistic Models, DDPM)에서 사용되는 알파(α), 베타(β), 그리고 알파의 누적곱(α_cumprod, 또는 (\bar{\alpha}_t))는 모델의 정방향 및 역방향 과정에서 중요한 역할을 한다.

  • 알파(α): 베타를 통해 정의된 스케일링 파라미터로, 정방향 과정에서 데이터에 점진적으로 노이즈를 덧쓴다.
  • 베타(β): 각 시간 단계에서 데이터에 추가되는 노이즈의 정도를 나타내는 파라미터이다.
  • 알파 누적곱((\bar{\alpha}_t)): 첫 단계에서 현재 단계까지의 모든 알파 값의 누적 곱으로, 정방향 과정의 전체 노이즈 스케일을 반영한다.

1. 알파(α)

알파(α)는 확산 과정에서 각 단계의 스케일링 파라미터로 사용된다. 이를 통해 데이터에 점진적으로 노이즈를 추가하게 된다. 알파는 통상적으로 시간 단계 (t)에 따라 정의되며, 알파는 주로 베타(β)를 통해 정의된다

[ \alpha_t = 1 - \beta_t ]

여기서 (\beta)는 각 시간 단계 (t)에서 정의된 노이즈 파라미터이다.

 

2. 베타(β)

베타(β)는 각 시간 단계에서 데이터에 추가되는 노이즈의 정도를 나타내는 파라미터이다. 베타는 확산 과정의 시간 단계에 따라 증가하는 방식으로 설계되며, 이를 통해 초기 단계에서는 적은 노이즈를 추가하고 후반 단계로 갈수록 더 많은 노이즈를 추가하게 된다.

 

일반적으로 베타는 선형적으로 증가하는 값으로 설정되면 다음과 같은 형태로 표현된다.

βt=βmin+tT(βmax−βmin)βt​=βmin​+Tt​(βmax​−βmin​)

여기서 βmin과 βmax는 베타의 최솟값과 최댓값이며, (T)는 총 시간 단계 수이다.

 

3. 알파 누적곱 ((\bar{\alpha}_t))

알파의 누적곱은 정방향 과정에서 각 시간 단계 (t)까지의 알파 값들의 누적 곱을 나타냅니다. 이는 정방향 과정에서 특정 시간 단계 (t)까지 적용된 전체 노이즈 스케일을 나타낸다.

αˉ<em>t=∏</em>s=1tαsαˉ<em>t=∏</em>s=1tαs

◆ 왜 이러한 파라미터들이 필요한가?

이 파라미터들은 DDPM의 정방향과 역방향 과정에서 중요한 역할을 한다:

 

- 정방향 과정 (Forward Process)

정방향 과정에서 원래 데이터 ( \mathbf{x}_0 )가 점진적으로 노이즈화되며, 이는 다음과 같은 수식을 따른다.

q(x<em>t∣x</em>t−1)=N(x<em>t;αtx</em>t−1,(1−αt)I)q(x<em>t∣x</em>t−1)=N(x<em>t;αt​​x</em>t−1,(1−αt​)I)

 

전체적으로 ( t ) 단계 이후의 데이터 ( \mathbf{x}_t )는 다음과 같이 잡음이 추가된 형태로 표현된다.

q(xt∣x0)=N(xt;αˉtx0,(1−αˉt)I)q(xt​∣x0​)=N(xt​;αˉt​​x0​,(1−αˉt​)I)

여기서 (\bar{\alpha}_t)는 모든 ( t ) 단계까지의 알파 값들의 누적 곱으로, 각 단계에서 추가된 총 노이즈를 반영한다.

 

- 역방향 과정 (Reverse Process)

역방향 과정에서 모델은 노이즈가 점진적으로 제거되면서 데이터를 복원한다. 이 과정은 다음과 같다.

[ p_\theta(\mathbf{x}{t-1} | \mathbf{x}t) = \mathcal{N}(\mathbf{x}{t-1}; \mu\theta(\mathbf{x}t, t), \Sigma\theta(t)) ]

여기서 ( \mu_\theta(\mathbf{x}t, t) )는 데이터 복원을 위한 평균값이고, (\Sigma\theta(t))는 필요한 공분산이다.

이 평균값 ( \mu_\theta )는 다음과 같이 알파와 베타를 통해 계산된다.

μθ(xt,t)∝1αt(xt−1−αt1−αˉ<em>tϵ</em>θ(xt,t))μθ​(xt​,t)∝αt​​1​(xt​−1−αˉ<em>t​1−αt​​ϵ</em>θ(xt​,t))

여기서 ( \mathbf{\epsilon}_\theta )는 모델이 예측한 노이즈이다.

 

알파, 베타, 알파의 누적곱을 계산하는 함수를 만들고 T=4000으로 호출해본다.

def variance_schedule(T, s=0.008, max_beta=0.999):
    t = np.arange(T + 1)
    f = np.cos((t / T + s) / (1 + s) * np.pi / 2) ** 2
    alpha = np.clip(f[1:] / f[:-1], 1 - max_beta, 1)
    alpha = np.append(1, alpha).astype(np.float32)  # add α₀ = 1
    beta = 1 - alpha
    alpha_cumprod = np.cumprod(alpha)
    return alpha, alpha_cumprod, beta  # αₜ , α̅ₜ , βₜ for t = 0 to T

np.random.seed(42)
T = 4000
alpha, alpha_cumprod, beta = variance_schedule(T)

패션 MNIST 데이터셋을 활용한 확산 모델

 

확산 과정을 거꾸로 수행하도록 모델을 훈련하려면 정방향 과정의 여러 타임 스텝에서 추출한 잡음 섞인 이미지가 필요하다. 데이터셋에서 깨끗한 이미지의 배치를 가져와 이런 이미지를 만드는 prepare_batch() 함수를 만든다.

def prepare_batch(X):
    X = tf.cast(X[..., tf.newaxis], tf.float32) * 2 - 1
    X_shape = tf.shape(X)
    t = tf.random.uniform([X_shape[0]], minval=1, maxval=T + 1, dtype=tf.int32)
    alpha_cm = tf.gather(alpha_cumprod, t)
    alpha_cm = tf.reshape(alpha_cm, [X_shape[0]] + [1] * (len(X_shape) - 1))
    noise = tf.random.normal(X_shape)
    return {
        'X_noisy': alpha_cm * 0.5 * X + (1 - alpha_cm) ** 0.5 * noise,
        'time': t,
    }, noise

[코드 설명]

  • 간단하게 하기 위해 패션 MNIST를 사용하므로 먼저 채널 축을 추가해야 한다. 또한 픽셀값을 -1에서 1로 스케일 조정하여 평균이 0이고 분산이 1인 최종 가우스 분포에 가깝게 만든다.
  • 배치의 각 이미지에 대해 1에서 T 사이의 임의의 타임 스텝을 포함하는 벡터 t를 생성한다.
  • tf.gather() 를 사용하여 벡터 t의 각 타임 스텝에 대한 alpha_cumprod 값을 추출한다. 이렇게 하면 각 이미지에 대해 알파 누적곱 값이 하나씩 포함된 벡터 alpha_cm이 만들어진다.
  • alpha_cm을 [배치 크기]에서 [배치 크기, 1, 1, 1] 로 크기를 바꾼다. 이는 배치 X에 alpha_cm을 브로드캐스팅 하기 위해 필요하다.
  • 평균이 0이고 분산이 1인 가우스 잡음을 생성한다
  • return ~ noise: 이미지에 확산 과정을 적용한다. 이 함수는 입력과 타깃을 포함하는 튜플을 반환한다. 입력은 잡음 이미지와 이미지 생성에 사용된 타임 스텝이 포함된 파이썬 딕셔너리로 포함된다. 타깃은 각 이미지를 생성하는데 사용된 가우스 잡음이다.

이제 훈련 데이터셋과 검증 데이터셋을 만들고 모든 배치에 prepare_batch() 함수를 적용한다. X_train과 X_valid에는 픽셀값이 0에서 1 사이인 패션 MNIST 이미지가 담겨있다.

def prepare_dataset(X, batch_size=32, shuffle=False):
    ds = tf.data.Dataset.from_tensor_slices(X)
    if shuffle:
        ds = ds.shuffle(buffer_size=10_000)
    return ds.batch(batch_size).map(prepare_batch).prefetch(1)

tf.random.set_seed(43)
train_set = prepare_dataset(X_train, batch_size=32, shuffle=True)
valid_set = prepare_dataset(X_valid, batch_size=32)

 

 

이제 확산 모델 자체를 구축할 준비가 되었다. 이미지와 시간을 모두 처리해야 한다. DDPM 논문에서 제안한 대로 트랜스포머 논문에서처럼 사인파 인코딩을 사용하여 시간을 인코딩한다. 시간 인덱스(정수)를 나타내는 m 정수의 벡터가 주어지면 이 층은 m × d 행렬을 반환하며, 여기서 _d_는 선택한 임베딩 크기이다.

# 사용자 정의 시간 인코딩 층 구현
embed_size = 64

class TimeEncoding(tf.keras.layers.Layer):
    def __init__(self, T, embed_size, dtype=tf.float32, **kwargs):
        super().__init__(dtype=dtype, **kwargs)
        assert embed_size % 2 == 0, "embed_size must be even"
        p, i = np.meshgrid(np.arange(T + 1), 2 * np.arange(embed_size // 2))
        t_emb = np.empty((T + 1, embed_size))
        t_emb[:, ::2] = np.sin(p / 10_000 ** (i / embed_size)).T
        t_emb[:, 1::2] = np.cos(p / 10_000 ** (i / embed_size)).T
        self.time_encodings = tf.constant(t_emb.astype(self.dtype))

    def call(self, inputs):
        return tf.gather(self.time_encodings, inputs)

 

이제 모델을 빌드해본다. 개선된 DDPM 문서에서는 UNet 모델을 사용한다. Conv2D + BatchNormalization 층을 통해 이미지를 처리하고 스킵 연결을 가진 UNet과 유사한 모델을 만든다. 점차적으로 이미지 다운샘플링(strides=2인 MaxPooling 층을 사용)한 다음 다시 업샘플링한다(Upsampling2D 층 사용). 다운샘플링 부분과 업샘플링 부분에 스킵 연결이 추가된다. 또한 Dense 레이어를 통과하여 올바른 크기로 크기를 조정한 후 각 블록의 출력에 시간 인코딩을 추가한다.

※ 참고: 이미지의 시간 인코딩은 마지막 축(채널)을 따라 이미지의 모든 픽셀에 추가된다. 따라서 Conv2D 층의 유닛 수는 임베딩 크기와 일치해야 하며, time_enc 텐서를 재구성하여 너비 및 높이 차원을 추가해야 한다.

def build_diffusion_model():
    X_noisy = tf.keras.layers.Input(shape=[28, 28, 1], name="X_noisy")
    time_input = tf.keras.layers.Input(shape=[], dtype=tf.int32, name="time")
    time_enc = TimeEncoding(T, embed_size)(time_input)

    dim = 16
    Z = tf.keras.layers.ZeroPadding2D((3, 3))(X_noisy)
    Z = tf.keras.layers.Conv2D(dim, 3)(Z)
    Z = tf.keras.layers.BatchNormalization()(Z)
    Z = tf.keras.layers.Activation("relu")(Z)

    time = tf.keras.layers.Dense(dim)(time_enc)  # 시간 인코딩 적용
    Z = time[:, tf.newaxis, tf.newaxis, :] + Z  # 모든 픽셀에 시간 데이터 추가

    skip = Z
    cross_skips = []  # UNet의 다운샘플링 & 업샘플링을 가로지르는 스킵 연결

    for dim in (32, 64, 128):
        Z = tf.keras.layers.Activation("relu")(Z)
        Z = tf.keras.layers.SeparableConv2D(dim, 3, padding="same")(Z)
        Z = tf.keras.layers.BatchNormalization()(Z)

        Z = tf.keras.layers.Activation("relu")(Z)
        Z = tf.keras.layers.SeparableConv2D(dim, 3, padding="same")(Z)
        Z = tf.keras.layers.BatchNormalization()(Z)

        cross_skips.append(Z)
        Z = tf.keras.layers.MaxPooling2D(3, strides=2, padding="same")(Z)
        skip_link = tf.keras.layers.Conv2D(dim, 1, strides=2,
                                           padding="same")(skip)
        Z = tf.keras.layers.add([Z, skip_link])

        time = tf.keras.layers.Dense(dim)(time_enc)
        Z = time[:, tf.newaxis, tf.newaxis, :] + Z
        skip = Z

    for dim in (64, 32, 16):
        Z = tf.keras.layers.Activation("relu")(Z)
        Z = tf.keras.layers.Conv2DTranspose(dim, 3, padding="same")(Z)
        Z = tf.keras.layers.BatchNormalization()(Z)

        Z = tf.keras.layers.Activation("relu")(Z)
        Z = tf.keras.layers.Conv2DTranspose(dim, 3, padding="same")(Z)
        Z = tf.keras.layers.BatchNormalization()(Z)

        Z = tf.keras.layers.UpSampling2D(2)(Z)

        skip_link = tf.keras.layers.UpSampling2D(2)(skip)
        skip_link = tf.keras.layers.Conv2D(dim, 1, padding="same")(skip_link)
        Z = tf.keras.layers.add([Z, skip_link])

        time = tf.keras.layers.Dense(dim)(time_enc)
        Z = time[:, tf.newaxis, tf.newaxis, :] + Z
        Z = tf.keras.layers.concatenate([Z, cross_skips.pop()], axis=-1)
        skip = Z

    outputs = tf.keras.layers.Conv2D(1, 3, padding="same")(Z)[:, 2:-2, 2:-2]
    return tf.keras.Model(inputs=[X_noisy, time_input], outputs=[outputs])

 

모델을 훈련하는 데, MAE 손실 또는 후버 손실을 사용하는 게 효과적이다.

tf.random.set_seed(42) 
model = build_diffusion_model()
model.compile(loss=tf.keras.losses.Huber(), optimizer="nadam")

# 모델 체크포인트 콜백 추가
checkpoint_cb = tf.keras.callbacks.ModelCheckpoint("my_diffusion_model.keras",
                                                   save_best_only=True)

history = model.fit(train_set, validation_data=valid_set, epochs=100,
                    callbacks=[checkpoint_cb])

 

모델을 훈련하고 나면 이를 사용하여 새로운 이미지를 생성할 수 있다.

 

모델이 훈련되면 이를 사용하여 새 이미지를 생성할 수 있습니다. 이를 위해 가우스 잡음을 생성하고 이것이 시간 T동안의 확산 과정의 결과라고 가정한다. 그런 다음 모델을 사용하여 T-1시점의 이미지를 예측한 다음 다시 호출하여 T-2를 구하는 식으로 각 단계에서 약간의 노이즈를 제거한다. 마지막으로 패션 MNIST 데이터셋에서 가져온 것처럼 보이는 이미지를 얻는다.

이 역방향 과정을 구현하는 함수를 작성하고 이를 호출하여 몇 개의 이미지를 생성해본다.

def generate(model, batch_size=32):
    X = tf.random.normal([batch_size, 28, 28, 1])
    for t in range(T, 0, -1):
        noise = (tf.random.normal if t > 1 else tf.zeros)(tf.shape(X))
        X_noise = model({'X_noisy': X, 'time': tf.constant([t] * batch_size)})
        X = (
            1 / alpha[t] ** 0.5
            * (X - beta[t] / (1 - alpha_cumprod[t]) ** 0.5 * X_noise)
            + (1 - alpha[t]) ** 0.5 * noise
        )
    return X

X_gen = generate(model) # 생성된 이미지

단, 이미지 생성 시 모델을 여러 번 호출해야 하므로 이미지 생성이 느리다. 더 작은 T값을 사용하거나 한 번에 여러 단계에 걸쳐 동일한 모델 예측을 사용하면 속도를 높일 수 있지만 결과 이미지가 좋지 않을 수 있다.


Stable Diffusion (스테이블 디퓨전)

 

스테이블 디퓨전(Stable Diffusion)은 2022년에 출시된 딥 러닝, 텍스트-이미지 모델이다. 텍스트 설명에 따라 상세한 이미지를 생성하는 데 주로 사용되지만 인페인팅, 아웃페인팅, 이미지 생성과 같은 다른 작업에도 적용할 수 있다. 스타트업 스태빌리티 AI(Stability AI)가 여러 학술 연구원 및 비영리 단체와 공동으로 개발했다.

스테이블 디퓨전은 심층 생성 신경망의 일종인 잠재 확산 모델이다. 코드 및 모델 가중치가 공개되었으며 최소 8GB VRAM이 있는 일반 GPU가 장착된 대부분의 소비자 하드웨어에서 실행할 수 있다. 이는 클라우드 서비스를 통해서만 액세스할 수 있었던 DALL-E 및 Midjourney와 같은 이전의 독점 텍스트-이미지 모델에서 출발했다.

https://stablediffusionweb.com/ko

 

Stable Diffusion Online

Stable Diffusion 온라인 Stable Diffusion은 임의의 텍스트 입력에서 사진과 같은 실감나는 이미지를 생성할 수 있는 잠재적인 텍스트에서 이미지 흩산 모델로, 놀라운 이미지를 생성하기 위한 자율적인

stablediffusionweb.com

 

Stable Diffusion 사이트에서 'singing cat (노래하는 고양이)'를 입력하면, *현재 영어, 숫자 등만 가능하다.

 

이미지가 잘 생성된다.


다음 내용

 

[딥러닝] 강화 학습(Reinforcement Learning)

이전 내용 [딥러닝] 비지도 학습: 오토인코더, GAN, 확산 모델이전 내용 오토인코더 오토인코더는 지도 방식을 사용하지 않고 잠재 표현 또는 입력 데이터의 밀집 표현(코딩)을 학습할 수 있는

puppy-foot-it.tistory.com


[출처]

핸즈 온 머신러닝

위키백과

스테이블 디퓨전 온라인(stable diffusion online)

728x90
반응형