Main Content

Denoise Signals with Generative Adversarial Networks

This example demonstrates how autoencoders (AEs) and generative adversarial networks (GANs) can be used for signal denoising. The example implements these deep learning models as objects that you can train using your own datasets of real, one-dimensional signals. You can then use the trained models to remove noise from signals similar to those used in the training.

The first part of the example trains the models to remove noise from synthetic sinusoidal signals corrupted with white Gaussian noise (WGN). The second and third parts of the example examine the efficacy of the models when denoising real-world noisy electrocardiogram (ECG) and seismic vibration signals.

Autoencoders

Autoencoders have been used in a wide variety of applications such as anomaly detection, image denoising, and sequence-to-sequence prediction. Autoencoders operate by first compressing the input to a lower-dimensional space called the latent space and then decompressing the latent space back into a space of higher dimensionality, typically one with the same size as the input. Autoencoders can be divided into two parts: the encoder which handles the compression, and the decoder which handles the decompression.

Generative Adversarial Networks

Generative adversarial networks have been widely used in image processing and are now being applied in other fields, including signal processing. A GAN comprises two submodels: a generator and a discriminator. The generator is typically the primary model of interest when training GANs. In this example, the generator is responsible for predicting the denoised signal from the noisy input. On the other hand, the discriminator is a supplementary model that you can leverage to improve the accuracy of the generator's predictions. This example uses as discriminator a binary classifier network that predicts whether the denoised signal produced by the generator is "real" (the true signal with zero noise) or "fake" (a prediction from the generator).

Case 1: Denoise Simple Sinusoids

This section demonstrates how to initialize and train the AE and GAN models and how to use the trained objects to denoise signals. The denoising performance is compared to an infinite-duration impulse response (IIR) lowpass filter.

Load and Visualize Data

The dataset contains simple sinusoidal signals corrupted with white Gaussian noise. Each signal consists of a sum of five sinusoidal components with random frequencies between 10 Hz and 256 Hz, random amplitudes between 0.5 and 5, and random initial phases phase between 0 and 2π. Each signal is sampled at 1024 Hz for a random duration between 1 and 2 seconds and embedded in white noise such that the signal-to-noise ratio (SNR) is one of –5, –3, –1, 0, 2, 4, and 6 dB.

Generate the predictors, X, and targets, T, for the training, validation, and testing datasets. View the sizes of the datasets.

[X,T] = helperGenerateSines(50e3);
XTrain = X.Train;
TTrain = T.Train;
XValid = X.Valid;
TValid = T.Valid;
XTest = X.Test;
TTest = T.Test;
clear X T 

whos XTrain TTrain XValid TValid
  Name            Size                Bytes  Class    Attributes

  TTrain      40000x1             495918840  cell               
  TValid       5000x1              61968256  cell               
  XTrain      40000x1             495918840  cell               
  XValid       5000x1              61968256  cell               

Plot the noisy predictor and clean target for the training signals with the lowest and highest SNR values. Display the time-domain signals and the corresponding one-sided spectra.

helperPlotTrainData(XTrain,TTrain,1024,true)

Create and Train Denoising Autoencoder

Create a helperDeepSignalDenoiserAE object and set its SignalLength property to 1024 based on the shortest possible length of the signals in the dataset. Set FilterSize to 128 for all convolutional layers. Set Leakiness to 0.3 for the leaky rectified linear unit (ReLU) layers, which can help when training on oscillating signals. Use the default values of 64 filters for each downsampling layer and 128 filters for each external layer. Use the default five downsampling layers to reduce the signal lengths to 32 in the latent space.

denoiserAESines = ...
    helperDeepSignalDenoiserAE(SignalLength=1024, ...
    FilterSize=128, ...
    Leakiness=0.3)
denoiserAESines = 
  helperDeepSignalDenoiserAE with properties:

   State:
                    IsTrained: 0

   Network Options:
                 SignalLength: 1024
               NumDownSamples: 5
                   FilterSize: 128
    NumDownSampleLayerFilters: 64
      NumExternalLayerFilters: 128
                    Leakiness: 0.3000

   Model Architecture:
         DenoisingAutoencoder: [1×1 dlnetwork]

   Training Options/Results:
               TrainedNetwork: []
              TrainingOptions: []

Define a structure array of training options. Use the default value of 1e-4 for the initial learn rate and a piecewise schedule to halve the learning rate every 25 epochs. Set the validation frequency to 32 iterations to perform validation at the end of each epoch. Set the doTrain flag to false to load a pretrained object instead of training.

doTrain = false;
if doTrain
    trainOpts = ...
        struct(MaxEpochs=100, ...
        MiniBatchSize=1250, ...
        ValidationFrequency=32, ...
        LearnRateDropPeriod=25, ...
        LearnRateDropFactor=0.5);
    trainAE(denoiserAESines, ...
        [XTrain TTrain],[XValid TValid],trainOpts);
else
    datasetZipFile = matlab.internal.examples.downloadSupportFile("SPT","data/trainedDenoiserObjectSines.zip");
    datasetFolderSines = fullfile(fileparts(datasetZipFile),"trainedDenoiserObjectSines");
    unzip(datasetZipFile,fileparts(datasetZipFile));
    delete(datasetZipFile);
    load(fullfile(datasetFolderSines,"denoiserAESines.mat"))
end

Create and Train Denoiser GAN

Create a helperDeepSignalDenoiserGAN object with the same properties as the denoising autoencoder.

denoiserGANSines = ...
    helperDeepSignalDenoiserGAN(SignalLength=1024, ...
    FilterSize=128, ...
    Leakiness=0.3)
denoiserGANSines = 
  helperDeepSignalDenoiserGAN with properties:

   State:
                    IsTrained: 0

   Network Options:
                 SignalLength: 1024
               NumDownSamples: 5
                   FilterSize: 128
    NumDownSampleLayerFilters: 64
      NumExternalLayerFilters: 128
                    Leakiness: 0.3000

   Model Architectures:
                    Generator: [1×1 dlnetwork]
                Discriminator: [1×1 dlnetwork]

   Training Options/Results:
                  LastNetwork: []
    BestValidationLossNetwork: []
              TrainingOptions: []

View the model architectures of the generator and discriminator networks. Because the same property values were used to initialize the GAN and the AE, the generator has the same architecture as the denoising AE. Additionally, the discriminator has the same architecture as the encoding portion of the denoising autoencoder, which is the first half containing the downsampling 1-D convolution layers.

helperPlotNetworks(denoiserGANSines)

Define a structure array of training options. Set the initial learn rate to 5e-5, and use a piecewise learning rate schedule to reduce the learn rate by a factor of 0.9 every 25 epochs. Set the L1-norm loss factor to 0.001 when computing the generator's loss function to improve training efficacy. Set the validation frequency to 32 iterations to validate at the end of each epoch. Set the doTrain flag to false to load a pretrained object instead of training.

doTrain = false;
if doTrain
    trainOpts = ...
        struct(MaxEpochs=200, ...
            MiniBatchSize=1250, ...
            ValidationFrequency=32, ...
            InitialLearnRate=5e-5, ...
            LearnRateDropPeriod=25, ...
            LearnRateDropFactor=0.9, ...
            L1LossFactor=0.001);
    trainGAN(denoiserGANSines, ...
        [XTrain TTrain],[XValid TValid],trainOpts);
else
    load(fullfile(datasetFolderSines,"denoiserGANSines.mat"))
end

Evaluate Denoising Performance

The signals used to train the object have SNRs ranging among –5, –3, –1, 0, 2, 4, and 6 dB. Compare the models' denoising performance with noisy signals with SNR values ranging from –7 to 7 dB.

Get denoised predictions for the testing data using the trained models.

YAE = denoise(denoiserAESines,XTest);
YGAN = denoise(denoiserGANSines,XTest);

Apply a lowpass IIR filter to the noisy data. Truncate the sequence lengths of the testing data to the SignalLength property defined for the denoising AE and GAN models. Generate the discrete-time filter object for a lowpass Butterworth filter with passband and stopband cuttoff frequencies of 256 Hz and 300 Hz, 0.05 dB passband ripple, and 60 dB stopband attenuation. Apply the lowpass filter to the testing data using a zero-phase digital filtering approach.

XTest = cellfun(@(X) X(1:denoiserGANSines.SignalLength), ...
    XTest,UniformOutput=false);
TTest = cellfun(@(T) T(1:denoiserGANSines.SignalLength), ...
    TTest,UniformOutput=false);

myFilter = designfilt("lowpassiir", ...
    DesignMethod="butter", ...
    SampleRate=1024, ...
    PassbandFrequency=256, ...
    StopbandFrequency=300, ...
    PassbandRipple=0.05, ...
    StopbandAttenuation=60);
YLP = cellfun(@(X) filtfilt(myFilter,X), ...
    XTest,UniformOutput=false);

Compute performance metrics: root mean square error (RMSE), percent relative difference (PRD), and signal-to-noise ratio.

[RMSE_in,PRD_in,SNR_in] = helperComputeMetrics(XTest,TTest);
[RMSE_LP,PRD_LP,SNR_LP] = helperComputeMetrics(YLP,TTest);
[RMSE_AE,PRD_AE,SNR_AE] = helperComputeMetrics(YAE,TTest);
[RMSE_GAN,PRD_GAN,SNR_GAN] = helperComputeMetrics(YGAN,TTest);

Create box plots of the metrics to compare performance between the filter and the deep learning networks.

helperPlotMetrics([RMSE_in RMSE_LP RMSE_AE RMSE_GAN], ...
    [PRD_in PRD_LP PRD_AE PRD_GAN], ...
    [SNR_in SNR_LP SNR_AE SNR_GAN], ...
    ["Input" "LP" "AE" "GAN"])

Both the autoencoder and GAN models produce denoised signals with notably higher SNRs and lower errors and relative differences than the lowpass filter. Although the GAN performs slightly better than the autoencoder, both deep learning models exhibit similar denoising performance overall.

Compare the denoising performance between the lowpass filter and the GAN model for the signals with the lowest and highest input SNR values.

helperCompareResults(XTest,TTest,YLP,YGAN,1024,["LP" "GAN"],true)

Inspecting the frequency spectra of the denoised signals reveals that the GAN removes noise in the frequency range between the component sinusoids, whereas the lowpass filter is only able to remove noise outside of the passband (above 256 Hz). In essence, the GAN behaves similarly to a multi-bandpass filter tuned specifically to the fundamental frequencies of each input signal.

Compute the average mean squared error (MSE) before and after denoising for each input SNR.

SNRs = -7:7;
MSE_in = zeros(length(SNRs),1);
MSE_LP = MSE_in;
MSE_AE = MSE_in;
MSE_GAN = MSE_in;

for ii = 1:length(SNRs)
    idx = find(round(SNR_in,1)-SNRs(ii) < 1);
    MSE_in(ii) = mean(RMSE_in(idx).^2);
    MSE_LP(ii) = mean(RMSE_LP(idx).^2);
    MSE_AE(ii) = mean(RMSE_AE(idx).^2);
    MSE_GAN(ii) = mean(RMSE_GAN(idx).^2);
end

Plot the MSE as a function of input SNR for the noisy and denoised signals. The autoencoder and GAN produce denoised signals with significantly lower mean squared errors than those from the lowpass filter, likely due to their ability to remove noise between the fundamental frequencies of the sinusoids.

figure
semilogy(SNRs,[MSE_in MSE_LP MSE_AE MSE_GAN])
legend(["Input" "LP" "AE" "GAN"],Location="Northeast")
ylabel("Average MSE")
xlabel("Input SNR (dB)")
grid on

Case 2: Denoise ECG Signals

In this section, you train the autoencoder and generative adversarial network objects to denoise ECG signals and compare their generalization performance.

Load and Visualize Data

The dataset comprises real ECG signals and noise from the PhysioNet MIT-BIH Arrhythmia database [1][2] and MIT-BIH Noise Stress Test database [1][3]. All signals were sampled at 300 Hz and divided into 1024-sample segments. Three kinds of noise were used to corrupt the signals: baseline wander, muscle artifact, and electrode motion. The three are combined, rescaled to produce target SNRs of –2.5, 0, 2.5, 5, and 7.5 dB, and added to the clean signals.

Begin by loading the predictors and targets for both the training and validation datasets.

datasetZipFile = matlab.internal.examples.downloadSupportFile("SPT","data/PhysionetMITBIH.zip");
datasetFolderECG = fullfile(fileparts(datasetZipFile),"PhysionetMITBIH");    
unzip(datasetZipFile,fileparts(datasetZipFile));
delete(datasetZipFile);
load(fullfile(datasetFolderECG,"ECG.mat"))
whos XTrain TTrain XValid TValid
  Name            Size                Bytes  Class    Attributes

  TTrain      38400x1             319180800  cell               
  TValid       4800x1              39897600  cell               
  XTrain      38400x1             319180800  cell               
  XValid       4800x1              39897600  cell               

Plot the predictors from the training partition with the lowest and highest SNR and their corresponding clean targets.

helperPlotTrainData(XTrain,TTrain,300)

Create and Train Denoising Autoencoder

Train the denoising autoencoder model with the ECG data. Use default values for all model parameters. Configure the training loop to last 100 epochs and set the mini-batch size to 800, which equates to 48 mini-batches per epoch. Use an initial learn rate of 1e-4 and reduce the learn rate by half every 25 epochs. Set the doTrain flag to false to load a pretrained object instead of training.

doTrain = false;
if doTrain
    denoiserAEECG = helperDeepSignalDenoiserAE()
    trainOpts = ...
        struct(MaxEpochs=100, ...
            MiniBatchSize=800, ...
            ValidationFrequency=48, ...
            LearnRateDropPeriod=25, ...
            LearnRateDropFactor=0.5);
    trainAE(denoiserAEECG, ...
        [XTrain TTrain],[XValid TValid],trainOpts);
else
    datasetZipFile = matlab.internal.examples.downloadSupportFile("SPT","data/trainedDenoiserObjectECG.zip");
    datasetFolderECG = fullfile(fileparts(datasetZipFile),"trainedDenoiserObjectECG");
    unzip(datasetZipFile,fileparts(datasetZipFile));
    delete(datasetZipFile);
    load(fullfile(datasetFolderECG,"denoiserAEECG.mat"))
end

Create and Train Denoising GAN

Train the denoising GAN model on the ECG data. Use the same architecture as the autoencoder by using default values for all model parameters. Set the maximum number of epochs to 200. Use a higher mini-batch size of 1200, which can sometimes help improve stability of the GAN training scheme. Use an initial learn rate of 1e-4 with a piecewise scheme that multiplies the learning rate by a reduction factor of 1/10 every 40 epochs. Set the L1-norm loss factor to 0.05 when calculating the generator loss during training. Set the doTrain flag to false to load a pretrained object instead of training.

doTrain = false;
if doTrain
    denoiserGANECG = helperDeepSignalDenoiserGAN()
    trainOpts = ...
        struct(MaxEpochs=200, ...
        MiniBatchSize=1200, ...
        ValidationFrequency=32, ...
        InitialLearnRate=1e-4, ...
        LearnRateDropPeriod=40, ...
        LearnRateDropFactor=sqrt(10)/10, ...
        L1LossFactor=0.05, ...
        OutputNetwork="both");
    trainGAN(denoiserGANECG, ...
        [XTrain TTrain],[XValid TValid],trainOpts);
else
    load(fullfile(datasetFolderECG,"denoiserGANECG.mat"))
end

Compare Generalization Performance

Signals with slightly different characteristics were synthesized to test the generalization performance of the trained models. In this testing dataset, the signals were generated using the same procedure as for the training and validation sets; however, the testing signals have target SNRs of –1, 3, and 7 dB.

Load the noisy signals and clean targets for the testing partition from file.

whos XTess TTest
  Name          Size               Bytes  Class    Attributes

  TTest      2880x1             23938560  cell               

Use the trained AE and GAN objects to denoise the testing data.

YAE = denoise(denoiserAEECG,XTest);
YGAN = denoise(denoiserGANECG,XTest);

Compute the performance metrics: RMSE, PRD, and SNR.

