mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3001 lines
92 KiB
3001 lines
92 KiB
/*M/////////////////////////////////////////////////////////////////////////////////////// |
|
// |
|
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. |
|
// |
|
// By downloading, copying, installing or using the software you agree to this license. |
|
// If you do not agree to this license, do not download, install, |
|
// copy or use the software. |
|
// |
|
// |
|
// Intel License Agreement |
|
// |
|
// Copyright (C) 2000, Intel Corporation, all rights reserved. |
|
// Third party copyrights are property of their respective owners. |
|
// |
|
// Redistribution and use in source and binary forms, with or without modification, |
|
// are permitted provided that the following conditions are met: |
|
// |
|
// * Redistribution's of source code must retain the above copyright notice, |
|
// this list of conditions and the following disclaimer. |
|
// |
|
// * Redistribution's in binary form must reproduce the above copyright notice, |
|
// this list of conditions and the following disclaimer in the documentation |
|
// and/or other materials provided with the distribution. |
|
// |
|
// * The name of Intel Corporation may not be used to endorse or promote products |
|
// derived from this software without specific prior written permission. |
|
// |
|
// This software is provided by the copyright holders and contributors "as is" and |
|
// any express or implied warranties, including, but not limited to, the implied |
|
// warranties of merchantability and fitness for a particular purpose are disclaimed. |
|
// In no event shall the Intel Corporation or contributors be liable for any direct, |
|
// indirect, incidental, special, exemplary, or consequential damages |
|
// (including, but not limited to, procurement of substitute goods or services; |
|
// loss of use, data, or profits; or business interruption) however caused |
|
// and on any theory of liability, whether in contract, strict liability, |
|
// or tort (including negligence or otherwise) arising in any way out of |
|
// the use of this software, even if advised of the possibility of such damage. |
|
// |
|
//M*/ |
|
|
|
#include "precomp.hpp" |
|
|
|
/****************************************************************************************\ |
|
COPYRIGHT NOTICE |
|
---------------- |
|
|
|
The code has been derived from libsvm library (version 2.6) |
|
(http://www.csie.ntu.edu.tw/~cjlin/libsvm). |
|
|
|
Here is the orignal copyright: |
|
------------------------------------------------------------------------------------------ |
|
Copyright (c) 2000-2003 Chih-Chung Chang and Chih-Jen Lin |
|
All rights reserved. |
|
|
|
Redistribution and use in source and binary forms, with or without |
|
modification, are permitted provided that the following conditions |
|
are met: |
|
|
|
1. Redistributions of source code must retain the above copyright |
|
notice, this list of conditions and the following disclaimer. |
|
|
|
2. Redistributions in binary form must reproduce the above copyright |
|
notice, this list of conditions and the following disclaimer in the |
|
documentation and/or other materials provided with the distribution. |
|
|
|
3. Neither name of copyright holders nor the names of its contributors |
|
may be used to endorse or promote products derived from this software |
|
without specific prior written permission. |
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR |
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
\****************************************************************************************/ |
|
|
|
using namespace cv; |
|
|
|
#define CV_SVM_MIN_CACHE_SIZE (40 << 20) /* 40Mb */ |
|
|
|
#include <stdarg.h> |
|
#include <ctype.h> |
|
|
|
#if 1 |
|
typedef float Qfloat; |
|
#define QFLOAT_TYPE CV_32F |
|
#else |
|
typedef double Qfloat; |
|
#define QFLOAT_TYPE CV_64F |
|
#endif |
|
|
|
// Param Grid |
|
bool CvParamGrid::check() const |
|
{ |
|
bool ok = false; |
|
|
|
CV_FUNCNAME( "CvParamGrid::check" ); |
|
__BEGIN__; |
|
|
|
if( min_val > max_val ) |
|
CV_ERROR( CV_StsBadArg, "Lower bound of the grid must be less then the upper one" ); |
|
if( min_val < DBL_EPSILON ) |
|
CV_ERROR( CV_StsBadArg, "Lower bound of the grid must be positive" ); |
|
if( step < 1. + FLT_EPSILON ) |
|
CV_ERROR( CV_StsBadArg, "Grid step must greater then 1" ); |
|
|
|
ok = true; |
|
|
|
__END__; |
|
|
|
return ok; |
|
} |
|
|
|
CvParamGrid CvSVM::get_default_grid( int param_id ) |
|
{ |
|
CvParamGrid grid; |
|
if( param_id == CvSVM::C ) |
|
{ |
|
grid.min_val = 0.1; |
|
grid.max_val = 500; |
|
grid.step = 5; // total iterations = 5 |
|
} |
|
else if( param_id == CvSVM::GAMMA ) |
|
{ |
|
grid.min_val = 1e-5; |
|
grid.max_val = 0.6; |
|
grid.step = 15; // total iterations = 4 |
|
} |
|
else if( param_id == CvSVM::P ) |
|
{ |
|
grid.min_val = 0.01; |
|
grid.max_val = 100; |
|
grid.step = 7; // total iterations = 4 |
|
} |
|
else if( param_id == CvSVM::NU ) |
|
{ |
|
grid.min_val = 0.01; |
|
grid.max_val = 0.2; |
|
grid.step = 3; // total iterations = 3 |
|
} |
|
else if( param_id == CvSVM::COEF ) |
|
{ |
|
grid.min_val = 0.1; |
|
grid.max_val = 300; |
|
grid.step = 14; // total iterations = 3 |
|
} |
|
else if( param_id == CvSVM::DEGREE ) |
|
{ |
|
grid.min_val = 0.01; |
|
grid.max_val = 4; |
|
grid.step = 7; // total iterations = 3 |
|
} |
|
else |
|
cvError( CV_StsBadArg, "CvSVM::get_default_grid", "Invalid type of parameter " |
|
"(use one of CvSVM::C, CvSVM::GAMMA et al.)", __FILE__, __LINE__ ); |
|
return grid; |
|
} |
|
|
|
// SVM training parameters |
|
CvSVMParams::CvSVMParams() : |
|
svm_type(CvSVM::C_SVC), kernel_type(CvSVM::RBF), degree(0), |
|
gamma(1), coef0(0), C(1), nu(0), p(0), class_weights(0) |
|
{ |
|
term_crit = cvTermCriteria( CV_TERMCRIT_ITER+CV_TERMCRIT_EPS, 1000, FLT_EPSILON ); |
|
} |
|
|
|
|
|
CvSVMParams::CvSVMParams( int _svm_type, int _kernel_type, |
|
double _degree, double _gamma, double _coef0, |
|
double _Con, double _nu, double _p, |
|
CvMat* _class_weights, CvTermCriteria _term_crit ) : |
|
svm_type(_svm_type), kernel_type(_kernel_type), |
|
degree(_degree), gamma(_gamma), coef0(_coef0), |
|
C(_Con), nu(_nu), p(_p), class_weights(_class_weights), term_crit(_term_crit) |
|
{ |
|
} |
|
|
|
|
|
/////////////////////////////////////// SVM kernel /////////////////////////////////////// |
|
|
|
CvSVMKernel::CvSVMKernel() |
|
{ |
|
clear(); |
|
} |
|
|
|
|
|
void CvSVMKernel::clear() |
|
{ |
|
params = 0; |
|
calc_func = 0; |
|
} |
|
|
|
|
|
CvSVMKernel::~CvSVMKernel() |
|
{ |
|
} |
|
|
|
|
|
CvSVMKernel::CvSVMKernel( const CvSVMParams* _params, Calc _calc_func ) |
|
{ |
|
clear(); |
|
create( _params, _calc_func ); |
|
} |
|
|
|
|
|
bool CvSVMKernel::create( const CvSVMParams* _params, Calc _calc_func ) |
|
{ |
|
clear(); |
|
params = _params; |
|
calc_func = _calc_func; |
|
|
|
if( !calc_func ) |
|
calc_func = params->kernel_type == CvSVM::RBF ? &CvSVMKernel::calc_rbf : |
|
params->kernel_type == CvSVM::POLY ? &CvSVMKernel::calc_poly : |
|
params->kernel_type == CvSVM::SIGMOID ? &CvSVMKernel::calc_sigmoid : |
|
params->kernel_type == CvSVM::CHI2 ? &CvSVMKernel::calc_chi2 : |
|
params->kernel_type == CvSVM::INTER ? &CvSVMKernel::calc_intersec : |
|
&CvSVMKernel::calc_linear; |
|
|
|
return true; |
|
} |
|
|
|
|
|
void CvSVMKernel::calc_non_rbf_base( int vcount, int var_count, const float** vecs, |
|
const float* another, Qfloat* results, |
|
double alpha, double beta ) |
|
{ |
|
int j, k; |
|
for( j = 0; j < vcount; j++ ) |
|
{ |
|
const float* sample = vecs[j]; |
|
double s = 0; |
|
for( k = 0; k <= var_count - 4; k += 4 ) |
|
s += sample[k]*another[k] + sample[k+1]*another[k+1] + |
|
sample[k+2]*another[k+2] + sample[k+3]*another[k+3]; |
|
for( ; k < var_count; k++ ) |
|
s += sample[k]*another[k]; |
|
results[j] = (Qfloat)(s*alpha + beta); |
|
} |
|
} |
|
|
|
|
|
void CvSVMKernel::calc_linear( int vcount, int var_count, const float** vecs, |
|
const float* another, Qfloat* results ) |
|
{ |
|
calc_non_rbf_base( vcount, var_count, vecs, another, results, 1, 0 ); |
|
} |
|
|
|
|
|
void CvSVMKernel::calc_poly( int vcount, int var_count, const float** vecs, |
|
const float* another, Qfloat* results ) |
|
{ |
|
CvMat R = cvMat( 1, vcount, QFLOAT_TYPE, results ); |
|
calc_non_rbf_base( vcount, var_count, vecs, another, results, params->gamma, params->coef0 ); |
|
if( vcount > 0 ) |
|
cvPow( &R, &R, params->degree ); |
|
} |
|
|
|
|
|
void CvSVMKernel::calc_sigmoid( int vcount, int var_count, const float** vecs, |
|
const float* another, Qfloat* results ) |
|
{ |
|
int j; |
|
calc_non_rbf_base( vcount, var_count, vecs, another, results, |
|
-2*params->gamma, -2*params->coef0 ); |
|
// TODO: speedup this |
|
for( j = 0; j < vcount; j++ ) |
|
{ |
|
Qfloat t = results[j]; |
|
double e = exp(-fabs(t)); |
|
if( t > 0 ) |
|
results[j] = (Qfloat)((1. - e)/(1. + e)); |
|
else |
|
results[j] = (Qfloat)((e - 1.)/(e + 1.)); |
|
} |
|
} |
|
|
|
|
|
void CvSVMKernel::calc_rbf( int vcount, int var_count, const float** vecs, |
|
const float* another, Qfloat* results ) |
|
{ |
|
CvMat R = cvMat( 1, vcount, QFLOAT_TYPE, results ); |
|
double gamma = -params->gamma; |
|
int j, k; |
|
|
|
for( j = 0; j < vcount; j++ ) |
|
{ |
|
const float* sample = vecs[j]; |
|
double s = 0; |
|
|
|
for( k = 0; k <= var_count - 4; k += 4 ) |
|
{ |
|
double t0 = sample[k] - another[k]; |
|
double t1 = sample[k+1] - another[k+1]; |
|
|
|
s += t0*t0 + t1*t1; |
|
|
|
t0 = sample[k+2] - another[k+2]; |
|
t1 = sample[k+3] - another[k+3]; |
|
|
|
s += t0*t0 + t1*t1; |
|
} |
|
|
|
for( ; k < var_count; k++ ) |
|
{ |
|
double t0 = sample[k] - another[k]; |
|
s += t0*t0; |
|
} |
|
results[j] = (Qfloat)(s*gamma); |
|
} |
|
|
|
if( vcount > 0 ) |
|
cvExp( &R, &R ); |
|
} |
|
|
|
/// Histogram intersection kernel |
|
void CvSVMKernel::calc_intersec( int vcount, int var_count, const float** vecs, |
|
const float* another, Qfloat* results ) |
|
{ |
|
int j, k; |
|
for( j = 0; j < vcount; j++ ) |
|
{ |
|
const float* sample = vecs[j]; |
|
double s = 0; |
|
for( k = 0; k <= var_count - 4; k += 4 ) |
|
s += std::min(sample[k],another[k]) + std::min(sample[k+1],another[k+1]) + |
|
std::min(sample[k+2],another[k+2]) + std::min(sample[k+3],another[k+3]); |
|
for( ; k < var_count; k++ ) |
|
s += std::min(sample[k],another[k]); |
|
results[j] = (Qfloat)(s); |
|
} |
|
} |
|
|
|
/// Exponential chi2 kernel |
|
void CvSVMKernel::calc_chi2( int vcount, int var_count, const float** vecs, |
|
const float* another, Qfloat* results ) |
|
{ |
|
CvMat R = cvMat( 1, vcount, QFLOAT_TYPE, results ); |
|
double gamma = -params->gamma; |
|
int j, k; |
|
for( j = 0; j < vcount; j++ ) |
|
{ |
|
const float* sample = vecs[j]; |
|
double chi2 = 0; |
|
for(k = 0 ; k < var_count; k++ ) |
|
{ |
|
double d = sample[k]-another[k]; |
|
double devisor = sample[k]+another[k]; |
|
/// if devisor == 0, the Chi2 distance would be zero, but calculation would rise an error because of deviding by zero |
|
if (devisor != 0) |
|
{ |
|
chi2 += d*d/devisor; |
|
} |
|
} |
|
results[j] = (Qfloat) (gamma*chi2); |
|
} |
|
if( vcount > 0 ) |
|
cvExp( &R, &R ); |
|
|
|
|
|
} |
|
|
|
void CvSVMKernel::calc( int vcount, int var_count, const float** vecs, |
|
const float* another, Qfloat* results ) |
|
{ |
|
const Qfloat max_val = (Qfloat)(FLT_MAX*1e-3); |
|
int j; |
|
(this->*calc_func)( vcount, var_count, vecs, another, results ); |
|
for( j = 0; j < vcount; j++ ) |
|
{ |
|
if( results[j] > max_val ) |
|
results[j] = max_val; |
|
} |
|
} |
|
|
|
|
|
// Generalized SMO+SVMlight algorithm |
|
// Solves: |
|
// |
|
// min [0.5(\alpha^T Q \alpha) + b^T \alpha] |
|
// |
|
// y^T \alpha = \delta |
|
// y_i = +1 or -1 |
|
// 0 <= alpha_i <= Cp for y_i = 1 |
|
// 0 <= alpha_i <= Cn for y_i = -1 |
|
// |
|
// Given: |
|
// |
|
// Q, b, y, Cp, Cn, and an initial feasible point \alpha |
|
// l is the size of vectors and matrices |
|
// eps is the stopping criterion |
|
// |
|
// solution will be put in \alpha, objective value will be put in obj |
|
// |
|
|
|
void CvSVMSolver::clear() |
|
{ |
|
G = 0; |
|
alpha = 0; |
|
y = 0; |
|
b = 0; |
|
buf[0] = buf[1] = 0; |
|
cvReleaseMemStorage( &storage ); |
|
kernel = 0; |
|
select_working_set_func = 0; |
|
calc_rho_func = 0; |
|
|
|
rows = 0; |
|
samples = 0; |
|
get_row_func = 0; |
|
} |
|
|
|
|
|
CvSVMSolver::CvSVMSolver() |
|
{ |
|
storage = 0; |
|
clear(); |
|
} |
|
|
|
|
|
CvSVMSolver::~CvSVMSolver() |
|
{ |
|
clear(); |
|
} |
|
|
|
|
|
CvSVMSolver::CvSVMSolver( int _sample_count, int _var_count, const float** _samples, schar* _y, |
|
int _alpha_count, double* _alpha, double _Cp, double _Cn, |
|
CvMemStorage* _storage, CvSVMKernel* _kernel, GetRow _get_row, |
|
SelectWorkingSet _select_working_set, CalcRho _calc_rho ) |
|
{ |
|
storage = 0; |
|
create( _sample_count, _var_count, _samples, _y, _alpha_count, _alpha, _Cp, _Cn, |
|
_storage, _kernel, _get_row, _select_working_set, _calc_rho ); |
|
} |
|
|
|
|
|
bool CvSVMSolver::create( int _sample_count, int _var_count, const float** _samples, schar* _y, |
|
int _alpha_count, double* _alpha, double _Cp, double _Cn, |
|
CvMemStorage* _storage, CvSVMKernel* _kernel, GetRow _get_row, |
|
SelectWorkingSet _select_working_set, CalcRho _calc_rho ) |
|
{ |
|
bool ok = false; |
|
int i, svm_type; |
|
|
|
CV_FUNCNAME( "CvSVMSolver::create" ); |
|
|
|
__BEGIN__; |
|
|
|
int rows_hdr_size; |
|
|
|
clear(); |
|
|
|
sample_count = _sample_count; |
|
var_count = _var_count; |
|
samples = _samples; |
|
y = _y; |
|
alpha_count = _alpha_count; |
|
alpha = _alpha; |
|
kernel = _kernel; |
|
|
|
C[0] = _Cn; |
|
C[1] = _Cp; |
|
eps = kernel->params->term_crit.epsilon; |
|
max_iter = kernel->params->term_crit.max_iter; |
|
storage = cvCreateChildMemStorage( _storage ); |
|
|
|
b = (double*)cvMemStorageAlloc( storage, alpha_count*sizeof(b[0])); |
|
alpha_status = (schar*)cvMemStorageAlloc( storage, alpha_count*sizeof(alpha_status[0])); |
|
G = (double*)cvMemStorageAlloc( storage, alpha_count*sizeof(G[0])); |
|
for( i = 0; i < 2; i++ ) |
|
buf[i] = (Qfloat*)cvMemStorageAlloc( storage, sample_count*2*sizeof(buf[i][0]) ); |
|
svm_type = kernel->params->svm_type; |
|
|
|
select_working_set_func = _select_working_set; |
|
if( !select_working_set_func ) |
|
select_working_set_func = svm_type == CvSVM::NU_SVC || svm_type == CvSVM::NU_SVR ? |
|
&CvSVMSolver::select_working_set_nu_svm : &CvSVMSolver::select_working_set; |
|
|
|
calc_rho_func = _calc_rho; |
|
if( !calc_rho_func ) |
|
calc_rho_func = svm_type == CvSVM::NU_SVC || svm_type == CvSVM::NU_SVR ? |
|
&CvSVMSolver::calc_rho_nu_svm : &CvSVMSolver::calc_rho; |
|
|
|
get_row_func = _get_row; |
|
if( !get_row_func ) |
|
get_row_func = params->svm_type == CvSVM::EPS_SVR || |
|
params->svm_type == CvSVM::NU_SVR ? &CvSVMSolver::get_row_svr : |
|
params->svm_type == CvSVM::C_SVC || |
|
params->svm_type == CvSVM::NU_SVC ? &CvSVMSolver::get_row_svc : |
|
&CvSVMSolver::get_row_one_class; |
|
|
|
cache_line_size = sample_count*sizeof(Qfloat); |
|
// cache size = max(num_of_samples^2*sizeof(Qfloat)*0.25, 64Kb) |
|
// (assuming that for large training sets ~25% of Q matrix is used) |
|
cache_size = MAX( cache_line_size*sample_count/4, CV_SVM_MIN_CACHE_SIZE ); |
|
|
|
// the size of Q matrix row headers |
|
rows_hdr_size = sample_count*sizeof(rows[0]); |
|
if( rows_hdr_size > storage->block_size ) |
|
CV_ERROR( CV_StsOutOfRange, "Too small storage block size" ); |
|
|
|
lru_list.prev = lru_list.next = &lru_list; |
|
rows = (CvSVMKernelRow*)cvMemStorageAlloc( storage, rows_hdr_size ); |
|
memset( rows, 0, rows_hdr_size ); |
|
|
|
ok = true; |
|
|
|
__END__; |
|
|
|
return ok; |
|
} |
|
|
|
|
|
float* CvSVMSolver::get_row_base( int i, bool* _existed ) |
|
{ |
|
int i1 = i < sample_count ? i : i - sample_count; |
|
CvSVMKernelRow* row = rows + i1; |
|
bool existed = row->data != 0; |
|
Qfloat* data; |
|
|
|
if( existed || cache_size <= 0 ) |
|
{ |
|
CvSVMKernelRow* del_row = existed ? row : lru_list.prev; |
|
data = del_row->data; |
|
assert( data != 0 ); |
|
|
|
// delete row from the LRU list |
|
del_row->data = 0; |
|
del_row->prev->next = del_row->next; |
|
del_row->next->prev = del_row->prev; |
|
} |
|
else |
|
{ |
|
data = (Qfloat*)cvMemStorageAlloc( storage, cache_line_size ); |
|
cache_size -= cache_line_size; |
|
} |
|
|
|
// insert row into the LRU list |
|
row->data = data; |
|
row->prev = &lru_list; |
|
row->next = lru_list.next; |
|
row->prev->next = row->next->prev = row; |
|
|
|
if( !existed ) |
|
{ |
|
kernel->calc( sample_count, var_count, samples, samples[i1], row->data ); |
|
} |
|
|
|
if( _existed ) |
|
*_existed = existed; |
|
|
|
return row->data; |
|
} |
|
|
|
|
|
float* CvSVMSolver::get_row_svc( int i, float* row, float*, bool existed ) |
|
{ |
|
if( !existed ) |
|
{ |
|
const schar* _y = y; |
|
int j, len = sample_count; |
|
assert( _y && i < sample_count ); |
|
|
|
if( _y[i] > 0 ) |
|
{ |
|
for( j = 0; j < len; j++ ) |
|
row[j] = _y[j]*row[j]; |
|
} |
|
else |
|
{ |
|
for( j = 0; j < len; j++ ) |
|
row[j] = -_y[j]*row[j]; |
|
} |
|
} |
|
return row; |
|
} |
|
|
|
|
|
float* CvSVMSolver::get_row_one_class( int, float* row, float*, bool ) |
|
{ |
|
return row; |
|
} |
|
|
|
|
|
float* CvSVMSolver::get_row_svr( int i, float* row, float* dst, bool ) |
|
{ |
|
int j, len = sample_count; |
|
Qfloat* dst_pos = dst; |
|
Qfloat* dst_neg = dst + len; |
|
if( i >= len ) |
|
{ |
|
Qfloat* temp; |
|
CV_SWAP( dst_pos, dst_neg, temp ); |
|
} |
|
|
|
for( j = 0; j < len; j++ ) |
|
{ |
|
Qfloat t = row[j]; |
|
dst_pos[j] = t; |
|
dst_neg[j] = -t; |
|
} |
|
return dst; |
|
} |
|
|
|
|
|
|
|
float* CvSVMSolver::get_row( int i, float* dst ) |
|
{ |
|
bool existed = false; |
|
float* row = get_row_base( i, &existed ); |
|
return (this->*get_row_func)( i, row, dst, existed ); |
|
} |
|
|
|
|
|
#undef is_upper_bound |
|
#define is_upper_bound(i) (alpha_status[i] > 0) |
|
|
|
#undef is_lower_bound |
|
#define is_lower_bound(i) (alpha_status[i] < 0) |
|
|
|
#undef is_free |
|
#define is_free(i) (alpha_status[i] == 0) |
|
|
|
#undef get_C |
|
#define get_C(i) (C[y[i]>0]) |
|
|
|
#undef update_alpha_status |
|
#define update_alpha_status(i) \ |
|
alpha_status[i] = (schar)(alpha[i] >= get_C(i) ? 1 : alpha[i] <= 0 ? -1 : 0) |
|
|
|
#undef reconstruct_gradient |
|
#define reconstruct_gradient() /* empty for now */ |
|
|
|
|
|
bool CvSVMSolver::solve_generic( CvSVMSolutionInfo& si ) |
|
{ |
|
int iter = 0; |
|
int i, j, k; |
|
|
|
// 1. initialize gradient and alpha status |
|
for( i = 0; i < alpha_count; i++ ) |
|
{ |
|
update_alpha_status(i); |
|
G[i] = b[i]; |
|
if( fabs(G[i]) > 1e200 ) |
|
return false; |
|
} |
|
|
|
for( i = 0; i < alpha_count; i++ ) |
|
{ |
|
if( !is_lower_bound(i) ) |
|
{ |
|
const Qfloat *Q_i = get_row( i, buf[0] ); |
|
double alpha_i = alpha[i]; |
|
|
|
for( j = 0; j < alpha_count; j++ ) |
|
G[j] += alpha_i*Q_i[j]; |
|
} |
|
} |
|
|
|
// 2. optimization loop |
|
for(;;) |
|
{ |
|
const Qfloat *Q_i, *Q_j; |
|
double C_i, C_j; |
|
double old_alpha_i, old_alpha_j, alpha_i, alpha_j; |
|
double delta_alpha_i, delta_alpha_j; |
|
|
|
#ifdef _DEBUG |
|
for( i = 0; i < alpha_count; i++ ) |
|
{ |
|
if( fabs(G[i]) > 1e+300 ) |
|
return false; |
|
|
|
if( fabs(alpha[i]) > 1e16 ) |
|
return false; |
|
} |
|
#endif |
|
|
|
if( (this->*select_working_set_func)( i, j ) != 0 || iter++ >= max_iter ) |
|
break; |
|
|
|
Q_i = get_row( i, buf[0] ); |
|
Q_j = get_row( j, buf[1] ); |
|
|
|
C_i = get_C(i); |
|
C_j = get_C(j); |
|
|
|
alpha_i = old_alpha_i = alpha[i]; |
|
alpha_j = old_alpha_j = alpha[j]; |
|
|
|
if( y[i] != y[j] ) |
|
{ |
|
double denom = Q_i[i]+Q_j[j]+2*Q_i[j]; |
|
double delta = (-G[i]-G[j])/MAX(fabs(denom),FLT_EPSILON); |
|
double diff = alpha_i - alpha_j; |
|
alpha_i += delta; |
|
alpha_j += delta; |
|
|
|
if( diff > 0 && alpha_j < 0 ) |
|
{ |
|
alpha_j = 0; |
|
alpha_i = diff; |
|
} |
|
else if( diff <= 0 && alpha_i < 0 ) |
|
{ |
|
alpha_i = 0; |
|
alpha_j = -diff; |
|
} |
|
|
|
if( diff > C_i - C_j && alpha_i > C_i ) |
|
{ |
|
alpha_i = C_i; |
|
alpha_j = C_i - diff; |
|
} |
|
else if( diff <= C_i - C_j && alpha_j > C_j ) |
|
{ |
|
alpha_j = C_j; |
|
alpha_i = C_j + diff; |
|
} |
|
} |
|
else |
|
{ |
|
double denom = Q_i[i]+Q_j[j]-2*Q_i[j]; |
|
double delta = (G[i]-G[j])/MAX(fabs(denom),FLT_EPSILON); |
|
double sum = alpha_i + alpha_j; |
|
alpha_i -= delta; |
|
alpha_j += delta; |
|
|
|
if( sum > C_i && alpha_i > C_i ) |
|
{ |
|
alpha_i = C_i; |
|
alpha_j = sum - C_i; |
|
} |
|
else if( sum <= C_i && alpha_j < 0) |
|
{ |
|
alpha_j = 0; |
|
alpha_i = sum; |
|
} |
|
|
|
if( sum > C_j && alpha_j > C_j ) |
|
{ |
|
alpha_j = C_j; |
|
alpha_i = sum - C_j; |
|
} |
|
else if( sum <= C_j && alpha_i < 0 ) |
|
{ |
|
alpha_i = 0; |
|
alpha_j = sum; |
|
} |
|
} |
|
|
|
// update alpha |
|
alpha[i] = alpha_i; |
|
alpha[j] = alpha_j; |
|
update_alpha_status(i); |
|
update_alpha_status(j); |
|
|
|
// update G |
|
delta_alpha_i = alpha_i - old_alpha_i; |
|
delta_alpha_j = alpha_j - old_alpha_j; |
|
|
|
for( k = 0; k < alpha_count; k++ ) |
|
G[k] += Q_i[k]*delta_alpha_i + Q_j[k]*delta_alpha_j; |
|
} |
|
|
|
// calculate rho |
|
(this->*calc_rho_func)( si.rho, si.r ); |
|
|
|
// calculate objective value |
|
for( i = 0, si.obj = 0; i < alpha_count; i++ ) |
|
si.obj += alpha[i] * (G[i] + b[i]); |
|
|
|
si.obj *= 0.5; |
|
|
|
si.upper_bound_p = C[1]; |
|
si.upper_bound_n = C[0]; |
|
|
|
return true; |
|
} |
|
|
|
|
|
// return 1 if already optimal, return 0 otherwise |
|
bool |
|
CvSVMSolver::select_working_set( int& out_i, int& out_j ) |
|
{ |
|
// return i,j which maximize -grad(f)^T d , under constraint |
|
// if alpha_i == C, d != +1 |
|
// if alpha_i == 0, d != -1 |
|
double Gmax1 = -DBL_MAX; // max { -grad(f)_i * d | y_i*d = +1 } |
|
int Gmax1_idx = -1; |
|
|
|
double Gmax2 = -DBL_MAX; // max { -grad(f)_i * d | y_i*d = -1 } |
|
int Gmax2_idx = -1; |
|
|
|
int i; |
|
|
|
for( i = 0; i < alpha_count; i++ ) |
|
{ |
|
double t; |
|
|
|
if( y[i] > 0 ) // y = +1 |
|
{ |
|
if( !is_upper_bound(i) && (t = -G[i]) > Gmax1 ) // d = +1 |
|
{ |
|
Gmax1 = t; |
|
Gmax1_idx = i; |
|
} |
|
if( !is_lower_bound(i) && (t = G[i]) > Gmax2 ) // d = -1 |
|
{ |
|
Gmax2 = t; |
|
Gmax2_idx = i; |
|
} |
|
} |
|
else // y = -1 |
|
{ |
|
if( !is_upper_bound(i) && (t = -G[i]) > Gmax2 ) // d = +1 |
|
{ |
|
Gmax2 = t; |
|
Gmax2_idx = i; |
|
} |
|
if( !is_lower_bound(i) && (t = G[i]) > Gmax1 ) // d = -1 |
|
{ |
|
Gmax1 = t; |
|
Gmax1_idx = i; |
|
} |
|
} |
|
} |
|
|
|
out_i = Gmax1_idx; |
|
out_j = Gmax2_idx; |
|
|
|
return Gmax1 + Gmax2 < eps; |
|
} |
|
|
|
|
|
void |
|
CvSVMSolver::calc_rho( double& rho, double& r ) |
|
{ |
|
int i, nr_free = 0; |
|
double ub = DBL_MAX, lb = -DBL_MAX, sum_free = 0; |
|
|
|
for( i = 0; i < alpha_count; i++ ) |
|
{ |
|
double yG = y[i]*G[i]; |
|
|
|
if( is_lower_bound(i) ) |
|
{ |
|
if( y[i] > 0 ) |
|
ub = MIN(ub,yG); |
|
else |
|
lb = MAX(lb,yG); |
|
} |
|
else if( is_upper_bound(i) ) |
|
{ |
|
if( y[i] < 0) |
|
ub = MIN(ub,yG); |
|
else |
|
lb = MAX(lb,yG); |
|
} |
|
else |
|
{ |
|
++nr_free; |
|
sum_free += yG; |
|
} |
|
} |
|
|
|
rho = nr_free > 0 ? sum_free/nr_free : (ub + lb)*0.5; |
|
r = 0; |
|
} |
|
|
|
|
|
bool |
|
CvSVMSolver::select_working_set_nu_svm( int& out_i, int& out_j ) |
|
{ |
|
// return i,j which maximize -grad(f)^T d , under constraint |
|
// if alpha_i == C, d != +1 |
|
// if alpha_i == 0, d != -1 |
|
double Gmax1 = -DBL_MAX; // max { -grad(f)_i * d | y_i = +1, d = +1 } |
|
int Gmax1_idx = -1; |
|
|
|
double Gmax2 = -DBL_MAX; // max { -grad(f)_i * d | y_i = +1, d = -1 } |
|
int Gmax2_idx = -1; |
|
|
|
double Gmax3 = -DBL_MAX; // max { -grad(f)_i * d | y_i = -1, d = +1 } |
|
int Gmax3_idx = -1; |
|
|
|
double Gmax4 = -DBL_MAX; // max { -grad(f)_i * d | y_i = -1, d = -1 } |
|
int Gmax4_idx = -1; |
|
|
|
int i; |
|
|
|
for( i = 0; i < alpha_count; i++ ) |
|
{ |
|
double t; |
|
|
|
if( y[i] > 0 ) // y == +1 |
|
{ |
|
if( !is_upper_bound(i) && (t = -G[i]) > Gmax1 ) // d = +1 |
|
{ |
|
Gmax1 = t; |
|
Gmax1_idx = i; |
|
} |
|
if( !is_lower_bound(i) && (t = G[i]) > Gmax2 ) // d = -1 |
|
{ |
|
Gmax2 = t; |
|
Gmax2_idx = i; |
|
} |
|
} |
|
else // y == -1 |
|
{ |
|
if( !is_upper_bound(i) && (t = -G[i]) > Gmax3 ) // d = +1 |
|
{ |
|
Gmax3 = t; |
|
Gmax3_idx = i; |
|
} |
|
if( !is_lower_bound(i) && (t = G[i]) > Gmax4 ) // d = -1 |
|
{ |
|
Gmax4 = t; |
|
Gmax4_idx = i; |
|
} |
|
} |
|
} |
|
|
|
if( MAX(Gmax1 + Gmax2, Gmax3 + Gmax4) < eps ) |
|
return 1; |
|
|
|
if( Gmax1 + Gmax2 > Gmax3 + Gmax4 ) |
|
{ |
|
out_i = Gmax1_idx; |
|
out_j = Gmax2_idx; |
|
} |
|
else |
|
{ |
|
out_i = Gmax3_idx; |
|
out_j = Gmax4_idx; |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
void |
|
CvSVMSolver::calc_rho_nu_svm( double& rho, double& r ) |
|
{ |
|
int nr_free1 = 0, nr_free2 = 0; |
|
double ub1 = DBL_MAX, ub2 = DBL_MAX; |
|
double lb1 = -DBL_MAX, lb2 = -DBL_MAX; |
|
double sum_free1 = 0, sum_free2 = 0; |
|
double r1, r2; |
|
|
|
int i; |
|
|
|
for( i = 0; i < alpha_count; i++ ) |
|
{ |
|
double G_i = G[i]; |
|
if( y[i] > 0 ) |
|
{ |
|
if( is_lower_bound(i) ) |
|
ub1 = MIN( ub1, G_i ); |
|
else if( is_upper_bound(i) ) |
|
lb1 = MAX( lb1, G_i ); |
|
else |
|
{ |
|
++nr_free1; |
|
sum_free1 += G_i; |
|
} |
|
} |
|
else |
|
{ |
|
if( is_lower_bound(i) ) |
|
ub2 = MIN( ub2, G_i ); |
|
else if( is_upper_bound(i) ) |
|
lb2 = MAX( lb2, G_i ); |
|
else |
|
{ |
|
++nr_free2; |
|
sum_free2 += G_i; |
|
} |
|
} |
|
} |
|
|
|
r1 = nr_free1 > 0 ? sum_free1/nr_free1 : (ub1 + lb1)*0.5; |
|
r2 = nr_free2 > 0 ? sum_free2/nr_free2 : (ub2 + lb2)*0.5; |
|
|
|
rho = (r1 - r2)*0.5; |
|
r = (r1 + r2)*0.5; |
|
} |
|
|
|
|
|
/* |
|
///////////////////////// construct and solve various formulations /////////////////////// |
|
*/ |
|
|
|
bool CvSVMSolver::solve_c_svc( int _sample_count, int _var_count, const float** _samples, schar* _y, |
|
double _Cp, double _Cn, CvMemStorage* _storage, |
|
CvSVMKernel* _kernel, double* _alpha, CvSVMSolutionInfo& _si ) |
|
{ |
|
int i; |
|
|
|
if( !create( _sample_count, _var_count, _samples, _y, _sample_count, |
|
_alpha, _Cp, _Cn, _storage, _kernel, &CvSVMSolver::get_row_svc, |
|
&CvSVMSolver::select_working_set, &CvSVMSolver::calc_rho )) |
|
return false; |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
{ |
|
alpha[i] = 0; |
|
b[i] = -1; |
|
} |
|
|
|
if( !solve_generic( _si )) |
|
return false; |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
alpha[i] *= y[i]; |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CvSVMSolver::solve_nu_svc( int _sample_count, int _var_count, const float** _samples, schar* _y, |
|
CvMemStorage* _storage, CvSVMKernel* _kernel, |
|
double* _alpha, CvSVMSolutionInfo& _si ) |
|
{ |
|
int i; |
|
double sum_pos, sum_neg, inv_r; |
|
|
|
if( !create( _sample_count, _var_count, _samples, _y, _sample_count, |
|
_alpha, 1., 1., _storage, _kernel, &CvSVMSolver::get_row_svc, |
|
&CvSVMSolver::select_working_set_nu_svm, &CvSVMSolver::calc_rho_nu_svm )) |
|
return false; |
|
|
|
sum_pos = kernel->params->nu * sample_count * 0.5; |
|
sum_neg = kernel->params->nu * sample_count * 0.5; |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
{ |
|
if( y[i] > 0 ) |
|
{ |
|
alpha[i] = MIN(1.0, sum_pos); |
|
sum_pos -= alpha[i]; |
|
} |
|
else |
|
{ |
|
alpha[i] = MIN(1.0, sum_neg); |
|
sum_neg -= alpha[i]; |
|
} |
|
b[i] = 0; |
|
} |
|
|
|
if( !solve_generic( _si )) |
|
return false; |
|
|
|
inv_r = 1./_si.r; |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
alpha[i] *= y[i]*inv_r; |
|
|
|
_si.rho *= inv_r; |
|
_si.obj *= (inv_r*inv_r); |
|
_si.upper_bound_p = inv_r; |
|
_si.upper_bound_n = inv_r; |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CvSVMSolver::solve_one_class( int _sample_count, int _var_count, const float** _samples, |
|
CvMemStorage* _storage, CvSVMKernel* _kernel, |
|
double* _alpha, CvSVMSolutionInfo& _si ) |
|
{ |
|
int i, n; |
|
double nu = _kernel->params->nu; |
|
|
|
if( !create( _sample_count, _var_count, _samples, 0, _sample_count, |
|
_alpha, 1., 1., _storage, _kernel, &CvSVMSolver::get_row_one_class, |
|
&CvSVMSolver::select_working_set, &CvSVMSolver::calc_rho )) |
|
return false; |
|
|
|
y = (schar*)cvMemStorageAlloc( storage, sample_count*sizeof(y[0]) ); |
|
n = cvRound( nu*sample_count ); |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
{ |
|
y[i] = 1; |
|
b[i] = 0; |
|
alpha[i] = i < n ? 1 : 0; |
|
} |
|
|
|
if( n < sample_count ) |
|
alpha[n] = nu * sample_count - n; |
|
else |
|
alpha[n-1] = nu * sample_count - (n-1); |
|
|
|
return solve_generic(_si); |
|
} |
|
|
|
|
|
bool CvSVMSolver::solve_eps_svr( int _sample_count, int _var_count, const float** _samples, |
|
const float* _y, CvMemStorage* _storage, |
|
CvSVMKernel* _kernel, double* _alpha, CvSVMSolutionInfo& _si ) |
|
{ |
|
int i; |
|
double p = _kernel->params->p, kernel_param_c = _kernel->params->C; |
|
|
|
if( !create( _sample_count, _var_count, _samples, 0, |
|
_sample_count*2, 0, kernel_param_c, kernel_param_c, _storage, _kernel, &CvSVMSolver::get_row_svr, |
|
&CvSVMSolver::select_working_set, &CvSVMSolver::calc_rho )) |
|
return false; |
|
|
|
y = (schar*)cvMemStorageAlloc( storage, sample_count*2*sizeof(y[0]) ); |
|
alpha = (double*)cvMemStorageAlloc( storage, alpha_count*sizeof(alpha[0]) ); |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
{ |
|
alpha[i] = 0; |
|
b[i] = p - _y[i]; |
|
y[i] = 1; |
|
|
|
alpha[i+sample_count] = 0; |
|
b[i+sample_count] = p + _y[i]; |
|
y[i+sample_count] = -1; |
|
} |
|
|
|
if( !solve_generic( _si )) |
|
return false; |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
_alpha[i] = alpha[i] - alpha[i+sample_count]; |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CvSVMSolver::solve_nu_svr( int _sample_count, int _var_count, const float** _samples, |
|
const float* _y, CvMemStorage* _storage, |
|
CvSVMKernel* _kernel, double* _alpha, CvSVMSolutionInfo& _si ) |
|
{ |
|
int i; |
|
double kernel_param_c = _kernel->params->C, sum; |
|
|
|
if( !create( _sample_count, _var_count, _samples, 0, |
|
_sample_count*2, 0, 1., 1., _storage, _kernel, &CvSVMSolver::get_row_svr, |
|
&CvSVMSolver::select_working_set_nu_svm, &CvSVMSolver::calc_rho_nu_svm )) |
|
return false; |
|
|
|
y = (schar*)cvMemStorageAlloc( storage, sample_count*2*sizeof(y[0]) ); |
|
alpha = (double*)cvMemStorageAlloc( storage, alpha_count*sizeof(alpha[0]) ); |
|
sum = kernel_param_c * _kernel->params->nu * sample_count * 0.5; |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
{ |
|
alpha[i] = alpha[i + sample_count] = MIN(sum, kernel_param_c); |
|
sum -= alpha[i]; |
|
|
|
b[i] = -_y[i]; |
|
y[i] = 1; |
|
|
|
b[i + sample_count] = _y[i]; |
|
y[i + sample_count] = -1; |
|
} |
|
|
|
if( !solve_generic( _si )) |
|
return false; |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
_alpha[i] = alpha[i] - alpha[i+sample_count]; |
|
|
|
return true; |
|
} |
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
CvSVM::CvSVM() |
|
{ |
|
decision_func = 0; |
|
class_labels = 0; |
|
class_weights = 0; |
|
storage = 0; |
|
var_idx = 0; |
|
kernel = 0; |
|
solver = 0; |
|
default_model_name = "my_svm"; |
|
|
|
clear(); |
|
} |
|
|
|
|
|
CvSVM::~CvSVM() |
|
{ |
|
clear(); |
|
} |
|
|
|
|
|
void CvSVM::clear() |
|
{ |
|
cvFree( &decision_func ); |
|
cvReleaseMat( &class_labels ); |
|
cvReleaseMat( &class_weights ); |
|
cvReleaseMemStorage( &storage ); |
|
cvReleaseMat( &var_idx ); |
|
delete kernel; |
|
delete solver; |
|
kernel = 0; |
|
solver = 0; |
|
var_all = 0; |
|
sv = 0; |
|
sv_total = 0; |
|
} |
|
|
|
|
|
CvSVM::CvSVM( const CvMat* _train_data, const CvMat* _responses, |
|
const CvMat* _var_idx, const CvMat* _sample_idx, CvSVMParams _params ) |
|
{ |
|
decision_func = 0; |
|
class_labels = 0; |
|
class_weights = 0; |
|
storage = 0; |
|
var_idx = 0; |
|
kernel = 0; |
|
solver = 0; |
|
default_model_name = "my_svm"; |
|
|
|
train( _train_data, _responses, _var_idx, _sample_idx, _params ); |
|
} |
|
|
|
|
|
int CvSVM::get_support_vector_count() const |
|
{ |
|
return sv_total; |
|
} |
|
|
|
|
|
const float* CvSVM::get_support_vector(int i) const |
|
{ |
|
return sv && (unsigned)i < (unsigned)sv_total ? sv[i] : 0; |
|
} |
|
|
|
|
|
bool CvSVM::set_params( const CvSVMParams& _params ) |
|
{ |
|
bool ok = false; |
|
|
|
CV_FUNCNAME( "CvSVM::set_params" ); |
|
|
|
__BEGIN__; |
|
|
|
int kernel_type, svm_type; |
|
|
|
params = _params; |
|
|
|
kernel_type = params.kernel_type; |
|
svm_type = params.svm_type; |
|
|
|
if( kernel_type != LINEAR && kernel_type != POLY && |
|
kernel_type != SIGMOID && kernel_type != RBF && |
|
kernel_type != INTER && kernel_type != CHI2) |
|
CV_ERROR( CV_StsBadArg, "Unknown/unsupported kernel type" ); |
|
|
|
if( kernel_type == LINEAR ) |
|
params.gamma = 1; |
|
else if( params.gamma <= 0 ) |
|
CV_ERROR( CV_StsOutOfRange, "gamma parameter of the kernel must be positive" ); |
|
|
|
if( kernel_type != SIGMOID && kernel_type != POLY ) |
|
params.coef0 = 0; |
|
else if( params.coef0 < 0 ) |
|
CV_ERROR( CV_StsOutOfRange, "The kernel parameter <coef0> must be positive or zero" ); |
|
|
|
if( kernel_type != POLY ) |
|
params.degree = 0; |
|
else if( params.degree <= 0 ) |
|
CV_ERROR( CV_StsOutOfRange, "The kernel parameter <degree> must be positive" ); |
|
|
|
if( svm_type != C_SVC && svm_type != NU_SVC && |
|
svm_type != ONE_CLASS && svm_type != EPS_SVR && |
|
svm_type != NU_SVR ) |
|
CV_ERROR( CV_StsBadArg, "Unknown/unsupported SVM type" ); |
|
|
|
if( svm_type == ONE_CLASS || svm_type == NU_SVC ) |
|
params.C = 0; |
|
else if( params.C <= 0 ) |
|
CV_ERROR( CV_StsOutOfRange, "The parameter C must be positive" ); |
|
|
|
if( svm_type == C_SVC || svm_type == EPS_SVR ) |
|
params.nu = 0; |
|
else if( params.nu <= 0 || params.nu >= 1 ) |
|
CV_ERROR( CV_StsOutOfRange, "The parameter nu must be between 0 and 1" ); |
|
|
|
if( svm_type != EPS_SVR ) |
|
params.p = 0; |
|
else if( params.p <= 0 ) |
|
CV_ERROR( CV_StsOutOfRange, "The parameter p must be positive" ); |
|
|
|
if( svm_type != C_SVC ) |
|
params.class_weights = 0; |
|
|
|
params.term_crit = cvCheckTermCriteria( params.term_crit, DBL_EPSILON, INT_MAX ); |
|
params.term_crit.epsilon = MAX( params.term_crit.epsilon, DBL_EPSILON ); |
|
ok = true; |
|
|
|
__END__; |
|
|
|
return ok; |
|
} |
|
|
|
|
|
|
|
void CvSVM::create_kernel() |
|
{ |
|
kernel = new CvSVMKernel(¶ms,0); |
|
} |
|
|
|
|
|
void CvSVM::create_solver( ) |
|
{ |
|
solver = new CvSVMSolver; |
|
} |
|
|
|
|
|
// switching function |
|
bool CvSVM::train1( int sample_count, int var_count, const float** samples, |
|
const void* _responses, double Cp, double Cn, |
|
CvMemStorage* _storage, double* alpha, double& rho ) |
|
{ |
|
bool ok = false; |
|
|
|
//CV_FUNCNAME( "CvSVM::train1" ); |
|
|
|
__BEGIN__; |
|
|
|
CvSVMSolutionInfo si; |
|
int svm_type = params.svm_type; |
|
|
|
si.rho = 0; |
|
|
|
ok = svm_type == C_SVC ? solver->solve_c_svc( sample_count, var_count, samples, (schar*)_responses, |
|
Cp, Cn, _storage, kernel, alpha, si ) : |
|
svm_type == NU_SVC ? solver->solve_nu_svc( sample_count, var_count, samples, (schar*)_responses, |
|
_storage, kernel, alpha, si ) : |
|
svm_type == ONE_CLASS ? solver->solve_one_class( sample_count, var_count, samples, |
|
_storage, kernel, alpha, si ) : |
|
svm_type == EPS_SVR ? solver->solve_eps_svr( sample_count, var_count, samples, (float*)_responses, |
|
_storage, kernel, alpha, si ) : |
|
svm_type == NU_SVR ? solver->solve_nu_svr( sample_count, var_count, samples, (float*)_responses, |
|
_storage, kernel, alpha, si ) : false; |
|
|
|
rho = si.rho; |
|
|
|
__END__; |
|
|
|
return ok; |
|
} |
|
|
|
|
|
bool CvSVM::do_train( int svm_type, int sample_count, int var_count, const float** samples, |
|
const CvMat* responses, CvMemStorage* temp_storage, double* alpha ) |
|
{ |
|
bool ok = false; |
|
|
|
CV_FUNCNAME( "CvSVM::do_train" ); |
|
|
|
__BEGIN__; |
|
|
|
CvSVMDecisionFunc* df = 0; |
|
const int sample_size = var_count*sizeof(samples[0][0]); |
|
int i, j, k; |
|
|
|
cvClearMemStorage( storage ); |
|
|
|
if( svm_type == ONE_CLASS || svm_type == EPS_SVR || svm_type == NU_SVR ) |
|
{ |
|
int sv_count = 0; |
|
|
|
CV_CALL( decision_func = df = |
|
(CvSVMDecisionFunc*)cvAlloc( sizeof(df[0]) )); |
|
|
|
df->rho = 0; |
|
if( !train1( sample_count, var_count, samples, svm_type == ONE_CLASS ? 0 : |
|
responses->data.i, 0, 0, temp_storage, alpha, df->rho )) |
|
EXIT; |
|
|
|
for( i = 0; i < sample_count; i++ ) |
|
sv_count += fabs(alpha[i]) > 0; |
|
|
|
sv_total = df->sv_count = sv_count; |
|
CV_CALL( df->alpha = (double*)cvMemStorageAlloc( storage, sv_count*sizeof(df->alpha[0])) ); |
|
CV_CALL( sv = (float**)cvMemStorageAlloc( storage, sv_count*sizeof(sv[0]))); |
|
|
|
for( i = k = 0; i < sample_count; i++ ) |
|
{ |
|
if( fabs(alpha[i]) > 0 ) |
|
{ |
|
CV_CALL( sv[k] = (float*)cvMemStorageAlloc( storage, sample_size )); |
|
memcpy( sv[k], samples[i], sample_size ); |
|
df->alpha[k++] = alpha[i]; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
int class_count = class_labels->cols; |
|
int* sv_tab = 0; |
|
const float** temp_samples = 0; |
|
int* class_ranges = 0; |
|
schar* temp_y = 0; |
|
assert( svm_type == CvSVM::C_SVC || svm_type == CvSVM::NU_SVC ); |
|
|
|
if( svm_type == CvSVM::C_SVC && params.class_weights ) |
|
{ |
|
const CvMat* cw = params.class_weights; |
|
|
|
if( !CV_IS_MAT(cw) || (cw->cols != 1 && cw->rows != 1) || |
|
cw->rows + cw->cols - 1 != class_count || |
|
(CV_MAT_TYPE(cw->type) != CV_32FC1 && CV_MAT_TYPE(cw->type) != CV_64FC1) ) |
|
CV_ERROR( CV_StsBadArg, "params.class_weights must be 1d floating-point vector " |
|
"containing as many elements as the number of classes" ); |
|
|
|
CV_CALL( class_weights = cvCreateMat( cw->rows, cw->cols, CV_64F )); |
|
CV_CALL( cvConvert( cw, class_weights )); |
|
CV_CALL( cvScale( class_weights, class_weights, params.C )); |
|
} |
|
|
|
CV_CALL( decision_func = df = (CvSVMDecisionFunc*)cvAlloc( |
|
(class_count*(class_count-1)/2)*sizeof(df[0]))); |
|
|
|
CV_CALL( sv_tab = (int*)cvMemStorageAlloc( temp_storage, sample_count*sizeof(sv_tab[0]) )); |
|
memset( sv_tab, 0, sample_count*sizeof(sv_tab[0]) ); |
|
CV_CALL( class_ranges = (int*)cvMemStorageAlloc( temp_storage, |
|
(class_count + 1)*sizeof(class_ranges[0]))); |
|
CV_CALL( temp_samples = (const float**)cvMemStorageAlloc( temp_storage, |
|
sample_count*sizeof(temp_samples[0]))); |
|
CV_CALL( temp_y = (schar*)cvMemStorageAlloc( temp_storage, sample_count)); |
|
|
|
class_ranges[class_count] = 0; |
|
cvSortSamplesByClasses( samples, responses, class_ranges, 0 ); |
|
//check that while cross-validation there were the samples from all the classes |
|
if( class_ranges[class_count] <= 0 ) |
|
CV_ERROR( CV_StsBadArg, "While cross-validation one or more of the classes have " |
|
"been fell out of the sample. Try to enlarge <CvSVMParams::k_fold>" ); |
|
|
|
if( svm_type == NU_SVC ) |
|
{ |
|
// check if nu is feasible |
|
for(i = 0; i < class_count; i++ ) |
|
{ |
|
int ci = class_ranges[i+1] - class_ranges[i]; |
|
for( j = i+1; j< class_count; j++ ) |
|
{ |
|
int cj = class_ranges[j+1] - class_ranges[j]; |
|
if( params.nu*(ci + cj)*0.5 > MIN( ci, cj ) ) |
|
{ |
|
// !!!TODO!!! add some diagnostic |
|
EXIT; // exit immediately; will release the model and return NULL pointer |
|
} |
|
} |
|
} |
|
} |
|
|
|
// train n*(n-1)/2 classifiers |
|
for( i = 0; i < class_count; i++ ) |
|
{ |
|
for( j = i+1; j < class_count; j++, df++ ) |
|
{ |
|
int si = class_ranges[i], ci = class_ranges[i+1] - si; |
|
int sj = class_ranges[j], cj = class_ranges[j+1] - sj; |
|
double Cp = params.C, Cn = Cp; |
|
int k1 = 0, sv_count = 0; |
|
|
|
for( k = 0; k < ci; k++ ) |
|
{ |
|
temp_samples[k] = samples[si + k]; |
|
temp_y[k] = 1; |
|
} |
|
|
|
for( k = 0; k < cj; k++ ) |
|
{ |
|
temp_samples[ci + k] = samples[sj + k]; |
|
temp_y[ci + k] = -1; |
|
} |
|
|
|
if( class_weights ) |
|
{ |
|
Cp = class_weights->data.db[i]; |
|
Cn = class_weights->data.db[j]; |
|
} |
|
|
|
if( !train1( ci + cj, var_count, temp_samples, temp_y, |
|
Cp, Cn, temp_storage, alpha, df->rho )) |
|
EXIT; |
|
|
|
for( k = 0; k < ci + cj; k++ ) |
|
sv_count += fabs(alpha[k]) > 0; |
|
|
|
df->sv_count = sv_count; |
|
|
|
CV_CALL( df->alpha = (double*)cvMemStorageAlloc( temp_storage, |
|
sv_count*sizeof(df->alpha[0]))); |
|
CV_CALL( df->sv_index = (int*)cvMemStorageAlloc( temp_storage, |
|
sv_count*sizeof(df->sv_index[0]))); |
|
|
|
for( k = 0; k < ci; k++ ) |
|
{ |
|
if( fabs(alpha[k]) > 0 ) |
|
{ |
|
sv_tab[si + k] = 1; |
|
df->sv_index[k1] = si + k; |
|
df->alpha[k1++] = alpha[k]; |
|
} |
|
} |
|
|
|
for( k = 0; k < cj; k++ ) |
|
{ |
|
if( fabs(alpha[ci + k]) > 0 ) |
|
{ |
|
sv_tab[sj + k] = 1; |
|
df->sv_index[k1] = sj + k; |
|
df->alpha[k1++] = alpha[ci + k]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// allocate support vectors and initialize sv_tab |
|
for( i = 0, k = 0; i < sample_count; i++ ) |
|
{ |
|
if( sv_tab[i] ) |
|
sv_tab[i] = ++k; |
|
} |
|
|
|
sv_total = k; |
|
CV_CALL( sv = (float**)cvMemStorageAlloc( storage, sv_total*sizeof(sv[0]))); |
|
|
|
for( i = 0, k = 0; i < sample_count; i++ ) |
|
{ |
|
if( sv_tab[i] ) |
|
{ |
|
CV_CALL( sv[k] = (float*)cvMemStorageAlloc( storage, sample_size )); |
|
memcpy( sv[k], samples[i], sample_size ); |
|
k++; |
|
} |
|
} |
|
|
|
df = (CvSVMDecisionFunc*)decision_func; |
|
|
|
// set sv pointers |
|
for( i = 0; i < class_count; i++ ) |
|
{ |
|
for( j = i+1; j < class_count; j++, df++ ) |
|
{ |
|
for( k = 0; k < df->sv_count; k++ ) |
|
{ |
|
df->sv_index[k] = sv_tab[df->sv_index[k]]-1; |
|
assert( (unsigned)df->sv_index[k] < (unsigned)sv_total ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
optimize_linear_svm(); |
|
ok = true; |
|
|
|
__END__; |
|
|
|
return ok; |
|
} |
|
|
|
|
|
void CvSVM::optimize_linear_svm() |
|
{ |
|
// we optimize only linear SVM: compress all the support vectors into one. |
|
if( params.kernel_type != LINEAR ) |
|
return; |
|
|
|
int class_count = class_labels ? class_labels->cols : |
|
params.svm_type == CvSVM::ONE_CLASS ? 1 : 0; |
|
|
|
int i, df_count = class_count > 1 ? class_count*(class_count-1)/2 : 1; |
|
CvSVMDecisionFunc* df = decision_func; |
|
|
|
for( i = 0; i < df_count; i++ ) |
|
{ |
|
int sv_count = df[i].sv_count; |
|
if( sv_count != 1 ) |
|
break; |
|
} |
|
|
|
// if every decision functions uses a single support vector; |
|
// it's already compressed. skip it then. |
|
if( i == df_count ) |
|
return; |
|
|
|
int var_count = get_var_count(); |
|
cv::AutoBuffer<double> vbuf(var_count); |
|
double* v = vbuf; |
|
float** new_sv = (float**)cvMemStorageAlloc(storage, df_count*sizeof(new_sv[0])); |
|
|
|
for( i = 0; i < df_count; i++ ) |
|
{ |
|
new_sv[i] = (float*)cvMemStorageAlloc(storage, var_count*sizeof(new_sv[i][0])); |
|
float* dst = new_sv[i]; |
|
memset(v, 0, var_count*sizeof(v[0])); |
|
int j, k, sv_count = df[i].sv_count; |
|
for( j = 0; j < sv_count; j++ ) |
|
{ |
|
const float* src = class_count > 1 && df[i].sv_index ? sv[df[i].sv_index[j]] : sv[j]; |
|
double a = df[i].alpha[j]; |
|
for( k = 0; k < var_count; k++ ) |
|
v[k] += src[k]*a; |
|
} |
|
for( k = 0; k < var_count; k++ ) |
|
dst[k] = (float)v[k]; |
|
df[i].sv_count = 1; |
|
df[i].alpha[0] = 1.; |
|
if( class_count > 1 && df[i].sv_index ) |
|
df[i].sv_index[0] = i; |
|
} |
|
|
|
sv = new_sv; |
|
sv_total = df_count; |
|
} |
|
|
|
|
|
bool CvSVM::train( const CvMat* _train_data, const CvMat* _responses, |
|
const CvMat* _var_idx, const CvMat* _sample_idx, CvSVMParams _params ) |
|
{ |
|
bool ok = false; |
|
CvMat* responses = 0; |
|
CvMemStorage* temp_storage = 0; |
|
const float** samples = 0; |
|
|
|
CV_FUNCNAME( "CvSVM::train" ); |
|
|
|
__BEGIN__; |
|
|
|
int svm_type, sample_count, var_count, sample_size; |
|
int block_size = 1 << 16; |
|
double* alpha; |
|
|
|
clear(); |
|
CV_CALL( set_params( _params )); |
|
|
|
svm_type = _params.svm_type; |
|
|
|
/* Prepare training data and related parameters */ |
|
CV_CALL( cvPrepareTrainData( "CvSVM::train", _train_data, CV_ROW_SAMPLE, |
|
svm_type != CvSVM::ONE_CLASS ? _responses : 0, |
|
svm_type == CvSVM::C_SVC || |
|
svm_type == CvSVM::NU_SVC ? CV_VAR_CATEGORICAL : |
|
CV_VAR_ORDERED, _var_idx, _sample_idx, |
|
false, &samples, &sample_count, &var_count, &var_all, |
|
&responses, &class_labels, &var_idx )); |
|
|
|
|
|
sample_size = var_count*sizeof(samples[0][0]); |
|
|
|
// make the storage block size large enough to fit all |
|
// the temporary vectors and output support vectors. |
|
block_size = MAX( block_size, sample_count*(int)sizeof(CvSVMKernelRow)); |
|
block_size = MAX( block_size, sample_count*2*(int)sizeof(double) + 1024 ); |
|
block_size = MAX( block_size, sample_size*2 + 1024 ); |
|
|
|
CV_CALL( storage = cvCreateMemStorage(block_size + sizeof(CvMemBlock) + sizeof(CvSeqBlock))); |
|
CV_CALL( temp_storage = cvCreateChildMemStorage(storage)); |
|
CV_CALL( alpha = (double*)cvMemStorageAlloc(temp_storage, sample_count*sizeof(double))); |
|
|
|
create_kernel(); |
|
create_solver(); |
|
|
|
if( !do_train( svm_type, sample_count, var_count, samples, responses, temp_storage, alpha )) |
|
EXIT; |
|
|
|
ok = true; // model has been trained succesfully |
|
|
|
__END__; |
|
|
|
delete solver; |
|
solver = 0; |
|
cvReleaseMemStorage( &temp_storage ); |
|
cvReleaseMat( &responses ); |
|
cvFree( &samples ); |
|
|
|
if( cvGetErrStatus() < 0 || !ok ) |
|
clear(); |
|
|
|
return ok; |
|
} |
|
|
|
struct indexedratio |
|
{ |
|
double val; |
|
int ind; |
|
int count_smallest, count_biggest; |
|
void eval() { val = (double) count_smallest/(count_smallest+count_biggest); } |
|
}; |
|
|
|
static int CV_CDECL |
|
icvCmpIndexedratio( const void* a, const void* b ) |
|
{ |
|
return ((const indexedratio*)a)->val < ((const indexedratio*)b)->val ? -1 |
|
: ((const indexedratio*)a)->val > ((const indexedratio*)b)->val ? 1 |
|
: 0; |
|
} |
|
|
|
bool CvSVM::train_auto( const CvMat* _train_data, const CvMat* _responses, |
|
const CvMat* _var_idx, const CvMat* _sample_idx, CvSVMParams _params, int k_fold, |
|
CvParamGrid C_grid, CvParamGrid gamma_grid, CvParamGrid p_grid, |
|
CvParamGrid nu_grid, CvParamGrid coef_grid, CvParamGrid degree_grid, |
|
bool balanced) |
|
{ |
|
bool ok = false; |
|
CvMat* responses = 0; |
|
CvMat* responses_local = 0; |
|
CvMemStorage* temp_storage = 0; |
|
const float** samples = 0; |
|
const float** samples_local = 0; |
|
|
|
CV_FUNCNAME( "CvSVM::train_auto" ); |
|
__BEGIN__; |
|
|
|
int svm_type, sample_count, var_count, sample_size; |
|
int block_size = 1 << 16; |
|
double* alpha; |
|
RNG* rng = &theRNG(); |
|
|
|
// all steps are logarithmic and must be > 1 |
|
double degree_step = 10, g_step = 10, coef_step = 10, C_step = 10, nu_step = 10, p_step = 10; |
|
double gamma = 0, curr_c = 0, degree = 0, coef = 0, p = 0, nu = 0; |
|
double best_degree = 0, best_gamma = 0, best_coef = 0, best_C = 0, best_nu = 0, best_p = 0; |
|
float min_error = FLT_MAX, error; |
|
|
|
if( _params.svm_type == CvSVM::ONE_CLASS ) |
|
{ |
|
if(!train( _train_data, _responses, _var_idx, _sample_idx, _params )) |
|
EXIT; |
|
return true; |
|
} |
|
|
|
clear(); |
|
|
|
if( k_fold < 2 ) |
|
CV_ERROR( CV_StsBadArg, "Parameter <k_fold> must be > 1" ); |
|
|
|
CV_CALL(set_params( _params )); |
|
svm_type = _params.svm_type; |
|
|
|
// All the parameters except, possibly, <coef0> are positive. |
|
// <coef0> is nonnegative |
|
if( C_grid.step <= 1 ) |
|
{ |
|
C_grid.min_val = C_grid.max_val = params.C; |
|
C_grid.step = 10; |
|
} |
|
else |
|
CV_CALL(C_grid.check()); |
|
|
|
if( gamma_grid.step <= 1 ) |
|
{ |
|
gamma_grid.min_val = gamma_grid.max_val = params.gamma; |
|
gamma_grid.step = 10; |
|
} |
|
else |
|
CV_CALL(gamma_grid.check()); |
|
|
|
if( p_grid.step <= 1 ) |
|
{ |
|
p_grid.min_val = p_grid.max_val = params.p; |
|
p_grid.step = 10; |
|
} |
|
else |
|
CV_CALL(p_grid.check()); |
|
|
|
if( nu_grid.step <= 1 ) |
|
{ |
|
nu_grid.min_val = nu_grid.max_val = params.nu; |
|
nu_grid.step = 10; |
|
} |
|
else |
|
CV_CALL(nu_grid.check()); |
|
|
|
if( coef_grid.step <= 1 ) |
|
{ |
|
coef_grid.min_val = coef_grid.max_val = params.coef0; |
|
coef_grid.step = 10; |
|
} |
|
else |
|
CV_CALL(coef_grid.check()); |
|
|
|
if( degree_grid.step <= 1 ) |
|
{ |
|
degree_grid.min_val = degree_grid.max_val = params.degree; |
|
degree_grid.step = 10; |
|
} |
|
else |
|
CV_CALL(degree_grid.check()); |
|
|
|
// these parameters are not used: |
|
if( params.kernel_type != CvSVM::POLY ) |
|
degree_grid.min_val = degree_grid.max_val = params.degree; |
|
if( params.kernel_type == CvSVM::LINEAR ) |
|
gamma_grid.min_val = gamma_grid.max_val = params.gamma; |
|
if( params.kernel_type != CvSVM::POLY && params.kernel_type != CvSVM::SIGMOID ) |
|
coef_grid.min_val = coef_grid.max_val = params.coef0; |
|
if( svm_type == CvSVM::NU_SVC || svm_type == CvSVM::ONE_CLASS ) |
|
C_grid.min_val = C_grid.max_val = params.C; |
|
if( svm_type == CvSVM::C_SVC || svm_type == CvSVM::EPS_SVR ) |
|
nu_grid.min_val = nu_grid.max_val = params.nu; |
|
if( svm_type != CvSVM::EPS_SVR ) |
|
p_grid.min_val = p_grid.max_val = params.p; |
|
|
|
CV_ASSERT( g_step > 1 && degree_step > 1 && coef_step > 1); |
|
CV_ASSERT( p_step > 1 && C_step > 1 && nu_step > 1 ); |
|
|
|
/* Prepare training data and related parameters */ |
|
CV_CALL(cvPrepareTrainData( "CvSVM::train_auto", _train_data, CV_ROW_SAMPLE, |
|
svm_type != CvSVM::ONE_CLASS ? _responses : 0, |
|
svm_type == CvSVM::C_SVC || |
|
svm_type == CvSVM::NU_SVC ? CV_VAR_CATEGORICAL : |
|
CV_VAR_ORDERED, _var_idx, _sample_idx, |
|
false, &samples, &sample_count, &var_count, &var_all, |
|
&responses, &class_labels, &var_idx )); |
|
|
|
sample_size = var_count*sizeof(samples[0][0]); |
|
|
|
// make the storage block size large enough to fit all |
|
// the temporary vectors and output support vectors. |
|
block_size = MAX( block_size, sample_count*(int)sizeof(CvSVMKernelRow)); |
|
block_size = MAX( block_size, sample_count*2*(int)sizeof(double) + 1024 ); |
|
block_size = MAX( block_size, sample_size*2 + 1024 ); |
|
|
|
CV_CALL( storage = cvCreateMemStorage(block_size + sizeof(CvMemBlock) + sizeof(CvSeqBlock))); |
|
CV_CALL(temp_storage = cvCreateChildMemStorage(storage)); |
|
CV_CALL(alpha = (double*)cvMemStorageAlloc(temp_storage, sample_count*sizeof(double))); |
|
|
|
create_kernel(); |
|
create_solver(); |
|
|
|
{ |
|
const int testset_size = sample_count/k_fold; |
|
const int trainset_size = sample_count - testset_size; |
|
const int last_testset_size = sample_count - testset_size*(k_fold-1); |
|
const int last_trainset_size = sample_count - last_testset_size; |
|
const bool is_regression = (svm_type == EPS_SVR) || (svm_type == NU_SVR); |
|
|
|
size_t resp_elem_size = CV_ELEM_SIZE(responses->type); |
|
size_t size = 2*last_trainset_size*sizeof(samples[0]); |
|
|
|
samples_local = (const float**) cvAlloc( size ); |
|
memset( samples_local, 0, size ); |
|
|
|
responses_local = cvCreateMat( 1, trainset_size, CV_MAT_TYPE(responses->type) ); |
|
cvZero( responses_local ); |
|
|
|
// randomly permute samples and responses |
|
for(int i = 0; i < sample_count; i++ ) |
|
{ |
|
int i1 = (*rng)(sample_count); |
|
int i2 = (*rng)(sample_count); |
|
const float* temp; |
|
float t; |
|
int y; |
|
|
|
CV_SWAP( samples[i1], samples[i2], temp ); |
|
if( is_regression ) |
|
CV_SWAP( responses->data.fl[i1], responses->data.fl[i2], t ); |
|
else |
|
CV_SWAP( responses->data.i[i1], responses->data.i[i2], y ); |
|
} |
|
|
|
if (!is_regression && class_labels->cols==2 && balanced) |
|
{ |
|
// count class samples |
|
int num_0=0,num_1=0; |
|
for (int i=0; i<sample_count; ++i) |
|
{ |
|
if (responses->data.i[i]==class_labels->data.i[0]) |
|
++num_0; |
|
else |
|
++num_1; |
|
} |
|
|
|
int label_smallest_class; |
|
int label_biggest_class; |
|
if (num_0 < num_1) |
|
{ |
|
label_biggest_class = class_labels->data.i[1]; |
|
label_smallest_class = class_labels->data.i[0]; |
|
} |
|
else |
|
{ |
|
label_biggest_class = class_labels->data.i[0]; |
|
label_smallest_class = class_labels->data.i[1]; |
|
int y; |
|
CV_SWAP(num_0,num_1,y); |
|
} |
|
const double class_ratio = (double) num_0/sample_count; |
|
// calculate class ratio of each fold |
|
indexedratio *ratios=0; |
|
ratios = (indexedratio*) cvAlloc(k_fold*sizeof(*ratios)); |
|
for (int k=0, i_begin=0; k<k_fold; ++k, i_begin+=testset_size) |
|
{ |
|
int count0=0; |
|
int count1=0; |
|
int i_end = i_begin + (k<k_fold-1 ? testset_size : last_testset_size); |
|
for (int i=i_begin; i<i_end; ++i) |
|
{ |
|
if (responses->data.i[i]==label_smallest_class) |
|
++count0; |
|
else |
|
++count1; |
|
} |
|
ratios[k].ind = k; |
|
ratios[k].count_smallest = count0; |
|
ratios[k].count_biggest = count1; |
|
ratios[k].eval(); |
|
} |
|
// initial distance |
|
qsort(ratios, k_fold, sizeof(ratios[0]), icvCmpIndexedratio); |
|
double old_dist = 0.0; |
|
for (int k=0; k<k_fold; ++k) |
|
old_dist += cv::abs(ratios[k].val-class_ratio); |
|
double new_dist = 1.0; |
|
// iterate to make the folds more balanced |
|
while (new_dist > 0.0) |
|
{ |
|
if (ratios[0].count_biggest==0 || ratios[k_fold-1].count_smallest==0) |
|
break; // we are not able to swap samples anymore |
|
// what if we swap the samples, calculate the new distance |
|
ratios[0].count_smallest++; |
|
ratios[0].count_biggest--; |
|
ratios[0].eval(); |
|
ratios[k_fold-1].count_smallest--; |
|
ratios[k_fold-1].count_biggest++; |
|
ratios[k_fold-1].eval(); |
|
qsort(ratios, k_fold, sizeof(ratios[0]), icvCmpIndexedratio); |
|
new_dist = 0.0; |
|
for (int k=0; k<k_fold; ++k) |
|
new_dist += cv::abs(ratios[k].val-class_ratio); |
|
if (new_dist < old_dist) |
|
{ |
|
// swapping really improves, so swap the samples |
|
// index of the biggest_class sample from the minimum ratio fold |
|
int i1 = ratios[0].ind * testset_size; |
|
for ( ; i1<sample_count; ++i1) |
|
{ |
|
if (responses->data.i[i1]==label_biggest_class) |
|
break; |
|
} |
|
// index of the smallest_class sample from the maximum ratio fold |
|
int i2 = ratios[k_fold-1].ind * testset_size; |
|
for ( ; i2<sample_count; ++i2) |
|
{ |
|
if (responses->data.i[i2]==label_smallest_class) |
|
break; |
|
} |
|
// swap |
|
const float* temp; |
|
int y; |
|
CV_SWAP( samples[i1], samples[i2], temp ); |
|
CV_SWAP( responses->data.i[i1], responses->data.i[i2], y ); |
|
old_dist = new_dist; |
|
} |
|
else |
|
break; // does not improve, so break the loop |
|
} |
|
cvFree(&ratios); |
|
} |
|
|
|
int* cls_lbls = class_labels ? class_labels->data.i : 0; |
|
curr_c = C_grid.min_val; |
|
do |
|
{ |
|
params.C = curr_c; |
|
gamma = gamma_grid.min_val; |
|
do |
|
{ |
|
params.gamma = gamma; |
|
p = p_grid.min_val; |
|
do |
|
{ |
|
params.p = p; |
|
nu = nu_grid.min_val; |
|
do |
|
{ |
|
params.nu = nu; |
|
coef = coef_grid.min_val; |
|
do |
|
{ |
|
params.coef0 = coef; |
|
degree = degree_grid.min_val; |
|
do |
|
{ |
|
params.degree = degree; |
|
|
|
float** test_samples_ptr = (float**)samples; |
|
uchar* true_resp = responses->data.ptr; |
|
int test_size = testset_size; |
|
int train_size = trainset_size; |
|
|
|
error = 0; |
|
for(int k = 0; k < k_fold; k++ ) |
|
{ |
|
memcpy( samples_local, samples, sizeof(samples[0])*test_size*k ); |
|
memcpy( samples_local + test_size*k, test_samples_ptr + test_size, |
|
sizeof(samples[0])*(sample_count - testset_size*(k+1)) ); |
|
|
|
memcpy( responses_local->data.ptr, responses->data.ptr, resp_elem_size*test_size*k ); |
|
memcpy( responses_local->data.ptr + resp_elem_size*test_size*k, |
|
true_resp + resp_elem_size*test_size, |
|
resp_elem_size*(sample_count - testset_size*(k+1)) ); |
|
|
|
if( k == k_fold - 1 ) |
|
{ |
|
test_size = last_testset_size; |
|
train_size = last_trainset_size; |
|
responses_local->cols = last_trainset_size; |
|
} |
|
|
|
// Train SVM on <train_size> samples |
|
if( !do_train( svm_type, train_size, var_count, |
|
(const float**)samples_local, responses_local, temp_storage, alpha ) ) |
|
EXIT; |
|
|
|
// Compute test set error on <test_size> samples |
|
for(int i = 0; i < test_size; i++, true_resp += resp_elem_size, test_samples_ptr++ ) |
|
{ |
|
float resp = predict( *test_samples_ptr, var_count ); |
|
error += is_regression ? powf( resp - *(float*)true_resp, 2 ) |
|
: ((int)resp != cls_lbls[*(int*)true_resp]); |
|
} |
|
} |
|
if( min_error > error ) |
|
{ |
|
min_error = error; |
|
best_degree = degree; |
|
best_gamma = gamma; |
|
best_coef = coef; |
|
best_C = curr_c; |
|
best_nu = nu; |
|
best_p = p; |
|
} |
|
degree *= degree_grid.step; |
|
} |
|
while( degree < degree_grid.max_val ); |
|
coef *= coef_grid.step; |
|
} |
|
while( coef < coef_grid.max_val ); |
|
nu *= nu_grid.step; |
|
} |
|
while( nu < nu_grid.max_val ); |
|
p *= p_grid.step; |
|
} |
|
while( p < p_grid.max_val ); |
|
gamma *= gamma_grid.step; |
|
} |
|
while( gamma < gamma_grid.max_val ); |
|
curr_c *= C_grid.step; |
|
} |
|
while( curr_c < C_grid.max_val ); |
|
} |
|
|
|
min_error /= (float) sample_count; |
|
|
|
params.C = best_C; |
|
params.nu = best_nu; |
|
params.p = best_p; |
|
params.gamma = best_gamma; |
|
params.degree = best_degree; |
|
params.coef0 = best_coef; |
|
|
|
CV_CALL(ok = do_train( svm_type, sample_count, var_count, samples, responses, temp_storage, alpha )); |
|
|
|
__END__; |
|
|
|
delete solver; |
|
solver = 0; |
|
cvReleaseMemStorage( &temp_storage ); |
|
cvReleaseMat( &responses ); |
|
cvReleaseMat( &responses_local ); |
|
cvFree( &samples ); |
|
cvFree( &samples_local ); |
|
|
|
if( cvGetErrStatus() < 0 || !ok ) |
|
clear(); |
|
|
|
return ok; |
|
} |
|
|
|
float CvSVM::predict( const float* row_sample, int row_len, bool returnDFVal ) const |
|
{ |
|
assert( kernel ); |
|
assert( row_sample ); |
|
|
|
int var_count = get_var_count(); |
|
assert( row_len == var_count ); |
|
(void)row_len; |
|
|
|
int class_count = class_labels ? class_labels->cols : |
|
params.svm_type == ONE_CLASS ? 1 : 0; |
|
|
|
float result = 0; |
|
cv::AutoBuffer<float> _buffer(sv_total + (class_count+1)*2); |
|
float* buffer = _buffer; |
|
|
|
if( params.svm_type == EPS_SVR || |
|
params.svm_type == NU_SVR || |
|
params.svm_type == ONE_CLASS ) |
|
{ |
|
CvSVMDecisionFunc* df = (CvSVMDecisionFunc*)decision_func; |
|
int i, sv_count = df->sv_count; |
|
double sum = -df->rho; |
|
|
|
kernel->calc( sv_count, var_count, (const float**)sv, row_sample, buffer ); |
|
for( i = 0; i < sv_count; i++ ) |
|
sum += buffer[i]*df->alpha[i]; |
|
|
|
result = params.svm_type == ONE_CLASS ? (float)(sum > 0) : (float)sum; |
|
} |
|
else if( params.svm_type == C_SVC || |
|
params.svm_type == NU_SVC ) |
|
{ |
|
CvSVMDecisionFunc* df = (CvSVMDecisionFunc*)decision_func; |
|
int* vote = (int*)(buffer + sv_total); |
|
int i, j, k; |
|
|
|
memset( vote, 0, class_count*sizeof(vote[0])); |
|
kernel->calc( sv_total, var_count, (const float**)sv, row_sample, buffer ); |
|
double sum = 0.; |
|
|
|
for( i = 0; i < class_count; i++ ) |
|
{ |
|
for( j = i+1; j < class_count; j++, df++ ) |
|
{ |
|
sum = -df->rho; |
|
int sv_count = df->sv_count; |
|
for( k = 0; k < sv_count; k++ ) |
|
sum += df->alpha[k]*buffer[df->sv_index[k]]; |
|
|
|
vote[sum > 0 ? i : j]++; |
|
} |
|
} |
|
|
|
for( i = 1, k = 0; i < class_count; i++ ) |
|
{ |
|
if( vote[i] > vote[k] ) |
|
k = i; |
|
} |
|
result = returnDFVal && class_count == 2 ? (float)sum : (float)(class_labels->data.i[k]); |
|
} |
|
else |
|
CV_Error( CV_StsBadArg, "INTERNAL ERROR: Unknown SVM type, " |
|
"the SVM structure is probably corrupted" ); |
|
|
|
return result; |
|
} |
|
|
|
float CvSVM::predict( const CvMat* sample, bool returnDFVal ) const |
|
{ |
|
float result = 0; |
|
float* row_sample = 0; |
|
|
|
CV_FUNCNAME( "CvSVM::predict" ); |
|
|
|
__BEGIN__; |
|
|
|
int class_count; |
|
|
|
if( !kernel ) |
|
CV_ERROR( CV_StsBadArg, "The SVM should be trained first" ); |
|
|
|
class_count = class_labels ? class_labels->cols : |
|
params.svm_type == ONE_CLASS ? 1 : 0; |
|
|
|
CV_CALL( cvPreparePredictData( sample, var_all, var_idx, |
|
class_count, 0, &row_sample )); |
|
result = predict( row_sample, get_var_count(), returnDFVal ); |
|
|
|
__END__; |
|
|
|
if( sample && (!CV_IS_MAT(sample) || sample->data.fl != row_sample) ) |
|
cvFree( &row_sample ); |
|
|
|
return result; |
|
} |
|
|
|
struct predict_body_svm { |
|
predict_body_svm(const CvSVM* _pointer, float* _result, const CvMat* _samples, CvMat* _results) |
|
{ |
|
pointer = _pointer; |
|
result = _result; |
|
samples = _samples; |
|
results = _results; |
|
} |
|
|
|
const CvSVM* pointer; |
|
float* result; |
|
const CvMat* samples; |
|
CvMat* results; |
|
|
|
void operator()( const cv::BlockedRange& range ) const |
|
{ |
|
for(int i = range.begin(); i < range.end(); i++ ) |
|
{ |
|
CvMat sample; |
|
cvGetRow( samples, &sample, i ); |
|
int r = (int)pointer->predict(&sample); |
|
if (results) |
|
results->data.fl[i] = (float)r; |
|
if (i == 0) |
|
*result = (float)r; |
|
} |
|
} |
|
}; |
|
|
|
float CvSVM::predict(const CvMat* samples, CV_OUT CvMat* results) const |
|
{ |
|
float result = 0; |
|
cv::parallel_for(cv::BlockedRange(0, samples->rows), |
|
predict_body_svm(this, &result, samples, results) |
|
); |
|
return result; |
|
} |
|
|
|
void CvSVM::predict( cv::InputArray _samples, cv::OutputArray _results ) const |
|
{ |
|
_results.create(_samples.size().height, 1, CV_32F); |
|
CvMat samples = _samples.getMat(), results = _results.getMat(); |
|
predict(&samples, &results); |
|
} |
|
|
|
CvSVM::CvSVM( const Mat& _train_data, const Mat& _responses, |
|
const Mat& _var_idx, const Mat& _sample_idx, CvSVMParams _params ) |
|
{ |
|
decision_func = 0; |
|
class_labels = 0; |
|
class_weights = 0; |
|
storage = 0; |
|
var_idx = 0; |
|
kernel = 0; |
|
solver = 0; |
|
default_model_name = "my_svm"; |
|
|
|
train( _train_data, _responses, _var_idx, _sample_idx, _params ); |
|
} |
|
|
|
bool CvSVM::train( const Mat& _train_data, const Mat& _responses, |
|
const Mat& _var_idx, const Mat& _sample_idx, CvSVMParams _params ) |
|
{ |
|
CvMat tdata = _train_data, responses = _responses, vidx = _var_idx, sidx = _sample_idx; |
|
return train(&tdata, &responses, vidx.data.ptr ? &vidx : 0, sidx.data.ptr ? &sidx : 0, _params); |
|
} |
|
|
|
|
|
bool CvSVM::train_auto( const Mat& _train_data, const Mat& _responses, |
|
const Mat& _var_idx, const Mat& _sample_idx, CvSVMParams _params, int k_fold, |
|
CvParamGrid C_grid, CvParamGrid gamma_grid, CvParamGrid p_grid, |
|
CvParamGrid nu_grid, CvParamGrid coef_grid, CvParamGrid degree_grid, bool balanced ) |
|
{ |
|
CvMat tdata = _train_data, responses = _responses, vidx = _var_idx, sidx = _sample_idx; |
|
return train_auto(&tdata, &responses, vidx.data.ptr ? &vidx : 0, |
|
sidx.data.ptr ? &sidx : 0, _params, k_fold, C_grid, gamma_grid, p_grid, |
|
nu_grid, coef_grid, degree_grid, balanced); |
|
} |
|
|
|
float CvSVM::predict( const Mat& _sample, bool returnDFVal ) const |
|
{ |
|
CvMat sample = _sample; |
|
return predict(&sample, returnDFVal); |
|
} |
|
|
|
|
|
void CvSVM::write_params( CvFileStorage* fs ) const |
|
{ |
|
//CV_FUNCNAME( "CvSVM::write_params" ); |
|
|
|
__BEGIN__; |
|
|
|
int svm_type = params.svm_type; |
|
int kernel_type = params.kernel_type; |
|
|
|
const char* svm_type_str = |
|
svm_type == CvSVM::C_SVC ? "C_SVC" : |
|
svm_type == CvSVM::NU_SVC ? "NU_SVC" : |
|
svm_type == CvSVM::ONE_CLASS ? "ONE_CLASS" : |
|
svm_type == CvSVM::EPS_SVR ? "EPS_SVR" : |
|
svm_type == CvSVM::NU_SVR ? "NU_SVR" : 0; |
|
const char* kernel_type_str = |
|
kernel_type == CvSVM::LINEAR ? "LINEAR" : |
|
kernel_type == CvSVM::POLY ? "POLY" : |
|
kernel_type == CvSVM::RBF ? "RBF" : |
|
kernel_type == CvSVM::SIGMOID ? "SIGMOID" : 0; |
|
|
|
if( svm_type_str ) |
|
cvWriteString( fs, "svm_type", svm_type_str ); |
|
else |
|
cvWriteInt( fs, "svm_type", svm_type ); |
|
|
|
// save kernel |
|
cvStartWriteStruct( fs, "kernel", CV_NODE_MAP + CV_NODE_FLOW ); |
|
|
|
if( kernel_type_str ) |
|
cvWriteString( fs, "type", kernel_type_str ); |
|
else |
|
cvWriteInt( fs, "type", kernel_type ); |
|
|
|
if( kernel_type == CvSVM::POLY || !kernel_type_str ) |
|
cvWriteReal( fs, "degree", params.degree ); |
|
|
|
if( kernel_type != CvSVM::LINEAR || !kernel_type_str ) |
|
cvWriteReal( fs, "gamma", params.gamma ); |
|
|
|
if( kernel_type == CvSVM::POLY || kernel_type == CvSVM::SIGMOID || !kernel_type_str ) |
|
cvWriteReal( fs, "coef0", params.coef0 ); |
|
|
|
cvEndWriteStruct(fs); |
|
|
|
if( svm_type == CvSVM::C_SVC || svm_type == CvSVM::EPS_SVR || |
|
svm_type == CvSVM::NU_SVR || !svm_type_str ) |
|
cvWriteReal( fs, "C", params.C ); |
|
|
|
if( svm_type == CvSVM::NU_SVC || svm_type == CvSVM::ONE_CLASS || |
|
svm_type == CvSVM::NU_SVR || !svm_type_str ) |
|
cvWriteReal( fs, "nu", params.nu ); |
|
|
|
if( svm_type == CvSVM::EPS_SVR || !svm_type_str ) |
|
cvWriteReal( fs, "p", params.p ); |
|
|
|
cvStartWriteStruct( fs, "term_criteria", CV_NODE_MAP + CV_NODE_FLOW ); |
|
if( params.term_crit.type & CV_TERMCRIT_EPS ) |
|
cvWriteReal( fs, "epsilon", params.term_crit.epsilon ); |
|
if( params.term_crit.type & CV_TERMCRIT_ITER ) |
|
cvWriteInt( fs, "iterations", params.term_crit.max_iter ); |
|
cvEndWriteStruct( fs ); |
|
|
|
__END__; |
|
} |
|
|
|
|
|
void CvSVM::write( CvFileStorage* fs, const char* name ) const |
|
{ |
|
CV_FUNCNAME( "CvSVM::write" ); |
|
|
|
__BEGIN__; |
|
|
|
int i, var_count = get_var_count(), df_count, class_count; |
|
const CvSVMDecisionFunc* df = decision_func; |
|
|
|
cvStartWriteStruct( fs, name, CV_NODE_MAP, CV_TYPE_NAME_ML_SVM ); |
|
|
|
write_params( fs ); |
|
|
|
cvWriteInt( fs, "var_all", var_all ); |
|
cvWriteInt( fs, "var_count", var_count ); |
|
|
|
class_count = class_labels ? class_labels->cols : |
|
params.svm_type == CvSVM::ONE_CLASS ? 1 : 0; |
|
|
|
if( class_count ) |
|
{ |
|
cvWriteInt( fs, "class_count", class_count ); |
|
|
|
if( class_labels ) |
|
cvWrite( fs, "class_labels", class_labels ); |
|
|
|
if( class_weights ) |
|
cvWrite( fs, "class_weights", class_weights ); |
|
} |
|
|
|
if( var_idx ) |
|
cvWrite( fs, "var_idx", var_idx ); |
|
|
|
// write the joint collection of support vectors |
|
cvWriteInt( fs, "sv_total", sv_total ); |
|
cvStartWriteStruct( fs, "support_vectors", CV_NODE_SEQ ); |
|
for( i = 0; i < sv_total; i++ ) |
|
{ |
|
cvStartWriteStruct( fs, 0, CV_NODE_SEQ + CV_NODE_FLOW ); |
|
cvWriteRawData( fs, sv[i], var_count, "f" ); |
|
cvEndWriteStruct( fs ); |
|
} |
|
|
|
cvEndWriteStruct( fs ); |
|
|
|
// write decision functions |
|
df_count = class_count > 1 ? class_count*(class_count-1)/2 : 1; |
|
df = decision_func; |
|
|
|
cvStartWriteStruct( fs, "decision_functions", CV_NODE_SEQ ); |
|
for( i = 0; i < df_count; i++ ) |
|
{ |
|
int sv_count = df[i].sv_count; |
|
cvStartWriteStruct( fs, 0, CV_NODE_MAP ); |
|
cvWriteInt( fs, "sv_count", sv_count ); |
|
cvWriteReal( fs, "rho", df[i].rho ); |
|
cvStartWriteStruct( fs, "alpha", CV_NODE_SEQ+CV_NODE_FLOW ); |
|
cvWriteRawData( fs, df[i].alpha, df[i].sv_count, "d" ); |
|
cvEndWriteStruct( fs ); |
|
if( class_count > 1 ) |
|
{ |
|
cvStartWriteStruct( fs, "index", CV_NODE_SEQ+CV_NODE_FLOW ); |
|
cvWriteRawData( fs, df[i].sv_index, df[i].sv_count, "i" ); |
|
cvEndWriteStruct( fs ); |
|
} |
|
else |
|
CV_ASSERT( sv_count == sv_total ); |
|
cvEndWriteStruct( fs ); |
|
} |
|
cvEndWriteStruct( fs ); |
|
cvEndWriteStruct( fs ); |
|
|
|
__END__; |
|
} |
|
|
|
|
|
void CvSVM::read_params( CvFileStorage* fs, CvFileNode* svm_node ) |
|
{ |
|
CV_FUNCNAME( "CvSVM::read_params" ); |
|
|
|
__BEGIN__; |
|
|
|
int svm_type, kernel_type; |
|
CvSVMParams _params; |
|
|
|
CvFileNode* tmp_node = cvGetFileNodeByName( fs, svm_node, "svm_type" ); |
|
CvFileNode* kernel_node; |
|
if( !tmp_node ) |
|
CV_ERROR( CV_StsBadArg, "svm_type tag is not found" ); |
|
|
|
if( CV_NODE_TYPE(tmp_node->tag) == CV_NODE_INT ) |
|
svm_type = cvReadInt( tmp_node, -1 ); |
|
else |
|
{ |
|
const char* svm_type_str = cvReadString( tmp_node, "" ); |
|
svm_type = |
|
strcmp( svm_type_str, "C_SVC" ) == 0 ? CvSVM::C_SVC : |
|
strcmp( svm_type_str, "NU_SVC" ) == 0 ? CvSVM::NU_SVC : |
|
strcmp( svm_type_str, "ONE_CLASS" ) == 0 ? CvSVM::ONE_CLASS : |
|
strcmp( svm_type_str, "EPS_SVR" ) == 0 ? CvSVM::EPS_SVR : |
|
strcmp( svm_type_str, "NU_SVR" ) == 0 ? CvSVM::NU_SVR : -1; |
|
|
|
if( svm_type < 0 ) |
|
CV_ERROR( CV_StsParseError, "Missing of invalid SVM type" ); |
|
} |
|
|
|
kernel_node = cvGetFileNodeByName( fs, svm_node, "kernel" ); |
|
if( !kernel_node ) |
|
CV_ERROR( CV_StsParseError, "SVM kernel tag is not found" ); |
|
|
|
tmp_node = cvGetFileNodeByName( fs, kernel_node, "type" ); |
|
if( !tmp_node ) |
|
CV_ERROR( CV_StsParseError, "SVM kernel type tag is not found" ); |
|
|
|
if( CV_NODE_TYPE(tmp_node->tag) == CV_NODE_INT ) |
|
kernel_type = cvReadInt( tmp_node, -1 ); |
|
else |
|
{ |
|
const char* kernel_type_str = cvReadString( tmp_node, "" ); |
|
kernel_type = |
|
strcmp( kernel_type_str, "LINEAR" ) == 0 ? CvSVM::LINEAR : |
|
strcmp( kernel_type_str, "POLY" ) == 0 ? CvSVM::POLY : |
|
strcmp( kernel_type_str, "RBF" ) == 0 ? CvSVM::RBF : |
|
strcmp( kernel_type_str, "SIGMOID" ) == 0 ? CvSVM::SIGMOID : -1; |
|
|
|
if( kernel_type < 0 ) |
|
CV_ERROR( CV_StsParseError, "Missing of invalid SVM kernel type" ); |
|
} |
|
|
|
_params.svm_type = svm_type; |
|
_params.kernel_type = kernel_type; |
|
_params.degree = cvReadRealByName( fs, kernel_node, "degree", 0 ); |
|
_params.gamma = cvReadRealByName( fs, kernel_node, "gamma", 0 ); |
|
_params.coef0 = cvReadRealByName( fs, kernel_node, "coef0", 0 ); |
|
|
|
_params.C = cvReadRealByName( fs, svm_node, "C", 0 ); |
|
_params.nu = cvReadRealByName( fs, svm_node, "nu", 0 ); |
|
_params.p = cvReadRealByName( fs, svm_node, "p", 0 ); |
|
_params.class_weights = 0; |
|
|
|
tmp_node = cvGetFileNodeByName( fs, svm_node, "term_criteria" ); |
|
if( tmp_node ) |
|
{ |
|
_params.term_crit.epsilon = cvReadRealByName( fs, tmp_node, "epsilon", -1. ); |
|
_params.term_crit.max_iter = cvReadIntByName( fs, tmp_node, "iterations", -1 ); |
|
_params.term_crit.type = (_params.term_crit.epsilon >= 0 ? CV_TERMCRIT_EPS : 0) + |
|
(_params.term_crit.max_iter >= 0 ? CV_TERMCRIT_ITER : 0); |
|
} |
|
else |
|
_params.term_crit = cvTermCriteria( CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 1000, FLT_EPSILON ); |
|
|
|
set_params( _params ); |
|
|
|
__END__; |
|
} |
|
|
|
|
|
void CvSVM::read( CvFileStorage* fs, CvFileNode* svm_node ) |
|
{ |
|
const double not_found_dbl = DBL_MAX; |
|
|
|
CV_FUNCNAME( "CvSVM::read" ); |
|
|
|
__BEGIN__; |
|
|
|
int i, var_count, df_count, class_count; |
|
int block_size = 1 << 16, sv_size; |
|
CvFileNode *sv_node, *df_node; |
|
CvSVMDecisionFunc* df; |
|
CvSeqReader reader; |
|
|
|
if( !svm_node ) |
|
CV_ERROR( CV_StsParseError, "The requested element is not found" ); |
|
|
|
clear(); |
|
|
|
// read SVM parameters |
|
read_params( fs, svm_node ); |
|
|
|
// and top-level data |
|
sv_total = cvReadIntByName( fs, svm_node, "sv_total", -1 ); |
|
var_all = cvReadIntByName( fs, svm_node, "var_all", -1 ); |
|
var_count = cvReadIntByName( fs, svm_node, "var_count", var_all ); |
|
class_count = cvReadIntByName( fs, svm_node, "class_count", 0 ); |
|
|
|
if( sv_total <= 0 || var_all <= 0 || var_count <= 0 || var_count > var_all || class_count < 0 ) |
|
CV_ERROR( CV_StsParseError, "SVM model data is invalid, check sv_count, var_* and class_count tags" ); |
|
|
|
CV_CALL( class_labels = (CvMat*)cvReadByName( fs, svm_node, "class_labels" )); |
|
CV_CALL( class_weights = (CvMat*)cvReadByName( fs, svm_node, "class_weights" )); |
|
CV_CALL( var_idx = (CvMat*)cvReadByName( fs, svm_node, "var_idx" )); |
|
|
|
if( class_count > 1 && (!class_labels || |
|
!CV_IS_MAT(class_labels) || class_labels->cols != class_count)) |
|
CV_ERROR( CV_StsParseError, "Array of class labels is missing or invalid" ); |
|
|
|
if( var_count < var_all && (!var_idx || !CV_IS_MAT(var_idx) || var_idx->cols != var_count) ) |
|
CV_ERROR( CV_StsParseError, "var_idx array is missing or invalid" ); |
|
|
|
// read support vectors |
|
sv_node = cvGetFileNodeByName( fs, svm_node, "support_vectors" ); |
|
if( !sv_node || !CV_NODE_IS_SEQ(sv_node->tag)) |
|
CV_ERROR( CV_StsParseError, "Missing or invalid sequence of support vectors" ); |
|
|
|
block_size = MAX( block_size, sv_total*(int)sizeof(CvSVMKernelRow)); |
|
block_size = MAX( block_size, sv_total*2*(int)sizeof(double)); |
|
block_size = MAX( block_size, var_all*(int)sizeof(double)); |
|
|
|
CV_CALL( storage = cvCreateMemStorage(block_size + sizeof(CvMemBlock) + sizeof(CvSeqBlock))); |
|
CV_CALL( sv = (float**)cvMemStorageAlloc( storage, |
|
sv_total*sizeof(sv[0]) )); |
|
|
|
CV_CALL( cvStartReadSeq( sv_node->data.seq, &reader, 0 )); |
|
sv_size = var_count*sizeof(sv[0][0]); |
|
|
|
for( i = 0; i < sv_total; i++ ) |
|
{ |
|
CvFileNode* sv_elem = (CvFileNode*)reader.ptr; |
|
CV_ASSERT( var_count == 1 || (CV_NODE_IS_SEQ(sv_elem->tag) && |
|
sv_elem->data.seq->total == var_count) ); |
|
|
|
CV_CALL( sv[i] = (float*)cvMemStorageAlloc( storage, sv_size )); |
|
CV_CALL( cvReadRawData( fs, sv_elem, sv[i], "f" )); |
|
CV_NEXT_SEQ_ELEM( sv_node->data.seq->elem_size, reader ); |
|
} |
|
|
|
// read decision functions |
|
df_count = class_count > 1 ? class_count*(class_count-1)/2 : 1; |
|
df_node = cvGetFileNodeByName( fs, svm_node, "decision_functions" ); |
|
if( !df_node || !CV_NODE_IS_SEQ(df_node->tag) || |
|
df_node->data.seq->total != df_count ) |
|
CV_ERROR( CV_StsParseError, "decision_functions is missing or is not a collection " |
|
"or has a wrong number of elements" ); |
|
|
|
CV_CALL( df = decision_func = (CvSVMDecisionFunc*)cvAlloc( df_count*sizeof(df[0]) )); |
|
cvStartReadSeq( df_node->data.seq, &reader, 0 ); |
|
|
|
for( i = 0; i < df_count; i++ ) |
|
{ |
|
CvFileNode* df_elem = (CvFileNode*)reader.ptr; |
|
CvFileNode* alpha_node = cvGetFileNodeByName( fs, df_elem, "alpha" ); |
|
|
|
int sv_count = cvReadIntByName( fs, df_elem, "sv_count", -1 ); |
|
if( sv_count <= 0 ) |
|
CV_ERROR( CV_StsParseError, "sv_count is missing or non-positive" ); |
|
df[i].sv_count = sv_count; |
|
|
|
df[i].rho = cvReadRealByName( fs, df_elem, "rho", not_found_dbl ); |
|
if( fabs(df[i].rho - not_found_dbl) < DBL_EPSILON ) |
|
CV_ERROR( CV_StsParseError, "rho is missing" ); |
|
|
|
if( !alpha_node ) |
|
CV_ERROR( CV_StsParseError, "alpha is missing in the decision function" ); |
|
|
|
CV_CALL( df[i].alpha = (double*)cvMemStorageAlloc( storage, |
|
sv_count*sizeof(df[i].alpha[0]))); |
|
CV_ASSERT( sv_count == 1 || (CV_NODE_IS_SEQ(alpha_node->tag) && |
|
alpha_node->data.seq->total == sv_count) ); |
|
CV_CALL( cvReadRawData( fs, alpha_node, df[i].alpha, "d" )); |
|
|
|
if( class_count > 1 ) |
|
{ |
|
CvFileNode* index_node = cvGetFileNodeByName( fs, df_elem, "index" ); |
|
if( !index_node ) |
|
CV_ERROR( CV_StsParseError, "index is missing in the decision function" ); |
|
CV_CALL( df[i].sv_index = (int*)cvMemStorageAlloc( storage, |
|
sv_count*sizeof(df[i].sv_index[0]))); |
|
CV_ASSERT( sv_count == 1 || (CV_NODE_IS_SEQ(index_node->tag) && |
|
index_node->data.seq->total == sv_count) ); |
|
CV_CALL( cvReadRawData( fs, index_node, df[i].sv_index, "i" )); |
|
} |
|
else |
|
df[i].sv_index = 0; |
|
|
|
CV_NEXT_SEQ_ELEM( df_node->data.seq->elem_size, reader ); |
|
} |
|
|
|
if( cvReadIntByName(fs, svm_node, "optimize_linear", 1) != 0 ) |
|
optimize_linear_svm(); |
|
create_kernel(); |
|
|
|
__END__; |
|
} |
|
|
|
#if 0 |
|
|
|
static void* |
|
icvCloneSVM( const void* _src ) |
|
{ |
|
CvSVMModel* dst = 0; |
|
|
|
CV_FUNCNAME( "icvCloneSVM" ); |
|
|
|
__BEGIN__; |
|
|
|
const CvSVMModel* src = (const CvSVMModel*)_src; |
|
int var_count, class_count; |
|
int i, sv_total, df_count; |
|
int sv_size; |
|
|
|
if( !CV_IS_SVM(src) ) |
|
CV_ERROR( !src ? CV_StsNullPtr : CV_StsBadArg, "Input pointer is NULL or invalid" ); |
|
|
|
// 0. create initial CvSVMModel structure |
|
CV_CALL( dst = icvCreateSVM() ); |
|
dst->params = src->params; |
|
dst->params.weight_labels = 0; |
|
dst->params.weights = 0; |
|
|
|
dst->var_all = src->var_all; |
|
if( src->class_labels ) |
|
dst->class_labels = cvCloneMat( src->class_labels ); |
|
if( src->class_weights ) |
|
dst->class_weights = cvCloneMat( src->class_weights ); |
|
if( src->comp_idx ) |
|
dst->comp_idx = cvCloneMat( src->comp_idx ); |
|
|
|
var_count = src->comp_idx ? src->comp_idx->cols : src->var_all; |
|
class_count = src->class_labels ? src->class_labels->cols : |
|
src->params.svm_type == CvSVM::ONE_CLASS ? 1 : 0; |
|
sv_total = dst->sv_total = src->sv_total; |
|
CV_CALL( dst->storage = cvCreateMemStorage( src->storage->block_size )); |
|
CV_CALL( dst->sv = (float**)cvMemStorageAlloc( dst->storage, |
|
sv_total*sizeof(dst->sv[0]) )); |
|
|
|
sv_size = var_count*sizeof(dst->sv[0][0]); |
|
|
|
for( i = 0; i < sv_total; i++ ) |
|
{ |
|
CV_CALL( dst->sv[i] = (float*)cvMemStorageAlloc( dst->storage, sv_size )); |
|
memcpy( dst->sv[i], src->sv[i], sv_size ); |
|
} |
|
|
|
df_count = class_count > 1 ? class_count*(class_count-1)/2 : 1; |
|
|
|
CV_CALL( dst->decision_func = cvAlloc( df_count*sizeof(CvSVMDecisionFunc) )); |
|
|
|
for( i = 0; i < df_count; i++ ) |
|
{ |
|
const CvSVMDecisionFunc *sdf = |
|
(const CvSVMDecisionFunc*)src->decision_func+i; |
|
CvSVMDecisionFunc *ddf = |
|
(CvSVMDecisionFunc*)dst->decision_func+i; |
|
int sv_count = sdf->sv_count; |
|
ddf->sv_count = sv_count; |
|
ddf->rho = sdf->rho; |
|
CV_CALL( ddf->alpha = (double*)cvMemStorageAlloc( dst->storage, |
|
sv_count*sizeof(ddf->alpha[0]))); |
|
memcpy( ddf->alpha, sdf->alpha, sv_count*sizeof(ddf->alpha[0])); |
|
|
|
if( class_count > 1 ) |
|
{ |
|
CV_CALL( ddf->sv_index = (int*)cvMemStorageAlloc( dst->storage, |
|
sv_count*sizeof(ddf->sv_index[0]))); |
|
memcpy( ddf->sv_index, sdf->sv_index, sv_count*sizeof(ddf->sv_index[0])); |
|
} |
|
else |
|
ddf->sv_index = 0; |
|
} |
|
|
|
__END__; |
|
|
|
if( cvGetErrStatus() < 0 && dst ) |
|
icvReleaseSVM( &dst ); |
|
|
|
return dst; |
|
} |
|
|
|
static int icvRegisterSVMType() |
|
{ |
|
CvTypeInfo info; |
|
memset( &info, 0, sizeof(info) ); |
|
|
|
info.flags = 0; |
|
info.header_size = sizeof( info ); |
|
info.is_instance = icvIsSVM; |
|
info.release = (CvReleaseFunc)icvReleaseSVM; |
|
info.read = icvReadSVM; |
|
info.write = icvWriteSVM; |
|
info.clone = icvCloneSVM; |
|
info.type_name = CV_TYPE_NAME_ML_SVM; |
|
cvRegisterType( &info ); |
|
|
|
return 1; |
|
} |
|
|
|
|
|
static int svm = icvRegisterSVMType(); |
|
|
|
/* The function trains SVM model with optimal parameters, obtained by using cross-validation. |
|
The parameters to be estimated should be indicated by setting theirs values to FLT_MAX. |
|
The optimal parameters are saved in <model_params> */ |
|
CV_IMPL CvStatModel* |
|
cvTrainSVM_CrossValidation( const CvMat* train_data, int tflag, |
|
const CvMat* responses, |
|
CvStatModelParams* model_params, |
|
const CvStatModelParams* cross_valid_params, |
|
const CvMat* comp_idx, |
|
const CvMat* sample_idx, |
|
const CvParamGrid* degree_grid, |
|
const CvParamGrid* gamma_grid, |
|
const CvParamGrid* coef_grid, |
|
const CvParamGrid* C_grid, |
|
const CvParamGrid* nu_grid, |
|
const CvParamGrid* p_grid ) |
|
{ |
|
CvStatModel* svm = 0; |
|
|
|
CV_FUNCNAME("cvTainSVMCrossValidation"); |
|
__BEGIN__; |
|
|
|
double degree_step = 7, |
|
g_step = 15, |
|
coef_step = 14, |
|
C_step = 20, |
|
nu_step = 5, |
|
p_step = 7; // all steps must be > 1 |
|
double degree_begin = 0.01, degree_end = 2; |
|
double g_begin = 1e-5, g_end = 0.5; |
|
double coef_begin = 0.1, coef_end = 300; |
|
double C_begin = 0.1, C_end = 6000; |
|
double nu_begin = 0.01, nu_end = 0.4; |
|
double p_begin = 0.01, p_end = 100; |
|
|
|
double rate = 0, gamma = 0, C = 0, degree = 0, coef = 0, p = 0, nu = 0; |
|
|
|
double best_rate = 0; |
|
double best_degree = degree_begin; |
|
double best_gamma = g_begin; |
|
double best_coef = coef_begin; |
|
double best_C = C_begin; |
|
double best_nu = nu_begin; |
|
double best_p = p_begin; |
|
|
|
CvSVMModelParams svm_params, *psvm_params; |
|
CvCrossValidationParams* cv_params = (CvCrossValidationParams*)cross_valid_params; |
|
int svm_type, kernel; |
|
int is_regression; |
|
|
|
if( !model_params ) |
|
CV_ERROR( CV_StsBadArg, "" ); |
|
if( !cv_params ) |
|
CV_ERROR( CV_StsBadArg, "" ); |
|
|
|
svm_params = *(CvSVMModelParams*)model_params; |
|
psvm_params = (CvSVMModelParams*)model_params; |
|
svm_type = svm_params.svm_type; |
|
kernel = svm_params.kernel_type; |
|
|
|
svm_params.degree = svm_params.degree > 0 ? svm_params.degree : 1; |
|
svm_params.gamma = svm_params.gamma > 0 ? svm_params.gamma : 1; |
|
svm_params.coef0 = svm_params.coef0 > 0 ? svm_params.coef0 : 1e-6; |
|
svm_params.C = svm_params.C > 0 ? svm_params.C : 1; |
|
svm_params.nu = svm_params.nu > 0 ? svm_params.nu : 1; |
|
svm_params.p = svm_params.p > 0 ? svm_params.p : 1; |
|
|
|
if( degree_grid ) |
|
{ |
|
if( !(degree_grid->max_val == 0 && degree_grid->min_val == 0 && |
|
degree_grid->step == 0) ) |
|
{ |
|
if( degree_grid->min_val > degree_grid->max_val ) |
|
CV_ERROR( CV_StsBadArg, |
|
"low bound of grid should be less then the upper one"); |
|
if( degree_grid->step <= 1 ) |
|
CV_ERROR( CV_StsBadArg, "grid step should be greater 1" ); |
|
degree_begin = degree_grid->min_val; |
|
degree_end = degree_grid->max_val; |
|
degree_step = degree_grid->step; |
|
} |
|
} |
|
else |
|
degree_begin = degree_end = svm_params.degree; |
|
|
|
if( gamma_grid ) |
|
{ |
|
if( !(gamma_grid->max_val == 0 && gamma_grid->min_val == 0 && |
|
gamma_grid->step == 0) ) |
|
{ |
|
if( gamma_grid->min_val > gamma_grid->max_val ) |
|
CV_ERROR( CV_StsBadArg, |
|
"low bound of grid should be less then the upper one"); |
|
if( gamma_grid->step <= 1 ) |
|
CV_ERROR( CV_StsBadArg, "grid step should be greater 1" ); |
|
g_begin = gamma_grid->min_val; |
|
g_end = gamma_grid->max_val; |
|
g_step = gamma_grid->step; |
|
} |
|
} |
|
else |
|
g_begin = g_end = svm_params.gamma; |
|
|
|
if( coef_grid ) |
|
{ |
|
if( !(coef_grid->max_val == 0 && coef_grid->min_val == 0 && |
|
coef_grid->step == 0) ) |
|
{ |
|
if( coef_grid->min_val > coef_grid->max_val ) |
|
CV_ERROR( CV_StsBadArg, |
|
"low bound of grid should be less then the upper one"); |
|
if( coef_grid->step <= 1 ) |
|
CV_ERROR( CV_StsBadArg, "grid step should be greater 1" ); |
|
coef_begin = coef_grid->min_val; |
|
coef_end = coef_grid->max_val; |
|
coef_step = coef_grid->step; |
|
} |
|
} |
|
else |
|
coef_begin = coef_end = svm_params.coef0; |
|
|
|
if( C_grid ) |
|
{ |
|
if( !(C_grid->max_val == 0 && C_grid->min_val == 0 && C_grid->step == 0)) |
|
{ |
|
if( C_grid->min_val > C_grid->max_val ) |
|
CV_ERROR( CV_StsBadArg, |
|
"low bound of grid should be less then the upper one"); |
|
if( C_grid->step <= 1 ) |
|
CV_ERROR( CV_StsBadArg, "grid step should be greater 1" ); |
|
C_begin = C_grid->min_val; |
|
C_end = C_grid->max_val; |
|
C_step = C_grid->step; |
|
} |
|
} |
|
else |
|
C_begin = C_end = svm_params.C; |
|
|
|
if( nu_grid ) |
|
{ |
|
if(!(nu_grid->max_val == 0 && nu_grid->min_val == 0 && nu_grid->step==0)) |
|
{ |
|
if( nu_grid->min_val > nu_grid->max_val ) |
|
CV_ERROR( CV_StsBadArg, |
|
"low bound of grid should be less then the upper one"); |
|
if( nu_grid->step <= 1 ) |
|
CV_ERROR( CV_StsBadArg, "grid step should be greater 1" ); |
|
nu_begin = nu_grid->min_val; |
|
nu_end = nu_grid->max_val; |
|
nu_step = nu_grid->step; |
|
} |
|
} |
|
else |
|
nu_begin = nu_end = svm_params.nu; |
|
|
|
if( p_grid ) |
|
{ |
|
if( !(p_grid->max_val == 0 && p_grid->min_val == 0 && p_grid->step == 0)) |
|
{ |
|
if( p_grid->min_val > p_grid->max_val ) |
|
CV_ERROR( CV_StsBadArg, |
|
"low bound of grid should be less then the upper one"); |
|
if( p_grid->step <= 1 ) |
|
CV_ERROR( CV_StsBadArg, "grid step should be greater 1" ); |
|
p_begin = p_grid->min_val; |
|
p_end = p_grid->max_val; |
|
p_step = p_grid->step; |
|
} |
|
} |
|
else |
|
p_begin = p_end = svm_params.p; |
|
|
|
// these parameters are not used: |
|
if( kernel != CvSVM::POLY ) |
|
degree_begin = degree_end = svm_params.degree; |
|
|
|
if( kernel == CvSVM::LINEAR ) |
|
g_begin = g_end = svm_params.gamma; |
|
|
|
if( kernel != CvSVM::POLY && kernel != CvSVM::SIGMOID ) |
|
coef_begin = coef_end = svm_params.coef0; |
|
|
|
if( svm_type == CvSVM::NU_SVC || svm_type == CvSVM::ONE_CLASS ) |
|
C_begin = C_end = svm_params.C; |
|
|
|
if( svm_type == CvSVM::C_SVC || svm_type == CvSVM::EPS_SVR ) |
|
nu_begin = nu_end = svm_params.nu; |
|
|
|
if( svm_type != CvSVM::EPS_SVR ) |
|
p_begin = p_end = svm_params.p; |
|
|
|
is_regression = cv_params->is_regression; |
|
best_rate = is_regression ? FLT_MAX : 0; |
|
|
|
assert( g_step > 1 && degree_step > 1 && coef_step > 1); |
|
assert( p_step > 1 && C_step > 1 && nu_step > 1 ); |
|
|
|
for( degree = degree_begin; degree <= degree_end; degree *= degree_step ) |
|
{ |
|
svm_params.degree = degree; |
|
//printf("degree = %.3f\n", degree ); |
|
for( gamma= g_begin; gamma <= g_end; gamma *= g_step ) |
|
{ |
|
svm_params.gamma = gamma; |
|
//printf(" gamma = %.3f\n", gamma ); |
|
for( coef = coef_begin; coef <= coef_end; coef *= coef_step ) |
|
{ |
|
svm_params.coef0 = coef; |
|
//printf(" coef = %.3f\n", coef ); |
|
for( C = C_begin; C <= C_end; C *= C_step ) |
|
{ |
|
svm_params.C = C; |
|
//printf(" C = %.3f\n", C ); |
|
for( nu = nu_begin; nu <= nu_end; nu *= nu_step ) |
|
{ |
|
svm_params.nu = nu; |
|
//printf(" nu = %.3f\n", nu ); |
|
for( p = p_begin; p <= p_end; p *= p_step ) |
|
{ |
|
int well; |
|
svm_params.p = p; |
|
//printf(" p = %.3f\n", p ); |
|
|
|
CV_CALL(rate = cvCrossValidation( train_data, tflag, responses, &cvTrainSVM, |
|
cross_valid_params, (CvStatModelParams*)&svm_params, comp_idx, sample_idx )); |
|
|
|
well = rate > best_rate && !is_regression || rate < best_rate && is_regression; |
|
if( well || (rate == best_rate && C < best_C) ) |
|
{ |
|
best_rate = rate; |
|
best_degree = degree; |
|
best_gamma = gamma; |
|
best_coef = coef; |
|
best_C = C; |
|
best_nu = nu; |
|
best_p = p; |
|
} |
|
//printf(" rate = %.2f\n", rate ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
//printf("The best:\nrate = %.2f%% degree = %f gamma = %f coef = %f c = %f nu = %f p = %f\n", |
|
// best_rate, best_degree, best_gamma, best_coef, best_C, best_nu, best_p ); |
|
|
|
psvm_params->C = best_C; |
|
psvm_params->nu = best_nu; |
|
psvm_params->p = best_p; |
|
psvm_params->gamma = best_gamma; |
|
psvm_params->degree = best_degree; |
|
psvm_params->coef0 = best_coef; |
|
|
|
CV_CALL(svm = cvTrainSVM( train_data, tflag, responses, model_params, comp_idx, sample_idx )); |
|
|
|
__END__; |
|
|
|
return svm; |
|
} |
|
|
|
#endif |
|
|
|
/* End of file. */ |
|
|
|
|