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

[딥러닝] 비지도 학습: 오토인코더

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

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

이전 내용 오토인코더, GAN, 확산 모델 ◆ 오토인코더오토인코더는 지도 방식을 사용하지 않고 잠재 표현 또는 입력 데이터의 밀집 표현(코딩)을 학습할 수 있는 인공 신경망이다. 코딩은 일반

puppy-foot-it.tistory.com


과소완전 선형 오토인코더로 PCA 수행하기

 

오토인코더가 선형 활성화 함수만 사용하고 비용 함수가 평균 제곱 오차(MSE)라면, 이는 주성분분석(PCA)을 수행하는 것으로 볼 수 있다.

 

아래는 3D 데이터셋에 PCA를 적용해 2D에 투영하는 간단한 선형 오토인코더를 만드는 예다.

tf.random.set_seed(42)

# 인코더 모델 정의
encoder = tf.keras.Sequential([
    tf.keras.layers.Dense(2, input_shape=(3,))  # 입력 형상 정의. 이 예에서는 입력 데이터가 (3,)형상이라고 가정.
])

# 디코더 모델 정의
decoder = tf.keras.Sequential([
    tf.keras.layers.Dense(3, input_shape=(2,))  # 디코더의 입력 형상도 정의.
])
autoencoder = tf.keras.Sequential([encoder, decoder])

optimizer = tf.keras.optimizers.SGD(learning_rate=0.5)
autoencoder.compile(loss="mse", optimizer=optimizer)

 

그리고 가상의 3D 데이터셋에 훈련하고, 이 모델을 사용해 동일한 데이터셋을 인코딩해본다. (2D로 투영)

# 3D 데이터셋 생성.

import numpy as np
from scipy.spatial.transform import Rotation

m = 60
X = np.zeros((m, 3))  # 3D 데이터 세트 초기화
np.random.seed(42)
angles = (np.random.rand(m) ** 3 + 0.5) * 2 * np.pi  # 고르지 않은 분포
X[:, 0], X[:, 1] = np.cos(angles), np.sin(angles) * 0.5  # 타원형
X += 0.28 * np.random.randn(m, 3)  # 노이즈 추가
X = Rotation.from_rotvec([np.pi / 29, -np.pi / 20, np.pi / 4]).apply(X)
X_train = X + [0.2, 0, 0.2]  # 약간 이동
history = autoencoder.fit(X_train, X_train, epochs=500, verbose=False)
codings = encoder.predict(X_train)
fig = plt.figure(figsize=(4,3))
plt.plot(codings[:,0], codings[:, 1], "b.")
plt.xlabel("$z_1$", fontsize=18)
plt.ylabel("$z_2$", fontsize=18, rotation=0)
plt.grid(True)
plt.show()

▶ 오토인코더는 PCA처럼 데이터에 있는 분산이 가능한 한 많이 보존되도록 데이터를 투영할 최상의 2D 평면을 찾는다.


적층 오토인코더

 

오토인코더 역시 다른 신경망들과 마찬가지로 은닉 층을 여러 개 가질 수 있으며, 이를 적층(또는 심층) 오토인코더라고 한다.

케라스로 일반적인 심층 MLP와 매우 비슷하게 적층 오토인코더를 구현할 수 있다.

패션 MNIST 를 통해 적층 오토인코더를 구현해본다.

# 패션 MNIST 데이터셋 로드, 스케일링 및 분할
fashion_mnist = tf.keras.datasets.fashion_mnist.load_data()
(X_train_full, y_train_full), (X_test, y_test) = fashion_mnist
X_train_full = X_train_full.astype(np.float32) / 255
X_test = X_test.astype(np.float32) / 255
X_train, X_valid = X_train_full[:-5000], X_train_full[-5000:]
y_train, y_valid = y_train_full[:-5000], y_train_full[-5000:]
tf.random.set_seed(42)  

stacked_encoder = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]),
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(30, activation="relu"),
])
stacked_decoder = tf.keras.Sequential([
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(28 * 28),
    tf.keras.layers.Reshape([28, 28])
])
stacked_ae = tf.keras.Sequential([stacked_encoder, stacked_decoder])

stacked_ae.compile(loss="mse", optimizer="nadam")
history = stacked_ae.fit(X_train, X_train, epochs=20,
                         validation_data=(X_valid, X_valid))

