AshanGimhana's picture
Upload folder using huggingface_hub
9375c9a verified
// Copyright (C) 2015 Davis E. King ([email protected])
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_DNn_CORE_H_
#define DLIB_DNn_CORE_H_
#include "core_abstract.h"
#include "../cuda/tensor.h"
#include <iterator>
#include <memory>
#include <sstream>
#include <type_traits>
#include "../statistics.h"
#include "../rand.h"
#include "../algs.h"
#include <utility>
#include <tuple>
#include <cmath>
#include <vector>
#include "../cuda/tensor_tools.h"
#include <type_traits>
#include "../metaprogramming.h"
#ifdef _MSC_VER
// Tell Visual Studio not to recursively inline functions very much because otherwise it
// takes hours to compile the DNN code sometimes. It's crazy. Hopefully we can remove
// this some day when the visual studio compiler is more efficient.
#pragma inline_depth(2)
#endif
namespace dlib
{
// ----------------------------------------------------------------------------------------
namespace impl
{
template <typename T, typename int_<decltype(&T::get_learning_rate_multiplier)>::type = 0>
double get_learning_rate_multiplier (
const T& obj,
special_
) { return obj.get_learning_rate_multiplier(); }
template <typename T>
double get_learning_rate_multiplier ( const T& , general_) { return 1; }
}
template <typename T>
double get_learning_rate_multiplier(const T& obj) { return impl::get_learning_rate_multiplier(obj, special_()); }
namespace impl
{
template <typename T, typename int_<decltype(&T::set_learning_rate_multiplier)>::type = 0>
void set_learning_rate_multiplier (
T& obj,
special_,
double learning_rate_multiplier
) { obj.set_learning_rate_multiplier(learning_rate_multiplier); }
template <typename T>
void set_learning_rate_multiplier (T& , general_, double) { }
}
template <typename T>
void set_learning_rate_multiplier(
T& obj,
double learning_rate_multiplier
)
{
DLIB_CASSERT(learning_rate_multiplier >= 0);
impl::set_learning_rate_multiplier(obj, special_(), learning_rate_multiplier);
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <typename T, typename int_<decltype(&T::get_bias_learning_rate_multiplier)>::type = 0>
double get_bias_learning_rate_multiplier (
const T& obj,
special_
) { return obj.get_bias_learning_rate_multiplier(); }
template <typename T>
double get_bias_learning_rate_multiplier ( const T& , general_) { return 1; }
}
template <typename T>
double get_bias_learning_rate_multiplier(const T& obj) { return impl::get_bias_learning_rate_multiplier(obj, special_()); }
namespace impl
{
template <typename T, typename int_<decltype(&T::set_bias_learning_rate_multiplier)>::type = 0>
void set_bias_learning_rate_multiplier (
T& obj,
special_,
double bias_learning_rate_multiplier
) { obj.set_bias_learning_rate_multiplier(bias_learning_rate_multiplier); }
template <typename T>
void set_bias_learning_rate_multiplier (T& , general_, double) { }
}
template <typename T>
void set_bias_learning_rate_multiplier(
T& obj,
double bias_learning_rate_multiplier
)
{
DLIB_CASSERT(bias_learning_rate_multiplier >= 0);
impl::set_bias_learning_rate_multiplier(obj, special_(), bias_learning_rate_multiplier);
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <typename T, typename int_<decltype(&T::get_weight_decay_multiplier)>::type = 0>
double get_weight_decay_multiplier (
const T& obj,
special_
) { return obj.get_weight_decay_multiplier(); }
template <typename T>
double get_weight_decay_multiplier ( const T& , general_) { return 1; }
}
template <typename T>
double get_weight_decay_multiplier(const T& obj) { return impl::get_weight_decay_multiplier(obj, special_()); }
namespace impl
{
template <typename T, typename int_<decltype(&T::set_weight_decay_multiplier)>::type = 0>
void set_weight_decay_multiplier (
T& obj,
special_,
double weight_decay_multiplier
) { obj.set_weight_decay_multiplier(weight_decay_multiplier); }
template <typename T>
void set_weight_decay_multiplier (T& , general_, double) { }
}
template <typename T>
void set_weight_decay_multiplier(
T& obj,
double weight_decay_multiplier
)
{
DLIB_CASSERT(weight_decay_multiplier >= 0);
impl::set_weight_decay_multiplier(obj, special_(), weight_decay_multiplier);
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <typename T, typename int_<decltype(&T::get_bias_weight_decay_multiplier)>::type = 0>
double get_bias_weight_decay_multiplier (
const T& obj,
special_
) { return obj.get_bias_weight_decay_multiplier(); }
template <typename T>
double get_bias_weight_decay_multiplier ( const T& , general_) { return 1; }
}
template <typename T>
double get_bias_weight_decay_multiplier(const T& obj) { return impl::get_bias_weight_decay_multiplier(obj, special_()); }
namespace impl
{
template <typename T, typename int_<decltype(&T::set_bias_weight_decay_multiplier)>::type = 0>
void set_bias_weight_decay_multiplier (
T& obj,
special_,
double bias_weight_decay_multiplier
) { obj.set_bias_weight_decay_multiplier(bias_weight_decay_multiplier); }
template <typename T>
void set_bias_weight_decay_multiplier (T& , general_, double) { }
}
template <typename T>
void set_bias_weight_decay_multiplier(
T& obj,
double bias_weight_decay_multiplier
)
{
DLIB_CASSERT(bias_weight_decay_multiplier >= 0);
impl::set_bias_weight_decay_multiplier(obj, special_(), bias_weight_decay_multiplier);
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <typename T, typename int_<decltype(&T::disable_bias)>::type = 0>
void disable_bias(
T& obj,
special_
) { obj.disable_bias(); }
template <typename T>
void disable_bias( const T& , general_) { }
}
template <typename T>
void disable_bias(
T& obj
)
{
impl::disable_bias(obj, special_());
}
// ----------------------------------------------------------------------------------------
namespace impl
{
// The reason we return an int for this version rather than doing the more straight forward thing (like we do above) is to avoid a bug in visual studio 2015.
template <typename T>
auto call_clean_method_if_exists (
T& obj,
special_
) -> typename int_<decltype(&T::clean)>::type { obj.clean(); return 0; }
template <typename T>
void call_clean_method_if_exists (T& , general_) {}
}
template <typename T>
void call_clean_method_if_exists(T& obj) { impl::call_clean_method_if_exists(obj, special_()); }
/*!
ensures
- calls obj.clean() if obj has a .clean() method.
!*/
// ----------------------------------------------------------------------------------------
namespace impl
{
class repeat_input_layer
{
/*!
None of the declarations in this object are really used. The only reason it
exists is to allow the repeat object to use a special input layer in its
internal networks which will cause add_tag_layer objects that happen to be
right at the input to not create copies of their input tensors. So
introducing the repeat_input_layer object allows us to optimize the
implementation of add_tag_layer for a special case that arises when it's
used in the context of the repeat layer.
!*/
public:
typedef int input_type;
template <typename forward_iterator>
void to_tensor (
forward_iterator ,
forward_iterator ,
resizable_tensor&
) const
{
}
friend void serialize(const repeat_input_layer&, std::ostream&){}
friend void deserialize(repeat_input_layer&, std::istream&){}
friend std::ostream& operator<<(std::ostream& out, const repeat_input_layer&) { return out; }
};
inline std::string tensor_to_str (
const tensor& t,
int& min_length
)
{
if (t.size() == 0)
return "";
std::ostringstream sout;
sout << "output size=(num:"<< t.num_samples() << ", ";
sout << "k:" << t.k() << ",";
while (sout.tellp() < 28) sout << " ";
sout << "nr:" << t.nr() << ",";
while (sout.tellp() < 28+8) sout << " ";
sout << "nc:" << t.nc() << ")";
while (sout.tellp() < min_length) sout << " ";
min_length = sout.tellp();
sout << "\t";
return sout.str();
}
}
// ----------------------------------------------------------------------------------------
// Tell us if T is one of the special layer types (i.e. add_layer, repeat, add_tag_layer, or
// add_skip_layer).
template <typename T> struct is_nonloss_layer_type : std::false_type {};
// Tell us if T is an instance of add_loss_layer.
template <typename T> struct is_loss_layer_type : std::false_type {};
// Tell us if T is an instance of add_layer
template <typename T> struct is_add_layer : std::false_type {};
namespace impl
{
template <size_t... indices, typename Tuple>
auto tuple_subset(
const Tuple& item,
compile_time_integer_list<indices...>
) -> decltype(std::make_tuple(std::get<indices>(item)...))
{
return std::make_tuple(std::get<indices>(item)...);
}
template <typename Head, typename... Tail>
std::tuple<Tail...> basic_tuple_tail(
const std::tuple<Head, Tail...>& item
)
{
return tuple_subset(item, typename make_compile_time_integer_range<sizeof...(Tail)>::type());
}
template <typename T>
std::tuple<T> tuple_flatten(const T& t)
{
return std::make_tuple(t);
}
template <typename... T>
auto tuple_flatten(
const std::tuple<T...>& item
) -> decltype(tuple_flatten(item, typename make_compile_time_integer_range<sizeof...(T)>::type()))
{
return tuple_flatten(item, typename make_compile_time_integer_range<sizeof...(T)>::type());
}
template <size_t... indices, typename... T>
auto tuple_flatten(
const std::tuple<T...>& item,
compile_time_integer_list<indices...>
) -> decltype(std::tuple_cat(tuple_flatten(std::get<indices-1>(item))...))
{
return std::tuple_cat(tuple_flatten(std::get<indices-1>(item))...);
}
template <typename T>
struct tuple_head_helper
{
typedef T type;
static const type& get(const T& item)
{
return item;
}
};
template <typename T, typename... U>
struct tuple_head_helper<std::tuple<T, U...>>
{
typedef typename tuple_head_helper<T>::type type;
static const type& get(const std::tuple<T,U...>& item)
{
return tuple_head_helper<T>::get(std::get<0>(item));
}
};
template <typename T> struct alwaysbool { typedef bool type; };
// one more structure for VS 2015 UP3 support workaround
template <typename T> struct alwaysbool2 { typedef bool type; };
resizable_tensor& rt();
// The significance of a layer's backward method requiring forward's outputs is
// that such as layer can't have an in-place layer stacked on top of it because
// in-place layers overwrite the output of the layer they sit on top of.
template <typename layer_type, typename SUBNET>
constexpr auto backward_requires_forward_output(
layer_type& layer,
SUBNET& sub
) -> typename alwaysbool<decltype(layer.backward(rt(),rt(),sub,rt()))>::type
{
return true;
}
template <typename layer_type, typename SUBNET>
constexpr auto backward_requires_forward_output(
layer_type& layer,
SUBNET& sub
) -> typename alwaysbool<decltype(layer.backward(rt(),sub,rt()))>::type
{
return false;
}
template <typename layer_type, typename SUBNET>
constexpr auto backward_requires_forward_output(
layer_type& layer,
SUBNET& sub
) -> typename alwaysbool<decltype(layer.backward_inplace(rt(),rt(),sub.get_gradient_input(),rt()))>::type
{
return true;
}
template <typename layer_type, typename SUBNET>
constexpr auto backward_requires_forward_output(
layer_type& layer,
SUBNET& sub
) -> typename alwaysbool<decltype(layer.backward_inplace(rt(),sub.get_gradient_input(),rt()))>::type
{
return false;
}
template <typename layer_type, typename SUBNET>
constexpr auto has_inplace_backward(
layer_type& layer,
SUBNET& sub
) -> typename alwaysbool2<decltype(layer.backward(rt(),rt(),sub,rt()))>::type
{
return false;
}
template <typename layer_type, typename SUBNET>
constexpr auto has_inplace_backward(
layer_type& layer,
SUBNET& sub
) -> typename alwaysbool2<decltype(layer.backward(rt(),sub,rt()))>::type
{
return false;
}
template <typename layer_type, typename SUBNET>
constexpr auto has_inplace_backward(
layer_type& layer,
SUBNET& sub
) -> typename alwaysbool2<decltype(layer.backward_inplace(rt(),rt(),sub.get_gradient_input(),rt()))>::type
{
return true;
}
template <typename layer_type, typename SUBNET>
constexpr auto has_inplace_backward(
layer_type& layer,
SUBNET& sub
) -> typename alwaysbool2<decltype(layer.backward_inplace(rt(),sub.get_gradient_input(),rt()))>::type
{
return true;
}
template <typename layer_type, typename SUBNET>
constexpr auto is_inplace_layer(
layer_type& layer,
const SUBNET& sub
) -> typename alwaysbool2<decltype(layer.forward(sub,rt()))>::type
{
return false;
}
template <typename layer_type, typename SUBNET>
constexpr auto is_inplace_layer(
layer_type& layer,
const SUBNET& sub
) -> typename alwaysbool<decltype(layer.forward_inplace(sub.get_output(),rt()))>::type
{
return true;
}
template <typename layer_type, typename SUBNET>
auto call_layer_backward(
layer_type& layer,
const tensor& computed_output,
const tensor& gradient_input,
SUBNET& sub,
tensor& params_grad
) -> decltype(layer.backward(computed_output,gradient_input,sub,params_grad))
{
layer.backward(computed_output,gradient_input,sub,params_grad);
}
template <typename layer_type, typename SUBNET>
auto call_layer_backward(
layer_type& layer,
const tensor& ,
const tensor& gradient_input,
SUBNET& sub,
tensor& params_grad
) -> decltype(layer.backward(gradient_input,sub,params_grad))
{
layer.backward(gradient_input,sub,params_grad);
}
template <typename layer_type, typename SUBNET>
auto call_layer_backward(
layer_type& layer,
const tensor& computed_output,
const tensor& gradient_input,
SUBNET& sub,
tensor& params_grad
) -> decltype(layer.backward_inplace(computed_output,gradient_input,sub.get_gradient_input(),params_grad))
{
layer.backward_inplace(computed_output,gradient_input,sub.get_gradient_input(),params_grad);
}
template <typename layer_type, typename SUBNET>
auto call_layer_backward(
layer_type& layer,
const tensor& ,
const tensor& gradient_input,
SUBNET& sub,
tensor& params_grad
) -> decltype(layer.backward_inplace(gradient_input,sub.get_gradient_input(),params_grad))
{
layer.backward_inplace(gradient_input,sub.get_gradient_input(),params_grad);
}
template <typename layer_type, typename SUBNET>
auto call_layer_forward(
layer_type& layer,
const SUBNET& sub,
tensor& /*data_output*/
) -> decltype(layer.forward(sub,rt()))
{
// This overload of call_layer_forward() is here because this template
// naturally gets instantiated but only on code paths that never get executed.
// So rather than writing a bunch of hard to read template magic around call
// sites we just have this overload that doesn't do anything (and an assert to
// make sure that's the case).
DLIB_CASSERT(false, "This should never happen");
}
template <typename layer_type, typename SUBNET>
auto call_layer_forward(
layer_type& layer,
const SUBNET& sub,
resizable_tensor& data_output
) -> decltype(layer.forward(sub,data_output))
{
layer.forward(sub,data_output);
}
template <typename layer_type, typename SUBNET>
auto call_layer_forward(
layer_type& layer,
const SUBNET& sub,
tensor& data_output
) -> decltype(layer.forward_inplace(sub.get_output(),data_output))
{
layer.forward_inplace(sub.get_output(),data_output);
}
template <typename layer_type, typename SUBNET>
auto call_layer_forward(
layer_type& layer,
const SUBNET& sub,
resizable_tensor& data_output
) -> decltype(layer.forward_inplace(sub.get_output(),data_output))
{
if (!have_same_dimensions(data_output, sub.get_output()))
data_output.copy_size(sub.get_output());
layer.forward_inplace(sub.get_output(),static_cast<tensor&>(data_output));
}
} // end namespace impl
template <typename... T>
typename impl::tuple_head_helper<std::tuple<T...>>::type tuple_head (
const std::tuple<T...>& item
)
{
return impl::tuple_head_helper<std::tuple<T...>>::get(item);
}
template <typename... T>
auto tuple_tail(
const std::tuple<T...>& item
) -> decltype(impl::basic_tuple_tail(impl::tuple_flatten(item)))
{
return impl::basic_tuple_tail(impl::tuple_flatten(item));
}
inline std::tuple<> tuple_tail(
const std::tuple<>& item
)
{
return item;
}
// ----------------------------------------------------------------------------------------
template <typename T>
class sstack
{
public:
typedef T value_type;
sstack() = delete;
sstack (
T* data_,
size_t s
) : data(data_), mysize(s) {}
const T& top() const
{
DLIB_CASSERT(size() != 0, "You can't call top() on an empty stack");
return *data;
}
T& top()
{
DLIB_CASSERT(size() != 0, "You can't call top() on an empty stack");
return *data;
}
size_t size() const { return mysize; }
sstack pop(size_t num=1)
{
DLIB_CASSERT(num <= size(), "You can't pop more things from the stack than it has in it.");
return sstack(data+num, mysize-num);
}
private:
T* data;
size_t mysize;
};
template <typename T>
sstack<T> make_sstack(std::vector<T>& item)
{
return sstack<T>(item.data(), item.size());
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
namespace dimpl
{
template <typename T, bool is_first = true, typename enabled=void>
class subnet_wrapper
{
/*!
WHAT THIS OBJECT REPRESENTS
This is a tool that makes an add_layer or add_loss_layer object
expose only the part of its interface defined by the SUBNET
type in layers_abstract.h. This way, when we pass subnetwork
objects to the layer callbacks those callbacks won't be able to
interact with the subnetworks in a way other than specified
by the SUBNET interface spec.
We also allow the top layer of a subnet_wrapper stack to call the
private_get_output() and private_get_gradient_input() functions. This
way, layers that have had their output/gradient overwritten by in-place
layers can only be accessed from the in-place layers that sit directly
on top of them since those in-place layers are the only layers that
know how to interact with them properly.
!*/
public:
subnet_wrapper(const subnet_wrapper&) = delete;
subnet_wrapper& operator=(const subnet_wrapper&) = delete;
subnet_wrapper(T& l_, unsigned int sef) : l(l_),_sample_expansion_factor(sef) {}
// Not much here because in this case T is one of the input layer types
// that doesn't have anything in it.
typedef T layer_details_type;
const layer_details_type& layer_details() const { return l; }
unsigned int sample_expansion_factor() const { return _sample_expansion_factor; }
private:
T& l;
unsigned int _sample_expansion_factor;
};
template <typename T>
class subnet_wrapper<T,true, typename std::enable_if<is_nonloss_layer_type<T>::value>::type>
{
public:
subnet_wrapper(const subnet_wrapper&) = delete;
subnet_wrapper& operator=(const subnet_wrapper&) = delete;
typedef T wrapped_type;
const static size_t num_computational_layers = T::num_computational_layers;
const static size_t num_layers = T::num_layers;
typedef typename T::layer_details_type layer_details_type;
subnet_wrapper(T& l_, unsigned int = 0) : l(l_),subnetwork(l.subnet(), l.sample_expansion_factor()) {}
const tensor& get_output() const { return l.private_get_output(); }
tensor& get_gradient_input() { return l.private_get_gradient_input(); }
const layer_details_type& layer_details() const { return l.layer_details(); }
const subnet_wrapper<typename T::subnet_type,false>& subnet() const { return subnetwork; }
subnet_wrapper<typename T::subnet_type,false>& subnet() { return subnetwork; }
unsigned int sample_expansion_factor() const { return l.sample_expansion_factor(); }
private:
T& l;
subnet_wrapper<typename T::subnet_type,false> subnetwork;
};
template <typename T>
class subnet_wrapper<T,false, typename std::enable_if<is_nonloss_layer_type<T>::value>::type>
{
public:
subnet_wrapper(const subnet_wrapper&) = delete;
subnet_wrapper& operator=(const subnet_wrapper&) = delete;
typedef T wrapped_type;
const static size_t num_computational_layers = T::num_computational_layers;
const static size_t num_layers = T::num_layers;
typedef typename T::layer_details_type layer_details_type;
subnet_wrapper(T& l_, unsigned int = 0) : l(l_),subnetwork(l.subnet(), l.sample_expansion_factor()) {}
const tensor& get_output() const { return l.get_output(); }
tensor& get_gradient_input() { return l.get_gradient_input(); }
const layer_details_type& layer_details() const { return l.layer_details(); }
const subnet_wrapper<typename T::subnet_type,false>& subnet() const { return subnetwork; }
subnet_wrapper<typename T::subnet_type,false>& subnet() { return subnetwork; }
unsigned int sample_expansion_factor() const { return l.sample_expansion_factor(); }
private:
T& l;
subnet_wrapper<typename T::subnet_type,false> subnetwork;
};
}
// ----------------------------------------------------------------------------------------
template <typename LAYER_DETAILS, typename SUBNET, typename enabled = void>
class add_layer;
template <typename LAYER_DETAILS, typename SUBNET, typename enabled>
void serialize(const add_layer<LAYER_DETAILS,SUBNET,enabled>& item, std::ostream& out);
template <typename LAYER_DETAILS, typename SUBNET, typename enabled>
void deserialize(add_layer<LAYER_DETAILS,SUBNET,enabled>& item, std::istream& in);
template <typename T, typename U>
struct is_nonloss_layer_type<add_layer<T,U>> : std::true_type {};
template <typename LAYER_DETAILS, typename SUBNET>
class add_layer<LAYER_DETAILS,SUBNET,
typename std::enable_if<is_nonloss_layer_type<SUBNET>::value>::type>
{
public:
typedef LAYER_DETAILS layer_details_type;
typedef SUBNET subnet_type;
typedef typename subnet_type::input_type input_type;
const static size_t num_layers = subnet_type::num_layers + 1;
const static size_t num_computational_layers = subnet_type::num_computational_layers + 1;
add_layer(
):
subnetwork(new subnet_type()),
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false)
{
if (this_layer_operates_inplace())
subnetwork->disable_output_and_gradient_getters();
}
add_layer(const add_layer& item)
{
details = item.details;
subnetwork.reset(new subnet_type(*item.subnetwork));
this_layer_setup_called = item.this_layer_setup_called;
gradient_input_is_stale = item.gradient_input_is_stale;
get_output_and_gradient_input_disabled = item.get_output_and_gradient_input_disabled;
x_grad = item.x_grad;
cached_output = item.cached_output;
params_grad = item.params_grad;
temp_tensor = item.temp_tensor;
}
add_layer& operator=(const add_layer& item) { add_layer(item).swap(*this); return *this;}
add_layer(add_layer&& item) : add_layer() { swap(item); }
add_layer& operator=(add_layer&& item) { swap(item); return *this; }
template <typename T, typename U, typename E>
friend class add_layer;
template <typename T, bool is_first, typename E>
friend class dimpl::subnet_wrapper;
template <unsigned long T, typename U, typename E>
friend class add_tag_layer;
template <template<typename> class T, typename U>
friend class add_skip_layer;
template <size_t N, template<typename> class L, typename S>
friend class repeat;
// Allow copying networks from one to another as long as their corresponding
// layers can be constructed from each other.
template <typename T, typename U, typename E>
add_layer(
const add_layer<T,U,E>& item
) :
details(item.layer_details()),
subnetwork(new subnet_type(item.subnet())),
this_layer_setup_called(item.this_layer_setup_called),
gradient_input_is_stale(item.gradient_input_is_stale),
get_output_and_gradient_input_disabled(item.get_output_and_gradient_input_disabled),
x_grad(item.x_grad),
cached_output(item.cached_output)
{
if (this_layer_operates_inplace())
subnetwork->disable_output_and_gradient_getters();
}
template <typename ...T>
add_layer(
const LAYER_DETAILS& layer_det,
T&& ...args
) :
details(layer_det),
subnetwork(new subnet_type(std::forward<T>(args)...)),
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false)
{
if (this_layer_operates_inplace())
subnetwork->disable_output_and_gradient_getters();
}
template <typename T, typename ...U>
struct disable_forwarding_constr
{
const static bool value = std::is_constructible<LAYER_DETAILS,T>::value;
};
template <typename ...T, typename ...U>
struct disable_forwarding_constr<std::tuple<T...>,U...>
{
const static bool value = disable_forwarding_constr<typename std::remove_reference<T>::type...>::value;
};
template <typename T, typename ...U>
struct disable_forwarding_constr<std::tuple<T>,U...>
{
const static bool value = disable_forwarding_constr<typename std::remove_reference<T>::type>::value;
};
template <typename ...U>
struct disable_forwarding_constr<std::tuple<>,U...>
{
const static bool value = true;
};
template <typename ...T>
struct disable_forwarding_constr<add_layer<T...>>
{
const static bool value = true;
};
template <
typename ...T,
typename = typename std::enable_if<!disable_forwarding_constr<typename std::remove_reference<T>::type...>::value>::type
>
add_layer(
T&& ...args
) :
subnetwork(new subnet_type(std::forward<T>(args)...)),
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false)
{
if (this_layer_operates_inplace())
subnetwork->disable_output_and_gradient_getters();
}
template <typename ...T>
add_layer(
LAYER_DETAILS&& layer_det,
T&& ...args
) :
details(std::move(layer_det)),
subnetwork(new subnet_type(std::forward<T>(args)...)),
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false)
{
if (this_layer_operates_inplace())
subnetwork->disable_output_and_gradient_getters();
}
template <typename ...T, typename LD, typename ...U>
add_layer(
const std::tuple<LD,U...>& layer_det,
T&& ...args
) :
details(tuple_head(layer_det)),
subnetwork(new subnet_type(tuple_tail(layer_det),std::forward<T>(args)...)),
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false)
{
if (this_layer_operates_inplace())
subnetwork->disable_output_and_gradient_getters();
}
template <typename ...T, typename LD, typename ...U>
add_layer(
std::tuple<>,
const std::tuple<LD,U...>& layer_det,
T&& ...args
) : add_layer(layer_det,args...) { }
add_layer (
std::tuple<>
) : add_layer() {}
template <typename ...T>
add_layer(
std::tuple<>,
LAYER_DETAILS&& layer_det,
T&& ...args
) : add_layer(layer_det, args...) { }
template <typename forward_iterator>
void to_tensor (
forward_iterator ibegin,
forward_iterator iend,
resizable_tensor& data
) const
{
subnetwork->to_tensor(ibegin,iend,data);
}
template <typename forward_iterator>
const tensor& operator() (
forward_iterator ibegin,
forward_iterator iend
)
{
to_tensor(ibegin,iend,temp_tensor);
return forward(temp_tensor);
}
const tensor& operator() (const input_type& x)
{
return (*this)(&x, &x+1);
}
const tensor& forward(const tensor& x)
{
subnetwork->forward(x);
const dimpl::subnet_wrapper<subnet_type> wsub(*subnetwork);
if (!this_layer_setup_called)
{
details.setup(wsub);
this_layer_setup_called = true;
}
if (this_layer_operates_inplace())
impl::call_layer_forward(details, wsub, private_get_output());
else
impl::call_layer_forward(details, wsub, cached_output);
gradient_input_is_stale = true;
return private_get_output();
}
private:
tensor& private_get_output() const
{
if (const_cast<add_layer&>(*this).this_layer_operates_inplace())
return subnetwork->private_get_output();
else
return const_cast<resizable_tensor&>(cached_output);
}
tensor& private_get_gradient_input()
{
if (this_layer_operates_inplace())
{
return subnetwork->private_get_gradient_input();
}
else
{
if (gradient_input_is_stale)
{
gradient_input_is_stale = false;
x_grad.copy_size(private_get_output());
x_grad = 0;
}
return x_grad;
}
}
void disable_output_and_gradient_getters (
) { get_output_and_gradient_input_disabled = true; }
public:
const tensor& get_output() const
{
if (get_output_and_gradient_input_disabled)
throw dlib::error("Accessing this layer's get_output() is disabled because an in-place layer has been stacked on top of it.");
return private_get_output();
}
tensor& get_gradient_input()
{
if (get_output_and_gradient_input_disabled)
throw dlib::error("Accessing this layer's get_gradient_input() is disabled because an in-place layer has been stacked on top of it.");
return private_get_gradient_input();
}
const tensor& get_final_data_gradient(
) const { return subnetwork->get_final_data_gradient(); }
void back_propagate_error(const tensor& x)
{
back_propagate_error(x, private_get_gradient_input());
}
void back_propagate_error(const tensor& x, const tensor& gradient_input)
{
dimpl::subnet_wrapper<subnet_type> wsub(*subnetwork);
params_grad.copy_size(details.get_layer_params());
impl::call_layer_backward(details, private_get_output(),
gradient_input, wsub, static_cast<tensor&>(params_grad));
subnetwork->back_propagate_error(x);
// zero out get_gradient_input()
gradient_input_is_stale = true;
}
template <typename solver_type>
void update_parameters(sstack<solver_type> solvers, double learning_rate)
{
DLIB_CASSERT(solvers.size()>=num_computational_layers);
// Don't try to adjust the parameters if this layer doesn't have any or the
// learning rate is disabled for this layer.
if (params_grad.size() != 0 && get_learning_rate_multiplier(details) != 0)
{
const tensor& step = solvers.top()(learning_rate, details, static_cast<const tensor&>(params_grad));
tt::add(details.get_layer_params(), details.get_layer_params(), step);
}
subnetwork->update_parameters(solvers.pop(), learning_rate);
}
template <typename solver_type>
void update_parameters(std::vector<solver_type>& solvers, double learning_rate)
{
update_parameters(make_sstack(solvers), learning_rate);
}
const tensor& get_parameter_gradient(
) const { return params_grad; }
tensor& get_parameter_gradient (
) { return params_grad; }
const subnet_type& subnet() const { return *subnetwork; }
subnet_type& subnet() { return *subnetwork; }
const layer_details_type& layer_details() const { return details; }
layer_details_type& layer_details() { return details; }
unsigned int sample_expansion_factor() const { return subnet().sample_expansion_factor(); }
void clean()
{
x_grad.clear();
cached_output.clear();
params_grad.clear();
temp_tensor.clear();
gradient_input_is_stale = true;
subnetwork->clean();
call_clean_method_if_exists(details);
}
friend void serialize(const add_layer& item, std::ostream& out)
{
int version = 2;
serialize(version, out);
serialize(*item.subnetwork, out);
serialize(item.details, out);
serialize(item.this_layer_setup_called, out);
serialize(item.gradient_input_is_stale, out);
serialize(item.get_output_and_gradient_input_disabled, out);
serialize(item.x_grad, out);
serialize(item.cached_output, out);
serialize(item.params_grad, out);
}
friend void deserialize(add_layer& item, std::istream& in)
{
int version = 0;
deserialize(version, in);
if (!(1 <= version && version <= 2))
throw serialization_error("Unexpected version found while deserializing dlib::add_layer.");
deserialize(*item.subnetwork, in);
deserialize(item.details, in);
deserialize(item.this_layer_setup_called, in);
deserialize(item.gradient_input_is_stale, in);
deserialize(item.get_output_and_gradient_input_disabled, in);
deserialize(item.x_grad, in);
deserialize(item.cached_output, in);
if (version == 2)
deserialize(item.params_grad, in);
}
friend std::ostream& operator<< (std::ostream& out, const add_layer& item)
{
int min_length = 0;
item.print(out, 0, min_length);
return out;
}
void print (std::ostream& out, unsigned long idx, int& min_length) const
{
out << "layer<" << idx << ">\t" << impl::tensor_to_str(private_get_output(), min_length) << layer_details() << "\n";
subnet().print(out, idx+1, min_length);
}
private:
bool this_layer_operates_inplace(
)
{
// This layer can run in-place if it's an in-place capable layer and also if
// the layer it's on top of doesn't need its own output tensor (since in-place
// layers overwrite that tensor)
return impl::is_inplace_layer(details, *subnetwork) && !subnetwork->this_layer_requires_forward_output();
}
bool this_layer_requires_forward_output(
)
{
return impl::backward_requires_forward_output(details, *subnetwork);
}
void swap(add_layer& item)
{
std::swap(subnetwork,item.subnetwork);
std::swap(details, item.details);
std::swap(this_layer_setup_called, item.this_layer_setup_called);
std::swap(gradient_input_is_stale, item.gradient_input_is_stale);
std::swap(get_output_and_gradient_input_disabled, item.get_output_and_gradient_input_disabled);
std::swap(x_grad, item.x_grad);
std::swap(cached_output, item.cached_output);
std::swap(params_grad, item.params_grad);
}
LAYER_DETAILS details;
std::unique_ptr<subnet_type> subnetwork;
bool this_layer_setup_called;
bool gradient_input_is_stale;
bool get_output_and_gradient_input_disabled;
// Note that if this_layer_operates_inplace()==true then x_grad and cached_output
// are not used at all. Instead, this layer uses these variables from the lower
// layer.
resizable_tensor x_grad;
resizable_tensor cached_output;
resizable_tensor params_grad;
// temp_tensor doesn't logically contribute to the state of this object.
// It is here only to prevent it from being reallocated over and over.
resizable_tensor temp_tensor;
};
template <typename T, typename U, typename E>
struct is_add_layer<add_layer<T,U,E>> : std::true_type {};
template <typename T, typename U, typename E>
struct is_add_layer<const add_layer<T,U,E>> : std::true_type {};
template <typename T, typename U, typename E>
struct is_add_layer<add_layer<T,U,E>&> : std::true_type {};
template <typename T, typename U, typename E>
struct is_add_layer<const add_layer<T,U,E>&> : std::true_type {};
// ----------------------------------------------------------------------------------------
// This version of add_layer handles the special case where the subnetwork being given is
// just an input layer object.
template <typename LAYER_DETAILS, typename INPUT_LAYER, typename enabled>
class add_layer
{
public:
typedef LAYER_DETAILS layer_details_type;
typedef INPUT_LAYER subnet_type;
typedef typename INPUT_LAYER::input_type input_type;
const static size_t num_layers = 2;
const static size_t num_computational_layers = 1;
add_layer(
):
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false),
_sample_expansion_factor(0)
{}
add_layer(const add_layer&) = default;
add_layer(add_layer&& item) : add_layer() { swap(item); }
add_layer& operator=(const add_layer&) = default;
add_layer& operator=(add_layer&& item) { swap(item); return *this; }
template <typename T, typename U, typename E>
friend class add_layer;
template <typename T, bool is_first, typename E>
friend class dimpl::subnet_wrapper;
template <unsigned long T, typename U, typename E>
friend class add_tag_layer;
template <template<typename> class T, typename U>
friend class add_skip_layer;
template <size_t N, template<typename> class L, typename S>
friend class repeat;
// Allow copying networks from one to another as long as their corresponding
// layers can be constructed from each other.
template <typename T, typename U, typename E>
add_layer(
const add_layer<T,U,E>& item
):
input_layer(item.subnet()),
details(item.layer_details()),
this_layer_setup_called(item.this_layer_setup_called),
gradient_input_is_stale(item.gradient_input_is_stale),
get_output_and_gradient_input_disabled(false),
_sample_expansion_factor(item._sample_expansion_factor),
x_grad(item.x_grad),
cached_output(item.cached_output),
grad_final(item.grad_final)
{
}
add_layer(
const LAYER_DETAILS& layer_det
) :
details(layer_det),
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false),
_sample_expansion_factor(0)
{}
add_layer(
const INPUT_LAYER& il
) :
input_layer(il),
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false),
_sample_expansion_factor(0)
{}
add_layer(
LAYER_DETAILS&& layer_det
) :
details(std::move(layer_det)),
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false),
_sample_expansion_factor(0)
{}
add_layer(
LAYER_DETAILS layer_det,
INPUT_LAYER il
) :
details(std::move(layer_det)),
input_layer(std::move(il)),
this_layer_setup_called(false),
gradient_input_is_stale(true),
get_output_and_gradient_input_disabled(false),
_sample_expansion_factor(0)
{}
add_layer(
std::tuple<>,
const LAYER_DETAILS& layer_det
) : add_layer(layer_det) {}
add_layer(
std::tuple<>,
LAYER_DETAILS&& layer_det
) : add_layer(layer_det) {}
add_layer(
std::tuple<>,
LAYER_DETAILS layer_det,
INPUT_LAYER il
) : add_layer(layer_det,il) {}
add_layer(
const std::tuple<LAYER_DETAILS>& layer_det
) : add_layer(tuple_head(layer_det)) {}
add_layer(
const std::tuple<LAYER_DETAILS>& layer_det,
INPUT_LAYER il
) : add_layer(tuple_head(layer_det),il) {}
template <typename forward_iterator>
void to_tensor (
forward_iterator ibegin,
forward_iterator iend,
resizable_tensor& data
) const
{
input_layer.to_tensor(ibegin, iend, data);
// make sure the input layer's to_tensor() function is implemented properly.
DLIB_CASSERT(data.num_samples() >= std::distance(ibegin,iend),
"The input layer can't produce fewer output tensors than there are inputs.");
DLIB_CASSERT(data.num_samples()%std::distance(ibegin,iend) == 0,
"The number of tensors produced by the input layer must be an integer multiple of the number of input objects.");
_sample_expansion_factor = data.num_samples()/std::distance(ibegin,iend);
data.async_copy_to_device();
}
template <typename forward_iterator>
const tensor& operator() (
forward_iterator ibegin,
forward_iterator iend
)
{
to_tensor(ibegin,iend,temp_tensor);
return forward(temp_tensor);
}
const tensor& operator() (const input_type& x)
{
return (*this)(&x, &x+1);
}
const tensor& forward (const tensor& x)
{
DLIB_CASSERT(sample_expansion_factor() != 0, "You must call to_tensor() before this function can be used.");
DLIB_CASSERT(x.num_samples()%sample_expansion_factor() == 0);
subnet_wrapper wsub(x, grad_final, _sample_expansion_factor);
if (!this_layer_setup_called)
{
details.setup(wsub);
this_layer_setup_called = true;
}
impl::call_layer_forward(details, wsub, cached_output);
gradient_input_is_stale = true;
return private_get_output();
}
private:
tensor& private_get_output() const { return const_cast<resizable_tensor&>(cached_output); }
tensor& private_get_gradient_input()
{
if (gradient_input_is_stale)
{
gradient_input_is_stale = false;
x_grad.copy_size(private_get_output());
x_grad = 0;
}
return x_grad;
}
void disable_output_and_gradient_getters (
) { get_output_and_gradient_input_disabled = true; }
public:
const tensor& get_output() const
{
if (get_output_and_gradient_input_disabled)
throw dlib::error("Accessing this layer's get_output() is disabled because an in-place layer has been stacked on top of it.");
return private_get_output();
}
tensor& get_gradient_input()
{
if (get_output_and_gradient_input_disabled)
throw dlib::error("Accessing this layer's get_gradient_input() is disabled because an in-place layer has been stacked on top of it.");
return private_get_gradient_input();
}
const tensor& get_final_data_gradient(
) const { return grad_final; }
void back_propagate_error(const tensor& x)
{
back_propagate_error(x, private_get_gradient_input());
}
void back_propagate_error(const tensor& x, const tensor& gradient_input)
{
// make sure grad_final is initialized to 0
if (!have_same_dimensions(x, grad_final))
grad_final.copy_size(x);
grad_final = 0;
subnet_wrapper wsub(x, grad_final, _sample_expansion_factor);
params_grad.copy_size(details.get_layer_params());
impl::call_layer_backward(details, private_get_output(),
gradient_input, wsub, static_cast<tensor&>(params_grad));
// zero out get_gradient_input()
gradient_input_is_stale = true;
}
template <typename solver_type>
void update_parameters(sstack<solver_type> solvers, double learning_rate)
{
DLIB_CASSERT(solvers.size()>=num_computational_layers);
// Don't try to adjust the parameters if this layer doesn't have any or the
// learning rate is disabled for this layer.
if (params_grad.size() != 0 && get_learning_rate_multiplier(details) != 0)
{
const tensor& step = solvers.top()(learning_rate, details, static_cast<const tensor&>(params_grad));
tt::add(details.get_layer_params(), details.get_layer_params(), step);
}
}
template <typename solver_type>
void update_parameters(std::vector<solver_type>& solvers, double learning_rate)
{
update_parameters(make_sstack(solvers), learning_rate);
}
const tensor& get_parameter_gradient(
) const { return params_grad; }
tensor& get_parameter_gradient (
) { return params_grad; }
const subnet_type& subnet() const { return input_layer; }
subnet_type& subnet() { return input_layer; }
const layer_details_type& layer_details() const { return details; }
layer_details_type& layer_details() { return details; }
unsigned int sample_expansion_factor() const { return _sample_expansion_factor; }
void clean()
{
x_grad.clear();
grad_final.clear();
cached_output.clear();
params_grad.clear();
temp_tensor.clear();
gradient_input_is_stale = true;
call_clean_method_if_exists(details);
}
friend void serialize(const add_layer& item, std::ostream& out)
{
int version = 3;
serialize(version, out);
serialize(item.input_layer, out);
serialize(item.details, out);
serialize(item.this_layer_setup_called, out);
serialize(item.gradient_input_is_stale, out);
serialize(item.get_output_and_gradient_input_disabled, out);
serialize(item.x_grad, out);
serialize(item.cached_output, out);
serialize(item.grad_final, out);
serialize(item._sample_expansion_factor, out);
}
friend void deserialize(add_layer& item, std::istream& in)
{
int version = 0;
deserialize(version, in);
if (!(2 <= version && version <= 3))
throw serialization_error("Unexpected version found while deserializing dlib::add_layer.");
deserialize(item.input_layer, in);
deserialize(item.details, in);
deserialize(item.this_layer_setup_called, in);
deserialize(item.gradient_input_is_stale, in);
deserialize(item.get_output_and_gradient_input_disabled, in);
deserialize(item.x_grad, in);
deserialize(item.cached_output, in);
deserialize(item.grad_final, in);
if (version >= 3)
deserialize(item._sample_expansion_factor, in);
else
item._sample_expansion_factor = 1; // all layer types set this to 1 in older dlib versions, so that's what we put here.
}
friend std::ostream& operator<< (std::ostream& out, const add_layer& item)
{
int min_length = 0;
item.print(out, 0, min_length);
return out;
}
void print (std::ostream& out, unsigned long idx, int& min_length) const
{
out << "layer<" << idx << ">\t" << impl::tensor_to_str(private_get_output(), min_length) << layer_details() << "\n";
// Don't print the repeat_input_layer since it doesn't exist from the user's
// point of view. It's just an artifact of how repeat<> works.
if (!std::is_same<subnet_type, impl::repeat_input_layer>::value)
out << "layer<" << idx+1 << ">\t" << subnet() << "\n";
}
private:
bool this_layer_requires_forward_output(
)
{
subnet_wrapper wsub(grad_final, grad_final, _sample_expansion_factor);
return impl::backward_requires_forward_output(details, wsub);
}
class subnet_wrapper
{
public:
subnet_wrapper(const tensor& x_, resizable_tensor& grad_final_, unsigned int sef) :
x(x_), grad_final(grad_final_), _sample_expansion_factor(sef) {}
subnet_wrapper(const subnet_wrapper&) = delete;
subnet_wrapper& operator=(const subnet_wrapper&) = delete;
unsigned int sample_expansion_factor() const { return _sample_expansion_factor;}
const tensor& get_output() const { return x; }
tensor& get_gradient_input()
{
if (!have_same_dimensions(x, grad_final))
{
grad_final.copy_size(x);
grad_final = 0;
}
return grad_final;
}
private:
const tensor& x;
resizable_tensor& grad_final;
unsigned int _sample_expansion_factor;
};
void swap(add_layer& item)
{
std::swap(input_layer, item.input_layer);
std::swap(details, item.details);
std::swap(this_layer_setup_called, item.this_layer_setup_called);
std::swap(gradient_input_is_stale, item.gradient_input_is_stale);
std::swap(get_output_and_gradient_input_disabled, item.get_output_and_gradient_input_disabled);
std::swap(x_grad, item.x_grad);
std::swap(cached_output, item.cached_output);
std::swap(grad_final, item.grad_final);
std::swap(_sample_expansion_factor, item._sample_expansion_factor);
}
subnet_type input_layer;
LAYER_DETAILS details;
bool this_layer_setup_called;
bool gradient_input_is_stale;
bool get_output_and_gradient_input_disabled;
mutable unsigned int _sample_expansion_factor;
resizable_tensor x_grad;
resizable_tensor cached_output;
resizable_tensor grad_final;
// The following 2 objects don't logically contribute to the state of this class.
// They are only here to prevent them from being reallocated over and over in
// member functions.
resizable_tensor params_grad;
resizable_tensor temp_tensor;
};
// ----------------------------------------------------------------------------------------
template <unsigned long ID, typename SUBNET, typename enabled=void>
class add_tag_layer;
template <template<typename SUBNET> class tag>
struct tag_id
{
const static unsigned long id = tag<impl::repeat_input_layer>::id;
};
template <unsigned long ID, typename SUBNET>
class add_tag_layer<ID,SUBNET,
typename std::enable_if<is_nonloss_layer_type<SUBNET>::value>::type>
{
public:
typedef SUBNET subnet_type;
typedef typename subnet_type::input_type input_type;
typedef int layer_details_type; // not really used anywhere, but required by subnet_wrapper.
const static size_t num_layers = subnet_type::num_layers + 1;
const static size_t num_computational_layers = subnet_type::num_computational_layers;
const static unsigned long id = ID;
add_tag_layer() {};
add_tag_layer(const add_tag_layer&) = default;
add_tag_layer(add_tag_layer&&) = default;
add_tag_layer& operator=(add_tag_layer&&) = default;
add_tag_layer& operator=(const add_tag_layer&) = default;
template <typename T>
add_tag_layer(
const add_tag_layer<ID,T>& item
) : subnetwork(item.subnet())
{}
template <typename ...T>
add_tag_layer(
T ...args
) :
subnetwork(std::move(args)...)
{
}
template <typename forward_iterator>
void to_tensor (
forward_iterator ibegin,
forward_iterator iend,
resizable_tensor& data
) const
{
subnetwork.to_tensor(ibegin,iend,data);
}
template <typename forward_iterator>
const tensor& operator() (
forward_iterator ibegin,
forward_iterator iend
)
{
return subnetwork(ibegin,iend);
}
const tensor& operator() (const input_type& x)
{
return subnetwork(x);
}
const tensor& forward(const tensor& x)
{
return subnetwork.forward(x);
}
const tensor& get_output() const { return subnetwork.get_output(); }
tensor& get_gradient_input()
{
return subnetwork.get_gradient_input();
}
const tensor& get_final_data_gradient(
) const { return subnetwork.get_final_data_gradient(); }
void back_propagate_error(const tensor& x)
{
subnetwork.back_propagate_error(x);
}
void back_propagate_error(const tensor& x, const tensor& gradient_input)
{
subnetwork.back_propagate_error(x,gradient_input);
}
template <typename solver_type>
void update_parameters(sstack<solver_type> solvers, double learning_rate)
{
subnetwork.update_parameters(solvers, learning_rate);
}
template <typename solver_type>
void update_parameters(std::vector<solver_type>& solvers, double learning_rate)
{
update_parameters(make_sstack(solvers), learning_rate);
}
const tensor& get_parameter_gradient(
) const { return params_grad; }
tensor& get_parameter_gradient (
) { return params_grad; }
const subnet_type& subnet() const { return subnetwork; }
subnet_type& subnet() { return subnetwork; }
unsigned int sample_expansion_factor() const { return subnet().sample_expansion_factor(); }
void clean()
{
subnetwork.clean();
}
friend void serialize(const add_tag_layer& item, std::ostream& out)
{
int version = 1;
serialize(version, out);
serialize(item.subnetwork, out);
}
friend void deserialize(add_tag_layer& item, std::istream& in)
{
int version = 0;
deserialize(version, in);
if (version != 1)
throw serialization_error("Unexpected version found while deserializing dlib::add_tag_layer.");
deserialize(item.subnetwork, in);
}
friend std::ostream& operator<< (std::ostream& out, const add_tag_layer& item)
{
int min_length = 0;
item.print(out, 0, min_length);
return out;
}
void print (std::ostream& out, unsigned long idx, int& min_length) const
{
out << "layer<" << idx << ">\t" << impl::tensor_to_str(private_get_output(), min_length) << "tag" << ID << "\n";
subnet().print(out, idx+1, min_length);
}
private:
template <typename T, typename U, typename E>
friend class add_layer;
template <typename T, bool is_first, typename E>
friend class dimpl::subnet_wrapper;
template <unsigned long T, typename U, typename E>
friend class add_tag_layer;
template <template<typename> class T, typename U>
friend class add_skip_layer;
template <size_t N, template<typename> class L, typename S>
friend class repeat;
// You wouldn't put a tag on a layer if you didn't want to access its forward
// outputs. So this is always true.
bool this_layer_requires_forward_output(
) { return true; }
void disable_output_and_gradient_getters (
)
{
// This should never happen because only inplace layers call
// disable_output_and_gradient_getters(), however, putting a tag layer right
// before an inplace layer basically means you don't want the following layer
// to operate in place. So the inplace layer should turn itself into an
// out-of-place layer and not call disable_output_and_gradient_getters().
DLIB_CASSERT(false,"This should never happen");
}
tensor& private_get_output() const
{ return subnetwork.private_get_output(); }
tensor& private_get_gradient_input()
{ return subnetwork.private_get_gradient_input(); }
subnet_type subnetwork;
// This member doesn't logically contribute to the state of the object since it is
// always empty. It's just here so we can have the get_parameter_gradient() methods
// which have to return something. So they return this empty tensor.
resizable_tensor params_grad;
};
// ----------------------------------------------------------------------------------------
template <typename ...T>
struct decorator_repeat_group
{
decorator_repeat_group(
T&& ...args
) : data(std::forward<T>(args)...) {}
std::tuple<T...> data;
};
template <typename ...T>
decorator_repeat_group<T...> repeat_group (
T&& ...args
)
{
return decorator_repeat_group<T...>(std::forward<T>(args)...);
}
template <
size_t num,
template<typename> class REPEATED_LAYER,
typename SUBNET
>
class repeat
{
static_assert(num > 0, "You can't have a layer repeated 0 times.");
public:
typedef SUBNET subnet_type;
typedef typename SUBNET::input_type input_type;
typedef int layer_details_type; // not really used anywhere, but required by subnet_wrapper.
const static size_t comp_layers_in_each_group = (REPEATED_LAYER<SUBNET>::num_computational_layers-SUBNET::num_computational_layers);
const static size_t comp_layers_in_repeated_group = comp_layers_in_each_group*num;
const static size_t num_computational_layers = comp_layers_in_repeated_group + SUBNET::num_computational_layers;
const static size_t layers_in_each_group = (REPEATED_LAYER<SUBNET>::num_layers-SUBNET::num_layers);
const static size_t layers_in_repeated_group = layers_in_each_group*num;
const static size_t num_layers = subnet_type::num_layers + layers_in_repeated_group;
typedef REPEATED_LAYER<impl::repeat_input_layer> repeated_layer_type;
repeat(
) :
details(num)
{
}
size_t num_repetitions (
) const { return num; }
const repeated_layer_type& get_repeated_layer (
size_t i
) const
{
DLIB_CASSERT(i < num_repetitions());
return details[i];
}
repeated_layer_type& get_repeated_layer (
size_t i
)
{
DLIB_CASSERT(i < num_repetitions());
return details[i];
}
repeat(const repeat&) = default;
repeat(repeat&&) = default;
repeat& operator=(repeat&&) = default;
repeat& operator=(const repeat&) = default;
template <template<typename> class T, typename U>
repeat(
const repeat<num,T,U>& item
) :
subnetwork(item.subnetwork)
{
for (auto&& d : item.details)
details.emplace_back(d);
}
template <typename T, typename ...U>
repeat(
T arg1,
U ...args2
):
details(num, std::move(arg1)),
subnetwork(std::move(args2)...)
{
}
template <typename ...T, typename ...U>
repeat(
decorator_repeat_group<T...>&& arg1,
U ...args2
):
details(num, arg1.data),
subnetwork(std::move(args2)...)
{
}
template <typename T, typename ...U>
repeat(
std::tuple<>,
T arg1,
U ...args2
):
details(num, std::move(arg1)),
subnetwork(std::move(args2)...)
{
}
template <typename forward_iterator>
void to_tensor (
forward_iterator ibegin,
forward_iterator iend,
resizable_tensor& data
) const
{
subnetwork.to_tensor(ibegin,iend,data);
// call to_tensor on the networks in details just to populate the
// _sample_expansion_factor values in those networks. Other than that this
// call is a noop.
for (auto& d : details)
d.to_tensor(ibegin, iend, data);
}
template <typename forward_iterator>
const tensor& operator() (
forward_iterator ibegin,
forward_iterator iend
)
{
to_tensor(ibegin,iend,temp_tensor);
return forward(temp_tensor);
}
const tensor& operator() (const input_type& x)
{
return (*this)(&x, &x+1);
}
const tensor& forward(const tensor& x)
{
subnetwork.forward(x);
details[details.size()-1].forward(subnetwork.get_output());
for (long i = details.size()-2; i >= 0; --i)
details[i].forward(details[i+1].get_output());
return private_get_output();
}
private:
tensor& private_get_output() const
{
return details[0].private_get_output();
}
tensor& private_get_gradient_input()
{
return details[0].private_get_gradient_input();
}
public:
const tensor& get_output() const
{
return details[0].get_output();
}
tensor& get_gradient_input()
{
return details[0].get_gradient_input();
}
const tensor& get_final_data_gradient(
) const { return subnetwork.get_final_data_gradient(); }
const tensor& get_parameter_gradient(
) const { return details[0].get_parameter_gradient(); }
tensor& get_parameter_gradient (
) { return details[0].get_parameter_gradient(); }
void back_propagate_error(const tensor& x)
{
back_propagate_error(x, private_get_gradient_input());
}
void back_propagate_error(const tensor& x, const tensor& gradient_input)
{
if (details.size() > 1)
{
details[0].back_propagate_error(details[1].get_output(), gradient_input);
for (size_t i = 1; i < details.size(); ++i)
{
if (i+1 < details.size())
details[i].back_propagate_error(details[i+1].get_output(), details[i-1].get_final_data_gradient());
else
details[i].back_propagate_error(subnetwork.get_output(), details[i-1].get_final_data_gradient());
}
}
else
{
details[0].back_propagate_error(subnetwork.get_output(), gradient_input);
}
subnetwork.back_propagate_error(x, details.back().get_final_data_gradient());
}
template <typename solver_type>
void update_parameters(sstack<solver_type> solvers, double learning_rate)
{
for (size_t i = 0; i < details.size(); ++i)
details[i].update_parameters(solvers.pop(comp_layers_in_each_group*i),learning_rate);
subnetwork.update_parameters(solvers.pop(comp_layers_in_each_group*details.size()),learning_rate);
}
template <typename solver_type>
void update_parameters(std::vector<solver_type>& solvers, double learning_rate)
{
update_parameters(make_sstack(solvers), learning_rate);
}
const subnet_type& subnet() const { return subnetwork; }
subnet_type& subnet() { return subnetwork; }
unsigned int sample_expansion_factor() const { return subnet().sample_expansion_factor(); }
void clean()
{
temp_tensor.clear();
subnetwork.clean();
for (auto&& d : details)
d.clean();
}
friend void serialize(const repeat& item, std::ostream& out)
{
int version = 1;
serialize(version, out);
serialize(item.details, out);
serialize(item.subnetwork, out);
}
friend void deserialize(repeat& item, std::istream& in)
{
int version = 0;
deserialize(version, in);
if (version != 1)
throw serialization_error("Unexpected version found while deserializing dlib::repeat.");
deserialize(item.details, in);
deserialize(item.subnetwork, in);
}
friend std::ostream& operator<< (std::ostream& out, const repeat& item)
{
int min_length = 0;
item.print(out, 0, min_length);
return out;
}
void print (std::ostream& out, unsigned long idx, int& min_length) const
{
for (size_t i = 0; i < num_repetitions(); ++i)
{
get_repeated_layer(i).print(out, idx, min_length);
idx += layers_in_each_group;
}
subnet().print(out, idx, min_length);
}
private:
template <typename T, typename U, typename E>
friend class add_layer;
template <typename T, bool is_first, typename E>
friend class dimpl::subnet_wrapper;
template <unsigned long T, typename U, typename E>
friend class add_tag_layer;
template <template<typename> class T, typename U>
friend class add_skip_layer;
template <size_t N, template<typename> class L, typename S>
friend class repeat;
bool this_layer_requires_forward_output(
)
{
return details[0].this_layer_requires_forward_output();
}
void disable_output_and_gradient_getters (
)
{
details[0].disable_output_and_gradient_getters();
}
std::vector<repeated_layer_type> details;
subnet_type subnetwork;
// temp_tensor doesn't logically contribute to the state of this class.
// It is here only to void needing to reallocate it over and over.
resizable_tensor temp_tensor;
};
template <
size_t num,
template<typename> class REPEATED_LAYER,
typename SUBNET
>
struct is_nonloss_layer_type<repeat<num,REPEATED_LAYER,SUBNET>> : std::true_type {};
// ----------------------------------------------------------------------------------------
// This version of add_tag_layer handles the special case where the subnetwork being given
// is just an input layer object.
template <unsigned long ID, typename INPUT_LAYER, typename enabled>
class add_tag_layer
{
public:
typedef INPUT_LAYER subnet_type;
typedef typename subnet_type::input_type input_type;
typedef int layer_details_type; // not really used anywhere, but required by subnet_wrapper.
const static size_t num_computational_layers = 0;
const static size_t num_layers = 2;
const static unsigned long id = ID;
add_tag_layer():cached_output_ptr(nullptr),gradient_input_is_stale(true),_sample_expansion_factor(0) {}
add_tag_layer(const add_tag_layer&) = default;
add_tag_layer& operator=(const add_tag_layer&) = default;
add_tag_layer(add_tag_layer&& item) : add_tag_layer() { swap(item); }
add_tag_layer& operator=(add_tag_layer&& item) { swap(item); return *this; }
template <typename T, typename E>
add_tag_layer(
const add_tag_layer<ID,T,E>& item
) : input_layer(item.subnet()),
cached_output(item.cached_output),
cached_output_ptr(nullptr),
grad_final(item.grad_final),
gradient_input_is_stale(item.gradient_input_is_stale),
_sample_expansion_factor(0)
{}
template <typename ...T>
add_tag_layer(
T ...args
) :
input_layer(std::move(args)...),
cached_output_ptr(nullptr),
gradient_input_is_stale(true),
_sample_expansion_factor(0)
{
}
add_tag_layer (
std::tuple<>
) :
cached_output_ptr(nullptr),
gradient_input_is_stale(true),
_sample_expansion_factor(0)
{}
template <typename forward_iterator>
void to_tensor (
forward_iterator ibegin,
forward_iterator iend,
resizable_tensor& data
) const
{
input_layer.to_tensor(ibegin,iend,data);
// make sure the input layer's to_tensor() function is implemented properly.
DLIB_CASSERT(data.num_samples() >= std::distance(ibegin,iend),
"The input layer can't produce fewer output tensors than there are inputs.");
DLIB_CASSERT(data.num_samples()%std::distance(ibegin,iend) == 0,
"The number of tensors produced by the input layer must be an integer multiple of the number of input objects.");
_sample_expansion_factor = data.num_samples()/std::distance(ibegin,iend);
data.async_copy_to_device();
}
unsigned int sample_expansion_factor() const { return _sample_expansion_factor; }
template <typename forward_iterator>
const tensor& operator() (
forward_iterator ibegin,
forward_iterator iend
)
{
input_layer.to_tensor(ibegin,iend,cached_output);
cached_output_ptr = nullptr;
return get_output();
}
const tensor& operator() (const input_type& x)
{
return (*this)(&x, &x+1);
}
const tensor& forward(const tensor& x)
{
// If this tag is the first layer in one of the sub networks inside a repeat
// layer then we don't want it to be creating copies of x. This is because, we
// can just hold a pointer to x since the way repeat is constructed guarantees
// that x will have a lifetime larger than this pointer.
if (is_same_type<INPUT_LAYER, impl::repeat_input_layer>::value)
cached_output_ptr = const_cast<tensor*>(&x);
else
cached_output = x;
gradient_input_is_stale = true;
return get_output();
}
const tensor& get_output() const
{
if (cached_output_ptr)
return *cached_output_ptr;
else
return cached_output;
}
const tensor& get_final_data_gradient(
) const { return grad_final; }
tensor& get_gradient_input()
{
if (!have_same_dimensions(get_output(), grad_final) ||
gradient_input_is_stale)
{
grad_final.copy_size(get_output());
grad_final = 0;
gradient_input_is_stale = false;
}
return grad_final;
}
void back_propagate_error(const tensor& /*x*/)
{
// nothing to do
}
void back_propagate_error(const tensor& /*x*/, const tensor& /*gradient_input*/)
{
// nothing to do
}
template <typename solver_type>
void update_parameters(sstack<solver_type> /*solvers*/, double /*learning_rate*/)
{
// nothing to do
}
template <typename solver_type>
void update_parameters(std::vector<solver_type>& solvers, double learning_rate)
{
update_parameters(make_sstack(solvers), learning_rate);
}
const subnet_type& subnet() const { return input_layer; }
subnet_type& subnet() { return input_layer; }
void clean()
{
grad_final.clear();
cached_output.clear();
cached_output_ptr = 0;
}
friend void serialize(const add_tag_layer& item, std::ostream& out)
{
int version = 2;
serialize(version, out);
serialize(item.input_layer, out);
serialize(item.cached_output, out);
serialize(item.grad_final, out);
serialize(item.gradient_input_is_stale, out);
serialize(item._sample_expansion_factor, out);
}
friend void deserialize(add_tag_layer& item, std::istream& in)
{
int version = 0;
deserialize(version, in);
if (!(1 <= version && version <= 2))
throw serialization_error("Unexpected version found while deserializing dlib::add_tag_layer.");
deserialize(item.input_layer, in);
deserialize(item.cached_output, in);
deserialize(item.grad_final, in);
deserialize(item.gradient_input_is_stale, in);
item.cached_output_ptr = nullptr;
if (version >= 2)
deserialize(item._sample_expansion_factor, in);
else
item._sample_expansion_factor = 1; // all layer types set this to 1 in older dlib versions, so that's what we put here.
}
friend std::ostream& operator<< (std::ostream& out, const add_tag_layer& item)
{
int min_length = 0;
item.print(out, 0, min_length);
return out;
}
void print (std::ostream& out, unsigned long idx, int& min_length) const
{
out << "layer<"<<idx << ">\t"<<impl::tensor_to_str(private_get_output(), min_length)<< "tag" << ID << "\n";
// Don't print the repeat_input_layer since it doesn't exist from the user's
// point of view. It's just an artifact of how repeat<> works.
if (!std::is_same<subnet_type, impl::repeat_input_layer>::value)
out << "layer<"<< idx+1 << ">\t" << subnet() << "\n";
}
private:
template <typename T, typename U, typename E>
friend class add_layer;
template <typename T, bool is_first, typename E>
friend class dimpl::subnet_wrapper;
template <unsigned long T, typename U, typename E>
friend class add_tag_layer;
template <template<typename> class T, typename U>
friend class add_skip_layer;
template <size_t N, template<typename> class L, typename S>
friend class repeat;
// You woudln't put a tag on a layer if you didn't want to access its forward
// outputs. So this is always true.
bool this_layer_requires_forward_output(
) { return true; }
void disable_output_and_gradient_getters (
)
{
// This should never happen because only inplace layers call
// disable_output_and_gradient_getters(), however, putting a tag layer right
// before an inplace layer basically means you don't want the following layer
// to operate in place. So the inplace layer should turn itself into an
// out-of-place layer and not call disable_output_and_gradient_getters().
DLIB_CASSERT(false,"This should never happen");
}
tensor& private_get_output() const
{ return const_cast<tensor&>(get_output()); }
tensor& private_get_gradient_input()
{ return get_gradient_input(); }
void swap(add_tag_layer& item)
{
std::swap(input_layer, item.input_layer);
std::swap(cached_output, item.cached_output);
std::swap(cached_output_ptr, item.cached_output_ptr);
std::swap(grad_final, item.grad_final);
std::swap(gradient_input_is_stale, item.gradient_input_is_stale);
std::swap(_sample_expansion_factor, item._sample_expansion_factor);
}
subnet_type input_layer;
resizable_tensor cached_output;
tensor* cached_output_ptr;
resizable_tensor grad_final;
bool gradient_input_is_stale;
mutable unsigned int _sample_expansion_factor;
};
template <unsigned long ID, typename U, typename E>
struct is_nonloss_layer_type<add_tag_layer<ID,U,E>> : std::true_type {};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <typename LOSS_DETAILS, typename SUBNET>
class add_loss_layer;
class no_label_type
{
private:
// We don't want anyone making these no_label_type objects. They are here only to
// allow add_loss_layer::training_label_type and dnn_trainer::training_label_type
// to exist which avoids needing to overload add_loss_layer and dnn_trainer for
// supervised an unsupervised losses. It also can be a type to use in template
// metaprogramming to indicate "no label". So here we make the constructor private
// with the exception that add_loss_layer objects can make it (again, just to
// simplify add_loss_layer's implementation).
no_label_type(){};
template <typename LOSS_DETAILS, typename SUBNET> friend class add_loss_layer;
template < typename net_type, typename solver_type > friend class dnn_trainer;
};
// ----------------------------------------------------------------------------------------
template <typename LOSS_DETAILS, typename SUBNET>
class add_loss_layer
{
template <typename T, typename enabled=void>
struct get_loss_layer_training_label_type
{
typedef no_label_type type;
};
template <typename T>
struct get_loss_layer_training_label_type<T,typename std::enable_if<sizeof(typename T::training_label_type)!=0>::type>
{
typedef typename T::training_label_type type;
};
template <typename T, typename enabled=void>
struct get_loss_layer_output_label_type
{
typedef no_label_type type;
};
template <typename T>
struct get_loss_layer_output_label_type<T,typename std::enable_if<sizeof(typename T::output_label_type)!=0>::type>
{
typedef typename T::output_label_type type;
};
public:
typedef LOSS_DETAILS loss_details_type;
typedef SUBNET subnet_type;
typedef typename subnet_type::input_type input_type;
const static size_t num_layers = subnet_type::num_layers + 1;
// Note that the loss layer doesn't count as an additional computational layer.
const static size_t num_computational_layers = subnet_type::num_computational_layers;
typedef typename get_loss_layer_training_label_type<LOSS_DETAILS>::type training_label_type;
typedef typename get_loss_layer_output_label_type<LOSS_DETAILS>::type output_label_type;
static_assert(is_nonloss_layer_type<SUBNET>::value,
"SUBNET must be of type add_layer, add_skip_layer, or add_tag_layer.");
add_loss_layer() {};
add_loss_layer(const add_loss_layer&) = default;
add_loss_layer& operator=(const add_loss_layer&) = default;
add_loss_layer(add_loss_layer&& item) : add_loss_layer() { swap(item); }
add_loss_layer& operator=(add_loss_layer&& item) { swap(item); return *this; }
template <typename T, typename U>
add_loss_layer(
const add_loss_layer<T,U>& item
) :
loss(item.loss_details()),
subnetwork(item.subnet())
{}
template <typename ...T>
add_loss_layer(
const LOSS_DETAILS& layer_det,
T&& ...args
) :
loss(layer_det),
subnetwork(std::forward<T>(args)...)
{
}
template <typename ...T>
add_loss_layer(
LOSS_DETAILS&& layer_det,
T&& ...args
) :
loss(std::move(layer_det)),
subnetwork(std::forward<T>(args)...)
{
}
template <typename T, typename ...U>
struct disable_forwarding_constr
{
const static bool value = std::is_constructible<LOSS_DETAILS,T>::value;
};
template <typename ...T>
struct disable_forwarding_constr<add_loss_layer<T...>>
{
const static bool value = true;
};
template <
typename ...T,
typename = typename std::enable_if<!disable_forwarding_constr<typename std::remove_reference<T>::type...>::value>::type
>
add_loss_layer(
T&& ...args
) :
subnetwork(std::forward<T>(args)...)
{
}
template <typename forward_iterator>
void to_tensor (
forward_iterator ibegin,
forward_iterator iend,
resizable_tensor& data
) const
{
subnetwork.to_tensor(ibegin,iend,data);
}
unsigned int sample_expansion_factor() const { return subnet().sample_expansion_factor(); }
template <typename output_iterator>
void operator() (
const tensor& x,
output_iterator obegin
)
{
subnetwork.forward(x);
const dimpl::subnet_wrapper<subnet_type> wsub(subnetwork);
loss.to_label(x, wsub, obegin);
}
template <typename forward_iterator, typename output_iterator>
void operator() (
forward_iterator ibegin,
forward_iterator iend,
output_iterator obegin
)
{
to_tensor(ibegin,iend,temp_tensor);
(*this)(temp_tensor, obegin);
}
const output_label_type& operator() (const input_type& x)
{
(*this)(&x, &x+1, &temp_label);
return temp_label;
}
template <typename ...T>
const output_label_type& process (const input_type& x, T&& ...args)
{
to_tensor(&x,&x+1,temp_tensor);
subnetwork.forward(temp_tensor);
const dimpl::subnet_wrapper<subnet_type> wsub(subnetwork);
loss.to_label(temp_tensor, wsub, &temp_label, std::forward<T>(args)...);
return temp_label;
}
template <typename iterable_type, typename ...T>
std::vector<output_label_type> process_batch (const iterable_type& data, size_t batch_size, T&& ...args)
{
std::vector<output_label_type> results(std::distance(data.begin(), data.end()));
auto o = results.begin();
auto i = data.begin();
auto num_remaining = results.size();
while(num_remaining != 0)
{
auto inc = std::min(batch_size, num_remaining);
to_tensor(i,i+inc,temp_tensor);
subnetwork.forward(temp_tensor);
const dimpl::subnet_wrapper<subnet_type> wsub(subnetwork);
loss.to_label(temp_tensor, wsub, o, std::forward<T>(args)...);
i += inc;
o += inc;
num_remaining -= inc;
}
return results;
}
void back_propagate_error(const tensor& x)
{
subnet().back_propagate_error(x);
}
void back_propagate_error(const tensor& x, const tensor& gradient_input)
{
subnet().back_propagate_error(x, gradient_input);
}
const tensor& get_final_data_gradient(
) const
{
return subnet().get_final_data_gradient();
}
const tensor& forward(const tensor& x)
{
return subnet().forward(x);
}
template <typename iterable_type>
std::vector<output_label_type> operator() (
const iterable_type& data,
size_t batch_size = 128
)
{
std::vector<output_label_type> results(std::distance(data.begin(), data.end()));
auto o = results.begin();
auto i = data.begin();
auto num_remaining = results.size();
while(num_remaining != 0)
{
auto inc = std::min(batch_size, num_remaining);
(*this)(i, i+inc, o);
i += inc;
o += inc;
num_remaining -= inc;
}
return results;
}
template <typename label_iterator>
double compute_loss (
const tensor& x,
label_iterator lbegin
)
{
subnetwork.forward(x);
dimpl::subnet_wrapper<subnet_type> wsub(subnetwork);
return loss.compute_loss_value_and_gradient(x, lbegin, wsub);
}
template <typename forward_iterator, typename label_iterator>
double compute_loss (
forward_iterator ibegin,
forward_iterator iend,
label_iterator lbegin
)
{
to_tensor(ibegin,iend,temp_tensor);
return compute_loss(temp_tensor, lbegin);
}
double compute_loss (
const tensor& x
)
{
subnetwork.forward(x);
dimpl::subnet_wrapper<subnet_type> wsub(subnetwork);
return loss.compute_loss_value_and_gradient(x, wsub);
}
template <typename forward_iterator>
double compute_loss (
forward_iterator ibegin,
forward_iterator iend
)
{
to_tensor(ibegin,iend,temp_tensor);
return compute_loss(temp_tensor);
}
template <typename label_iterator>
double compute_parameter_gradients (
const tensor& x,
label_iterator lbegin
)
{
subnetwork.forward(x);
dimpl::subnet_wrapper<subnet_type> wsub(subnetwork);
double l = loss.compute_loss_value_and_gradient(x, lbegin, wsub);
subnetwork.back_propagate_error(x);
return l;
}
template <typename forward_iterator, typename label_iterator>
double compute_parameter_gradients (
forward_iterator ibegin,
forward_iterator iend,
label_iterator lbegin
)
{
to_tensor(ibegin,iend,temp_tensor);
return compute_parameter_gradients(temp_tensor, lbegin);
}
double compute_parameter_gradients (
const tensor& x
)
{
subnetwork.forward(x);
dimpl::subnet_wrapper<subnet_type> wsub(subnetwork);
double l = loss.compute_loss_value_and_gradient(x, wsub);
subnetwork.back_propagate_error(x);
return l;
}
template <typename forward_iterator>
double compute_parameter_gradients (
forward_iterator ibegin,
forward_iterator iend
)
{
to_tensor(ibegin,iend,temp_tensor);
return compute_parameter_gradients(temp_tensor);
}
template <typename solver_type>
void update_parameters (
sstack<solver_type> solvers,
double learning_rate
)
{
subnetwork.update_parameters(solvers, learning_rate);
}
template <typename solver_type>
void update_parameters(std::vector<solver_type>& solvers, double learning_rate)
{
update_parameters(make_sstack(solvers), learning_rate);
}
const subnet_type& subnet() const { return subnetwork; }
subnet_type& subnet() { return subnetwork; }
const loss_details_type& loss_details() const { return loss; }
loss_details_type& loss_details() { return loss; }
void clean (
)
{
temp_tensor.clear();
subnetwork.clean();
}
template <typename T, typename U>
friend void serialize(const add_loss_layer<T,U>& item, std::ostream& out);
template <typename T, typename U>
friend void deserialize(add_loss_layer<T,U>& item, std::istream& in);
friend std::ostream& operator<< (std::ostream& out, const add_loss_layer& item)
{
int min_length = 0;
item.print(out, 0, min_length);
return out;
}
void print (std::ostream& out, unsigned long idx, int& min_length) const
{
out << "layer<" << idx << ">\t" << loss_details() << "\n";
subnet().print(out, idx+1, min_length);
}
private:
void swap(add_loss_layer& item)
{
std::swap(loss, item.loss);
std::swap(subnetwork, item.subnetwork);
}
loss_details_type loss;
subnet_type subnetwork;
// These two objects don't logically contribute to the state of this object. They
// are here to prevent them from being reallocated over and over.
output_label_type temp_label;
resizable_tensor temp_tensor;
};
template <typename LOSS_DETAILS, typename SUBNET>
void serialize(const add_loss_layer<LOSS_DETAILS,SUBNET>& item, std::ostream& out)
{
int version = 1;
serialize(version, out);
serialize(item.loss, out);
serialize(item.subnetwork, out);
}
template <typename LOSS_DETAILS, typename SUBNET>
void deserialize(add_loss_layer<LOSS_DETAILS,SUBNET>& item, std::istream& in)
{
int version = 0;
deserialize(version, in);
if (version != 1)
throw serialization_error("Unexpected version found while deserializing dlib::add_loss_layer.");
deserialize(item.loss, in);
deserialize(item.subnetwork, in);
}
template <typename T, typename U>
struct is_loss_layer_type<add_loss_layer<T,U>> : std::true_type {};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
namespace impl
{
template <unsigned int i, typename T, typename enabled = void>
struct layer_helper
{
static_assert(i < T::num_layers, "Call to layer() attempted to access non-existing layer in neural network.");
static T& makeT();
// If you get error here mentioning lack of member "subnet" in "dlib::input<...>",
// then likely your "dlib::layer<...>" invocation wasn't able to find requested layer.
// This could happen for instance when trying to use skip layer for non-existing tag.
using next_type = typename std::remove_reference<decltype(makeT().subnet())>::type;
using type = typename layer_helper<i-1,next_type>::type;
static type& layer(T& n)
{
return layer_helper<i-1,next_type>::layer(n.subnet());
}
};
template <
unsigned int i,
size_t N, template<typename> class L, typename S
>
struct layer_helper<i,repeat<N,L,S>, typename std::enable_if<(i!=0&&i>=repeat<N,L,S>::layers_in_repeated_group)>::type>
{
const static size_t layers_in_repeated_group = repeat<N,L,S>::layers_in_repeated_group;
static repeat<N,L,S>& makeT();
using next_type = typename std::remove_reference<decltype(makeT().subnet())>::type;
using type = typename layer_helper<i-layers_in_repeated_group,next_type>::type;
static type& layer(repeat<N,L,S>& n)
{
return layer_helper<i-layers_in_repeated_group,next_type>::layer(n.subnet());
}
};
template <
unsigned int i,
size_t N, template<typename> class L, typename S
>
struct layer_helper<i,repeat<N,L,S>, typename std::enable_if<(i!=0&&i<repeat<N,L,S>::layers_in_repeated_group)>::type>
{
const static size_t layers_in_each_group = repeat<N,L,S>::layers_in_each_group;
typedef typename repeat<N,L,S>::repeated_layer_type repeated_layer_type;
using next_type = repeated_layer_type;
using type = typename layer_helper<i%layers_in_each_group,next_type>::type;
static type& layer(repeat<N,L,S>& n)
{
return layer_helper<i%layers_in_each_group,next_type>::layer(n.get_repeated_layer(i/layers_in_each_group));
}
};
template <
size_t N, template<typename> class L, typename S
>
struct layer_helper<0,repeat<N,L,S>, void>
{
typedef typename repeat<N,L,S>::repeated_layer_type repeated_layer_type;
using type = repeated_layer_type;
static type& layer(repeat<N,L,S>& n)
{
return n.get_repeated_layer(0);
}
};
template <
unsigned int i,
size_t N, template<typename> class L, typename S
>
struct layer_helper<i,const repeat<N,L,S>, typename std::enable_if<(i!=0&&i>=repeat<N,L,S>::layers_in_repeated_group)>::type>
{
const static size_t layers_in_repeated_group = repeat<N,L,S>::layers_in_repeated_group;
static const repeat<N,L,S>& makeT();
using next_type = const typename std::remove_reference<decltype(makeT().subnet())>::type;
using type = const typename layer_helper<i-layers_in_repeated_group,next_type>::type;
static type& layer(const repeat<N,L,S>& n)
{
return layer_helper<i-layers_in_repeated_group,next_type>::layer(n.subnet());
}
};
template <
unsigned int i,
size_t N, template<typename> class L, typename S
>
struct layer_helper<i,const repeat<N,L,S>, typename std::enable_if<(i!=0&&i<repeat<N,L,S>::layers_in_repeated_group)>::type>
{
const static size_t layers_in_each_group = repeat<N,L,S>::layers_in_each_group;
typedef typename repeat<N,L,S>::repeated_layer_type repeated_layer_type;
using next_type = const repeated_layer_type;
using type = const typename layer_helper<i%layers_in_each_group,next_type>::type;
static type& layer(const repeat<N,L,S>& n)
{
return layer_helper<i%layers_in_each_group,next_type>::layer(n.get_repeated_layer(i/layers_in_each_group));
}
};
template <
size_t N, template<typename> class L, typename S
>
struct layer_helper<0,const repeat<N,L,S>, void>
{
typedef typename repeat<N,L,S>::repeated_layer_type repeated_layer_type;
using type = const repeated_layer_type;
static type& layer(const repeat<N,L,S>& n)
{
return n.get_repeated_layer(0);
}
};
template <typename T>
struct layer_helper<0,T,void>
{
using type = T;
static type& layer(T& n)
{
return n;
}
};
template <template<typename> class Match, typename T, unsigned int i, typename enabled = void>
struct layer_helper_match
{
static T& makeT();
using next_type = typename std::remove_reference<decltype(makeT().subnet())>::type;
using type = typename layer_helper_match<Match,next_type,i>::type;
static type& layer(T& n)
{
return layer_helper_match<Match,next_type,i>::layer(n.subnet());
}
};
// This overload catches add_layer and add_loss_layer templates.
template <template<typename> class Match, typename T, unsigned int i>
struct layer_helper_match<Match,T,i,
typename std::enable_if<std::is_same<const T,const Match<typename T::subnet_type>>::value>::type>
{
using type = typename layer_helper<i,T>::type;
static type& layer(T& n)
{
return layer_helper<i,T>::layer(n);
}
};
// This overload catches input templates.
template <template<typename> class Match, typename T, unsigned int i>
struct layer_helper_match<Match,T,i,
typename std::enable_if<std::is_same<const T,const Match<typename T::input_type>>::value>::type>
{
using type = typename layer_helper<i,T>::type;
static type& layer(T& n)
{
return layer_helper<i,T>::layer(n);
}
};
// This overload catches subnet_wrapper templates.
template <template<typename> class Match, typename T, unsigned int i>
struct layer_helper_match<Match,T,i,
typename std::enable_if<std::is_same<const typename T::wrapped_type,
const Match<typename T::wrapped_type::subnet_type>>::value>::type>
{
using type = typename layer_helper<i,T>::type;
static type& layer(T& n)
{
return layer_helper<i,T>::layer(n);
}
};
}
template <unsigned int i, typename T>
typename impl::layer_helper<i,T>::type& layer (T& n)
{
return impl::layer_helper<i,T>::layer(n);
}
template <template<typename> class Match, typename T>
typename impl::layer_helper_match<Match,T,0>::type& layer (T& n)
{
return impl::layer_helper_match<Match,T,0>::layer(n);
}
template <template<typename> class Match, unsigned int i, typename T>
typename impl::layer_helper_match<Match,T,i>::type& layer (T& n)
{
return impl::layer_helper_match<Match,T,i>::layer(n);
}
// ----------------------------------------------------------------------------------------
namespace dimpl
{
template <typename T>
T& get_input_details (
T& net
)
{
return net;
}
template <typename T, bool is_first, typename enabled>
auto get_input_details (
dimpl::subnet_wrapper<T,is_first,enabled>& net
) -> decltype(net.layer_details())&
{
return net.layer_details();
}
template <typename T, bool is_first, typename enabled>
auto get_input_details (
const dimpl::subnet_wrapper<T,is_first,enabled>& net
) -> decltype(net.layer_details())&
{
return net.layer_details();
}
}
template <typename net_type>
auto input_layer (
net_type& net
) -> decltype(dimpl::get_input_details(layer<net_type::num_layers-1>(net)))&
{
// Calling input_layer() on a subnet_wrapper is a little funny since the behavior of
// .subnet() returns another subnet_wrapper rather than an input details object as it
// does in add_layer.
return dimpl::get_input_details(layer<net_type::num_layers-1>(net));
}
// ----------------------------------------------------------------------------------------
template <template<typename> class TAG_TYPE, typename SUBNET>
class add_skip_layer
{
public:
typedef SUBNET subnet_type;
typedef typename subnet_type::input_type input_type;
typedef int layer_details_type; // not really used anywhere, but required by subnet_wrapper.
const static size_t num_layers = subnet_type::num_layers + 1;
const static size_t num_computational_layers = subnet_type::num_computational_layers;
const static unsigned long id = tag_id<TAG_TYPE>::id;
add_skip_layer() {};
add_skip_layer(const add_skip_layer&) = default;
add_skip_layer(add_skip_layer&&) = default;
add_skip_layer& operator=(add_skip_layer&&) = default;
add_skip_layer& operator=(const add_skip_layer&) = default;
template <typename T>
add_skip_layer(
const add_skip_layer<TAG_TYPE,T>& item
) : subnetwork(item.subnet())
{}
template <typename ...T>
add_skip_layer(
T ...args
) :
subnetwork(std::move(args)...)
{
}
template <typename forward_iterator>
void to_tensor (
forward_iterator ibegin,
forward_iterator iend,
resizable_tensor& data
) const
{
subnetwork.to_tensor(ibegin,iend,data);
}
template <typename forward_iterator>
const tensor& operator() (
forward_iterator ibegin,
forward_iterator iend
)
{
subnetwork(ibegin,iend);
return layer<TAG_TYPE>(subnetwork).get_output();
}
const tensor& operator() (const input_type& x)
{
subnetwork(x);
return layer<TAG_TYPE>(subnetwork).get_output();
}
const tensor& forward(const tensor& x)
{
subnetwork.forward(x);
return layer<TAG_TYPE>(subnetwork).get_output();
}
const tensor& get_output() const
{
return layer<TAG_TYPE>(subnetwork).get_output();
}
tensor& get_gradient_input()
{
return layer<TAG_TYPE>(subnetwork).get_gradient_input();
}
const tensor& get_final_data_gradient(
) const
{
return subnetwork.get_final_data_gradient();
}
void back_propagate_error(const tensor& x)
{
subnetwork.back_propagate_error(x);
}
template <typename solver_type>
void update_parameters(sstack<solver_type> solvers, double learning_rate)
{
subnetwork.update_parameters(solvers, learning_rate);
}
template <typename solver_type>
void update_parameters(std::vector<solver_type>& solvers, double learning_rate)
{
update_parameters(make_sstack(solvers), learning_rate);
}
const tensor& get_parameter_gradient(
) const { return params_grad; }
tensor& get_parameter_gradient (
) { return params_grad; }
const subnet_type& subnet() const
{
return subnetwork;
}
subnet_type& subnet()
{
return subnetwork;
}
unsigned int sample_expansion_factor() const { return subnet().sample_expansion_factor(); }
void clean()
{
subnetwork.clean();
}
friend void serialize(const add_skip_layer& item, std::ostream& out)
{
int version = 1;
serialize(version, out);
serialize(item.subnetwork, out);
}
friend void deserialize(add_skip_layer& item, std::istream& in)
{
int version = 0;
deserialize(version, in);
if (version != 1)
throw serialization_error("Unexpected version found while deserializing dlib::add_skip_layer.");
deserialize(item.subnetwork, in);
}
friend std::ostream& operator<< (std::ostream& out, const add_skip_layer& item)
{
int min_length = 0;
item.print(out, 0, min_length);
return out;
}
void print (std::ostream& out, unsigned long idx, int& min_length) const
{
out << "layer<" << idx << ">\t"<<impl::tensor_to_str(private_get_output(), min_length) <<"skip"<<id<<"\n";
subnet().print(out, idx+1, min_length);
}
private:
template <typename T, typename U, typename E>
friend class add_layer;
template <typename T, bool is_first, typename E>
friend class dimpl::subnet_wrapper;
template <unsigned long T, typename U, typename E>
friend class add_tag_layer;
template <template<typename> class T, typename U>
friend class add_skip_layer;
template <size_t N, template<typename> class L, typename S>
friend class repeat;
bool this_layer_requires_forward_output(
) { return layer<TAG_TYPE>(subnetwork).this_layer_requires_forward_output(); }
void disable_output_and_gradient_getters (
) { layer<TAG_TYPE>(subnetwork).disable_output_and_gradient_getters(); }
tensor& private_get_output() const
{ return layer<TAG_TYPE>(subnetwork).private_get_output(); }
tensor& private_get_gradient_input()
{ return layer<TAG_TYPE>(subnetwork).private_get_gradient_input(); }
subnet_type subnetwork;
// This member doesn't logically contribute to the state of the object since it is
// always empty. It's just here so we can have the get_parameter_gradient() methods
// which have to return something. So they return this empty tensor.
resizable_tensor params_grad;
};
template <template<typename> class T, typename U>
struct is_nonloss_layer_type<add_skip_layer<T,U>> : std::true_type {};
template <typename SUBNET> using tag1 = add_tag_layer< 1, SUBNET>;
template <typename SUBNET> using tag2 = add_tag_layer< 2, SUBNET>;
template <typename SUBNET> using tag3 = add_tag_layer< 3, SUBNET>;
template <typename SUBNET> using tag4 = add_tag_layer< 4, SUBNET>;
template <typename SUBNET> using tag5 = add_tag_layer< 5, SUBNET>;
template <typename SUBNET> using tag6 = add_tag_layer< 6, SUBNET>;
template <typename SUBNET> using tag7 = add_tag_layer< 7, SUBNET>;
template <typename SUBNET> using tag8 = add_tag_layer< 8, SUBNET>;
template <typename SUBNET> using tag9 = add_tag_layer< 9, SUBNET>;
template <typename SUBNET> using tag10 = add_tag_layer<10, SUBNET>;
template <typename SUBNET> using skip1 = add_skip_layer< tag1, SUBNET>;
template <typename SUBNET> using skip2 = add_skip_layer< tag2, SUBNET>;
template <typename SUBNET> using skip3 = add_skip_layer< tag3, SUBNET>;
template <typename SUBNET> using skip4 = add_skip_layer< tag4, SUBNET>;
template <typename SUBNET> using skip5 = add_skip_layer< tag5, SUBNET>;
template <typename SUBNET> using skip6 = add_skip_layer< tag6, SUBNET>;
template <typename SUBNET> using skip7 = add_skip_layer< tag7, SUBNET>;
template <typename SUBNET> using skip8 = add_skip_layer< tag8, SUBNET>;
template <typename SUBNET> using skip9 = add_skip_layer< tag9, SUBNET>;
template <typename SUBNET> using skip10 = add_skip_layer<tag10, SUBNET>;
// ----------------------------------------------------------------------------------------
namespace timpl
{
inline void fill_with_gassuan_random_numbers (
tensor& t,
dlib::rand& rnd,
double sigma = 1
)
{
float* data = t.host();
for (size_t i = 0; i < t.size(); ++i)
data[i] = rnd.get_random_gaussian()*sigma;
}
class test_layer_subnet
{
public:
test_layer_subnet (
dlib::rand& rnd_
) : rnd(rnd_)
{
// Output and gradient_input have to have the same dimensions in each
// layer.
const long num_samples = rnd.get_random_32bit_number()%4+3;
const long k = rnd.get_random_32bit_number()%4+2;
const long nr = rnd.get_random_32bit_number()%4+2;
const long nc = rnd.get_random_32bit_number()%4+2;
output.set_size(num_samples, k, nr, nc);
gradient_input.set_size(num_samples, k, nr, nc);
// Use a non-zero initial gradient to make sure the layers add to it
// rather than assign and blow away the initial value.
fill_with_gassuan_random_numbers(gradient_input, rnd, 0.01);
fill_with_gassuan_random_numbers(output, rnd);
}
tensor& get_mutable_output() { return output; }
const tensor& get_output() const { return output; }
const tensor& private_get_output() const { return get_output(); }
const test_layer_subnet& subnet() const { init_sub(); return *subnetwork; }
tensor& get_gradient_input() { return gradient_input; }
tensor& private_get_gradient_input() { return get_gradient_input(); }
test_layer_subnet& subnet() { init_sub(); return *subnetwork; }
unsigned long count_outputs() const
{
if (subnetwork)
return subnetwork->count_outputs() + output.size();
else
return output.size();
}
float& get_output_element(unsigned long i)
{
if (i < output.size())
return output.host()[i];
else
return subnet().get_output_element(i-output.size());
}
float get_gradient_input_element(unsigned long i) const
{
if (i < gradient_input.size())
return gradient_input.host()[i];
else
return subnet().get_gradient_input_element(i-gradient_input.size());
}
private:
// We lazily initialize sub-layers as needed when someone tries to call
// subnet()
void init_sub() const
{
if (!subnetwork)
subnetwork.reset(new test_layer_subnet(rnd));
}
dlib::rand& rnd;
mutable std::unique_ptr<test_layer_subnet> subnetwork;
resizable_tensor output;
resizable_tensor gradient_input;
};
}
struct layer_test_results
{
layer_test_results() : was_good(true) {}
explicit layer_test_results(const std::string& l) : log(l),was_good(false) {}
std::string log;
bool was_good;
operator bool() const { return was_good; }
};
inline std::ostream& operator<< (std::ostream& out, const layer_test_results& item)
{
out << item.log;
return out;
}
template <
typename layer_details_type
>
layer_test_results impl_test_layer (
layer_details_type l,
const float base_eps
)
{
using namespace timpl;
// Do some setup
running_stats<double> rs_data, rs_params;
dlib::rand rnd;
std::ostringstream sout;
for (int iter = 0; iter < 10; ++iter)
{
test_layer_subnet subnetwork(rnd);
resizable_tensor output, out2, out3;
// Run setup() and forward() as well to make sure any calls to subnet() have
// happened before we start assuming we know how many data elements there are
// (since we do a lazy layer creation thing based on calls to subnet() inside
// test_layer_subnet).
l.setup(subnetwork);
impl::call_layer_forward(l, subnetwork, output);
resizable_tensor input_grad;
input_grad.copy_size(output);
fill_with_gassuan_random_numbers(input_grad, rnd);
// The f() we are computing gradients of is this thing. It's value at the current
// parameter and data values is:
//sout << "f(data,params): " << dot(output, input_grad) << std::endl;
// We are going to save a copy of the subnetwork.get_gradient_input() data before we do
// backpropagation since the backward() function is supposed to *add* to the
// gradients rather than overwrite them. We will use this saved data to check if
// that is the case.
const unsigned long num_data_inputs = subnetwork.count_outputs();
std::vector<float> initial_gradient_input(num_data_inputs);
for (unsigned long i = 0; i < num_data_inputs; ++i)
initial_gradient_input[i] = subnetwork.get_gradient_input_element(i);
// Now tell the layer to compute all the gradients. In the rest of this function
// we will just be checking that these gradients were computed correctly by
// comparing them to a central differences approximation.
resizable_tensor params_grad;
params_grad.copy_size(l.get_layer_params());
// But first, set the params grad to something crazy so that it's very obvious if
// it doesn't get fully assigned.
params_grad = std::numeric_limits<float>::infinity();
impl::call_layer_backward(l, output, input_grad, subnetwork, params_grad);
static_assert(impl::is_inplace_layer(l, subnetwork) == impl::has_inplace_backward(l, subnetwork),
"Layer not defined correctly. forward and backward methods must either both be in-place or both out-of-place. ");
// Make sure the outputs of forward() and backward() are the same when they are run
// in in-place mode.
if (impl::is_inplace_layer(l, subnetwork))
{
test_layer_subnet subnetwork2(rnd);
layer_details_type ll(l);
ll.setup(subnetwork2);
resizable_tensor ip_out;
impl::call_layer_forward(ll, subnetwork2, ip_out);
impl::call_layer_forward(ll, subnetwork2, subnetwork2.get_mutable_output());
const auto forward_error = max(abs(mat(ip_out) - mat(subnetwork2.get_output())));
if (forward_error > 0.00001)
{
using namespace std;
sout << "This layer is supposed to support in-place computations but the output of forward_inplace()\n";
sout << "changes when invoked in-place vs. out-of-place. The error was: " << forward_error << endl;
return layer_test_results(sout.str());
}
resizable_tensor params_grad;
params_grad.copy_size(ll.get_layer_params());
params_grad = std::numeric_limits<float>::infinity();
resizable_tensor input_grad;
input_grad.copy_size(ip_out);
fill_with_gassuan_random_numbers(input_grad, rnd);
resizable_tensor params_grad1, params_grad2, data_grad1, data_grad2;
params_grad1 = params_grad;
params_grad2 = params_grad;
// Now call backward() and make sure it works as well. Recall that when an
// in-place layer works in-place it assigns to it's outputs but when it's
// not running in-place it adds. So we initialize to a non-zero value to
// check that this is the behavior that really executes.
subnetwork2.get_gradient_input() = 9;
impl::call_layer_backward(ll, ip_out, input_grad, subnetwork2, params_grad1);
data_grad1 = subnetwork2.get_gradient_input();
subnetwork2.get_gradient_input() = mat(input_grad);
impl::call_layer_backward(ll, ip_out, subnetwork2.get_gradient_input(), subnetwork2, params_grad2);
data_grad2 = subnetwork2.get_gradient_input();
if (params_grad.size() != 0)
{
const auto backward_param_error = max(abs(mat(params_grad1) - mat(params_grad2)));
if (backward_param_error > 0.00001)
{
using namespace std;
sout << "This layer is supposed to support in-place computations but the output of backward_inplace()\n";
sout << "changes when invoked in-place vs. out-of-place. The error was: " << backward_param_error << endl;
return layer_test_results(sout.str());
}
}
const auto backward_data_error = max(abs(mat(data_grad1)-9 - mat(data_grad2)));
if (backward_data_error > 0.00001)
{
using namespace std;
sout << "This layer is supposed to support in-place computations but the output of backward_inplace()\n";
sout << "changes when invoked in-place vs. out-of-place. The error was: " << backward_data_error << endl;
return layer_test_results(sout.str());
}
}
// ==================================================================
// first validate the way the parameter gradients are computed
for (unsigned long i = 0; i < params_grad.size(); ++i)
{
layer_details_type l1(l);
float eps = l1.get_layer_params().host()[i]*base_eps;
if (eps == 0)
eps = base_eps;
const float oldval = l1.get_layer_params().host()[i];
l1.get_layer_params().host()[i] = oldval+eps;
impl::call_layer_forward(l1, subnetwork, out2);
l1.get_layer_params().host()[i] = oldval-eps;
impl::call_layer_forward(l1, subnetwork, out3);
l1.get_layer_params().host()[i] = oldval;
// Compute a reference derivative via a central differences approximation and
// compare it to the one output by the layer and make sure they match.
double reference_derivative = (dot(out2,input_grad)-dot(out3, input_grad))/(2*eps);
double output_derivative = params_grad.host()[i];
double relative_error;
if (reference_derivative*output_derivative != 0)
relative_error = (reference_derivative - output_derivative)/(reference_derivative);
else
relative_error = (reference_derivative - output_derivative);
double absolute_error = (reference_derivative - output_derivative);
rs_params.add(std::abs(relative_error));
if (std::abs(relative_error) > 0.05 && std::abs(absolute_error) > 0.006)
{
using namespace std;
sout << "Gradient error in parameter #" << i <<". Relative error: "<< relative_error << endl;
sout << "expected derivative: " << reference_derivative << endl;
sout << "output derivative: " << output_derivative << endl;
sout << "iteration: " << iter << endl;
return layer_test_results(sout.str());
}
}
// ==================================================================
// now validate the data gradients
for (unsigned long i = 0; i < num_data_inputs; ++i)
{
const float oldval = subnetwork.get_output_element(i);
float eps = oldval*base_eps;
if (eps == 0)
eps = base_eps;
subnetwork.get_output_element(i) = oldval+eps;
impl::call_layer_forward(l, subnetwork, out2);
subnetwork.get_output_element(i) = oldval-eps;
impl::call_layer_forward(l, subnetwork, out3);
subnetwork.get_output_element(i) = oldval;
// Compute a reference derivative via a central differences approximation and
// compare it to the one output by the layer and make sure they match.
double reference_derivative = (dot(out2,input_grad)-dot(out3, input_grad))/(2*eps);
double output_derivative = subnetwork.get_gradient_input_element(i);
output_derivative -= initial_gradient_input[i];
double relative_error;
if (reference_derivative*output_derivative != 0)
relative_error = (reference_derivative - output_derivative)/(reference_derivative);
else
relative_error = (reference_derivative - output_derivative);
double absolute_error = (reference_derivative - output_derivative);
rs_data.add(std::abs(relative_error));
if (std::abs(relative_error) > 0.05 && std::abs(absolute_error) > 0.006)
{
using namespace std;
sout << "Gradient error in data variable #" << i <<". Relative error: "<< relative_error << endl;
sout << "expected derivative: " << reference_derivative << endl;
sout << "output derivative: " << output_derivative << endl;
sout << "iteration: " << iter << endl;
return layer_test_results(sout.str());
}
}
} // end for (int iter = 0; iter < 10; ++iter)
if (rs_params.mean() > 0.003)
{
using namespace std;
sout << "Average parameter gradient error is somewhat large at: "<< rs_params.mean() << endl;
return layer_test_results(sout.str());
}
if (rs_data.mean() > 0.003)
{
using namespace std;
sout << "Average data gradient error is somewhat large at: "<< rs_data.mean() << endl;
return layer_test_results(sout.str());
}
return layer_test_results();
}
template <
typename layer_details_type
>
layer_test_results test_layer (
layer_details_type l
)
{
// Try a few different derivative step sizes to see if any work.
for (float base_eps = 0.0001; base_eps < 0.1; base_eps *= 2)
{
auto result = impl_test_layer(l, base_eps);
if (result)
return result;
}
// However, if none of the step sizes worked then try this one and probably result
// in returning an error.
return impl_test_layer(l, 0.01);
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <size_t i, size_t num>
struct vl_loop
{
template <
typename net_type,
typename visitor
>
static void visit(
net_type& net,
visitor&& v
)
{
// Call whatever version of the visitor the user provided.
call_if_valid(v, i, layer<i>(net));
call_if_valid(v, layer<i>(net));
vl_loop<i+1, num>::visit(net,v);
}
};
template <size_t num>
struct vl_loop<num,num>
{
template <
typename net_type,
typename visitor
>
static void visit(
net_type&,
visitor&&
)
{
// Base case of recursion. Don't do anything.
}
};
template <size_t i, size_t num>
struct vl_loop_backwards
{
template <
typename net_type,
typename visitor
>
static void visit(
net_type& net,
visitor&& v
)
{
vl_loop_backwards<i+1, num>::visit(net,v);
// Call whatever version of the visitor the user provided.
call_if_valid(v, i, layer<i>(net));
call_if_valid(v, layer<i>(net));
}
};
template <size_t num>
struct vl_loop_backwards<num,num>
{
template <
typename net_type,
typename visitor
>
static void visit(
net_type&,
visitor&&
)
{
// Base case of recursion. Don't do anything.
}
};
}
template <
typename net_type,
typename visitor
>
void visit_layers(
net_type& net,
visitor v
)
{
impl::vl_loop<0, net_type::num_layers>::visit(net, v);
}
template <
typename net_type,
typename visitor
>
void visit_layers_backwards(
net_type& net,
visitor v
)
{
impl::vl_loop_backwards<0, net_type::num_layers>::visit(net, v);
}
template <
size_t begin,
size_t end,
typename net_type,
typename visitor
>
void visit_layers_range(
net_type& net,
visitor v
)
{
static_assert(begin <= end, "Invalid range");
static_assert(end <= net_type::num_layers, "Invalid range");
impl::vl_loop<begin,end>::visit(net, v);
}
template <
size_t begin,
size_t end,
typename net_type,
typename visitor
>
void visit_layers_backwards_range(
net_type& net,
visitor v
)
{
static_assert(begin <= end, "Invalid range");
static_assert(end <= net_type::num_layers, "Invalid range");
impl::vl_loop_backwards<begin,end>::visit(net, v);
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <size_t i, unsigned long tag_id>
struct vl_until_tag
{
template <
typename net_type,
typename next_net_type,
typename visitor
>
static void visit(
net_type& net,
next_net_type& next_net,
visitor&& v
)
{
call_if_valid(v, next_net);
vl_until_tag<i+1,tag_id>::visit(net,layer<i+1>(net),v);
}
template <
typename net_type,
typename SUBNET,
typename visitor
>
static void visit(
net_type& net,
const add_tag_layer<tag_id,SUBNET>& next_net,
visitor&& v
)
{
call_if_valid(v, next_net);
}
template <
typename net_type,
typename SUBNET,
typename visitor
>
static void visit(
net_type& net,
add_tag_layer<tag_id,SUBNET>& next_net,
visitor&& v
)
{
call_if_valid(v, next_net);
}
};
}
template <
unsigned long tag_id,
typename net_type,
typename visitor
>
void visit_layers_until_tag(
net_type& net,
visitor v
)
{
impl::vl_until_tag<0,tag_id>::visit(net, net, v);
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <
typename visitor
>
class visitor_computational_layer
{
public:
explicit visitor_computational_layer(visitor& v) : v_(v) {}
template <typename layer>
void do_visit(size_t idx, layer& l) const
{
// Call whatever version of the visitor the user provided.
call_if_valid(v_, idx, l.layer_details());
call_if_valid(v_, l.layer_details());
}
// const case
template <typename T, typename U, typename E>
void operator()(size_t idx, const add_layer<T,U,E>& l) const { do_visit(idx, l); }
// non-const cast
template <typename T, typename U, typename E>
void operator()(size_t idx, add_layer<T,U,E>& l) const { do_visit(idx, l); }
private:
visitor& v_;
};
}
template <
typename net_type,
typename visitor
>
void visit_computational_layers(
net_type& net,
visitor v
)
{
visit_layers(net, impl::visitor_computational_layer<visitor>(v));
}
template <
size_t begin,
size_t end,
typename net_type,
typename visitor
>
void visit_computational_layers_range(
net_type& net,
visitor v
)
{
visit_layers_range<begin,end>(net, impl::visitor_computational_layer<visitor>(v));
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <
typename visitor
>
class visit_layer_parameters
{
public:
explicit visit_layer_parameters(visitor& v) : v_(v) {}
template <typename layer>
void operator()(layer& l)
{
// Call whatever version of the visitor the user provided.
const bool visitor_called = call_if_valid(v_, computational_layer_idx, l.get_layer_params()) ||
call_if_valid(v_, l.get_layer_params());
DLIB_CASSERT(visitor_called, "A visitor function with an incorrect signature was given to visit_layer_parameters()");
++computational_layer_idx;
}
private:
size_t computational_layer_idx = 0;
visitor& v_;
};
}
template <
typename net_type,
typename visitor
>
void visit_layer_parameters(
net_type& net,
visitor v
)
{
visit_computational_layers(net, impl::visit_layer_parameters<visitor>(v));
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <
typename visitor
>
class visit_layer_parameter_gradients
{
public:
explicit visit_layer_parameter_gradients(visitor& v) : v_(v) {}
template <typename layer>
void do_visit(layer& l)
{
// Call whatever version of the visitor the user provided.
const bool visitor_called = call_if_valid(v_, computational_layer_idx, l.get_parameter_gradient()) ||
call_if_valid(v_, l.get_parameter_gradient());
DLIB_CASSERT(visitor_called, "A visitor function with an incorrect signature was given to visit_layer_parameter_gradients()");
++computational_layer_idx;
}
// const version
template <typename T, typename U, typename E>
void operator()(const add_layer<T,U,E>& l) { do_visit(l); }
// non-const version
template <typename T, typename U, typename E>
void operator()(add_layer<T,U,E>& l) { do_visit(l); }
private:
size_t computational_layer_idx = 0;
visitor& v_;
};
}
template <
typename net_type,
typename visitor
>
void visit_layer_parameter_gradients(
net_type& net,
visitor v
)
{
visit_layers(net, impl::visit_layer_parameter_gradients<visitor>(v));
}
// ----------------------------------------------------------------------------------------
}
#endif // DLIB_DNn_CORE_H_