PLearn 0.1
|
00001 // -*- C++ -*- 00002 00003 // PythonProcessedLearner.cc 00004 // 00005 // Copyright (C) 2006 Nicolas Chapados 00006 // 00007 // Redistribution and use in source and binary forms, with or without 00008 // modification, are permitted provided that the following conditions are met: 00009 // 00010 // 1. Redistributions of source code must retain the above copyright 00011 // notice, this list of conditions and the following disclaimer. 00012 // 00013 // 2. Redistributions in binary form must reproduce the above copyright 00014 // notice, this list of conditions and the following disclaimer in the 00015 // documentation and/or other materials provided with the distribution. 00016 // 00017 // 3. The name of the authors may not be used to endorse or promote 00018 // products derived from this software without specific prior written 00019 // permission. 00020 // 00021 // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 00022 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 00023 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 00024 // NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 00025 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 00026 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 00027 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 00028 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 00029 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 00030 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00031 // 00032 // This file is part of the PLearn library. For more information on the PLearn 00033 // library, go to the PLearn Web site at www.plearn.org 00034 00035 // Authors: Nicolas Chapados 00036 00040 #include "PythonProcessedLearner.h" 00041 #include <plearn/base/lexical_cast.h> 00042 #include <plearn/sys/Profiler.h> 00043 00044 namespace PLearn { 00045 using namespace std; 00046 00047 PLEARN_IMPLEMENT_OBJECT( 00048 PythonProcessedLearner, 00049 "Allows preprocessing operations to be carried out by a Python code snippet", 00050 "This PLearner allows embedding a PythonCodeSnippet, and define Python\n" 00051 "operations to be carried out during the computeOutput. The current\n" 00052 "implementation does not attempt to provide a full implementability of the\n" 00053 "PLearner protocol in Python -- only computeOutput is supported for now, and\n" 00054 "the intended use is to specify some fixed preprocessing inside a\n" 00055 "ChainedLearner, such as what would otherwise be performed by a\n" 00056 "VPLPreprocessedLearner.\n" 00057 "\n" 00058 "The Python code snippet (see the option 'code' below) must define the\n" 00059 "following functions:\n" 00060 "\n" 00061 " def getOutputNames(train_fieldnames, inputsize, targetsize, weightsize, extrasize):\n" 00062 " \"\"\"Return the names of the outputs computed by the learner, namely\n" 00063 " the implementation of the PLearner::getOutputNames() function.\n" 00064 " This is called every time setTrainingSet() is called on the PLearner.\"\"\"\n" 00065 "\n" 00066 " def computeOutput(input):\n" 00067 " \"\"\"Return the result of the computation. The size of the output\n" 00068 " vector must be the same as the number of elements returned by the\n" 00069 " getOutputNames() function.\"\"\"\n" 00070 "\n" 00071 "The Python code snippet has access to the following (injected) interface:\n" 00072 "\n" 00073 "- getParams(): return the map with the contents of the 'params' option.\n" 00074 "\n" 00075 "- getParam(key): return the string value of params[key].\n" 00076 "\n" 00077 "- setParam(key,value): set a new value for the 'params' element\n" 00078 " corresponding to key.\n" 00079 "\n" 00080 "NOTE ON RELOADED MODELS. If a PythonProcessedLearner is saved to disk and\n" 00081 "later reloaded, it is likely that a setTrainingSet will not be called on\n" 00082 "the model (which would establish the outputsize through a call to\n" 00083 "getOutputNames). If this happens, the PythonProcessedLearner looks in its\n" 00084 "'params' to locate the following entries:\n" 00085 "\n" 00086 "- fieldnames\n" 00087 "- inputsize\n" 00088 "- targetsize\n" 00089 "- weightsize\n" 00090 "- extrasize\n" 00091 "\n" 00092 "If it can find them all, the outputsize() function automatically invokes\n" 00093 "getOutputNames to establish the proper dimensions of the learner. Note\n" 00094 "that your Python code should normally manually update these options within\n" 00095 "the getOutputNames() function to ensure that they are saved with the\n" 00096 "learner after training.\n" 00097 ); 00098 00099 PythonProcessedLearner::PythonProcessedLearner() 00100 : m_code(""), 00101 python() 00102 { } 00103 00104 void PythonProcessedLearner::declareOptions(OptionList& ol) 00105 { 00106 declareOption( 00107 ol, "code", &PythonProcessedLearner::m_code, OptionBase::buildoption, 00108 "The Python code snippet. The functions described in the class\n" 00109 "documentation must be provided. Note that, after an initial build(),\n" 00110 "changing this string calling build() again DOES NOT result in the\n" 00111 "recompilation of the code.\n"); 00112 00113 declareOption( 00114 ol, "params", &PythonProcessedLearner::m_params, OptionBase::buildoption, 00115 "General-purpose parameters that are injected into the Python code\n" 00116 "snippet and accessible via the getParam/setParam functions. Can be\n" 00117 "used for passing processing arguments to the Python code.\n"); 00118 00119 // Now call the parent class' declareOptions 00120 inherited::declareOptions(ol); 00121 } 00122 00123 00124 //##### build ############################################################### 00125 00126 void PythonProcessedLearner::build() 00127 { 00128 inherited::build(); 00129 build_(); 00130 } 00131 00132 void PythonProcessedLearner::build_() 00133 { 00134 // First step, compile the Python code 00135 compileAndInject(); 00136 PLASSERT( python ); 00137 00138 // Ensure that the required functions are defined 00139 const char* FUNC_ERR = "%s: the Python code snippet must define the function '%s'"; 00140 if (! python->isInvokable("getOutputNames")) 00141 PLERROR(FUNC_ERR, __FUNCTION__, "getOutputNames"); 00142 if (! python->isInvokable("computeOutput")) 00143 PLERROR(FUNC_ERR, __FUNCTION__, "computeOutput"); 00144 } 00145 00146 00147 void PythonProcessedLearner::makeDeepCopyFromShallowCopy(CopiesMap& copies) 00148 { 00149 inherited::makeDeepCopyFromShallowCopy(copies); 00150 00151 deepCopyField(m_outputnames, copies); 00152 00153 // Recompile the Python code in a fresh environment 00154 python = 0; 00155 compileAndInject(); 00156 } 00157 00158 00159 void PythonProcessedLearner::setTrainingSet(VMat training_set, bool call_forget) 00160 { 00161 inherited::setTrainingSet(training_set, call_forget); 00162 VMat trset = getTrainingSet(); 00163 PLASSERT( trset ); 00164 PLASSERT( python ); 00165 00166 TVec<string> fields = trset->fieldNames(); 00167 int inputsize = trset->inputsize(); 00168 int targetsize = trset->targetsize(); 00169 int weightsize = trset->weightsize(); 00170 int extrasize = trset->extrasize(); 00171 00172 m_outputnames = python->invoke("getOutputNames", fields, inputsize, targetsize, 00173 weightsize, extrasize).as< TVec<string> >(); 00174 } 00175 00176 00177 int PythonProcessedLearner::outputsize() const 00178 { 00179 PLASSERT( python ); 00180 00181 // Reloaded model for which we have not yet established the outputsize 00182 if (m_outputnames.size() == 0) 00183 const_cast<PythonProcessedLearner*>(this)->setOutputNamesFromParams(); 00184 00185 return m_outputnames.size(); 00186 } 00187 00188 void PythonProcessedLearner::forget() 00189 { 00190 // no-op in current version 00191 } 00192 00193 void PythonProcessedLearner::train() 00194 { 00195 // No-op in current version 00196 } 00197 00198 00199 void PythonProcessedLearner::computeOutput(const Vec& input, Vec& output) const 00200 { 00201 PLASSERT( python ); 00202 Profiler::start("PythonProcessedLearner"); 00203 Vec processed = python->invoke("computeOutput", input).as<Vec>(); 00204 output << processed; 00205 Profiler::end("PythonProcessedLearner"); 00206 } 00207 00208 void PythonProcessedLearner::computeCostsFromOutputs(const Vec& input, const Vec& output, 00209 const Vec& target, Vec& costs) const 00210 { 00211 // No-op in current version 00212 } 00213 00214 TVec<string> PythonProcessedLearner::getTestCostNames() const 00215 { 00216 return TVec<string>(); 00217 } 00218 00219 TVec<string> PythonProcessedLearner::getTrainCostNames() const 00220 { 00221 return TVec<string>(); 00222 } 00223 00224 TVec<string> PythonProcessedLearner::getOutputNames() const 00225 { 00226 return m_outputnames; 00227 } 00228 00229 00230 //##### getParams ########################################################### 00231 00232 PythonObjectWrapper 00233 PythonProcessedLearner::getParams(const TVec<PythonObjectWrapper>& args) const 00234 { 00235 if (args.size() != 0) 00236 PLERROR("PythonProcessedLearner::getParams: expected 0 argument; got %d", 00237 args.size()); 00238 00239 return PythonObjectWrapper(m_params); 00240 } 00241 00242 00243 //##### getParam ############################################################ 00244 00245 PythonObjectWrapper 00246 PythonProcessedLearner::getParam(const TVec<PythonObjectWrapper>& args) const 00247 { 00248 if (args.size() != 1) 00249 PLERROR("PythonProcessedLearner::getParam: expected 1 argument; got %d", 00250 args.size()); 00251 string key = args[0].as<string>(); 00252 map<string,string>::const_iterator found = m_params.find(key); 00253 if (found != m_params.end()) 00254 return PythonObjectWrapper(found->second); 00255 else 00256 return PythonObjectWrapper(); // None 00257 } 00258 00259 00260 //##### setParam ############################################################ 00261 00262 PythonObjectWrapper 00263 PythonProcessedLearner::setParam(const TVec<PythonObjectWrapper>& args) 00264 { 00265 if (args.size() != 2) 00266 PLERROR("PythonProcessedLearner::setParam: expected 2 arguments; got %d", 00267 args.size()); 00268 string key = args[0].as<string>(); 00269 string value = args[1].as<string>(); 00270 m_params[key] = value; 00271 return PythonObjectWrapper(); 00272 } 00273 00274 00275 //##### compileAndInject #################################################### 00276 00277 void PythonProcessedLearner::compileAndInject() 00278 { 00279 if (! python) { 00280 python = new PythonCodeSnippet(m_code); 00281 PLASSERT( python ); 00282 python->build(); 00283 python->inject("getParams", this, &PythonProcessedLearner::getParams); 00284 python->inject("getParam", this, &PythonProcessedLearner::getParam); 00285 python->inject("setParam", this, &PythonProcessedLearner::setParam); 00286 } 00287 } 00288 00289 00290 //##### setOutputNamesFromParams ############################################ 00291 00292 void PythonProcessedLearner::setOutputNamesFromParams() 00293 { 00294 PLASSERT( python ); 00295 map<string,string>::iterator NOT_FOUND = m_params.end(); 00296 if (m_params.find("fieldnames") == NOT_FOUND || 00297 m_params.find("inputsize") == NOT_FOUND || 00298 m_params.find("targetsize") == NOT_FOUND || 00299 m_params.find("weightsize") == NOT_FOUND || 00300 m_params.find("extrasize") == NOT_FOUND ) 00301 PLERROR("%s: Cannot find one of {'fieldnames','inputsize','targetsize',\n" 00302 "'weightsize','extrasize'} in params in order to set outputnames\n" 00303 "from params", __FUNCTION__); 00304 00305 string fieldnames = m_params["fieldnames"]; 00306 search_replace(fieldnames, "'", "\""); 00307 TVec<string> fields = lexical_cast< TVec<string> >(fieldnames); 00308 int inputsize = lexical_cast<int>(m_params["inputsize" ]); 00309 int targetsize = lexical_cast<int>(m_params["targetsize"]); 00310 int weightsize = lexical_cast<int>(m_params["weightsize"]); 00311 int extrasize = lexical_cast<int>(m_params["extrasize" ]); 00312 00313 m_outputnames = python->invoke("getOutputNames", fields, inputsize, targetsize, 00314 weightsize, extrasize).as< TVec<string> >(); 00315 } 00316 00317 } // end of namespace PLearn 00318 00319 00320 /* 00321 Local Variables: 00322 mode:c++ 00323 c-basic-offset:4 00324 c-file-style:"stroustrup" 00325 c-file-offsets:((innamespace . 0)(inline-open . 0)) 00326 indent-tabs-mode:nil 00327 fill-column:79 00328 End: 00329 */ 00330 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=79 :