%% Code Generation for a Sequence-to-Sequence LSTM Network
% This example demonstrates how to generate CUDA(R) code for a long short-term
% memory (LSTM) network. The example generates a MEX application that makes 
% predictions at each step of an input timeseries. Two methods are
% demonstrated: a method using a standard LSTM network, and a method
% leveraging the stateful behavior of the same LSTM network.
% This example uses accelerometer sensor data from a 
% smartphone carried on the body and makes predictions on the activity of the
% wearer. User movements are classified into one of five categories,
% namely dancing, running, sitting, standing, and walking. The example uses
% a pretrained LSTM network. For more information on training, see the 
% <docid:nnet_ug#mw_a5ad1d19-0e12-4965-ae93-85f6fe8774e9 Sequence to Sequence Classification Using Deep Learning>
% example from Deep Learning Toolbox(TM).

% Copyright 2019 The MathWorks, Inc.

%% Third-Party Prerequisites
% *Required*
%
% This example generates CUDA MEX and has the following third-party 
% requirements.
%
% * CUDA enabled NVIDIA(R) GPU and compatible driver.
%
% *Optional*
%
% For non-MEX builds such as static, dynamic libraries or executables,
% this example has the following additional requirements.
%
% * NVIDIA toolkit.
% * NVIDIA cuDNN library.
% * Environment variables for the compilers and libraries. For more
% information, see
% <docid:gpucoder_gs#mw_aa8b0a39-45ea-4295-b244-52d6e6907bff Third-party
% Products> and <docid:gpucoder_gs#mw_453fbbd7-fd08-44a8-9113-a132ed383275
% Environment Variables>.

%% Verify GPU Environment
% Use the <docid:gpucoder_ref#mw_0957d820-192f-400a-8045-0bb746a75278 
% coder.checkGpuInstall> function to verify that the compilers and 
% libraries necessary for running this example are set up correctly.
envCfg = coder.gpuEnvConfig('host');
envCfg.DeepLibTarget = 'cudnn';
envCfg.DeepCodegen = 1;
envCfg.Quiet = 1;
coder.checkGpuInstall(envCfg);

%% The |lstmnet_predict| Entry-Point Function
% A sequence-to-sequence LSTM network enables you to make different 
% predictions for each individual time step of a data sequence. The 
% <matlab:edit(fullfile(matlabroot,'examples','deeplearning_shared','main','lstmnet_predict.m')) lstmnet_predict.m> 
% entry-point function takes an input sequence and passes it to a trained LSTM network 
% for prediction. Specifically, the function uses the LSTM network trained 
% in the _Sequence to Sequence Classification Using Deep Learning_ example. 
% The function loads the network object from the |lstmnet_predict.mat| 
% file into a persistent variable and reuses the persistent object on 
% subsequent prediction calls. 
%
% To display an interactive visualization of the network architecture and 
% information about the network layers, use the <docid:nnet_ref#mw_8d52b67d-b6b3-4c62-b573-56fdf4dce6a0 analyzeNetwork>
% function.
type('lstmnet_predict.m')

%% Generate CUDA MEX
% To generate CUDA MEX for the |lstmnet_predict.m| entry-point function, create a
% GPU configuration object and specify the target to be MEX. Set the target
% language to C++. Create a deep learning configuration
% object that specifies the target library as cuDNN. Attach this deep
% learning configuration object to the GPU configuration object.
cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');

%%
% At compile time, GPU Coder(TM) must know the data types of all the inputs to 
% the entry-point function. Specify the type and size of the input argument to 
% the <docid:coder_ref#br46oyi-1 codegen> command by using the 
% <docid:coder_ref#bsu_35x coder.typeof> function. For this example, the input is 
% of double data type with a feature dimension value of three and a variable 
% sequence length. Specifying the sequence length as variable-sized enables us
% to perform prediction on an input sequence of any length.
matrixInput = coder.typeof(double(0),[3 Inf],[false true]);

%%
% Run the codegen command.
codegen -config cfg lstmnet_predict -args {matrixInput} -report

%% Run Generated MEX on Test Data
% Load the |HumanActivityValidate| MAT-file. This MAT-file stores the variable 
% |XValidate| that contains sample timeseries of sensor readings on which
% you can test the generated code. Call |lstmnet_predict_mex| on the first observation.
load HumanActivityValidate
YPred1 = lstmnet_predict_mex(XValidate{1});