[단계별 설명]

- 인코더 정의:

  • stacked_encoder는 입력 이미지의 중요한 특징을 추출하는 역할.
  • Flatten 레이어는 28x28 크기의 2D 입력 이미지를 평탄화하여 784 요소를 가진 1D 벡터로 변환.
  • 첫 번째 Dense 레이어는 100개의 뉴런을 사용해 활성화 함수로 ReLU를 적용.
  • 두 번째 Dense 레이어는 30개의 뉴런을 사용해 활성화 함수로 ReLU를 적용하여 데이터의 잠재 표현(latent representation)을 얻음.

- 디코더 정의:

  • stacked_decoder는 압축된 표현을 원래의 이미지 형태로 복원.
  • 첫 번째 Dense 레이어는 100개의 뉴런을 사용하여 활성화 함수로 ReLU를 적용.
  • 두 번째 Dense 레이어는 28x28 크기의 이미지로 복원하기 위해 784개의 뉴런을 사용.
  • Reshape 레이어는 1D 벡터를 다시 28x28 크기의 2D 이미지로 변환.

- 자동 인코더 정의:

  • stacked_ae는 인코더와 디코더를 연결한 자동 인코더 모델.

- 모델 컴파일:

  • 자동 인코더를 훈련하기 위해 모델을 컴파일.
  • 손실 함수(Loss Function)로 평균 제곱 오차(Mean Squared Error, MSE)를 사용.
  • 옵티마이저(Optimizer)로 Nadam(Nesterov-accelerated Adaptive Moment Estimation)을 사용.

- 모델 훈련:

  • X_train을 입력으로 하여 X_train을 복원하도록 모델을 훈련. 이는 지도 학습을 사용하지 않는 비지도 학습.
  • epochs=20은 훈련을 20회 반복하는 것을 의미.
  • validation_data=(X_valid, X_valid)는 검증 데이터로 X_valid를 사용하여 훈련 중 모델의 성능을 평가.

- 입력 데이터:

  • 이 코드는 MNIST 데이터셋과 같은 28x28 크기의 그레이스케일 이미지를 사용한다고 가정.
  • 입력 이미지는 (28, 28) 형태를 가지며, 각 픽셀 값은 [0, 1] 범위에 있도록 정규화된 상태.

◆ 재구성 시각화

오토인코더가 적절히 훈련되었는지 확인하는 한 가지 방법은 입력과 출력을 비교하는 것으로, 입력과 출력의 차이가 너무 크지 않아야 한다. 검증 세트에서 원본 이미지를 재구성된 것과 함께 그려본다.

def plot_reconstructions(model, images=X_valid, n_images=5):
    reconstructions = np.clip(model.predict(images[:n_images]), 0, 1)
    fig = plt.figure(figsize=(n_images * 1.5, 3))
    for image_index in range(n_images):
        plt.subplot(2, n_images, 1 + image_index)
        plt.imshow(images[image_index], cmap="binary")
        plt.axis("off")
        plt.subplot(2, n_images, 1 + n_images + image_index)
        plt.imshow(reconstructions[image_index], cmap="binary")
        plt.axis("off")

plot_reconstructions(stacked_ae)
plt.show()

▶ 재구성된 이미지를 식별할 수는 있으나 정보를 조금 많이 잃었다.

이런 경우, 모델을 더 오래 훈련하거나, 인코더와 디코더 층을 늘리거나, 코딩의 크기를 늘려야할 수도 있다.


◆ 패선MNIST 데이터셋 시각화

적층 오토인코더를 훈련했으므로 이 모델을 사용해 데이터셋의 차원을 축소할 수 있다. 오토인코더의 장점은 샘플과 특성이 많은 대용량 데이터셋을 다룰 수 있다는 점이므로, 오토인코더를 사용해 적절한 수준으로 차원을 축소한 후 다른 차원 축소 알고리즘을 사용해 패선 MNIST 데이터셋을 시각화해본다.

먼저 적층 오토인코더의 인코더 모델을 사용해 차원을 30으로 줄이고, t-SNE 알고리즘을 구현한 사이킷런 클래스로 시각화를 위해 차원을 2까지 줄인다. 그리고 이 데이터셋을 산점도 그래프로 그려본다.

