rec/fdeep/import_model.hpp
2020-03-18 14:42:46 +08:00

1109 lines
43 KiB
C++
Executable File

// Copyright 2016, Tobias Hermann.
// https://github.com/Dobiasd/frugally-deep
// Distributed under the MIT License.
// (See accompanying LICENSE file or at
// https://opensource.org/licenses/MIT)
#pragma once
#include "fdeep/base64.hpp"
#if defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
#endif
#if defined _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4706)
#pragma warning(disable : 4996)
#endif
#include <nlohmann/json.hpp>
#if defined _MSC_VER
#pragma warning(pop)
#endif
#if defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic pop
#endif
#include "fdeep/common.hpp"
#include "fdeep/layers/add_layer.hpp"
#include "fdeep/layers/average_layer.hpp"
#include "fdeep/layers/average_pooling_2d_layer.hpp"
#include "fdeep/layers/batch_normalization_layer.hpp"
#include "fdeep/layers/bidirectional_layer.hpp"
#include "fdeep/layers/concatenate_layer.hpp"
#include "fdeep/layers/conv_2d_layer.hpp"
#include "fdeep/layers/cropping_2d_layer.hpp"
#include "fdeep/layers/dense_layer.hpp"
#include "fdeep/layers/depthwise_conv_2d_layer.hpp"
#include "fdeep/layers/elu_layer.hpp"
#include "fdeep/layers/flatten_layer.hpp"
#include "fdeep/layers/global_average_pooling_2d_layer.hpp"
#include "fdeep/layers/global_max_pooling_2d_layer.hpp"
#include "fdeep/layers/hard_sigmoid_layer.hpp"
#include "fdeep/layers/input_layer.hpp"
#include "fdeep/layers/layer.hpp"
#include "fdeep/layers/leaky_relu_layer.hpp"
#include "fdeep/layers/embedding_layer.hpp"
#include "fdeep/layers/lstm_layer.hpp"
#include "fdeep/layers/gru_layer.hpp"
#include "fdeep/layers/prelu_layer.hpp"
#include "fdeep/layers/linear_layer.hpp"
#include "fdeep/layers/max_pooling_2d_layer.hpp"
#include "fdeep/layers/maximum_layer.hpp"
#include "fdeep/layers/model_layer.hpp"
#include "fdeep/layers/multiply_layer.hpp"
#include "fdeep/layers/pooling_2d_layer.hpp"
#include "fdeep/layers/relu_layer.hpp"
#include "fdeep/layers/reshape_layer.hpp"
#include "fdeep/layers/separable_conv_2d_layer.hpp"
#include "fdeep/layers/selu_layer.hpp"
#include "fdeep/layers/sigmoid_layer.hpp"
#include "fdeep/layers/softmax_layer.hpp"
#include "fdeep/layers/softplus_layer.hpp"
#include "fdeep/layers/subtract_layer.hpp"
#include "fdeep/layers/tanh_layer.hpp"
#include "fdeep/layers/time_distributed_layer.hpp"
#include "fdeep/layers/upsampling_2d_layer.hpp"
#include "fdeep/layers/zero_padding_2d_layer.hpp"
#include "fdeep/shape5.hpp"
#include "fdeep/shape5_variable.hpp"
#include "fdeep/tensor5.hpp"
#include <fplus/fplus.hpp>
#include <algorithm>
#include <iostream>
#include <limits>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace fdeep { namespace internal
{
template<typename KeyT, typename ValueT>
ValueT json_object_get(const nlohmann::json& data, KeyT&& key, ValueT&& default_value)
{
auto&& it = data.find(key);
if (it != data.end())
return *it;
else
return default_value;
}
inline fplus::maybe<std::size_t> create_maybe_size_t(const nlohmann::json& data)
{
if (data.is_null())
{
return fplus::nothing<std::size_t>();
}
const std::size_t result = data;
return fplus::just(result);
}
inline shape5_variable create_shape5_variable(const nlohmann::json& data)
{
assertion(data.is_array(), "shape5_variable needs to be an array");
assertion(data.size() > 0, "need at least one dimension");
if (data.size() == 1)
return shape5_variable(
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>(),
create_maybe_size_t(data[0]));
if (data.size() == 2)
return shape5_variable(
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>(),
create_maybe_size_t(data[0]),
create_maybe_size_t(data[1]));
if (data.size() == 3)
return shape5_variable(
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>(),
create_maybe_size_t(data[0]),
create_maybe_size_t(data[1]),
create_maybe_size_t(data[2]));
if (data.size() == 4)
return shape5_variable(
fplus::nothing<std::size_t>(),
create_maybe_size_t(data[0]),
create_maybe_size_t(data[1]),
create_maybe_size_t(data[2]),
create_maybe_size_t(data[3]));
if (data.size() == 5)
return shape5_variable(
create_maybe_size_t(data[0]),
create_maybe_size_t(data[1]),
create_maybe_size_t(data[2]),
create_maybe_size_t(data[3]),
create_maybe_size_t(data[4]));
if (data.size() == 6) // todo: is this needed?
return shape5_variable(
create_maybe_size_t(data[1]),
create_maybe_size_t(data[2]),
create_maybe_size_t(data[3]),
create_maybe_size_t(data[4]),
create_maybe_size_t(data[5]));
raise_error("shape5_variable needs 1, 2, 3, 4 or 5 dimensions");
return shape5_variable(
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>(),
fplus::nothing<std::size_t>()); // Should never be called
}
inline shape5 create_shape5(const nlohmann::json& data)
{
assertion(data.is_array(), "shape5 needs to be an array");
assertion(data.size() > 0, "need at least one dimension");
if (data.size() == 1)
return shape5(1, 1, 1, 1, data[0]);
if (data.size() == 2)
return shape5(1, 1, 1, data[0], data[1]);
if (data.size() == 3)
return shape5(1, 1, data[0], data[1], data[2]);
if (data.size() == 4)
return shape5(1, data[0], data[1], data[2], data[3]);
if (data.size() == 5)
return shape5(data[0], data[1], data[2], data[3], data[4]);
raise_error("shape5 needs 1, 2, 3, 4 or 5 dimensions");
return shape5(0, 0, 0, 0, 0); // Should never be called
}
inline shape2 create_shape2(const nlohmann::json& data)
{
if (data.is_array())
{
assertion(data.size() == 1 || data.size() == 2,
"invalid number of dimensions in shape2");
if (data.size() == 1)
return shape2(1, data[0]);
else
return shape2(data[0], data[1]);
}
else
{
const std::size_t width = data;
return shape2(1, width);
}
}
inline std::size_t create_size_t(const nlohmann::json& int_data)
{
const int val = int_data;
assertion(val >= 0, "invalid size_t value");
return static_cast<std::size_t>(val);
}
inline int create_int(const nlohmann::json& int_data)
{
const int val = int_data;
return val;
}
inline float_vec decode_floats(const nlohmann::json& data)
{
assertion(data.is_array() || data.is_string(),
"invalid float array format");
if (data.is_array() && !data.empty() && data[0].is_number())
{
const float_vec result = data;
return result;
}
assertion(std::numeric_limits<float>::is_iec559,
"The floating-point format of your system is not supported.");
const auto res = Base64_decode(json_data_strs_char_prodiver(data, '='));
float_vec out;
assertion(res.size() % 4 == 0, "invalid float vector data");
out.reserve(res.size() / 4);
for (std::size_t i = 0; i < res.size(); i+=4)
{
float_type val = static_cast<float_type>(
*(reinterpret_cast<const float*>(&(res[i]))));
out.push_back(val);
}
return out;
}
inline tensor5 create_tensor5(const nlohmann::json& data)
{
const shape5 shape = create_shape5(data["shape"]);
return tensor5(shape, decode_floats(data["values"]));
}
template <typename T, typename F>
std::vector<T> create_vector(F f, const nlohmann::json& data)
{
if (data.is_array())
return fplus::transform_convert<std::vector<T>>(f, data);
else
return fplus::singleton_seq(f(data));
}
inline std::vector<shape5_variable> create_shape5s_variable(const nlohmann::json& data)
{
return create_vector<shape5_variable>(create_shape5_variable, data);
}
inline node_connection create_node_connection(const nlohmann::json& data)
{
assertion(data.is_array(), "invalid format for inbound node");
const std::string layer_id = data.front();
const auto node_idx = create_size_t(data[1]);
const auto tensor_idx = create_size_t(data[2]);
return node_connection(layer_id, node_idx, tensor_idx);
}
using get_param_f =
std::function<nlohmann::json(const std::string&, const std::string&)>;
using get_global_param_f = std::function<nlohmann::json(const std::string&)>;
layer_ptr create_layer(const get_param_f&, const get_global_param_f&,
const nlohmann::json&);
inline layer_ptr create_model_layer(const get_param_f& get_param,
const get_global_param_f& get_global_param, const nlohmann::json& data,
const std::string& name)
{
assertion(data["config"]["layers"].is_array(), "missing layers array");
const auto layers = create_vector<layer_ptr>(
fplus::bind_1st_and_2nd_of_3(create_layer, get_param, get_global_param),
data["config"]["layers"]);
assertion(data["config"]["input_layers"].is_array(), "no input layers");
const auto inputs = create_vector<node_connection>(
create_node_connection, data["config"]["input_layers"]);
const auto outputs = create_vector<node_connection>(
create_node_connection, data["config"]["output_layers"]);
return std::make_shared<model_layer>(name, layers, inputs, outputs);
}
inline void fill_with_zeros(float_vec& xs)
{
std::fill(std::begin(xs), std::end(xs), static_cast<float_type>(0));
}
inline padding create_padding(const std::string& padding_str)
{
return fplus::throw_on_nothing(error("no padding"),
fplus::choose<std::string, padding>({
{ std::string("valid"), padding::valid },
{ std::string("same"), padding::same },
}, padding_str));
}
inline layer_ptr create_conv_2d_layer(const get_param_f& get_param,
const get_global_param_f& get_global_param, const nlohmann::json& data,
const std::string& name)
{
const std::string padding_str = data["config"]["padding"];
const auto pad_type = create_padding(padding_str);
const shape2 strides = create_shape2(data["config"]["strides"]);
const shape2 dilation_rate = create_shape2(data["config"]["dilation_rate"]);
const auto filter_count = create_size_t(data["config"]["filters"]);
float_vec bias(filter_count, 0);
const bool use_bias = data["config"]["use_bias"];
if (use_bias)
bias = decode_floats(get_param(name, "bias"));
assertion(bias.size() == filter_count, "size of bias does not match");
const float_vec weights = decode_floats(get_param(name, "weights"));
const shape2 kernel_size = create_shape2(data["config"]["kernel_size"]);
assertion(weights.size() % kernel_size.area() == 0,
"invalid number of weights");
const std::size_t filter_depths =
weights.size() / (kernel_size.area() * filter_count);
const shape5 filter_shape(1, 1,
kernel_size.height_, kernel_size.width_, filter_depths);
const bool padding_valid_uses_offset_depth_1 =
get_global_param("conv2d_valid_offset_depth_1");
const bool padding_same_uses_offset_depth_1 =
get_global_param("conv2d_same_offset_depth_1");
const bool padding_valid_uses_offset_depth_2 =
get_global_param("conv2d_valid_offset_depth_2");
const bool padding_same_uses_offset_depth_2 =
get_global_param("conv2d_same_offset_depth_2");
return std::make_shared<conv_2d_layer>(name,
filter_shape, filter_count, strides, pad_type,
padding_valid_uses_offset_depth_1, padding_same_uses_offset_depth_1,
padding_valid_uses_offset_depth_2, padding_same_uses_offset_depth_2,
dilation_rate, weights, bias);
}
inline layer_ptr create_separable_conv_2D_layer(const get_param_f& get_param,
const get_global_param_f& get_global_param, const nlohmann::json& data,
const std::string& name)
{
const std::string padding_str = data["config"]["padding"];
const auto pad_type = create_padding(padding_str);
const shape2 strides = create_shape2(data["config"]["strides"]);
const shape2 dilation_rate = create_shape2(data["config"]["dilation_rate"]);
const auto filter_count = create_size_t(data["config"]["filters"]);
float_vec bias(filter_count, 0);
const bool use_bias = data["config"]["use_bias"];
if (use_bias)
bias = decode_floats(get_param(name, "bias"));
assertion(bias.size() == filter_count, "size of bias does not match");
const float_vec slice_weights = decode_floats(
get_param(name, "slice_weights"));
const float_vec stack_weights = decode_floats(
get_param(name, "stack_weights"));
const shape2 kernel_size = create_shape2(data["config"]["kernel_size"]);
assertion(slice_weights.size() % kernel_size.area() == 0,
"invalid number of weights");
assertion(stack_weights.size() % filter_count == 0,
"invalid number of weights");
const std::size_t input_depth = slice_weights.size() / kernel_size.area();
const std::size_t stack_output_depths_1 =
stack_weights.size() / input_depth;
assertion(stack_output_depths_1 == filter_count, "invalid weights sizes");
const shape5 filter_shape(1, 1, kernel_size.height_, kernel_size.width_, 1);
float_vec bias_0(input_depth, 0);
const bool padding_valid_uses_offset_depth_1 =
get_global_param("separable_conv2d_valid_offset_depth_1");
const bool padding_same_uses_offset_depth_1 =
get_global_param("separable_conv2d_same_offset_depth_1");
const bool padding_valid_uses_offset_depth_2 =
get_global_param("separable_conv2d_valid_offset_depth_2");
const bool padding_same_uses_offset_depth_2 =
get_global_param("separable_conv2d_same_offset_depth_2");
return std::make_shared<separable_conv_2d_layer>(name, input_depth,
filter_shape, filter_count, strides, pad_type,
padding_valid_uses_offset_depth_1, padding_same_uses_offset_depth_1,
padding_valid_uses_offset_depth_2, padding_same_uses_offset_depth_2,
dilation_rate, slice_weights, stack_weights, bias_0, bias);
}
inline layer_ptr create_depthwise_conv_2D_layer(const get_param_f& get_param,
const get_global_param_f& get_global_param, const nlohmann::json& data,
const std::string& name)
{
const std::string padding_str = data["config"]["padding"];
const auto pad_type = create_padding(padding_str);
const shape2 strides = create_shape2(data["config"]["strides"]);
const shape2 dilation_rate = create_shape2(data["config"]["dilation_rate"]);
const float_vec slice_weights = decode_floats(
get_param(name, "slice_weights"));
const shape2 kernel_size = create_shape2(data["config"]["kernel_size"]);
assertion(slice_weights.size() % kernel_size.area() == 0,
"invalid number of weights");
const std::size_t input_depth = slice_weights.size() / kernel_size.area();
const shape5 filter_shape(1, 1, kernel_size.height_, kernel_size.width_, 1);
const std::size_t filter_count = input_depth;
float_vec bias(filter_count, 0);
const bool use_bias = data["config"]["use_bias"];
if (use_bias)
bias = decode_floats(get_param(name, "bias"));
assertion(bias.size() == filter_count, "size of bias does not match");
const bool padding_valid_uses_offset_depth_1 =
get_global_param("separable_conv2d_valid_offset_depth_1");
const bool padding_same_uses_offset_depth_1 =
get_global_param("separable_conv2d_same_offset_depth_1");
const bool padding_valid_uses_offset_depth_2 =
get_global_param("separable_conv2d_valid_offset_depth_2");
const bool padding_same_uses_offset_depth_2 =
get_global_param("separable_conv2d_same_offset_depth_2");
return std::make_shared<depthwise_conv_2d_layer>(name, input_depth,
filter_shape, filter_count, strides, pad_type,
padding_valid_uses_offset_depth_1, padding_same_uses_offset_depth_1,
padding_valid_uses_offset_depth_2, padding_same_uses_offset_depth_2,
dilation_rate, slice_weights, bias);
}
inline layer_ptr create_input_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
assertion(data["inbound_nodes"].empty(),
"input layer is not allowed to have inbound nodes");
const auto input_shape = create_shape5_variable(data["config"]["batch_input_shape"]);
return std::make_shared<input_layer>(name, input_shape);
}
inline layer_ptr create_batch_normalization_layer(const get_param_f& get_param,
const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
const float_vec moving_mean = decode_floats(get_param(name, "moving_mean"));
const float_vec moving_variance =
decode_floats(get_param(name, "moving_variance"));
const bool center = data["config"]["center"];
const bool scale = data["config"]["scale"];
const float_type epsilon = data["config"]["epsilon"];
float_vec gamma;
float_vec beta;
if (scale) gamma = decode_floats(get_param(name, "gamma"));
if (center) beta = decode_floats(get_param(name, "beta"));
return std::make_shared<batch_normalization_layer>(
name, moving_mean, moving_variance, beta, gamma, epsilon);
}
inline layer_ptr create_dropout_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
// dropout rate equals zero in forward pass
return std::make_shared<linear_layer>(name);
}
inline layer_ptr create_max_pooling_2d_layer(
const get_param_f&, const get_global_param_f& get_global_param,
const nlohmann::json& data, const std::string& name)
{
const auto pool_size = create_shape2(data["config"]["pool_size"]);
const auto strides = create_shape2(data["config"]["strides"]);
const std::string padding_str = data["config"]["padding"];
const auto pad_type = create_padding(padding_str);
const bool padding_valid_uses_offset =
get_global_param("max_pooling_2d_valid_offset");
const bool padding_same_uses_offset =
get_global_param("max_pooling_2d_same_offset");
return std::make_shared<max_pooling_2d_layer>(name,
pool_size, strides, pad_type,
padding_valid_uses_offset,
padding_same_uses_offset);
}
inline layer_ptr create_average_pooling_2d_layer(
const get_param_f&, const get_global_param_f& get_global_param,
const nlohmann::json& data, const std::string& name)
{
const auto pool_size = create_shape2(data["config"]["pool_size"]);
const auto strides = create_shape2(data["config"]["strides"]);
const std::string padding_str = data["config"]["padding"];
const auto pad_type = create_padding(padding_str);
const bool padding_valid_uses_offset =
get_global_param("average_pooling_2d_valid_offset");
const bool padding_same_uses_offset =
get_global_param("average_pooling_2d_same_offset");
return std::make_shared<average_pooling_2d_layer>(name,
pool_size, strides, pad_type,
padding_valid_uses_offset,
padding_same_uses_offset);
}
inline layer_ptr create_global_max_pooling_2d_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<global_max_pooling_2d_layer>(name);
}
inline layer_ptr create_global_average_pooling_2d_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<global_average_pooling_2d_layer>(name);
}
inline layer_ptr create_upsampling_2d_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
const auto scale_factor = create_shape2(data["config"]["size"]);
return std::make_shared<upsampling_2d_layer>(name, scale_factor);
}
inline layer_ptr create_dense_layer(const get_param_f& get_param,
const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
const float_vec weights = decode_floats(get_param(name, "weights"));
std::size_t units = data["config"]["units"];
float_vec bias(units, 0);
const bool use_bias = data["config"]["use_bias"];
if (use_bias)
bias = decode_floats(get_param(name, "bias"));
assertion(bias.size() == units, "size of bias does not match");
return std::make_shared<dense_layer>(
name, units, weights, bias);
}
inline layer_ptr create_concatenate_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
const std::int32_t keras_axis = data["config"]["axis"];
return std::make_shared<concatenate_layer>(name, keras_axis);
}
inline layer_ptr create_add_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<add_layer>(name);
}
inline layer_ptr create_maximum_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<maximum_layer>(name);
}
inline layer_ptr create_multiply_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<multiply_layer>(name);
}
inline layer_ptr create_average_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<average_layer>(name);
}
inline layer_ptr create_subtract_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<subtract_layer>(name);
}
inline layer_ptr create_flatten_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<flatten_layer>(name);
}
inline layer_ptr create_zero_padding_2d_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
const auto padding =
create_vector<std::vector<std::size_t>>(fplus::bind_1st_of_2(
create_vector<std::size_t, decltype(create_size_t)>, create_size_t),
data["config"]["padding"]);
assertion(padding.size() == 2 && padding[0].size() == padding[1].size(),
"invalid padding format");
if (padding[0].size() == 1)
{
const std::size_t top_pad = 0;
const std::size_t bottom_pad = 0;
const std::size_t left_pad = padding[0][0];
const std::size_t right_pad = padding[1][0];
return std::make_shared<zero_padding_2d_layer>(name,
top_pad, bottom_pad, left_pad, right_pad);
}
else
{
const std::size_t top_pad = padding[0][0];
const std::size_t bottom_pad = padding[0][1];
const std::size_t left_pad = padding[1][0];
const std::size_t right_pad = padding[1][1];
return std::make_shared<zero_padding_2d_layer>(name,
top_pad, bottom_pad, left_pad, right_pad);
}
}
inline layer_ptr create_cropping_2d_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
const auto cropping =
create_vector<std::vector<std::size_t>>(fplus::bind_1st_of_2(
create_vector<std::size_t, decltype(create_size_t)>, create_size_t),
data["config"]["cropping"]);
assertion(cropping.size() == 2 && cropping[0].size() == cropping[1].size(),
"invalid cropping format");
if (cropping[0].size() == 1)
{
const std::size_t top_crop = 0;
const std::size_t bottom_crop = 0;
const std::size_t left_crop = cropping[0][0];
const std::size_t right_crop = cropping[1][0];
return std::make_shared<cropping_2d_layer>(name,
top_crop, bottom_crop, left_crop, right_crop);
}
else
{
const std::size_t top_crop = cropping[0][0];
const std::size_t bottom_crop = cropping[0][1];
const std::size_t left_crop = cropping[1][0];
const std::size_t right_crop = cropping[1][1];
return std::make_shared<cropping_2d_layer>(name,
top_crop, bottom_crop, left_crop, right_crop);
}
}
inline layer_ptr create_reshape_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
const auto target_shape =
create_vector<int>(create_int, data["config"]["target_shape"]);
const auto filled_shape =
fplus::fill_left(1, 3, target_shape);
return std::make_shared<reshape_layer>(name, filled_shape);
}
inline bool json_obj_has_member(const nlohmann::json& data,
const std::string& member_name)
{
return data.is_object() && data.find(member_name) != data.end();
}
inline activation_layer_ptr create_linear_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<linear_layer>(name);
}
inline activation_layer_ptr create_softmax_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<softmax_layer>(name);
}
inline activation_layer_ptr create_softplus_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<softplus_layer>(name);
}
inline activation_layer_ptr create_tanh_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<tanh_layer>(name);
}
inline activation_layer_ptr create_sigmoid_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<sigmoid_layer>(name);
}
inline activation_layer_ptr create_hard_sigmoid_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<hard_sigmoid_layer>(name);
}
inline activation_layer_ptr create_relu_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
float_type max_value = std::numeric_limits<float_type>::max();
if (json_obj_has_member(data, "config") &&
json_obj_has_member(data["config"], "max_value"))
{
max_value = data["config"]["max_value"];
}
return std::make_shared<relu_layer>(name, max_value);
}
inline activation_layer_ptr create_selu_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json&,
const std::string& name)
{
return std::make_shared<selu_layer>(name);
}
inline activation_layer_ptr create_leaky_relu_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
float_type alpha = 1.0f;
if (json_obj_has_member(data, "config") &&
json_obj_has_member(data["config"], "alpha"))
{
alpha = data["config"]["alpha"];
}
return std::make_shared<leaky_relu_layer>(name, alpha);
}
inline layer_ptr create_leaky_relu_layer_isolated(
const get_param_f& get_param, const get_global_param_f& get_global_param,
const nlohmann::json& data, const std::string& name)
{
return create_leaky_relu_layer(get_param, get_global_param, data, name);
}
inline layer_ptr create_prelu_layer(
const get_param_f& get_param, const get_global_param_f&,
const nlohmann::json& data, const std::string& name)
{
std::vector<std::size_t> shared_axes;
if (json_obj_has_member(data, "config") &&
json_obj_has_member(data["config"], "shared_axes") &&
!data["config"]["shared_axes"].empty())
{
shared_axes = create_vector<std::size_t>(create_size_t,
data["config"]["shared_axes"]);
}
const float_vec alpha = decode_floats(get_param(name, "alpha"));
return std::make_shared<prelu_layer>(name, alpha, shared_axes);
}
inline activation_layer_ptr create_elu_layer(
const get_param_f&, const get_global_param_f&, const nlohmann::json& data,
const std::string& name)
{
float_type alpha = 1.0f;
if (json_obj_has_member(data, "config") &&
json_obj_has_member(data["config"], "alpha"))
{
alpha = data["config"]["alpha"];
}
return std::make_shared<elu_layer>(name, alpha);
}
inline layer_ptr create_elu_layer_isolated(
const get_param_f& get_param, const get_global_param_f& get_global_param,
const nlohmann::json& data, const std::string& name)
{
return create_elu_layer(get_param, get_global_param, data, name);
}
inline layer_ptr create_relu_layer_isolated(
const get_param_f& get_param, const get_global_param_f& get_global_param,
const nlohmann::json& data, const std::string& name)
{
return create_relu_layer(get_param, get_global_param, data, name);
}
inline activation_layer_ptr create_activation_layer_type_name(
const get_param_f& get_param, const get_global_param_f& get_global_param,
const nlohmann::json& data,
const std::string& type, const std::string& name)
{
const std::unordered_map<std::string,
std::function<activation_layer_ptr(const get_param_f&,
const get_global_param_f&, const nlohmann::json&,
const std::string&)>>
creators = {
{"linear", create_linear_layer},
{"softmax", create_softmax_layer},
{"softplus", create_softplus_layer},
{"tanh", create_tanh_layer},
{"sigmoid", create_sigmoid_layer},
{"hard_sigmoid", create_hard_sigmoid_layer},
{"relu", create_relu_layer},
{"selu", create_selu_layer},
{"elu", create_elu_layer}
};
return fplus::throw_on_nothing(
error("unknown activation type: " + type),
fplus::get_from_map(creators, type))(
get_param, get_global_param, data, name);
}
inline layer_ptr create_activation_layer(
const get_param_f& get_param, const get_global_param_f& get_global_param,
const nlohmann::json& data, const std::string& name)
{
const std::string type = data["config"]["activation"];
return create_activation_layer_type_name(get_param, get_global_param,
data, type, name);
}
inline node create_node(const nlohmann::json& inbound_nodes_data)
{
assertion(inbound_nodes_data.is_array(), "nodes need to be an array");
return node(create_vector<node_connection>(create_node_connection,
inbound_nodes_data));
}
inline nodes create_nodes(const nlohmann::json& data)
{
assertion(data["inbound_nodes"].is_array(), "no inbound nodes");
const std::vector<nlohmann::json> inbound_nodes_data =
data["inbound_nodes"];
return fplus::transform(create_node, inbound_nodes_data);
}
inline layer_ptr create_embedding_layer(const get_param_f &get_param,
const get_global_param_f &,
const nlohmann::json &data,
const std::string &name)
{
const std::size_t input_dim = data["config"]["input_dim"];
const std::size_t output_dim = data["config"]["output_dim"];
const float_vec weights = decode_floats(get_param(name, "weights"));
return std::make_shared<embedding_layer>(name, input_dim, output_dim, weights);
}
inline layer_ptr create_lstm_layer(const get_param_f &get_param,
const get_global_param_f &,
const nlohmann::json &data,
const std::string &name)
{
const std::size_t units = data["config"]["units"];
const std::string unit_activation = data["config"]["activation"];
const std::string recurrent_activation = data["config"]["recurrent_activation"];
const bool use_bias = data["config"]["use_bias"];
float_vec bias;
if (use_bias)
bias = decode_floats(get_param(name, "bias"));
const float_vec weights = decode_floats(get_param(name, "weights"));
const float_vec recurrent_weights = decode_floats(get_param(name, "recurrent_weights"));
const bool return_sequences = data["config"]["return_sequences"];
return std::make_shared<lstm_layer>(name, units, unit_activation,
recurrent_activation, use_bias, return_sequences,
weights, recurrent_weights, bias);
}
inline layer_ptr create_gru_layer(const get_param_f &get_param,
const get_global_param_f &,
const nlohmann::json &data,
const std::string &name)
{
const std::size_t units = data["config"]["units"];
const std::string unit_activation = data["config"]["activation"];
const std::string recurrent_activation = data["config"]["recurrent_activation"];
const bool use_bias = data["config"]["use_bias"];
float_vec bias;
if (use_bias)
bias = decode_floats(get_param(name, "bias"));
const float_vec weights = decode_floats(get_param(name, "weights"));
const float_vec recurrent_weights = decode_floats(get_param(name, "recurrent_weights"));
auto&& config = data["config"];
const bool reset_after = json_object_get(config, "reset_after", false);
const bool return_sequences = json_object_get(config, "return_sequences", false);
return std::make_shared<gru_layer>(name, units, unit_activation,
recurrent_activation, use_bias, reset_after, return_sequences,
weights, recurrent_weights, bias);
}
inline layer_ptr create_bidirectional_layer(const get_param_f& get_param,
const get_global_param_f&,
const nlohmann::json& data,
const std::string& name)
{
const std::string merge_mode = data["config"]["merge_mode"];
const std::size_t units = data["config"]["layer"]["config"]["units"];
const std::string unit_activation = data["config"]["layer"]["config"]["activation"];
const std::string recurrent_activation = data["config"]["layer"]["config"]["recurrent_activation"];
const bool use_bias = data["config"]["layer"]["config"]["use_bias"];
const std::string wrapped_layer_type = data["config"]["layer"]["class_name"];
float_vec forward_bias;
float_vec backward_bias;
if (use_bias)
{
forward_bias = decode_floats(get_param(name, "forward_bias"));
backward_bias = decode_floats(get_param(name, "backward_bias"));
}
const float_vec forward_weights = decode_floats(get_param(name, "forward_weights"));
const float_vec backward_weights = decode_floats(get_param(name, "backward_weights"));
const float_vec forward_recurrent_weights = decode_floats(get_param(name, "forward_recurrent_weights"));
const float_vec backward_recurrent_weights = decode_floats(get_param(name, "backward_recurrent_weights"));
auto&& layer_config = data["config"]["layer"]["config"];
const bool reset_after = json_object_get(layer_config, "reset_after", false);
const bool return_sequences = json_object_get(layer_config, "return_sequences", false);
return std::make_shared<bidirectional_layer>(name, merge_mode, units, unit_activation,
recurrent_activation, wrapped_layer_type,
use_bias, reset_after, return_sequences,
forward_weights, forward_recurrent_weights, forward_bias,
backward_weights, backward_recurrent_weights, backward_bias);
}
inline layer_ptr create_time_distributed_layer(const get_param_f& get_param,
const get_global_param_f& get_global_param,
const nlohmann::json& data,
const std::string& name)
{
const std::string wrapped_layer_type = data["config"]["layer"]["class_name"];
nlohmann::json data_inner_layer = data["config"]["layer"];
data_inner_layer["name"] = data["name"];
data_inner_layer["inbound_nodes"] = data["inbound_nodes"];
const std::size_t td_input_len = std::size_t(decode_floats(get_param(name, "td_input_len")).front());
const std::size_t td_output_len = std::size_t(decode_floats(get_param(name, "td_output_len")).front());
layer_ptr inner_layer = create_layer(get_param, get_global_param, data_inner_layer);
return std::make_shared<time_distributed_layer>(name, inner_layer, td_input_len, td_output_len);
}
inline layer_ptr create_layer(const get_param_f& get_param,
const get_global_param_f& get_global_param, const nlohmann::json& data)
{
const std::string name = data["name"];
const std::unordered_map<std::string,
std::function<layer_ptr(const get_param_f&,
const get_global_param_f&, const nlohmann::json&,
const std::string&)>>
creators = {
{"Model", create_model_layer},
{"Conv1D", create_conv_2d_layer},
{"Conv2D", create_conv_2d_layer},
{"SeparableConv1D", create_separable_conv_2D_layer},
{"SeparableConv2D", create_separable_conv_2D_layer},
{"DepthwiseConv2D", create_depthwise_conv_2D_layer},
{"InputLayer", create_input_layer},
{"BatchNormalization", create_batch_normalization_layer},
{"Dropout", create_dropout_layer},
{"LeakyReLU", create_leaky_relu_layer_isolated},
{"PReLU", create_prelu_layer },
{"ELU", create_elu_layer_isolated},
{"ReLU", create_relu_layer_isolated},
{"MaxPooling1D", create_max_pooling_2d_layer},
{"MaxPooling2D", create_max_pooling_2d_layer},
{"AveragePooling1D", create_average_pooling_2d_layer},
{"AveragePooling2D", create_average_pooling_2d_layer},
{"GlobalMaxPooling1D", create_global_max_pooling_2d_layer},
{"GlobalMaxPooling2D", create_global_max_pooling_2d_layer},
{"GlobalAveragePooling1D", create_global_average_pooling_2d_layer},
{"GlobalAveragePooling2D", create_global_average_pooling_2d_layer},
{"UpSampling1D", create_upsampling_2d_layer},
{"UpSampling2D", create_upsampling_2d_layer},
{"Dense", create_dense_layer},
{"Add", create_add_layer},
{"Maximum", create_maximum_layer},
{"Concatenate", create_concatenate_layer},
{"Multiply", create_multiply_layer},
{"Average", create_average_layer},
{"Subtract", create_subtract_layer},
{"Flatten", create_flatten_layer},
{"ZeroPadding1D", create_zero_padding_2d_layer},
{"ZeroPadding2D", create_zero_padding_2d_layer},
{"Cropping1D", create_cropping_2d_layer},
{"Cropping2D", create_cropping_2d_layer},
{"Activation", create_activation_layer},
{"Reshape", create_reshape_layer},
{"Embedding", create_embedding_layer},
{"LSTM", create_lstm_layer},
{"GRU", create_gru_layer},
{"Bidirectional", create_bidirectional_layer},
{"TimeDistributed", create_time_distributed_layer},
};
const std::string type = data["class_name"];
auto result = fplus::throw_on_nothing(
error("unknown layer type: " + type),
fplus::get_from_map(creators, type))(
get_param, get_global_param, data, name);
if (type != "Activation" &&
json_obj_has_member(data["config"], "activation")
&& type != "GRU"
&& type != "LSTM"
&& type != "Bidirectional")
{
result->set_activation(
create_activation_layer_type_name(get_param, get_global_param, data,
data["config"]["activation"], ""));
}
result->set_nodes(create_nodes(data));
return result;
}
struct test_case
{
tensor5s input_;
tensor5s output_;
};
using test_cases = std::vector<test_case>;
inline test_case load_test_case(const nlohmann::json& data)
{
assertion(data["inputs"].is_array(), "test needs inputs");
assertion(data["outputs"].is_array(), "test needs outputs");
return {
create_vector<tensor5>(create_tensor5, data["inputs"]),
create_vector<tensor5>(create_tensor5, data["outputs"])
};
}
inline test_cases load_test_cases(const nlohmann::json& data)
{
return create_vector<test_case>(load_test_case, data);
}
inline void check_test_outputs(float_type epsilon,
const tensor5s& outputs, const tensor5s& targets)
{
assertion(outputs.size() == targets.size(), "invalid output count");
for (std::size_t i = 0; i < outputs.size(); ++i)
{
const auto& output = outputs[i];
const auto& target = targets[i];
assertion(output.shape() == target.shape(), "wrong output size");
for (std::size_t y = 0; y < output.shape().height_; ++y)
{
for (std::size_t x = 0; x < output.shape().width_; ++x)
{
for (std::size_t z = 0; z < output.shape().depth_; ++z)
{
if (!fplus::is_in_closed_interval_around(epsilon,
target.get(0, 0, y, x, z), output.get(0, 0, y, x, z)))
{
const std::string msg =
std::string("test failed: ") +
"output=" + fplus::show(i) + " " +
"pos=" +
fplus::show(y) + "," +
fplus::show(x) + "," +
fplus::show(z) + " " +
"value=" + fplus::show(output.get(0, 0, y, x, z)) + " "
"target=" + fplus::show(target.get(0, 0, y, x, z));
internal::raise_error(msg);
}
}
}
}
}
}
} } // namespace fdeep, namespace internal