Main Content

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

少ない労力でのラベル付き信号セットの反復的な作成

この例では、信号のラベル付けに伴う労力を軽減する、深層学習ベースの反復的なワークフローを紹介します。

信号データへのラベル付けは、多大な労力を必要とする手間と費用のかかるタスクです。この労力を軽減できれば、信号処理の問題に対する深層学習ソリューションの開発は、大幅に高速化される可能性があります。

信号データ セット内の関心領域に対するラベル付けというタスクを検討します。最初のアプローチでは、すべてのデータへのラベル付けを手作業で行います。このアプローチでは、多大な時間と労力が必要になります。この例で検証する代替手法では、ラベル付け処理を反復的に行います。各反復において、信号のサブセットがラベルの付いていないデータ セットから選択され、自動ラベル付け用に事前学習済みの深層ネットワークに送信されます。ラベル付け担当者は、結果として得られたラベルを調査し、正しくないラベルを修正します。検証済みのラベル付き信号が学習データ セットに追加され、拡張された学習データを使用して深層ネットワークに再学習させます。

それでもなお、ラベル付け担当者は、各反復において、ネットワークによってラベル付けされたすべての信号を確認および調査する必要があります。とはいえ、タスクは、ゼロからの信号へのラベル付けから、信頼性の高いネットワークが生成した不正確なラベルの修正に変化しています。後者のタスクで要求されるラベル付けの労力は、はるかに小さいものとなります。新しい反復ごとに、ネットワークに学習させるために使用するデータは多くなります。それにより、ネットワークの予測およびラベル付けのパフォーマンスが向上します。そのため、ラベル修正のために人が介入する必要性は、反復ごとに小さくなります。

この例では、深層学習を使用した波形セグメンテーションで示されている手続きに従って長短期記憶 (LSTM) ネットワークの学習を行います。LSTM ネットワークでは、ECG 信号サンプルを 3 つの関心領域のいずれかに属するものとして分類できます。

データ

この例では、一般に公開されている QT データベースのデータを使用した、ECG 信号領域のラベル付けを検討します [1] [2]。データは、合計 105 人の患者からの約 15 分間の ECG の記録で構成されています。各記録を取得するために、検査員は患者の胸部の異なる場所に 2 つの電極を配置して、2 チャネル信号にします。データベースは、自動化されたエキスパート システムによって生成される信号領域ラベルを提供します [3]。ラベルは、ECG 測定における P 波、R 波、および QRS 群領域の位置に対応します。105 個の 2 チャネルの ECG 信号から成る各チャネルは、自動化されたエキスパート システムによってそれぞれラベル付けされ、210 個の MAT ファイルに領域ラベルと共に保存された合計 210 個の ECG 信号に対して個別に扱われます。このファイルは、https://www.mathworks.com/supportfiles/SPT/data/QTDatabaseECGData1.zip で入手できます。

関数 downloadSupportFile を使用してデータセットをダウンロードします。

% Download the data
datasetZipFile = matlab.internal.examples.downloadSupportFile('SPT','data/QTDatabaseECGData1.zip');
datasetFolder = fullfile(fileparts(datasetZipFile),'QTDataset');
if ~exist(datasetFolder,'dir')     
     unzip(datasetZipFile,fileparts(datasetZipFile));
end

unzip 操作を実行すると、フォルダー datasetFolder が作成され、210 個の MAT ファイルが格納されます。各ファイルでは、変数 ecgSignal に ECG 信号が格納され、変数 signalRegionLabels に領域ラベルのテーブルが格納されます。また、各ファイルでは、変数 Fs に信号のサンプル レートが格納されます。この例では、信号のサンプル レートはすべて 250 Hz です。

ファイル内のデータにアクセスするための信号データストアを作成します。SignalVariableNames パラメーターを使用して、各ファイルから読み取りたい信号変数名を指定します。

sds = signalDatastore(datasetFolder,'SignalVariableNames',["ecgSignal","signalRegionLabels"]);

