AI/Hands-on ML

[핸즈온 머신러닝] 17장(1) - 오토인코더와 GAN을 사용한 표현 학습과 생성적 학습

KIM DEON 2021. 5. 17. 02:25

17. 오토인코더와 GAN

오토인코더

지도 없이도 잠재 표현 또는 코딩이라 부르는 입력 데이터의 밀집 표현을 학습할 수 있는 신경망

- 코딩은 일반적으로 입력보다 훨씬 낮은 차원을 가지므로 오토인코더가 차원 축소, 특히 시각화에 사용됨

(오토인코더가 강력한 특성 추출기처럼 작동하므로 심층 신경망의 비지도 사전훈련에 사용되는 것)

- 일부 오토인코더는 훈련 데이터와 매우 비슷한 새로운 데이터를 생성할 수 있음 -> 생성 모델

 

생성적 적대 신경망 (GAN)

초해상도(이미지 해상도 높이기), 이미지를 컬러고 바꾸기, 강력한 이미지 편집, 간단한 스케치->실제 이미지, 동영상에서 다음 프레임 예측하기, 데이터 증식, 다른 모델의 취약점 식별 및 개선 등 널리 사용됨

 

오토인코더와 GAN 모두 비지도 학습으로, 둘 다 밀집 표현을 학습하고 생성 모델로 사용할 수 있음

 

오토인코더 

- 단순히 입력을 출력으로 복사하는 방법을 배움

- 네트워크에 제약을 가해, 이 작업을 어렵게 만듦 (잠재 표현의 크기를 제한하거나, 입력에 잡음을 추가하고 원본 입력을 복원하도록 네트워크를 훈련)

- 오토인코더가 바로 복사하지 못하고, 데이터를 효율적으로 표현하는 방법을 배움

- 즉, 코딩은 일정 제약 조건하에, 항등 함수를 학습하려는 오토인코더의 노력으로 생긴 부산물

 

GAN

- 생성자와 판별자로 구성

- 생성자는 훈련 데이터와 비슷하게 보이는 데이터를 생성

- 판별자는 가짜 데이터와 진짜 데이터를 구별함

- 신경망이 훈련하는 동안 생성자와 판별자가 서로 경쟁함 -> 적대적 훈련

 


17. 1 효율적인 데이터 표현

- 위 두 숫자 시퀀스를 외울 때, 처음 봤을 땐 첫 번째 시퀀스가 짧으므로 더 쉬워보이지만,

- 두 번째 시퀀스를 잘 보면 50부터 14까지 짝수를 나열했음을 알 수 있음

-> 이런 패턴을 알면 두 번째 시퀀스를 외우는 것이 더 쉬움

-> 즉, 긴 시퀀스를 기억할 때 패턴을 찾는 것이 유용함

=> 오토인코더의 경우, 훈련하는 동안 오토인코더에 제약을 가해서 데이터에 있는 패턴을 찾아 활용함


체스 플레이어가 체스 패턴을 통해 체스판의 말 위치를 외우는 것 처럼,

오토인코더는 입력을 받아 효율적인 내부 표현으로 바꾸고 입력과 가장 가까운 어떤 것을 출력함

 

체스 기억 실험(왼쪽), 간단한 오토인코더(오른쪽)

오토인코더는 항상 두 부분으로 구성

- 입력을 내부 표현으로 바꾸는 인코더 (인지 네트워크)

- 내부 표현을 출력으로 바꾸는 디코더 (생성 네트워크)

 

autoencoder (출처:https://excelsior-cjh.tistory.com/187)

 

출력층의 뉴런 수가 입력 개수와 동일하다는 것을 제외하면, 오토인코더는 다층 퍼셉트론과 구조가 동일

- 뉴런 두 개로 구성된 하나의 은닉층 (인코더)

- 뉴런 세 개로 구성된 출력층 (디코더)

- 오토인코더가 입력을 재구성하기 때문에 출력을 재구성 이라고 부르고,

- 비용 함수는 재구성이 입력과 다를 때 모델에 벌점을 부과하는 재구성 손실을 포함함

 

내부의 표현이 입력 데이터보다 저차원(3차원 -> 2차원)이기 때문에, 이런 오토인코더를 과소완전(undercomplete) 이라고 함

- 과소완전 오토인코더는 입력을 코딩으로 간단히 복사할 수 없으며, 입력과 똑같은 것을 출력하기 위한 방법을 찾아야함

-> 이를 통해 입력 데이터에서 가장 중요한 특성을 학습하도록 만듦

- 적어도 입력 데이터에 대해서는 복원(재구성)을 잘한다는 특징이 있음

 


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

PCA 

- 차원 축소 방법 중 하나로, 선형적으로 데이터 차원을 감소시킴

-데이터가 퍼져있는 방향으로 좌표축을 이동하는 방식으로,

 원본 데이터의 분산을 최대로 보존하는 저차원의 초평면을 찾고자함

- 차원축소 및 PCA 다시보기

PCA 와 Autoencoder (출처: https://www.jeremyjordan.me/autoencoders)

 

오토인코더가 선형 활성화 함수 + 비용함수 MSE 라면, PCA를 수행하는 것과 같음

 

3D 데이터 셋에 PCA를 적용해 2D에 투영하는 선형 오토인코더

from tensorflow import keras

encoder = keras.models.Sequential([keras.layers.Dense(2, input_shape=[3])])
decoder = keras.models.Sequential([keras.layers.Dense(3, input_shape=[2])])
autoencoder = keras.models.Sequential([encoder, decoder])

autoencoder.compile(loss="mse", optimizer=keras.optimizers.SGD(lr=0.1))

- 오토인코더는 인코더 다음에 디코더가 뒤따르는 Sequential 모델

- 오토인코더의 출력 개수 = 입력 개수 (여기서는 3개)

- 단순한 PCA 수행을 위해서는 활성화 함수 사용 X (= 모든 뉴런이 선형), 비용함수는 MSE

 

3D 데이터셋에 훈련 후 모델을 사용해 동일한 데이터셋을 인코딩 (= 2D로 투영)

history = autoencoder.fit(X_train, X_train, epochs=20)
codings = encoder.predict(X_train)

- 동일한 데이터셋 X_train 이 입력과 타깃에도 사용됨

 

원본 3D 데이터셋(왼쪽)과 은닉층 출력(코딩 층, 오른쪽)

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

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

 


17. 3 적층 오토인코더, Stacked AutoEncoder

은닉층을 여러 개 가지는 오토인코더

- 층을 더 추가하여 더 복잡한 코딩을 학습할 수 있음

- 오토인코더가 너무 강력해지지 않도록 주의

  • 인코더가 너무 강력해서 각각의 입력 데이터를 임의의 한 숫자로 매핑하도록 학습했다고 가정
  • 훈련 데이터를 완벽히 재구성하겠지만, 유용한 데이터 표현을 학습하지 못할 것

적층 오토인코더

- 적층 오토인코더는 전형적으로 가운데 은닉층(코딩 층)을 기준으로 대칭

- 위 예시는 입력 784개, 뉴런 300개로 된 은닉층, 뉴런 150개로 된 가운데 은닉층, 뉴런 300개로 된 은닉층, 뉴런 784개로 된 출력층


17. 3. 1 케라스를 사용하여 적층 오토인코더 구현하기

패션 MNIST 데이터셋에서 SELU 활성화 함수를 사용한 적층 오토인코더

stacked_encoder = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.Dense(100, activation="selu"),
    keras.layers.Dense(30, activation="selu"),
])
stacked_decoder = keras.models.Sequential([
    keras.layers.Dense(100, activation="selu", input_shape=[30]),
    keras.layers.Dense(28 * 28, activation="sigmoid"),
    keras.layers.Reshape([28, 28])
])
stacked_ae = keras.models.Sequential([stacked_encoder, stacked_decoder])
stacked_ae.compile(loss="binary_crossentropy",
                   optimizer=keras.optimizers.SGD(lr=1.5))
history = stacked_ae.fit(X_train, X_train, epochs=10,
                         validation_data=(X_valid, X_valid))

 

- 오토인코더 모델을 인코더와 디코더 두 모델로 나눔

- 인코더는 28x28 픽셀의 흑백 이미지를 받고, 이미지를 784 크기의 벡터로 표현하기 위해 펼침

- SELU 활성화 함수를 사용하는 Dense 층 두 개에 통과시킴

- 각 입력 이미지에 대해 인코더는 크기가 30인 벡터를 출력함

- 디코더는 크기가 30인 코딩을 받아, 크기가 커지는 Dense 층 두 개에 통과시킴

- 최종 벡터를 28x28 배열로 변경하여 디코더의 출력이 인코더의 입력과 동일한 크기가 되도록 함

- 적층 인코더를 컴파일할 때 평균 제곱 오차 대신 이진 크로스 엔트로피 손실을 사용

  • 재구성 작업을 다중 레이블 이진 분류 문제로 다루는 것
  • 각 필셀의 강도는 픽셀이 검정일 확률을 나타냄

- X_train을 입력과 타깃으로 사용해 모델을 훈련함 (X_valid를 검증 입력과 검증 타킷으로 사용)

 


17. 3. 2 재구성 시각화

오토인코더가 잘 훈련되었는지 확인하는 방법은 입력과 출력을 비교하는 것

- 입력과 출력의 차이가 크지 않아야 함

def plot_image(image):
	plt.imshow(image, cmap="binary")
    plt.axis("off")

def show_reconstructions(model, n_images=5):
    reconstructions = model.predict(images[:n_images])
    fig = plt.figure(figsize=(n_images * 1.5, 3))
    for image_index in range(n_images):
        plt.subplot(2, n_images, 1 + image_index)
        plot_image(images[image_index])
        plt.subplot(2, n_images, 1 + n_images + image_index)
        plot_image(reconstructions[image_index])
        
show_reconstructions(stacked_ae)

원본 이미지(위)와 재구성(아래)

- 재구성된 이미지를 식별할 수는 있지만, 정보를 많이 읽은 모습

- 모델을 더 훈련하고 인코더/디코더 층을 늘리거나 코딩을 늘리면 나아질 수 있으나, 네트워크가 너무 강력하면 데이터에서 유익한 패턴을 학습하지 못하고 완벽한 재구성 이미지를 만드려고할 것

 


17. 3. 3 패션 MNIST 데이터셋 시각화

훈련한 적층 오토인코더 모델을 사용해, 데이터 셋의 차원을 축소할 수 있음

- 시각화 입장에서 보면, 다른 차원 축소 알고리즘만큼의 성능은 아니지만

- 샘플과 특성이 많은 대용량 데이터셋을 다룰 수 있음

- 오토인코더를 사용해 적절히 차원을 축소하고, 다른 차원 축소 알고리즘을 사용해 시각화하는 것도 방법

 

왜 대용량 고차원 데이터셋을 차원 축소하는 데 적합한가?

https://kdeon.tistory.com/33

8장 차원축소 장에서 잠깐 다루었던 LLE, Isomap 등은 각 샘플 별로 근방에 있는 데이터를 사용하는 neighborhood 기반 방법임

-> 즉, 가까운 애들이 manifold 상에서도 가까울 것이라고 가정하는 것

매니폴드 상의 거리 (출처)

-> 하지만, 의미적으로 가까울 것이라고 생각하는 샘플들이 가깝지 않을 수 있음

(고차원 데이터 간의 유클리디안 거리 != 유의미한 거리)

-> 데이터 공간이 고차원일 수록, 매니폴드를 찾기 어려울 수록

-> neighborhood방식이 아닌 DNN 기반의 오토인코더가 유용할 수 있음

 

패션 MNIST 데이터셋 시각화

- 적층 오토인코더의 인코더 모델을 사용해 차원을 30으로 줄이고,

- t-SNE 알고리즘을 구현한 사이킷런 클래스를 통해 차원을 2까지 줄임

 

t-SNE

비슷한 샘플은 가까이, 비슷하지 않은 샘플은 멀리 떨어지도록 하면서 차원을 축소

(거리가 가까운 샘플의 정보를 우선 보존하기 위함)

- 주로 시각화에 많이 사용

- 특히 고차원 공간에 있는 샘플의 군집을 시각화할 때 사용

(ex. MNIST 데이터셋을 2D로 시각화할 때)

 

from sklearn.manifold import TSNE

X_valid_compressed = stacked_encoder.predict(X_valid)
tsne = TSNE()
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")

오토인코더 뒤에 t-SNE 를 사용한 패션 MNIST 시각화

- 각 클래스를 다른 색으로 표현

- t-SNE 알고리즘이 식별한 클러스터가 클래스와 잘 매칭이 됨

 


17. 3. 4 적층 오토인코더를 사용한 비지도 사전훈련

11장에서 다루었던 것 처럼,

비슷한 문제를 학습한 신경망을 찾아 하위층을 재사용하는 방법을 통해, 적은 훈련 데이터를 사용해 고성능 모델을 훈련할 수 있음

 

레이블된 훈련 데이터가 많지 않을때 적층 오토인코더를 통해 훈련하기 

- 대부분 레이블되지 않은 대량의 데이터셋을, 먼저 전체 데이터셋을 사용해 적층 오토인코더를 훈련

- 그 다음, 오토인코더의 하위층을 재사용해 실제 문제를 해결하기 위한 신경망을 만들고, 레이블된 데이터를 사용해 훈련

 

구현도 아래와 같이 가능

1. 레이블된 것, 레이블되지 않은 모든 훈련 데이터를 사용해 오토인코더를 훈련

2. 그 후 인코더 층을 재사용하여 새로운 신경망을 만듦

3. 레이블된 데이터를 통해 훈련

 


17. 3. 5 가중치 묶기

