Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add adapters for experiment version control #83

Merged
merged 3 commits into from
May 18, 2018

Conversation

bouthilx
Copy link
Member

@bouthilx bouthilx commented May 16, 2018

Depends on #86

Why:

Experiment branches because of changes in the configuration or
the user's code. This make experiments incompatible with one another
unless we define adapters such that a branched experiment B can access
trials from parent experiment A.

How:

Adapters have two main methods, forward and backward. The method forward
defines how trials from parent experiment A are filtered or adapted to
be compatible with experiment B. Modifications are only applied at
execution time and are not saved anywhere in the database. Adapters only
provides a view on an experiment.

There is adapters for

  • Dimension addition
  • Dimension deletion
  • Dimension renaming
  • Change of dimension prior
  • Change of algorithm
  • Change of code
  • Combining different adapters

Adapters all have a to_dict method which provides sufficient
information to rebuild the adapter. This is to facilitate save of
adapters in a database and retrieval.

Adapters can be build using the factory class Adapter(**kwargs) or using
Adapter.build(list_of_dicts).

@bouthilx bouthilx changed the base branch from develop to feature/evc May 16, 2018 03:07
@bouthilx bouthilx changed the title Adapter - First draft WIP: Adapter - First draft May 16, 2018
@bouthilx bouthilx changed the title WIP: Adapter - First draft [WIP] Adapter - First draft May 16, 2018
@bouthilx bouthilx changed the title [WIP] Adapter - First draft Add adapters for experiment version control May 17, 2018
@bouthilx bouthilx requested review from tsirif and corneauf May 17, 2018 18:03
@codecov-io
Copy link

codecov-io commented May 17, 2018

Codecov Report

Merging #83 into feature/evc will increase coverage by 0.57%.
The diff coverage is 99.64%.

Impacted file tree graph

@@               Coverage Diff               @@
##           feature/evc      #83      +/-   ##
===============================================
+ Coverage        95.22%   95.79%   +0.57%     
===============================================
  Files               45       48       +3     
  Lines             3725     4284     +559     
  Branches           295      331      +36     
===============================================
+ Hits              3547     4104     +557     
- Misses             143      144       +1     
- Partials            35       36       +1
Impacted Files Coverage Δ
src/orion/core/worker/trial.py 100% <100%> (ø) ⬆️
tests/unittests/core/test_trial.py 100% <100%> (ø) ⬆️
src/orion/core/evc/__init__.py 100% <100%> (ø)
tests/unittests/core/evc/test_adapters.py 100% <100%> (ø)
src/orion/core/evc/adapters.py 98.7% <98.7%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2fdc7b3...15490fa. Read the comment docs.

Copy link
Member

@tsirif tsirif left a comment

Choose a reason for hiding this comment

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

Beautiful code man, I really enjoyed it!

(self.param.name, trial))

adapted_trial = copy.deepcopy(trial)
adapted_trial.params.append(self.param)
Copy link
Member

Choose a reason for hiding this comment

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

Q: Is the default value part of Trial.Param?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, a Trial.Param instance is basically a dimension.sample() turned into a param. So, if we have a default value, Trial.Param is a very convenient and natural way to encapsulate such default value. Furthermore, for DimensionAddition and DimensionDeletion we need to add a Trial.Param object to each trials, so using such encapsulation for default value is very very convenient.

Copy link
Member

Choose a reason for hiding this comment

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

I agree. This means that default_value should be a slotted attribute of Trial.Param?

Copy link
Member

Choose a reason for hiding this comment

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

#88

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it should. The default_value is encapsulated as the value of Trial.Param. You could have either

Trial.Param(dimension.name, dimension.type, dimension.sample())

or

Trial.Param(dimension.name, dimension.type, dimension.default_value)

We don't need to know if it was sampled or if it is the default value. It simply is the value of the param.

Copy link
Member

Choose a reason for hiding this comment

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