関数 read を呼び出すたびに、ECG 信号および領域ラベルのテーブルが格納された 2 要素 cell 配列がデータストアによって返されます。データストアの関数 preview を使用して、最初のファイルの内容が、長さ 225,000 サンプルの ECG 信号、および 3385 個の領域ラベルを含むテーブルであることを確認します。

data = preview(sds)
data=2×1 cell array
    {225000×1 double}
    {  3385×2 table }

領域ラベル テーブルの最初の数行を確認し、領域範囲のインデックスと領域クラスの値 (P、T、または QRS) が各行に含まれていることを確認します。

head(data{2})
    ROILimits     Value
    __________    _____

     83    117     P   
    130    153     QRS 
    201    246     T   
    285    319     P   
    332    357     QRS 
    412    457     T   
    477    507     P   
    524    547     QRS 

signalMask オブジェクトを使用して、最初の 1000 個のサンプルのラベルを可視化します。

MGroundTruth = signalMask(data{2});
plotsigroi(MGroundTruth,data{1}(1:1000))

sequence-to-sequence 分類を実行するために、関心領域ラベルを categorical シーケンスに変換して、深層ネットワークの学習を可能にします。データストアの関数 transform を使用して、信号データをディスクから読み取る際に変換を適用します。

numFiles = numel(sds.Files);
sds = transform(sds,@getmask);

信号とラベルのサイズを変更 (分割) して、長さ 5,000 のサンプルの複数のセグメントを取得し、フーリエ シンクロスクイーズド変換 (FSST) を使用して各 ECG セグメントを時間-周波数領域に変換します。

sds = transform(sds,@resizeData);
sdsFSST = transform(sds,@(x,fs)extractFSSTFeatures(x,250));

ファイルの 70% を学習に使用し、30% をテストに使用します。学習とテストの信号がランダムに選択されるよう、データセットをシャッフルします。

rng default
[trainIdx,~,testIdx] = dividerand(numFiles,0.7,0,0.3);

trainDs = subset(sds,trainIdx); % resized 5000 sample signals and labels
trainDsFSST = subset(sdsFSST,trainIdx); % FSST-transformed signals and labels

testDsFSST = subset(sdsFSST,testIdx);

データストアの readall メソッドを使用して、すべてのデータをメモリに読み取ります。このアクションによって各 ECG 信号を読み取り、上記すべての変換を適用して、フーリエ シンクロスクイーズド変換された複数の ECG セグメントを返します。UseParallel オプションを使用すると、Parallel Computing Toolbox™ がある場合には、お使いのコンピューターで利用可能なプロセッサを使用して、データセットを並列で変換します。

% Get FSST-transformed signals
ecgFSSTData = readall(trainDsFSST,UseParallel=true);
Starting parallel pool (parpool) using the 'local' profile ...
Connected to the parallel pool (number of workers: 8).
testFSSTData = readall(testDsFSST,UseParallel=true);

ecgFSST = ecgFSSTData(:,1);
ecgLabels = ecgFSSTData(:,2);

testECGFSST = testFSSTData(:,1);
testLabels = testFSSTData(:,2);

% Get time domain signal segments so that we can plot some labeling results
ecgData = readall(trainDs);
ecgSignals = ecgData(:,1);

この例では、深層ネットワークに反復的に学習させることにより、信号のラベル付けに伴う労力を軽減できることを示します。各反復では次が行われます。

  1. ネットワークが、以前にラベル付けされたフレームを使用して、ラベルが付いていないデータ フレームのサブセットにラベル付けします。

  2. ラベル付け担当者が、ラベル付けの誤りを手作業で修正します。

  3. 修正されたラベル付けが、以前にラベル付けされたフレームに追加されます。

  4. 拡張されたラベル付き信号のセットが、次回の反復に向けたネットワークの学習に使用されます。

定量的な比較を行うために、次の 2 つのシナリオをシミュレーションします。

  • 基本となるシナリオでは、人がゼロからデータセット全体にラベル付けを行い、完全にラベル付けされた ecgFrames セットを使用してネットワークに学習させます。

  • 2 番目のシナリオでは、ecgFrames データにラベルが付いておらず、そのラベル付けを反復的な方法で行うものとします。

完全にラベル付けされた ECG データ セットを使用した予測パフォーマンス

BiLSTM ネットワークを作成し、完全にラベル付けされた ecgFrames セットを使用してそのネットワークに学習させることにより、予測パフォーマンスの上限を得ます。上述のように、このアプローチではデータ セット全体に対して総当たり的なラベル付けが必要になり、必要な労力が最大となります。ラベル付けされた ecgFrames セットでネットワークに学習させ、テスト データ セットに対する予測精度を計算します。

ネットワーク アーキテクチャ

深層学習層を使用して BiLSTM ネットワークを作成します。

  • 信号の FSST に含まれる特徴の数 (周波数領域サンプルの総数。この例では 40) をサイズとして使用して、sequenceInputLayer を指定します。

  • 各信号サンプルにラベルが付けられているため、200 個の非表示ノードを使用して bilstmLayer を指定し、OutputModesequence に設定します。

  • P 波、QRS 群、T 波、および N/A という 4 つのカテゴリに対応する出力サイズ 4 を使用して fullyConnectedLayer を指定します。

  • softmaxLayerclassificationLayer を追加して、推定ラベルを出力します。

% Training with the full emulated unlabeled data set
layers = [ ...
    sequenceInputLayer(size(ecgFSST{1},1))
    bilstmLayer(200,'OutputMode','sequence')
    fullyConnectedLayer(4)
    softmaxLayer
    classificationLayer];

traningOptions で最適化ソルバーとハイパーパラメーターを指定して、ネットワークに学習させます。この例では、ADAM オプティマイザーと 50 のミニバッチ サイズを使用します。CPU または GPU を使用してネットワークに学習させます。GPU を使用するには Parallel Computing Toolbox™ が必要です。サポートされている GPU については、GPU 計算の要件 (Parallel Computing Toolbox)を参照してください。その他のパラメーターの詳細については、trainingOptions (Deep Learning Toolbox)を参照してください。この例では、'ExecutionEnvironment' の名前と値のペアを使用する学習に GPU を使用します。

options = trainingOptions('adam', ...
    'MaxEpochs',10, ...
    'MiniBatchSize',50, ...
    'ExecutionEnvironment','gpu', ...
    'InitialLearnRate',0.01, ...
    'LearnRateDropPeriod',6, ...
    'LearnRateSchedule','piecewise', ...
    'GradientThreshold',1, ...
    'Shuffle','every-epoch',...
    'Plots','training-progress',...
    'Verbose',0,...
    'DispatchInBackground',true);

完全にラベル付けされた ecgFrames データ セットを使用して、ネットワークの学習を行います。

baselineNet = trainNetwork(ecgFSST,ecgLabels,layers,options);

学習済みネットワークを使用してテスト フレームを分類し、平均予測精度を計算します。基準となる予測精度は約 90% です。

predictLabelsAll = classify(baselineNet,testECGFSST,'MiniBatchSize',50);
accuracyAll = mean(cellfun(@(x,y)mean(x==y),predictLabelsAll,testLabels));
fprintf('The baseline prediction accuracy is %2.1f%%.\n',accuracyAll*100);
The baseline prediction accuracy is 89.9%.

人によるループでの反復的なラベル付け

ラベル付けの労力を軽減するために、次の反復的なアプローチを試します。最初の時点では ecgFrames データ セットにラベルが付いておらず、データに手作業でラベル付けするものとします。実際には、この例では、データ セットが提供するグラウンド トゥルース ラベルを使用します。

初期ネットワークの学習

まず、ecgFrames セットから 25 個のフレームを選択し、それらに手作業でラベル付けします。反復処理の最初のステップとして、最初にラベル付けされたこのセットを使用して BiLSTM ネットワークの学習を行います。

numInitFrames = 25;

currentTrainingSet = ecgFSST(1:numInitFrames,1);
currentTrainingLabels = ecgLabels(1:numInitFrames);

最初の学習データ セットには 25 個のフレームしか存在しないため、学習エポックの回数を増やし、ミニバッチ サイズが小さくなるように学習オプションを設定します。

options = trainingOptions('adam', ...
    'MaxEpochs',20, ...
    'MiniBatchSize',5, ...
    'ExecutionEnvironment','gpu', ...
    'InitialLearnRate',0.01, ...
    'LearnRateDropPeriod',6, ...
    'LearnRateSchedule','piecewise', ...
    'GradientThreshold',1, ...
    'Shuffle','every-epoch', ...
    'Plots','none',...
    'Verbose',0,...
    'DispatchInBackground',true);

最初の学習データ セットで BiLSTM ネットワークに学習させ、パフォーマンスのベースラインの確立に使用されるのと同じテスト データ セットを使用してラベルを予測します。この最初のネットワークの予測精度は約 40% です。

initNet = trainNetwork(currentTrainingSet,currentTrainingLabels,layers,options);
initPrediction = classify(initNet,testECGFSST,'MiniBatchSize',50); 
initAccuracy = mean(cellfun(@(x,y)mean(x==y),initPrediction,testLabels));
fprintf('The prediction accuracy is %2.1f%%.\n',initAccuracy*100);
The prediction accuracy is 43.7%.

ラベル付け

次のステップでは、ecgFrames セットから 200 個の新しいデータ フレームを選択し、それらを事前学習済みのネットワーク initNet に供給することで、信号に自動でラベル付けします。

iteration = 1;
% Number of frames to label at each iteration
numFrames = 200; 
% Select the next set of frames to label
indexNext = numInitFrames+1:numInitFrames+numFrames;
% Use classify to label the new frames
currentPrediction = classify(initNet,ecgFSST(indexNext),'MiniBatchSize',50);

ネットワークが生成したラベル付けの結果を評価し、その結果をグラウンド トゥルースと比較します。このネットワークでパフォーマンスが最高および最低だったケースの ECG 信号のインデックスを見つけます。

errs = cellfun(@(x,y)sum(x~=y),ecgLabels(indexNext),currentPrediction);
[~,bestIndex] = min(errs);
[~,worstIndex] = max(errs);

最高のケースのシナリオでは、グラウンド トゥルース ラベルおよびネットワークが予測したラベルを重ね合わせた最初の 750 サンプルをプロットします。

ecgSignalOfInterest = ecgSignals{indexNext(bestIndex)};
groundTruthLabels = ecgLabels{indexNext(bestIndex)};
predictedLabels = currentPrediction{bestIndex};

MGroundTruth = signalMask(groundTruthLabels);
figure
plotsigroi(MGroundTruth,ecgSignalOfInterest(1:750))
title('Ground Truth - best-case scenario')

MPredicted = signalMask(predictedLabels);
figure
plotsigroi(MPredicted,ecgSignalOfInterest(1:750))
title('Labeling by Network - best-case scenario')

このフレームでは、ネットワークは良好なラベル付けを行いました。その結果、ネットワークの結果を検査する人は、予測されたラベルを少ない労力で修正できます。

ただし、ネットワークのラベル付けのパフォーマンスがそれほど高くない場合もあります。最低のケースのシナリオで得られた結果をプロットします。

ecgSignalOfInterest = ecgSignals{indexNext(worstIndex)};
groundTruthLabels = ecgLabels{indexNext(worstIndex)};
predictedLabels = currentPrediction{worstIndex};

MGroundTruth = signalMask(groundTruthLabels);
figure
plotsigroi(MGroundTruth,ecgSignalOfInterest(1:750))
title('Ground Truth - worst-case scenario')

MPredicted = signalMask(predictedLabels);
figure
plotsigroi(MPredicted,ecgSignalOfInterest(1:750))
title('Labeling by Network - worst-case scenario')

この信号に対するネットワークのパフォーマンスはそれほど良好ではありません。このケースでは、ラベル付け担当者が予測されたラベルにいくつかの修正を加えなければなりません。

200 個のデータ フレームに対する修正の労力を定量化するために、ネットワークのラベル付け誤り率、およびラベル付け担当者が修正しなければならないフレームあたりの平均サンプル数を計算します。

numSamplesPerFrame = 5000;
networkLabelingErrorRate(iteration) = 1-mean(cellfun(@(x,y)mean(x==y),currentPrediction,ecgLabels(indexNext)));
averageNumOfCorrectionsPerFrame(iteration) = networkLabelingErrorRate(iteration) * numSamplesPerFrame;
fprintf('The average number of corrections per frame is %2.1f.\n',averageNumOfCorrectionsPerFrame(iteration));
The average number of corrections per frame is 2211.3.

最初の反復では、フレームあたり平均で約 2,200 個のサンプルを人が修正しなければなりません。フレームあたりの修正サンプル数は、労力を表すのに便利なメトリクスです。とはいえ実際には、ラベル付け担当者が各サンプルのラベルを修正する必要はありません。ラベル付け担当者はその代わりに、領域の範囲の拡張または短縮だけは行う必要があります。

最初の反復の終わりでは、人が 200 個のフレームを検査し、値に誤りがあるラベルを修正することになります。ネットワークとラベル付け担当者の作業によって、反復の終了時、データ フレームには正しいラベルが付けられます。

次の反復では、新しくラベル付けされた 200 個のフレームを currentTrainingSet セットに追加して、ネットワークに再学習させ、ラベル付け反復を繰り返すことができます。次のチャートは、最初の反復後の各反復におけるワークフローを示しています。

ラベル付け反復の繰り返し

新たに修正されたラベル付きフレームを追加して学習セットを拡張し、ラベル付け対象となる別の 200 個のデータ フレームを選択し、パフォーマンスが満足できる結果になるまでラベル付け反復を繰り返します。

% Include the initial training set and the 200 newly labeled data frames
maxIter = 15;
indexTraining = 1:numInitFrames+numFrames;

networkAccuracy = zeros(1,15);
networkAccuracy(iteration) = initAccuracy;

options = trainingOptions('adam', ...
    'MaxEpochs',20, ...
    'MiniBatchSize',50, ...
    'ExecutionEnvironment','gpu', ...
    'InitialLearnRate',0.01, ...
    'LearnRateDropPeriod',6, ...
    'LearnRateSchedule','piecewise', ...
    'GradientThreshold',1, ...
    'Shuffle','every-epoch', ...
    'Plots','none', ...
    'Verbose',0);

for iteration = 2:maxIter
    % Extended training data set
    currentTrainingSet = ecgFSST(indexTraining,1);    
    % Emulate human correction by assigning ground-truth labels to the
    % extended training set
    currentTrainingLabels = ecgLabels(indexTraining);

    % Train network with extended training set
    currentNet = trainNetwork(currentTrainingSet,currentTrainingLabels,layers,options);
    
    % Predict labels for the test data set and calculate the accuracy to
    % compare to baseline performance
    currentTestSetPrediction = classify(currentNet,testECGFSST,'MiniBatchSize',50);
    networkAccuracy(iteration) = mean(cellfun(@(x,y)mean(x==y),currentTestSetPrediction,testLabels));
    
    % Get another numFrames data frames for human labeler
    indexNext = indexTraining(end)+1:indexTraining(end)+numFrames;
    
    % Measure average number of human corrections per frame in this iteration
    currentPrediction = classify(currentNet,ecgFSST(indexNext),'MiniBatchSize',50);
    networkLabelingErrorRate(iteration) = 1-mean(cellfun(@(x,y)mean(x==y),currentPrediction,ecgLabels(indexNext)));
    averageNumOfCorrectionsPerFrame(iteration) = networkLabelingErrorRate(iteration) * numSamplesPerFrame;
    
    indexTraining = 1:indexNext(end);
