PLearn 0.1
KFoldSplitter.cc
Go to the documentation of this file.
00001 // -*- C++ -*-
00002 
00003 // KFoldSplitter.cc
00004 //
00005 // Copyright (C) 1998 Pascal Vincent
00006 // Copyright (C) 1999,2000 Pascal Vincent, Yoshua Bengio and University of Montreal
00007 // Copyright (C) 2002 Frederic Morin
00008 // Copyright (C) 2008 Jerome Louradour
00009 //
00010 // Redistribution and use in source and binary forms, with or without
00011 // modification, are permitted provided that the following conditions are met:
00012 //
00013 //  1. Redistributions of source code must retain the above copyright
00014 //     notice, this list of conditions and the following disclaimer.
00015 //
00016 //  2. Redistributions in binary form must reproduce the above copyright
00017 //     notice, this list of conditions and the following disclaimer in the
00018 //     documentation and/or other materials provided with the distribution.
00019 //
00020 //  3. The name of the authors may not be used to endorse or promote
00021 //     products derived from this software without specific prior written
00022 //     permission.
00023 //
00024 // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
00025 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00026 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
00027 // NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00028 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
00029 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00030 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00031 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00032 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00033 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00034 //
00035 // This file is part of the PLearn library. For more information on the PLearn
00036 // library, go to the PLearn Web site at www.plearn.org
00037 
00038 /* *******************************************************
00039  * $Id: KFoldSplitter.cc 9229 2008-07-10 18:55:38Z saintmlx $
00040  ******************************************************* */
00041 
00044 #include "KFoldSplitter.h"
00045 #include "VMat_basic_stats.h"
00046 #include <plearn/vmat/ConcatRowsVMatrix.h>
00047 #include <plearn/vmat/SubVMatrix.h>
00048 #include <plearn/vmat/ClassSubsetVMatrix.h>
00049 
00050 namespace PLearn {
00051 using namespace std;
00052 
00054 // KFoldSplitter //
00056 KFoldSplitter::KFoldSplitter(int k)
00057     : append_non_constant_test(false),
00058       append_train(false),
00059       include_test_in_train(false),
00060       balance_classes(false),
00061       K(k)
00062 {
00063     // Default cross-validation range is the whole dataset.
00064     cross_range.first   = 0;
00065     cross_range.second  = 1;
00066 }
00067 
00068 PLEARN_IMPLEMENT_OBJECT(KFoldSplitter,
00069                         "K-fold cross-validation splitter.",
00070                         "KFoldSplitter implements K splits of the dataset into a training-set and a test-set.\n"
00071                         "To perform leave-one-out cross-validation, K must be set to -1 (or, obviously, to the\n"
00072                         "exact number of examples).\n"
00073                         "The cross-validation may be performed only on a subset of the source data, using the option\n"
00074                         "'cross_range', that will define a range of samples on which to perform cross-validation.\n"
00075                         "All samples before this range will systematically be added to the train set, while all samples\n"
00076                         "after this range will be added to the test set.\n"
00077     );
00078 
00080 // declareOptions //
00082 void KFoldSplitter::declareOptions(OptionList& ol)
00083 {
00084     declareOption(ol, "K", &KFoldSplitter::K, OptionBase::buildoption,
00085                   "Split dataset in K parts (you can use K = -1 to perform leave-one-out CV).");
00086 
00087     declareOption(ol, "append_train", &KFoldSplitter::append_train, OptionBase::buildoption,
00088                   "If set to 1, the trainset will be appended after in the returned sets.");
00089 
00090     declareOption(ol, "append_non_constant_test",
00091                   &KFoldSplitter::append_non_constant_test,
00092                   OptionBase::buildoption,
00093         "If true, the non-constant part of the test set will be appended\n"
00094         "in the returned sets. This mostly makes sense when 'cross_range'\n"
00095         "is not (0:1).",
00096         OptionBase::advanced_level);
00097 
00098     declareOption(ol, "include_test_in_train", &KFoldSplitter::include_test_in_train, OptionBase::buildoption,
00099                   "If set to 1, the test set will be included in the train set.");
00100 
00101     declareOption(ol, "cross_range", &KFoldSplitter::cross_range,
00102                   OptionBase::buildoption,
00103         "The range on which cross-validation is applied (similar to the\n"
00104         "FractionSplitter ranges).",
00105         OptionBase::advanced_level);
00106 
00107     declareOption(ol, "balance_classes", &KFoldSplitter::balance_classes,
00108                   OptionBase::buildoption,
00109         "Should we balance classes inside the splits to obtain the same\n"
00110         "class frequencies. This corresponds to concatenating the results\n"
00111         "of a K-Fold performed on the subsets of examples from each class.\n"
00112         "Note that it currently does not support leave-one-out, and that\n"
00113         "you might obtain strange results if K > number of samples in one\n"
00114         "class.\n"
00115         "Note also that for this option to work, you have to label your\n"
00116         "classes from 0 to (n_classes-1), and all classes must be present\n"
00117         "in the source VMat.");
00118 
00119     inherited::declareOptions(ol);
00120 }
00121 
00123 // build_ //
00125 void KFoldSplitter::build_()
00126 {
00127     PLASSERT( K > 0 || K == -1 );
00128 }
00129 
00131 // build //
00133 void KFoldSplitter::build()
00134 {
00135     inherited::build();
00136     build_();
00137 }
00138 
00140 // nsplits //
00142 int KFoldSplitter::nsplits() const
00143 {
00144     return K > 0 ? K
00145                  : dataset ? dataset->length()
00146                            : -1;
00147 }
00148 
00150 // nSetsPerSplit //
00152 int KFoldSplitter::nSetsPerSplit() const
00153 {
00154     int nsets = 2;
00155     if (append_train)
00156         nsets++;
00157     if (append_non_constant_test)
00158         nsets++;
00159     return nsets;
00160 }
00161 
00163 // getSplit //
00165 TVec<VMat> KFoldSplitter::getSplit(int k)
00166 {
00167     if (k >= nsplits())
00168         PLERROR("KFoldSplitter::getSplit() - k (%d) cannot be greater than "
00169                 " the number of splits (%d)", k, nsplits());
00170 
00171     real start = cross_range.first;
00172     real end   = cross_range.second;
00173     int n_data = dataset->length();
00174     PLASSERT_MSG(start >= 0 && end >= 0 && end > start && start < n_data && end < n_data,
00175                  string("start=")+tostring(start)+", end="+tostring(end)+", n_data="+tostring(n_data));
00176     int i_start = 0;
00177     if (start > 0)
00178         i_start = start >= 1 ? int(round(start)) : int(round(n_data * start));
00179     int i_end = n_data;
00180     if (!fast_exact_is_equal(end, 1))
00181         i_end   = end   >= 1 ? int(round(end))   : int(round(n_data * end));
00182     // The cross validation will be done only on examples i_start, ..., i_end - 1.
00183     int n_cross_data = i_end - i_start;
00184     bool do_partial_cross = (n_cross_data != n_data);
00185     if (K > 0 && K > n_data)
00186         PLERROR("In KFoldSplitter::getSplit - The number of splits (%d) cannot"
00187                 " be greater than the number of samples in the dataset (%d). "
00188                 "If you want to perform leave-one-out cross-validation, please"
00189                 "set K = -1", K, n_data);
00190     real test_fraction = K > 0 ? (n_cross_data/(real)K) : 0;
00191     if ((int)(test_fraction) < 1)
00192         test_fraction = 1; // leave-one-out cross-validation
00193 
00194     TVec<VMat> split_(2);
00195     VMat non_constant_test;
00196     if (do_partial_cross) {
00197         if (balance_classes)
00198             PLERROR("balance_classes not implemented with partial_cross");
00199         VMat sub_data = new SubVMatrix(dataset, i_start, 0, n_cross_data, dataset->width());
00200         split(sub_data, test_fraction, split_[0], split_[1], k, true);
00201         non_constant_test = split_[1];
00202         if (i_start > 0) {
00203             VMat constant_train = new SubVMatrix(dataset, 0, 0, i_start, dataset->width());
00204             split_[0] = new ConcatRowsVMatrix(constant_train, split_[0]);
00205         }
00206         if (i_end < n_data) {
00207             VMat constant_test = new SubVMatrix(dataset, i_end, 0, n_data - i_end, dataset->width());
00208             split_[1] = new ConcatRowsVMatrix(split_[1], constant_test);
00209         }
00210     }
00211     else {
00212         if (balance_classes)
00213         {
00214             PLASSERT( dataset->targetsize() > 0 );
00215             TVec<VMat> tmp_split_(2);
00216             int i_class = 0;
00217             if (test_fraction > 1.0)
00218                 test_fraction /= n_cross_data;
00219             else
00220                 PLERROR("In KFoldSplitter::getSplit - Leave-one-out not implemented");
00221             while (true) { // break point below
00222                 VMat dataset_class = new ClassSubsetVMatrix(dataset, i_class);
00223                 int length = dataset_class->length();
00224                 if (length == 0 ) break;
00225                 if (length < K)
00226                     PLWARNING("In KFoldSplitter::getSplit - There are less "
00227                             "samples from class %d (N = %d) than splits "
00228                             "(K = %d): you may get weird results",
00229                             i_class, length, K);
00230                 split(dataset_class, test_fraction, tmp_split_[0], tmp_split_[1], k, true);
00231                 if (i_class == 0) {
00232                     split_ = tmp_split_.copy();
00233                 } else {
00234                     split_[0] = new ConcatRowsVMatrix(split_[0], tmp_split_[0]);
00235                     split_[1] = new ConcatRowsVMatrix(split_[1], tmp_split_[1]);
00236                 }
00237                 i_class++;
00238             }
00239         }
00240         else
00241             split(dataset, test_fraction, split_[0], split_[1], k, true);
00242         non_constant_test = split_[1];
00243     }
00244     if (include_test_in_train)
00245         split_[0] = new ConcatRowsVMatrix(split_[0], split_[1]);
00246     if (append_train)
00247         split_.append(split_[0]);
00248     if (append_non_constant_test) {
00249         PLCHECK_MSG(!balance_classes, "Not implemented");
00250         split_.append(non_constant_test);
00251     }
00252     return split_;
00253 }
00254 
00255 } // end of namespace PLearn
00256 
00257 
00258 /*
00259   Local Variables:
00260   mode:c++
00261   c-basic-offset:4
00262   c-file-style:"stroustrup"
00263   c-file-offsets:((innamespace . 0)(inline-open . 0))
00264   indent-tabs-mode:nil
00265   fill-column:79
00266   End:
00267 */
00268 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=79 :
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines