Skip to content

Add cookbook of cross validation with pipeline - #4380

Merged
karlnapf merged 39 commits into
shogun-toolbox:developfrom
vinx13:cookbook/pipeline_xval
Jan 31, 2019
Merged

Add cookbook of cross validation with pipeline#4380
karlnapf merged 39 commits into
shogun-toolbox:developfrom
vinx13:cookbook/pipeline_xval

Conversation

@vinx13

@vinx13 vinx13 commented Jul 20, 2018

Copy link
Copy Markdown
Member

data pr shogun-toolbox/shogun-data#164
also need to merge #4377 first

@@ -0,0 +1,34 @@
============================
Cross Validation on Pipeline

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on "a" pipeline

Cross Validation on Pipeline
============================

In this example, we illustrate how to use cross-validation with :sgclass:`CPipeline`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe link to some cross-validation cookbook. There is some way to link to notebooks, I think MKL does this

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha it is just below, nevermind :)

-------
Example
-------
We'll use as example a binary classification problem solvable by a pipeline consisted of a transformer :sgclass:`CPruneVarSubMean` and a machine :sgclass:`CLibLinear`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly weight English:
We demonstrate a pipeline consisting of a transformer ..., and LibLinear for binary classification.
(maybe also link to the liblinear cookbook)

-------
We'll use as example a binary classification problem solvable by a pipeline consisted of a transformer :sgclass:`CPruneVarSubMean` and a machine :sgclass:`CLibLinear`.

Imagine we have files with training data. We create :sgclass:`CDenseFeatures` (here 64 bit floats aka RealFeatures) as

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is copy pasted, but this is now outdates when we use factories.
Just say: "We create CFeatures and CLabels via loading from files"

.. sgexample:: cross_validation_pipeline:create_features


We use :sgclass:`CPruneVarSubMean` to normalize the features and then use :sgclass:`CLibLinear` for classification.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence basically repeats the intro, so I would just remove it



We use :sgclass:`CPruneVarSubMean` to normalize the features and then use :sgclass:`CLibLinear` for classification.
The transformer and the machine are chained as a :sgclass:`CPipeline`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We create a Cpipeline, and chain the transformer and the classifier.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, I would mention that "we first chain all transformers, and then finalize the pipeline with the classifier" (since you use "then")

.. sgexample:: cross_validation_pipeline:create_pipeline

Next, we initialize a splitting strategy :sgclass:`CStratifiedCrossValidationSplitting` to divide the dataset into :math:`k-` folds for the :math:`k-` fold cross validation.
We also have to decide on an evaluation criterion class (from :sgclass:`CEvaluation`) to evaluate the performance of the trained models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see CEvaluation

.. sgexample:: cross_validation_pipeline:create_pipeline

Next, we initialize a splitting strategy :sgclass:`CStratifiedCrossValidationSplitting` to divide the dataset into :math:`k-` folds for the :math:`k-` fold cross validation.
We also have to decide on an evaluation criterion class (from :sgclass:`CEvaluation`) to evaluate the performance of the trained models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last word model (singular)

Next, we initialize a splitting strategy :sgclass:`CStratifiedCrossValidationSplitting` to divide the dataset into :math:`k-` folds for the :math:`k-` fold cross validation.
We also have to decide on an evaluation criterion class (from :sgclass:`CEvaluation`) to evaluate the performance of the trained models.
In this case, we use :sgclass:`CAccuracyMeasure`.
We then instantiate :sgclass:`CCrossValidation` and set the number of cross validation's runs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also mention something like "The pipeline instance behaves just like a machine and this can be directly passed to CCrossValidation"

#![create_pipeline]

#![create_cross_validation]
StratifiedCrossValidationSplitting splitting_strategy(labels_train, 2)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont we have factories for those things?
I would prefer if all newly added examples would fully use the new api so we dont have to refactor later

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have factory for CSplittingStrategy

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind creating one? That should be pretty easy!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karlnapf since there are multiple subclasses of CSplittingStrategy, are we going to do some string comparison by name?

@karlnapf karlnapf Jul 23, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check the factory machine in factory.h, you just have to add some macro lines
the call should be splitting_strategy("StratifiedCrossValidationSplitting", labels=labels_train, k=2)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we need to do initialization in init() method, instead of the constructor
e.g.

