-
-
Notifications
You must be signed in to change notification settings - Fork 218
Improved numerical accuracy for log_sum_exp, softmax, and log_softmax #3371
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
Open
helske
wants to merge
8
commits into
stan-dev:develop
Choose a base branch
from
helske:softmax
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
3cc61f5
additional tests for log_softmax and log_sum_exp
873012f
fix fwd tests (remove const)
5af9ced
TEST to TEST_F
b99d75a
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot 1411777
improve accuracy and consistency of softmax et al
47d29a7
Merge branch 'softmax' of https://github.com/helske/math into softmax
13adae4
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot 6a52e49
Add missing newline at end of fwd/fun/log_softmax.hpp
SteveBronder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||||||||
| #include <stan/math/prim/fun/constants.hpp> | ||||||||||||
| #include <stan/math/prim/fun/to_ref.hpp> | ||||||||||||
| #include <stan/math/prim/fun/log_sum_exp.hpp> | ||||||||||||
| #include <stan/math/prim/fun/softmax.hpp> | ||||||||||||
| #include <cmath> | ||||||||||||
| #include <vector> | ||||||||||||
|
|
||||||||||||
|
|
@@ -56,11 +57,10 @@ inline auto log_sum_exp(T&& x) { | |||||||||||
| using T_fvar_inner = typename value_type_t<decltype(v)>::Scalar; | ||||||||||||
| using mat_type = Eigen::Matrix<T_fvar_inner, -1, -1>; | ||||||||||||
| mat_type vals = v.val(); | ||||||||||||
| mat_type exp_vals = vals.array().exp(); | ||||||||||||
|
|
||||||||||||
| return fvar<T_fvar_inner>( | ||||||||||||
| log_sum_exp(vals), | ||||||||||||
| v.d().cwiseProduct(exp_vals).sum() / exp_vals.sum()); | ||||||||||||
| const auto probs = softmax(vals); | ||||||||||||
| return fvar<T_fvar_inner>(log_sum_exp(vals), | ||||||||||||
| v.d().cwiseProduct(probs).sum()); | ||||||||||||
|
Comment on lines
+61
to
+63
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,24 +29,25 @@ inline auto softmax(T&& x) { | |
| /** | ||
| * Return the softmax of the specified vector of `fvar` values. | ||
| * | ||
| * @tparam Vec Eigen vector with `fvar` scalar | ||
| * @param x vector to transform | ||
| * @return softmax of the vector, or an empty result if the input is empty | ||
| * @tparam Mat Eigen vector or matrix with `fvar` scalar | ||
| * @param x vector or matrix to transform | ||
| * @return softmax of the vector, matrix, or an empty result if the input is | ||
| * empty | ||
| */ | ||
| template <typename Vec, require_eigen_vector_vt<is_fvar, Vec>* = nullptr> | ||
| inline auto softmax(Vec&& x) { | ||
| using vec = std::decay_t<Vec>; | ||
| constexpr int Rows = vec::RowsAtCompileTime; | ||
| constexpr int Cols = vec::ColsAtCompileTime; | ||
| using T = typename value_type_t<vec>::Scalar; | ||
| decltype(auto) x_ref = to_ref(std::forward<Vec>(x)); | ||
| template <typename Mat, require_eigen_vt<is_fvar, Mat>* = nullptr> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using the same approach from template <typename Mat, require_eigen_vt<is_fvar, Mat>* = nullptr>
inline plain_type_t<Mat> softmax(Mat&& x) {
if (x.size() == 0) {
return {};
}
decltype(auto) x_ref = to_ref(std::forward<Mat>(x));
const auto s = softmax(value_of(x_ref)).eval();
const auto d_in = x_ref.d().eval();
return to_fvar(s, s.array() * (d_in.array() - s.cwiseProduct(d_in).sum()));
} |
||
| inline auto softmax(Mat&& x) { | ||
| using mat = std::decay_t<Mat>; | ||
| constexpr int Rows = mat::RowsAtCompileTime; | ||
| constexpr int Cols = mat::ColsAtCompileTime; | ||
| using T = typename value_type_t<mat>::Scalar; | ||
| decltype(auto) x_ref = to_ref(std::forward<Mat>(x)); | ||
| if (x_ref.size() == 0) { | ||
| return Eigen::Matrix<fvar<T>, Rows, Cols>{}; | ||
| } | ||
| const auto s = softmax(value_of(x_ref)); | ||
| const auto d_in = x_ref.d(); | ||
| const auto dot_sd = s.dot(d_in); | ||
| Eigen::Matrix<fvar<T>, Rows, Cols> result(x_ref.size()); | ||
| const auto dot_sd = (s.array() * d_in.array()).sum(); | ||
| Eigen::Matrix<fvar<T>, Rows, Cols> result(x_ref.rows(), x_ref.cols()); | ||
| result.val() = s; | ||
| result.d() = (s.array() * (d_in.array() - dot_sd)).matrix(); | ||
| return result; | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of 'gotchas' to be aware of. When calling
.val()/value_of()/.d()on a matrix offvartypes, the resulting expression is essentially a view over non-contiguous memory. This makes Eigen expressions less efficient and vectorisable, so if you need to use them multiple times then it's generally more performant to pay the copy-cost once so that the following expressions will be faster.The size of expressions can be checked without triggering an evaluation (correct me if I'm wrong there @SteveBronder) so you can use
if (x.size() == 0)for the early return, sinceto_ref()can evaluate expressions.There are also a few helper functions in the math library for simplifying the construction which you could use. Putting it all together, a simplified implementation could look like: