I'm implementing some mathematical functions right now for future use in more exciting things and wanted to know if I'm on the right track in my approach, as I don't have much C++ experience.
Here I'm offering my softmax function implementation.
#include <cmath>
#include <iterator>
#include <functional>
#include <numeric>
#include <type_traits>
template <typename It>
void softmax (It beg, It end)
{
using VType = typename std::iterator_traits<It>::value_type;
static_assert(std::is_floating_point<VType>::value,
"Softmax function only applicable for floating types");
auto max_ele { *std::max_element(beg, end) };
std::transform(
beg,
end,
beg,
[&](VType x){ return std::exp(x - max_ele); });
VType exptot = std::accumulate(beg, end, 0.0);
std::transform(
beg,
end,
beg,
std::bind2nd(std::divides<VType>(), exptot));
}
Info on softmax
The softmax function is defined as
$$\text{softmax}(\mathbf{x})_{i} = \frac{\exp(x_{i})}{\sum_{j=1}^{n} \exp(x_{j})}$$
As mentioned in a comment, my reasoning for evaluating the softmax after subtracting the maximum of my vector is numerical stability. Consider the case where each element of my iterator is equal to some constant \$\alpha\$. If \$\alpha\$ is large and positive we might overflow or if large and negative we might underflow. Subtracting \$\max _{i} x_{i}\$, we ensure that the largest argument taken by \$\exp\$ is \$0\$ (so no overflow) and that at least one denominator term protects us from underflow leading to division by zero.
Thoughts while writing this
Is the
static_assert
a reasonable thing to check? The other thought I had was to use SFINAE which I don't know too much about. Liketemplate <typename Condition> using EnableIf = std::enable_if_t<Condition::value>; template < typename It, EnableIf<std::is_floating_point< typename std::iterator_traits<It>::value_type > >... > void softmax(...)
What I liked about this approach that if I try to pass an iterator with a non-floating point type somewhere I get a nice immediate error recognition in my Emacs session for no function overload.. but I thought the
static_assert
might be better because there really is no other overload I have in mind forsoftmax
- passing a non-floating iterator is really just an error.Is there any possible issues / considerations when using the value type of the iterator like I am doing here?
beg == end
? (I don't expect a problem with that, as the division won't happen when you have zero elements, but it may be relevant if you later refactor the code). \$\endgroup\$