[RMSE_in,PRD_in,SNR_in] = helperComputeMetrics(XTest,TTest);
[RMSE_AE,PRD_AE,SNR_AE] = helperComputeMetrics(YAE,TTest);
[RMSE_GAN,PRD_GAN,SNR_GAN] = helperComputeMetrics(YGAN,TTest);

Compare the overall denoising results using box plots.

helperPlotMetrics([RMSE_in RMSE_AE RMSE_GAN], ...
    [PRD_in PRD_AE PRD_GAN], ...
    [SNR_in SNR_AE SNR_GAN], ...
    ["Input" "AE" "GAN"])

The autoencoder and GAN exhibit similar denoising performance overall. However, the autoencoder is slightly better at producing signals with higher SNR and lower error on average.

Plot the results (prediction) alongside the noisy signal (predictor) and clean signal (target) for the highest and lowest input SNR.

helperCompareResults(XTest,TTest,YAE,YGAN,300)

Compared to the autoencoder, the GAN produces denoised signals with peak amplitudes closer to the clean target. Close inspection of the denoised signals reveals that the autoencoder exhibits a smoothing phenomenon that is effective at removing noise but results in less accurate peak shapes and amplitudes.

Compute the average MSE before and after denoising for each input SNR.

SNRs = -1:4:7;
MSE_in = zeros(length(SNRs),1);
MSE_AE = MSE_in;
MSE_GAN = MSE_in;

for ii = 1:length(SNRs)
    idx = find(round(SNR_in,1)-SNRs(ii) < 1);
    MSE_in(ii) = mean(RMSE_in(idx).^2);
    MSE_AE(ii) = mean(RMSE_AE(idx).^2);
    MSE_GAN(ii) = mean(RMSE_GAN(idx).^2);
end

Plot the MSE as a function of input SNR for the noisy and denoised signals. The autoencoder and GAN both generate denoised signals with notably lower mean squared errors than the noisy inputs. However, the autoencoder consistently produces signals with slightly lower MSE.

figure
semilogy(SNRs,[MSE_in MSE_AE MSE_GAN])
legend(["Input" "AE" "GAN"],Location="Northeast")
ylabel("Average MSE")
xlabel("Input SNR (dB)")
grid on

Case 3: Denoise Seismic Vibration Signals

In this section, you train the denoising autoencoder and generative adversarial network objects on earthquake measurements and compare their denoising performance.

Load and Visualize Data

This section uses a preprocessed version of the Stanford Earthquake Dataset (STEAD) [4]. The subset used is constructed by randomly selecting 20,000 seismic measurements of 60-second durations sampled at 100 Hz and individually normalizing them based on their maximum amplitudes. The noise data are constructed from a basis of 2907 entries of isolated noise that are also randomly sampled and rescaled to achieve target SNRs of –10, –6, –2, 2, and 6 dB with respect to the seismic signals. Finally, the signals are separated into the training, validation, and testing sets using an 80–10–10 split.

Load the predictors and targets for both the training and validation datasets.

datasetZipFile = matlab.internal.examples.downloadSupportFile("SPT","data/STEAD.zip");
datasetFolderSeismic = fullfile(fileparts(datasetZipFile),"STEAD");    
unzip(datasetZipFile,fileparts(datasetZipFile));
delete(datasetZipFile);
load(fullfile(datasetFolderSeismic,"Earthquake.mat"))
whos XTrain TTrain XValid TValid
  Name            Size                Bytes  Class    Attributes

  TTrain      16000x1             526208000  cell               
  TValid       2000x1              65776000  cell               
  XTrain      16000x1             526208000  cell               
  XValid       2000x1              65776000  cell               

Plot the predictors from the training partition with the lowest and highest SNR and their corresponding clean targets.

helperPlotTrainData(XTrain,TTrain,100)

Create and Train Denoising Autoencoder

Train the autoencoder model with the seismic data. Set the autoencoder to a signal length of 4096 with four downsampling layers, 64 and 32 filters for the external and internal convolutional layers, a convolutional filter size of 64, and use 0.1 for the leakiness of the leaky ReLU layers. Set the training duration to 200 epochs and set the mini-batch size to 500 to achieve 32 mini-batches per epoch. Use an initial learn rate of 1e-4, and use a piecewise learning scheme to halve it every 50 epochs. Set the validation frequency to 32 to perform validation at the end of every epoch. Set the doTrain flag to false to load a pretrained object instead of training.

doTrain = false;
if doTrain
    denoiserAESeismic = ...
        helperDeepSignalDenoiserAE(SignalLength=4096, ...
            NumDownSamples=4, ...
            FilterSize=64, ...
            NumDownSampleLayerFilters=32, ...
            NumExternalLayerFilters=64, ...
            Leakiness=0.1)
    trainOpts = struct(MaxEpochs=200, ...
        MiniBatchSize=500, ...
        ValidationFrequency=32, ...
        LearnRateDropPeriod=50, ...
        LearnRateDropFactor=0.5);
    trainAE(denoiserAESeismic, ...
        [XTrain TTrain],[XValid TValid],trainOpts);
else
    datasetZipFile = matlab.internal.examples.downloadSupportFile("SPT","data/trainedDenoiserObjectSeismic.zip");
    datasetFolderECG = fullfile(fileparts(datasetZipFile),"trainedDenoiserObjectSeismic");
    unzip(datasetZipFile,fileparts(datasetZipFile));
    delete(datasetZipFile);
    load(fullfile(datasetFolderECG,"denoiserAESeismic.mat"))
end

Create and Train Denoising GAN

