// Copyright (C) 2009 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_DPCA_h_ #define DLIB_DPCA_h_ #include "dpca_abstract.h" #include #include #include "../algs.h" #include "../matrix.h" #include namespace dlib { // ---------------------------------------------------------------------------------------- template < typename matrix_type > class discriminant_pca { /*! INITIAL VALUE - vect_size == 0 - total_count == 0 - between_count == 0 - within_count == 0 - between_weight == 1 - within_weight == 1 CONVENTION - vect_size == in_vector_size() - total_count == the number of times add_to_total_variance() has been called. - within_count == the number of times add_to_within_class_variance() has been called. - between_count == the number of times add_to_between_class_variance() has been called. - between_weight == between_class_weight() - within_weight == within_class_weight() - if (total_count != 0) - total_sum == the sum of all vectors given to add_to_total_variance() - the covariance of all the elements given to add_to_total_variance() is given by: - let avg == total_sum/total_count - covariance == total_cov/total_count - avg*trans(avg) - if (within_count != 0) - within_cov/within_count == the normalized within class scatter matrix - if (between_count != 0) - between_cov/between_count == the normalized between class scatter matrix !*/ public: struct discriminant_pca_error : public error { discriminant_pca_error(const std::string& message): error(message) {} }; typedef typename matrix_type::mem_manager_type mem_manager_type; typedef typename matrix_type::type scalar_type; typedef typename matrix_type::layout_type layout_type; typedef matrix general_matrix; typedef matrix column_matrix; discriminant_pca ( ) { clear(); } void clear( ) { total_count = 0; between_count = 0; within_count = 0; vect_size = 0; between_weight = 1; within_weight = 1; total_sum.set_size(0); between_cov.set_size(0,0); total_cov.set_size(0,0); within_cov.set_size(0,0); } long in_vector_size ( ) const { return vect_size; } void set_within_class_weight ( scalar_type weight ) { // make sure requires clause is not broken DLIB_ASSERT(weight >= 0, "\t void discriminant_pca::set_within_class_weight()" << "\n\t You can't use negative weight values" << "\n\t weight: " << weight << "\n\t this: " << this ); within_weight = weight; } scalar_type within_class_weight ( ) const { return within_weight; } void set_between_class_weight ( scalar_type weight ) { // make sure requires clause is not broken DLIB_ASSERT(weight >= 0, "\t void discriminant_pca::set_between_class_weight()" << "\n\t You can't use negative weight values" << "\n\t weight: " << weight << "\n\t this: " << this ); between_weight = weight; } scalar_type between_class_weight ( ) const { return between_weight; } template void add_to_within_class_variance( const matrix_exp& x, const matrix_exp& y ) { // make sure requires clause is not broken DLIB_ASSERT(is_col_vector(x) && is_col_vector(y) && x.size() == y.size() && (in_vector_size() == 0 || x.size() == in_vector_size()), "\t void discriminant_pca::add_to_within_class_variance()" << "\n\t Invalid inputs were given to this function" << "\n\t is_col_vector(x): " << is_col_vector(x) << "\n\t is_col_vector(y): " << is_col_vector(y) << "\n\t x.size(): " << x.size() << "\n\t y.size(): " << y.size() << "\n\t in_vector_size(): " << in_vector_size() << "\n\t this: " << this ); vect_size = x.size(); if (within_count == 0) { within_cov = (x-y)*trans(x-y); } else { within_cov += (x-y)*trans(x-y); } ++within_count; } template void add_to_between_class_variance( const matrix_exp& x, const matrix_exp& y ) { // make sure requires clause is not broken DLIB_ASSERT(is_col_vector(x) && is_col_vector(y) && x.size() == y.size() && (in_vector_size() == 0 || x.size() == in_vector_size()), "\t void discriminant_pca::add_to_between_class_variance()" << "\n\t Invalid inputs were given to this function" << "\n\t is_col_vector(x): " << is_col_vector(x) << "\n\t is_col_vector(y): " << is_col_vector(y) << "\n\t x.size(): " << x.size() << "\n\t y.size(): " << y.size() << "\n\t in_vector_size(): " << in_vector_size() << "\n\t this: " << this ); vect_size = x.size(); if (between_count == 0) { between_cov = (x-y)*trans(x-y); } else { between_cov += (x-y)*trans(x-y); } ++between_count; } template void add_to_total_variance( const matrix_exp& x ) { // make sure requires clause is not broken DLIB_ASSERT(is_col_vector(x) && (in_vector_size() == 0 || x.size() == in_vector_size()), "\t void discriminant_pca::add_to_total_variance()" << "\n\t Invalid inputs were given to this function" << "\n\t is_col_vector(x): " << is_col_vector(x) << "\n\t in_vector_size(): " << in_vector_size() << "\n\t x.size(): " << x.size() << "\n\t this: " << this ); vect_size = x.size(); if (total_count == 0) { total_cov = x*trans(x); total_sum = x; } else { total_cov += x*trans(x); total_sum += x; } ++total_count; } const general_matrix dpca_matrix ( const double eps = 0.99 ) const { general_matrix dpca_mat; general_matrix eigenvalues; dpca_matrix(dpca_mat, eigenvalues, eps); return dpca_mat; } const general_matrix dpca_matrix_of_size ( const long num_rows ) { // make sure requires clause is not broken DLIB_ASSERT(0 < num_rows && num_rows <= in_vector_size(), "\t general_matrix discriminant_pca::dpca_matrix_of_size()" << "\n\t Invalid inputs were given to this function" << "\n\t num_rows: " << num_rows << "\n\t in_vector_size(): " << in_vector_size() << "\n\t this: " << this ); general_matrix dpca_mat; general_matrix eigenvalues; dpca_matrix_of_size(dpca_mat, eigenvalues, num_rows); return dpca_mat; } void dpca_matrix ( general_matrix& dpca_mat, general_matrix& eigenvalues, const double eps = 0.99 ) const { // make sure requires clause is not broken DLIB_ASSERT(0 < eps && eps <= 1 && in_vector_size() != 0, "\t void discriminant_pca::dpca_matrix()" << "\n\t Invalid inputs were given to this function" << "\n\t eps: " << eps << "\n\t in_vector_size(): " << in_vector_size() << "\n\t this: " << this ); compute_dpca_matrix(dpca_mat, eigenvalues, eps, 0); } void dpca_matrix_of_size ( general_matrix& dpca_mat, general_matrix& eigenvalues, const long num_rows ) { // make sure requires clause is not broken DLIB_ASSERT(0 < num_rows && num_rows <= in_vector_size(), "\t general_matrix discriminant_pca::dpca_matrix_of_size()" << "\n\t Invalid inputs were given to this function" << "\n\t num_rows: " << num_rows << "\n\t in_vector_size(): " << in_vector_size() << "\n\t this: " << this ); compute_dpca_matrix(dpca_mat, eigenvalues, 1, num_rows); } void swap ( discriminant_pca& item ) { using std::swap; swap(total_cov, item.total_cov); swap(total_sum, item.total_sum); swap(total_count, item.total_count); swap(vect_size, item.vect_size); swap(between_cov, item.between_cov); swap(between_count, item.between_count); swap(between_weight, item.between_weight); swap(within_cov, item.within_cov); swap(within_count, item.within_count); swap(within_weight, item.within_weight); } friend void deserialize ( discriminant_pca& item, std::istream& in ) { deserialize( item.total_cov, in); deserialize( item.total_sum, in); deserialize( item.total_count, in); deserialize( item.vect_size, in); deserialize( item.between_cov, in); deserialize( item.between_count, in); deserialize( item.between_weight, in); deserialize( item.within_cov, in); deserialize( item.within_count, in); deserialize( item.within_weight, in); } friend void serialize ( const discriminant_pca& item, std::ostream& out ) { serialize( item.total_cov, out); serialize( item.total_sum, out); serialize( item.total_count, out); serialize( item.vect_size, out); serialize( item.between_cov, out); serialize( item.between_count, out); serialize( item.between_weight, out); serialize( item.within_cov, out); serialize( item.within_count, out); serialize( item.within_weight, out); } discriminant_pca operator+ ( const discriminant_pca& item ) const { // make sure requires clause is not broken DLIB_ASSERT((in_vector_size() == 0 || item.in_vector_size() == 0 || in_vector_size() == item.in_vector_size()) && between_class_weight() == item.between_class_weight() && within_class_weight() == item.within_class_weight(), "\t discriminant_pca discriminant_pca::operator+()" << "\n\t The two discriminant_pca objects being added must have compatible parameters" << "\n\t in_vector_size(): " << in_vector_size() << "\n\t item.in_vector_size(): " << item.in_vector_size() << "\n\t between_class_weight(): " << between_class_weight() << "\n\t item.between_class_weight(): " << item.between_class_weight() << "\n\t within_class_weight(): " << within_class_weight() << "\n\t item.within_class_weight(): " << item.within_class_weight() << "\n\t this: " << this ); discriminant_pca temp(item); // We need to make sure to ignore empty matrices. That's what these if statements // are for. if (total_count != 0 && temp.total_count != 0) { temp.total_cov += total_cov; temp.total_sum += total_sum; temp.total_count += total_count; } else if (total_count != 0) { temp.total_cov = total_cov; temp.total_sum = total_sum; temp.total_count = total_count; } if (between_count != 0 && temp.between_count != 0) { temp.between_cov += between_cov; temp.between_count += between_count; } else if (between_count != 0) { temp.between_cov = between_cov; temp.between_count = between_count; } if (within_count != 0 && temp.within_count != 0) { temp.within_cov += within_cov; temp.within_count += within_count; } else if (within_count != 0) { temp.within_cov = within_cov; temp.within_count = within_count; } return temp; } discriminant_pca& operator+= ( const discriminant_pca& rhs ) { (*this + rhs).swap(*this); return *this; } private: void compute_dpca_matrix ( general_matrix& dpca_mat, general_matrix& eigenvalues, const double eps, long num_rows ) const { general_matrix cov; // now combine the three measures of variance into a single matrix by using the // within_weight and between_weight weights. cov = get_total_covariance_matrix(); if (within_count != 0) cov -= within_weight*within_cov/within_count; if (between_count != 0) cov += between_weight*between_cov/between_count; eigenvalue_decomposition eig(make_symmetric(cov)); eigenvalues = eig.get_real_eigenvalues(); dpca_mat = eig.get_pseudo_v(); // sort the eigenvalues and eigenvectors so that the biggest eigenvalues come first rsort_columns(dpca_mat, eigenvalues); long num_vectors = 0; if (num_rows == 0) { // Some of the eigenvalues might be negative. So first lets zero those out // so they won't get considered. eigenvalues = pointwise_multiply(eigenvalues > 0, eigenvalues); // figure out how many eigenvectors we want in our dpca matrix const double thresh = sum(eigenvalues)*eps; double total = 0; for (long r = 0; r < eigenvalues.size() && total < thresh; ++r) { // Don't even think about looking at eigenvalues that are 0. If we go this // far then we have all we need. if (eigenvalues(r) == 0) break; ++num_vectors; total += eigenvalues(r); } if (num_vectors == 0) throw discriminant_pca_error("While performing discriminant_pca, all eigenvalues were negative or 0"); } else { num_vectors = num_rows; } // So now we know we want to use num_vectors of the first eigenvectors. So // pull those out and discard the rest. dpca_mat = trans(colm(dpca_mat,range(0,num_vectors-1))); // also clip off the eigenvalues we aren't using eigenvalues = rowm(eigenvalues, range(0,num_vectors-1)); } general_matrix get_total_covariance_matrix ( ) const /*! ensures - returns the covariance matrix of all the data given to the add_to_total_variance() !*/ { // if we don't even know the dimensionality of the vectors we are dealing // with then just return an empty matrix if (vect_size == 0) return general_matrix(); // we know the vector size but we have zero total covariance. if (total_count == 0) { general_matrix temp(vect_size,vect_size); temp = 0; return temp; } // In this case we actually have something to make a total covariance matrix out of. // So do that. column_matrix avg = total_sum/total_count; return total_cov/total_count - avg*trans(avg); } general_matrix total_cov; column_matrix total_sum; scalar_type total_count; long vect_size; general_matrix between_cov; scalar_type between_count; scalar_type between_weight; general_matrix within_cov; scalar_type within_count; scalar_type within_weight; }; template < typename matrix_type > inline void swap ( discriminant_pca& a, discriminant_pca& b ) { a.swap(b); } // ---------------------------------------------------------------------------------------- } #endif // DLIB_DPCA_h_