// 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_