위 예제처럼, 오토인코더가 대칭일 땐, 디코더의 가중치와 인코더의 가중치를 묶는 것이 일반적

- 모델에 있는 가중치의 수를 절반으로 줄여 훈련 속도를 높이고, 과대적합의 위험을 줄임

 

어떤 오토인코더가 N개 층을 갖고, WL이 L번째 층의 가중치를 나타낼 때

- (1은 첫 번째 은닉층, N/2은 코딩 층, N은 출력층)

- 디코더 층의 가중치는 WN-L+1 = WL^T (L = 1,2, ..., N/2)

 

케라스의 사용자 정의 층을 만들어 층 간 가중치 묶기

class DenseTranspose(keras.layers.Layer):
    def __init__(self, dense, activation=None, **kwargs):
        self.dense = dense
        self.activation = keras.activations.get(activation)
        super().__init__(**kwargs)
    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)

- 일반적인 Dense 층과 비슷하지만, 다른 Dense 층의 전치된 가중치를 사용함

- 편향 벡터는 독자적으로 사용

 

위 사용자 정의층을 사용하여 적층 오토인코더 구성

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

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

tied_decoder = keras.models.Sequential([
    DenseTranspose(dense_2, activation="selu"),
    DenseTranspose(dense_1, activation="sigmoid"),
    keras.layers.Reshape([28, 28])
])

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

- 이 모델은 절반의 파라미터로 이전 모델보다 약간 낮은 내구성 오차를 달성함

 


17. 3. 6. 한 번에 오토인코더 한 개씩 훈련하기

한 번에 전체 오토인코더를 훈련하지 않고, 오토인코더 하나를 훈련하고 이를 쌓아올려서 한 개의 적층 오토인코더를 만들 수 있음

- 현재는 많이 사용 x

- '탐욕적 방식의 층별 훈련' 에 대한 논문에 등장

한 번에 오토인코더 한 개씩 훈련하기

- 단계 1 에서 첫 번째 오토인코더는 입력을 재구성하도록 학습

- 이 오토인코더를 사용해 전체 훈련 세트는 인코딩하여 (압축된) 새 훈련 세트를 만듦

- 새로운 훈련 세트에서 두 번째 오토인코더를 훈련 (단계 2)

- 마지막으로 모든 오토인코더를 사용해 전체 네트워크를 만듦 (단계 3, 각 오토인코더의 은닉층을 먼저 쌓고, 그 다음 출력층을 (반대로) 쌓음) -> 최종 적층 오토인코더

 


2006년 탐욕적 층별 접근 방법을 사용해 심층 신경망을 비지도 형태로 사전훈련될 수 있음을 발견 한 것이 딥러닝에 대한 큰 관심을 일으킴

-> 이를 위해 제한된 볼츠만 머신(RBM)을 사용했고, 오토인코더에도 적용된다는 것이 밝혀짐

- 11장에서 소개한 여러 기법으로 한 번에 심층 신경망을 훈련할 수 있게 되기 전까지는 이 방식이 심층 신경망을 효율적으로 훈련하는 유일한 방법이었음

 

오토인코더는 밀집 네트워크 외에도, 합성곱 오토인코더나 순환 오토인코더도 만들 수 있음

 


적층 오토인코더 - 모델 파라미터 초기화 (pre-training)

- 오토인코더가 많이 사용되던 2000년대 초반에는 (DNN 활성화 전), 차원 축소 외에도 pre-training에 많이 사용되었음(지금은 x)

- 오토인코더를 쌓아가며 학습해 pretrain을 시켜놓고 학습에 사용하는 방식

출처: https://deepinsight.tistory.com/126?category=842339

- 입력 데이터를 잘 표현하는 첫 번재 layer의 1000개에 대한 weight를 학습하고자함

- 입력 데이터를 복원하는 과정에서 데이터의 특징을 가지고 있는 weight를 학습하게 됨

-> 다시 말해, 적어도 입력 데이터에 대해서는 복원을 할 수 있는 weight를 얻고자 하는 것

출처: https://deepinsight.tistory.com/126?category=842339

- 다음 layer도 마찬가지로, weight를 복원하는 오토인코더를 구성하여 입력데이터를 잘 표현하도록 학습

출처: https://deepinsight.tistory.com/126?category=842339

- 학습이 끝나면, 오토인코더로 weight를 초기화해주고, 모델 학습을 위해 Backpropagation 진행

 

-> 오토인코더의 이런 특성으로 모델을 초기화하는 (pre-training) 용도로 많이 사용되었음

(오토인코더를 활용해, 입력 데이터의 특성을 나타내는 파라미터를 얻는 용도)