정규화 플로우 튜토리얼, 파트1: 분포와 행렬식

Eric Jang의 튜토리얼을 번역한 게시물입니다.

정규화 플로우는 생성 모델, 베이지안 딥러닝, 혹은 강화학습을 다루는 머신러닝 전문가라면 배워둘만한 유용한 알고리즘 기술입니다. 정규화 플로우로 (가우시안과 같은) 간단한 밀도를 생성모델, 강화학습, 변분추론에 사용할 수 있는 복잡한 분포로 변환할 수 있기 때문이죠. TensorFlow는 간편하게 플로우를 구현하고 실제 데이터에 대해서 학습시키는 여러가지 편리한 함수를 제공합니다.

이 튜토리얼은 두 파트로 나뉘는데요.

  • 파트 1: 분포와 행렬식: 이 포스트에서는 어떻게 임의의 밀도를 가역 변환하여 보다 복잡한 밀도를 만들 수 있는지, 또한 어떻게 이러한 변환을 연쇄(chain)하여 “정규화 플로우”를 만들 수 있는지 설명합니다.
  • 파트 2: 최신 정규화 플로우: 후속 포스트에서는 정규화 플로우를 학습하기 위한 최신 기술들을 조사하고, 요새 쏟아져나온 생성 모델링 기술 — 오토리그레시브 모델, MAF, IAF, NICE, Real-NVP, Parallel-Wavenet — 들이 서로 어떤 관계가 있는지 설명합니다.

이 튜토리얼 시리즈는 독자가 선형 대수학, 확률, 신경망, TensorFlow에 대한 기초 지식을 가지고 있다고 상정합니다. 최근 딥러닝과 생성 모델의 발전 경향을 알고 있다면 플로우 기법의 동기나 문맥을 이해하는데 도움이 되겠지만, 필수는 아닙니다.

배경

통계적 머신러닝 알고리즘은 모수 분포 p(x; θ)를 데이터에 피팅하여 데이터 구조를 학습합니다. 주어진 임의의 데이터셋을 분포로 나타낼 수 있다면, 다음과 같은 것들이 가능하죠: 

  1. 학습된 분포에서 샘플링하여 새로운 데이터를 “공짜로” 생성할 수 있습니다; 데이터의 참된 생성 과정을 거칠 필요가 없는 것입니다. 이는 데이터를 생성하는데 자원 소모가 심한 경우, 다시 말해 실제 실험을 수행하는데 오랜 시간이 걸리는 경우에 유용합니다 [1]. 또한 샘플링을 공간에 대한 고차원 적분의 추정량으로 사용할 수도 있습니다.
  2. 테스트 때 관찰된 데이터의 우도를 측정합니다 (이는 기각 샘플링이나 모델 평가에 사용할 수 있습니다)
  3. 변수간 조건 관계를 찾아냅니다. 예를 들어서, p(x2|x1) 같은 분포를 학습하면 리그레션 모델이나 분류 모델을 만들 수 있습니다.
  4. 엔트로피, 상호 정보, 분포의 모멘트 등과 같은 복잡도 측도를 사용해서 알고리즘에 점수를 줄 수 있습니다.

최근의 이미지와 오디오 생성 모델에서 볼 수 있듯, (1)번의 샘플링은 활발히 사용하고 있습니다. 이러한 생성 모델은 벌써 구글 서비스상용 어플리케이션에 적용되고 있고요.

그러나, (2), (3)번의 비조건 & 조건 우도 추정이나 (4)번의 모델 스코어링에 대해서는 상대적으로 리서치 커뮤니티의 관심이 덜합니다. 예를 들면, GAN 디코더의 서포트를 어떻게 계산할 수 있는지 (디코더가 어느 정도의 출력 공간에 0이 아닌 확률을 부여하는지), 혹은 DRAW 분포VAE에 대해서 특정 이미지의 밀도를 어떻게 계산하는지는 아직 알려지지 않았고, 혹여 임의의 분포들에 대해서 해석적 밀도를 안다고 하더라도 그들 사이 다양한 거리측정(KL, earth-mover distance)을 해석적으로 구하는 법은 알지 못합니다.

그럴듯한 샘플을 생성하는 것만으로는 충분치 않습니다: “데이터가 얼마나 그럴듯한가?” [2]에 대한 답을 주는 것도, (예. 강화학습에서 멀티모달 폴리시의 발산을 샘플링/평가하기 위해서) 유연한 조건 밀도를 갖는 것도 , 변분 추론에서 풍부한 사전확률과 사후확률 족을 고를 수 있는 것도 중요하죠.

잠시 우리의 친구 정규 분포를 생각해봅시다. 분포의 꽃이죠: 정규 분포에서 샘플링하는 것도 쉽고,  정규 분포의 해석적 밀도도 알고, 다른 정규 분포에 대한 KL 발산도 알고요,  중심 극한 정리 덕에 거의 모든 데이터에 사용할 수 있다는 것도 확실하며, 거기에 재매개변수화 기법을 통해서 샘플을 통한 역전파까지 가능합니다. 정규 분포는 이처럼 쉽게 사용할 수 있어 다양한 생성 모델링과 강화 학습 알고리즘에 널리 쓰이고 있습니다.

하지만 정규 분포가 우리가 마주치게 되는 실제 문제들에 딱 들어맞지는 않죠. 강화 학습에서는 — 특히 로보틱스와 같이 연속적인 컨트롤 과제에서는 — 폴리시를 보통 다변량 가우시안으로 모델링합니다.

구조상 단변량 가우시안은 멀티모달 분포로부터의 샘플링을 요하는 문제에 대해서 좋은 성능을 보여주지 못합니다. 단변량 가우시안이 잘 작동하지 않는 대표적인 예로 에이전트가 호수를 가로질러 집으로 가는 문제가 있죠. 에이전트는 호수를 시계방향 (왼쪽) 혹은 반시계 방향(오른쪽)으로 돌아서 집에 갈 수 있지만, 가우시안 폴리시는 두 모드를 동시에 표상할 수 없습니다. 대신에 두 모드의 선형적 컴비네이션을 평균으로 삼는 가우시안으로부터 액션을 골라서, 얼음같이 차가운 물에 곧장 들어가는 결과를 낳게 되죠. 비극이에요!

위의 예시는 때로 정규 분포가 지나치게 단순할 수도 있다는 것을 잘 보여줍니다. 대칭에 대한 가정이 실패하는 경우 외에도, 가우시안은 드문 이벤트에 강건하지 못하며, 고차원에서는 대부분의 밀도가 가장자리에 집중되어 있습니다. 다음과 같은 특성을 가진 보다 나은 분포를 찾을 수는 없을까요?

  1. 이미지나 강화 학습 환경의 가치 함수와 같은 풍부한 멀티모달 데이터 분포를 모델링할 정도로 복잡하면서도
  2. 샘플링, 밀도 평가, 재매개변수화가 가능한 샘플과 같이 정규 분포의 편리한 특성은 유지하는 그런 분포 말이죠.

답은 예스입니다! 이를 가능케하는 다음과 같은 몇 가지 방법이 있죠:

  • 멀티모달 폴리시를 표상할 혼합 모델을 사용합니다. 여기서 카테고리란 “옵션”을, 혼합은 하위 폴리시를 표상하겠지요. 이렇게 나오는 샘플은 샘플링하거나 평가하기가 쉽지만, 재매개변수화하기는 쉽지 않아서 VAE나 사후 추론에 사용하기는 어렵습니다. 하지만  굼벨-소프트맥스 / 콘크리트 완화를 사용해서 “옵션” 카테고리를 완화하면 멀티모달, 재매개변수화가 가능한 분포를 얻을 수 있죠.
  • 폴리시 / 가치 분포의 오토리그레시브 분해. 특히 이산 분포(e.g. 카테고리)는 임의의 이산 분포를 모델링 할 수 있습니다.
  • 강화학습에서는 재귀 폴리시, 소음, 혹은 분포적 강화 학습을 통해 가치 분포의 대칭을 깨서 이러한 문제 자체를 피할 수 있습니다. 이는 각 시간 스텝에서 복잡한 가치 분포를 보다 간단한 조건 분포로 압축해서 도움을 줍니다.
  • 소위 비방향성 그래픽 모델이라 불리는 에너지 기반 모델로 학습하면, 정규화된 확률론적 해석을 피할 수 있습니다. 여기서 강화 학습에 이러한 접근법을 적용한 최근의 예시를 참조하세요.
  • 정규화 플로우: 쉽게 조정할 수 있는 분포에서 출발해, 부피 추적이 가능한 가역 변환을 학습합니다.

