Main Content

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

条件付き GAN の使用による合成信号の生成

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

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

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

信号合成用の CGAN

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

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

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

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

データの読み込み

シミュレーション データは、シミュレーション データを使用した複数クラス故障検出 (Predictive Maintenance Toolbox)の例に示されているポンプの 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×1×1 の配列に形状変更します。

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

  • バッチ正規化と ReLU 層を用いた一連の 1 次元転置畳み込み層を使用して、結果の配列を 1201 x 1 x 1 の配列にアップサンプリング。

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

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

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

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

このネットワークは、次を行います。

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

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

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

  • 結果の配列を、スケール 0.2 の leaky ReLU 層をもつ一連の 1 次元畳み込み層を使用して、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オブジェクトを生成します。

  • GPU での学習用に、データをgpuArray (Parallel Computing Toolbox)オブジェクトに変換します。

  • dlfevalと補助関数 modelGradients を使用してモデル勾配を評価します。

  • 関数adamupdateを使用してネットワーク パラメーターを更新します。

補助関数 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;

CPU 上で CGAN を実行するように実行環境を設定します。GPU で CGAN を実行するには、executionEnvironment を "gpu" に設定するか、ライブ エディターで "Run on 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 の学習過程の監視と一般的な故障モードの識別を参照してください。この例では、ジェネレーターとディスクリミネーターの両方のスコアが 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 秒かかります。