Main Content

변분 오토인코더(VAE)를 훈련시켜 영상 생성하기

이 예제에서는 딥러닝 변분 오토인코더(VAE)를 훈련시켜 영상을 생성하는 방법을 보여줍니다.

데이터 모음의 관측값을 강하게 표현하는 데이터를 생성하려면 변분 오토인코더를 사용하면 됩니다. 오토인코더는 입력값을 복제하도록 훈련시킨 모델 유형으로, 입력값을 더 낮은 차원의 공간으로 변환하고(인코딩 단계) 낮은 차원의 표현으로부터 입력값을 복원합니다(디코딩 단계).

다음 도식은 숫자 영상을 복원하는 오토인코더의 기본 구조를 보여줍니다.

변분 오토인코더를 사용하여 새 영상을 생성하려면 확률 벡터를 디코더에 입력합니다.

변분 오토인코더는 잠재 공간에 확률 분포를 적용하고 디코더 출력값의 분포가 관측된 데이터의 분포와 일치하도록 분포를 학습한다는 점에서 일반 오토인코더와 다릅니다. 특히, 잠재 출력값은 인코더로 학습된 분포에서 무작위로 샘플링됩니다.

이 예제에서는 MNIST 데이터셋 [1]을 사용합니다. 이 데이터셋에는 손으로 쓴 숫자 회색조 영상이 훈련용으로 60,000개, 테스트용으로 10,000개 들어 있습니다.

데이터 불러오기

http://yann.lecun.com/exdb/mnist/에서 훈련 MNIST 파일과 테스트 MNIST 파일을 다운로드하고, 이 예제에 지원 파일로 첨부되어 있는 processImagesMNIST 함수를 사용하여 영상을 추출합니다. 이 함수에 액세스하려면 이 예제를 라이브 스크립트로 여십시오. VAE는 레이블이 지정된 데이터가 필요하지 않습니다.

trainImagesFile = "train-images-idx3-ubyte.gz";
testImagesFile = "t10k-images-idx3-ubyte.gz";

XTrain = processImagesMNIST(trainImagesFile);
Read MNIST image data...
Number of images in the dataset:  60000 ...
XTest = processImagesMNIST(testImagesFile);
Read MNIST image data...
Number of images in the dataset:  10000 ...

신경망 아키텍처 정의하기

오토인코더에는 인코더와 디코더의 두 가지 부분이 있습니다. 인코더는 영상 입력값을 받아서 컨벌루션과 같은 일련의 다운샘플링 연산을 사용하여 잠재 벡터 표현을 출력합니다(인코딩). 이와 비슷하게 디코더는 잠재 벡터 표현을 입력값으로 받아 전치된 컨벌루션과 같은 일련의 업샘플링 연산을 사용하여 입력값을 복원합니다.

이 예제에서는 입력값 샘플링에 사용자 지정 계층 samplingLayer를 사용합니다. 이 계층에 액세스하려면 이 예제를 라이브 스크립트로 여십시오. 계층은 로그-분산 벡터 log(σ2)과 결합된 평균 벡터 μ를 입력값으로 받아 N(μi,σi2)에서 요소를 샘플링합니다. 계층은 로그-분산을 사용하여 훈련 과정을 수치적으로 더 안정되게 만듭니다.

인코더 신경망 아키텍처 정의하기

28×28×1 영상을 16×1 잠재 벡터로 다운샘플링하는 다음과 같은 인코더 신경망을 정의합니다.

  • 영상 입력값의 경우 훈련 데이터와 일치하는 입력 크기를 갖는 영상 입력 계층을 지정합니다. 데이터를 정규화하지 않습니다.

  • 입력값을 다운샘플링하기 위해 두 블록의 2차원 컨벌루션 계층과 ReLU 계층을 지정합니다.

  • 평균과 로그-분산이 결합된 벡터를 출력하기 위해 출력 채널의 개수가 잠재 채널의 개수의 2배인 완전 연결 계층을 지정합니다.

  • 통계량으로 지정된 인코딩을 샘플링하기 위해 사용자 지정 계층 samplingLayer를 사용하여 샘플링 계층을 포함시킵니다. 이 계층에 액세스하려면 이 예제를 라이브 스크립트로 여십시오.

numLatentChannels = 16;
imageSize = [28 28 1];