Train the denoising GAN model for seismic signal denoising. Configure the training loop to terminate after 200 epochs and set the mini-batch size to 500. Set 1e-4 as the initial learn rate. Use a piecewise learning scheme to multiply the learn rate by a reduction factor of 0.9 every 10 epochs. Set L1LossFactor to 1 to use the L1-norm of residual errors when computing the generator's loss. The GradientDecay factor is set to 0.5 for the GAN (compared to 0.9 when training the AE), which can help improve the stability of training. Set the doTrain flag to false to load a pretrained object instead of training.

doTrain = false;
if doTrain
    denoiserGANSeismic = ...
        helperDeepSignalDenoiserGAN(SignalLength=4096, ...
            NumDownSamples=4, ...
            FilterSize=64, ...
            NumDownSampleLayerFilters=32, ...
            NumExternalLayerFilters=64, ...
            Leakiness=0.1)
    trainOpts = struct(MaxEpochs=200, ...
        MiniBatchSize=500, ...
        ValidationFrequency=32, ...
        InitialLearnRate=1e-4, ...
        LearnRateDropPeriod=10, ...
        LearnRateDropFactor=0.9, ...
        L1LossFactor=1, ...
        GradientDecay=0.5, ...
        OutputNetwork="both");
    trainGAN(denoiserGANSeismic, ...
        [XTrain TTrain],[XValid TValid],trainOpts);
else
    load(fullfile(datasetFolderECG,"denoiserGANSeismic.mat"))
end

Compare Generalization Performance

The testing signals are created with slightly different characteristics to test the generalization performance of the trained objects. The signals in the testing partition were generated using the same procedure as for the training and validation partitions but with target SNRs ranging between –12 and 8 dB with increments of 2 dB.

Load the noisy signals and clean targets for the testing partition from file.

whos XTest TTest
  Name          Size               Bytes  Class    Attributes

  TTest      2000x1             65776000  cell               
  XTest      2000x1             65776000  cell               

Use the trained AE and GAN objects to denoise the testing data.

YAE = denoise(denoiserAESeismic,XTest);
YGAN = denoise(denoiserGANSeismic,XTest);

Compute the RMSE, PRD, and SNR metrics.

[RMSE_in,PRD_in,SNR_in] = helperComputeMetrics(XTest,TTest);
[RMSE_AE,PRD_AE,SNR_AE] = helperComputeMetrics(YAE,TTest);
[RMSE_GAN,PRD_GAN,SNR_GAN] = helperComputeMetrics(YGAN,TTest);

Compare the overall denoising performance using box plots.

helperPlotMetrics([RMSE_in RMSE_AE RMSE_GAN], ...
    [PRD_in PRD_AE PRD_GAN], ...
    [SNR_in SNR_AE SNR_GAN], ...
    ["Input" "AE" "GAN"])

The GAN slightly outperforms the AE at improving SNR and reducing the RMSE and PRD of the signals in the test partition. However, both deep learning models are able to produce denoised signals with significantly higher SNRs compared to the noisy input signals.

Plot the best and worst denoised results (prediction) alongside the noisy signal (predictor) and clean signal (target).

helperCompareResults(XTest,TTest,YAE,YGAN,100)

The GAN generates slightly more accurate denoised signals than the autoencoder. Most notably, however, both models demonstrate the ability to identify the approximate onset of seismic activity for input signals with significantly low SNR.

Compute the average MSE before and after denoising for each input SNR.

SNRs = -12:2:8;
MSE_in = zeros(length(SNRs),1);
MSE_AE = MSE_in;
MSE_GAN = MSE_in;

for ii = 1:length(SNRs)
    idx = find(round(SNR_in,1)-SNRs(ii) < 1);
    MSE_in(ii) = mean(RMSE_in(idx).^2);
    MSE_AE(ii) = mean(RMSE_AE(idx).^2);
    MSE_GAN(ii) = mean(RMSE_GAN(idx).^2);
end

Plot the results to compare the MSE of the denoised signals produced by the AE and GAN models for each input SNR. The GAN produces denoised signals with lower average mean squared error than the autoencoder for all input SNR values. However, both deep learning models reduce the average MSE of the noisy inputs by more than an order of magnitude.

figure
semilogy(SNRs,[MSE_in MSE_AE MSE_GAN])
legend(["Input" "AE" "GAN"],Location="Northeast")
ylabel("Average MSE")
xlabel("Input SNR (dB)")
grid on

Conclusion

This example shows how to train autoencoder and generative adversarial deep learning networks to remove noise from different types of signals. Both model types are provided as custom MATLAB® classes via M-files, and they can be immediately used to train on your own dataset of one-dimensional signals. In the examples shown here, GAN tends to have slightly better performance than the AE. However, the GAN is more expensive to train with regard to time and computational cost. It is good practice to train and compare the two models to see which one works best for a particular set of data.

Acknowledgment

This example contains information from the PhysioNet MIT-BIH Arrhythmia Database and the PhysioNet MIT-BIH Noise Stress Test Database, which are made available under the Open Data Commons Attribution License (ODC-By) v1.0 available at https://opendatacommons.org/licenses/by/1-0/.

This example contains information from the STanford EArthquake Dataset (STEAD), which is made available under the Creative Commons Attribution License v4.0 International available at https://creativecommons.org/licenses/by/4.0/. The MathWorks, Inc. makes no warranty of any kind, express or implied, regarding the accuracy, validity, reliability, availability, or completeness of any information derived from this dataset. Copyright © 2019 S. Mostafa Mousavi, Yixiao Sheng, Weiqiang Zhu, and Gregory C. Beroza.

References

[1] Goldberger, A., L. Amaral, L. Glass, J. Hausdorff, P. C. Ivanov, R. Mark, J. E. Mietus, G. B. Moody, C. K. Peng, and H. E. Stanley. "PhysioBank, PhysioToolkit, and PhysioNet: Components of a new research resource for complex physiologic signals. Circulation [Online]. 101 (23), pp. e215–e220. (2000).

[2] Moody, G. B., and R. G. Mark. "The impact of the MIT-BIH Arrhythmia Database." IEEE Engineering in Medicine and Biology Magazine, vol. 20, no. 3, pp.45–50, May–June 2001. (PMID: 11446209)

[3] Moody, G. B., W. E. Muldrow, and R. G. Mark. "A noise stress test for arrhythmia detectors." Computers in Cardiology, vol. 11, pp. 381–384, 1984.

[4] Mousavi, S. M., Y. Sheng, W. Zhu, and G. C. Beroza. "STanford EArthquake Dataset (STEAD): A Global Data Set of Seismic Signals for AI." IEEE Access, vol. 7, pp. 179464–76, 2019. https://doi.org/10.1109/ACCESS.2019.2947848.

Appendix: Helper Functions

helperPlotNetworks

This function plots the architecture of the generator and discriminator networks of an input helperDeepSignalDenoiserGAN object.

function helperPlotNetworks(obj)
% This function is only intended to support this example. 
% It may be changed or removed in a future release. 

figure(Position=[0 0 1000 1000])
tiledlayout(1,2,TileSpacing="compact")
nexttile
plot(obj.Generator)
title("Generator")
nexttile
plot(obj.Discriminator)
title("Discriminator")

end

helperPlotTrainData

This function plots the input and target signals from the training partition with the highest and lowest SNR values.

function helperPlotTrainData(X,T,fsample,plotFreqSpectrum)
% This function is only intended to support this example. 
% It may be changed or removed in a future release. 

% Setup for potential subplotting
if nargin < 4
    plotFreqSpectrum = 0;
    tileFactor = 0;
elseif plotFreqSpectrum > 0
    tileFactor = 1;
end

% Identify highest and lowest SNR inputs
SNRs = cellfun(@(x,t) snr(t,x-t), X, T);
[val(1),ind(1)] = min(SNRs);
[val(2),ind(2)] = max(SNRs);

% Plot examples via loop
for ii = 1:2
    % Initialize plot
    figure(Position=[0 0 1000 400])
    tl = tiledlayout(1,tileFactor+1,TileSpacing="compact");
    tl.Title.String = "SNR_{in} = "+val(ii)+" dB";

    % Time domain plots
        % Prepare to transpose if necessary
        [nRow,nCol] = size(X{1});

        % Compute time domain and get signals
        L = length(X{ind(ii)});
        time = linspace(0,L/fsample,L);
        if nRow > nCol
            X_time = X{ind(ii)};
            T_time = T{ind(ii)};
        else
            X_time = X{ind(ii)}';
            T_time = T{ind(ii)}';
        end

        % Plot predictor and target (time domain)
        nexttile(tl,1)
        ax = stackedplot(time,[X_time T_time]);
        ax.DisplayLabels = ["Noisy" "Clean (Target)"];
        lims = [min(ax.AxesProperties(1).YLimits(1), ...
            ax.AxesProperties(2).YLimits(1)) ...
            max(ax.AxesProperties(1).YLimits(2), ...
            ax.AxesProperties(2).YLimits(2))];
        lims = max(abs(lims)).*[-1 1];
        [ax.AxesProperties(1).YLimits, ...
            ax.AxesProperties(2).YLimits] = deal(lims);
        ax.XLabel = "Time (s)";
        grid on

    % Frequency domain plots, if requested
    if plotFreqSpectrum
        % First, zoom the time domain plots
        ax.XLimits = [0 round(time(floor(end/5)),1)];

        % Single-sided FFT for predictor and target
        freq = fsample*(0:floor(L/2))/L;
        X_freq = fft(X_time);
        X_freq = abs(X_freq(1:floor(L/2)+1))/L;
        X_freq(2:end-1) = 2*X_freq(2:end-1);
        T_freq = fft(T_time);
        T_freq = abs(T_freq(1:floor(L/2)+1))/L;
        T_freq(2:end-1) = 2*T_freq(2:end-1);

        % Plot predictor and target (freq domain)
        nexttile(tl,2)
        ax = stackedplot(freq,[X_freq T_freq]);
        ax.DisplayLabels = ["" ""];
        lims = [min(ax.AxesProperties(1).YLimits(1), ...
            ax.AxesProperties(2).YLimits(1)) ...
            max(ax.AxesProperties(1).YLimits(2), ...
            ax.AxesProperties(2).YLimits(2))];
        lims = [0 max(abs(lims))];
        [ax.AxesProperties(1).YLimits, ...
            ax.AxesProperties(2).YLimits] = deal(lims);
        ax.XLimits = [0 round(freq(end))];
        ax.XLabel = "Frequency (Hz)";
        grid on

    end
end

end

helperCompareResults

This function plots two denoised signals alongside the input and target signals.

function helperCompareResults(X,T,Y1,Y2,fsample,modelLabels, ...
    plotFreqSpectrum)
% This function is only intended to support this example. 
% It may be changed or removed in a future release. 

% Setup based on number of input arguments
if nargin < 7
    plotFreqSpectrum = 0;
    tileFactor = 0;
elseif plotFreqSpectrum > 0
    tileFactor = 1;
end
if nargin < 6
    modelLabels = ["AE" "GAN"];
end

% Identify highest and lowest SNR inputs
SNRs = cellfun(@(x,t) snr(t,x-t),X,T);
[val(1),ind(1)] = min(SNRs);
[val(2),ind(2)] = max(SNRs);

% Plot examples via loop
for ii = 1:2
    % Prepare to transpose if necessary
    [nRow,nCol] = size(X{1});
    
    % Get signals, and compute prediction SNRs
    if nRow > nCol
        X_time = X{ind(ii)};
        T_time = T{ind(ii)};
        Y1_time = Y1{ind(ii)};
        Y2_time = Y2{ind(ii)};
    else
        X_time = X{ind(ii)}';
        T_time = T{ind(ii)}';
        Y1_time = Y1{ind(ii)}';
        Y2_time = Y2{ind(ii)}';
    end
    SNR_out1 = snr(Y1_time,Y1_time-T_time);
    SNR_out2 = snr(Y2_time,Y2_time-T_time);

    % Initialize plot
    figure(Position=[0 0 1000 600])
    tl = tiledlayout(1,tileFactor+1,TileSpacing="compact");
    tl.Title.String = "SNR_{in} = "+val(ii)+" dB, "+ ...
        "SNR_{"+modelLabels(1)+"} = "+SNR_out1+" dB, "+ ...
        "SNR_{"+modelLabels(2)+"} = "+SNR_out2+" dB";

    % Time domain plots
        % Compute time domain
        L = length(X{ind(ii)});
        time = linspace(0,L/fsample,L);

        % Plot predictor, target, and prediction (time domain)
        nexttile(tl,1)
        ax = stackedplot(time,[X_time T_time Y1_time Y2_time]);
        ax.DisplayLabels = ["Noisy" "Clean (Target)" ...
           "Denoised ("+modelLabels(1)+")" ...
           "Denoised ("+modelLabels(2)+")"];
        lims = [min([ax.AxesProperties(1).YLimits(1) ...
            ax.AxesProperties(2).YLimits(1) ...
            ax.AxesProperties(3).YLimits(1) ...
            ax.AxesProperties(4).YLimits(1)]) ...
            max([ax.AxesProperties(1).YLimits(2) ...
            ax.AxesProperties(2).YLimits(2) ...
            ax.AxesProperties(3).YLimits(2) ...
            ax.AxesProperties(4).YLimits(2)])];
        lims = max(abs(lims)).*[-1 1];
        [ax.AxesProperties(1).YLimits, ...
            ax.AxesProperties(2).YLimits, ...
            ax.AxesProperties(3).YLimits, ...
            ax.AxesProperties(4).YLimits] = deal(lims);
        ax.XLabel = "Time (s)";
        grid on

    % Frequency domain plots, if requested
    if plotFreqSpectrum
        % First, zoom the time domain plots
        ax.XLimits = [0 round(time(floor(end/5)),1)];

        % Single-sided FFT for predictor, target, and predictions
        freq = fsample*(0:(L/2))/L;
        X_freq = fft(X_time);
        X_freq = abs(X_freq(1:L/2+1))/L;
        X_freq(2:end-1) = 2*X_freq(2:end-1);
        T_freq = fft(T_time);
        T_freq = abs(T_freq(1:L/2+1))/L;
        T_freq(2:end-1) = 2*T_freq(2:end-1);
        Y1_freq = fft(Y1_time);
        Y1_freq = abs(Y1_freq(1:L/2+1))/L;
        Y1_freq(2:end-1) = 2*Y1_freq(2:end-1);
        Y2_freq = fft(Y2_time);
        Y2_freq = abs(Y2_freq(1:L/2+1))/L;
        Y2_freq(2:end-1) = 2*Y2_freq(2:end-1);

        % Plot predictor, target, and prediction (freq domain)
        nexttile(tl,2)
        ax = stackedplot(freq,[X_freq T_freq Y1_freq Y2_freq]);
        ax.DisplayLabels = ["" "" "" ""];
        lims = [min([ax.AxesProperties(1).YLimits(1) ...
            ax.AxesProperties(2).YLimits(1) ...
            ax.AxesProperties(3).YLimits(1) ...
            ax.AxesProperties(4).YLimits(1)]) ...
            max([ax.AxesProperties(1).YLimits(2) ...
            ax.AxesProperties(2).YLimits(2) ...
            ax.AxesProperties(3).YLimits(2) ...
            ax.AxesProperties(4).YLimits(2)])];
        lims = max(abs(lims)).*[0 1];
        [ax.AxesProperties(1).YLimits, ...
            ax.AxesProperties(2).YLimits, ...
            ax.AxesProperties(3).YLimits, ...
            ax.AxesProperties(4).YLimits] = deal(lims);
        ax.XLimits = [0 round(freq(end))];
        ax.XLabel = "Frequency (Hz)";
        grid on

    end
end

end

helperComputeMetrics

This function computes the RMSE, PRD, and SNR performance metrics.

function [RMSE,PRD,SNR] = helperComputeMetrics(Y,T)
% This function is only intended to support this example. 
% It may be changed or removed in a future release. 

RMSE = cellfun(@(Y,T) rmse(Y,T),Y,T,UniformOutput=true);

PRD = cellfun(@(Y,T) 100*sqrt(sum((Y-T).^2)./sum(T.^2)), ...
    Y,T,UniformOutput=true);

SNR = cellfun(@(Y,T) snr(T,Y-T),Y,T,UniformOutput=true);

end

helperPlotMetrics

This function plots the RMSE, PRD, and SNR performance metrics.

function helperPlotMetrics(RMSE,PRD,SNR,labels)
% This function is only intended to support this example. 
% It may be changed or removed in a future release. 

figure(Position=[0 0 1400 1000])
subplot(1,3,1)
    boxplot(SNR)
    title("SNR")
    xticklabels(labels)
    grid on
spax = subplot(1,3,2);    
    boxplot(RMSE)
    title("RMSE")
    xticklabels(labels)
    grid on
    spax.YLim(1) = 0;
spax = subplot(1,3,3);
    boxplot(PRD)
    title("PRD")
    xticklabels(labels)
    grid on
    spax.YLim(1) = 0;

end

helperGenerateSines

This function generates training and testing sets of simple sinusoidal signals with added Gaussian white noise.

function [X,T] = helperGenerateSines(numSignals)
% This function is only intended to support this example. 
% It may be changed or removed in a future release. 

% Specify train/valid/test partitioning
partSizes = [80 10 10]./100;
partNames = ["Train" "Valid" "Test"];

% Specify signal characteristics
seqLength = [1024 2048];  % Range of sequence lengths to consider
magRange = [0.5 5];       % Range of magnitudes
freqRange = [10 256];     % Range of frequencies (Hz)
numSinesPerSig = 5;       % Number of component sinusoids per signal

% Define SNR targets for each partition
SNR_targ.Train = [-5 -3 -1 0 2 4 6];
SNR_targ.Valid = [-5 -3 -1 0 2 4 6];
SNR_targ.Test  = -7:7;

% Define time domain
t = linspace(0,seqLength(2)/seqLength(1), ...
    seqLength(2));

% Loop over number of partitions
for ii = 1:length(partSizes)
    % Initialize arrays for T and N
    if ii == 1
        T.(partNames(ii)) = ...
            zeros(ceil(partSizes(ii)*numSignals),seqLength(2));
    else
        T.(partNames(ii)) = ...
            zeros(floor(partSizes(ii)*numSignals),seqLength(2));
    end
    N.(partNames(ii)) = cell(size(T.(partNames(ii)),1),1);

    % Setup for signal generation:
    % random frequency, amplitude, and phase
    freq = (2*pi).*(freqRange(1)+diff(freqRange).* ...
        rand(floor(partSizes(ii)*numSignals),numSinesPerSig));
    amp = magRange(1) + diff(magRange).*...
        rand(floor(partSizes(ii)*numSignals),numSinesPerSig);
    phase = (2*pi).*rand(floor(partSizes(ii)*numSignals),numSinesPerSig);
    
    % Generate target/clean signals
    for jj = 1:numSinesPerSig
        T.(partNames(ii)) = T.(partNames(ii)) + ...
            amp(:,jj).*sin(freq(:,jj)*t+phase(:,jj));
    end
    
    % Zero-center, transpose to column-oriented, 
    % and convert to cell array
    T.(partNames(ii)) = (T.(partNames(ii))-mean(T.(partNames(ii)),2))';
    T.(partNames(ii)) = mat2cell(T.(partNames(ii)), ...
        size(T.(partNames(ii)),1), ...
        ones(size(T.(partNames(ii)),2),1))';

    % Randomly truncate T to vary lengths
    idx = seqLength(1) + ...
        randi(diff(seqLength),length(T.(partNames(ii))),1);
    T.(partNames(ii)) = cellfun(@(c1,c2) c1(1:c2), ...
        T.(partNames(ii)), ...
        num2cell(idx), ...
        UniformOutput=false);

    % Generate noise (N) based on number of SNRin
    nSNR = length(SNR_targ.(partNames(ii)));
    idx = [floor(length(T.(partNames(ii)))/nSNR).* ...
        ones(1,nSNR).*(0:nSNR-1) length(T.(partNames(ii)))];
    for jj = 1:nSNR
        % Calculate power of noise required to achieve target SNR
        Pnoise_targ = 10^(-SNR_targ.(partNames(ii))(jj)/10).* ...
            cellfun(@(c1) sum(c1.^2)/length(c1), ...
            T.(partNames(ii))(idx(jj)+1:idx(jj+1)), ...
            UniformOutput=true);
        % Synthesize WGN, truncate to match length of T, 
        % Calculate current power of the noise
        WGN = cellfun(@(c1) randn(length(c1),1), ...
            T.(partNames(ii))(idx(jj)+1:idx(jj+1)), ...
            UniformOutput=false);
        Pnoise_curr = cellfun(@(c1) sum(c1.^2)/length(c1), ...
            WGN,UniformOutput=true);
        % Generate Noise according to target SNR
        N.(partNames(ii))(idx(jj)+1:idx(jj+1)) = ...
            cellfun(@(c1,c2) c1.*c2, ...
            num2cell(sqrt(Pnoise_targ./Pnoise_curr)), ...
            WGN,UniformOutput=false);
    end

    % Create noisy signals (X)
    X.(partNames(ii)) = cellfun(@(c1,c2) plus(c1,c2), ...
        T.(partNames(ii)), ...
        N.(partNames(ii)), ...
        UniformOutput=false );
end

% Shuffle via random permute
for ii = 1:length(partSizes)
    idx = randperm(length(X.(partNames(ii))));
    X.(partNames(ii)) = X.(partNames(ii))(idx);
    T.(partNames(ii)) = T.(partNames(ii))(idx);
end

end

See Also

Functions

Related Topics