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

Generalised unary vector function framework #1558

Merged
merged 36 commits into from
Jan 24, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ca89f78
Initial implementation
Dec 14, 2019
de964ba
Merge branch 'develop' into feature/vec_gen_design
Dec 15, 2019
6b54c52
Add forwarding, rev & fwd versions
Dec 16, 2019
eebfd95
Merge branch 'develop' into feature/vec_gen_design
Dec 17, 2019
2b085d1
Add autodiff tests, remove arr versions
Dec 17, 2019
ac3d5e3
Nested testing
Dec 24, 2019
77f3a59
Fix tests, update doc
Dec 26, 2019
b3a1132
Tidy doc
Dec 27, 2019
27d1b1f
Merge branch 'develop' into feature/vec_gen_design
Dec 27, 2019
a4b83a7
Cpplint
Dec 27, 2019
d97c963
Tidy missing doc
Dec 29, 2019
461f0b0
log_softmax doc errors
Dec 29, 2019
cd0a362
Merge branch 'develop' into feature/vec_gen_design
Dec 29, 2019
9fa5124
[Jenkins] auto-formatting by clang-format version 5.0.0-3~16.04.1 (ta…
stan-buildbot Dec 29, 2019
cba3a30
Fix failing test
Dec 30, 2019
e5a6b28
Merge develop
Dec 30, 2019
5a934fe
Revert head replacement
Dec 31, 2019
b7b2171
Merge branch 'develop' into feature/vec_gen_design
Dec 31, 2019
8ebea40
[Jenkins] auto-formatting by clang-format version 5.0.0-3~16.04.1 (ta…
stan-buildbot Dec 31, 2019
a73be6d
Merge commit '426ad8fe5a2858b9d367aade1b25a631ac5e97e8' into merge_af…
rok-cesnovar Jan 5, 2020
8b5cc7f
Merge commit 'd7eb73884e5fad18eaf323760e4625317e1c4c91' into merge_af…
rok-cesnovar Jan 5, 2020
df34056
Merge commit '2b2f7ddff32c12e1e253a6179bf81c1845962306' into merge_af…
rok-cesnovar Jan 5, 2020
8a7017a
Merge commit '731b5f8cf6566db4f13a06851d56cc9e54029146' into merge_af…
rok-cesnovar Jan 5, 2020
8214c93
Merge branch 'develop' into merge_after_flatten
rok-cesnovar Jan 5, 2020
d776eac
merge conflicts fix
rok-cesnovar Jan 5, 2020
09c4004
[Jenkins] auto-formatting by clang-format version 5.0.0-3~16.04.1 (ta…
stan-buildbot Jan 5, 2020
a0eb3df
fix header guard
rok-cesnovar Jan 5, 2020
4e83afb
remove include
rok-cesnovar Jan 5, 2020
2e4f6b1
Merge branch 'develop' into feature/vec_gen_design
Jan 10, 2020
febffbe
Address review comments
Jan 11, 2020
67e23b8
Merge branch 'develop' into feature/vec_gen_design
Jan 11, 2020
477cf9f
[Jenkins] auto-formatting by clang-format version 5.0.0-3~16.04.1 (ta…
stan-buildbot Jan 11, 2020
02afdb9
Fix merge error
Jan 11, 2020
8d8539a
cpplint
Jan 11, 2020
1fce5ac
Merge branch 'develop' into feature/vec_gen_design
Jan 13, 2020
f3b3286
Address comments
Jan 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion stan/math/fwd/arr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include <stan/math/prim/arr.hpp>
#include <stan/math/fwd/scal.hpp>

#include <stan/math/fwd/arr/fun/log_sum_exp.hpp>
#include <stan/math/fwd/arr/fun/sum.hpp>
#include <stan/math/fwd/arr/fun/to_fvar.hpp>

Expand Down
31 changes: 0 additions & 31 deletions stan/math/fwd/arr/fun/log_sum_exp.hpp

This file was deleted.

65 changes: 34 additions & 31 deletions stan/math/fwd/mat/fun/log_softmax.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,47 @@
#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <stan/math/prim/mat/fun/log_softmax.hpp>
#include <stan/math/prim/mat/fun/softmax.hpp>
#include <stan/math/prim/meta.hpp>

namespace stan {
namespace math {

template <typename T>
inline Eigen::Matrix<fvar<T>, Eigen::Dynamic, 1> log_softmax(
const Eigen::Matrix<fvar<T>, Eigen::Dynamic, 1>& alpha) {
using Eigen::Dynamic;
using Eigen::Matrix;

Matrix<T, Dynamic, 1> alpha_t(alpha.size());
for (int k = 0; k < alpha.size(); ++k) {
alpha_t(k) = alpha(k).val_;
}

Matrix<T, Dynamic, 1> softmax_alpha_t = softmax(alpha_t);
Matrix<T, Dynamic, 1> log_softmax_alpha_t = log_softmax(alpha_t);

Matrix<fvar<T>, Dynamic, 1> log_softmax_alpha(alpha.size());
for (int k = 0; k < alpha.size(); ++k) {
log_softmax_alpha(k).val_ = log_softmax_alpha_t(k);
log_softmax_alpha(k).d_ = 0;
}

for (int m = 0; m < alpha.size(); ++m) {
T negative_alpha_m_d_times_softmax_alpha_t_m
= -alpha(m).d_ * softmax_alpha_t(m);
for (int k = 0; k < alpha.size(); ++k) {
if (m == k) {
log_softmax_alpha(k).d_
+= alpha(m).d_ + negative_alpha_m_d_times_softmax_alpha_t_m;
} else {
log_softmax_alpha(k).d_ += negative_alpha_m_d_times_softmax_alpha_t_m;
/**
* Return the log softmax of the specified vector or container of vectors.
*
* @tparam T Type of input vector or matrix.
* @param[in] x Unconstrained input vector.
* @return Softmax of the input.
* @throw std::domain_error If the input vector is size 0.
*/
template <typename T, require_t<is_fvar<scalar_type_t<T>>>...>
inline auto log_softmax(T&& x) {
return apply_vector_unary<T>::apply(std::forward<T>(x), [&](auto& alpha) {
Copy link
Contributor

@t4c1 t4c1 Jan 3, 2020

Choose a reason for hiding this comment

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

Perfect forwarding brings no benefits here. This would work as well with no additional copys and the code is a bit shorter:

inline auto log_softmax(const T& x) {
  return apply_vector_unary<T>::apply(x, [&](auto& alpha) {

I think the same holds true for every other place in this PR that uses perfect forwarding.

Perfect forwarding is better than just const refs only if a variable can outlive the function in question. For example if the function can directley return the variable or construct some object that holds the variable.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Perfect forwarding is better than just const refs only if a variable can outlive the function in question. For example if the function can directley return the variable or construct some object that holds the variable.

Can you link me to a doc that talks about that? My understanding of PF is that it tells the compiler "I'm okay with moving ownership of this memory and leaving the original object in an undefined state"

In particular, for the below think about a recursive function that can have a long ref stack that the compiler probably can't sort through. PF gives the compiler the ability to move ownership of that memory. So we don't have to worry about whether the compiler will have alias issues when deciding to inline recurses.

For example if the function can directly return the variable or construct some object that holds the variable.

The CPP guides have a lot on this with a nice little table in F.15 has some good heuristics

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#fcall-parameter-passing

In particular this seems to recommend Andrew keep it as a forwarding reference

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f19-for-forward-parameters-pass-by-tp-and-only-stdforward-the-parameter

It feels like the above func is just something we want to allow the compiler to move through if it's able

Copy link
Contributor

@t4c1 t4c1 Jan 13, 2020

Choose a reason for hiding this comment

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

Can you link me to a doc that talks about that?

Sorry, that is just my understanding and I a quick search does not find anything on when perfect forwarding is benefitial and when it is not. I can't think of any other example, but please correct me if I am wrong.

My understanding of PF is that it tells the compiler "I'm okay with moving ownership of this memory and leaving the original object in an undefined state"

I would say more accurate would be "I am CAPABLE of either moving a temporary object or using a reference to lvalue object." In this PR no function can actually move the parameter, so const refs are just as good.

PF gives the compiler the ability to move ownership of that memory. So we don't have to worry about whether the compiler will have alias issues when deciding to inline recurses.

I don't know anything about that. Can you link any doc or benchmark that supports this?

Thanks for the links to the guidelines. However I disagree with your conclusion that they suggest we should use perfect forwarding. The first link points to section that says simple means of parameter passing (which exclude perfect forwarding) should be used, unless benefits of something more advanced are demonstrated.

The second link suggests nothing on whether perfect forwarding should be used or not. It only explains what a function accepting a forwarding reference should do with it. It says that a function should be flagged (as violatin those guidelines) if it does anything with this argument except std::forward-ing it exactley once.

I would add to this that std::forward-ing an argument to function is only beneficial if that function either uses perfect forwarding or has two overloads accepting const lvalue ref and rvalue ref.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that we shouldn't clutter the code with perfect forwarding examples where they can't be used. This came up in another PR with autodiff types, which have no data content to move.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@t4c1 at work but ill link some stuff tonight

Maybe we should write up an rfc to talk about what types and sizes should use certain argument types. I tend to be way to pf giddy

using T_fvar = value_type_t<decltype(alpha)>;
using T_fvar_inner = typename T_fvar::Scalar;

Eigen::Matrix<T_fvar_inner, -1, 1> alpha_t = alpha.val();
Eigen::Matrix<T_fvar_inner, -1, 1> softmax_alpha_t = softmax(alpha_t);

Eigen::Matrix<T_fvar, -1, 1> log_softmax_alpha(alpha.size());
log_softmax_alpha.val() = log_softmax(alpha_t);
log_softmax_alpha.d().setZero();

for (int m = 0; m < alpha.size(); ++m) {
T_fvar_inner negative_alpha_m_d_times_softmax_alpha_t_m
= -alpha(m).d_ * softmax_alpha_t(m);
for (int k = 0; k < alpha.size(); ++k) {
if (m == k) {
log_softmax_alpha(k).d_
+= alpha(m).d_ + negative_alpha_m_d_times_softmax_alpha_t_m;
} else {
log_softmax_alpha(k).d_ += negative_alpha_m_d_times_softmax_alpha_t_m;
}
}
}
}

return log_softmax_alpha;
return log_softmax_alpha;
});
}

} // namespace math
Expand Down
32 changes: 26 additions & 6 deletions stan/math/fwd/mat/fun/log_sum_exp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,39 @@
#define STAN_MATH_FWD_MAT_FUN_LOG_SUM_EXP_HPP

#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <stan/math/prim/mat/vectorize/apply_vector_unary.hpp>
#include <stan/math/fwd/core.hpp>
#include <stan/math/prim/mat/fun/log_sum_exp.hpp>

namespace stan {
namespace math {

template <typename T, int R, int C>
fvar<T> log_sum_exp(const Eigen::Matrix<fvar<T>, R, C>& v) {
Eigen::Matrix<T, R, C> vals = v.val();
Eigen::Matrix<T, R, C> exp_vals = vals.array().exp();
/**
* Return the log of the sum of the exponentiated values of the specified
* matrix of values. The matrix may be a full matrix, a vector,
* a row vector, or a container of these.
*
* The function is defined as follows to prevent overflow in exponential
* calculations.
*
* \f$\log \sum_{n=1}^N \exp(x_n) = \max(x) + \log \sum_{n=1}^N \exp(x_n -
* \max(x))\f$.
*
* @tparam T Type of input vector or matrix.
* @param[in] x Matrix of specified values.
* @return The log of the sum of the exponentiated vector values.
*/
template <typename T, require_t<is_fvar<scalar_type_t<T>>>...>
inline auto log_sum_exp(T&& x) {
return apply_vector_unary<T>::reduce(std::forward<T>(x), [&](auto& v) {
using T_fvar_inner = typename value_type_t<decltype(v)>::Scalar;
t4c1 marked this conversation as resolved.
Show resolved Hide resolved
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>(log_sum_exp(vals),
v.d().cwiseProduct(exp_vals).sum() / exp_vals.sum());
return fvar<T_fvar_inner>(
log_sum_exp(vals), v.d().cwiseProduct(exp_vals).sum() / exp_vals.sum());
});
}

} // namespace math
Expand Down
1 change: 0 additions & 1 deletion stan/math/prim/arr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#include <stan/math/prim/arr/fun/dot_self.hpp>
#include <stan/math/prim/arr/fun/fill.hpp>
#include <stan/math/prim/arr/fun/inverse_softmax.hpp>
#include <stan/math/prim/arr/fun/log_sum_exp.hpp>
#include <stan/math/prim/arr/fun/promote_elements.hpp>
#include <stan/math/prim/arr/fun/promote_scalar.hpp>
#include <stan/math/prim/arr/fun/promote_scalar_type.hpp>
Expand Down
48 changes: 0 additions & 48 deletions stan/math/prim/arr/fun/log_sum_exp.hpp

This file was deleted.

1 change: 1 addition & 0 deletions stan/math/prim/mat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@
#include <stan/math/prim/mat/prob/wishart_rng.hpp>

#include <stan/math/prim/mat/vectorize/apply_scalar_unary.hpp>
#include <stan/math/prim/mat/vectorize/apply_vector_unary.hpp>

#include <stan/math/prim/arr.hpp>

Expand Down
20 changes: 10 additions & 10 deletions stan/math/prim/mat/fun/log_softmax.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <stan/math/prim/mat/fun/log_sum_exp.hpp>
#include <stan/math/prim/mat/vectorize/apply_vector_unary.hpp>

namespace stan {
namespace math {
Expand Down Expand Up @@ -32,18 +33,17 @@ namespace math {
* \right.
* \f$
*
* @tparam T type of elements in the vector
* @param[in] v Vector to transform.
* @return Unit simplex result of the softmax transform of the vector.
* @tparam T Type of input vector to transform.
* @param[in] x Vector to transform.
* @return log unit simplex result of the softmax transform of the vector.
*/
template <typename T>
inline Eigen::Matrix<T, Eigen::Dynamic, 1> log_softmax(
const Eigen::Matrix<T, Eigen::Dynamic, 1>& v) {
check_nonzero_size("log_softmax", "v", v);
return v.array() - log_sum_exp(v);
template <typename T, require_t<std::is_arithmetic<scalar_type_t<T>>>...>
inline auto log_softmax(T&& x) {
return apply_vector_unary<T>::apply(std::forward<T>(x), [&](auto& v) {
check_nonzero_size("log_softmax", "v", v);
return (v.array() - log_sum_exp(v)).matrix();
});
}

} // namespace math
} // namespace stan

#endif
32 changes: 16 additions & 16 deletions stan/math/prim/mat/fun/log_sum_exp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define STAN_MATH_PRIM_MAT_FUN_LOG_SUM_EXP_HPP

#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <stan/math/prim/mat/vectorize/apply_vector_unary.hpp>
#include <stan/math/prim/scal/fun/constants.hpp>
#include <cmath>
#include <vector>
Expand All @@ -12,31 +13,30 @@ namespace math {
/**
* Return the log of the sum of the exponentiated values of the specified
* matrix of values. The matrix may be a full matrix, a vector,
* or a row vector.
* a row vector, or a container of these.
*
* The function is defined as follows to prevent overflow in exponential
* calculations.
*
* \f$\log \sum_{n=1}^N \exp(x_n) = \max(x) + \log \sum_{n=1}^N \exp(x_n -
* \max(x))\f$.
*
* @tparam R number of rows, can be Eigen::Dynamic
* @tparam C number of columns, can be Eigen::Dynamic
*
* @param[in] x Matrix of specified values
* @tparam T Type of input vector or matrix.
* @param[in] x Matrix of specified values.
* @return The log of the sum of the exponentiated vector values.
*/
template <int R, int C>
double log_sum_exp(const Eigen::Matrix<double, R, C>& x) {
if (x.size() == 0) {
return NEGATIVE_INFTY;
}

const double max = x.maxCoeff();
if (!std::isfinite(max)) {
return max;
}
return max + std::log((x.array() - max).exp().sum());
template <typename T, require_t<std::is_arithmetic<scalar_type_t<T>>>...>
inline auto log_sum_exp(T&& x) {
return apply_vector_unary<T>::reduce(std::forward<T>(x), [&](auto& v) {
if (v.size() == 0) {
return NEGATIVE_INFTY;
}
const double max = v.maxCoeff();
if (!std::isfinite(max)) {
return max;
}
return max + std::log((v.array() - max).exp().sum());
});
}

} // namespace math
Expand Down
1 change: 0 additions & 1 deletion stan/math/prim/mat/prob/categorical_logit_lpmf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/arr/fun/log_sum_exp.hpp>
#include <stan/math/prim/mat/fun/log_softmax.hpp>
#include <stan/math/prim/mat/fun/log_sum_exp.hpp>
#include <stan/math/prim/mat/fun/sum.hpp>
Expand Down
Loading