Main Content

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

条件付き GAN を使用した合成信号の生成

この例では、条件付き敵対的生成ネットワークを使用して合成ポンプ信号を生成する方法を説明します。

敵対的生成ネットワーク (GAN) を使用して、ネットワークへの実際のデータ入力に似た合成データを生成できます。GAN は、シミュレーションの計算量が多い場合や実験のコストが高い場合に便利です。条件付き GAN (CGAN) では、学習プロセス中にデータ ラベルを使用して、特定カテゴリに属するデータを生成できます。

この例では、ポンプの Simulink™ モデルで得られるシミュレートされた信号を、CGAN 用の学習データ セットの役割を果たす "実" データとして扱います。CGAN では 1-D 畳み込みネットワークを使用し、カスタム学習ループと深層学習配列を使って学習が行われます。また、この例では主成分分析 (PCA) を使用して、生成信号と実信号の特性を視覚的に比較します。

信号合成用の CGAN

CGAN は、敵対者として一緒に学習を行う 2 つのネットワークで構成されています。

  1. ジェネレーター ネットワーク — このネットワークは、ラベルと乱数の配列を入力として与えられ、同じラベルに対応する学習データの観測値と同じ構造をもつデータを生成します。ジェネレーターの目的は、ディスクリミネーターが "実データ" として分類する、ラベル付きのデータを生成することです。

  2. ディスクリミネーター ネットワーク — このネットワークは、学習データとジェネレーターからの生成データの両方の観測値を含むラベル付きデータのバッチが与えられ、観測値が "実データ" か "生成データ" かの分類を試みます。ディスクリミネーターの目的は、ラベル付き実データとラベル付き生成データの両方のバッチが与えられたときに、ジェネレーターによって "騙され" ないことです。

この方法の理想的な結果は、入力ラベルごとにいかにも本物らしいデータをジェネレーターに生成させ、ラベルごとの学習データの特性を表す強い特徴をディスクリミネーターに学習させることです。

データの読み込み

シミュレーション データは、シミュレーション データを使用した複数クラス故障検出の例に提示されているポンプの Simulink モデルによって生成されます。この Simulink モデルは、シリンダーの漏れ、吸込口の閉塞、ベアリング摩擦の増加という 3 つのタイプの故障をモデル化するよう構成されています。データ セットには 1575 個のポンプ出力流量信号が含まれています。そのうち 760 個は健全状態の信号であり、815 個は 1 つの故障、2 つの故障の組み合わせ、または 3 つの故障の組み合わせです。各信号には、サンプル レート 1000 Hz の 1201 個の信号サンプルがあります。

MATLAB® の tempdir コマンドによって指定された場所にある一時ディレクトリに、データをダウンロードして解凍します。tempdir で指定されるものとは別のフォルダーにデータがある場合は、以下のコードでディレクトリ名を変更してください。

% Download the data
dataURL = 'https://ssd.mathworks.com/supportfiles/SPT/data/PumpSignalGAN.zip';
saveFolder = fullfile(tempdir,'PumpSignalGAN'); 
zipFile = fullfile(tempdir,'PumpSignalGAN.zip');
if ~exist(fullfile(saveFolder,'simulatedDataset.mat'),'file')
    websave(zipFile,dataURL);
    % Unzip the data
    unzip(zipFile,saveFolder)
end

zip ファイルには、次の学習データ セットと事前学習済みの CGAN が含まれています。

  • simulatedDataset — シミュレートされた信号と、それらに対応する categorical ラベル

  • GANModel — シミュレーション データで学習させたジェネレーターとディスクリミネーター

学習データ セットを読み込み、ゼロ平均および単位分散をもつように信号を標準化します。

load(fullfile(saveFolder,'simulatedDataset.mat')) % load data set
meanFlow = mean(flow,2);
flowNormalized = flow-meanFlow;
stdFlow = std(flowNormalized(:));
flowNormalized = flowNormalized/stdFlow;

健全状態の信号には 1 のラベルが付けられ、故障状態の信号には 2 のラベルが付けられます。

ジェネレーター ネットワークの定義

乱数値からなる 1 x 1 x 100 の配列とそれに対応するラベルが与えられて流量信号を生成する、次の 2 入力ネットワークを定義します。

ネットワークは以下を行います。

  • カスタム層によって、1 x 1 x 100 のノイズの配列を 4 x 1 x 1024 の配列に投影および形状変更する。

  • categorical ラベルを埋め込みベクトルに変換し、それらを 4 x 1 x 1 の配列に形状変更する。

  • 2 つの入力からの結果を、チャネル次元に沿って連結する。出力は 4 x 1 x 1025 の配列になります。

  • バッチ正規化層と ReLU 層をもつ一連の 1-D 転置畳み込み層を使用して、結果の配列を 1201 x 1 x 1 の配列にアップサンプルする。

ノイズ入力を投影して形状変更するには、この例にサポート ファイルとして添付されているカスタム層 projectAndReshapeLayer を使用します。projectAndReshapeLayer オブジェクトは、全結合層を使用して入力をアップスケールし、出力を指定のサイズに形状変更します。

ラベルをネットワークに入力するには、imageInputLayer オブジェクトを使用して、1 行 1 列のサイズを指定します。ラベル入力を埋め込んで形状変更するには、この例にサポート ファイルとして添付されているカスタム層 embedAndReshapeLayer を使用します。embedAndReshapeLayer オブジェクトは、埋め込みと全結合演算を使用して、categorical ラベルを指定サイズの 1 チャネル配列に変換します。categorical 入力では、埋め込み次元 100 を使用します。

% Generator Network

numFilters = 64;
numLatentInputs = 100;
projectionSize = [4 1 1024];
numClasses = 2;
embeddingDimension = 100;

layersGenerator = [
    imageInputLayer([1 1 numLatentInputs],'Normalization','none','Name','in')
    projectAndReshapeLayer(projectionSize,numLatentInputs,'proj');
    concatenationLayer(3,2,'Name','cat');
    transposedConv2dLayer([5 1],8*numFilters,'Name','tconv1')
    batchNormalizationLayer('Name','bn1','Epsilon',5e-5)
    reluLayer('Name','relu1')
    transposedConv2dLayer([10 1],4*numFilters,'Stride',4,'Cropping',[1 0],'Name','tconv2')
    batchNormalizationLayer('Name','bn2','Epsilon',5e-5)
    reluLayer('Name','relu2')
    transposedConv2dLayer([12 1],2*numFilters,'Stride',4,'Cropping',[1 0],'Name','tconv3')
    batchNormalizationLayer('Name','bn3','Epsilon',5e-5)
    reluLayer('Name','relu3')
    transposedConv2dLayer([5 1],numFilters,'Stride',4,'Cropping',[1 0],'Name','tconv4')
    batchNormalizationLayer('Name','bn4','Epsilon',5e-5)
    reluLayer('Name','relu4')
    transposedConv2dLayer([7 1],1,'Stride',2,'Cropping',[1 0],'Name','tconv5')
    ];

lgraphGenerator = layerGraph(layersGenerator);

layers = [
    imageInputLayer([1 1],'Name','labels','Normalization','none')
    embedAndReshapeLayer(projectionSize(1:2),embeddingDimension,numClasses,'emb')];

lgraphGenerator = addLayers(lgraphGenerator,layers);
lgraphGenerator = connectLayers(lgraphGenerator,'emb','cat/in2');

ジェネレーターのネットワーク構造をプロットします。

plot(lgraphGenerator)

カスタム学習ループを使ってネットワークに学習させ、自動微分を有効にするには、層グラフを dlnetwork オブジェクトに変換します。

dlnetGenerator = dlnetwork(lgraphGenerator);

ディスクリミネーター ネットワークの定義

信号のセットとそれらに対応するラベルを指定して 1201 行 1 列の実信号と生成信号を分類する、次の 2 入力ネットワークを定義します。

このネットワークは以下を行います。

  • 1201 x 1 x 1 の信号を入力として受け取る。

  • categorical ラベルを埋め込みベクトルに変換し、それらを 1201 x 1 x 1 の配列に形状変換する。

  • 2 つの入力からの結果を、チャネル次元に沿って連結する。出力は 1201 x 1 x 1025 の配列です。

  • 結果の配列を、スケール 0.2 の leaky ReLU 層をもつ一連の 1-D 転置畳み込み層を使用して、1 x 1 x 1 の配列であるスカラー予測スコアにダウンサンプルする。

% Discriminator Network

scale = 0.2;
inputSize = [1201 1 1];

layersDiscriminator = [
    imageInputLayer(inputSize,'Normalization','none','Name','in')
    concatenationLayer(3,2,'Name','cat')
    convolution2dLayer([17 1],8*numFilters,'Stride',2,'Padding',[1 0],'Name','conv1')
    leakyReluLayer(scale,'Name','lrelu1')
    convolution2dLayer([16 1],4*numFilters,'Stride',4,'Padding',[1 0],'Name','conv2')
    leakyReluLayer(scale,'Name','lrelu2')
    convolution2dLayer([16 1],2*numFilters,'Stride',4,'Padding',[1 0],'Name','conv3')
    leakyReluLayer(scale,'Name','lrelu3')
    convolution2dLayer([8 1],numFilters,'Stride',4,'Padding',[1 0],'Name','conv4')
    leakyReluLayer(scale,'Name','lrelu4')
    convolution2dLayer([8 1],1,'Name','conv5')];

lgraphDiscriminator = layerGraph(layersDiscriminator);

layers = [
    imageInputLayer([1 1],'Name','labels','Normalization','none')
    embedAndReshapeLayer(inputSize,embeddingDimension,numClasses,'emb')];

lgraphDiscriminator = addLayers(lgraphDiscriminator,layers);
lgraphDiscriminator = connectLayers(lgraphDiscriminator,'emb','cat/in2');

ディスクリミネーターのネットワーク構造をプロットします。

plot(lgraphDiscriminator)

カスタム学習ループを使ってネットワークに学習させ、自動微分を有効にするには、層グラフを dlnetwork オブジェクトに変換します。

dlnetDiscriminator = dlnetwork(lgraphDiscriminator);

モデルの学習

カスタム学習ループを使用して、CGAN モデルに学習させます。学習データ全体をループして、反復のたびにネットワーク パラメーターを更新します。学習の進行状況を監視するには、乱数値の 2 つの固定配列をジェネレーターに入力して得られた健全状態の生成信号と故障状態の生成信号、および 2 つのネットワークのスコアのプロットを表示します。

エポックごとに学習データをシャッフルし、データのミニバッチ全体でループします。

各ミニバッチについて以下を行います。

  • ジェネレーター ネットワーク用の乱数値の配列を含むdlarray (Deep Learning Toolbox)オブジェクトを生成する。

  • GPU で学習する場合、データをgpuArray (Parallel Computing Toolbox)オブジェクトに変換する。

  • dlfeval (Deep Learning Toolbox)と補助関数 modelGradients を使用して、モデル勾配を評価する。

  • 関数adamupdate (Deep Learning Toolbox)を使用してネットワーク パラメーターを更新する。

補助関数 modelGradients は、ジェネレーターとディスクリミネーターのネットワーク、入力データのミニバッチ、および乱数値の配列を入力として取り、ネットワークの学習可能なパラメーターに関する損失の勾配と、2 つのネットワークのスコアを返します。損失関数は、補助関数 ganLoss 内で定義されます。

学習オプションの指定

学習のパラメーターを設定します。

params.numLatentInputs = numLatentInputs;
params.numClasses = numClasses;
params.sizeData = [inputSize length(labels)];
params.numEpochs = 1000;
params.miniBatchSize = 256;

% Specify the options for Adam optimizer
params.learnRate = 0.0002;
params.gradientDecayFactor = 0.5;
params.squaredGradientDecayFactor = 0.999;

CGAN を CPU で実行するための実行環境を設定します。CGAN を GPU で実行するには、executionEnvironment を "gpu" に設定するか、ライブ エディターで [GPU で実行します] オプションを選択します。GPU を使用するには、Parallel Computing Toolbox™ が必要です。サポートされている GPU については、GPU 計算の要件 (Parallel Computing Toolbox)を参照してください。

executionEnvironment = "cpu";
params.executionEnvironment = executionEnvironment;

事前学習済みのネットワークを読み込んで、学習プロセスをスキップします。コンピューターでネットワークを学習させるには、trainNowtrue に設定するか、ライブ エディターで [Train CGAN now] オプションを選択します。

trainNow = false;
if trainNow
    % Train the CGAN
    [dlnetGenerator,dlnetDiscriminator] = trainGAN(dlnetGenerator, ...
        dlnetDiscriminator,flowNormalized,labels,params); %#ok
else
    % Use pretrained CGAN (default)
    load(fullfile(tempdir,'PumpSignalGAN','GANModel.mat')) % load data set
end