layersE = [
    imageInputLayer(imageSize,Normalization="none")
    convolution2dLayer(3,32,Padding="same",Stride=2)
    reluLayer
    convolution2dLayer(3,64,Padding="same",Stride=2)
    reluLayer
    fullyConnectedLayer(2*numLatentChannels)
    samplingLayer];

디코더 신경망 아키텍처 정의하기

16×1 잠재 벡터에서 28×28×1 영상을 복원하는 다음과 같은 디코더 신경망을 정의합니다.

  • 특징 벡터 입력값의 경우 입력 크기가 잠재 채널의 개수와 일치하는 특징 입력 계층을 지정합니다.

  • 사용자 지정 계층 projectAndReshapeLayer를 사용하여 잠재 입력값을 7×7×64 배열로 사영 및 형태 변경합니다. 이 계층은 이 예제에 지원 파일로 첨부되어 있습니다. 이 계층에 액세스하려면 예제를 라이브 스크립트로 여십시오. 사영 크기를 [7 7 64]로 지정합니다.

  • 입력값을 업샘플링하기 위해 두 블록의 전치된 컨벌루션 계층과 ReLU 계층을 지정합니다.

  • 크기가 28×28×1인 영상을 출력하기 위해 하나의 3×3 필터가 있는 전치된 컨벌루션 계층을 포함시킵니다.

  • 출력값을 [0,1] 범위의 값에 매핑하기 위해 시그모이드 활성화 계층을 포함시킵니다.

projectionSize = [7 7 64];
numInputChannels = size(imageSize,1);

layersD = [
    featureInputLayer(numLatentChannels)
    projectAndReshapeLayer(projectionSize)
    transposedConv2dLayer(3,64,Cropping="same",Stride=2)
    reluLayer
    transposedConv2dLayer(3,32,Cropping="same",Stride=2)
    reluLayer
    transposedConv2dLayer(3,numInputChannels,Cropping="same")
    sigmoidLayer];

사용자 지정 훈련 루프를 사용하여 두 신경망을 훈련시키고 자동 미분을 활성화하기 위해 계층 배열을 dlnetwork 객체로 변환합니다.

netE = dlnetwork(layersE);
netD = dlnetwork(layersD);

모델 손실 함수 정의하기

모델 손실과 학습 가능한 파라미터에 대한 손실 기울기를 반환하는 함수를 정의합니다.

modelLoss 함수(이 예제의 모델 손실 함수 섹션에 정의되어 있음)는 인코더 신경망과 디코더 신경망, 입력 데이터로 구성된 미니 배치를 입력값으로 받고, 신경망의 손실과 학습 가능한 파라미터에 대한 손실의 기울기를 반환합니다. 손실을 계산하기 위해 함수는 ELBOloss 함수(이 예제의 ELBO 손실 함수 섹션에 정의되어 있음)를 사용하며, 인코더에서 제공하는 평균 및 로그-분산 출력값을 입력값으로 받아 ELBO(Evidence Lower Bound) 손실을 계산하는 데 사용합니다.

훈련 옵션 지정하기

미니 배치 크기 128, 학습률 0.001로 Epoch 30회만큼 훈련합니다.

numEpochs = 30;
miniBatchSize = 128;
learnRate = 1e-3;

모델 훈련시키기

사용자 지정 훈련 루프를 사용하여 모델을 훈련시킵니다.

훈련시키는 동안 영상의 미니 배치를 처리하고 관리하는 minibatchqueue 객체를 만듭니다. 각 미니 배치에 대해 다음을 수행합니다.

  • 훈련 데이터를 배열 데이터저장소로 변환합니다. 네 번째 차원에 대해 반복하도록 지정합니다.

  • 사용자 지정 미니 배치 전처리 함수 preprocessMiniBatch(이 예제의 끝부분에서 정의되어 있음)를 사용하여 여러 관측값을 하나의 미니 배치로 결합합니다.

  • 각각 공간(spatial), 공간(spatial), 채널(channel), 배치(batch)를 뜻하는 차원 레이블 "SSCB"를 사용하여 영상 데이터의 형식을 지정합니다. 기본적으로 minibatchqueue 객체는 기본 유형 single을 사용하여 데이터를 dlarray 객체로 변환합니다.

  • 사용 가능한 GPU가 있으면 GPU에서 훈련시킵니다. 사용 가능한 GPU가 있으면 minibatchqueue 객체는 기본적으로 각 출력값을 gpuArray로 변환합니다. GPU를 사용하려면 Parallel Computing Toolbox™와 지원되는 GPU 장치가 필요합니다. 지원되는 장치에 대한 자세한 내용은 릴리스별 GPU 지원 (Parallel Computing Toolbox) 항목을 참조하십시오.

  • 모든 미니 배치가 동일한 크기가 되도록 부분 미니 배치는 모두 버립니다.