from sklearn.manifold import TSNE
X_valid_compressed = stacked_encoder.predict(X_valid)
tsne = TSNE(init='pca', learning_rate='auto', random_state=42)
X_valid_2D = tsne.fit_transform(X_valid_compressed)

# 산점도 시각화
plt.scatter(X_valid_2D[:, 0], X_valid_2D[:, 1], c=y_valid, s=10, cmap='tab10')
plt.show()

 

그리고 좀 더 이해하기 쉽도록 이미지 박스를 넣는 방식으로 보완한다.

import matplotlib as mpl

plt.figure(figsize=(10, 8))
cmap = plt.cm.tab10
Z = X_valid_2D
Z = (Z - Z.min()) / (Z.max() - Z.min())  # 0-1 범위로 정규화
plt.scatter(Z[:, 0], Z[:, 1], c=y_valid, s=10, cmap=cmap)
image_positions = np.array([[1., 1.]])
for index, position in enumerate(Z):
    dist = ((position - image_positions) ** 2).sum(axis=1)
    if dist.min() > 0.02: # 다른 이미지와 충분히 멀리 떨어져 있는 경우
        image_positions = np.r_[image_positions, [position]]
        imagebox = mpl.offsetbox.AnnotationBbox(
            mpl.offsetbox.OffsetImage(X_valid[index], cmap="binary"),
            position, bboxprops={"edgecolor": cmap(y_valid[index]), "lw": 2})
        plt.gca().add_artist(imagebox)

plt.axis("off")
plt.show()


◆ 적층 인코더를 사용한 비지도 사전 훈련

레이블된 훈련 데이터가 많지 않은 복잡한 자도 학습 문제를 다루어야 한다면 비슷한 문제를 학습한 신경망을 찾아 하위 층을 재사용하는 것도 한 방법이다. 즉, 기존의 네트워크에서 학습한 특성 감지 기능을 재사용한다.

비슷하게 대부분의 데이터가 레이블되지 않은 대량의 데이터셋이 있다면 먼저 전체 데이터를 사용해 적층 오토인코더를 훈련하고, 오토인코더의 하위 층을 재사용해 실제 문제를 해결하기 위한 신경망을 만들고 레이블된 데이터를 사용해 훈련할 수 있다.

분류기를 훈련할 때 레이블된 훈련 데이터가 많지 않으면 사전 훈련된 층 가장 하위에 있는 층 하나 이상을 동결하는 것이 좋다.

구현할 때는 모든 훈련 데이터를 사용해 오토인코더를 훈련하고 인코더 층을 재사용하여 새로운 신경망을 만들면 된다.

 

[적층 오토인코더를 훈련하기 위한 몇 가지 기술]

1. 가중치 묶기

오토인코더가 완벽하게 대칭일 땐 디코더의 가중치와 인코더의 가중치를 묶으면 모델에 있는 가중치의 수가 절반으로 줄어들어 훈련 속도가 증가하고 과대적합의 위험이 줄어든다.

케라스의 사용자 정의 층을 만들어 층 간에 가중치를 묶어본다.

class DenseTranspose(tf.keras.layers.Layer):
    def __init__(self, dense, activation=None, **kwargs):
        super().__init__(**kwargs)
        self.dense = dense
        self.activation = tf.keras.activations.get(activation)

    def build(self, batch_input_shape):
        self.biases = self.add_weight(name='bias',
                                      shape=self.dense.input_shape[-1],
                                      initializer='zeros')
        super().build(batch_input_shape)

    def call(self, inputs):
        Z = tf.matmul(inputs, self.dense.weights[0], transpose_b=True)
        return self.activation(Z + self.biases)

 

그리고 새로운 적층 오토인코더를 만든다.

tf.random.set_seed(42)

dense_1 = tf.keras.layers.Dense(100, activation="relu")
dense_2 = tf.keras.layers.Dense(30, activation="relu")

tied_encoder = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]),
    dense_1,
    dense_2
])

tied_decoder = tf.keras.Sequential([
    DenseTranspose(dense_2, activation="relu"),
    DenseTranspose(dense_1),
    tf.keras.layers.Reshape([28, 28])
])

tied_ae = tf.keras.Sequential([tied_encoder, tied_decoder])

# 모델을 컴파일하고 훈련
tied_ae.compile(loss="mse", optimizer="nadam")
history = tied_ae.fit(X_train, X_train, epochs=10,
                      validation_data=(X_valid, X_valid))

※ 해당 코드는 자꾸 아래와 같은 에러가 뜨는 데, 구글링을 해보고 챗gpt에 수없이 물어보면서 수정해보는 데도 제대로 되질 않아서 결국 해결하지 못하고 넘긴다.

AttributeError: 'Dense' object has no attribute 'input_shape'

 

 

2. 오토인코더 한 개씩 훈련하기

전체 오토인코더를 훈련하는 대신 오토인코더 하나를 훈련하고 이를 쌓아올려서 한 개의 적층 오토인코더를 만들 수 있다.

def train_autoencoder(n_neurons, X_train, X_valid, n_epochs=10,
                      output_activation=None):
    n_inputs = X_train.shape[-1]
    encoder = tf.keras.layers.Dense(n_neurons, activation="relu")
    decoder = tf.keras.layers.Dense(n_inputs, activation=output_activation)
    autoencoder = tf.keras.Sequential([encoder, decoder])
    autoencoder.compile(loss="mse", optimizer="nadam")
    autoencoder.fit(X_train, X_train, epochs=n_epochs,
                    validation_data=(X_valid, X_valid))
    return encoder, decoder, encoder(X_train), encoder(X_valid)
tf.random.set_seed(42)

X_train_flat = tf.keras.layers.Flatten()(X_train)
X_valid_flat = tf.keras.layers.Flatten()(X_valid)
enc1, dec1, X_train_enc1, X_valid_enc1 = train_autoencoder(
    100, X_train_flat, X_valid_flat)
enc2, dec2, _, _ = train_autoencoder(
    30, X_train_enc1, X_valid_enc1, output_activation="relu")

합성곱 오토인코더

 

이미지를 다루는 경우에는 오토인코더가 좋은 성능을 내지 못하기 때문에, 이미지를 다룰 때는 합성곱 신경망이 밀집 네트워크보다 훨씬 잘 맞는다. 따라서 비지도 사전 훈련이나 차원 축소를 위해 이미지에 대한 오토인코더를 만들려면 합성곱 오토인코더를 만들어야 한다.

인코더는 합성곱 층과 풀링 층으로 구성된 일반적인 CNN이며, 전형적으로 입려에서 공간 방향의 차원(높이와 너비)을 줄이고 깊이(특성 맵의 개수)를 늘린다.

디코더는 거꾸로 이미지의 스케일을 늘리고 깊이를 원본 차원으로 돌려야 한다. 이를 위해서 전치 합성곱 층을 사용하거나, 합성곱 층과 업샘플링 층을 연결할 수 있다.

다음은 패션 MNIST 데이터셋에 대한 간단한 합성곱 오토인코더이다.

tf.random.set_seed(42) 

conv_encoder = tf.keras.Sequential([
    tf.keras.layers.Reshape([28, 28, 1], input_shape=[28, 28]),
    tf.keras.layers.Conv2D(16, 3, padding="same", activation="relu"),
    tf.keras.layers.MaxPool2D(pool_size=2),  # 출력: 14 × 14 x 16
    tf.keras.layers.Conv2D(32, 3, padding="same", activation="relu"),
    tf.keras.layers.MaxPool2D(pool_size=2),  # 출력: 7 × 7 x 32
    tf.keras.layers.Conv2D(64, 3, padding="same", activation="relu"),
    tf.keras.layers.MaxPool2D(pool_size=2),  # 출력: 3 × 3 x 64
    tf.keras.layers.Conv2D(30, 3, padding="same", activation="relu"),
    tf.keras.layers.GlobalAvgPool2D()  # 출력: 30
])
conv_decoder = tf.keras.Sequential([
    tf.keras.layers.Dense(3 * 3 * 16),
    tf.keras.layers.Reshape((3, 3, 16)),
    tf.keras.layers.Conv2DTranspose(32, 3, strides=2, activation="relu"),
    tf.keras.layers.Conv2DTranspose(16, 3, strides=2, padding="same",
                                    activation="relu"),
    tf.keras.layers.Conv2DTranspose(1, 3, strides=2, padding="same"),
    tf.keras.layers.Reshape([28, 28])
])
conv_ae = tf.keras.Sequential([conv_encoder, conv_decoder])

