08. 오토인코더 (AutoEncoder)
핸즈온 머신러닝 교재를 가지고 공부한 것을 정리한 포스팅입니다.
08. 오토인코더 - Autoencoder
저번 포스팅 07. 순환 신경망, RNN에서는 자연어, 음성신호, 주식과 같은 연속적인 데이터에 적합한 모델인 RNN, LSTM, GRU에 대해 알아보았다. 이번 포스팅에서는 딥러닝에서의 비지도 학습(unsupervised learning)이라고 할 수 있는 오코인코더(autoencoder)에 대해 알아보도록 하자.
1. 오토인코더 란?
오토인코더(Autoencoder)는 아래의 그림과 같이 단순히 입력을 출력으로 복사하는 신경망이다. 어떻게 보면 간단한 신경망처럼 보이지만 네트워크에 여러가지 방법으로 제약을 줌으로써 어려운 신경망으로 만든다. 예를들어 아래 그림처럼 hidden layer의 뉴런 수를 input layer(입력층) 보다 작게해서 데이터를 압축(차원을 축소)한다거나, 입력 데이터에 노이즈(noise)를 추가한 후 원본 입력을 복원할 수 있도록 네트워크를 학습시키는 등 다양한 오토인코더가 있다. 이러한 제약들은 오토인코더가 단순히 입력을 바로 출력으로 복사하지 못하도록 방지하며, 데이터를 효율적으로 표현(representation)하는 방법을 학습하도록 제어한다.
2. Uncomplete 오토인코더
오토인코더는 위의 그림에서 볼 수 있듯이 항상 인코더(encoder)와 디코더(decoder), 두 부분으로 구성되어 있다.
인코더(encoder) : 인지 네트워크(recognition network)라고도 하며, 입력을 내부 표현으로 변환한다.
디코더(decoder) : 생성 네트워크(generative nework)라고도 하며, 내부 표현을 출력으로 변환한다.
오토인코더는 위의 그림에서 처럼, 입력과 출력층의 뉴런 수가 동일하다는 것만 제외하면 일반적인 MLP(Multi-Layer Perceptron)과 동일한 구조이다. 오토인코더는 입력을 재구성하기 때문에 출력을 재구성(reconstruction)이라고도 하며, 손실함수는 입력과 재구성(출력)의 차이를 가지고 계산한다.
위 그림의 오토인토더는 히든 레이어의 뉴런(노드, 유닛)이 입력층보다 작으므로 입력이 저차원으로 표현되는데, 이러한 오토인코더를 Undercomplete Autoencoder라고 한다. undercomplete 오토인코더는 저차원을 가지는 히든 레이어에 의해 입력을 그대로 출력으로 복사할 수 없기 때문에, 출력이 입력과 같은 것을 출력하기 위해 학습해야 한다. 이러한 학습을 통해 undercomplete 오토인코더는 입력 데이터에서 가장 중요한 특성(feature)을 학습하도록 만든다.
2.1 Undercomplete Linear 오토인코더로 PCA 구현하기
위에서 살펴본 Undercomplete 오토인코더에서 활성화 함수를 sigmoid, ReLU같은 비선형(non-linear)함수가 아니라 선형(linear) 함수를 사용하고, 손실함수로 MSE(Mean Squared Error)를 사용할 경우에는 PCA라고 볼 수 있다.
아래의 예제코드는 가상의 3차원 데이터셋을 undercomplete 오토인코더를 사용해 2차원으로 축소하는 PCA를 수행한 코드이다. 전체 코드는 ExcelsiorCJH's GitHub에서 확인할 수 있다.
위의 코드에서 입력의 개수(n_inputs
)와 출력의 개수(n_outputs
)가 동일한 것을 알 수 있으며, PCA를 위해 tf.layers.dense()
에서 따로 활성화 함수를 지정해주지 않아 모든 뉴런이 선형인 것을 알 수 있다.
3. Stacked 오토인코더
Stacked 오토인코더 또는 deep 오토인코더는 여러개의 히든 레이어를 가지는 오토인코더이며, 레이어를 추가할수록 오토인코더가 더 복잡한 코딩(부호화)을 학습할 수 있다. stacked 오토인코더의 구조는 아래의 그림과 같이 가운데 히든레이어(코딩층)을 기준으로 대칭인 구조를 가진다.
3.1 텐서플로로 stacked 오토인코더 구현
Stacked 오토인코더는 기본적인 Deep MLP와 비슷하게 구현할 수 있다. 아래의 예제는 He 초기화, ELU 활성화 함수, 규제(regularization)을 사용해 MNIST 데이터셋에 대한 stacked 오토인코더를 구현한 코드이다. 오토인코더는 레이블이 없는 즉, 레이블(?, 정답)이 입력 자기자신이기 때문에 MNIST 데이터셋의 레이블은 사용되지 않는다. 아래 코드의 전체코드는 'ExcelsiorCJH's GitHub에서 확인할 수 있다.
위의 코드를 학습 시킨 후에 테스트셋의 일부를 재구성하였을 때, 아래의 그림과 같은 결과가 나온다.
3.2 가중치 묶기
위(3.1)에서 구현한 stacked 오토인코더처럼, 오토인코더가 완전히 대칭일 때에는 일반적으로 인코더(encoder)의 가중치와 디코더(decoder)의 가중치를 묶어준다. 이렇게 가중치를 묶어주게 되면, 네트워크의 가중치 수가 절반으로 줄어들기 때문에 학습 속도를 높이고 오버피팅의 위험을 줄여준다.
위의 그림을 수식으로 나타내면, 예를들어 오토인코더가 개의 층을 가지고 있고 이 번째 층의 가중치를 나타낸다고 할 때, 디코더 층의 가중치는 로 정의할 수 있다.
텐서플로에서는 tf.layers.dense()
를 이용해서 가중치를 묶는 것이 복잡하기 때문에 아래의 코드처럼 직접 층을 구현해주는 것이 좋다. 아래의 예제는 3.1에서 구현한 stacked 오토인코더를 가중치를 묶어서 작성한 코드이다.
3.3 한 번에 한 층씩 학습하기
3.1과 3.2에서 처럼 한 번에 전체 오토인코더를 학습시키는 것보다 아래의 그림처럼 한 번에 오토인코더 하나를 학습하고, 이를 쌓아올려서 한 개의 stacked-오토인코더를 만드는 것이 훨씬 빠르며 이러한 방식은 아주 깊은 오토인코더일 경우에 유용하다.
[단계 1]에서 첫 번째 오토인코더는 입력을 재구성하도록 학습된다.
[단계 2]에서는 두 번째 오토인코더가 첫 번째 히든 레이어(
Hidden 1
)의 출력을 재구성하도록 학습된다.[단계 3]에서는 단계1 ~ 2의 오토인코더를 합쳐 최종적으로 하나의 stacked-오토인코더를 구현한다.
텐서플로에서 이렇게 여러 단계의 오토인코더를 학습시키는 방법으로는 다음과 같이 두 가지 방법이 있다.
각 단계마다 다른 텐서플로 그래프(graph)를 사용하는 방법
하나의 그래프에 각 단계의 학습을 수행하는 방법
위의 두 가지 방법에 대한 코드는 ExcelsiorCJH's GitHub 에서 확인할 수 있다.
4. Stacked-오토인코더를 이용한 비지도 사전학습
대부분이 레이블되어 있지 않는 데이터셋이 있을 때, 먼저 전체 데이터를 사용해 stacked-오토인코더를 학습시킨다. 그런 다음 오토인코더의 하위 레이어를 재사용해 분류와 같은 실제 문제를 해결하기 위한 신경망을 만들고 레이블된 데이터를 사용해 학습시킬 수 있다.
위와 같은 방법을 텐서플로에서 구현할 때는 Transfer Learning포스팅에서 살펴본 방법과 같이 구현하면 된다. 이러한 비지도 사전학습 방법에 대한 소스코드는 여기에서 확인할 수 있다.
5. Denoising 오토인코더
오토인코더가 의미있는 특성(feature)을 학습하도록 제약을 주는 다른 방법은 입력에 노이즈(noise, 잡음)를 추가하고, 노이즈가 없는 원본 입력을 재구성하도록 학습시키는 것이다. 노이즈는 아래의 그림처럼 입력에 가우시안(Gaussian) 노이즈를 추가하거나, 드롭아웃(dropout)처럼 랜덤하게 입력 유닛(노드)를 꺼서 발생 시킬 수 있다.
5.1 텐서플로로 구현하기
이번에는 텐서플로를 이용해 가우시안 노이즈와 드롭아웃을 이용한 denoising-오토인코더를 구현해보도록 하자. 오토인코더 학습에 사용한 데이터셋은 위에서 부터 다뤘던 MNIST 데이터셋이다. 아래의 코드에 대한 전체 코드는 ExcelsiorCJH's GitHub에서 확인할 수 있다.
5.1.1 Gaussian noise
위의 가우시안 노이즈를 추가한 denoising-오토인코더의 MNIST 재구성 결과는 다음과 같다.
5.1.2 Dropout
6. Sparse 오토인코더
오토인코더가 좋은 특성을 추출하도록 만드는 다른 제약 방법은 희소성(sparsity)를 이용하는 것인데, 이러한 오토인코더를 Sparse Autoencoder라고 한다. 이 방법은 손실함수에 적절한 항을 추가하여 오토인코더가 코딩층(coding layer, 가운데 층)에서 활성화되는 뉴런 수를 감소시키는 것이다. 예를들어 코딩층에서 평균적으로 5% 뉴런만 홀성화되도록 만들어 주게 되면, 오토인코더는 5%의 뉴런을 조합하여 입력을 재구성해야하기 때문에 유용한 특성을 표현하게 된다.
이러한 Sparse-오토인코더를 만들기 위해서는 먼저 학습 단계에서 코딩층의 실제 sparse(희소) 정도를 측정해야 하는데, 전체 학습 배치(batch)에 대해 코딩층의 평균적인 활성화를 계산한다. 배치의 크기는 너무 작지 않게 설정 해준다.
위에서 각 뉴런에 대한 평균 활성화 정도를 계산하여 구하고, 손실함수에 희소 손실(sparsity loss)를 추가하여 뉴런이 크게 활성화 되지 않도록 규제할 수 있다. 예를들어 한 뉴런의 평균 활성화가 0.3
이고 목표 희소 정도가 0.1
이라면, 이 뉴런은 덜 활성화 되도록 해야한다. 희소 손실을 구하는 간단한 방법으로는 제곱 오차 를 추가하는 방법이 있다. 하지만, Sparse-오토인코더에서는 아래의 그래프 처럼 MSE보다 더 경사가 급한 쿨백 라이블러 발산(KL-divergense, Kullback-Leibler divergense)을 사용한다.
6.1 쿨백 라이블러 발산
쿨백-라이블러 발산(Kullback-Leibler divergence, KLD)은 두 확률분포의 차이를 계산하는 데 사용하는 함수이다. 예를들어 딥러닝 모델을 만들 때, 학습 데이터셋의 분포 와 모델이 추정한 데이터의 분포 간에 차이를 KLD를 활용해 구할 수 있다(ratsgo's blog).
Sparse-오토인코더에서는 코딩층에서 뉴런이 활성화될 목표 확률 와 실제확률 (학습 배치에 대한 평균 활성화) 사이의 발산을 측정하며, 식은 다음과 같다.
위의 식을 이용해 코딩층의 각 뉴런에 대해 희소 손실을 구하고 이 손실을 모두 합한 뒤 희소 가중치 하이퍼파라미터를 곱하여 손실함수의 결과에 더해준다.
6.2 텐서플로 구현
이번에는 텐서플로를 이용해 Sparse-오토인코더를 구현해보도록 하자.
7. Variational AutoEncoder (VAE)
VAE(Variational AutoEncoder)는 2014년 D.Kingma와 M.Welling이 Auto-Encoding Variational Bayes 논문에서 제안한 오토인코더의 한 종류이다. VAE는 위에서 살펴본 오터인코더와는 다음과 같은 다른점이 있다.
VAE는 확률적 오토인코더(probabilistic autoencoder)다. 즉, 학습이 끝난 후에도 출력이 부분적으로 우연에 의해 결정된다.
VAE는 생성 오토인코더(generatie autoencoder)이며, 학습 데이터셋에서 샘플링된 것과 같은 새로운 샘플을 생성할 수 있다.
VAE의 구조는 아래의 그림과 같다.
VAE의 코딩층은 다른 오토인코더와는 다른 부분이 있는데 주어진 입력에 대해 바로 코딩을 만드는 것이 아니라, 인코더(encoder)는 평균 코딩 와 표준편차 코딩 을 만든다. 실제 코딩은 평균이 이고 표준편차가 인 가우시안 분포(gaussian distribution)에서 랜덤하게 샘플링되며, 이렇게 샘플링된 코딩을 디코더(decoder)가 원본 입력으로 재구성하게 된다.
VAE는 마치 가우시안 분포에서 샘플링된 것처럼 보이는 코딩을 만드는 경향이 있는데, 학습하는 동안 손실함수가 코딩(coding)을 가우시안 샘플들의 집합처럼 보이는 형태를 가진 코딩 공간(coding space) 또는 잠재 변수 공간(latent space)로 이동시키기 때문이다.
이러한 이유로 VAE는 학습이 끝난 후에 새로운 샘플을 가우시안 분포로 부터 랜덤한 코딩을 샘플링해 디코딩해서 생성할 수 있다.
7.1 VAE의 손실함수
VAE의 손실함수는 두 부분으로 구성되어 있다. 첫 번째는 오토인코더가 입력을 재구성하도록 만드는 일반적인 재구성 손실(reconstruction loss)이고, 두 번째는 가우시안 분포에서 샘플된 것 샅은 코딩을 가지도록 오토인코더를 제어하는 latent loss이다. 이 손실함수의 식에 대해서는 ratsgo님의 블로그를 참고하면 자세히 설명되어 있다. (나도 언젠가 이해해서 포스팅할 날이 오기를...)
7.2 텐서플로 구현
8. 마무리
이번 포스팅에서는 자기지도학습(self-supervised learning)인 오토인코더에 대해 개념과 uncomplete, stacked, denoising, sparse, VAE 오토인코더에 대해 알아보았다. 위의 코드에 대한 전체 코드는 https://github.com/ExcelsiorCJH/Hands-On-ML/blob/master/Chap15-Autoencoders/Chap15-Autoencoders.ipynb 에서 확인할 수 있다.