diff --git a/dlib/svm/empirical_kernel_map.h b/dlib/svm/empirical_kernel_map.h index 9c7427843..f371d934d 100644 --- a/dlib/svm/empirical_kernel_map.h +++ b/dlib/svm/empirical_kernel_map.h @@ -95,7 +95,7 @@ namespace dlib // 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*std::numeric_limits::epsilon(); + const scalar_type eps = max_norm*100*std::numeric_limits::epsilon(); // Copy all the basis_samples into basis but make sure we don't copy any samples // that have length 0 @@ -218,6 +218,25 @@ namespace dlib return projection_function(weights, kernel, vector_to_matrix(basis)); } + const matrix 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); + } + const matrix& project ( const sample_type& samp ) const diff --git a/dlib/svm/empirical_kernel_map_abstract.h b/dlib/svm/empirical_kernel_map_abstract.h index ee903f834..c8424c0a7 100644 --- a/dlib/svm/empirical_kernel_map_abstract.h +++ b/dlib/svm/empirical_kernel_map_abstract.h @@ -256,6 +256,37 @@ namespace dlib this->project() on that sample. !*/ + const matrix get_transformation_to ( + const empirical_kernel_map& target + ) const; + /*! + requires + - get_kernel() == target.get_kernel() + - out_vector_size() != 0 + - target.out_vector_size() != 0 + ensures + - A point in the kernel feature space defined by the kernel get_kernel() typically + has different representations with respect to different empirical_kernel_maps. + This function lets you obtain a transformation matrix that will allow you + to map between these different representations. That is, this function returns + a matrix M with the following properties: + - M maps vectors represented according to *this into the representation used by target. + - M.nr() == target.out_vector_size() + - M.nc() == this->out_vector_size() + - Let V be a vector of this->out_vector_size() length. Then define two distance_functions + DF1 = this->convert_to_distance_function(V) + DF2 = target.convert_to_distance_function(M*V) + + Then DF1(DF2) == 0 // i.e. the distance between these two points should be 0 + + That is, DF1 and DF2 both represent the same point in kernel feature space. Note + that the above equality is only approximate. If the vector V represents a point in + kernel space that isn't in the span of the basis samples used by target then the + equality is approximate. However, if it is in their span then the equality will + be exact. For example, if target's basis samples are a superset of the basis samples + used by *this then the equality will always be exact (within rounding error). + !*/ + void swap ( empirical_kernel_map& item ); diff --git a/dlib/test/empirical_kernel_map.cpp b/dlib/test/empirical_kernel_map.cpp index 7e0580923..a97bce144 100644 --- a/dlib/test/empirical_kernel_map.cpp +++ b/dlib/test/empirical_kernel_map.cpp @@ -96,7 +96,7 @@ namespace // make sure the distances match const double dist_error = abs(length(proj_samples[idx1] - proj_samples[idx2]) - dist_funct(samples[idx2])); - DLIB_TEST_MSG( dist_error < 1e-7, dist_error); + DLIB_TEST_MSG( dist_error < 1e-6, dist_error); // make sure the dot products match DLIB_TEST(abs(dot(proj_samples[idx1],proj_samples[idx2]) - dec_funct(samples[idx2])) < 1e-10); @@ -160,6 +160,87 @@ namespace } + + + + } + + for (int j = 1; j <= 20; ++j) + { + dlog << LTRACE << "j: " << j; + sample_type samp, samp2; + std::vector samples1; + std::vector samples2; + print_spinner(); + // make some random samples. At the end samples1 will be a subset of samples2 + for (int i = 0; i < 5*j; ++i) + { + samples1.push_back(randm(10,1,rnd)); + samples2.push_back(samples1.back()); + } + for (int i = 0; i < 5*j; ++i) + { + samples2.push_back(randm(10,1,rnd)); + } + // add on a little bit to make sure there is at least one non-zero sample. If all the + // samples are zero then empirical_kernel_map_error will be thrown and we don't want that. + samples1.front()(0) += 0.001; + samples2.front()(0) += 0.001; + + ekm.load(kern, samples1); + ekm2.load(kern, samples2); + + dlog << LTRACE << "ekm.out_vector_size(): " << ekm.out_vector_size(); + dlog << LTRACE << "ekm2.out_vector_size(): " << ekm2.out_vector_size(); + const double eps = 1e-6; + + matrix transform; + // Make sure transformations back to yourself work right. Note that we can't just + // check that transform is the identity matrix since it might be an identity transform + // for only a subspace of vectors (this happens if the ekm maps points into a subspace of + // all possible ekm.out_vector_size() vectors). + transform = ekm.get_transformation_to(ekm); + DLIB_TEST(transform.nr() == ekm.out_vector_size()); + DLIB_TEST(transform.nc() == ekm.out_vector_size()); + for (unsigned long i = 0; i < samples1.size(); ++i) + { + samp = ekm.project(samples1[i]); + DLIB_TEST_MSG(length(samp - transform*samp) < eps, length(samp - transform*samp)); + samp = ekm.project((samples1[0] + samples1[i])/2); + DLIB_TEST_MSG(length(samp - transform*samp) < eps, length(samp - transform*samp)); + } + + transform = ekm2.get_transformation_to(ekm2); + DLIB_TEST(transform.nr() == ekm2.out_vector_size()); + DLIB_TEST(transform.nc() == ekm2.out_vector_size()); + for (unsigned long i = 0; i < samples2.size(); ++i) + { + samp = ekm2.project(samples2[i]); + DLIB_TEST_MSG(length(samp - transform*samp) < eps, length(samp - transform*samp)); + samp = ekm2.project((samples2[0] + samples2[i])/2); + DLIB_TEST_MSG(length(samp - transform*samp) < eps, length(samp - transform*samp)); + //dlog << LTRACE << "mapping error: " << length(samp - transform*samp); + } + + + // now test the transform from ekm to ekm2 + transform = ekm.get_transformation_to(ekm2); + DLIB_TEST(transform.nr() == ekm2.out_vector_size()); + DLIB_TEST(transform.nc() == ekm.out_vector_size()); + for (unsigned long i = 0; i < samples1.size(); ++i) + { + samp = ekm.project(samples1[i]); + distance_function df1 = ekm.convert_to_distance_function(samp); + distance_function df2 = ekm2.convert_to_distance_function(transform*samp); + DLIB_TEST_MSG(df1(df2) < eps, df1(df2)); + //dlog << LTRACE << "mapping error: " << df1(df2); + + + samp = ekm.project((samples1[0] + samples1[i])/2); + df1 = ekm.convert_to_distance_function(samp); + df2 = ekm2.convert_to_distance_function(transform*samp); + DLIB_TEST_MSG(df1(df2) < eps, df1(df2)); + } } } @@ -172,8 +253,10 @@ namespace rnd.set_seed(cast_to_string(thetime)); print_spinner(); + dlog << LINFO << "test with linear kernel"; test_with_kernel(linear_kernel()); print_spinner(); + dlog << LINFO << "test with rbf kernel"; test_with_kernel(radial_basis_kernel(0.2)); print_spinner(); }