# 모델을 컴파일하고 훈련.
conv_ae.compile(loss="mse", optimizer="nadam")
history = conv_ae.fit(X_train, X_train, epochs=10,
                      validation_data=(X_valid, X_valid))
plot_reconstructions(conv_ae)
plt.show()


잡음 제거 오토인코더

 

오토인코더가 유용한 특성을 학습하도록 강제하는 다른 방법은 입력에 잡음을 추가하고 잡음이 없는 원본 입력을 복원하도록 훈련하는 것이다. 잡음은 입력에 순순한 가우스 잡음을 입력에 추가하거나 드롭아웃처럼 랜덤으로 입력을 껴서 발생시킬 수 있다.

구현은 인코더의 입력에 적용한 Dropout 층이 있는 (또는 GaussianNoise 층을 사용한) 일반적인 적층 오토인코더이며, Dropout 층은 훈련하는 동안에만 활성화되며, GaussianNoise 층도 마찬가지다.

# 드롭아웃 사용
tf.random.set_seed(42)

dropout_encoder = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]),
    tf.keras.layers.Dropout(0.5),
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(30, activation="relu")
])
dropout_decoder = tf.keras.Sequential([
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(28 * 28),
    tf.keras.layers.Reshape([28, 28])
])
dropout_ae = tf.keras.Sequential([dropout_encoder, dropout_decoder])

# 모델을 컴파일하고 훈련.
dropout_ae.compile(loss="mse", optimizer="nadam")
history = dropout_ae.fit(X_train, X_train, epochs=10,
                         validation_data=(X_valid, X_valid))

※ GaussianNoise 층을 사용 시에는 Dropout 레이어를 tf.keras.layers.GaussianNoise(0.2) 로 교체하면 된다.

tf.random.set_seed(42)
dropout = tf.keras.layers.Dropout(0.5)
plot_reconstructions(dropout_ae, dropout(X_valid, training=True))
plt.show()


희소 오토인코더

 

희소는 비용 함수에 적절한 항을 추가하여 오토인코더가 코딩 층에서 활성화되는 뉴런 수를 감소시키도록 만든다. 예를 들어 코딩 층에서 평균적으로 5% 뉴런만 활성화되도록 강제할 수 있는데, 이렇게하면 오토인코더가 적은 수의 활성화된 뉴런을 조합하여 입력을 표현해야 한다. 

간단한 방법은 코딩을 0과 1 사이 값으로 제한하기 위해 코딩 층에 시그모이드 활성화 함수를 사용하고 큰 코딩 층을 사용한다. 그리고 코딩 층의 활성화 값에 L1 규제를 추가한다.

※ L2 노름 대신 L1 노름을 사용하면 신경망이 모든 코딩을 감소시키는 대신 입력 이미지에서 필요한 것을 제거하고 가장 중요한 코딩을 보전하도록 만든다.

tf.random.set_seed(42)

sparse_l1_encoder = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]),
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(300, activation="sigmoid"),
    tf.keras.layers.ActivityRegularization(l1=1e-4)
])
sparse_l1_decoder = tf.keras.Sequential([
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(28 * 28),
    tf.keras.layers.Reshape([28, 28])
])
sparse_l1_ae = tf.keras.Sequential([sparse_l1_encoder, sparse_l1_decoder])

# 모델을 컴파일하고 훈련
sparse_l1_ae.compile(loss="mse", optimizer="nadam")
history = sparse_l1_ae.fit(X_train, X_train, epochs=10,
                           validation_data=(X_valid, X_valid))

 

종종 더 나은 결과를 내는 또 다른 방법은 훈련 반복마다 코딩 층의 실제 희소 정도를 측정하고 측정된 희소 정도가 타깃 희소 정도와 다르면 모델에 벌칙을 부과하는 것이다. 이를 위해 전체 훈련 배치에 대해 코딩 층에 있는 각 뉴런의 평균적인 활성화를 계산한다. 배치 크기는 너무 작지 않아야 하는데, 그렇지 않으면 평균값이 정확하지 않다.