以下の学習プロットは、ジェネレーター ネットワークとディスクリミネーター ネットワークのスコア例を示しています。ネットワークのスコアを解釈する方法の詳細については、GAN の学習過程の監視と一般的な故障モードの識別 (Deep Learning Toolbox)を参照してください。この例では、ジェネレーターとディスクリミネーターの両方のスコアが 0.5 近くに収束しており、学習パフォーマンスが良好であることを示しています。

training-scores.PNG

流量信号の合成

ジェネレーター ネットワークに入力するための、乱数値の 1 x 1 x 100 の配列 2000 個のバッチを含む dlarray オブジェクトを作成します。再現可能な結果を得るために、乱数発生器をリセットします。

rng default

numTests = 2000;
ZNew = randn(1,1,numLatentInputs,numTests,'single');
dlZNew = dlarray(ZNew,'SSCB');

最初の 1000 個の乱数配列は健全状態、残りは故障状態として指定します。

TNew = ones(1,1,1,numTests,'single');
TNew(1,1,1,numTests/2+1:end) = single(2);
dlTNew = dlarray(TNew,'SSCB');

GPU を使用して信号を生成するには、データを gpuArray オブジェクトに変換します。

if executionEnvironment == "gpu"
    dlZNew = gpuArray(dlZNew);
    dlTNew = gpuArray(dlTNew);
end

乱数値とラベルの 1 x 1 x 100 の配列からなるバッチを指定して関数 predict をジェネレーターに対し使用し、合成信号を生成して、元の流量信号で実行した標準化ステップを元に戻します。

dlXGeneratedNew = predict(dlnetGenerator,dlZNew,dlTNew)*stdFlow+meanFlow;

信号の特徴の可視化

イメージや音声信号とは異なり、一般的な信号には、人間の知覚では区別しにくい特性があります。実信号と生成信号、あるいは健全状態の信号と故障状態の信号を比較するには、実信号の統計的特徴に主成分分析 (PCA) を適用し、その後、生成信号の特徴を同じ PCA 部分空間に投影します。

特徴抽出

元の実信号と生成信号を 1 つのデータ行列に組み合わせます。補助関数 helperExtractFeature を使用して、平均や分散のような一般的な信号統計量や、スペクトル特性などの特性を抽出します。

idxGenerated = 1:numTests;
idxReal = numTests+1:numTests+size(flow,2);

XGeneratedNew = squeeze(extractdata(gather(dlXGeneratedNew)));
x = [XGeneratedNew single(flow)];

features = zeros(size(x,2),14,'like',x);

for ii = 1:size(x,2)
    features(ii,:) = helperExtractFeature(x(:,ii));
end

features の各行は 1 つの信号の特徴に対応します。

健全状態の生成信号と故障状態の生成信号、および健全状態の実信号と故障状態の実信号のラベルを変更します。

L = [squeeze(TNew)+2;labels.'];

ラベルの定義は以下のようになっています。

  • 1 — 健全状態の生成信号

  • 2 — 故障状態の生成信号

  • 3 — 健全状態の実信号

  • 4 — 故障状態の実信号

主成分分析

実信号の特徴に対して PCA を実行し、その同じ PCA 部分空間に生成信号の特徴を投影します。W は係数で、Y はスコアです。

% PCA via svd
featuresReal = features(idxReal,:);
mu = mean(featuresReal,1);
[~,S,W] = svd(featuresReal-mu);
S = diag(S);
Y = (features-mu)*W;

特異ベクトル S では、最初の 3 つの特異値が S のエネルギーの 99% を構成しています。最初の 3 つの主成分を利用することで、信号の特徴を可視化できます。

sum(S(1:3))/sum(S)
ans = single
    0.9923

最初の 3 つの主成分を使用して、すべての信号の特徴をプロットします。PCA 部分空間において、生成信号の分布は、実信号の分布と類似しています。

idxHealthyR = L==1;
idxFaultR = L==2;

idxHealthyG = L==3;
idxFaultG = L==4;

pp = Y(:,1:3);

figure
scatter3(pp(idxHealthyR,1),pp(idxHealthyR,2),pp(idxHealthyR,3),'o')
xlabel('1st Principal Component')
ylabel('2nd Principal Component')
zlabel('3rd Principal Component')
hold on
scatter3(pp(idxFaultR,1),pp(idxFaultR,2),pp(idxFaultR,3),'d')
scatter3(pp(idxHealthyG,1),pp(idxHealthyG,2),pp(idxHealthyG,3),'s')
scatter3(pp(idxFaultG,1),pp(idxFaultG,2),pp(idxFaultG,3),'+')
view(-10,20)
legend('Real healthy','Real faulty','Generated healthy','Generated faulty', ...
    'Location','Best')
hold off

実信号と生成信号の違いをより適切に把握するには、最初の 2 つの主成分を使用して部分空間をプロットします。

view(2)

健全状態と故障状態の信号は、実信号か生成信号かに関わりなく、PCA 部分空間の同じ領域にあり、生成信号が実信号と類似の特徴をもっていることが示されています。

実信号のラベルの予測

CGAN のパフォーマンスをさらに示すために、生成信号に基づき SVM 分類器に学習させ、その後、実信号が健全状態か故障状態かを予測します。

生成信号を学習データ セットとして、実信号をテスト データ セットとして設定します。数値ラベルを文字ベクトルに変更します。

LABELS = {'Healthy','Faulty'};
strL = LABELS([squeeze(TNew);labels.']).';

dataTrain = features(idxGenerated,:);
dataTest = features(idxReal,:);

labelTrain = strL(idxGenerated);
labelTest = strL(idxReal);

predictors = dataTrain; 
response = labelTrain;
cvp = cvpartition(size(predictors,1),'KFold',5);

生成信号を使用して SVM 分類器に学習させます。

SVMClassifier = fitcsvm( ...
    predictors(cvp.training(1),:), ...
    response(cvp.training(1)),'KernelFunction','polynomial', ...
    'PolynomialOrder',2, ...
    'KernelScale','auto', ...
    'BoxConstraint',1, ...
    'ClassNames',LABELS, ...
    'Standardize',true);

学習済みの分類器を使用して、実信号の予測ラベルを取得します。分類器は、90% を超える予測精度を達成します。

actualValue = labelTest;
predictedValue = predict(SVMClassifier,dataTest);
predictAccuracy = mean(cellfun(@strcmp,actualValue,predictedValue))
predictAccuracy = 0.9460

混同行列を使用して、各カテゴリの予測パフォーマンスについての詳細情報を表示します。混同行列は各カテゴリにおいて、生成信号に基づき学習した分類器が高い精度を達成していることを示しています。

figure
confusionchart(actualValue,predictedValue)

ケース スタディ

実信号と生成信号のスペクトル特性を比較します。GPU 学習の動作は非確定的なため、CGAN モデルを自分で学習させた場合、その結果はこの例での結果と異なっている可能性があります。

ポンプのモーター回転数は 950 rpm、つまり 15.833 Hz であり、ポンプにはシリンダーが 3 つあるため、流量は 15.833 Hz の 3 倍、つまり 47.5 Hz の基本波と、47.5 Hz の倍数の高調波をもつと想定されます。健全状態の実信号と生成信号の 1 組のスペクトルをプロットします。プロットでは、健全状態の生成信号は 47.5 Hz と 47.5 Hz の 2 倍において比較的高いパワー値を示しています。これは健全状態の実信号とまったく同じです。

Fs = 1000;
pspectrum([x(:,1) x(:,2006)],Fs)
set(gca,'XScale','log')
legend('Generated healthy','Real healthy')

故障がある場合は、ポンプのモーター回転数 15.833 Hz およびその高調波で共振が発生します。故障状態の実信号と生成信号の 1 組のスペクトルをプロットします。生成信号は、約 15.833 Hz とその高調波において比較的高いパワー値を示しています。これは故障状態の実信号と類似しています。

pspectrum([x(:,1011) x(:,2100)],Fs)
set(gca,'XScale','log')
legend('Generated faulty','Real faulty')

故障状態の実信号と生成信号の別の組のスペクトルをプロットします。故障状態の生成信号のスペクトル特性は理論的な解析とあまり一致しておらず、故障状態の実信号と異なっています。この CGAN には、ネットワーク構造またはハイパーパラメーターの調整による改善の余地があると言えます。

pspectrum([x(:,1001) x(:,2600)],Fs)
set(gca,'XScale','log')
legend('Generated faulty','Real faulty')

計算時間

Simulink シミュレーションでは、2000 件のポンプ流量信号の生成に約 14 時間を要します。Parallel Computing Toolbox™ がある場合は、8 つの並列ワーカーを使って、この時間を約 1.7 時間に短縮できます。

NVIDIA Titan V GPU を使用した場合、CGAN は学習に 1.5 時間、同量の合成データの生成に 70 秒を要します。

参考

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

関連するトピック