Main Content

설명 가능한 FCDD 신경망을 사용하여 영상 이상 검출하기

이 예제에서는 단일 클래스 FCDD(fully convolutional data description) 이상 감지 신경망을 사용하여 알약 영상에서 결함을 검출하는 방법을 보여줍니다.

이상 감지의 핵심적 목표는 훈련된 신경망이 영상을 이상 영상으로 분류한 이유를 관측자가 이해할 수 있도록 하는 것입니다. FCDD는 설명 가능한 분류를 수행하여 클래스 예측값에 대해 신경망이 이 분류 결정을 내리게 된 근거를 설명하는 정보를 제공합니다[1]. FCDD 신경망은 각 픽셀이 비정상일 확률과 함께 히트맵을 반환합니다. 분류기는 이상 점수 히트맵의 평균값에 따라 영상에 정상 또는 비정상으로 레이블을 지정합니다.

분류 데이터 세트에 사용할 알약 영상 다운로드하기

이 예제에서는 PillQC 데이터 세트를 사용합니다. 데이터 세트에는 3가지 클래스의 영상 즉, 결함이 없는 normal 영상, 알약에 흠집이 있는 chip 영상, 알약에 이물질이 묻은 dirt 영상이 포함되어 있습니다. 데이터 세트는 149개의 normal 영상, 43개의 chip 영상, 138개의 dirt 영상을 제공합니다. 데이터 세트의 크기는 3.57MB입니다.

dataDir을 원하는 데이터 세트 위치로 설정합니다. downloadPillQCData 헬퍼 함수를 사용하여 데이터 세트를 다운로드합니다. 이 함수는 예제에 지원 파일로 첨부되어 있습니다. 함수는 ZIP 파일을 다운로드한 후 데이터를 하위 디렉터리 chip, dirt, normal에 추출합니다.

dataDir = fullfile(tempdir,"PillDefects");
downloadPillQCData(dataDir)

다음 이미지는 각 클래스에 해당하는 영상의 예입니다. 왼쪽은 결함이 없는 정상 알약, 중간은 이물질로 오염된 알약, 오른쪽은 흠집이 있는 알약입니다. 이 데이터 세트의 영상에는 그림자가 있거나, 초점이 흐리거나, 배경색이 다른 경우들이 포함되어 있지만, 이 예제에서 사용하는 방법은 이러한 영상 수집 아티팩트에 대해 견고합니다.

montageImage.png

데이터를 불러오고 전처리하기

영상 데이터를 읽어오고 관리하는 imageDatastore를 만듭니다. 디렉터리 이름에 따라 각 영상의 레이블을 chip, dirt 또는 normal로 지정합니다.

imageDir = fullfile(dataDir,"pillQC-main","images");
imds = imageDatastore(imageDir,IncludeSubfolders=true,LabelSource="foldernames");

데이터를 훈련 세트, 보정 세트, 테스트 세트로 분할하기

splitAnomalyData 함수를 사용하여 훈련 세트, 보정 세트, 테스트 세트를 만듭니다. 이 예제에서는 이상값 노출을 사용하는 FCDD 접근 방식을 구현합니다. 이 방식에서 훈련 데이터는 주로 정상 영상으로 구성되고 소수의 비정상 영상이 추가됩니다. 이 모델은 주로 정상 장면의 샘플을 훈련하지만 그럼에도 불구하고 정상 장면과 비정상 장면을 구분하는 방법을 학습합니다.

훈련 데이터 세트에 정상 영상을 50% 할당하고 각 이상 클래스를 5%의 작은 비율로 할당합니다. 보정 세트에는 정상 영상을 10% 할당하고 각 이상 클래스를 20%를 할당합니다. 나머지 영상을 테스트 세트에 할당합니다.

normalTrainRatio  = 0.5;
anomalyTrainRatio = 0.05;
normalCalRatio  = 0.10;
anomalyCalRatio = 0.20;
normalTestRatio  = 1 - (normalTrainRatio + normalCalRatio);
anomalyTestRatio = 1 - (anomalyTrainRatio + anomalyCalRatio);

anomalyClasses = ["chip","dirt"];
[imdsTrain,imdsCal,imdsTest] = splitAnomalyData(imds,anomalyClasses, ...
    NormalLabelsRatio=[normalTrainRatio normalCalRatio normalTestRatio], ...
    AnomalyLabelsRatio=[anomalyTrainRatio anomalyCalRatio anomalyTestRatio]);