dsTrain = arrayDatastore(XTrain,IterationDimension=4);
numOutputs = 1;

mbq = minibatchqueue(dsTrain,numOutputs, ...
    MiniBatchSize = miniBatchSize, ...
    MiniBatchFcn=@preprocessMiniBatch, ...
    MiniBatchFormat="SSCB", ...
    PartialMiniBatch="discard");

Adam 솔버에 대한 파라미터를 초기화합니다.

trailingAvgE = [];
trailingAvgSqE = [];
trailingAvgD = [];
trailingAvgSqD = [];

훈련 진행 상황 모니터의 총 반복 횟수를 계산합니다.

numObservationsTrain = size(XTrain,4);
numIterationsPerEpoch = ceil(numObservationsTrain / miniBatchSize);
numIterations = numEpochs * numIterationsPerEpoch;

훈련 진행 상황 모니터를 초기화합니다. monitor 객체를 생성할 때 타이머가 시작되므로 이 객체를 훈련 루프와 가깝게 생성해야 합니다.

monitor = trainingProgressMonitor( ...
    Metrics="Loss", ...
    Info="Epoch", ...
    XLabel="Iteration");

사용자 지정 훈련 루프를 사용하여 신경망을 훈련시킵니다. 각 Epoch에 대해 데이터를 섞고 루프를 사용해 데이터의 미니 배치를 순회합니다. 각 미니 배치에 대해 다음을 수행합니다.

  • dlfeval 함수와 modelLoss 함수를 사용하여 모델 손실과 모델 기울기를 평가합니다.

  • adamupdate 함수를 사용하여 인코더와 디코더 신경망 파라미터를 업데이트합니다.

  • 훈련 과정을 표시합니다.

epoch = 0;
iteration = 0;

% Loop over epochs.
while epoch < numEpochs && ~monitor.Stop
    epoch = epoch + 1;

    % Shuffle data.
    shuffle(mbq);

    % Loop over mini-batches.
    while hasdata(mbq) && ~monitor.Stop
        iteration = iteration + 1;

        % Read mini-batch of data.
        X = next(mbq);

        % Evaluate loss and gradients.
        [loss,gradientsE,gradientsD] = dlfeval(@modelLoss,netE,netD,X);

        % Update learnable parameters.
        [netE,trailingAvgE,trailingAvgSqE] = adamupdate(netE, ...
            gradientsE,trailingAvgE,trailingAvgSqE,iteration,learnRate);

        [netD, trailingAvgD, trailingAvgSqD] = adamupdate(netD, ...
            gradientsD,trailingAvgD,trailingAvgSqD,iteration,learnRate);

        % Update the training progress monitor. 
        recordMetrics(monitor,iteration,Loss=loss);
        updateInfo(monitor,Epoch=epoch + " of " + numEpochs);
        monitor.Progress = 100*iteration/numIterations;
    end
end

신경망 테스트하기

훈련된 오토인코더를 홀드아웃 테스트 세트를 사용하여 테스트합니다. 훈련 데이터와 동일한 단계를 사용하여 데이터의 미니 배치 대기열을 만들되, 데이터의 부분 미니 배치를 버리지 않습니다.

dsTest = arrayDatastore(XTest,IterationDimension=4);
numOutputs = 1;

mbqTest = minibatchqueue(dsTest,numOutputs, ...
    MiniBatchSize = miniBatchSize, ...
    MiniBatchFcn=@preprocessMiniBatch, ...
    MiniBatchFormat="SSCB");

modelPredictions 함수를 사용하여 훈련된 오토인코더로 예측을 수행합니다.

YTest = modelPredictions(netE,netD,mbqTest);

테스트 영상과 복원된 영상의 평균 제곱 오차를 취하여 복원 오차를 시각화하고 히스토그램으로 시각화합니다.

err = mean((XTest-YTest).^2,[1 2 3]);
figure
histogram(err)
xlabel("Error")
ylabel("Frequency")
title("Test Data")

새 영상 생성하기

무작위로 샘플링된 영상 인코딩을 디코더에 통과시켜 새 영상의 배치를 생성합니다.

