PLearn 0.1
PythonProcessedLearner.cc
Go to the documentation of this file.
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 :
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines