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

100 lines
3.5 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/layers/layer.hpp"
#include "fdeep/tensor5.hpp"
#include <fplus/fplus.hpp>
#include <string>
namespace fdeep { namespace internal
{
// Takes a single stack volume (shape5(1, 1, 1, 1, n)) as input.
class dense_layer : public layer
{
public:
static RowMajorMatrixXf generate_params(std::size_t n_in,
const float_vec& weights, const float_vec& bias)
{
assertion(weights.size() % bias.size() == 0, "invalid params");
return eigen_row_major_mat_from_values(n_in + 1, bias.size(),
fplus::append(weights, bias));
}
dense_layer(const std::string& name, std::size_t units,
const float_vec& weights,
const float_vec& bias) :
layer(name),
n_in_(weights.size() / bias.size()),
n_out_(units),
params_(generate_params(n_in_, weights, bias))
{
assertion(bias.size() == units, "invalid bias count");
assertion(weights.size() % units == 0, "invalid weight count");
}
protected:
tensor5s apply_impl(const tensor5s& inputs) const override
{
assertion(inputs.size() == 1, "invalid number of input tensors");
auto input = inputs.front();
// According to the Keras documentation
// https://keras.io/layers/core/#dense
// "if the input to the layer has a rank greater than 2,
// then it is flattened prior to the initial dot product with kernel."
// But this seems to not be the case.
// Instead it does this: https://stackoverflow.com/a/43237727/1866775
// Otherwise the following would need to be done:
// if (input.shape().get_not_one_dimension_count() > 1)
// {
// input = flatten_tensor5(input);
// }
const auto input_parts = fplus::split_every(
input.shape().depth_, *input.as_vector());
const auto result_value_vectors = fplus::transform(
[this](const auto& input_part) -> float_vec
{
assertion(input_part.size() == n_in_,
"Invalid input value count.");
const auto bias_padded_input = bias_pad_input(input_part);
const auto result = bias_padded_input * params_;
assertion(result.rows() == 1, "invalid result size.");
return *eigen_row_major_mat_to_values(result);
},
input_parts);
const auto result_values = fplus::concat(result_value_vectors);
assertion(result_values.size() % n_out_ == 0,
"Invalid number of output values.");
return {tensor5(shape5(
input.shape().size_dim_5_,
input.shape().size_dim_4_,
input.shape().height_,
input.shape().width_,
n_out_),
fplus::make_shared_ref<fdeep::float_vec>(result_values))};
}
static RowMajorMatrixXf bias_pad_input(const float_vec& input)
{
RowMajorMatrixXf m(1, input.size() + 1);
for (std::size_t z = 0; z < input.size(); ++z)
{
m(0, static_cast<EigenIndex>(z)) = input[z];
}
m(0, static_cast<EigenIndex>(input.size())) = 1;
return m;
}
std::size_t n_in_;
std::size_t n_out_;
RowMajorMatrixXf params_;
};
} } // namespace fdeep, namespace internal