%%
% |YPred1| is a 5-by-53888 numeric matrix containing the probabilities
% of the five classes for each of the 53888 time steps. For each time step, find
% the predicted class by calculating the index of the maximum probability.
[~, maxIndex] = max(YPred1, [], 1);

%%
% Associate the indices of max probability to the corresponding label.
% Display the first ten labels. From the results, you can see that the
% network predicted the human to be sitting for the first ten time steps.
labels = categorical({'Dancing', 'Running', 'Sitting', 'Standing', 'Walking'});
predictedLabels1 = labels(maxIndex);
disp(predictedLabels1(1:10))

%% Compare Predictions with Test Data
% Use a plot to compare the MEX output data with the test data.
figure
plot(predictedLabels1,'.-');
hold on
plot(YValidate{1});
hold off

xlabel("Time Step")
ylabel("Activity")
title("Predicted Activities")
legend(["Predicted" "Test Data"])

%% Call Generated MEX on an Observation with a Different Sequence Length
% Call |lstmnet_predict_mex| on the second observation with a different sequence length. 
% In this example, |XValidate{2}| has a sequence length of 64480
% whereas |XValidate{1}| had a sequence length of 53888.
% The generated code handles prediction correctly because we specified the 
% sequence length dimension to be variable-size.
YPred2 = lstmnet_predict_mex(XValidate{2});
[~, maxIndex] = max(YPred2, [], 1);
predictedLabels2 = labels(maxIndex);
disp(predictedLabels2(1:10))

%% Generate MEX that takes in Multiple Observations
% If you want to perform prediction on many observations at once, you can
% group the observations together in a cell array and pass the cell array 
% for prediction. The cell array must be a column cell array, and each cell
% must contain one observation. Each observation must have the same feature
% dimension, but the sequence lengths may vary.
% In this example, |XValidate| contains five observations. To generate
% a MEX that can take |XValidate|
% as input, specify the input
% type to be a 5-by-1 cell array. Further, specify that each cell
% be of the same type as |matrixInput|, the type you specified
% for the single observation in the previous |codegen| command.
matrixInput = coder.typeof(double(0),[3 Inf],[false true]);
cellInput = coder.typeof({matrixInput}, [5 1]);

codegen -config cfg lstmnet_predict -args {cellInput} -report

YPred3 = lstmnet_predict_mex(XValidate);

%%
% The output is a 5-by-1 cell array of predictions for the
% five observations passed in.

disp(YPred3)

%% Generate MEX with Stateful LSTM
% Instead of passing the entire timeseries to predict in one step, we can
% run prediction on an input by streaming in one timestep at a time, making use of the
% function 
% <docid:nnet_ref#mw_e64205e2-5297-4ff5-ad37-0d5375e19ef2 predictAndUpdateState>
% This function takes in an input, produces an
% output prediction, and updates the internal state of the network so that 
% future predictions take this initial input into account.
%
% The entry-point function 
% <matlab:edit(fullfile(matlabroot,'examples','deeplearning_shared',main','lstmnet_predict_and_update.m')) lstmnet_predict_and_update.m> 
% takes in a single-timestep input and processes the input using the
% <docid:nnet_ref#mw_e64205e2-5297-4ff5-ad37-0d5375e19ef2 predictAndUpdateState> 
% function. |predictAndUpdateState| outputs a prediction for the input timestep and updates
% the network so that subsequent inputs are treated as subsequent timesteps
% of the same sample.
% After passing in all timesteps one at a time, the resulting output is the 
% same as if all timesteps were passed in as a single input.
type('lstmnet_predict_and_update.m')

%%
% Run codegen on this new design file. Since we are taking in a single timestep each call,
% we specify |matrixInput| to have a fixed sequence dimension of 1 instead
% of a variable sequence length.
matrixInput = coder.typeof(double(0),[3 1]);
codegen -config cfg lstmnet_predict_and_update -args {matrixInput} -report

%%
% Run the generated MEX on the first validation sample's first timestep.
firstSample = XValidate{1};
firstTimestep = firstSample(:,1);
YPredStateful = lstmnet_predict_and_update_mex(firstTimestep);
[~, maxIndex] = max(YPredStateful, [], 1);
predictedLabelsStateful1 = labels(maxIndex)

%% 
% Compare the output label with the ground truth.
YValidate{1}(1)