이 페이지의 최신 내용은 아직 번역되지 않았습니다. 최신 내용은 영문으로 볼 수 있습니다.

이미지 분류를 위해 잔차 네트워크 훈련시키기

이 예제에서는 잔차 연결을 사용하여 심층 학습 신경망을 만들고 CIFAR-10 데이터에 대해 훈련시키는 방법을 보여줍니다. 잔차 연결은 컨벌루션 신경망 아키텍처에서 자주 사용되는 요소입니다. 잔차 연결을 사용하면 네트워크의 기울기 흐름이 향상되고 보다 심층의 네트워크를 훈련시킬 수 있습니다.

많은 응용 사례에서는 계층들의 단순한 단일 시퀀스로 구성된 하나의 네트워크만 사용해도 충분합니다. 그러나 일부 응용 사례에서는 계층이 여러 계층으로부터 입력값을 받아 여러 계층으로 출력값을 보낼 수 있는 보다 복잡한 그래프 구조의 네트워크가 필요합니다. 이러한 유형의 네트워크를 종종 DAG(유방향 비순환 그래프) 네트워크라 부릅니다. 잔차 네트워크는 기본 네트워크 계층을 우회하는 잔차(또는 지름길) 네트워크를 갖는 DAG 네트워크의 일종입니다. 잔차 연결은 출력 계층에서 네트워크의 앞쪽 계층으로 파라미터 기울기가 보다 쉽게 전파되도록 해 주므로 보다 심층의 네트워크를 훈련시킬 수 있습니다. 이처럼 네트워크 심도가 증가하면 더욱 어려운 작업에서 정확도가 높아질 수 있습니다.

그래프 구조를 갖는 네트워크를 만들고 훈련시키려면 다음 단계를 수행하십시오.

  • layerGraph를 사용하여 LayerGraph 객체를 만듭니다. 계층 그래프는 네트워크 아키텍처를 지정합니다. 빈 계층 그래프를 만든 다음 계층을 추가할 수 있습니다. 네트워크 계층으로 구성된 배열에서 직접 계층 그래프를 만들 수도 있습니다. 이 경우 layerGraph는 배열에 있는 계층을 차례로 연결합니다.

  • addLayers를 사용하여 계층 그래프에 계층을 추가하고, removeLayers를 사용하여 그래프에서 계층을 제거합니다.

  • connectLayers를 사용하여 계층을 다른 계층에 연결하고, disconnectLayers를 사용하여 계층을 다른 계층으로부터 연결을 끊습니다.

  • plot을 사용하여 네트워크 아키텍처를 플로팅합니다.

  • trainNetwork를 사용하여 네트워크를 훈련시킵니다. 훈련된 네트워크는 DAGNetwork 객체가 됩니다.

  • classifypredict를 사용하여 새 데이터에 대해 분류와 예측을 수행합니다.

이미지 분류를 위해 사전 훈련된 네트워크를 불러올 수도 있습니다. 자세한 내용은 사전 훈련된 심층 신경망 항목을 참조하십시오.

데이터 준비하기

CIFAR-10 데이터 세트를 다운로드합니다[1]. 이 데이터 세트에는 60,000개의 이미지가 포함되어 있습니다. 각 이미지는 크기가 32x32이고 3개의 색 채널(RGB)을 갖습니다. 데이터 세트의 크기는 175MB입니다. 인터넷 연결에 따라 다운로드 과정에 약간의 시간이 걸릴 수 있습니다.

datadir = tempdir; 
downloadCIFARData(datadir);

CIFAR-10 훈련 및 테스트 이미지를 4차원 배열로 불러옵니다. 훈련 세트에는 50,000개의 이미지가 포함되어 있고, 테스트 세트에는 10,000개의 이미지가 포함되어 있습니다. 네트워크 검증을 위해 CIFAR-10 테스트 이미지를 사용합니다.

[XTrain,YTrain,XValidation,YValidation] = loadCIFARData(datadir);

훈련 이미지에서 임의의 샘플을 표시합니다.

figure;
idx = randperm(size(XTrain,4),20);
im = imtile(XTrain(:,:,:,idx),'ThumbnailSize',[96,96]);
imshow(im)

네트워크 훈련에 사용할 augmentedImageDatastore 객체를 만듭니다. 훈련 중에 데이터저장소는 세로 축을 따라 훈련 이미지를 무작위로 뒤집고 최대 4개의 픽셀을 가로와 세로 방향으로 무작위로 평행 이동합니다. 데이터 증대는 네트워크가 과적합되는 것을 방지하고 훈련 이미지의 정확한 세부 정보가 기억되지 않도록 하는 데 도움이 됩니다.

imageSize = [32 32 3];
pixelRange = [-4 4];
imageAugmenter = imageDataAugmenter( ...
    'RandXReflection',true, ...
    'RandXTranslation',pixelRange, ...
    'RandYTranslation',pixelRange);
augimdsTrain = augmentedImageDatastore(imageSize,XTrain,YTrain, ...
    'DataAugmentation',imageAugmenter, ...
    'OutputSizeMode','randcrop');

네트워크 아키텍처 정의하기

잔차 네트워크 아키텍처는 다음과 같은 구성요소로 이루어져 있습니다.

  • 컨벌루션 계층, 배치 정규화 계층, ReLU 계층이 순차적으로 연결된 기본 분기.

  • 기본 분기의 컨벌루션 유닛을 우회하는 잔차 연결. 잔차 연결의 출력값과 컨벌루션 유닛의 출력값은 요소별로 더해집니다. 활성화 값의 크기가 변경되면 잔차 연결 또한 1x1 컨벌루션 계층을 포함해야 합니다. 잔차 연결은 출력 계층에서 네트워크의 앞쪽 계층으로 파라미터 기울기가 더 쉽게 흐르도록 해 주므로 보다 심층의 네트워크를 훈련시킬 수 있습니다.

기본 분기 만들기

먼저 네트워크의 기본 분기를 만듭니다. 기본 분기는 5개의 섹션으로 구성됩니다.

  • 이미지 입력 계층 및 활성화 값이 있는 초기 컨벌루션을 포함하는 첫 번째 섹션.

  • 서로 다른 특징 크기(32x32, 16x16, 8x8)를 갖는 세 단계의 컨벌루션 계층. 각 단계는 N개의 컨벌루션 유닛을 포함합니다. 예제의 이 부분에서 N = 2입니다. 각 컨벌루션 유닛은 활성화 값이 있는 3x3 컨벌루션 계층 2개를 포함합니다. netWidth 파라미터는 네트워크 너비로, 네트워크의 첫 번째 단계에 있는 컨벌루션 계층의 필터 개수로 정의됩니다. 두 번째 단계와 세 번째 단계의 첫 번째 컨벌루션 유닛들은 공간 차원을 인자 2로 다운샘플링합니다. 전체 네트워크에서 각 컨벌루션 계층에 필요한 연산량이 대략 같도록 하려면 공간적 다운샘플링을 수행할 때마다 필터의 개수를 2배만큼 늘리십시오.

  • 전역 평균 풀링 계층, 완전 연결 계층, 완전 연결 계층, 소프트맥스 계층, 분류 계층을 갖는 마지막 섹션.

convolutionalUnit(numF,stride,tag)를 사용하여 컨벌루션 유닛을 만듭니다. numF는 각 계층에 있는 컨벌루션 필터의 개수이고, stride는 요소의 첫 번째 컨벌루션 계층의 스트라이드이고, tag는 계층 이름 앞에 추가할 문자형 배열입니다. convolutionalUnit 함수는 이 예제의 끝에서 정의됩니다.

모든 계층에 고유한 이름을 지정합니다. 컨벌루션 유닛의 계층은 'SjUk'로 시작하는 이름을 갖습니다. 여기서 j는 단계 인덱스이고 k는 그 단계 내에서의 컨벌루션 유닛의 인덱스입니다. 예를 들어, 'S2U1'은 단계 2, 유닛 1을 나타냅니다.

netWidth = 16;
layers = [
    imageInputLayer([32 32 3],'Name','input')
    convolution2dLayer(3,netWidth,'Padding','same','Name','convInp')
    batchNormalizationLayer('Name','BNInp')
    reluLayer('Name','reluInp')
    
    convolutionalUnit(netWidth,1,'S1U1')
    additionLayer(2,'Name','add11')
    reluLayer('Name','relu11')
    convolutionalUnit(netWidth,1,'S1U2')
    additionLayer(2,'Name','add12')
    reluLayer('Name','relu12')
    
    convolutionalUnit(2*netWidth,2,'S2U1')
    additionLayer(2,'Name','add21')
    reluLayer('Name','relu21')
    convolutionalUnit(2*netWidth,1,'S2U2')
    additionLayer(2,'Name','add22')
    reluLayer('Name','relu22')
    
    convolutionalUnit(4*netWidth,2,'S3U1')
    additionLayer(2,'Name','add31')
    reluLayer('Name','relu31')
    convolutionalUnit(4*netWidth,1,'S3U2')
    additionLayer(2,'Name','add32')
    reluLayer('Name','relu32')
    
    averagePooling2dLayer(8,'Name','globalPool')
    fullyConnectedLayer(10,'Name','fcFinal')
    softmaxLayer('Name','softmax')
    classificationLayer('Name','classoutput')
    ];

계층 배열에서 계층 그래프를 만듭니다. layerGraphlayers에 있는 모든 계층을 순차적으로 연결합니다. 계층 그래프를 플로팅합니다.

lgraph = layerGraph(layers);
figure('Units','normalized','Position',[0.2 0.2 0.6 0.6]);
plot(lgraph);

잔차 연결 만들기

컨벌루션 유닛 주위에 잔차 연결을 추가합니다. 대부분의 잔차 연결은 아무런 조작 없이 컨벌루션 유닛의 출력값에 요소별로 더해지기만 합니다.

'reluInp'에서 'add11' 계층으로 잔차 연결을 만듭니다. 계층을 만들 때 덧셈 계층의 입력값의 개수를 2로 지정했으므로 이 계층은 이름이 각각 'in1''in2'인 2개의 입력값을 갖습니다. 첫 번째 컨벌루션 유닛의 마지막 계층은 이미 'in1' 입력값에 연결되어 있습니다. 덧셈 계층은 첫 번째 컨벌루션 유닛과 'reluInp' 계층의 출력값을 더합니다.

마찬가지로, 'relu11' 계층을 'add12' 계층의 두 번째 입력값에 연결합니다. 계층 그래프를 플로팅하여 계층이 올바르게 연결되었는지 확인합니다.

lgraph = connectLayers(lgraph,'reluInp','add11/in2');
lgraph = connectLayers(lgraph,'relu11','add12/in2');

figure('Units','normalized','Position',[0.2 0.2 0.6 0.6]);
plot(lgraph);

컨벌루션 유닛에 있는 계층 활성화 값의 크기가 변경되면(즉, 공간적으로 다운샘플링되고 채널 차원에서 업샘플링되면) 잔차 연결에 있는 활성화 값의 크기도 변경되어야 합니다. 1x1 컨벌루션 계층을 배치 정규화 계층과 함께 사용하여 잔차 연결의 활성화 값 크기를 변경합니다.

skip1 = [
    convolution2dLayer(1,2*netWidth,'Stride',2,'Name','skipConv1')
    batchNormalizationLayer('Name','skipBN1')];
lgraph = addLayers(lgraph,skip1);
lgraph = connectLayers(lgraph,'relu12','skipConv1');
lgraph = connectLayers(lgraph,'skipBN1','add21/in2');

네트워크의 두 번째 단계에 항등 연결을 추가합니다.

lgraph = connectLayers(lgraph,'relu21','add22/in2');

또 다른 1x1 컨벌루션 계층과 배치 정규화 계층을 함께 사용하여 두 번째 단계와 세 번째 단계 사이의 잔차 연결의 활성화 값 크기를 변경합니다.

skip2 = [
    convolution2dLayer(1,4*netWidth,'Stride',2,'Name','skipConv2')
    batchNormalizationLayer('Name','skipBN2')];
lgraph = addLayers(lgraph,skip2);
lgraph = connectLayers(lgraph,'relu22','skipConv2');
lgraph = connectLayers(lgraph,'skipBN2','add31/in2');

마지막 항등 연결을 추가하고 최종 계층 그래프를 플로팅합니다.

lgraph = connectLayers(lgraph,'relu31','add32/in2');

figure('Units','normalized','Position',[0.2 0.2 0.6 0.6]);
plot(lgraph)

보다 심층의 네트워크 만들기

임의의 심도와 너비를 갖는 CIFAR-10 데이터에 대해 잔차 연결이 있는 계층 그래프를 만들려면 지원 함수 residualCIFARlgraph를 사용하십시오.

lgraph = residualCIFARlgraph(netWidth,numUnits,unitType)은 CIFAR-10 데이터에 대해 잔차 연결이 있는 계층 그래프를 만듭니다.

  • netWidth는 네트워크 너비로, 네트워크의 첫 번째 3x3 컨벌루션 계층에 있는 필터의 개수로 정의됩니다.

  • numUnits는 네트워크의 기본 분기에 있는 컨벌루션 유닛의 개수입니다. 네트워크는 각 단계가 모두 동일한 개수의 컨벌루션 유닛을 갖는 3개의 단계로 이루어져 있으므로 numUnits는 3의 정수 배수가 되어야 합니다.

  • unitType은 컨벌루션 유닛의 유형으로, "standard" 또는 "bottleneck"으로 지정됩니다. 표준 컨벌루션 유닛은 3x3 컨벌루션 계층 2개로 이루어집니다. 병목 지점 컨벌루션 유닛은 채널 차원의 다운샘플링을 위한 1x1 계층 1개, 3x3 컨벌루션 계층 1개, 채널 차원의 업샘플링을 위한 1x1 계층 1개, 총 3개의 컨벌루션 계층으로 이루어집니다. 따라서 병목 지점 컨벌루션 유닛은 표준 유닛보다 컨벌루션 계층이 50% 많지만 공간 3x3 컨벌루션의 개수는 절반밖에 되지 않습니다. 이러한 두 가지 유닛 유형은 연산 복잡도가 비슷하지만, 병목 지점 유닛을 사용할 때 잔차 연결에서 전파되는 특징의 총 개수는 4배가 많습니다. 순차 컨벌루션 계층과 완전 연결 계층의 최대 개수로 정의되는 총 심도는 표준 유닛을 갖는 네트워크의 경우 2*numUnits + 2이고 병목 지점 유닛을 갖는 네트워크의 경우 3*numUnits + 2입니다.

표준 컨벌루션 유닛이 9개이고(단계 하나당 요소 3개) 너비가 16인 잔차 네트워크를 만듭니다. 네트워크의 총 심도는 2*9+2 = 20이 됩니다.

numUnits = 9;
netWidth = 16;
lgraph = residualCIFARlgraph(netWidth,numUnits,"standard");
figure('Units','normalized','Position',[0.1 0.1 0.8 0.8]);
plot(lgraph)

네트워크 훈련시키기

훈련 옵션을 지정합니다. Epoch 80회에 대해 네트워크를 훈련시킵니다. 미니 배치 크기에 비례하는 학습률을 선택하고, Epoch 60회가 지나면 학습률을 10배만큼 줄입니다. 검증 데이터를 사용하여 Epoch당 한 번씩 네트워크를 검증합니다.

