Main Content

Compress Deep Learning Network for Battery State of Charge Estimation

Since R2024b

This example shows how to compress a neural network for predicting the state of charge of a battery using projection.

Neural networks can take up large amounts of memory. If you have a memory requirement for your network, for example because you want to embed it into a resource-constrained hardware target, then you might need to compress your model to meet the requirement. Compressing a network reduces the size of the network in memory whilst aiming to maintain overall performance. For example, in the following image, you can see an example where compressing a network using first pruning and then projection reduces the number of learnables and has little impact on the root mean squared error (RMSE).

This example is the fourth step in a series of examples that take you through a battery state of charge estimation workflow. You can run each step independently or work through the steps in order. This example follows the Train Deep Learning Network for Battery State of Charge Estimation example. For more information about the full workflow, see Battery State of Charge Estimation Using Deep Learning.

Load the test data. If you have run the previous step, then load the data you saved in the previous step. Otherwise, the example prepares the data as shown in Prepare Data for Battery State of Charge Estimation Using Deep Learning.

if ~exist("XTrain","var") || ~exist("YTrain","var") || ~exist("XVal","var") || ~exist("YVal","var")
    [XTrain,YTrain,XVal,YVal] = prepareBSOCData;
end

Load a pretrained network. If you have run the previous step, then the example uses your trained network. Otherwise, load a pretrained network. This network has been trained using the steps shown in Train Deep Learning Network for Battery State of Charge Estimation.

if ~exist("recurrentNet")
    load("pretrainedBSOCNetwork.mat")
end

This example requires the Deep Learning Toolbox™ Model Quantization Library support package. This support package is a free add-on that you can download using the Add-On Explorer.

Explore Compression Levels

Projection is a layer-wise compression technique that replaces a large layer with one or more layers with a smaller total number of parameters.

The compressNetworkUsingProjection function uses principal component analysis (PCA) to identify the subspace of learnable parameters that result in the highest variance in neuron activations. Then, the function projects the layer onto this lower-dimensional subspace and performs the layer operation within the lower-dimensional space.

The PCA step can be computationally intensive. If you expect to compress the same network multiple times (for example, when exploring different levels of compression), then perform the PCA step first using the neuronPCA function, then pass the output of this function to the compressNetworkUsingProjection function as an input argument.

The neuronPCA function analyzes the network activations using a data set of training data, or a representative subset thereof. This analysis requires only the predictors of the training data to compute the network activations. It does not require the training targets.

Create a mini-batch queue containing the training data.

mbq = minibatchqueue(...
    arrayDatastore(XTrain',IterationDimension=1,OutputType="same",ReadSize=numel(XTrain)), ...
    MiniBatchSize=32, ...
    MiniBatchFormat="TCB", ...
    MiniBatchFcn=@(X) cat(3,X{:}));

Create the neuronPCA object. To view information about the steps of the neuron PCA algorithm, set the VerbosityLevel option to "steps".

npca = neuronPCA(recurrentNet,mbq,VerbosityLevel="steps");
Using solver mode "direct".
Computing covariance matrices for activations connected to: "lstm_1/in","lstm_1/out","lstm_2/in","lstm_2/out","fc/in","fc/out"
Computing eigenvalues and eigenvectors for activations connected to: "lstm_1/in","lstm_1/out","lstm_2/in","lstm_2/out","fc/in","fc/out"
neuronPCA analyzed 3 layers: "lstm_1","lstm_2","fc"

View the properties of the neuronPCA object.

npca
npca = 
  neuronPCA with properties:

                  LayerNames: ["lstm_1"    "lstm_2"    "fc"]
      ExplainedVarianceRange: [0 1]
    LearnablesReductionRange: [0.8525 0.9884]
            InputEigenvalues: {[3×1 double]  [256×1 double]  [128×1 double]}
           InputEigenvectors: {[3×3 double]  [256×256 double]  [128×128 double]}
           OutputEigenvalues: {[256×1 double]  [128×1 double]  [1.0907]}
          OutputEigenvectors: {[256×256 double]  [128×128 double]  [1]}

The explained variance of a network details how well the space of network activations can capture the underlying features of the data. To explore different amounts of compression, iterate over different values of the ExplainedVarianceGoal option of the compressNetworkUsingProjection function and compare the results. Alternatively, you can sweep over the learnable reduction goal.

numValues = 10;
explainedVarGoal = 1 - [0 logspace(-3,0,numValues)];
explainedVariance = zeros(1,numValues);
learnablesReduction = zeros(1,numValues);
valRMSE = zeros(1,numValues);

for i = 1:numValues
    varianceGoal = explainedVarGoal(i);

    [trialNetwork,info] = compressNetworkUsingProjection(recurrentNet,npca, ...
        ExplainedVarianceGoal=varianceGoal, ...
        VerbosityLevel="off");

    explainedVariance(i) = info.ExplainedVariance;
    learnablesReduction(i) = info.LearnablesReduction;
    valRMSE(i) = testnet(trialNetwork,XVal,YVal,"rmse");
end

Plot the RMSE of the compressed networks against their explained variance goal.

figure
tiledlayout("flow")
nexttile
plot(learnablesReduction,valRMSE,'*-')
ylabel("RMSE")
xscale("log")
title("Effect of Different Compression Levels")

nexttile
plot(learnablesReduction,explainedVariance,'*-')
ylim([0.8 1])
ylabel("Explained Variance")
xlabel("Learnable Reduction")
xscale("log")

Figure contains 2 axes objects. Axes object 1 with title Effect of Different Compression Levels, ylabel RMSE contains an object of type line. Axes object 2 with xlabel Learnable Reduction, ylabel Explained Variance contains an object of type line.

The graph shows that an increase in learnable reduction has a corresponding increase in RMSE (decrease in accuracy). A learnable reduction value of around 96% shows a good compromise between the amount of compression and RMSE.

If you require more compression than that, specify a higher learnables reduction and then retrain the model after projection to fine-tune it. Fine-tuning the model can regain significant amounts of accuracy lost in the projection step.

Compress Network Using Projection

Compress the network using the neuronPCA object with a learnable reduction goal of 96% using the compressNetworkUsingProjection function. To ensure that the projected network supports library-free code generation, specify that the projected layers are unpacked.

recurrentNetProjected = compressNetworkUsingProjection(recurrentNet,npca, ...
    LearnablesReductionGoal=0.96, ...
    UnpackProjectedLayers=true);
Compressed network has 96.0% fewer learnable parameters.
Projection compressed 3 layers: "lstm_1","lstm_2","fc"

Compare Original and Compressed Networks

Evaluate the projected network´s performance on the test data set.

RMSE = testnet(recurrentNet,XVal,YVal,"rmse")
RMSE = 
0.0333
RMSEProjected = testnet(recurrentNetProjected,XVal,YVal,"rmse")
RMSEProjected = 
0.0360

Compressing the network has negligible impact on the RMSE of the predictions.

Compare the error and model size of the original and compressed networks.

learnables = numLearnables(recurrentNet);
learnablesProjected = numLearnables(recurrentNetProjected);
% Plot root mean squared error.
figure
tiledlayout(1,2)
nexttile
bar([RMSE RMSEProjected])
xticklabels(["Original" "Projected"])
ylabel("RMSE")

% Plot numbers of learnables.
nexttile
bar([learnables learnablesProjected])
xticklabels(["Original" "Projected"])
ylabel("Number of Learnables")

Figure contains 2 axes objects. Axes object 1 with ylabel RMSE contains an object of type bar. Axes object 2 with ylabel Number of Learnables contains an object of type bar.

Compared to the original network, the projected network has a significantly smaller size and has only slightly less prediction accuracy. Smaller networks typically have a reduced inference time.

Test Compressed Model Predictions

Test the model predictions on unseen test data. Specify the temperature range to test over. This example tests the model on data at the temperatures -10, 0, 10, and 25 degrees Celsius.

temp = ["n10" "0" "10" "25"];

Use the minibatchpredict function to generate predictions for each temperature.

dataTrue = [];
YPred = [];
RMSE = [];

for i = 1:4

% Load test data.
filename = "BSOC_" + temp(i) + "_degrees.mat";
testFile = fullfile("BSOCTestingData",filename);
dataTrue{i} = load(testFile);

load("BSOCTrainingMaxMinStats.mat")
dataTrue{i}.X = rescale(dataTrue{i}.X,InputMin=minX,InputMax=maxX);

% Generate predictions.
YPred{i} = minibatchpredict(recurrentNet,dataTrue{i}.X);
YPredCompressed{i} = minibatchpredict(recurrentNetProjected,dataTrue{i}.X);

end

Plot the predictions from each network and the measured values. You can see that the compressed model predicts the true value well.

figure
tiledlayout("flow")
for i = 1:4
    nexttile
plot(dataTrue{i}.Y); 
hold on;
plot(YPredCompressed{i});
hold off
xlabel("Sample")
ylabel("Y")
if i == 4
legend(["True" "Predicted (Compressed)"],Location="southeast")
end
title("Temperature: " + temp(i) + "°C")
end

Figure contains 4 axes objects. Axes object 1 with title Temperature: n10°C, xlabel Sample, ylabel Y contains 2 objects of type line. Axes object 2 with title Temperature: 0°C, xlabel Sample, ylabel Y contains 2 objects of type line. Axes object 3 with title Temperature: 10°C, xlabel Sample, ylabel Y contains 2 objects of type line. Axes object 4 with title Temperature: 25°C, xlabel Sample, ylabel Y contains 2 objects of type line. These objects represent True, Predicted (Compressed).

Save Network

Save the compressed neural network.

useCompressedNetwork = true;

if useCompressedNetwork
    recurrentNet = recurrentNetProjected;
end

Supporting Functions

numLearnables

The numLearnables function receives a network as input and returns the total number of learnables in that network.

function N = numLearnables(net)
    N = 0;
    for i = 1:size(net.Learnables,1)
        N = N + numel(net.Learnables.Value{i});
    end
end

Next step: Test Deep Learning Network for Battery State of Charge Estimation. You can also open the next example using the openExample function.

openExample("deeplearning_shared/TestModelForBatteryStateOfChargeEstimationExample")

See Also

|

Related Topics