Splitting anomaly dataset
-------------------------
* Finalizing... Done.
* Number of files and proportions per class in all the datasets:

                     Input                  Train                Validation                Test        
              NumFiles     Ratio     NumFiles     Ratio      NumFiles     Ratio     NumFiles     Ratio 
              ___________________    ____________________    ___________________    ___________________

    chip         43        0.1303        2        0.02381        9       0.17647       32        0.1641
    dirt        138       0.41818        7       0.083333       28       0.54902      103       0.52821
    normal      149       0.45152       75        0.89286       14       0.27451       60       0.30769

추가로, 훈련 데이터를 하나는 정상 데이터만 포함하고 다른 하나는 이상 데이터만 포함하는 두 개의 데이터저장소로 분할합니다.

[imdsNormalTrain,imdsAnomalyTrain] = splitAnomalyData(imdsTrain,anomalyClasses, ...
    NormalLabelsRatio=[1 0 0],AnomalyLabelsRatio=[0 1 0],Verbose=false);

훈련 데이터 증대하기

transform 함수를 헬퍼 함수 augmentDataForPillAnomalyDetector로 지정된 사용자 지정 전처리 연산과 함께 사용하여 훈련 데이터를 증대합니다. 이 헬퍼 함수는 예제에 지원 파일로 첨부되어 있습니다.

augmentDataForPillAnomalyDetector 함수는 각 입력 영상에 무작위로 90도 회전과 가로 및 세로 반사를 적용합니다.

imdsNormalTrain = transform(imdsNormalTrain,@augmentDataForPillAnomalyDetector);
imdsAnomalyTrain = transform(imdsAnomalyTrain,@augmentDataForPillAnomalyDetector);

transform 함수를 addLabelData 헬퍼 함수로 지정된 연산과 함께 사용하여 이진 레이블을 보정 데이터 세트와 테스트 데이터 세트에 추가합니다. 이 헬퍼 함수는 이 예제의 끝에 정의되어 있으며, normal 클래스의 영상에는 이진 레이블 0을, chip 또는 dirt 클래스의 영상에는 이진 레이블 1을 할당합니다.

dsCal = transform(imdsCal,@addLabelData,IncludeInfo=true);
dsTest = transform(imdsTest,@addLabelData,IncludeInfo=true);

9개의 증대된 훈련 영상 샘플을 시각화합니다.

exampleData = readall(subset(imdsNormalTrain,1:9));
montage(exampleData(:,1));

FCDD 모델 만들기

이 예제는 FCDD(fully convolutional data description) 모델을 사용합니다[1]. FCDD의 기본 개념은 입력 영상의 각 영역에 이상 부분이 포함될 확률을 설명하는 이상 점수 맵을 생성하도록 신경망을 훈련시키는 것입니다.

pretrainedEncoderNetwork 함수는 ImageNet 사전 훈련 Inception-v3 신경망에서 사전 훈련 백본으로 사용할 처음 3개의 다운샘플링 단계를 반환합니다.

backbone = pretrainedEncoderNetwork("inceptionv3",3);

fcddAnomalyDetector 함수를 Inception-v3 백본과 함께 사용하여 FCDD 이상 감지기 신경망을 만듭니다.

net = fcddAnomalyDetector(backbone);

신경망을 훈련시키거나 사전 훈련된 신경망 다운로드하기

기본적으로 이 예제에서는 헬퍼 함수 downloadTrainedNetwork를 사용하여 사전 훈련된 버전의 FCDD 이상 감지기를 다운로드합니다. 헬퍼 함수는 이 예제에 지원 파일로 첨부되어 있습니다. 사전 훈련된 신경망을 사용하면 훈련이 완료될 때까지 기다리지 않고 전체 예제를 실행할 수 있습니다.

신경망을 훈련시키려면 다음 코드에서 doTraining 변수를 true로 설정하십시오. 필드에 값을 입력하여 numEpochs 훈련에 사용할 Epoch의 횟수를 지정합니다. trainFCDDAnomalyDetector 함수를 사용하여 모델을 훈련시킵니다.

가능한 경우 한 개 이상의 GPU에서 훈련을 진행합니다. GPU를 사용하려면 Parallel Computing Toolbox™와 CUDA® 지원 NVIDIA® GPU가 필요합니다. 자세한 내용은 GPU 연산 요구 사항 (Parallel Computing Toolbox) 항목을 참조하십시오. 훈련은 NVIDIA Titan RTX™에서 3분 정도 걸립니다.

doTraining = false;
numEpochs = 200;
if doTraining
    options = trainingOptions("adam", ...
        Shuffle="every-epoch",...
        MaxEpochs=numEpochs,InitialLearnRate=1e-4, ...
        MiniBatchSize=32,...
        BatchNormalizationStatistics="moving");
    detector = trainFCDDAnomalyDetector(imdsNormalTrain,imdsAnomalyTrain,net,options);
    modelDateTime = string(datetime("now",Format="yyyy-MM-dd-HH-mm-ss"));
    save(fullfile(dataDir,"trainedPillAnomalyDetector-"+modelDateTime+".mat"),"detector");
else
    trainedPillAnomalyDetectorNet_url = "https://ssd.mathworks.com/supportfiles/"+ ...
        "vision/data/trainedFCDDPillAnomalyDetectorSpkg.zip";
    downloadTrainedNetwork(trainedPillAnomalyDetectorNet_url,dataDir);
    load(fullfile(dataDir,"folderForSupportFilesInceptionModel", ...
        "trainedPillFCDDNet.mat"));
end

이상 임계값 설정하기

이상 감지기의 이상 점수 임계값을 선택합니다. 그러면 이상 감지기는 점수가 해당 임계값보다 높은지 낮은지에 따라 영상을 분류합니다. 이 예제에서는 정상 영상과 이상 영상을 모두 포함한 보정 데이터 세트를 사용하여 임계값을 선택합니다.

보정 세트의 각 영상에 대해 평균 이상 점수와 ground truth 레이블을 구합니다.

scores = predict(detector,dsCal);
labels = imdsCal.Labels ~= "normal";

정상 클래스와 이상 클래스에 대한 평균 이상 점수의 히스토그램을 플로팅합니다. 분포가 모델에서 예측한 이상 점수에 따라 잘 분리되었습니다.

numBins = 20;
[~,edges] = histcounts(scores,numBins);
figure
hold on
hNormal = histogram(scores(labels==0),edges);
hAnomaly = histogram(scores(labels==1),edges);
hold off
legend([hNormal,hAnomaly],"Normal","Anomaly")
xlabel("Mean Anomaly Score")
ylabel("Counts")

anomalyThreshold 함수를 사용하여 최적 이상 임계값을 계산합니다. 보정 데이터 세트에 대해 처음 두 개 입력 인수를 ground truth 레이블 labels와 예측 이상 점수 scores로 지정합니다. 참양성 이상 영상의 labels 값이 true이므로, 세 번째 입력 인수를 true로 지정합니다. anomalyThreshold 함수는 감지기의 최적 임계값과 ROC(수신자 조작 특성) 곡선(rocmetrics (Deep Learning Toolbox) 객체로 저장됨)을 반환합니다.

[thresh,roc] = anomalyThreshold(labels,scores,true);

이상 감지기의 Threshold 속성을 최적 값으로 설정합니다.

detector.Threshold = thresh;

rocmetricsplot (Deep Learning Toolbox) 객체 함수를 사용하여 ROC를 플로팅합니다. ROC 곡선은 가능한 임계값 범위에서의 분류기 성능을 나타냅니다. ROC 곡선의 각 점은 보정 세트 영상이 각기 다른 임계값을 사용하여 분류되었을 때의 거짓양성률(x 좌표)과 참양성률(y 좌표)을 나타냅니다. 파란색 실선은 ROC 곡선을 나타냅니다. 빨간색 파선은 50% 성공률에 해당하는 no-skill(분류 능력 없음) 분류기를 나타냅니다. ROC AUC(곡선 아래 면적) 메트릭은 분류기 성능을 나타내며, 완벽한 분류기에 해당하는 최대 ROC AUC는 1.0입니다.

plot(roc)
title("ROC AUC: "+ roc.AUC)

분류 모델 평가하기

테스트 세트의 각 영상을 정상 또는 이상 영상으로 분류합니다.

testSetOutputLabels = classify(detector,dsTest);

각 테스트 영상의 ground truth 레이블을 가져옵니다.

testSetTargetLabels = dsTest.UnderlyingDatastores{1}.Labels;

