Main Content

Convert Digit Recognition Neural Network to Fixed Point and Generate C Code

This example shows how to convert a neural network classification model in Simulink™ to fixed point using the Fixed-Point Tool and Lookup Table Optimizer. Following the conversion, you can generate C code using Simulink Coder.

Overview

Using the Fixed-Point Tool, you can convert a design from floating point to fixed point. Use the Lookup Table Optimizer to generate memory-efficient lookup table replacements for unbounded functions such as exp and log2. Using these tools, this example shows how to convert a trained floating-point neural network classification model to use embedded-efficient fixed-point data types.

Digit Classification and MNIST Dataset

MNIST handwritten digit dataset is a commonly used dataset in the field of neural networks. For an example showing a simple way to create a two-layered neural network using this dataset, see Artificial Neural Networks for Beginners.

Data and Neural Network Training

Download the training and test MNIST files according to the directions in Artificial Neural Networks for Beginners. Load the data and train the network.

%Load Data
tr = csvread('train.csv', 1, 0);                  % read train.csv
sub = csvread('test.csv', 1, 0);                  % read test.csv

% Prepare Data
n = size(tr, 1);                    % number of samples in the dataset
targets  = tr(:,1);                 % 1st column is |label|
targets(targets == 0) = 10;         % use '10' to present '0'
targetsd = dummyvar(targets);       % convert label into a dummy variable
inputs = tr(:,2:end);               % the rest of columns are predictors

inputs = inputs';                   % transpose input
targets = targets';                 % transpose target
targetsd = targetsd';               % transpose dummy variable

rng(1);                             % for reproducibility
c = cvpartition(n,'Holdout',n/3);   % hold out 1/3 of the dataset

Xtrain = inputs(:, training(c));    % 2/3 of the input for training
Ytrain = targetsd(:, training(c));  % 2/3 of the target for training
Xtest = inputs(:, test(c));         % 1/3 of the input for testing
Ytest = targets(test(c));           % 1/3 of the target for testing
Ytestd = targetsd(:, test(c));      % 1/3 of the dummy variable for testing

% Train Network
hiddenLayerSize = 100;
net = patternnet(hiddenLayerSize);

[net, tr] = train(net, Xtrain, Ytrain);
view(net);

outputs = net(Xtest);
errors = gsubtract(Ytest, outputs);
performance = perform(net, Ytest, outputs);

figure, plotperform(tr);

Close the view of the network.

nnet.guis.closeAllViews();

Model Preparation for Fixed-Point Conversion

After training the network, use the gensim function from the Deep Learning Toolbox™ to generate a Simulink model.

sys_name = gensim(net, 'Name', 'mTrainedNN');

The model generated by the gensim function contains the neural network with trained weights and biases. Prepare the trained neural network for conversion to fixed point by enabling signal logging at the output of the network, and adding input stimuli and verification blocks. The modified model is fxpdemo_mnist_classification.

Open and inspect the model.

model = 'fxpdemo_mnist_classification';
system_under_design = [model '/Pattern Recognition Neural Network'];
baseline_output = [model '/yarr'];
open_system(model);

To open the Fixed-Point Tool, right-click the Function Fitting Neural Network subsystem and select Fixed-Point Tool. Alternatively, use the command-line interface of the Fixed-Point Tool. The Fixed Point Tool and its command-line interface help you prepare your model for conversion, and convert your system to fixed point. You can use the Fixed-Point Tool to collect range and overflow instrumentation of objects in your model via simulation and range analysis. In this example, use the command-line interface of the Fixed-Point Tool to convert the neural network to fixed point.

converter = DataTypeWorkflow.Converter(system_under_design);

Run Simulation to Collect Ranges

Simulate the model with instrumentation to collect ranges. Enable instrumentation using the 'Range collection using double override' shortcut. Save the simulation run name for use in later steps.

converter.applySettingsFromShortcut('Range collection using double override');
collect_ranges = converter.CurrentRunName;
sim_out = converter.simulateSystem();

Plot the correct classification rate before the conversion to establish baseline behavior.

plotConfusionMatrix(sim_out, baseline_output, system_under_design, 'Classification rate before conversion');

Propose Fixed-Point Data Types

The Fixed-Point Tool uses range information obtained through simulation to propose fixed-point data types for blocks in the system under design. In this example, to ensure that the tools propose signed data types for all blocks in the subsystem, disable the ProposeSignedness option in the ProposalSettings object.

ps = DataTypeWorkflow.ProposalSettings;
converter.proposeDataTypes(collect_ranges, ps);

Apply Proposed Data Types

By default, the Fixed-Point Tool applies all of the proposed data types. Use the applyDataTypes method to apply the data types. If you want to only apply a subset of the proposals, in the Fixed-Point Tool use the Accept check box to specify the proposals that you want to apply.

converter.applyDataTypes(collect_ranges);

Verify Data Types

Proposed types should handle all possible inputs correctly. Set the model to simulate using the newly applied types, simulate the model, and observe that the neural network regression accuracy remains the same after the conversion.

converter.applySettingsFromShortcut('Range collection with specified data types');
sim_out = converter.simulateSystem();

Plot the correct classification rate of the fixed-point model.

plotConfusionMatrix(sim_out, baseline_output, system_under_design, 'Classification rate after fixed-point conversion');

Replace Activation Functions With an Optimized Lookup Table

For more efficient code, replace the Tanh Activation function in the first layer with either a lookup table or a CORDIC implementation. In this example, use the Lookup Table Optimizer to get a lookup table to replace tanh. In this example, specify EvenPow2Spacing for the breakpoint spacing for faster execution speed.

block_path = [system_under_design '/Layer 1/tansig'];
p = FunctionApproximation.Problem(block_path);
p.Options.WordLengths = 16;
p.Options.BreakpointSpecification = 'EvenPow2Spacing';
solution  = p.solve;
solution.replaceWithApproximate;
|  ID |  Memory (bits) | Feasible | Table Size | Breakpoints WLs | TableData WL | BreakpointSpecification |             Error(Max,Current) | 
|   0 |             64 |        0 |          2 |              16 |           16 |         EvenPow2Spacing |     7.812500e-03, 1.000000e+00 |
|   1 |           8224 |        1 |        512 |              16 |           16 |         EvenPow2Spacing |     7.812500e-03, 1.525879e-03 |
|   2 |           4128 |        1 |        256 |              16 |           16 |         EvenPow2Spacing |     7.812500e-03, 5.981445e-03 |
|   3 |           2080 |        0 |        128 |              16 |           16 |         EvenPow2Spacing |     7.812500e-03, 2.331543e-02 |

Best Solution
|  ID |  Memory (bits) | Feasible | Table Size | Breakpoints WLs | TableData WL | BreakpointSpecification |             Error(Max,Current) |
|   2 |           4128 |        1 |        256 |              16 |           16 |         EvenPow2Spacing |     7.812500e-03, 5.981445e-03 |

Follow the same steps to replace the exp function in the softmax implementation in the second layer with a lookup table.

block_path = [system_under_design '/Layer 2/softmax/Exp'];
p = FunctionApproximation.Problem(block_path);
p.Options.WordLengths = 16;
p.Options.BreakpointSpecification = 'EvenPow2Spacing';

To get an optimized lookup table, define finite lower and upper bounds for the inputs.

p.InputLowerBounds = -40;
p.InputUpperBounds = 0;
solution  = p.solve;
solution.replaceWithApproximate;
|  ID |  Memory (bits) | Feasible | Table Size | Breakpoints WLs | TableData WL | BreakpointSpecification |             Error(Max,Current) | 
|   0 |             64 |        0 |          2 |              16 |           16 |         EvenPow2Spacing |     7.812500e-03, 9.996643e-01 |
|   1 |           2608 |        1 |        161 |              16 |           16 |         EvenPow2Spacing |     7.812500e-03, 6.907394e-03 |
|   2 |           1328 |        0 |         81 |              16 |           16 |         EvenPow2Spacing |     7.812500e-03, 2.451896e-02 |

Best Solution
|  ID |  Memory (bits) | Feasible | Table Size | Breakpoints WLs | TableData WL | BreakpointSpecification |             Error(Max,Current) |
|   1 |           2608 |        1 |        161 |              16 |           16 |         EvenPow2Spacing |     7.812500e-03, 6.907394e-03 |

Verify model accuracy after replacing the functions with the lookup table approximations.

converter.applySettingsFromShortcut(converter.ShortcutsForSelectedSystem{2});
sim_out = converter.simulateSystem;

plotConfusionMatrix(sim_out, baseline_output, system_under_design, 'Classification rate after function replacement');

Generate C code

To generate C code, right-click the Function Fitting Neural Network subsystem, select C/C++ Code > Build Subsystem. Click the Build button when prompted for tunable parameters.

References

[1] LeCun, Y., C. Cortes, and C. J. C. Burges. "The MNIST Database of Handwritten Digits." http://yann.lecun.com/exdb/mnist/.

See Also

|