-
Notifications
You must be signed in to change notification settings - Fork 52
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
Add adapters for experiment version control #83
Conversation
4b32985
to
fa4d2a8
Compare
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
There was a problem hiding this 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) |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. 😝
src/orion/core/evc/adapters.py
Outdated
return ret | ||
|
||
|
||
class DimensionDeletion(DimensionAddition): |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/orion/core/worker/trial.py
Outdated
@@ -132,13 +136,15 @@ class Result(Value): | |||
function or of an 'constraint' expression. | |||
""" | |||
|
|||
__slots__ = ('name', '_type', 'value') |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/orion/core/worker/trial.py
Outdated
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') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
tests/unittests/core/test_utils.py
Outdated
@@ -0,0 +1,37 @@ | |||
#!/usr/bin/env python | |||
# -*- coding: utf-8 -*- | |||
"""Test base fonctionnalities of :mod:`orion.core.utils`.""" |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
fa4d2a8
to
ccd41cf
Compare
ccd41cf
to
a0dcf1b
Compare
@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.
a0dcf1b
to
15490fa
Compare
Can this be merged? |
@DatCorno @tsirif It's up to you. You both approve the PR in its current state? |
I am waiting for corno's input. I approved this message.
…On Fri, May 18, 2018, 11:46 Xavier Bouthillier ***@***.***> wrote:
@DatCorno <https://github.com/DatCorno> @tsirif
<https://github.com/tsirif> It's up to you. You both approve the PR in
its current state?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#83 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AD5VAUM4_BV8ci6imsQFBihcHRUJ3_QRks5tzuzugaJpZM4UAk81>
.
|
From what I can see everything's good. |
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
Adapters all have a
to_dict
method which provides sufficientinformation 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).