Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding MultiLabel SoftMargin Loss #2345

Merged
merged 56 commits into from
Aug 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
e1eb983
Initial work. Forward/ Backward left.
iamshnoo Mar 24, 2020
7269357
Merge remote-tracking branch 'upstream/master' into multilabel_softma…
iamshnoo Mar 25, 2020
a3c3bb7
Completed Forward and Backward. Tests left.
iamshnoo Mar 31, 2020
92af440
Merge remote-tracking branch 'upstream/master' into multilabel_softma…
iamshnoo Mar 31, 2020
b2604b0
Fix style errors.
iamshnoo Mar 31, 2020
fc6335d
Fix weird indentation issue.
iamshnoo Apr 1, 2020
b6275ec
Manually fix the indentation in browser.
iamshnoo Apr 1, 2020
b42c360
Shift line 39 by 4 spaces to left
iamshnoo Apr 1, 2020
90c48ae
Shift line 39 by 2 spaces to the right
iamshnoo Apr 1, 2020
c0a3ef6
Corrected errors. Wrote test cases.
iamshnoo Apr 4, 2020
194f3ed
Merge remote-tracking branch 'upstream/master' into multilabel_softma…
iamshnoo Apr 4, 2020
c8280f1
Fix style errors.
iamshnoo Apr 4, 2020
6783aa7
Correct minor errors in backward function.
iamshnoo Apr 4, 2020
71e1a89
Resolve namespace error.
iamshnoo Apr 4, 2020
43d3506
Re-wrote backward correctly, updated tests
iamshnoo Apr 4, 2020
a346c1a
Fix typo
iamshnoo Apr 4, 2020
aa4b596
Fix errors in backward, tests. Update history.
iamshnoo Apr 5, 2020
503185b
Merge remote-tracking branch 'upstream/master'
iamshnoo Apr 5, 2020
1c43613
Attempt to fix merge conflict
iamshnoo Apr 5, 2020
b06486f
Merge remote-tracking branch 'upstream/master'.
iamshnoo Apr 5, 2020
67ec433
Prettify if-else construct.
iamshnoo Apr 5, 2020
6ec768f
Remove unnecesary spaces.
iamshnoo Apr 5, 2020
ab55a5e
Fix documentation.
iamshnoo Apr 6, 2020
697944c
Fix some changes requested in review (part 1).
iamshnoo Apr 7, 2020
70eb8bc
Minor fixes.
iamshnoo Apr 7, 2020
81fece7
Typo fix.
iamshnoo Apr 7, 2020
9521457
Temporary fix.
iamshnoo Apr 7, 2020
ba11c43
My fixes didn't fix anything.
iamshnoo Apr 7, 2020
3089f63
Some more typos. Should work okay now!
iamshnoo Apr 7, 2020
9e4da86
Merge remote-tracking branch 'upstream/master'.
iamshnoo Apr 7, 2020
17806e0
Adjust tolerance value for CheckMatrices.
iamshnoo Apr 7, 2020
e2eef8a
Fix to match format suggested in review.
iamshnoo Apr 7, 2020
0ddda95
Change weight mat to rowvec. Overload constructor.
iamshnoo Apr 9, 2020
86fec68
Merge 'upstream/master' and resolve conflicts.
iamshnoo Apr 9, 2020
43d91db
Fix typo.
iamshnoo Apr 9, 2020
1c1d33d
Apply suggestions from code review.
iamshnoo Apr 9, 2020
c395ed3
Correctly pass parameter weights in constructor.
iamshnoo Apr 9, 2020
eb17f31
Merge branch 'multilabel_softmargin_loss' of https://github.com/iamsh…
iamshnoo Apr 9, 2020
dcd02d9
Update tests. Minor fixes.
iamshnoo Apr 11, 2020
a450f71
Merge recent upstream changes.
iamshnoo Apr 22, 2020
56ce6fa
Fix tolerance value issue with PyTorch print trick
iamshnoo Apr 22, 2020
f298c7a
Re-design the API as per discussion.
iamshnoo May 5, 2020
9789fde
Merge recent upstream changes.
iamshnoo May 5, 2020
d3068cc
Fix some style issues.
iamshnoo May 5, 2020
4782809
Re-factor test cases.
iamshnoo May 5, 2020
4541966
Merge recent upstream changes into branch.
iamshnoo May 8, 2020
6470ae8
Adding .vscode to .gitignore
iamshnoo May 18, 2020
34153d5
Merge upstream/master into this branch.
iamshnoo May 18, 2020
c5a24d6
Add space between lines in HISTORY.md
iamshnoo May 18, 2020
9b2a3ac
Merge usptream changes.
iamshnoo May 31, 2020
6340e64
Apply suggestions from code review
iamshnoo May 31, 2020
77463d8
Fix bug in Forward().
iamshnoo Jun 1, 2020
733db6a
Merge upstream
jeffin143 Feb 21, 2021
180f917
Merge upstream
jeffin143 Feb 21, 2021
ebaa6c3
Merge branch 'master' into multilabel_softmargin_loss
jeffin143 Feb 28, 2021
bf61e97
Merge branch 'master' into multilabel_softmargin_loss
jeffin143 Aug 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
### mlpack ?.?.?
###### ????-??-??
* Added `Multi Label Soft Margin Loss` loss function for neural networks
(#2345).

* Added Decision Tree Regressor (#2905). It can be used using the class
`mlpack::tree::DecisionTreeRegressor`. It is accessible only though C++.

Expand Down
2 changes: 2 additions & 0 deletions src/mlpack/methods/ann/loss_functions/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ set(SOURCES
mean_squared_error_impl.hpp
mean_squared_logarithmic_error.hpp
mean_squared_logarithmic_error_impl.hpp
multilabel_softmargin_loss.hpp
multilabel_softmargin_loss_impl.hpp
negative_log_likelihood.hpp
negative_log_likelihood_impl.hpp
poisson_nll_loss.hpp
Expand Down
114 changes: 114 additions & 0 deletions src/mlpack/methods/ann/loss_functions/multilabel_softmargin_loss.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* @file methods/ann/loss_functions/multilabel_softmargin_loss.hpp
* @author Anjishnu Mukherjee
*
* Definition of the Multi Label Soft Margin Loss function.
iamshnoo marked this conversation as resolved.
Show resolved Hide resolved
*
* It is a criterion that optimizes a multi-label one-versus-all loss based on
* max-entropy, between input x and target y of size (N, C) where N is the
* batch size and C is the number of classes.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_ANN_LOSS_FUNCTION_MULTILABEL_SOFTMARGIN_LOSS_HPP
#define MLPACK_ANN_LOSS_FUNCTION_MULTILABEL_SOFTMARGIN_LOSS_HPP

#include <mlpack/prereqs.hpp>

namespace mlpack {
namespace ann /** Artificial Neural Network. */ {

/**
* @tparam InputDataType Type of the input data (arma::colvec, arma::mat,
* arma::sp_mat or arma::cube).
* @tparam OutputDataType Type of the output data (arma::colvec, arma::mat,
* arma::sp_mat or arma::cube).
*/
template <
typename InputDataType = arma::mat,
typename OutputDataType = arma::mat
>
class MultiLabelSoftMarginLoss
{
public:
/**
* Create the MultiLabelSoftMarginLoss object.
*
* @param reduction Specifies the reduction to apply to the output. If false,
* 'mean' reduction is used, where sum of the output will be
* divided by the number of elements in the output. If
* true, 'sum' reduction is used and the output will be
* summed. It is set to true by default.
* @param weights A manual rescaling weight given to each class. It is a
* (1, numClasses) row vector.
*/
MultiLabelSoftMarginLoss(const bool reduction = true,
const arma::rowvec& weights = arma::rowvec());

/**
* Computes the Multi Label Soft Margin Loss function.
*
* @param input Input data used for evaluating the specified function.
* @param target The target vector with same shape as input.
*/
template<typename InputType, typename TargetType>
typename InputType::elem_type Forward(const InputType& input,
const TargetType& target);

/**
* Ordinary feed backward pass of a neural network.
*
* @param input The propagated input activation.
* @param target The target vector.
* @param output The calculated error.
*/
template<typename InputType, typename TargetType, typename OutputType>
void Backward(const InputType& input,
const TargetType& target,
OutputType& output);

//! Get the output parameter.
OutputDataType& OutputParameter() const { return outputParameter; }
//! Modify the output parameter.
OutputDataType& OutputParameter() { return outputParameter; }

//! Get the weights assigned to each class.
const arma::rowvec& ClassWeights() const { return classWeights; }
//! Modify the weights assigned to each class.
arma::rowvec& ClassWeights() { return classWeights; }

//! Get the type of reduction used.
bool Reduction() const { return reduction; }
//! Modify the type of reduction used.
bool& Reduction() { return reduction; }

/**
* Serialize the layer.
*/
template<typename Archive>
void serialize(Archive& ar, const unsigned int /* version */);

private:
//! Locally-stored output parameter object.
OutputDataType outputParameter;

//! The boolean value that tells if reduction is sum or mean.
bool reduction;

//! A (1, numClasses) shaped vector with weights for each class.
arma::rowvec classWeights;

// An internal parameter used during initialisation of class weights.
bool weighted;
}; // class MultiLabelSoftMarginLoss

} // namespace ann
} // namespace mlpack

// include implementation.
#include "multilabel_softmargin_loss_impl.hpp"

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @file methods/ann/loss_functions/multilabel_softmargin_loss_impl.hpp
* @author Anjishnu Mukherjee
*
* Implementation of the Multi Label Soft Margin Loss function.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_ANN_LOSS_FUNCTION_MULTILABEL_SOFTMARGIN_LOSS_IMPL_HPP
#define MLPACK_METHODS_ANN_LOSS_FUNCTION_MULTILABEL_SOFTMARGIN_LOSS_IMPL_HPP

// In case it hasn't been included.
#include "multilabel_softmargin_loss.hpp"

namespace mlpack {
namespace ann /** Artifical Neural Network. */ {

template<typename InputDataType, typename OutputDataType>
MultiLabelSoftMarginLoss<InputDataType, OutputDataType>::
MultiLabelSoftMarginLoss(
const bool reduction,
const arma::rowvec& weights) :
reduction(reduction),
weighted(false)
{
if (weights.n_elem)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be easier to understand what happens here, if we provide a second constructor to set the weights. Let me know what you think.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi zoq, if you look at commit f298c7a, on May 5th, there I re-designed the API of this function to the current format after a long discussion above with @kartikdutt18. The conclusion of that discussion can be seen in this comment.

So, I think initially I had done what you are suggesting (let me know if you mean otherwise) using a overloaded constructor to set the weights. But then Kartik wanted me to design the API like this.

I am okay with both. Even though I would prefer the approach with an overloaded constructor

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I am guessing the other constructor would also have weights as param and probably a reduction also as param. It might ve redundant to have two constructors with same parameters. We would only have one forward function so we would have take into account both cases. For initialization we check if the vector is empty, if yes, we set reset to true and in forward pass we initialize the weights equal to input features and we set reset to false so we don't initialize every iteration. If weights are passed then we set reset to false and copy weights so no initialization in forward pass. Maybe I missed something, let me know what you think.
Regards.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of API wouldn't an overloaded constructor be redundant. I am guessing one constructor will be with parameter reduction and one with both reduction and weights parameter. We can simply have one constructor and set weights to default value and in forward pass set them to 1 only in the first pass. This allows us to have a forward function which doesn't depend on which constructors object we are dealing with. I have left a comment explaining something similar.

{
classWeights = weights;
weighted = true;
}
}

template<typename InputDataType, typename OutputDataType>
template<typename InputType, typename TargetType>
typename InputType::elem_type
MultiLabelSoftMarginLoss<InputDataType, OutputDataType>::Forward(
const InputType& input, const TargetType& target)
{
if (!weighted)
{
classWeights.ones(1, input.n_cols);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could go into the other constructor, see comment above, that way we don't set this in each call of forward.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree! Detailed comment above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I suggested using a reset parameter so this initialized only in the first pass. I am using my phone so sorry I can't take a look at the code. Similar thing is done in adaptive pooling layers. Regards.

weighted = true;
}

InputType logSigmoid = arma::log((1 / (1 + arma::exp(-input))));
InputType logSigmoidNeg = arma::log(1 / (1 + arma::exp(input)));
iamshnoo marked this conversation as resolved.
Show resolved Hide resolved
InputType loss = arma::mean(arma::sum(-(target % logSigmoid +
(1 - target) % logSigmoidNeg)) % classWeights, 1);

if (reduction)
return arma::as_scalar(loss);

return arma::as_scalar(loss / input.n_rows);
}

template<typename InputDataType, typename OutputDataType>
template<typename InputType, typename TargetType, typename OutputType>
void MultiLabelSoftMarginLoss<InputDataType, OutputDataType>::Backward(
const InputType& input,
const TargetType& target,
OutputType& output)
{
output.set_size(size(input));
InputType sigmoid = (1 / (1 + arma::exp(-input)));
output = -(target % (1 - sigmoid) - (1 - target) % sigmoid) %
arma::repmat(classWeights, target.n_rows, 1) / output.n_elem;

if (reduction)
output = output * input.n_rows;
}

template<typename InputDataType, typename OutputDataType>
template<typename Archive>
void MultiLabelSoftMarginLoss<InputDataType, OutputDataType>::serialize(
Archive& ar,
const unsigned int /* version */)
{
ar(CEREAL_NVP(classWeights));
ar(CEREAL_NVP(reduction));
}

} // namespace ann
} // namespace mlpack

#endif
117 changes: 117 additions & 0 deletions src/mlpack/tests/loss_functions_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <mlpack/methods/ann/loss_functions/hinge_embedding_loss.hpp>
#include <mlpack/methods/ann/loss_functions/cosine_embedding_loss.hpp>
#include <mlpack/methods/ann/loss_functions/l1_loss.hpp>
#include <mlpack/methods/ann/loss_functions/multilabel_softmargin_loss.hpp>
#include <mlpack/methods/ann/loss_functions/soft_margin_loss.hpp>
#include <mlpack/methods/ann/loss_functions/mean_absolute_percentage_error.hpp>
#include <mlpack/methods/ann/loss_functions/triplet_margin_loss.hpp>
Expand Down Expand Up @@ -1033,3 +1034,119 @@ TEST_CASE("HingeLossTest", "[LossFunctionsTest]")
REQUIRE(output.n_rows == input.n_rows);
REQUIRE(output.n_cols == input.n_cols);
}

/**
* Simple test for the MultiLabel Softmargin Loss function.
*/
TEST_CASE("MultiLabelSoftMarginLossTest", "[LossFunctionsTest]")
{
arma::mat input, target, output, expectedOutput;
double loss;
MultiLabelSoftMarginLoss<> module1;
MultiLabelSoftMarginLoss<> module2(false);

input = arma::mat("0.1778 0.0957 0.1397 0.1203 0.2403 0.1925 -0.2264 -0.3400 "
"-0.3336");
target = arma::mat("0 1 0 1 0 0 0 0 1");
input.reshape(3, 3);
target.reshape(3, 3);

// Test for sum reduction.

// Calculated using torch.nn.MultiLabelSoftMarginLoss(reduction='sum').
expectedOutput = arma::mat("0.1814 -0.1587 0.1783 -0.1567 0.1866 0.1827 "
"0.1479 0.1386 -0.1942");
expectedOutput.reshape(3, 3);

// Test the Forward function. Loss should be 2.14829.
// Value calculated using torch.nn.MultiLabelSoftMarginLoss(reduction='sum').
loss = module1.Forward(input, target);
REQUIRE(loss == Approx(2.14829).epsilon(1e-5));

// Test the Backward function.
module1.Backward(input, target, output);
REQUIRE(arma::as_scalar(arma::accu(output)) ==
Approx(0.505909).epsilon(1e-5));
REQUIRE(output.n_rows == input.n_rows);
REQUIRE(output.n_cols == input.n_cols);
CheckMatrices(output, expectedOutput, 0.1);

// Test for mean reduction.

// Calculated using torch.nn.MultiLabelSoftMarginLoss(reduction='mean').
expectedOutput = arma::mat("0.0605 -0.0529 0.0594 -0.0522 0.0622 0.0609 "
"0.0493 0.0462 -0.0647");
expectedOutput.reshape(3, 3);

// Test the Forward function. Loss should be 0.716095.
// Value calculated using torch.nn.MultiLabelSoftMarginLoss(reduction='mean').
loss = module2.Forward(input, target);
REQUIRE(loss == Approx(0.716095).epsilon(1e-5));

// Test the Backward function.
module2.Backward(input, target, output);
REQUIRE(arma::as_scalar(arma::accu(output)) ==
Approx(0.168636).epsilon(1e-5));
REQUIRE(output.n_rows == input.n_rows);
REQUIRE(output.n_cols == input.n_cols);
CheckMatrices(output, expectedOutput, 0.1);
}

/**
* Simple test for the MultiLabel Softmargin Loss function.
*/
TEST_CASE("MultiLabelSoftMarginLossWeightedTest", "[LossFunctionsTest]")
{
arma::mat input, target, output, expectedOutput;
arma::rowvec weights;
double loss;
weights = arma::mat("1 2 3");
MultiLabelSoftMarginLoss<> module1(true, weights);
MultiLabelSoftMarginLoss<> module2(false, weights);

input = arma::mat("0.1778 0.0957 0.1397 0.2256 0.1203 0.2403 0.1925 0.3144 "
"-0.2264 -0.3400 -0.3336 -0.8695");
target = arma::mat("0 1 0 1 1 0 0 0 0 0 1 0");
input.reshape(4, 3);
target.reshape(4, 3);

// Test for sum reduction.

// Calculated using torch.nn.MultiLabelSoftMarginLoss(reduction='sum').
expectedOutput = arma::mat("0.1814 -0.1587 0.1783 -0.1479 -0.3133 0.3732 "
"0.3653 0.3853 0.4436 0.4158 -0.5826 0.2954");
expectedOutput.reshape(4, 3);

// Test the Forward function. Loss should be 5.35057.
// Value calculated using torch.nn.MultiLabelSoftMarginLoss(reduction='sum').
loss = module1.Forward(input, target);
REQUIRE(loss == Approx(5.35057).epsilon(1e-5));

// Test the Backward function.
module1.Backward(input, target, output);
REQUIRE(arma::as_scalar(arma::accu(output)) ==
Approx(1.43577).epsilon(1e-5));
REQUIRE(output.n_rows == input.n_rows);
REQUIRE(output.n_cols == input.n_cols);
CheckMatrices(output, expectedOutput, 0.1);

// Test for mean reduction.

// Calculated using torch.nn.MultiLabelSoftMarginLoss(reduction='mean').
expectedOutput = arma::mat("0.0454 -0.0397 0.0446 -0.0370 -0.0783 0.0933 "
"0.0913 0.0963 0.1109 0.1040 -0.1457 0.0738");
expectedOutput.reshape(4, 3);

// Test the Forward function. Loss should be 1.33764.
// Value calculated using torch.nn.MultiLabelSoftMarginLoss(reduction='mean').
loss = module2.Forward(input, target);
REQUIRE(loss == Approx(1.33764).epsilon(1e-5));

// Test the Backward function.
module2.Backward(input, target, output);
REQUIRE(arma::as_scalar(arma::accu(output)) ==
Approx(0.358943).epsilon(1e-5));
REQUIRE(output.n_rows ==input.n_rows);
REQUIRE(output.n_cols == input.n_cols);
CheckMatrices(output, expectedOutput, 0.1);
}