오토인코더, GAN, 확산 모델
확산 모델(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)에서 수행되는 방식이다. 이를 통해 고해상도 이미지를 보다 효율적으로 생성할 수 있다.
- 주요 특징
- 잠재 공간 변환: 원래 이미지 공간 대신 더 작은 차원의 잠재 공간에서 데이터를 변환한다.
- 효율성 향상: 잠재 공간에서의 조작은 계산 비용을 줄이고 학습 속도를 높일 수 있다.
- 구현 개요
- 잠재 공간으로 인코딩: 원래 이미지를 잠재 공간 벡터로 변환한다.
- 잠재 공간 내 확산 과정: 잠재 공간에서 노이즈를 추가하고 제거하는 확산 과정을 수행한다.
- 데코딩: 최종적으로 잠재 공간 벡터를 원래 이미지 공간으로 복원한다.
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;αtx</em>t−1,(1−αt)I)
전체적으로 ( t ) 단계 이후의 데이터 ( \mathbf{x}_t )는 다음과 같이 잡음이 추가된 형태로 표현된다.
q(xt∣x0)=N(xt;αˉtx0,(1−αˉt)I)q(xt∣x0)=N(xt;αˉtx0,(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)∝αt1(xt−1−αˉ<em>t1−α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 사이트에서 'singing cat (노래하는 고양이)'를 입력하면, *현재 영어, 숫자 등만 가능하다.
이미지가 잘 생성된다.
다음 내용
[출처]
핸즈 온 머신러닝
위키백과
스테이블 디퓨전 온라인(stable diffusion online)
'[파이썬 Projects] > <파이썬 딥러닝, 신경망>' 카테고리의 다른 글
[문제 해결] 주피터노트북에 GPU 연결하기 (2) | 2024.12.04 |
---|---|
[딥러닝] 강화 학습(Reinforcement Learning) - 1 (3) | 2024.12.04 |
[딥러닝] 비지도 학습: 적대적 생성 신경망(GAN) (1) | 2024.12.04 |
[딥러닝] 비지도 학습: 오토인코더 (1) | 2024.12.03 |
[딥러닝] 비지도 학습: 오토인코더, GAN, 확산 모델 (0) | 2024.12.03 |