우리는 마지막 방법, 정규화 플로우에 대해서 알아봅시다.

변수 변환, 부피 변환

일차원 확률 변수의 선형 변환을 통해서 직관적으로 접근해볼까요? X는 Uniform(0,1)의 분포를 따른다고 합시다. 확률 변수 YY = f(X) = 2X + 1의 관계를 갖습니다. 즉 Y는 “소스 분포” X의 간단한 아핀변화 (스케일과 시프트)입니다. 이는 X의 샘플 xi에 간단하게 함수 f를 적용해서 Y의 샘플로 바꿔줄 수 있다는 의미지요.

flow1.png

초록색 정사각형은 p(x)와 p(y) 둘 모두에 대한 실수 도메인의 확률 질량을 나타냅니다 – 높이가 해당값에서의 밀도 함수를 나타내죠. 모든 분포의 확률 질량은 1로 적분되어야 하므로, 모든 곳에서 변수를 2배로 스케일링한다면 해당되는 모든 확률 밀도를 2로 나누어주어야 합니다. 그래서 초록색 정사각형과 파란색 직사각형의 총 면적은 1로 동일하게 되는 것입니다. 

특정 x와 그에 무한소로 근접한 x + dx에 대해서 줌인하여 f를 적용하면, (y, y + dy) 쌍을 얻을 수 있습니다. 

flow2.png

왼쪽은 국소적으로 증가하는 함수이고 (dy / dx > 0), 오른쪽은 국소적으로 감소하는 함수 (dy / dx < 0) 입니다. 총 확률을 보존하기 위해서, dx 구간에서의 p(x)의 변화는 dy 구간에서의 p(y)의 변화와 동등해야 합니다:

p(x)dx = p(y)dy

확률을 보존하기 위해서는 y의 변화량만 신경쓰면 되고, 그 방향성은 상관 없습니다 (x에서 f(x)가 증가하는지 감소하는지 상관 없이, y의 변화량이 동일하다고 가정합니다). 그러므로 p(y) = p(x)|dx / dy|이 성립하고,  로그 공간에서 이는 logp(y) = logp(x) + log|dx / dy|이 됩니다. 로그 밀도를 계산하는 편이 수치 안정성 면에서 낫죠.

이제 두 개의 변수가 있는 다변 상황을 생각해봅시다. 또 다시 도메인에서 무한소로 작은 영역으로 줌인하면, 처음 베이스 분포의 “선분”은 dx가 너비인 정사각형이 됩니다.

직사각형 패치 (x1, x2, x3, x4)를 단순히 시프트하는 변환은 넓이를 변화시키지 않는다는 것을 기억하세요. 우리가 관심있는건 x의 단위 넓이당 변화율이므로 변위 dx를 측량 단위로 생각할 수 있는데, 이는 인위적이죠. 다음의 해석을 간단하게, 단위에 관계 없이 다루기 위해서 원점에 위치한 단위 정사각형을, 다시말해 (0, 0), (1, 0), (0, 1), (1, 1)의 네 점을 생각해봅시다. 

이를 [[a, b];[c, d]] 행렬과 곱하면, 밑의 오른쪽 그림에서와 같이 네 점은 평행사변형으로 변합니다. (0,0)은 (0, 0)으로, (1, 0)은 (a, b)로, (0, 1)은 (c, d)로, (1, 1)은 (a + c, b + d)로 보내집니다. 

flow3.png