Oh I see now. I didn't see it. 😝

return ret


class DimensionDeletion(DimensionAddition):
Copy link
Member

Choose a reason for hiding this comment

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

Oh this is what you meant you could have done with a composition. Yessss

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll rework it a bit.

@@ -106,6 +106,10 @@ def to_dict(self):
)
return ret

def __eq__(self, other):
Copy link
Member

Choose a reason for hiding this comment

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

This should have been done earlier. It is useful fo the hash_name property as well. Shall I right a __hash__ and change the implementation of the hash_name? I think I should. This will be in a future PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes sure if there is hashing that is certainly related to eq.

Copy link
Member

Choose a reason for hiding this comment

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

#89

@@ -132,13 +136,15 @@ class Result(Value):
function or of an 'constraint' expression.
"""

__slots__ = ('name', '_type', 'value')
Copy link
Member

Choose a reason for hiding this comment

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

Oh yes. Sorry for that. The tuple defining the slots can be still in Trial.Value. What needs to be done is to declare __slots__ = () in Trial.Param and Trial.Result, so that a namespace __dict__ won't be created for Trial.Param or Trial.Result. An object instantiated from the inherited classes will be able only have access to the non-blank namespace defined by the base class.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh! I will try that.

allowed_types = ('objective', 'constraint', 'gradient')

class Param(Value):
"""Types for a `Param` can be either an integer (discrete value),
floating precision numerical or a categorical expression (e.g. a string).
"""

__slots__ = ('name', '_type', 'value')
Copy link
Member

Choose a reason for hiding this comment

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

same here

@@ -0,0 +1,37 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Test base fonctionnalities of :mod:`orion.core.utils`."""
Copy link
Member

Choose a reason for hiding this comment

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

Oh typo here, bad me

Copy link
Member Author

Choose a reason for hiding this comment

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

Bad me? I did the typo, no?

@bouthilx bouthilx changed the base branch from feature/evc to develop May 18, 2018 00:32
@bouthilx bouthilx changed the base branch from develop to feature/evc May 18, 2018 00:32
@bouthilx
Copy link
Member Author

@tsirif I'm glad you enjoyed reading it ;) Thanks for your support 😛

I think I addressed everything you mentioned now.

Why:

Experiment branches because of changes in the configuration or
the user's code. This make experiments incompatible with one another
unless we define adapters such that a branched experiment B can access
trials from parent experiment A.

How:

Adapters have two main methods, forward and backward. The method forward
defines how trials from parent experiment A are filtered or adapted to
be compatible with experiment B. Modifications are only applied at
execution time and are not saved anywhere in the database. Adapters only
provides a *view* on an experiment.

There is adapters for

* Dimension addition
* Dimension deletion
* Dimension renaming
* Change of dimension prior
* Change of algorithm
* Change of code
* Combining different adapters

Adapters all have a `to_dict` method which provides sufficient
information to rebuild the adapter. This is to facilitate save of
adapters in a database and retrieval.

Adapters can be build using the factory class Adapter(**kwargs) or using
Adapter.build(list_of_dicts).
Why:

Classes do not inherit __slots__, it needs to be redefined in each
children. The parent class Value's __init__ depends on the combination
of kwargs parsing and __slots__ to raise AttributeError on invalid
arguments. Since Param and Result do not inherint __slots__, they fail
to raise an AttributeError when passed bad attributes.
Why:

We need to compare params very often inside adapters. Also Param
objects with identical name, type and values should be considered as
identical.
@corneauf
Copy link
Member

Can this be merged?

@bouthilx
Copy link
Member Author

@DatCorno @tsirif It's up to you. You both approve the PR in its current state?

@tsirif
Copy link
Member

tsirif commented May 18, 2018 via email

@corneauf
Copy link
Member

From what I can see everything's good.

@bouthilx bouthilx merged commit 74f12a9 into Epistimio:feature/evc May 18, 2018
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.

4 participants