evaluateAnomalyDetection 함수로 성능 메트릭을 계산하여 이상 감지기를 평가합니다. 이 함수는 테스트 데이터 세트에 대해 감지기의 정확도, 정밀도, 민감도, 특이도를 평가하는 몇몇 메트릭을 계산합니다.

metrics = evaluateAnomalyDetection(testSetOutputLabels,testSetTargetLabels,anomalyClasses);
Evaluating anomaly detection results
------------------------------------
* Finalizing... Done.
* Data set metrics:

    GlobalAccuracy    MeanAccuracy    Precision    Recall     Specificity    F1Score    FalsePositiveRate    FalseNegativeRate
    ______________    ____________    _________    _______    ___________    _______    _________________    _________________

       0.96923          0.97778           1        0.95556         1         0.97727            0                0.044444     

metricsConfusionMatrix 속성은 테스트 세트에 대한 혼동행렬을 포함합니다. 혼동행렬을 추출하고 혼동 플롯을 표시합니다. 이 예제의 분류 모델은 매우 정확하며, 거짓양성과 거짓음성으로 예측하는 비율은 작습니다.

M = metrics.ConfusionMatrix{:,:};
confusionchart(M,["Normal","Anomaly"])
acc = sum(diag(M)) / sum(M,"all");
title("Accuracy: "+acc)

이 예제에서 dirtchip을 지정한 것처럼 여러 개의 이상 클래스 레이블을 지정하는 경우, evaluateAnomalyDetection 함수는 전체 데이터 세트와 각 이상 클래스에 대해 메트릭을 계산합니다. 클래스별 메트릭은 anomalyDetectionMetrics 객체 metricsClassMetrics 속성으로 반환됩니다.

metrics.ClassMetrics
ans=2×2 table
               Accuracy    AccuracyPerSubClass
               ________    ___________________

    Normal           1         {1×1 table}    
    Anomaly    0.95556         {2×1 table}    

metrics.ClassMetrics(2,"AccuracyPerSubClass").AccuracyPerSubClass{1}
ans=2×1 table
            AccuracyPerSubClass
            ___________________

    chip          0.84375      
    dirt          0.99029      

분류 결정 설명하기

이상 감지기에서 예측한 이상 히트맵을 사용하면 영상이 정상 또는 이상으로 분류된 이유를 설명하는 데 도움이 됩니다. 이 방법은 거짓음성과 거짓양성의 패턴을 식별하는 데 유용합니다. 이러한 패턴을 사용하여, 훈련 데이터의 클래스 밸런싱을 높이거나 신경망 성능을 개선하기 위한 전략을 파악할 수 있습니다.

이상 히트맵의 표시 범위 계산하기

정상 영상과 이상 영상을 포함하여 전체 보정 세트에서 관찰된 이상 점수 범위를 반영하는 표시 범위를 계산합니다. 영상들에 동일한 표시 범위를 사용하면 각 영상을 자체 최솟값과 최댓값으로 스케일링하는 것보다 훨씬 더 쉽게 영상을 비교할 수 있습니다. 이 예제의 모든 히트맵에 표시 범위를 적용합니다.

minMapVal = inf;
maxMapVal = -inf;
reset(dsCal)
while hasdata(dsCal)
    img = read(dsCal);
    map = anomalyMap(detector,img{1});
    minMapVal = min(min(map,[],"all"),minMapVal);
    maxMapVal = max(max(map,[],"all"),maxMapVal);
end
displayRange = [minMapVal,maxMapVal];

이상 영상의 히트맵 보기

올바로 분류된 이상 영상을 선택합니다. 다음은 참양성 분류에 해당하는 결과입니다. 영상을 표시합니다.

