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

Create binary crossentropy primitive #945

Merged
merged 2 commits into from Apr 13, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
71 changes: 71 additions & 0 deletions phylanx/plugins/keras_support/binary_crossentropy_operation.hpp
@@ -0,0 +1,71 @@
// Copyright (c) 2018 Bita Hasheminezhad
// Copyright (c) 2018 Hartmut Kaiser
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#if !defined(PHYLANX_PLUGINS_KERAS_SUPPORT_BINARY_CROSSENTROPY_OPERATION)
#define PHYLANX_PLUGINS_KERAS_SUPPORT_BINARY_CROSSENTROPY_OPERATION

#include <phylanx/config.hpp>
#include <phylanx/execution_tree/primitives/base_primitive.hpp>
#include <phylanx/execution_tree/primitives/primitive_component_base.hpp>
#include <phylanx/ir/node_data.hpp>

#include <hpx/lcos/future.hpp>

#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace phylanx { namespace execution_tree { namespace primitives {

class bin_cross_operation
: public primitive_component_base
, public std::enable_shared_from_this<bin_cross_operation>
{
protected:
hpx::future<primitive_argument_type> eval(
primitive_arguments_type const& operands,
primitive_arguments_type const& args,
eval_context ctx) const override;
using val_type = double;
using arg_type = ir::node_data<val_type>;

public:
static match_pattern_type const match_data;

bin_cross_operation() = default;

bin_cross_operation(primitive_arguments_type&& operands,
std::string const& name, std::string const& codename);

private:
primitive_argument_type bin_cross0d(
arg_type&& target, arg_type&& output, bool from_logbits) const;
primitive_argument_type bin_cross1d(
arg_type&& target, arg_type&& output, bool from_logbits) const;
primitive_argument_type bin_cross2d(
arg_type&& target, arg_type&& output,
bool from_logbits) const;

#if defined(PHYLANX_HAVE_BLAZE_TENSOR)
primitive_argument_type bin_cross3d(
arg_type&& target, arg_type&& output,
bool from_logbits) const;
#endif
};
inline primitive create_bin_cross_operation(hpx::id_type const& locality,
primitive_arguments_type&& operands,
std::string const& name = "", std::string const& codename = "")
{
return create_primitive_component(
locality,
"binary_crossentropy",
std::move(operands), name, codename);
}
}}}

#endif
1 change: 1 addition & 0 deletions phylanx/plugins/keras_support/keras_support.hpp
Expand Up @@ -17,6 +17,7 @@
#include <phylanx/plugins/keras_support/sigmoid_operation.hpp>
#include <phylanx/plugins/keras_support/softmax_operation.hpp>
#include <phylanx/plugins/keras_support/categorical_crossentropy_operation.hpp>
#include <phylanx/plugins/keras_support/binary_crossentropy_operation.hpp>
#include <phylanx/plugins/keras_support/softplus_operation.hpp>
#include <phylanx/plugins/keras_support/softsign_operation.hpp>

Expand Down
251 changes: 251 additions & 0 deletions src/plugins/keras_support/binary_crossentropy_operation.cpp
@@ -0,0 +1,251 @@
// Copyright (c) 2018 Bita Hasheminezhad
// Copyright (c) 2018 Hartmut Kaiser
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <phylanx/config.hpp>
#include <phylanx/ir/node_data.hpp>
#include <phylanx/execution_tree/primitives/node_data_helpers.hpp>
#include <phylanx/plugins/keras_support/binary_crossentropy_operation.hpp>
#include <phylanx/util/blaze_traits.hpp>
#include <phylanx/util/assign.hpp>
#include <phylanx/plugins/statistics/statistics_base.hpp>
#include <phylanx/plugins/statistics/sum_operation.hpp>

#include <hpx/include/lcos.hpp>
#include <hpx/include/naming.hpp>
#include <hpx/include/util.hpp>
#include <hpx/throw_exception.hpp>
#include <hpx/util/assert.hpp>

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <type_traits>

#include <blaze/Math.h>
#if defined(PHYLANX_HAVE_BLAZE_TENSOR)
#include <blaze_tensor/Math.h>
#endif

///////////////////////////////////////////////////////////////////////////////
namespace phylanx { namespace execution_tree { namespace primitives
{
///////////////////////////////////////////////////////////////////////////
match_pattern_type const bin_cross_operation::match_data =
{
hpx::util::make_tuple("binary_crossentropy",
std::vector<std::string>{
"binary_crossentropy(_1_target,_2_output,"
"__arg(_3_from_logits,false))"
},
&create_bin_cross_operation, &create_primitive<bin_cross_operation>,
R"(target, output, from_logits
Args:

target (array_like) : input array
output (array_like) : this array is not output back to the caller
but is passed back with the return values.
from_logits (optional, boolean): boolean value, default = False

Returns:
The value should be the same as would be returned by the following
Python function:

def bin_cross(target, output, from_logits=False):
if not from_logits:
output = np.clip(output, 1e-7, 1 - 1e-7)
output = np.log(output / (1 - output))
return (target * -np.log(sigmoid(output)) +
(1 - target) * -np.log(1 - sigmoid(output)))
)")
};
constexpr double clip_low = 1e-7;
constexpr double clip_high = 1 - clip_low;

///////////////////////////////////////////////////////////////////////////
bin_cross_operation::bin_cross_operation(primitive_arguments_type&& operands,
std::string const& name, std::string const& codename)
: primitive_component_base(std::move(operands), name, codename)
{}

primitive_argument_type bin_cross_operation::bin_cross0d(
arg_type&& target,arg_type&& output,bool from_logits) const
{
double output_ = output.scalar();
double target_ = target.scalar();
if(!from_logits) {
double tmp = (std::min)(clip_high,(std::max)(clip_low,output_));
output_ = std::log(tmp/(1-tmp));
}
double sig = 1/(1+exp(-output_));
target_ = -target_*std::log(sig) - (1-target_)*std::log(1-sig);
primitive_argument_type part1(std::move(target_)), part2(std::move(output_));
primitive_arguments_type both{part1, part2};
phylanx::ir::range tup(both);
return primitive_argument_type{ std::move(tup) };
}

///////////////////////////////////////////////////////////////////////////
primitive_argument_type bin_cross_operation::bin_cross1d(
arg_type&& target,arg_type&& output,bool from_logits) const
{
assign_vector<arg_type> output_(output);
assign_vector<arg_type> target_(target);
if(!from_logits) {
output_ = blaze::map(output.vector(),[](double o_){
double tmp = (std::min)(clip_high,(std::max)(clip_low,o_));
return std::log(tmp/(1-tmp));
});
}
target_ = blaze::map(target.vector(), output.vector(),[](double t_,double o_){
double sig = 1/(1+exp(-o_));
return -t_*std::log(sig) - (1-t_)*std::log(1-sig);
});
primitive_argument_type part1(std::move(target)), part2(std::move(output));
primitive_arguments_type both{part1, part2};
phylanx::ir::range tup(both);
return primitive_argument_type{ std::move(tup) };
}
///////////////////////////////////////////////////////////////////////////

using matrix_type =
blaze::DynamicMatrix<double>;

using vector_type =
blaze::DynamicVector<double>;

#if defined(PHYLANX_HAVE_BLAZE_TENSOR)
using tensor_type =
blaze::DynamicTensor<double>;
#endif

///////////////////////////////////////////////////////////////////////////
primitive_argument_type bin_cross_operation::bin_cross2d(
arg_type&& target, arg_type&& output,bool from_logits) const
{
assign_matrix<arg_type> output_(output);
assign_matrix<arg_type> target_(target);
if(!from_logits) {
output_ = blaze::map(output.matrix(),[](double o_){
double tmp = (std::min)(clip_high,(std::max)(clip_low,o_));
return std::log(tmp/(1-tmp));
});
}
target_ = blaze::map(target.matrix(), output.matrix(),[](double t_,double o_){
double sig = 1/(1+exp(-o_));
return -t_*std::log(sig) - (1-t_)*std::log(1-sig);
});
primitive_argument_type part1(std::move(target)), part2(std::move(output));
primitive_arguments_type both{part1, part2};
phylanx::ir::range tup(both);
return primitive_argument_type{ std::move(tup) };
}

///////////////////////////////////////////////////////////////////////////
#if defined(PHYLANX_HAVE_BLAZE_TENSOR)
primitive_argument_type bin_cross_operation::bin_cross3d(
arg_type&& target, arg_type&& output, bool from_logits) const
{
assign_tensor<arg_type> output_(output);
assign_tensor<arg_type> target_(target);
if(!from_logits) {
output_ = blaze::map(output.tensor(),[](double o_){
double tmp = (std::min)(clip_high,(std::max)(clip_low,o_));
return std::log(tmp/(1-tmp));
});
}
target_ = blaze::map(target.tensor(), output.tensor(),[](double t_,double o_){
double sig = 1/(1+exp(-o_));
return -t_*std::log(sig) - (1-t_)*std::log(1-sig);
});
primitive_argument_type part1(std::move(target)), part2(std::move(output));
primitive_arguments_type both{part1, part2};
phylanx::ir::range tup(both);
return primitive_argument_type{ std::move(tup) };
}
#endif

///////////////////////////////////////////////////////////////////////////
hpx::future<primitive_argument_type> bin_cross_operation::eval(
primitive_arguments_type const& operands,
primitive_arguments_type const& args,
eval_context ctx) const
{
for (auto const& i : operands)
{
if (!valid(i))
{
HPX_THROW_EXCEPTION(hpx::bad_parameter,
"bin_cross_operation::eval",
util::generate_error_message(
"the bin_cross_operation primitive requires that the "
"arguments given by the operands array are "
"valid",
name_, codename_));
}
}

auto this_ = this->shared_from_this();
return hpx::dataflow(hpx::launch::sync,
hpx::util::unwrapping([this_ = std::move(this_)](
primitive_arguments_type&& args)
-> primitive_argument_type {
// Extract logits
bool from_logits =
static_cast<bool>(false);

// from_logits is the third argument
if (args.size() > 2)
{
if (valid(args[2]))
from_logits =
execution_tree::extract_scalar_boolean_value(
args[2], this_->name_, this_->codename_);
}

// Extract the matrix, the result should always be double
arg_type target = extract_numeric_value(
std::move(args[0]), this_->name_, this_->codename_);
arg_type output = extract_numeric_value(
std::move(args[1]), this_->name_, this_->codename_);

std::size_t target_dims = target.num_dimensions();
std::size_t output_dims = output.num_dimensions();
HPX_ASSERT(target_dims == output_dims);

switch (target_dims)
{
case 0:
return this_->bin_cross0d(
std::move(target),std::move(output),from_logits);
case 1:
return this_->bin_cross1d(
std::move(target),std::move(output),from_logits);
case 2:
return this_->bin_cross2d(
std::move(target),std::move(output),from_logits);
#if defined(PHYLANX_HAVE_BLAZE_TENSOR)
case 3:
return this_->bin_cross3d(
std::move(target),std::move(output),from_logits);
#endif
default:
HPX_THROW_EXCEPTION(hpx::bad_parameter,
"bin_cross_operation::eval",
util::generate_error_message("operand a has an invalid "
"number of dimensions",
this_->name_, this_->codename_));
}
}),
detail::map_operands(
operands, functional::value_operand{}, args,
name_, codename_, std::move(ctx)));
}
}}}

2 changes: 2 additions & 0 deletions src/plugins/keras_support/keras_support.cpp
Expand Up @@ -35,6 +35,8 @@ PHYLANX_REGISTER_PLUGIN_FACTORY(softmax_operation_plugin,
phylanx::execution_tree::primitives::softmax_operation::match_data);
PHYLANX_REGISTER_PLUGIN_FACTORY(cat_cross_operation_plugin,
phylanx::execution_tree::primitives::cat_cross_operation::match_data);
PHYLANX_REGISTER_PLUGIN_FACTORY(bin_cross_operation_plugin,
phylanx::execution_tree::primitives::bin_cross_operation::match_data);
PHYLANX_REGISTER_PLUGIN_FACTORY(softplus_operation_plugin,
phylanx::execution_tree::primitives::softplus_operation::match_data);
PHYLANX_REGISTER_PLUGIN_FACTORY(softsign_operation_plugin,
Expand Down
1 change: 1 addition & 0 deletions tests/unit/python/execution_tree/CMakeLists.txt
Expand Up @@ -20,6 +20,7 @@ set(tests
set_operation
slice
categorical_crossentropy
binary_crossentropy
)

foreach(test ${tests})
Expand Down