LCOV - code coverage report
Current view: top level - src - gensvm_gridsearch.c (source / functions) Hit Total Coverage
Test: coverage.all Lines: 115 177 65.0 %
Date: 2017-02-21 18:44:20 Functions: 3 5 60.0 %

          Line data    Source code
       1             : /**
       2             :  * @file gensvm_gridsearch.c
       3             :  * @author G.J.J. van den Burg
       4             :  * @date 2014-01-07
       5             :  * @brief Functions for finding the optimal parameters for the dataset
       6             :  *
       7             :  * @details
       8             :  * The GenSVM algorithm takes a number of parameters. The functions in
       9             :  * this file are used to find the optimal parameters.
      10             :  *
      11             :  * @copyright
      12             :  Copyright 2016, G.J.J. van den Burg.
      13             : 
      14             :  This file is part of GenSVM.
      15             : 
      16             :  GenSVM is free software: you can redistribute it and/or modify
      17             :  it under the terms of the GNU General Public License as published by
      18             :  the Free Software Foundation, either version 3 of the License, or
      19             :  (at your option) any later version.
      20             : 
      21             :  GenSVM is distributed in the hope that it will be useful,
      22             :  but WITHOUT ANY WARRANTY; without even the implied warranty of
      23             :  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      24             :  GNU General Public License for more details.
      25             : 
      26             :  You should have received a copy of the GNU General Public License
      27             :  along with GenSVM. If not, see <http://www.gnu.org/licenses/>.
      28             : 
      29             :  */
      30             : 
      31             : #include "gensvm_gridsearch.h"
      32             : 
      33             : extern FILE *GENSVM_OUTPUT_FILE;
      34             : 
      35             : /**
      36             :  * @brief Initialize a GenQueue from a Training instance
      37             :  *
      38             :  * @details
      39             :  * A Training instance describes the grid to search over. This funtion
      40             :  * creates all tasks that need to be performed and adds these to
      41             :  * a GenQueue. Each task contains a pointer to the train and test datasets
      42             :  * which are supplied. Note that the tasks are created in a specific order of
      43             :  * the parameters, to ensure that the GenModel::V of a previous parameter
      44             :  * set provides the best possible initial estimate of GenModel::V for the next
      45             :  * parameter set.
      46             :  *
      47             :  * @param[in]   grid    Training struct describing the grid search
      48             :  * @param[in]   queue           pointer to a GenQueue that will be used to
      49             :  *                              add the tasks to
      50             :  * @param[in]   train_data      GenData of the training set
      51             :  * @param[in]   test_data       GenData of the test set
      52             :  *
      53             :  */
      54           2 : void gensvm_fill_queue(struct GenGrid *grid, struct GenQueue *queue,
      55             :                 struct GenData *train_data, struct GenData *test_data)
      56             : {
      57             :         long i, j, k;
      58           2 :         long N, cnt = 0;
      59           2 :         struct GenTask *task = NULL;
      60           2 :         queue->i = 0;
      61             : 
      62           2 :         N = grid->Np;
      63           2 :         N *= grid->Nl;
      64           2 :         N *= grid->Nk;
      65           2 :         N *= grid->Ne;
      66           2 :         N *= grid->Nw;
      67             :         // these parameters are not necessarily non-zero
      68           2 :         N *= grid->Ng > 0 ? grid->Ng : 1;
      69           2 :         N *= grid->Nc > 0 ? grid->Nc : 1;
      70           2 :         N *= grid->Nd > 0 ? grid->Nd : 1;
      71             : 
      72           2 :         queue->tasks = Calloc(struct GenTask *, N);
      73           2 :         queue->N = N;
      74             : 
      75             :         // initialize all tasks
      76          80 :         for (i=0; i<N; i++) {
      77          78 :                 task = gensvm_init_task();
      78          78 :                 task->ID = i;
      79          78 :                 task->train_data = train_data;
      80          78 :                 task->test_data = test_data;
      81          78 :                 task->folds = grid->folds;
      82          78 :                 task->kerneltype = grid->kerneltype;
      83          78 :                 queue->tasks[i] = task;
      84             :         }
      85             : 
      86             :         // These loops mimick a large nested for loop. The advantage is that
      87             :         // Nd, Nc and Ng which are on the outside of the nested for loop can
      88             :         // now be zero, without large modification (see below). Whether this
      89             :         // is indeed better than the nested for loop has not been tested.
      90           2 :         cnt = 1;
      91           2 :         i = 0;
      92          30 :         while (i < N) {
      93         104 :                 for (j=0; j<grid->Np; j++) {
      94         156 :                         for (k=0; k<cnt; k++) {
      95          78 :                                 queue->tasks[i]->p = grid->ps[j];
      96          78 :                                 i++;
      97             :                         }
      98             :                 }
      99             :         }
     100             : 
     101           2 :         cnt *= grid->Np;
     102           2 :         i = 0;
     103          17 :         while (i < N) {
     104          39 :                 for (j=0; j<grid->Nl; j++) {
     105         104 :                         for (k=0; k<cnt; k++) {
     106         156 :                                 queue->tasks[i]->lambda =
     107          78 :                                         grid->lambdas[j];
     108          78 :                                 i++;
     109             :                         }
     110             :                 }
     111             :         }
     112             : 
     113           2 :         cnt *= grid->Nl;
     114           2 :         i = 0;
     115          17 :         while (i < N) {
     116          26 :                 for (j=0; j<grid->Nk; j++) {
     117          91 :                         for (k=0; k<cnt; k++) {
     118          78 :                                 queue->tasks[i]->kappa = grid->kappas[j];
     119          78 :                                 i++;
     120             :                         }
     121             :                 }
     122             :         }
     123             : 
     124           2 :         cnt *= grid->Nk;
     125           2 :         i = 0;
     126          17 :         while (i < N) {
     127          26 :                 for (j=0; j<grid->Nw; j++) {
     128          91 :                         for (k=0; k<cnt; k++) {
     129         156 :                                 queue->tasks[i]->weight_idx =
     130          78 :                                         grid->weight_idxs[j];
     131          78 :                                 i++;
     132             :                         }
     133             :                 }
     134             :         }
     135             : 
     136           2 :         cnt *= grid->Nw;
     137           2 :         i = 0;
     138          17 :         while (i < N) {
     139          26 :                 for (j=0; j<grid->Ne; j++) {
     140          91 :                         for (k=0; k<cnt; k++) {
     141          78 :                                 queue->tasks[i]->epsilon = grid->epsilons[j];
     142          78 :                                 i++;
     143             :                         }
     144             :                 }
     145             :         }
     146             : 
     147           2 :         cnt *= grid->Ne;
     148           2 :         i = 0;
     149          10 :         while (i < N && grid->Ng > 0) {
     150          18 :                 for (j=0; j<grid->Ng; j++) {
     151          84 :                         for (k=0; k<cnt; k++) {
     152          72 :                                 queue->tasks[i]->gamma = grid->gammas[j];
     153          72 :                                 i++;
     154             :                         }
     155             :                 }
     156             :         }
     157             : 
     158           2 :         cnt *= grid->Ng > 0 ? grid->Ng : 1;
     159           2 :         i = 0;
     160           6 :         while (i < N && grid->Nc > 0) {
     161           8 :                 for (j=0; j<grid->Nc; j++) {
     162          78 :                         for (k=0; k<cnt; k++) {
     163          72 :                                 queue->tasks[i]->coef = grid->coefs[j];
     164          72 :                                 i++;
     165             :                         }
     166             :                 }
     167             :         }
     168             : 
     169           2 :         cnt *= grid->Nc > 0 ? grid->Nc : 1;
     170           2 :         i = 0;
     171           5 :         while (i < N && grid->Nd > 0) {
     172           3 :                 for (j=0; j<grid->Nd; j++) {
     173          74 :                         for (k=0; k<cnt; k++) {
     174          72 :                                 queue->tasks[i]->degree = grid->degrees[j];
     175          72 :                                 i++;
     176             :                         }
     177             :                 }
     178             :         }
     179           2 : }
     180             : 
     181             : /**
     182             :  * @brief Check if the kernel parameters change between tasks
     183             :  *
     184             :  * @details
     185             :  * In the current strategy for training the kernel matrix is decomposed once,
     186             :  * and tasks with the same kernel settings are performed sequentially. When a
     187             :  * task needs to be done with different kernel parameters, the kernel matrix
     188             :  * needs to be recalculated. This function is used to check whether this is
     189             :  * the case.
     190             :  *
     191             :  * @param[in]   newtask         the next task
     192             :  * @param[in]   oldtask         the old task
     193             :  * @return                      whether the kernel needs to be reevaluated
     194             :  */
     195           9 : bool gensvm_kernel_changed(struct GenTask *newtask, struct GenTask *oldtask)
     196             : {
     197           9 :         if (oldtask == NULL)
     198           1 :                 return true;
     199           8 :         if (newtask->kerneltype != oldtask->kerneltype) {
     200           1 :                 return true;
     201           7 :         } else if (newtask->kerneltype == K_POLY) {
     202           2 :                 if (newtask->gamma != oldtask->gamma)
     203           0 :                         return true;
     204           2 :                 if (newtask->coef != oldtask->coef)
     205           0 :                         return true;
     206           2 :                 if (newtask->degree != oldtask->degree)
     207           1 :                         return true;
     208           1 :                 return false;
     209           5 :         } else if (newtask->kerneltype == K_RBF) {
     210           2 :                 if (newtask->gamma != oldtask->gamma)
     211           1 :                         return true;
     212           1 :                 return false;
     213           3 :         } else if (newtask->kerneltype == K_SIGMOID) {
     214           2 :                 if (newtask->gamma != oldtask->gamma)
     215           0 :                         return true;
     216           2 :                 if (newtask->coef != oldtask->coef)
     217           1 :                         return true;
     218           1 :                 return false;
     219             :         }
     220           1 :         return false;
     221             : }
     222             : 
     223             : /**
     224             :  * @brief Compute the kernels for the folds of the train and test datasets
     225             :  *
     226             :  * @details
     227             :  * When the kernel parameters change in a kernel grid search, the kernel
     228             :  * pre- and post-processing has to be done for the new kernel parameters. This 
     229             :  * is done here for each of the folds. Each of the training folds is 
     230             :  * preprocessed, and each of the test folds is postprocessed.
     231             :  *
     232             :  * @param[in]           folds           number of cross validation folds
     233             :  * @param[in]           model           GenModel with new kernel parameters
     234             :  * @param[in,out]       train_folds     array of train datasets
     235             :  * @param[in,out]       test_folds      array of test datasets
     236             :  *
     237             :  */
     238           0 : void gensvm_kernel_folds(long folds, struct GenModel *model,
     239             :                 struct GenData **train_folds, struct GenData **test_folds)
     240             : {
     241             :         long f;
     242             : 
     243           0 :         if (model->kerneltype != K_LINEAR)
     244           0 :                 note("Computing kernel ... ");
     245           0 :         for (f=0; f<folds; f++) {
     246           0 :                 if (train_folds[f]->Z != train_folds[f]->RAW)
     247           0 :                         free(train_folds[f]->Z);
     248           0 :                 if (test_folds[f]->Z != test_folds[f]->RAW)
     249           0 :                         free(test_folds[f]->Z);
     250           0 :                 gensvm_kernel_preprocess(model, train_folds[f]);
     251           0 :                 gensvm_kernel_postprocess(model, train_folds[f],
     252           0 :                                 test_folds[f]);
     253             :         }
     254           0 :         if (model->kerneltype != K_LINEAR)
     255           0 :                 note("done.\n");
     256           0 : }
     257             : 
     258             : /**
     259             :  * @brief Run the grid search for a GenQueue
     260             :  *
     261             :  * @details
     262             :  * Given a GenQueue of GenTask struct to be trained, a grid search is launched to
     263             :  * find the optimal parameter configuration. As is also done within
     264             :  * cross_validation(), the optimal weights of one parameter set are used as
     265             :  * initial estimates for GenModel::V in the next parameter set. Note that to
     266             :  * optimally exploit this feature of the optimization algorithm, the order in
     267             :  * which tasks are considered is important. This is considered in
     268             :  * make_queue().
     269             :  *
     270             :  * The performance found by cross validation is stored in the GenTask struct.
     271             :  *
     272             :  * @param[in,out]       q       GenQueue with GenTask instances to run
     273             :  */
     274           0 : void gensvm_train_queue(struct GenQueue *q)
     275             : {
     276             :         long f, folds;
     277           0 :         double perf, duration, current_max = 0;
     278           0 :         struct GenTask *task = get_next_task(q);
     279           0 :         struct GenTask *prevtask = NULL;
     280           0 :         struct GenModel *model = gensvm_init_model();
     281             :         struct timespec main_s, main_e, loop_s, loop_e;
     282             : 
     283           0 :         folds = task->folds;
     284             : 
     285           0 :         model->n = 0;
     286           0 :         model->m = task->train_data->m;
     287           0 :         model->K = task->train_data->K;
     288           0 :         gensvm_allocate_model(model);
     289           0 :         gensvm_init_V(NULL, model, task->train_data);
     290             : 
     291           0 :         long *cv_idx = Calloc(long, task->train_data->n);
     292           0 :         gensvm_make_cv_split(task->train_data->n, task->folds, cv_idx);
     293             : 
     294           0 :         struct GenData **train_folds = Malloc(struct GenData *, task->folds);
     295           0 :         struct GenData **test_folds = Malloc(struct GenData *, task->folds);
     296           0 :         for (f=0; f<folds; f++) {
     297           0 :                 train_folds[f] = gensvm_init_data();
     298           0 :                 test_folds[f] = gensvm_init_data();
     299           0 :                 gensvm_get_tt_split(task->train_data, train_folds[f],
     300           0 :                                 test_folds[f], cv_idx, f);
     301             :         }
     302             : 
     303           0 :         Timer(main_s);
     304           0 :         while (task) {
     305           0 :                 gensvm_task_to_model(task, model);
     306           0 :                 if (gensvm_kernel_changed(task, prevtask)) {
     307           0 :                         gensvm_kernel_folds(task->folds, model, train_folds,
     308             :                                         test_folds);
     309             :                 }
     310             : 
     311           0 :                 Timer(loop_s);
     312           0 :                 perf = gensvm_cross_validation(model, train_folds, test_folds,
     313           0 :                                 folds, task->train_data->n);
     314           0 :                 Timer(loop_e);
     315             : 
     316           0 :                 current_max = maximum(current_max, perf);
     317           0 :                 duration = gensvm_elapsed_time(&loop_s, &loop_e);
     318             : 
     319           0 :                 gensvm_gridsearch_progress(task, q->N, perf, duration,
     320             :                                 current_max);
     321             : 
     322           0 :                 q->tasks[task->ID]->performance = perf;
     323           0 :                 prevtask = task;
     324           0 :                 task = get_next_task(q);
     325             :         }
     326           0 :         Timer(main_e);
     327             : 
     328           0 :         note("\nTotal elapsed training time: %8.8f seconds\n",
     329             :                         gensvm_elapsed_time(&main_s, &main_e));
     330             : 
     331           0 :         gensvm_free_model(model);
     332           0 :         for (f=0; f<folds; f++) {
     333           0 :                 gensvm_free_data(train_folds[f]);
     334           0 :                 gensvm_free_data(test_folds[f]);
     335             :         }
     336           0 :         free(train_folds);
     337           0 :         free(test_folds);
     338           0 :         free(cv_idx);
     339           0 : }
     340             : 
     341             : /**
     342             :  * @brief Print the description of the current task on screen
     343             :  *
     344             :  * @details
     345             :  * To track the progress of the grid search the parameters of the current task
     346             :  * are written to the output specified in GENSVM_OUTPUT_FILE. Since the
     347             :  * parameters differ with the specified kernel, this function writes a
     348             :  * parameter string depending on which kernel is used.
     349             :  *
     350             :  * @param[in]   task            the GenTask specified
     351             :  * @param[in]   N               total number of tasks
     352             :  * @param[in]   perf            performance of the current task
     353             :  * @param[in]   duration        time duration of the current task
     354             :  * @param[in]   current_max     current best performance
     355             :  *
     356             :  */
     357           4 : void gensvm_gridsearch_progress(struct GenTask *task, long N, double perf, 
     358             :                 double duration, double current_max)
     359             : {
     360             :         char buffer[GENSVM_MAX_LINE_LENGTH];
     361           4 :         sprintf(buffer, "(%03li/%03li)\t", task->ID+1, N);
     362           4 :         if (task->kerneltype == K_POLY)
     363           1 :                 sprintf(buffer + strlen(buffer), "d = %2.2f\t", task->degree);
     364           4 :         if (task->kerneltype == K_POLY || task->kerneltype == K_SIGMOID)
     365           2 :                 sprintf(buffer + strlen(buffer), "c = %2.2f\t", task->coef);
     366           6 :         if (task->kerneltype == K_POLY || task->kerneltype == K_SIGMOID ||
     367           2 :                         task->kerneltype == K_RBF)
     368           3 :                 sprintf(buffer + strlen(buffer), "g = %3.3f\t", task->gamma);
     369           4 :         sprintf(buffer + strlen(buffer), "eps = %g\tw = %i\tk = %2.2f\t"
     370             :                         "l = %f\tp = %2.2f\t", task->epsilon,
     371             :                         task->weight_idx, task->kappa, task->lambda, task->p);
     372           4 :         note(buffer);
     373           4 :         note("\t%3.3f%% (%3.3fs)\t(best = %3.3f%%)\n", perf, duration, 
     374             :                         current_max);
     375           4 : }

Generated by: LCOV version 1.12