각 뉴런에 대한 평균 활성화 정도를 알면 비용 함수에 희소 손실을 추가하여 너무 활성화되거나 충분히 활성화되지 않은 뉴런에 벌칙을 가할 수 있다.

코딩 층의 각 뉴런에 대해 희소 손실을 계산했다면 이 손실들을 모두 합해서 비용 함수의 결과에 더한다. 희소 손실과 재구성 손실의 상대적 중요도를 제어하기 위해 희소 손실에 희소 가중치 하이퍼파라미터를 곱한다. 이 가중치가 너무 크면 모델이 목표 희소에 가깝게 되나, 입력을 적절히 재구성하지 못해서 쓸모없는 모델이 될 수 있다. 그 반대로는 모델이 희소 목표를 거의 무사하여 어떤 특성도 학습하지 못한다.

 

KL(쿨백-라이블러) 발산 규제를 적용하기 위한 사용자 정의 규제를 만들어본다.

※ 쿨백-라이블러 발산(Kullback–Leibler divergence, KLD)은 두 확률분포의 차이를 계산하는 데에 사용하는 함수로, 어떤 이상적인 분포에 대해, 그 분포를 근사하는 다른 분포를 사용해 샘플링을 한다면 발생할 수 있는 정보 엔트로피 차이를 계산한다. 상대 엔트로피(relative entropy), 정보 획득량(information gain), 인포메이션 다이버전스(information divergence)라고도 한다. 정보이론에서는 상대 엔트로피, 기계학습의 결정 트리에서는 정보 획득량을 주로 사용한다.
kl_divergence = tf.keras.losses.kullback_leibler_divergence

class KLDivergenceRegularizer(tf.keras.regularizers.Regularizer):
    def __init__(self, weight, target):
        self.weight = weight
        self.target = target

    def __call__(self, inputs):
        mean_activities = tf.reduce_mean(inputs, axis=0)
        return self.weight * (
            kl_divergence(self.target, mean_activities) +
            kl_divergence(1. - self.target, 1. - mean_activities))

 

코딩 층의 활성화에 KLDivergenceRegularizer를 적용해 희소 오토인코더를 만든다.

tf.random.set_seed(42)

kld_reg = KLDivergenceRegularizer(weight=5e-3, target=0.1)
sparse_kl_encoder = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]),
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(300, activation="sigmoid",
                          activity_regularizer=kld_reg)
])
sparse_kl_decoder = tf.keras.Sequential([
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(28 * 28),
    tf.keras.layers.Reshape([28, 28])
])
sparse_kl_ae = tf.keras.Sequential([sparse_kl_encoder, sparse_kl_decoder])

# 모델을 컴파일하고 훈련
sparse_kl_ae.compile(loss="mse", optimizer="nadam")
history = sparse_kl_ae.fit(X_train, X_train, epochs=10,
                           validation_data=(X_valid, X_valid))

변이형 오토인코더

 

변이형 오토인코더는 확률적 인코더로, 훈련이 끝난 후에도 출력이 부분적으로 우연에 의해 결정된다. 또한 생성 오토인코더로, 마치 훈련 세트에서 샘플링된 것 같은 새로운 샘플을 생성할 수 있다.

변이형 오토인코더는 효율적인 근사 베이즈 추론 방법인 변분 베이즈 추론을 수행하는데, 베이즈 추론은 베이즈 정리에서 유도된 방정식을 사용하여 새로운 데이터를 기반으로 확률 분포를 업데이트하는 것을 의미한다. 데이터 분포의 좋은 근삿값을 찾는 것이 목적으로, 원래 분포를 사전 분포, 업데이트된 분포를 사후 분포라고 한다.

 

패선 MNIST 데이터셋에서 변이형 오토인코더를 만들어본다.

먼저 mean과 log_var 가 주어졌을 때 코딩을 샘플링하는 사용자 정의 층을 만든다.

class Sampling(tf.keras.layers.Layer):
    def call(self, inputs):
        mean, log_var = inputs
        return tf.random.normal(tf.shape(log_var)) * tf.exp(log_var / 2) + mean

 

그리고 함수형 API를 사용해 인코더를 만든다.

codings_size = 10

