Main Content

심층 신경망 디자이너를 사용하여 시계열 전망 신경망 구축하기

이 예제에서는 심층 신경망 디자이너 앱을 사용하여, 시계열 데이터를 전망하는 간단한 장단기 기억(LSTM) 신경망을 만드는 방법을 보여줍니다.

LSTM 신경망은 루프를 사용하여 시간 스텝을 순회하고 순환 신경망(RNN) 상태를 업데이트하여 입력 데이터를 처리하는 RNN입니다. RNN 상태에는 모든 이전 시간 스텝에서 기억한 정보가 포함됩니다. LSTM 신경망을 사용하면 이전 시간 스텝을 입력값으로 사용해서 시계열 또는 시퀀스의 후속 값을 전망할 수 있습니다. 시계열을 전망하는 LSTM 신경망을 만들려면 심층 신경망 디자이너 앱을 사용하십시오.

다음 그림은 폐루프 예측을 사용하여 값을 전망한 시퀀스 예를 보여줍니다.

시퀀스 데이터 불러오기

WaveformData에서 예제 데이터를 불러옵니다. 이 데이터에 액세스하려면 예제를 라이브 스크립트로 여십시오. 파형 데이터 세트는 3개 채널로 이루어진 다양한 길이로 생성된 합성 파형 데이터를 포함합니다. 이 예제에서는 이전 시간 스텝에서 주어진 값으로 파형의 미래 값을 전망하는 LSTM 신경망을 훈련시킵니다.

load WaveformData

처음 몇 개 시퀀스의 크기를 확인합니다.

data(1:4)
ans=4×1 cell array
    {103×3 double}
    {136×3 double}
    {140×3 double}
    {124×3 double}

채널 개수를 확인합니다. LSTM 신경망을 훈련시키려면 각 시퀀스의 채널 개수가 같아야 합니다.

numChannels = size(data{1},2)
numChannels = 3

시퀀스 중 일부를 시각화합니다.

idx = 1;
numChannels = size(data{idx},2);

figure
stackedplot(data{idx},DisplayLabels="Channel " + (1:numChannels))

훈련을 위해 데이터 준비하기

시퀀스의 미래 시간 스텝 값을 전망하려면 목표값을 시간 스텝 하나만큼 값이 이동된 훈련 시퀀스로 지정하십시오. 훈련 시퀀스의 마지막 시간 스텝은 포함하지 마십시오. 즉, LSTM 신경망은 입력 시퀀스의 시간 스텝마다 다음 시간 스텝의 값을 예측하도록 학습합니다. 예측 변수는 최종 시간 스텝이 없는 훈련 시퀀스입니다.

numObservations = numel(data);
XData = cell(numObservations,1);
TData = cell(numObservations,1);
for n = 1:numObservations
    X = data{n};
    XData{n} = X(1:end-1,:);
    TData{n} = X(2:end,:);
end

데이터를 훈련 세트, 검증 세트, 테스트 세트로 분할합니다. 데이터의 80%는 훈련용, 10%는 검증용, 10%는 테스트용으로 사용합니다. 데이터를 분할하려면 이 예제에 지원 파일로 첨부된 trainingPartitions 함수를 사용합니다. 이 파일에 액세스하려면 이 예제를 라이브 스크립트로 여십시오.

[idxTrain,idxValidation,idxTest] = trainingPartitions(numObservations,[0.8 0.1 0.1]);

XTrain = XData(idxTrain);
TTrain = TData(idxTrain);

XValidation = XData(idxValidation);
TValidation = TData(idxValidation);

XTest = XData(idxTest);
TTest = TData(idxTest);

더 적합한 피팅을 위해, 그리고 훈련의 발산을 방지하기 위해 채널의 평균이 0, 분산이 1이 되도록 예측 변수와 목표값을 정규화합니다. 예측을 수행할 때는 훈련 데이터와 동일한 통계량을 사용하여 검증 데이터와 테스트 데이터도 정규화해야 합니다.

시퀀스에 대해 채널당 평균값과 표준편차 값을 계산합니다. 훈련 데이터에 대해 평균과 표준편차를 쉽게 계산하려면 cell2mat 함수를 사용하여 결합된 시퀀스를 포함하는 숫자형 배열을 만드십시오.

muX = mean(cell2mat(XTrain));
sigmaX = std(cell2mat(XTrain),0);

muT = mean(cell2mat(TTrain));
sigmaT = std(cell2mat(TTrain),0);

계산된 평균값과 표준편차 값을 사용하여 훈련 데이터를 정규화합니다.

for n = 1:numel(XTrain)
    XTrain{n} = (XTrain{n} - muX) ./ sigmaX;
    TTrain{n} = (TTrain{n} - muT) ./ sigmaT;
end

훈련 데이터에서 계산된 통계량을 사용하여 검증 데이터와 테스트 데이터를 정규화합니다.

for n = 1:numel(XValidation)
    XValidation{n} = (XValidation{n} - muX) ./ sigmaX;
    TValidation{n} = (TValidation{n} - muT) ./ sigmaT;
end

for n = 1:numel(XTest)
    XTest{n} = (XTest{n} - muX) ./ sigmaX;
    TTest{n} = (TTest{n} - muT) ./ sigmaT;
end

신경망 아키텍처 정의하기

신경망을 구축하기 위해 심층 신경망 디자이너 앱을 엽니다.

deepNetworkDesigner

시퀀스 신경망을 만들기 위해 시퀀스 신경망 섹션의 Sequence-to-Sequence 위에 커서를 올리고 열기를 클릭합니다.

이렇게 하면 시퀀스 분류 문제에 적합한 사전 작성된 신경망이 열립니다. 신경망에는 다음 계층이 포함되어 있습니다.

  • sequenceInputLayer

  • lstmLayer

  • dropoutLayer

  • fullyConnectedLayer

  • softmaxLayer

마지막 계층을 편집하여 분류 신경망을 시계열 전망에 적합한 신경망으로 변환할 수 있습니다. 먼저, 소프트맥스 계층을 삭제합니다.

다음으로, 계층의 속성을 조정하여 파형 데이터 세트에 적합하도록 만듭니다. 목표가 시계열의 미래 데이터 점을 전망하는 것이므로 출력 크기는 입력 크기와 같아야 합니다. 이 예제에서 입력 데이터에 3개의 입력 채널이 있으므로 신경망 출력값에도 3개의 출력 채널이 있어야 합니다.

시퀀스 입력 계층 input을 선택하고 InputSize를 3으로 설정합니다.

완전 연결 계층 fc를 선택하고 OutputSize를 3으로 설정합니다.

LSTM 계층은 128개의 은닉 유닛을 가집니다. 은닉 유닛의 개수는 계층에서 학습한 정보의 양을 결정합니다. 더 많은 은닉 유닛을 사용하면 더 정확한 결과를 얻을 수 있지만 훈련 데이터에 과적합을 초래할 가능성이 더 커집니다. 드롭아웃 계층은 계층에 대한 입력값을 무작위로 0으로 설정하고 훈련 반복 간에 신경망 아키텍처를 효과적으로 변경하기 때문에 과적합 방지에 도움이 됩니다. 드롭아웃 확률이 높을수록 정보가 손실되고 학습 과정이 느려지는 대신 모델 일반화가 향상될 수 있습니다.

신경망이 훈련 준비가 되었는지 확인하려면 분석을 클릭하십시오. 딥러닝 신경망 분석기에 보고되는 오류나 경고가 없으므로 신경망이 훈련할 준비가 된 것입니다. 신경망을 내보내기 위해 내보내기를 클릭합니다. 앱은 신경망을 변수 net_1에 저장합니다.

훈련 옵션 지정하기

훈련 옵션을 지정합니다. 옵션 중에서 선택하려면 경험적 분석이 필요합니다. 실험을 실행하여 다양한 훈련 옵션 구성을 살펴보려면 실험 관리자 앱을 사용합니다.

  • Adam 최적화를 사용하여 훈련시킵니다.

  • 훈련을 Epoch 200회 수행합니다. 크기가 큰 데이터 세트의 경우에는 양호한 피팅을 위해 이렇게 많은 Epoch 횟수만큼 훈련시키지 않아도 될 수 있습니다.

  • 각 미니 배치에서 시퀀스들이 같은 길이를 갖도록 왼쪽을 채웁니다. 왼쪽을 채우면 RNN이 시퀀스의 끝에서 채우기 값을 예측하는 것을 방지합니다.

  • 매 Epoch마다 데이터를 섞습니다.

  • 검증 데이터를 사용하여 과적합을 모니터링합니다.

  • RMS 오차를 모니터링합니다.

  • 훈련 진행 상황을 플롯으로 표시합니다.

  • 상세 출력값을 비활성화합니다.

options = trainingOptions("adam", ...
    MaxEpochs=200, ...
    SequencePaddingDirection="left", ...
    Shuffle="every-epoch", ...
    ValidationData={XValidation,TValidation}, ...
    Metrics="rmse", ...
    Plots="training-progress", ...
    Verbose=false);

신경망 훈련시키기

trainnet 함수를 사용하여 LSTM 신경망을 훈련시킵니다. 회귀의 경우 평균제곱오차 손실을 사용합니다. 기본적으로 trainnet 함수는 GPU를 사용할 수 있으면 GPU를 사용합니다. GPU를 사용하려면 Parallel Computing Toolbox™ 라이선스와 지원되는 GPU 장치가 필요합니다. 지원되는 장치에 대한 자세한 내용은 GPU 연산 요구 사항 (Parallel Computing Toolbox) 항목을 참조하십시오. GPU를 사용할 수 없는 경우, 함수는 CPU를 사용합니다. 실행 환경을 지정하려면 ExecutionEnvironment 훈련 옵션을 사용하십시오.

net = trainnet(XTrain,TTrain,net_1,"mse",options);

순환 신경망 테스트하기

minibatchpredict 함수를 사용하여 예측을 수행합니다. 기본적으로 minibatchpredict 함수는 GPU를 사용할 수 있으면 GPU를 사용합니다. 훈련에 사용된 것과 동일한 채우기 옵션을 사용하여 시퀀스를 채웁니다. 다양한 길이의 시퀀스를 포함하는 sequence-to-sequence 작업의 경우 UniformOutput 옵션을 false로 설정하여 예측값을 셀형 배열로 반환합니다.

YTest = minibatchpredict(net,XTest, ...
    SequencePaddingDirection="left", ...
    UniformOutput=false);

테스트 시퀀스마다 예측값과 목표값 사이의 RMSE(RMS 오차)를 계산합니다. RMSE를 계산하기 전에 채우기 값을 제거하여 무시합니다.

numObservationsTest = numel(XTest);

for n = 1:numObservationsTest
    
    T = TTest{n};
    sequenceLength = size(T,1);
    Y = YTest{n}(end-sequenceLength+1:end,:);

    err(n) = rmse(Y,T,"all");
end

오차를 히스토그램으로 시각화합니다. 값이 낮을수록 높은 정확도를 나타냅니다.

figure
histogram(err)
xlabel("RMSE")
ylabel("Frequency")

모든 테스트 관측값에 대해 평균 RMSE를 계산합니다.

mean(err,"all")
ans = single
    0.5304

미래의 시간 스텝 전망하기

입력 시계열 또는 시퀀스에 대해 미래의 여러 시간 스텝의 값을 전망하려면 predict 함수를 사용하여 한 번에 하나의 시간 스텝을 예측한 다음 각 예측에 대해 RNN 상태를 업데이트하십시오. 각 예측에서 직전의 예측을 함수의 입력값으로 사용합니다.

테스트 시퀀스 하나를 플롯으로 시각화합니다.

idx = 3;
X = XTest{idx};
T = TTest{idx};

figure
stackedplot(X,DisplayLabels="Channel " + (1:numChannels))
xlabel("Time Step")
title("Test Observation " + idx)

전망 방법에는 개루프 전망과 폐루프 전망의 두 가지 방법이 있습니다.

  • 개루프 전망 — 입력 데이터만 사용하여 시퀀스에서 다음 시간 스텝을 예측합니다. 후속 시간 스텝을 예측할 때는 데이터 소스에서 실제 값을 수집하여 입력값으로 사용합니다. 예를 들어, 시퀀스의 1부터 t-1까지의 시간 스텝에서 수집한 데이터를 사용하여 시간 스텝 t에서의 값을 예측한다고 해보겠습니다. 시간 스텝 t+1에서 예측을 수행하려면 시간 스텝 t에서의 실제 값이 기록될 때까지 기다렸다가 이 값을 입력값으로 사용하여 다음 예측을 수행합니다. RNN에 실제 값을 전달하여 다음 예측을 수행할 수 있다면 개루프 전망을 사용하십시오.

  • 폐루프 전망 — 이전 예측을 입력값으로 사용하여 시퀀스의 후속 시간 스텝을 예측합니다. 이 경우 모델은 예측을 수행하는 데 실제 값이 필요하지 않습니다. 예를 들어, 시퀀스의 1부터 t-1까지의 시간 스텝에서 수집된 데이터만 사용해서 t부터 t+k까지의 시간 스텝의 값을 예측한다고 해보겠습니다. 시간 스텝 i에 대해 예측을 수행하려면 시간 스텝 i-1에서 예측된 값을 입력값으로 사용합니다. 여러 개의 후속 시간 스텝을 전망해야 하거나 다음 예측을 수행하기 전에 RNN에 전달할 수 있는 실제 값이 없다면 폐루프 전망을 사용합니다.

개루프 전망

개루프 전망을 수행합니다.

resetState 함수로 먼저 상태를 재설정하여 RNN 상태를 초기화한 다음 입력 데이터의 처음 몇 개의 시간 스텝을 사용하여 초기 예측을 수행합니다. 입력 데이터의 처음 75개의 시간 스텝을 사용하여 RNN 상태를 업데이트합니다.

net = resetState(net);
offset = 75;
[Z,state] = predict(net,X(1:offset,:));
net.State = state;

예측을 더 전망하려면 시간 스텝을 루프를 사용해 순회하고 predict 함수를 사용하여 예측을 수행합니다. 각 예측 후에 RNN 상태를 업데이트합니다. 루프를 사용해 입력 데이터의 시간 스텝을 순회하여 테스트 관측값의 나머지 시간 스텝에 대해 값을 전망하고 이 값들을 RNN의 입력값으로 사용합니다. 초기 예측의 마지막 시간 스텝은 첫 번째로 전망된 시간 스텝입니다.

numTimeSteps = size(X,1);
numPredictionTimeSteps = numTimeSteps - offset;
Y = zeros(numPredictionTimeSteps,numChannels);
Y(1,:) = Z(end,:);

for t = 1:numPredictionTimeSteps-1
    Xt = X(offset+t,:);
    [Y(t+1,:),state] = predict(net,Xt);
    net.State = state;
end

예측값과 입력값을 비교합니다.

figure
t = tiledlayout(numChannels,1);
title(t,"Open Loop Forecasting")

for i = 1:numChannels
    nexttile
    plot(X(:,i))
    hold on
    plot(offset:numTimeSteps,[X(offset,i) Y(:,i)'],"--")
    ylabel("Channel " + i)
end

xlabel("Time Step")
nexttile(1)
legend(["Input" "Forecasted"])

폐루프 전망

폐루프 전망을 수행합니다.

resetState 함수로 먼저 상태를 재설정하여 RNN 상태를 초기화한 다음 입력 데이터의 처음 몇 개의 시간 스텝을 사용하여 초기 예측 Z를 수행합니다. 입력 데이터의 모든 시간 스텝을 사용하여 RNN 상태를 업데이트합니다.

net = resetState(net);
offset = size(X,1);
[Z,state] = predict(net,X(1:offset,:));
net.State = state;

예측을 더 전망하려면 시간 스텝을 루프를 사용해 순회하고 predict 함수를 사용하여 예측을 수행합니다. 각 예측 후에 RNN 상태를 업데이트합니다. 이전에 예측된 값을 RNN으로 반복적으로 전달하여 다음 200개의 시간 스텝을 전망합니다. RNN이 더 예측을 수행하기 위해 입력 데이터가 필요하지 않으므로 전망할 시간 스텝의 개수를 원하는 대로 지정할 수 있습니다. 초기 예측의 마지막 시간 스텝은 첫 번째로 전망된 시간 스텝입니다.

numPredictionTimeSteps = 200;
Y = zeros(numPredictionTimeSteps,numChannels);
Y(1,:) = Z(end,:);

for t = 2:numPredictionTimeSteps
    [Y(t,:),state] = predict(net,Y(t-1,:));
    net.State = state;
end

전망한 값을 플롯으로 시각화합니다.

numTimeSteps = offset + numPredictionTimeSteps;

figure
t = tiledlayout(numChannels,1);
title(t,"Closed Loop Forecasting")

for i = 1:numChannels
    nexttile
    plot(X(1:offset,i))
    hold on
    plot(offset:numTimeSteps,[X(offset,i) Y(:,i)'],"--")
    ylabel("Channel " + i)
end

xlabel("Time Step")
nexttile(1)
legend(["Input" "Forecasted"])

폐루프 전망은 임의의 시간 스텝 개수를 전망할 수 있지만 전망이 진행되는 동안 RNN이 실제 값을 얻을 수 없기 때문에 개루프 전망보다 정확도가 낮을 수 있습니다.

참고 항목

관련 항목