CStratifiedCrossValidationSplitting::CStratifiedCrossValidationSplitting(

this need refactor right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes, this will be moved into the method that is called from outside, i.e. build_subsets ... putting it into a helper method makes sense

Transformer subMean = transformer("PruneVarSubMean")
Machine svm = machine("LibLinear")

PipelineBuilder builder()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, could you pls use a factory for this. We dont want to use constructors in the examples

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you explain how to use factory here?
we could create a factory PipelineBuilder* pipeline_builder(), or?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes exactly, though I would just name it pipeline

#![create_cross_validation]
StratifiedCrossValidationSplitting splitting_strategy(labels_train, 2)
Evaluation evaluation_criteron = evaluation("AccuracyMeasure")
CrossValidation cross(pipeline, feats_train, labels_train, splitting_strategy, evaluation_criteron, False)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

factory if possible (this might be more tricky)

@karlnapf karlnapf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great example! I made some comments

@karlnapf

Copy link
Copy Markdown
Member

Let me know if the factory creating worked....

Machine svm = machine("LibLinear")

PipelineBuilder builder()
PipelineBuilder builder = pipeline()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly like this! :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW why is the C++ ype not just called Pipeline and the Machine called PipelineMachine?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think Pipeline and PipelineMachine might be a bit confusing, while PipelineBuilder can indicate its usage as a builder

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, I dont really agree, I think then should actually return CMachine (since the subsequent object will be used in this fashion) ... why do we need to know that a machine is a pipeline if it behaves as a machine?
And then Pipeline is the thing that builds the stuff.
Makes for a cleaner API imo

@vigsterkr @lisitsyn @iglesias what are your thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karlnapf are we talking now about PipelineMachine and PipelineBuilder, or why then returns Pipeline* ? :) imo those are different things or?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So CPipeline -> CPipelineMachine, CPipelineBuilder ->CPipeline, then returns CMachine

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo builder is just clearer but i dont have any strong feelings about it...
note my second comment about extraction and observation of pipeline.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes the stages thing is a problem

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e. until we cannot register and cleanly extract stages from a pipeline this sort of explicit exposure is required :) otherwise the whole thing becomes totally opaque once built

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it! thx for clarifying.
I still would slightly prefer different names (CPipelineMachine, CPipeline) but it is only a minor difference, also no strong feelings. Can leave it as it is

@karlnapf

Copy link
Copy Markdown
Member

rebase and merge :)


.. sgexample:: cross_validation_pipeline:create_features

We first chain all transformers, and then finalize the pipeline with the classifier.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it one as said above? Then change or remove "all".

Labels labels_test = labels(f_labels_test)
#![create_features]

#![create_pipeline

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing ]?


PipelineBuilder builder = pipeline()
builder.over(subMean)
Pipeline pipeline = builder.then(svm)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the API contain these two builder and pipeline concepts? What about just a pipeline where steps are added (and of course the order of addition matters).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea is to separate construction of pipeline into a single class so that the pipeline object is immutable, and then we don't need to verify elements of pipeline everytime

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty much the same thing that got me confused about the builder vs the pipeline.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the second one, Pipeline can be Machine here because that's enough, we use it as machine in xval. however, if we want to get elements in the pipeline, we still need Pipeline type for now

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about adding a factory for it? So users can call
PipelineMachine(machine)?
Alternatively, for the c++ folks, there is as

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that there might be mutability reason justifying two types. Why both part of the interface though?

@vigsterkr

vigsterkr commented Jul 27, 2018 via email

Copy link
Copy Markdown
Member

@vigsterkr

vigsterkr commented Jul 27, 2018 via email

Copy link
Copy Markdown
Member

@iglesias

iglesias commented Jul 27, 2018 via email

Copy link
Copy Markdown
Collaborator

@vinx13
vinx13 force-pushed the cookbook/pipeline_xval branch from 0e3b84b to f8120b9 Compare July 30, 2018 12:22

PipelineBuilder builder = pipeline()
builder.over(subMean)
Machine pipeline = builder.then(svm)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this overloading of var name and the factory work? Just asking

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this overload the factory name, the factory works in this case. thanks for letting me know.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the flow of this, so good to merge from my side

@vinx13
vinx13 force-pushed the cookbook/pipeline_xval branch from f8120b9 to 5bd958c Compare August 8, 2018 13:15
@karlnapf

karlnapf commented Aug 8, 2018

Copy link
Copy Markdown
Member

Can you rebase data and then we can merge this as well!

@vinx13
vinx13 force-pushed the cookbook/pipeline_xval branch 2 times, most recently from 7651aa9 to 8752675 Compare August 14, 2018 12:00
@karlnapf

Copy link
Copy Markdown
Member

Shall we get this in soon? :)

@vinx13

vinx13 commented Nov 12, 2018

Copy link
Copy Markdown
Member Author

@karlnapf As I can remember, previously we faced the choice on whether pipeline builder should return CMachine* or CPipeline*. The first choice, returning CMachine* and then providing a pipeline factory, works though being ugly. According to @vigsterkr 's idea, in my last commit I tried to make CPipeline as base class, but got some different errors on travis