testSetAnomalyLabels = testSetTargetLabels ~= "normal";
idxTruePositive = find(testSetAnomalyLabels' & testSetOutputLabels,1,"last");
dsExample = subset(dsTest,idxTruePositive);
img = read(dsExample);
img = img{1};
map = anomalyMap(detector,img);
imshow(anomalyMapOverlay(img,map,MapRange=displayRange,Blend="equal"))

정상 영상의 히트맵 보기

올바로 분류된 정상 영상을 선택하고 표시합니다. 다음은 참음성 분류에 해당하는 결과입니다.

idxTrueNegative = find(~(testSetAnomalyLabels' | testSetOutputLabels));
dsExample = subset(dsTest,idxTrueNegative);
img = read(dsExample);
img = img{1};
map = anomalyMap(detector,img);
imshow(anomalyMapOverlay(img,map,MapRange=displayRange,Blend="equal"))

거짓음성 영상의 히트맵 보기

거짓음성은 알약에 비정상적인 결함이 있는 영상을 신경망에서 정상으로 분류한 경우입니다. 신경망에서 제공된 설명을 바탕으로 오분류에 대한 중요한 정보를 얻을 수 있습니다.

테스트 세트에서 거짓음성 영상을 찾습니다. transform 함수를 사용하여 거짓음성 영상의 히트맵 오버레이를 구합니다. 변환 연산은 anomalyMapOverlay 함수를 적용하여 테스트 세트에서 각 거짓음성에 대한 히트맵 오버레이를 구하는 익명 함수에 의해 지정됩니다.

falseNegativeIdx = find(testSetAnomalyLabels' & ~testSetOutputLabels);
if ~isempty(falseNegativeIdx)
    fnExamples = subset(dsTest,falseNegativeIdx);
    fnExamplesWithHeatmapOverlays = transform(fnExamples,@(x) {...
        anomalyMapOverlay(x{1},anomalyMap(detector,x{1}), ...
        MapRange=displayRange,Blend="equal")});
    fnExamples = readall(fnExamples);
    fnExamples = fnExamples(:,1);
    fnExamplesWithHeatmapOverlays = readall(fnExamplesWithHeatmapOverlays);
    montage(fnExamples)
    montage(fnExamplesWithHeatmapOverlays)
else
    disp("No false negatives detected.")
end

거짓양성 영상의 히트맵 보기

거짓양성은 알약에 비정상적인 결함이 없는 영상을 신경망에서 이상으로 분류한 경우입니다. 테스트 세트에서 거짓양성을 찾습니다. 신경망에서 제공된 설명을 바탕으로 오분류에 대한 중요한 정보를 얻을 수 있습니다. 예를 들어 이상 점수가 영상 배경을 국한하여 식별했다면 전처리 과정에서 배경을 억제하는 것을 고려할 수 있습니다.

falsePositiveIdx = find(~testSetAnomalyLabels' & testSetOutputLabels);
if ~isempty(falsePositiveIdx)
    fpExamples = subset(dsTest,falsePositiveIdx);
    fpExamplesWithHeatmapOverlays = transform(fpExamples,@(x) { ...
        anomalyMapOverlay(x{1},anomalyMap(detector,x{1}), ...
        MapRange=displayRange,Blend="equal")});
    fpExamples = readall(fpExamples);
    fpExamples = fpExamples(:,1);
    fpExamplesWithHeatmapOverlays = readall(fpExamplesWithHeatmapOverlays);
    montage(fpExamples)
    montage(fpExamplesWithHeatmapOverlays)
else
    disp("No false positives detected.")
end
No false positives detected.

지원 함수

addLabelData 헬퍼 함수는 data의 레이블 정보를 one-hot 형식으로 인코딩된 표현으로 생성합니다.

function [data,info] = addLabelData(data,info)
    if info.Label == categorical("normal")
        onehotencoding = 0;
    else
        onehotencoding = 1;
    end
    data = {data,onehotencoding};
end

참고 문헌

[1] Liznerski, Philipp, Lukas Ruff, Robert A. Vandermeulen, Billy Joe Franks, Marius Kloft, and Klaus-Robert Müller. "Explainable Deep One-Class Classification." Preprint, submitted March 18, 2021. https://arxiv.org/abs/2007.01760.

[2] Ruff, Lukas, Robert A. Vandermeulen, Billy Joe Franks, Klaus-Robert Müller, and Marius Kloft. "Rethinking Assumptions in Deep Anomaly Detection." Preprint, submitted May 30, 2020. https://arxiv.org/abs/2006.00339.

[3] Simonyan, Karen, and Andrew Zisserman. "Very Deep Convolutional Networks for Large-Scale Image Recognition." Preprint, submitted April 10, 2015. https://arxiv.org/abs/1409.1556.

[4] ImageNet. https://www.image-net.org.

참고 항목

| | | | | | | | | (Deep Learning Toolbox) | (Deep Learning Toolbox)

관련 예제

세부 정보