miniBatchSize = 128;
learnRate = 0.1*miniBatchSize/128;
valFrequency = floor(size(XTrain,4)/miniBatchSize);
options = trainingOptions('sgdm', ...
    'InitialLearnRate',learnRate, ...
    'MaxEpochs',80, ...
    'MiniBatchSize',miniBatchSize, ...
    'VerboseFrequency',valFrequency, ...
    'Shuffle','every-epoch', ...
    'Plots','training-progress', ...
    'Verbose',false, ...
    'ValidationData',{XValidation,YValidation}, ...
    'ValidationFrequency',valFrequency, ...
    'LearnRateSchedule','piecewise', ...
    'LearnRateDropFactor',0.1, ...
    'LearnRateDropPeriod',60);

trainNetwork를 사용하여 네트워크를 훈련시키려면 doTraining 플래그를 true로 설정하십시오. 그렇지 않으면 사전 훈련된 네트워크를 불러오십시오. 성능 좋은 GPU에서 네트워크를 훈련시키는 데는 약 2시간이 걸립니다. GPU가 없으면 훈련 시간이 훨씬 더 많이 걸립니다.

doTraining = false;
if doTraining
    trainedNet = trainNetwork(augimdsTrain,lgraph,options);
else
    load('CIFARNet-20-16.mat','trainedNet');
end

훈련된 네트워크 평가하기

(데이터 증대가 적용되지 않은) 훈련 세트와 검증 세트에 대해 네트워크의 최종 정확도를 계산합니다.

[YValPred,probs] = classify(trainedNet,XValidation);
validationError = mean(YValPred ~= YValidation);
YTrainPred = classify(trainedNet,XTrain);
trainError = mean(YTrainPred ~= YTrain);
disp("Training error: " + trainError*100 + "%")
Training error: 2.862%
disp("Validation error: " + validationError*100 + "%")
Validation error: 9.76%

정오분류표를 플로팅합니다. 열 및 행 요약을 사용하여 각 클래스의 정밀도를 표시하고 다시 호출합니다. 네트워크는 고양이와 개를 가장 많이 혼동하고 있습니다.

figure('Units','normalized','Position',[0.2 0.2 0.4 0.4]);
cm = confusionchart(YValidation,YValPred);
cm.Title = 'Confusion Matrix for Validation Data';
cm.ColumnSummary = 'column-normalized';
cm.RowSummary = 'row-normalized';

테스트 이미지 9개의 무작위 샘플을 예측된 클래스 및 해당 클래스의 확률과 함께 표시합니다.

figure
idx = randperm(size(XValidation,4),9);
for i = 1:numel(idx)
    subplot(3,3,i)
    imshow(XValidation(:,:,:,idx(i)));
    prob = num2str(100*max(probs(idx(i),:)),3);
    predClass = char(YValPred(idx(i)));
    title([predClass,', ',prob,'%'])
end

convolutionalUnit(numF,stride,tag)는 2개의 컨벌루션 계층과 여기에 대응하는 배치 정규화 계층 및 ReLU 계층으로 구성된 계층 배열을 만듭니다. numF는 컨벌루션 필터의 개수이고, stride는 첫 번째 컨벌루션 계층의 스트라이드이고, tag는 모든 계층 이름의 앞에 추가되는 태그입니다.

function layers = convolutionalUnit(numF,stride,tag)
layers = [
    convolution2dLayer(3,numF,'Padding','same','Stride',stride,'Name',[tag,'conv1'])
    batchNormalizationLayer('Name',[tag,'BN1'])
    reluLayer('Name',[tag,'relu1'])
    convolution2dLayer(3,numF,'Padding','same','Name',[tag,'conv2'])
    batchNormalizationLayer('Name',[tag,'BN2'])];
end

참고 문헌

[1] Krizhevsky, Alex. "Learning multiple layers of features from tiny images." (2009). https://www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf

[2] He, Kaiming, Xiangyu Zhang, Shaoqing Ren, and Jian Sun. "Deep residual learning for image recognition." In Proceedings of the IEEE conference on computer vision and pattern recognition, pp. 770-778. 2016.

참고 항목

| | |

관련 항목