numImages = 64;

ZNew = randn(numLatentChannels,numImages);
ZNew = dlarray(ZNew,"CB");

YNew = predict(netD,ZNew);
YNew = extractdata(YNew);

Figure에 생성된 영상을 표시합니다.

figure
I = imtile(YNew);
imshow(I)
title("Generated Images")

VAE는 영상의 강한 특징 표현을 학습하였으며 훈련 데이터와 유사한 영상을 생성할 수 있습니다.

헬퍼 함수

모델 손실 함수

modelLoss 함수는 인코더 신경망과 디코더 신경망, 입력 데이터로 구성된 미니 배치를 입력값으로 받고, 신경망의 손실과 학습 가능한 파라미터에 대한 손실의 기울기를 반환합니다. 이 함수는 훈련 영상을 인코더에 통과시키고 결과 영상 인코딩을 디코더에 통과시킵니다. 손실을 계산하기 위해 이 함수는 인코더의 샘플링 계층에서 제공하는 평균과 로그-분산 통계량 출력값을 elboLoss 함수에 사용합니다.

function [loss,gradientsE,gradientsD] = modelLoss(netE,netD,X)

% Forward through encoder.
[Z,mu,logSigmaSq] = forward(netE,X);

% Forward through decoder.
Y = forward(netD,Z);

% Calculate loss and gradients.
loss = elboLoss(Y,X,mu,logSigmaSq);
[gradientsE,gradientsD] = dlgradient(loss,netE.Learnables,netD.Learnables);

end

ELBO 손실 함수

ELBOloss 함수는 인코더가 반환하는 평균과 로그-분산 출력값을 받아서 ELBO 손실을 계산하는 데 사용합니다. ELBO 손실은 다음과 같이 두 개별 손실 항의 합으로 정의됩니다.

ELBO loss=reconstruction loss+KL loss.

복원 손실은 다음과 같이 평균 제곱 오차(MSE)를 사용하여 디코더 출력값이 원래 입력값과 얼마나 비슷한지 측정합니다.

reconstruction loss=MSE(reconstructed image,input image).

KL 손실(Kullback–Leibler 발산)은 두 확률 분포 간의 차이를 측정합니다. 이 경우에 KL 손실을 최소화한다는 것은 학습된 평균 및 분산을 대상 분포(정규분포)의 평균 및 분산에 가능한 한 가까워지게 한다는 것을 의미합니다. 크기가 K인 잠재 차원에 대해 KL 손실은 다음과 같이 구합니다.

KL loss=-0.5i=1K(1+log(σi2)-μi2-σi2).

KL 손실 항을 포함하는 것의 실용적인 효과는 잠재 공간의 중앙을 중심으로 복원 손실로 인해 학습된 클러스터를 빽빽하게 채워 넣어 샘플을 추출할 연속 공간을 형성한다는 것입니다.

function loss = elboLoss(Y,T,mu,logSigmaSq)

% Reconstruction loss.
reconstructionLoss = mse(Y,T);

% KL divergence.
KL = -0.5 * sum(1 + logSigmaSq - mu.^2 - exp(logSigmaSq),1);
KL = mean(KL);

% Combined loss.
loss = reconstructionLoss + KL;

end

모델 예측 함수

modelPredictions 함수는 인코더와 디코더 network 객체, 입력 데이터의 minibatchqueuembq 를 입력값으로 받아서, minibatchqueue 객체의 모든 데이터에 대해 반복하면서 모델 예측을 계산합니다.

function Y = modelPredictions(netE,netD,mbq)

Y = [];

% Loop over mini-batches.
while hasdata(mbq)
    X = next(mbq);

    % Forward through encoder.
    Z = predict(netE,X);

    % Forward through dencoder.
    XGenerated = predict(netD,Z);

    % Extract and concatenate predictions.
    Y = cat(4,Y,extractdata(XGenerated));
end

end

미니 배치 전처리 함수

preprocessMiniBatch 함수는 네 번째 차원에 따라 입력값을 결합하여 예측 변수의 미니 배치를 전처리합니다.

function X = preprocessMiniBatch(dataX)

% Concatenate.
X = cat(4,dataX{:});

end

참고 문헌

  1. LeCun, Y., C. Cortes, and C. J. C. Burges. "The MNIST Database of Handwritten Digits." http://yann.lecun.com/exdb/mnist/.

참고 항목

| | | | | |

관련 항목