rec/fdeep/model.hpp

275 lines
8.5 KiB
C++
Raw Normal View History

2020-03-18 06:42:46 +00:00
// 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/import_model.hpp"
#include "fdeep/common.hpp"
#include "fdeep/layers/layer.hpp"
#include "fdeep/tensor5.hpp"
#include <algorithm>
#include <string>
#include <vector>
namespace fdeep
{
class model
{
public:
// A single forward pass (no batches).
tensor5s predict(const tensor5s& inputs) const
{
const auto input_shapes = fplus::transform(
fplus_c_mem_fn_t(tensor5, shape, shape5),
inputs);
internal::assertion(input_shapes
== get_input_shapes(),
std::string("Invalid inputs shape.\n") +
"The model takes " + show_shape5s_variable(get_input_shapes()) +
" but provided was: " + show_shape5s(input_shapes));
const auto outputs = model_layer_->apply(inputs);
return outputs;
}
// Forward pass multiple data.
// When parallelly == true, the work is distributed to up to
// as many CPUs as data entries are provided.
std::vector<tensor5s> predict_multi(const std::vector<tensor5s>& inputs_vec,
bool parallelly) const
{
const auto f = [this](const tensor5s& inputs) -> tensor5s
{
return predict(inputs);
};
if (parallelly)
{
return fplus::transform_parallelly(f, inputs_vec);
}
else
{
return fplus::transform(f, inputs_vec);
}
}
// Convenience wrapper around predict for models with
// single tensor outputs of shape (1, 1, z).
// Suitable for classification models with more than one output neuron.
// Returns the index of the output neuron with the maximum activation.
std::size_t predict_class(const tensor5s& inputs) const
{
const tensor5s outputs = predict(inputs);
internal::assertion(outputs.size() == 1,
"invalid number of outputs");
const auto output_shape = outputs.front().shape();
internal::assertion(output_shape.without_depth().area() == 1,
"invalid output shape");
return internal::tensor5_max_pos(outputs.front()).z_;
}
// Convenience wrapper around predict for models with
// single tensor outputs of shape (1, 1, 1),
// typically used for regression or binary classification.
// Returns this one activation value.
float_type predict_single_output(const tensor5s& inputs) const
{
const tensor5s outputs = predict(inputs);
internal::assertion(outputs.size() == 1,
"invalid number of outputs");
const auto output_shape = outputs.front().shape();
internal::assertion(output_shape.volume() == 1,
"invalid output shape");
return outputs.front().get(0, 0, 0, 0, 0);
}
const std::vector<shape5_variable>& get_input_shapes() const
{
return input_shapes_;
}
const std::vector<shape5> get_dummy_input_shapes() const
{
return fplus::transform(
fplus::bind_1st_of_2(internal::make_shape5_with,
shape5(1, 1, 42, 42, 42)),
get_input_shapes());
}
// Returns zero-filled tensors with the models input shapes.
tensor5s generate_dummy_inputs() const
{
return fplus::transform([](const shape5& shape) -> tensor5
{
return tensor5(shape, 0);
}, get_dummy_input_shapes());
}
// Measure time of one single forward pass using dummy input data.
double test_speed() const
{
const auto inputs = generate_dummy_inputs();
fplus::stopwatch stopwatch;
predict(inputs);
return stopwatch.elapsed();
}
const std::string& name() const
{
return model_layer_->name_;
}
const std::string& hash() const
{
return hash_;
}
private:
model(const internal::layer_ptr& model_layer,
const std::vector<shape5_variable>& input_shapes,
const std::string& hash) :
input_shapes_(input_shapes),
model_layer_(model_layer),
hash_(hash) {}
friend model read_model(std::istream&, bool,
const std::function<void(std::string)>&, float_type);
std::vector<shape5_variable> input_shapes_;
internal::layer_ptr model_layer_;
std::string hash_;
};
// Write an std::string to std::cout.
inline void cout_logger(const std::string& str)
{
std::cout << str << std::flush;
}
// Load and construct an fdeep::model from an istream
// providing the exported json content.
// Throws an exception if a problem occurs.
inline model read_model(std::istream& model_file_stream,
bool verify = true,
const std::function<void(std::string)>& logger = cout_logger,
float_type verify_epsilon = static_cast<float_type>(0.0001))
{
const auto log = [&logger](const std::string& msg)
{
if (logger)
{
logger(msg + "\n");
}
};
fplus::stopwatch stopwatch;
const auto log_sol = [&stopwatch, &logger](const std::string& msg)
{
// stopwatch.reset();
// if (logger)
// {
// logger(msg + " ... ");
// }
};
const auto log_duration = [&stopwatch, &logger]()
{
if (logger)
{
// logger("done. elapsed time: " +
// fplus::show_float(0, 6, stopwatch.elapsed()) + " s\n");
}
stopwatch.reset();
};
log_sol("Loading json");
nlohmann::json json_data;
model_file_stream >> json_data;
log_duration();
const std::string image_data_format = json_data["image_data_format"];
internal::assertion(image_data_format == "channels_last",
"only channels_last data format supported");
const std::function<nlohmann::json(
const std::string&, const std::string&)>
get_param = [&json_data]
(const std::string& layer_name, const std::string& param_name)
-> nlohmann::json
{
return json_data["trainable_params"][layer_name][param_name];
};
const std::function<nlohmann::json(const std::string&)>
get_global_param =
[&json_data](const std::string& param_name) -> nlohmann::json
{
return json_data[param_name];
};
const model full_model(internal::create_model_layer(
get_param, get_global_param, json_data["architecture"],
json_data["architecture"]["config"]["name"]),
internal::create_shape5s_variable(json_data["input_shapes"]),
internal::json_object_get<std::string, std::string>(
json_data, "hash", ""));
if (verify)
{
if (!json_data["tests"].is_array())
{
log("No test cases available");
}
else
{
const auto tests = internal::load_test_cases(json_data["tests"]);
json_data = {}; // free RAM
for (std::size_t i = 0; i < tests.size(); ++i)
{
log_sol("Running test " + fplus::show(i + 1) +
" of " + fplus::show(tests.size()));
const auto output = full_model.predict(tests[i].input_);
log_duration();
check_test_outputs(verify_epsilon, output, tests[i].output_);
}
}
}
return full_model;
}
inline model read_model_from_string(const std::string& content,
bool verify = true,
const std::function<void(std::string)>& logger = cout_logger,
float_type verify_epsilon = static_cast<float_type>(0.0001))
{
std::istringstream content_stream(content);
return read_model(content_stream, verify, logger, verify_epsilon);
}
// Load and construct an fdeep::model from file.
// Throws an exception if a problem occurs.
inline model load_model(const std::string& file_path,
bool verify = true,
const std::function<void(std::string)>& logger = cout_logger,
float_type verify_epsilon = static_cast<float_type>(0.0001))
{
fplus::stopwatch stopwatch;
std::ifstream in_stream(file_path);
internal::assertion(in_stream.good(), "Can not open " + file_path);
const auto model = read_model(in_stream, verify, logger, verify_epsilon);
if (logger)
{
const std::string additional_action = verify ? ", testing" : "";
logger("Loading, constructing" + additional_action +
" of " + file_path + " took " +
fplus::show_float(0, 6, stopwatch.elapsed()) + " s overall.\n");
}
return model;
}
} // namespace fdeep