what():  �[1;31m[ERROR]�[0m In file /opt/shogun/src/shogun/base/SGObject.h line 369: Cannot put parameter CrossValidation::machine of type shogun::CMachine*, incompatible provided type shogun::CPipeline*.

Maybe @lisitsyn has idea?

@karlnapf

Copy link
Copy Markdown
Member

Ah I remember now.
Well the real question is whether Pipeline should be part of the modular interfaces (i.e. the base types) or not.

@karlnapf karlnapf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool that it works. I like that this is a general solution to the problem, despite being a not super elegant.

Any further thoughts on this idea of implicitly casting a pipeline to CMachine via an argument of a factory? @lisitsyn @vigsterkr

@vinx13
vinx13 force-pushed the cookbook/pipeline_xval branch from 6fe7b65 to 7a00f6e Compare January 29, 2019 09:22
@vinx13
vinx13 force-pushed the cookbook/pipeline_xval branch from 7a00f6e to d2a3a74 Compare January 29, 2019 09:25
"get_real_vector": "$object.get($arguments)",
"get_real_matrix": "$object.get($arguments)"
"get_real_matrix": "$object.get($arguments)",
"put_machine": "$object.put($arguments)"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will not work as we cannot extract the arguments yet and you would need to pass the second argument to the machine factory

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can do this once #4490 is solved


PipelineBuilder builder = pipeline()
builder.over(subMean)
Machine pipeline = builder.then(svm)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type here should be "Pipeline" as that is what the builder returns (and now Pipeline is part of swig as well)

SplittingStrategy strategy = splitting_strategy("StratifiedCrossValidationSplitting", labels=labels_train, num_subsets=2)
Evaluation evaluation_criterion = evaluation("AccuracyMeasure")
MachineEvaluation cross = machine_evaluation("CrossValidation", features=feats_train, labels=labels_train, splitting_strategy=strategy, evaluation_criterion=evaluation_criterion, autolock=False, num_runs=2)
cross.put_machine("machine", pipeline)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to cross.put(machine(pipeline)) and it should work
we can change that to put_machine later once #4490 is in

#![create_cross_validation]
SplittingStrategy strategy = splitting_strategy("StratifiedCrossValidationSplitting", labels=labels_train, num_subsets=2)
Evaluation evaluation_criterion = evaluation("AccuracyMeasure")
MachineEvaluation cross = machine_evaluation("CrossValidation", features=feats_train, labels=labels_train, splitting_strategy=strategy, evaluation_criterion=evaluation_criterion, autolock=False, num_runs=2)

@karlnapf karlnapf Jan 29, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OT: Can I suggest that we don't provide features and labels to crossvalidation but instead as parameters of CMachineEvaluation::evaluate(CFeatures*, CLabels*) ? Different PR though
@lisitsyn @vigsterkr @iglesias

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey mate, I can't say much based on this snippet. What's your point? Also based on the last line, CrossValidation here seems to be a type of MachineEvaluation, no?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d like to pass it as function arguments if the evaluation function rather than as fields before that....

@karlnapf karlnapf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

;)

@karlnapf

Copy link
Copy Markdown
Member

Great that this now works !
Shall we address the other comments and then finally merge this?

@karlnapf karlnapf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think only some minor things are left

Comment thread examples/meta/generator/targets/cpp.json
Comment thread src/interfaces/python/swig_typemaps.i Outdated
"""
_obj = getattr(sys.modules[__name__], object_name)
def _internal_factory(name, **kwargs):
def _internal_factory(name, *args, **kwargs):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed abymore

Comment thread src/shogun/machine/Pipeline.cpp Outdated
{
return get_machine()->get_machine_problem_type();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can’t this be in factory?

->then(machine);
->then(machine)
->as<CPipeline>();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that needed?

@karlnapf

karlnapf commented Jan 31, 2019

Copy link
Copy Markdown
Member

Something about include paths is broken in the example ... sorry

@@ -0,0 +1,33 @@
============================

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor. the ==== should be as long as the text

We also have to decide on an evaluation criterion class (see :sgclass:`CEvaluation`) to evaluate the performance of the trained model.
In this case, we use :sgclass:`CAccuracyMeasure`.
We then instantiate :sgclass:`CCrossValidation` and set the number of cross validation's runs.
The pipeline instance behaves just like a :sgclass:`CMachine` and this can be directly passed to :sgclass:`CCrossValidation`.

@karlnapf karlnapf Jan 31, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not exactly true anymore, as we need to call the factory now. I suggest we just avoid this and say
"We next create a cross-validation instance and pass the generated pipeline."

Comment thread src/shogun/util/factory.h
* See also CPipelineBuilder and CPipeline.
* @return new instance of CPipelineBuilder
*/
CPipelineBuilder* pipeline()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gf712 would you be up for making the definitions of the factory methods a bit neater? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure! what do you have in mind?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, just checked, not sure factory.h can even be made that much simpler?

@karlnapf

Copy link
Copy Markdown
Member

Cool!
Thanks for the big amount of work. It is awesome to have this in now :)

@karlnapf
karlnapf merged commit ab0aa3c into shogun-toolbox:develop Jan 31, 2019
vigsterkr pushed a commit to vigsterkr/shogun that referenced this pull request Mar 9, 2019
* Add meta example and cookbook of pipeline cross validation
ktiefe pushed a commit to ktiefe/shogun that referenced this pull request Jul 30, 2019
* Add meta example and cookbook of pipeline cross validation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants