mirror of
https://gitcode.com/gh_mirrors/ope/OpenFace.git
synced 2026-05-16 12:17:57 +00:00
430 lines
16 KiB
C++
430 lines
16 KiB
C++
// Copyright (C) 2009 Davis E. King (davis@dlib.net)
|
|
// License: Boost Software License See LICENSE.txt for the full license.
|
|
#ifndef DLIB_EMPIRICAL_KERNEl_MAP_H_
|
|
#define DLIB_EMPIRICAL_KERNEl_MAP_H_
|
|
|
|
#include "../matrix.h"
|
|
#include "empirical_kernel_map_abstract.h"
|
|
#include "linearly_independent_subset_finder.h"
|
|
#include <vector>
|
|
#include "../algs.h"
|
|
#include "kernel_matrix.h"
|
|
#include "function.h"
|
|
|
|
namespace dlib
|
|
{
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
template <typename kernel_type, typename EXP>
|
|
const decision_function<kernel_type> convert_to_decision_function (
|
|
const projection_function<kernel_type>& project_funct,
|
|
const matrix_exp<EXP>& vect
|
|
)
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(project_funct.out_vector_size() > 0 && is_vector(vect) &&
|
|
project_funct.out_vector_size() == vect.size() && project_funct.weights.nc() == project_funct.basis_vectors.size(),
|
|
"\t const decision_function convert_to_decision_function()"
|
|
<< "\n\t Invalid inputs to this function."
|
|
<< "\n\t project_funct.out_vector_size(): " << project_funct.out_vector_size()
|
|
<< "\n\t project_funct.weights.nc(): " << project_funct.weights.nc()
|
|
<< "\n\t project_funct.basis_vectors.size(): " << project_funct.basis_vectors.size()
|
|
<< "\n\t is_vector(vect): " << is_vector(vect)
|
|
<< "\n\t vect.size(): " << vect.size()
|
|
);
|
|
|
|
return decision_function<kernel_type>(trans(project_funct.weights)*vect,
|
|
0,
|
|
project_funct.kernel_function,
|
|
project_funct.basis_vectors);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
template <typename kern_type>
|
|
class empirical_kernel_map
|
|
{
|
|
public:
|
|
|
|
struct empirical_kernel_map_error : public error
|
|
{
|
|
empirical_kernel_map_error(const std::string& message): error(message) {}
|
|
};
|
|
|
|
typedef kern_type kernel_type;
|
|
typedef typename kernel_type::sample_type sample_type;
|
|
typedef typename kernel_type::scalar_type scalar_type;
|
|
typedef typename kernel_type::mem_manager_type mem_manager_type;
|
|
|
|
void clear (
|
|
)
|
|
{
|
|
empirical_kernel_map().swap(*this);
|
|
}
|
|
|
|
template <typename T>
|
|
void load(
|
|
const kernel_type& kernel_,
|
|
const T& basis_samples
|
|
)
|
|
{
|
|
load_impl(kernel_, mat(basis_samples));
|
|
}
|
|
|
|
void load(
|
|
const linearly_independent_subset_finder<kernel_type>& lisf
|
|
)
|
|
{
|
|
if (lisf.size() == 0)
|
|
{
|
|
std::ostringstream sout;
|
|
sout << "An empty linearly_independent_subset_finder was supplied to the\n"
|
|
<< "empirical_kernel_map::load() function. One reason this might occur\n"
|
|
<< "is if your dataset contains only zero vectors (or vectors \n"
|
|
<< "approximately zero).\n";
|
|
clear();
|
|
throw empirical_kernel_map_error(sout.str());
|
|
}
|
|
|
|
kernel = lisf.get_kernel();
|
|
weights = trans(chol(lisf.get_inv_kernel_marix()));
|
|
basis.resize(lisf.size());
|
|
for (unsigned long i = 0; i < basis.size(); ++i)
|
|
basis[i] = lisf[i];
|
|
|
|
}
|
|
|
|
const kernel_type get_kernel (
|
|
) const
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(out_vector_size() > 0,
|
|
"\tconst kernel_type empirical_kernel_map::get_kernel()"
|
|
<< "\n\t You have to load this object with a kernel before you can call this function"
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
return kernel;
|
|
}
|
|
|
|
long out_vector_size (
|
|
) const
|
|
{
|
|
return weights.nr();
|
|
}
|
|
|
|
unsigned long basis_size (
|
|
) const
|
|
{
|
|
return basis.size();
|
|
}
|
|
|
|
const sample_type& operator[] (
|
|
unsigned long idx
|
|
) const
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT( idx < basis_size(),
|
|
"\t const sample_type& empirical_kernel_map::operator[](idx)"
|
|
<< "\n\t Invalid inputs to this function."
|
|
<< "\n\t basis_size(): " << basis_size()
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
return basis[idx];
|
|
}
|
|
|
|
template <typename EXP>
|
|
const decision_function<kernel_type> convert_to_decision_function (
|
|
const matrix_exp<EXP>& vect
|
|
) const
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(out_vector_size() != 0 && is_vector(vect) && out_vector_size() == vect.size(),
|
|
"\t const decision_function empirical_kernel_map::convert_to_decision_function()"
|
|
<< "\n\t Invalid inputs to this function."
|
|
<< "\n\t out_vector_size(): " << out_vector_size()
|
|
<< "\n\t is_vector(vect): " << is_vector(vect)
|
|
<< "\n\t vect.size(): " << vect.size()
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
return decision_function<kernel_type>(trans(weights)*vect, 0, kernel, mat(basis));
|
|
}
|
|
|
|
template <typename EXP>
|
|
const distance_function<kernel_type> convert_to_distance_function (
|
|
const matrix_exp<EXP>& vect
|
|
) const
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(out_vector_size() != 0 && is_vector(vect) && out_vector_size() == vect.size(),
|
|
"\t const distance_function empirical_kernel_map::convert_to_distance_function()"
|
|
<< "\n\t Invalid inputs to this function."
|
|
<< "\n\t out_vector_size(): " << out_vector_size()
|
|
<< "\n\t is_vector(vect): " << is_vector(vect)
|
|
<< "\n\t vect.size(): " << vect.size()
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
return distance_function<kernel_type>(trans(weights)*vect, dot(vect,vect), kernel, mat(basis));
|
|
}
|
|
|
|
const projection_function<kernel_type> get_projection_function (
|
|
) const
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(out_vector_size() != 0,
|
|
"\tconst projection_function empirical_kernel_map::get_projection_function()"
|
|
<< "\n\t You have to load this object with data before you can call this function"
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
return projection_function<kernel_type>(weights, kernel, mat(basis));
|
|
}
|
|
|
|
const matrix<scalar_type,0,0,mem_manager_type> get_transformation_to (
|
|
const empirical_kernel_map& target
|
|
) const
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(out_vector_size() != 0 &&
|
|
target.out_vector_size() != 0 &&
|
|
get_kernel() == target.get_kernel(),
|
|
"\t const matrix empirical_kernel_map::get_transformation_to(target)"
|
|
<< "\n\t Invalid inputs were given to this function"
|
|
<< "\n\t out_vector_size(): " << out_vector_size()
|
|
<< "\n\t target.out_vector_size(): " << target.out_vector_size()
|
|
<< "\n\t get_kernel()==target.get_kernel(): " << (get_kernel()==target.get_kernel())
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
return target.weights * kernel_matrix(target.get_kernel(),target.basis, basis)*trans(weights);
|
|
}
|
|
|
|
void get_transformation_to (
|
|
const empirical_kernel_map& target,
|
|
matrix<scalar_type, 0, 0, mem_manager_type>& tmat,
|
|
projection_function<kernel_type>& partial_projection
|
|
) const
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(out_vector_size() != 0 &&
|
|
target.out_vector_size() != 0 &&
|
|
get_kernel() == target.get_kernel() &&
|
|
basis_size() < target.basis_size(),
|
|
"\t void empirical_kernel_map::get_transformation_to(target, tmat, partial_projection)"
|
|
<< "\n\t Invalid inputs were given to this function"
|
|
<< "\n\t out_vector_size(): " << out_vector_size()
|
|
<< "\n\t target.out_vector_size(): " << target.out_vector_size()
|
|
<< "\n\t basis_size(): " << basis_size()
|
|
<< "\n\t target.basis_size(): " << target.basis_size()
|
|
<< "\n\t get_kernel()==target.get_kernel(): " << (get_kernel()==target.get_kernel())
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
#ifdef ENABLE_ASSERTS
|
|
for (unsigned long i = 0; i < basis_size(); ++i)
|
|
{
|
|
DLIB_ASSERT(dlib::equal((*this)[i], target[i]),
|
|
"\t const matrix empirical_kernel_map::get_transformation_to(target, tmat, partial_projection)"
|
|
<< "\n\t target must contain a superset of the basis vectors in *this"
|
|
<< "\n\t i: " << i
|
|
<< "\n\t this: " << this
|
|
);
|
|
}
|
|
#endif
|
|
|
|
const unsigned long num1 = basis.size();
|
|
const unsigned long num2 = target.basis.size();
|
|
|
|
tmat = colm(target.weights, range(0,num1-1))*kernel_matrix(kernel, basis)*trans(weights);
|
|
|
|
empirical_kernel_map temp_ekm;
|
|
temp_ekm.load(kernel, rowm(mat(target.basis), range(num1,num2-1)));
|
|
|
|
partial_projection = temp_ekm.get_projection_function();
|
|
|
|
partial_projection.weights = colm(target.weights,range(num1,num2-1))*
|
|
kernel_matrix(kernel, temp_ekm.basis)*
|
|
trans(temp_ekm.weights)*
|
|
partial_projection.weights;
|
|
}
|
|
|
|
const matrix<scalar_type,0,1,mem_manager_type>& project (
|
|
const sample_type& samp
|
|
) const
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(out_vector_size() != 0,
|
|
"\tconst matrix empirical_kernel_map::project()"
|
|
<< "\n\t You have to load this object with data before you can call this function"
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
temp1 = kernel_matrix(kernel, basis, samp);
|
|
temp2 = weights*temp1;
|
|
return temp2;
|
|
}
|
|
|
|
const matrix<scalar_type,0,1,mem_manager_type>& project (
|
|
const sample_type& samp,
|
|
scalar_type& projection_error
|
|
) const
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(out_vector_size() != 0,
|
|
"\tconst matrix empirical_kernel_map::project()"
|
|
<< "\n\t You have to load this object with data before you can call this function"
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
temp1 = kernel_matrix(kernel, basis, samp);
|
|
temp2 = weights*temp1;
|
|
// This value should never be negative (it measures squared distance) but I'm putting the abs()
|
|
// here just for good measure since rounding error might push it slightly negative.
|
|
projection_error = std::abs( kernel(samp,samp) - dot(temp2,temp2));
|
|
|
|
return temp2;
|
|
}
|
|
|
|
void swap (
|
|
empirical_kernel_map& item
|
|
)
|
|
{
|
|
basis.swap(item.basis);
|
|
weights.swap(item.weights);
|
|
std::swap(kernel, item.kernel);
|
|
|
|
temp1.swap(item.temp1);
|
|
temp2.swap(item.temp2);
|
|
}
|
|
|
|
friend void serialize (
|
|
const empirical_kernel_map& item,
|
|
std::ostream& out
|
|
)
|
|
{
|
|
serialize(item.basis, out);
|
|
serialize(item.weights, out);
|
|
serialize(item.kernel, out);
|
|
}
|
|
|
|
friend void deserialize (
|
|
empirical_kernel_map& item,
|
|
std::istream& in
|
|
)
|
|
{
|
|
deserialize(item.basis, in);
|
|
deserialize(item.weights, in);
|
|
deserialize(item.kernel, in);
|
|
}
|
|
|
|
private:
|
|
|
|
template <typename T>
|
|
void load_impl(
|
|
const kernel_type& kernel_,
|
|
const T& basis_samples
|
|
)
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(basis_samples.size() > 0 && is_vector(basis_samples),
|
|
"\tvoid empirical_kernel_map::load(kernel,basis_samples)"
|
|
<< "\n\t You have to give a non-empty set of basis_samples and it must be a vector"
|
|
<< "\n\t basis_samples.size(): " << basis_samples.size()
|
|
<< "\n\t is_vector(basis_samples): " << is_vector(basis_samples)
|
|
<< "\n\t this: " << this
|
|
);
|
|
|
|
// clear out the weights before we begin. This way if an exception throws
|
|
// this object will already be in the right state.
|
|
weights.set_size(0,0);
|
|
kernel = kernel_;
|
|
basis.clear();
|
|
basis.reserve(basis_samples.size());
|
|
|
|
// find out the value of the largest norm of the elements in basis_samples.
|
|
const scalar_type max_norm = max(diag(kernel_matrix(kernel, basis_samples)));
|
|
// we will consider anything less than or equal to this number to be 0
|
|
const scalar_type eps = max_norm*100*std::numeric_limits<scalar_type>::epsilon();
|
|
|
|
// Copy all the basis_samples into basis but make sure we don't copy any samples
|
|
// that have length 0
|
|
for (long i = 0; i < basis_samples.size(); ++i)
|
|
{
|
|
const scalar_type norm = kernel(basis_samples(i), basis_samples(i));
|
|
if (norm > eps)
|
|
{
|
|
basis.push_back(basis_samples(i));
|
|
}
|
|
}
|
|
|
|
if (basis.size() == 0)
|
|
{
|
|
clear();
|
|
throw empirical_kernel_map_error("All basis_samples given to empirical_kernel_map::load() were zero vectors");
|
|
}
|
|
|
|
matrix<scalar_type,0,0,mem_manager_type> K(kernel_matrix(kernel, basis)), U,W,V;
|
|
|
|
if (svd2(false,true,K,U,W,V))
|
|
{
|
|
clear();
|
|
throw empirical_kernel_map_error("While loading empirical_kernel_map with data, SVD failed to converge.");
|
|
}
|
|
|
|
|
|
// now count how many elements of W are non-zero
|
|
const long num_not_zero = static_cast<long>(sum(W>eps));
|
|
|
|
// Really, this should never happen. But I'm checking for good measure.
|
|
if (num_not_zero == 0)
|
|
{
|
|
clear();
|
|
throw empirical_kernel_map_error("While loading empirical_kernel_map with data, SVD failed");
|
|
}
|
|
|
|
weights.set_size(num_not_zero, basis.size());
|
|
|
|
// now fill the weights matrix with the output of the SVD
|
|
long counter = 0;
|
|
for (long i =0; i < W.size(); ++i)
|
|
{
|
|
double val = W(i);
|
|
if (val > eps)
|
|
{
|
|
val = std::sqrt(val);
|
|
set_rowm(weights,counter) = rowm(trans(V),i)/val;
|
|
++counter;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
std::vector<sample_type> basis;
|
|
matrix<scalar_type,0,0,mem_manager_type> weights;
|
|
kernel_type kernel;
|
|
|
|
// These members don't contribute to the logical state of this object. They are
|
|
// just here so that they don't have to be reallocated every time the project() function
|
|
// is called.
|
|
mutable matrix<scalar_type,0,1,mem_manager_type> temp1, temp2;
|
|
|
|
};
|
|
|
|
template <typename kernel_type>
|
|
void swap (
|
|
empirical_kernel_map<kernel_type>& a,
|
|
empirical_kernel_map<kernel_type>& b
|
|
) { a.swap(b); }
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
}
|
|
|
|
#endif // DLIB_EMPIRICAL_KERNEl_MAP_H_
|
|
|