end

ラベル付けのパフォーマンス

ラベル付け反復を 15 回繰り返すと、currentTrainingSet には 2825 個のデータ フレームが存在するようになり、この個数は ecgDataset セット全体に含まれる 6543 個のデータ フレームの約半数に相当します。2825 個のフレームで学習を行ったネットワークの予測精度はすでに、基準となる精度に非常に近くなっています。

accuDiff = accuracyAll-networkAccuracy(end);
fprintf('The accuracy difference is %2.1f%%.\n',accuDiff*100);
The accuracy difference is 2.1%.

各反復における学習データ セットのサイズに関して、テスト データ セットに対するネットワークの予測精度をプロットします。完全にラベル付けされたデータ セットで取得した精度の上限を表示します。検証されるデータ フレーム数が増えれば、ネットワークの予測精度が向上します。

figure
examinedDataSize = 25:200:2825;
plot(examinedDataSize,networkAccuracy,'*-')
hold on
% Prediction accuracy upper bound
plot(examinedDataSize,ones(1,15)*accuracyAll,'r--')
grid on
xlabel('Training set size')
title('Accuracy for the test data set')
xlim([25 2825])
legend('Labeling Network','Upper Bound','Location','southeast')

反復が進むにつれて、学習データセットのサイズが大きくなるほど、フレームあたりの人による平均修正数が減少します。検証されてネットワークの学習に使用されるデータ フレームが増えれば、選択したフレームのラベル修正に必要な労力が軽減されます。

figure
plot(examinedDataSize,averageNumOfCorrectionsPerFrame,'*-')
grid on
xlabel('Training set size')
title('Average number of human corrections per frame')
xlim([25 2825])

15 回すべてのラベル付け反復を通じて、人による修正が必要な信号サンプルは、フレームあたり平均して約 700 個になります。前述のとおり、ラベル付けされた領域の修正は、実際には個々のサンプルのラベルを変更するのではなく、領域の範囲を人が拡張または短縮することによって行います。

fprintf('The average number of corrections per frame is %2.1f.\n',mean(averageNumOfCorrectionsPerFrame));
The average number of corrections per frame is 716.9.

まとめ

この例では、ECG データ セットの半分にラベル付けするだけで、完全にラベル付けされたデータ セットで深層ネットワークに学習させた場合と同様の予測精度が達成できることを示しました。推奨した反復ラベル付けワークフローを採用することにより、ラベル付け担当者による確認が必要なデータセットは半分に抑えられ、修正が必要な信号サンプルはフレームあたり平均して 700 個のみになります。一方で、総当たり的なラベル付けでは、データ セット内のすべてのフレームを確認し、すべてのサンプルに対してゼロからラベル付けしなければなりません。

参考文献

[1] Goldberger, Ary L., Luis A. N. Amaral, Leon Glass, Jeffery M. Hausdorff, Plamen Ch. Ivanov, Roger G. Mark, Joseph E. Mietus, George B. Moody, Chung-Kang Peng, and H. Eugene Stanley. "PhysioBank, PhysioToolkit, and PhysioNet: Components of a New Research Resource for Complex Physiologic Signals." Circulation. Vol. 101, Number 23, 2000, pp. e215–e220. [Circulation Electronic Pages; http://circ.ahajournals.org/content/101/23/e215.full].

[2] Laguna, Pablo, Roger G. Mark, Ary L. Goldberger, and George B. Moody. "A Database for Evaluation of Algorithms for Measurement of QT and Other Waveform Intervals in the ECG."Computers in Cardiology.Vol.24, 1997, pp. 673–676.

[3] Laguna, Pablo, Raimon Jané, and Pere Caminal. "Automatic detection of wave boundaries in multilead ECG signals: Validation with the CSE database." Computers and Biomedical Research.Vol. 27, Number 1, 1994, pp. 45–60.

参考

|