SHOGUN
v2.0.0
|
00001 /* 00002 * This program is free software; you can redistribute it and/or modify 00003 * it under the terms of the GNU General Public License as published by 00004 * the Free Software Foundation; either version 3 of the License, or 00005 * (at your option) any later version. 00006 * 00007 * Copyright (C) 2012 Jacob Walker 00008 */ 00009 00010 #include <shogun/modelselection/GradientModelSelection.h> 00011 #include <shogun/modelselection/ParameterCombination.h> 00012 #include <shogun/modelselection/ModelSelectionParameters.h> 00013 #include <shogun/machine/Machine.h> 00014 #include <shogun/lib/Map.h> 00015 00016 using namespace shogun; 00017 00018 #ifdef HAVE_NLOPT 00019 00020 #include <nlopt.h> 00021 00022 double CGradientModelSelection::nlopt_function(unsigned n, 00023 const double *x, double *grad, void *my_func_data) 00024 { 00025 nlopt_package* pack = (nlopt_package*)my_func_data; 00026 00027 shogun::CMachineEvaluation* m_machine_eval = pack->m_machine_eval; 00028 00029 shogun::CParameterCombination* m_current_combination = 00030 pack->m_current_combination; 00031 00032 bool print_state = pack->print_state; 00033 00034 /* Get result vector first to get names of parameters*/ 00035 shogun::CGradientResult* result = 00036 (shogun::CGradientResult*)(m_machine_eval->evaluate()); 00037 00038 if (result->get_result_type() != GRADIENTEVALUATION_RESULT) 00039 SG_SERROR("Evaluation result not a GradientEvaluationResult!"); 00040 00041 if ((unsigned)result->total_variables != n) 00042 { 00043 SG_SERROR("Mismatch between total variables in result and variables in " \ 00044 "NLOPT!\n"); 00045 } 00046 00047 shogun::CMachine* machine = m_machine_eval->get_machine(); 00048 00049 if (print_state) 00050 result->print_result(); 00051 00052 index_t curr_index = 0; 00053 00054 /*Set parameter values from x vector*/ 00055 for (index_t i = 0; i < result->gradient.get_num_elements(); i++) 00056 { 00057 shogun::CMapNode<TParameter*, SGVector<float64_t> >* node = 00058 result->gradient.get_node_ptr(i); 00059 00060 TParameter* param = node->key; 00061 00062 CSGObject* parent = result->parameter_dictionary.get_element(param); 00063 00064 if (param->m_datatype.m_ctype == CT_VECTOR) 00065 { 00066 if (!param->m_datatype.m_length_y) 00067 SG_SERROR("Parameter vector %s has no length\n", param->m_name); 00068 00069 index_t length = *(param->m_datatype.m_length_y); 00070 00071 for (index_t j = 0; j < length; j++) 00072 { 00073 if (!parent || !m_current_combination->set_parameter( 00074 param->m_name, (float64_t)x[curr_index+j], parent, j)) 00075 { 00076 SG_SERROR("Parameter %s not found in combination \ 00077 tree.\n", 00078 param->m_name); 00079 } 00080 } 00081 00082 curr_index += length; 00083 } 00084 00085 else if (param->m_datatype.m_ctype == CT_SGVECTOR) 00086 { 00087 if (!param->m_datatype.m_length_y) 00088 SG_SERROR("Parameter vector %s has no length\n", param->m_name); 00089 00090 index_t length = *(param->m_datatype.m_length_y); 00091 00092 for (index_t j = 0; j < length; j++) 00093 { 00094 if (!parent || !m_current_combination->set_parameter( 00095 param->m_name, (float64_t)x[curr_index+j], parent, j)) 00096 { 00097 SG_SERROR("Parameter %s not found in combination \ 00098 tree.\n", 00099 param->m_name); 00100 } 00101 } 00102 curr_index += length; 00103 } 00104 00105 else 00106 { 00107 if (!parent || !m_current_combination->set_parameter( 00108 param->m_name, (float64_t)x[curr_index], parent)) 00109 { 00110 SG_SERROR("Parameter %s not found in combination tree.\n", 00111 param->m_name); 00112 } 00113 curr_index++; 00114 } 00115 } 00116 00117 /*Apply them to the machine*/ 00118 m_current_combination->apply_to_modsel_parameter( 00119 machine->m_model_selection_parameters); 00120 00121 /*Get rid of this first result*/ 00122 SG_UNREF(result); 00123 00124 /*Get a result based on updated parameter values*/ 00125 result = (shogun::CGradientResult*)(m_machine_eval->evaluate()); 00126 00127 if (result->get_result_type() != GRADIENTEVALUATION_RESULT) 00128 SG_SERROR("Evaluation result not a GradientEvaluationResult!"); 00129 00130 curr_index = 0; 00131 00132 /*Store the gradient into the grad vector*/ 00133 for (index_t i = 0; i < result->gradient.get_num_elements(); i++) 00134 { 00135 shogun::CMapNode<TParameter*, SGVector<float64_t> >* node = 00136 result->gradient.get_node_ptr(i); 00137 00138 for(index_t j = 0; j < node->data.vlen; j++) 00139 grad[curr_index+j] = node->data[j]; 00140 00141 curr_index += node->data.vlen; 00142 } 00143 00144 /*Get function value*/ 00145 float64_t function_value = result->quantity[0]; 00146 00147 SG_UNREF(result); 00148 SG_UNREF(machine); 00149 00150 return function_value; 00151 } 00152 00153 #endif 00154 00155 CGradientModelSelection::CGradientModelSelection( 00156 CModelSelectionParameters* model_parameters, 00157 CMachineEvaluation* machine_eval) : CModelSelection(model_parameters, 00158 machine_eval) { 00159 init(); 00160 } 00161 00162 void CGradientModelSelection::init() 00163 { 00164 m_max_evaluations = 1000; 00165 m_grad_tolerance = 1e-4; 00166 m_current_combination = NULL; 00167 00168 SG_ADD((CSGObject**)&m_current_combination, "current_combination", 00169 "Current Combination", MS_NOT_AVAILABLE); 00170 SG_ADD(&m_grad_tolerance, "gradient_tolerance", 00171 "gradient_tolerance", MS_NOT_AVAILABLE); 00172 SG_ADD(&m_max_evaluations, "max_evaluations", "Max Evaluations", 00173 MS_NOT_AVAILABLE); 00174 } 00175 00176 CGradientModelSelection::CGradientModelSelection() : CModelSelection(NULL, 00177 NULL) 00178 { 00179 init(); 00180 } 00181 00182 CGradientModelSelection::~CGradientModelSelection() 00183 { 00184 SG_UNREF(m_current_combination); 00185 } 00186 00187 void CGradientModelSelection::test_gradients() 00188 { 00189 00190 float64_t delta = 0.001; 00191 float64_t error_tol = 0.1; 00192 float64_t orig_value, new_value; 00193 float64_t orig_eval, new_eval; 00194 float64_t approx_grad, true_grad; 00195 00196 CMachine* machine = m_machine_eval->get_machine(); 00197 00198 m_current_combination->apply_to_modsel_parameter( 00199 machine->m_model_selection_parameters); 00200 00201 CGradientResult* result = (CGradientResult*)(m_machine_eval->evaluate()); 00202 00203 /*Set parameter values from x vector*/ 00204 for (index_t i = 0; i < result->gradient.get_num_elements(); i++) 00205 { 00206 shogun::CMapNode<TParameter*, SGVector<float64_t> >* node = 00207 result->gradient.get_node_ptr(i); 00208 00209 orig_eval = result->quantity[0]; 00210 00211 TParameter* param = node->key; 00212 00213 for (index_t j = 0; j < node->data.vlen; j++) 00214 { 00215 true_grad = node->data[j]; 00216 00217 index_t index = -1; 00218 00219 if (param->m_datatype.m_ctype == CT_VECTOR || 00220 param->m_datatype.m_ctype == CT_SGVECTOR) 00221 { 00222 index = j; 00223 orig_value = (*((float64_t**)param->m_parameter))[j]; 00224 } 00225 00226 else 00227 orig_value = *((float64_t*)param->m_parameter); 00228 00229 new_value = orig_value+delta; 00230 00231 CSGObject* parent = result->parameter_dictionary.get_element(param); 00232 00233 00234 if (!parent || !m_current_combination->set_parameter( 00235 param->m_name, new_value, parent, index)) 00236 { 00237 SG_ERROR("Parameter %s not found in combination tree.\n", 00238 param->m_name); 00239 } 00240 00241 m_current_combination->apply_to_modsel_parameter( 00242 machine->m_model_selection_parameters); 00243 00244 CGradientResult* new_result = 00245 (CGradientResult*)(m_machine_eval->evaluate()); 00246 00247 new_eval = new_result->quantity[0]; 00248 00249 approx_grad = (new_eval-orig_eval)/delta; 00250 00251 if (abs(approx_grad - true_grad) > error_tol) 00252 { 00253 SG_ERROR("Gradient of function with respect to variable %i of %s" \ 00254 " is incorrect.\n" \ 00255 "True value is approximately %f, but calculated value is" \ 00256 " %f\n", j, param->m_name, 00257 approx_grad, true_grad); 00258 } 00259 00260 if (!parent || !m_current_combination->set_parameter( 00261 param->m_name, orig_value, parent, index)) 00262 { 00263 SG_ERROR("Parameter %s not found in combination tree.\n", 00264 param->m_name); 00265 } 00266 00267 m_current_combination->apply_to_modsel_parameter( 00268 machine->m_model_selection_parameters); 00269 00270 SG_UNREF(new_result); 00271 } 00272 } 00273 00274 SG_UNREF(machine); 00275 SG_UNREF(result); 00276 } 00277 00278 CParameterCombination* CGradientModelSelection::select_model(bool print_state) 00279 { 00280 00281 #ifdef HAVE_NLOPT 00282 00283 //Get a random initial combination 00284 SG_UNREF(m_current_combination); 00285 m_current_combination = m_model_parameters->get_single_combination(); 00286 SG_REF(m_current_combination); 00287 00288 CMachine* machine = m_machine_eval->get_machine(); 00289 00290 if (print_state) 00291 { 00292 SG_PRINT("trying combination:\n"); 00293 m_current_combination->print_tree(); 00294 } 00295 00296 m_current_combination->apply_to_modsel_parameter( 00297 machine->m_model_selection_parameters); 00298 00299 /*How many of these parameters have derivatives?*/ 00300 CGradientResult* result = (CGradientResult*)(m_machine_eval->evaluate()); 00301 00302 if (result->get_result_type() != GRADIENTEVALUATION_RESULT) 00303 SG_ERROR("Evaluation result not a GradientEvaluationResult!"); 00304 00305 index_t n = result->total_variables; 00306 00307 double* lb = SG_MALLOC(double, n); 00308 double* x = SG_MALLOC(double, n); 00309 00310 index_t cur_index = 0; 00311 00312 //Update x with initial values 00313 for (index_t i = 0; i < result->gradient.get_num_elements(); i++) 00314 { 00315 shogun::CMapNode<TParameter*, SGVector<float64_t> >* node = 00316 result->gradient.get_node_ptr(i); 00317 00318 TParameter* param = node->key; 00319 00320 CSGObject* parent = result->parameter_dictionary.get_element(param); 00321 00322 TParameter* final = m_current_combination->get_parameter( 00323 param->m_name, parent); 00324 00325 if (!final) 00326 { 00327 SG_ERROR("Could not find parameter %s "\ 00328 "in Parameter Combination\n", param->m_name); 00329 } 00330 00331 if (final->m_datatype.m_ctype == CT_VECTOR) 00332 { 00333 if (!param->m_datatype.m_length_y) 00334 SG_ERROR("Parameter vector %s has no length\n", param->m_name); 00335 00336 index_t length = *(final->m_datatype.m_length_y); 00337 00338 for (index_t j = 0; j < length; j++) 00339 x[cur_index+j] = *((float64_t**)(final->m_parameter))[j]; 00340 00341 cur_index += length; 00342 } 00343 00344 else if (final->m_datatype.m_ctype == CT_SGVECTOR) 00345 { 00346 if (!param->m_datatype.m_length_y) 00347 SG_ERROR("Parameter vector %s has no length\n", param->m_name); 00348 00349 index_t length = *(final->m_datatype.m_length_y); 00350 00351 for (index_t j = 0; j < length; j++) 00352 x[cur_index+j] = (*(float64_t**)(final->m_parameter))[j]; 00353 00354 cur_index += length; 00355 } 00356 00357 else 00358 { 00359 x[cur_index] = *((float64_t*)(final->m_parameter)); 00360 cur_index++; 00361 } 00362 00363 } 00364 00365 cur_index = 0; 00366 00367 CParameterCombination* lower_combination = 00368 m_model_parameters->get_single_combination(false); 00369 00370 //Update x with initial values 00371 for (index_t i = 0; i < result->gradient.get_num_elements(); i++) 00372 { 00373 shogun::CMapNode<TParameter*, SGVector<float64_t> >* node = 00374 result->gradient.get_node_ptr(i); 00375 00376 TParameter* param = node->key; 00377 00378 CSGObject* parent = result->parameter_dictionary.get_element(param); 00379 00380 TParameter* final = lower_combination->get_parameter( 00381 param->m_name, parent); 00382 00383 if (!final) 00384 { 00385 SG_ERROR("Could not find parameter %s "\ 00386 "in Parameter Combination\n", param->m_name); 00387 } 00388 00389 if (final->m_datatype.m_ctype == CT_VECTOR) 00390 { 00391 if (!param->m_datatype.m_length_y) 00392 SG_ERROR("Parameter vector %s has no length\n", param->m_name); 00393 00394 index_t length = *(final->m_datatype.m_length_y); 00395 00396 for (index_t j = 0; j < length; j++) 00397 lb[cur_index+j] = *((float64_t**)(final->m_parameter))[j]; 00398 00399 cur_index += length; 00400 } 00401 00402 else if (final->m_datatype.m_ctype == CT_SGVECTOR) 00403 { 00404 if (!param->m_datatype.m_length_y) 00405 SG_ERROR("Parameter vector %s has no length\n", param->m_name); 00406 00407 index_t length = *(final->m_datatype.m_length_y); 00408 00409 for (index_t j = 0; j < length; j++) 00410 lb[cur_index+j] = (*(float64_t**)(final->m_parameter))[j]; 00411 00412 cur_index += length; 00413 } 00414 00415 else 00416 { 00417 lb[cur_index] = *((float64_t*)(final->m_parameter)); 00418 cur_index++; 00419 } 00420 00421 } 00422 00423 //Setting up nlopt 00424 nlopt_opt opt; 00425 00426 nlopt_package pack; 00427 00428 pack.m_current_combination = m_current_combination; 00429 pack.m_machine_eval = m_machine_eval; 00430 pack.print_state = print_state; 00431 00432 opt = nlopt_create(NLOPT_LD_MMA, n); // algorithm and dimensionality 00433 nlopt_set_maxeval(opt, m_max_evaluations); 00434 nlopt_set_xtol_rel(opt, m_grad_tolerance); 00435 nlopt_set_lower_bounds(opt, lb); 00436 00437 if (m_machine_eval->get_evaluation_direction() == ED_MINIMIZE) 00438 { 00439 if (print_state) 00440 SG_SPRINT("Minimizing Objective Function\n"); 00441 00442 nlopt_set_min_objective(opt, nlopt_function, &pack); 00443 } 00444 00445 else 00446 { 00447 if (print_state) 00448 SG_SPRINT("Maximizing Objective Function\n"); 00449 00450 nlopt_set_max_objective(opt, nlopt_function, &pack); 00451 } 00452 00453 double minf; //the minimum objective value, upon return 00454 00455 // test_gradients(); 00456 00457 //Optimize our function! 00458 if (nlopt_optimize(opt, x, &minf) < 0) 00459 SG_ERROR("nlopt failed!\n"); 00460 00461 // test_gradients(); 00462 00463 //Clean up. 00464 SG_FREE(lb); 00465 SG_FREE(x); 00466 nlopt_destroy(opt); 00467 00468 //Admittedly weird, but I am unreferencing 00469 //m_current_combination from this stack and 00470 //passing it on to another. 00471 SG_UNREF(machine); 00472 SG_UNREF(result); 00473 SG_UNREF(lower_combination); 00474 00475 SG_REF(m_current_combination); 00476 00477 return m_current_combination; 00478 00479 #endif 00480 00481 //If we don't have NLOPT then return nothing. 00482 SG_PRINT("Shogun not configured for NLOPT. Returning NULL combination\n"); 00483 00484 return NULL; 00485 }