function X = crossentropy(X, T, varargin)
%CROSSENTROPY    Cross-entropy loss for classification problems
%
%   Y = CROSSENTROPY(X,TARGETS) computes the cross-entropy loss of the
%   predictions X and the target values TARGETS for classification problems
%   with mutually-exclusive classes. X must be a formatted dlarray. The
%   input TARGETS can be a dlarray or numeric array. The size of each
%   dimension of TARGETS must match the size of the corresponding dimension
%   of X. If TARGETS is a formatted dlarray, its dimension format must
%   match the format of X. If TARGETS is an unformatted dlarray or a
%   numeric array, its size vector must exactly match the size vector of X
%   and the dimension format of X is implicitly applied to TARGETS. Output
%   Y is an unformatted scalar dlarray computed as the average logarithmic
%   loss across the batch dimension.
% 
%   Y = CROSSENTROPY(X,TARGETS,'TargetCategories',TARGETCATS) specifies the
%   relationship between the target categories and computes the
%   cross-entropy loss for the specified classification problem. TARGETCATS
%   must be a char array or string with one of the following values:
%   'exclusive' - (default) each observation in X is exclusively
%                 assigned to one category (single-label classification).
%   'independent' - each observation can be assigned to more than one
%                   independent category (multi-label classification).
%                   Cross-entropy loss for independent classes is also
%                   referred as binary cross-entropy loss.
%
%   Y = CROSSENTROPY(__,'DataFormat',FMT) computes the cross-entropy loss
%   of an unformatted dlarray or a numeric array X. FMT specifies the
%   dimension labels of the input data X. FMT must be a char array or
%   string. If TARGETS is a formatted dlarray, its dimension format must
%   match the format FMT. The size of each dimension of TARGETS must match
%   the size of the corresponding dimension of X. If TARGETS is an
%   unformatted dlarray or a numeric array, the size vector of TARGETS must
%   exactly match the size vector of X. FMT is implicitly applied to
%   TARGETS. If X is a numeric array, TARGETS must be a dlarray.
%
%   Example 1:
%       % Create input data as formatted dlarray containing 32 observations
%       % with unnormalized scores for ten output categories.
%       numCategories = 10; batchSize = 32; 
%       X = dlarray(rand(numCategories,batchSize),'CB'); 
%       % Convert unnormalized scores to probabilities of membership of
%       % each of the ten categories.
%       X = softmax(X); 
%       % Create target values as unformatted dlarrays with one-hot encoding
%       % for membership in the second category. 
%       targets = dlarray(zeros(numCategories,batchSize)); 
%       targets(2,:) = 1; 
%       % Compute cross-entropy loss between probability vectors X and 
%       % targets.
%       Z = crossentropy(X,targets);
%
%   Example 2:
%       % Create input data as unformatted dlarray containing 32
%       % observations with unnormalized scores for ten output categories.
%       numCategories = 10; batchSize = 32;
%       X = dlarray(rand(numCategories,batchSize));
%       % Convert unnormalized scores to probabilities of membership of
%       % each of the ten categories.
%       X = softmax(X,'DataFormat','CB');
%       % Create target values as numeric array with one-hot encoding for
%       % membership in the second category.
%       targets = zeros(numCategories,batchSize);
%       targets(2,:) = 1;
%       % Compute cross-entropy loss between probability vectors X and
%       % targets.
%       Z = crossentropy(X,targets,'DataFormat','CB');
%
%   Example 3:
%       % Create input data as an unformatted dlarray containing 32
%       % observations with unnormalized scores for ten output categories.
%       numCategories = 10; batchSize = 32; 
%       X = dlarray(rand(numCategories,batchSize),'CB'); 
%       % Convert unnormalized scores to probabilities of membership of
%       % each of the ten categories. 
%       X = sigmoid(X); 
%       % Create target values as numeric array for membership in the 
%       % second and sixth category. 
%       targets = zeros(numCategories,batchSize); 
%       targets(2,:) = 1; targets(6,:) = 1;
%       % Compute cross-entropy loss between probability vectors X and 
%       % targets for multi-label classification. 
%       Z = crossentropy(X,targets,'TargetCategories','independent');
%
%   See also DLARRAY, SOFTMAX, SIGMOID

%   Copyright 2019-2020 The MathWorks, Inc.

narginchk(2,6);

% Create a parser with required and optional parameters
[parser, args] = iCreateInputParser(varargin{:});
if ~isempty(parser)
    % Parse the arguments
    args = deep.internal.dlarray.parseArguments(parser, varargin{:});
end

% Handle input format
X = deep.internal.dlarray.validateDataFormatArg(X, args.DataFormat);

% Validate the optional cross-entropy type argument
hasIndependentTargetCategories = iValidateTargetCategories(args.TargetCategories);
if hasIndependentTargetCategories
    fun = @internal_bincrossentropy;
else
    fun = @internal_catcrossentropy;
end

% Validate true values and return them as dlarray
T = validateTrueValues(X, T, args.DataFormat);

% Internal API
X = deep.internal.dlarray.compareData(X, T, fun);

% Remove any permutation from the output dlarray
X.DataToViewMap = [];
X.ViewToDataMap = [];
% Remove dimensions from the dlarray
X = stripdims(X);

end


function [parser, defaultArgs] = iCreateInputParser(varargin)
% Default values
targetCategories  = 'exclusive';
defaultFormat = [];

defaultArgs = struct(...
    'TargetCategories', targetCategories, ...
    'DataFormat', defaultFormat);

if isempty(varargin)
    parser = [];
else
    % Create parser
    parser = inputParser;
    parser.addParameter('TargetCategories', targetCategories, @(x)validateattributes(x, {'char','string'}, {'nonempty'})); 
    parser.addParameter('DataFormat', defaultFormat, @(x)validateattributes(x, {'char','string'}, {'nonempty'})); 
end

end

function hasIndependentTargetCategories = iValidateTargetCategories(value)
value = validatestring(value,{'exclusive','independent'}, 'crossentropy','Target categories');
if strcmp(value,'exclusive')
    hasIndependentTargetCategories = false; % categorical cross-entropy
else
    hasIndependentTargetCategories = true; % binary cross-entropy
end
end