따라서 X 도메인에서의 단위 정사각형은 Y 도메인에서의 변형된 평행사변형에 대응됩니다. 그래서 단위면적당 변화율은 평행사변형의 면적이 되지요. 다시말해 adbc가 됩니다. 그런데 평행사변형의 면적 adbc는 선형 변환의 행렬식의 절대값과 똑같습니다!

3차원에서는, “평행사변형의 면적의 변화”가 “평행육면체의 부피의 변화”가 됩니다. 보다 고차원에서 이는 “n-평행체의 부피의 변화”가 되고요. 그래도 기본 개념은 같습니다 – 행렬식은 선형 변환의 부피 변환의 양(그리고 방향)과 동일하고, 이는 어떤 수의 차원에도 일반화가 됩니다.

만약 변환 f가 비선형적이면 어떨까요? 공간의 모든 지점의 왜곡을 하나의 평행사변형으로 추적하는 대신, 무한소로 작은 평행사변형이 도메인의 각 지점의 부피 왜곡의 양에 대응된다고 상상해보세요. 수학적으로, 이 국소적으로 선형인 부피 변화는 |det(J(f-1(x)))|입니다. 여기서 J(f-1(x))은 역함수의 자코비안으로, 아까 dx / dy로 표현했던 값의 보다 고차원적 일반화입니다.

y = f(x)

p(y) = p(f -1(y)) · |detJ(f -1(y))|

logp(y) = logp(f -1(y)) + log|det(J(f -1(y)))|

제가 중고등학교에서 행렬식을 배웠을 때, 일견 작위적으로 보이는 행렬식의 정의가 상당히 헷갈렸었죠. 행렬식이 무엇을 의미하는지가 아니라 행렬식을 계산하는 방법만 배웠기 때문입니다: 행렬식은 주어진 변환의 선형화된 국소적 부피 변화율을 나타냅니다.

TensorFlow로 변환한 분포

TensorFlow는 확률 분포를 쉽게 변환할 수 있도록 간편한 API를 제공합니다. 변환 분포는 변환하고자 하는 베이스 분포 객체와, Bijector 객체로 특정지어지는데, 이 Bijector 객체는 (1) 정방향 변환 y = f(x), f: Rd → Rd (2) 역방향 변환 x = f-1(y),  그리고 (3) 자코비안의 로그 행렬식의 역 log|det(J(f -1(y)))|를 구현합니다. 앞으로는 이 값을 ILDJ (Inverse Log Determinant of the Jacobian)이라고 표기하겠습니다.

정방향 샘플링은 아주 간단하죠:

bijector.forward(base_dist.sample())

다음처럼 변환된 분포의 로그 밀도를 평가합니다.

distribution.log_prob(bijector.inverse(x)) + bijector.inverse_log_det_jacobian(x)

게다가 bijector.forward가 미분가능한 함수일 때,  Y = bijector.forward(x)는 샘플 x = base_distribution.sample()에 대해 재매개변수화가 가능한 분포입니다. 이는 정규화 플로우를 VAE에서 변분 사후 분포의 대체재로 (가우시안의 대안으로) 바로 사용할 수 있다는 의미지요.

사실 자주 사용되는 TensorFlow 분포 중 몇몇은 이러한 변환 분포를 사용하여 구현됩니다. 

소스 분포 Bijector.forward 변환 분포
Normal exp(x) LogNormal
Exp(rate=1) -log(x) Gumbel(0, 1)
Gumbel(0, 1) Softmax(x) Gumbel-Softmax/Concrete

위와 같은 표준 규칙에 따라 변환 분포는 Bijector-1BaseDistribution의 형태로 명명됩니다. 따라서 정규 분포에 적용된 ExpBijector는 LogNormal이 됩니다. 이러한 명명법의 예외사항도 있기는 합니다 – 굼벨-소프트맥스 분포는 굼벨 분포에 SoftmaxCentered bijector를 적용하는 RelaxedOneHotCategorical 분포로 구현됩니다.

정규화 플로우와 Flexible Bijectors를 학습시키기

Bijector를 한 개만 사용할 필요는 없겠죠? 신경망에서 레이어를 연쇄(chain)시키는 것 처럼, bijector를 연쇄할 수 있습니다 [3]. 이러한 구조가 바로 “정규화 플로우”죠. 또한, bijector가 bijector.log_prob에 대해서 수정가능한 파라미터를 가지고 있다면, bijector를 학습해서 베이스 분포를 임의의 밀도에 맞도록 변환할 수 있습니다. 각 bijector 함수가 학습가능한 “레이어”로 동작하므로, 변환 파라미터를 모델링 하려는 데이터 분포에 맞도록 옵티마이저를 사용하여 학습시킬 수 있습니다. 최대 우도 추정은 이를 수행하는 한가지 알고리즘으로 , 변환된 분포하에서 학습 데이터 포인트들이 최대 로그 확률을 갖도록 모델의 파라미터를 수정합니다. 확률이 아닌 로그 확률을 계산하고 최적화는 이유는 수치 안정성 때문입니다. 

다음 Shakir Mohamed와 Danilo Rezende의 UAW 발표(슬라이드)는 이러한 개념을 잘 보여줍니다:

shakir_danilo_slide.png

하지만 임의의 N x N 자코비안 행렬의 행렬식을 계산하는 것은 O(N3)의 복잡도를 갖습니다. 신경망에 넣기에는 너무 과하죠. 또한 임의의 근사 함수의 역을 구해야 하는 문제도 있습니다. 최근 대부분의 정규화 플로우  연구는, 계산 효율적인 ILDJ를 유지하면서도 정방향 역방향 계산시 GPU의 병렬 계산을 활용하는, 풍부한 표현력의 Bijectors을 어떻게 디자인할지에 집중되어있습니다.

코드 예시

100줄 정도의 TensorFlow 코드로 기초적인 정규화 플로우를 만들어봅시다. 이 코드 예시는 다음 라이브러리를 사용합니다:

  • TF Distributions – 텐서플로우에서 확률 분포를 컨트롤하기 위한 일반적인 API입니다. 이 튜토리얼은 TensorFlow r1.5 이상을 요합니다.
  • TF Bijector – 확률 분포에 대해 동작하는 오퍼레이터를 만들기 위한 일반적인 API입니다. 
  • Numpy, Matplotlib
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
tfd = tf.contrib.distributions
tfb = tfd.bijectors

모델링하려는 분포는 p(x1, x2)=N(x1|μ=1 / 4x22, σ=1) ⋅ N(x2|μ=0, σ=4). 다음의 코드로 타겟 분포에서 샘플을 생성할 수 있습니다 (Tensorflow 내에서 샘플을 생성하면 각 미니배치마다 샘플을 CPU에서 GPU로 옮기는 수고를 덜 수 있습니다):

batch_size=512
x2_dist = tfd.Normal(loc=0., scale=4.)
x2_samples = x2_dist.sample(batch_size)
x1 = tfd.Normal(loc=.25 * tf.square(x2_samples),
                scale=tf.ones(batch_size, dtype=tf.float32))
x1_samples = x1.sample()
x_samples = tf.stack([x1_samples, x2_samples], axis=1)<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>

ar_toy.png

베이스 분포로는 등방성 가우시안을 사용하겠습니다.

base_dist = tfd.MultivariateNormalDiag(loc=tf.zeros([2], tf.float32))

다음은 bijector를 만들어서 TransformedDistribution을 생성할겁니다. 보통의 완전 연결 신경망을 닮은 플로우를 만들어보죠. 비선형성과 행렬곱을 반복해서요.

아핀 함수의 자코비안을 구하는 것은 쉽지만, 행렬식은 O(n3)이 걸릴 수도 있죠, 너무 느려서 이걸 계산할 수는 없습니다. 대신, TensorFlow는 행렬식을 훨씬 효율적으로 계산할 수 있도록 설계된 아핀 변환을 제공합니다. 이 아핀 변환은 M에 저랭크 업데이트를 더하는 형식이죠:

M + V D VT

det(M + V D VT)을 경제적으로 구하기 위해서 행렬식 보조정리를 사용합니다.

다음으로는 선형적이지 않은 함수를 표현하기 위해서 가역 비선형성이 필요합니다 (아니면 아핀 bijector를 연쇄해도 아핀에 그치겠죠). 시그모이드 / 하이퍼탄젠트는 일견 좋아보이지만, 역함수를 구하기가 매우 불안정합니다: -1 혹은 1 근처에서 출력의 작은 변화가 입력에서 엄청난 변화에 대응되니까요. 제가 했던 실험에서는 포화되는 비선형성을 2개 이상 연쇄하면 항상 기울기가 폭발했습니다. 그에 반해 ReLU는 안정적이지만, x < 0구간에서는 역을 구할 수 없죠. 그래서 PReLU(매개변수화된 ReLU)를 사용하기로 했습니다. Leaky ReLU와 동일하지만 음수 영역에서 학습 가능한 기울기를 갖게 되죠.

 

# quite easy to interpret - multiplying by alpha causes a contraction in volume.
class LeakyReLU(tfb.Bijector):
    def __init__(self, alpha=0.5, validate_args=False, name="leaky_relu"):
        super(LeakyReLU, self).__init__(
            event_ndims=1, validate_args=validate_args, name=name)
    self.alpha = alpha

    def _forward(self, x):
        return tf.where(tf.greater_equal(x, 0), x, self.alpha * x)

    def _inverse(self, y):
        return tf.where(tf.greater_equal(y, 0), y, 1. / self.alpha * y)

    def _inverse_log_det_jacobian(self, y):
        event_dims = self._event_dims_tensor(y)
        I = tf.ones_like(y)
        J_inv = tf.where(tf.greater_equal(y, 0), I, 1.0 / self.alpha * I)
        # abs is actually redundant here, since this det Jacobian is &gt;<span id="mce_SELREST_end" style="overflow:hidden;line-height:0;"></span> 0
        log_abs_det_J_inv = tf.log(tf.abs(J_inv))
        return tf.reduce_sum(log_abs_det_J_inv, axis=event_dims)

PReLU는 성분 단위 변환이므로 자코비안이 대각선입니다. 대각 행렬의 행렬식은 대각 성분의 곱이므로, 로그 자코비안의 대각성분을 더해서 IDLJ를 구할 수 있고요 [4]. tfb.Chain()을 사용해서 “다층 퍼셉트론 Bijector”를 만들고, 이를 기저 분포에 적용하여 변환 분포를 만듭니다:

d, r = 2, 2
DTYPE = tf.float32
bijectors = []
num_layers = 6
for i in range(num_layers):
    with tf.variable_scope('bijector_%d' % i):
        V = tf.get_variable('V', [d, r], dtype=DTYPE)  # factor loading
        shift = tf.get_variable('shift', [d], dtype=DTYPE)  # affine shift
        L = tf.get_variable('L', [d * (d + 1) / 2],
                            dtype=DTYPE)  # lower triangular
        bijectors.append(tfb.Affine(
            scale_tril=tfd.fill_triangular(L),
            scale_perturb_factor=V,
            shift=shift,
        ))
        alpha = tf.abs(tf.get_variable('alpha', [], dtype=DTYPE)) + .01
        bijectors.append(LeakyReLU(alpha=alpha))
# Last layer is affine. Note that tfb.Chain takes a list of bijectors in the *reverse* order
# that they are applied.
mlp_bijector = tfb.Chain(
    list(reversed(bijectors[:-1])), name='2d_mlp_bijector')
dist = tfd.TransformedDistribution(
    distribution=base_dist,
    bijector=mlp_bijector
)

끝으로, 최대 우도 추정을 통해서 모델을 학습합니다: 우리 모델에 대해서 실제 데이터 분포에서 나온 샘플의 기대 로그 확률을 최대화하는 것이지요.

loss = -tf.reduce_mean(dist.log_prob(x_samples))
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
NUM_STEPS = int(1e5)
global_step = []
np_losses = []
for i in range(NUM_STEPS):
    _, np_loss = sess.run([train_op, loss])
    if i % 1000 == 0:
        global_step.append(i)
        np_losses.append(np_loss)
    if i % int(1e4) == 0:
        print(i, np_loss)

시작했던 사분면에 따라서 기저 분포로부터의 샘플의 색깔을 다르게 하여 공간이 (서서히) 왜곡되는 것을 시각화할 수 있습니다.

toy2d_flow.png

toy2d_out.png

이걸로 끝! TensorFlow distributions을 통해 깔끔하고 가독성 높은 코드로 자코비안의 행렬식을 구현하고 자동으로 축적할 수 있습니다. 이 포스트의 전체 소스코드는 깃헙에서 참고하세요.

아마 여기서 디포메이션이 꽤 느리고, 간단한 변환을 배울 때도 많은 수의 레이어가 필요하다는 점을 눈치채셨을 겁니다 [5]. 다음 포스트에서는 정규화 플로우를 학습하기 위한 보다 최신 기법들을 다루겠습니다.

감사의 말

정규화 플로우의 이해를 도와준 Dustin Tran에게, 이 포스트를 검수해준 Luke Metz, Katherine Lee, Samy Bengio에게, 그리고 코드 디버깅을 도와준 Ben Poole, Rif A. Saurous, Ian Langmore에게 깊은 감사를 전합니다. 다들 최고에요!

각주

[1] 한정된 데이터 셋으로부터의 *새로운* 정보로 데이터셋을 증강할 수 있다는 생각은 무척 꺼림칙하기도 합니다. 또한 통계적 머신러닝이 정말로 참된 생성 프로세스(예. 유체 역학 시뮬레이션)를 대체할 수 있는지, 실은 그것이 계산을 분할 상환하기에 효과적인 방법에 그쳐 트레이닝 /테스트 분포에서 얻어지는 일반화란 그저 운이 좋은 것에 불과한지는 두고봐야 알 일입니다.

[2] “그럴듯한” 이미지를 만드는데 있어서 높은 로그 우도가 필요하지도 충분하지도 않다는 논의는 생각할 거리를 던져줍니다. 이에 대해서는 생성 모델을 평가하는 법을 참조하세요. 그래도 로그 우도라는 잣대라도 있는 것이 아무것도 없는 것 보다는 낫지 않나요? 실제로 유용하게 쓰이는 진단 방법이기도 하고요.

[3] 정규화 플로우와 GAN 사이에는 밀접한 관계가 있습니다: 제너레이터의 역을 배우는 GAN의 인코더-디코더 구조가 바로 그것이죠 (ALI / BiGAN). 이 경우 독자적인 인코더가 X = G(u)를 만족시키는 u = G-1(X)를 수복하는 목적을 지니므로, 제너레이터는 간단한 균등 분포에 대한 플로우라고 생각할 수도 있죠. 하지만 X에 대해서 팽창/수축의 부피를 구할 수 없기 대문에 GAN에서는 밀도를 수복할 수가 없습니다. LDJ를 수치적으로 모델링하거나 구조적으로 선형-시간 자코비안을 강제하는 것이 아마 완전히 틀린 방법은 아닐테지만요.

[4]  “대각행렬의 행렬식은 대각선 성분의 곱이다”라는 보조 정리는 기하학적 관점에서 매우 직관적입니다: 각 차원의 길이 왜곡이 다른 차원에 대해서 독립적이기에, 총 부피 변화는 그저 각 방향에서의 변화의 곱이라는 것이죠. 고차원 직사각형 프리즘의 부피를 구하는 것과 마찬가지입니다.

[5] 각 아핀 변환이 2 x 2 행렬에 불과하고, PReLU가 기저 분포를 매우 느리게 “왜곡”하기 때문에 (그래서 데이터를 구부려 올바른 형태로 맞추기 위해서는 PReLU가 여러개 필요하죠), 이 다층 퍼셉트론은 수용력이 꽤나 한정적입니다. 사실 저차원 분포를 나타내기에 이 다층 퍼셉트론은 매우 좋지 못한 선택이지만, 여기서는 교육적 목적으로 사용했습니다.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s