inputs = tf.keras.layers.Input(shape=[28, 28])
Z = tf.keras.layers.Flatten()(inputs)
Z = tf.keras.layers.Dense(150, activation='relu')(Z)
Z = tf.keras.layers.Dense(100, activation='relu')(Z)
codings_mean = tf.keras.layers.Dense(coding_size)(Z)
codings_log_var = tf.keras.layers.Dense(codings_size)(Z)
codings = Sampling()([codings_mean, codings_log_var])
variational_encoder = tf.keras.Model(
    inputs=[inputs], outputs=[codings_mean, codings_log_var, codings])

 

그다음 디코더를 만든다.

디코더는 함수형 API 대신 시퀀셜 API를 사용할 수도 있다.

decoder_inputs = tf.keras.layers.Input(shape=[codings_size])
x = tf.keras.layers.Dense(100, activation='relu')(decoder_inputs)
x = tf.keras.layers.Dense(150, activation='relu')(x)
x = tf.keras.layers.Dense(28 * 28)(x)
outputs = tf.keras.layers.Reshape([28, 28])(x)
variational_decoder = tf.keras.Model(inputs=[decoder_inputs], outputs=[outputs])

 

변이형 오토인코더 모델을 만든다.

인코더의 처음 두 개 출력을 무시하고 코딩만 디코더에 주입한다.

_, _, codings = variational_encoder(inputs)
reconstructions = variational_decoder(codings)
variational_ae = tf.keras.Model(inputs=[inputs], outputs=[reconstructions])

 

잠재 손실과 재구성 손실을 추가한다.

# 사용자 정의 손실 함수
def vae_loss(inputs, reconstructions):
    reconstruction_loss = tf.reduce_mean(tf.keras.losses.mean_absolute_error(inputs, reconstructions))
    latent_loss = -0.5 * tf.reduce_sum(
        1 + codings_log_var - tf.exp(codings_log_var) - tf.square(codings_mean), axis=-1)
    return reconstruction_loss + tf.reduce_mean(latent_loss) / 784.  # 784는 전체 픽셀 수

 

마지막으로 오토인코더를 컴파일하고 훈련한다.

# 오토인코더 컴파일 및 훈련
variational_ae.compile(loss='mae', optimizer='nadam')
history = variational_ae.fit(X_train, X_train, epochs=25, batch_size=128,
                             validation_data=(X_valid, X_valid))

패선 MNIST 이미지 생성하기

 

변이형 오토인코더를 사용해 패션 의류처럼 보이는 이미지를 생성해본다. 이를 위해 가우스 분포에서 랜덤한 코딩을 샘플링하여 디코딩해야 한다.

tf.random.set_seed(42)

codings = tf.random.normal(shape=[3 * 7, codings_size])
images = variational_decoder(codings).numpy()
def plot_multiple_images(images, n_cols=None):
    n_cols = n_cols or len(images)
    n_rows = (len(images) - 1) // n_cols + 1
    if images.shape[-1] == 1:
        images = images.squeeze(axis=-1)
    plt.figure(figsize=(n_cols, n_rows))
    for index, image in enumerate(images):
        plt.subplot(n_rows, n_cols, index + 1)
        plt.imshow(image, cmap="binary")
        plt.axis("off")

plot_multiple_images(images, 7)
plt.show()

▶ 흐릿하긴 하나, 형체를 알아볼 수는 있다.

 

변이형 오토인코더는 시맨틱 보간을 수행할 수 있다. 두 이미지가 겹쳐 보이는 것 같은 픽셀 수준의 보간 대신 코딩 수준에서 두 이미지를 보간할 수 있다.

※ 보간은 두점을 연결하는 방법을 의미한다. 여기서 말하는 연결은 궤적(Trajectory)를 생성한다는 뜻이다. 보간이 필요한 이유는 정보를 압축한 것을 다시 복원하기 위함이다.

tf.random.set_seed(42)

codings = np.zeros([7, codings_size])
codings[:, 3] = np.linspace(-0.8, 0.8, 7)
images = variational_decoder(codings).numpy()

# 이미지 출력
plot_multiple_images(images)
plt.show()

▶ 점진적으로 변하는 이미지를 얻을 수 있다.


다음 내용

 

[딥러닝] 비지도 학습: 생성적 적대 신경망(GAN)

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

puppy-foot-it.tistory.com


[출처]

핸즈 온 머신러닝

위키백과

네이버 지식백과

728x90
반응형