-
-
Notifications
You must be signed in to change notification settings - Fork 1k
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
AnyParameterProperties refactor #4412
Conversation
* added mask_attribute to keep track of parameter availabilities * updated constructors and getters * added some documentation
`AnyParameterProperties::HYPERPARAMETER | AnyParameterProperties::GRADIENT_PARAM | AnyParameterProperties::MODELSELECTION_PARAM` is equivalent to all parameter property flags being false (`0x00000000`)
modelselection_param is actually hyperparameter now and model_param is a seperate definition, e.g. bias and weights
src/shogun/base/AnyParameter.h
Outdated
AnyParameterProperties() | ||
: m_description(), m_model_selection(MS_NOT_AVAILABLE), | ||
m_gradient(GRADIENT_NOT_AVAILABLE) | ||
static const int32_t HYPERPARAMETER = 0x00000001; |
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.
What about making it an int enum?
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.
Ah that's neat! I'll implement that.
src/shogun/base/AnyParameter.h
Outdated
return m_gradient; | ||
EGradientAvailability return_val; | ||
if (m_mask_attribute & GRADIENT_PARAM) | ||
return_val = EGradientAvailability::GRADIENT_AVAILABLE; |
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.
it makes sense to return immediately without return_val
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.
Do you mean with a one liner like this?
return static_cast<EGradientAvailability>((m_mask_attribute & GRADIENT_PARAM) > 0);
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.
Yeah maybe with some helper function so it looks shorter? There is some repeated code in these flags.
src/shogun/base/AnyParameter.h
Outdated
/** @brief Class AnyParameterProperties keeps track of parameter properties. | ||
* | ||
* Currently there are three types of parameters: | ||
* -# HYPERPARAMETER: the parameter determines how the training is |
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.
Pls don’t put such things into the docs, they outdated at light speed.
Better describe that there are attributes, maybe an example, but not losing them all
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.
OK!
src/shogun/base/AnyParameter.h
Outdated
: m_description(), m_model_selection(MS_NOT_AVAILABLE), | ||
m_gradient(GRADIENT_NOT_AVAILABLE) | ||
static const int32_t HYPERPARAMETER = 0x00000001; | ||
static const int32_t GRADIENT_PARAM = 0x00000010; |
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.
You can also use the << operator to shift the 1 a desired number of digits
See the stack overflow post
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 had a look at the post, and the Enum with bit operations is pretty cool. I just thought that the hexadecimal representation is more readable than doing the bit shifts, no?
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.
at the end of the day, it is style. You can either define the mask as you did with hexadecimal as you did there. Or, instead, you can generate those masks on the fly using a shift operator, and then the HYPERPARAMETER
constant is the number of bits that you shift, i.e.
my_mask &&
(1 << HYPERPARAMETER) &&
(1 << GRADIENT_AVAILABLE) &&
The latter has the advantage that somebody reading the code never is exposed to hexadecimal numbers, nor powers or two (which can be confusing). On the other hand, masking operations (as what I wrote above) becomes a bit more notation heavy. Idk what is best to be honest. I think the shifting is more standard and therefore I think we should follow that.
Ah one thing I think is good about the shifting approach is that you can change the size of the mask easily. You are using a 32bit int above, but you only write down the last 8 bits, which is weird. Imagine you now changed the mask to 64 bit. Then the hexadecimals would be quite long, whereas the shifts wouldnt be.
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.
OK! I think you're right, I have changed the class members to use bit shifts, it is a bit cleaner.
src/shogun/base/AnyParameter.h
Outdated
static const int32_t GRADIENT_PARAM = 0x00000010; | ||
static const int32_t MODEL_PARAM = 0x00000100; | ||
|
||
/** default constructor where all parameter types are switched to false |
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.
Minor: sentences start with capital letters
src/shogun/base/AnyParameter.h
Outdated
{ | ||
} | ||
/** legacy constructor */ |
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 would remove that. Until we haven't removed it, it is not legacy.
src/shogun/base/AnyParameter.h
Outdated
} | ||
|
||
private: | ||
std::string m_description; | ||
EModelSelectionAvailability m_model_selection; | ||
EGradientAvailability m_gradient; | ||
/** mask to store all param flags*/ | ||
int32_t m_mask_attribute; |
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.
what about calling it "attribute_mask" ? :)
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.
Yup, I just noticed that I switched the order...
src/shogun/base/AnyParameter.h
Outdated
if (hyperparameter) | ||
m_mask_attribute |= HYPERPARAMETER; | ||
} | ||
/** mask constructor */ |
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.
if you add doxygen docs, pls document all parameters
minor: capital M
src/shogun/base/AnyParameter.h
Outdated
: m_description(description), m_model_selection(model_selection), | ||
m_gradient(gradient) | ||
{ | ||
m_mask_attribute = 0x00000000; | ||
if (model_selection) |
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.
wouldnt this work?
m_attribute_mask =
(MODEL_PARAM & model_selection) |
(GRADIENT_PARAM & gradient) |
(HYPERPARAMETER & hyperparameter)
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.
or in the new style
m_attribute_mask =
((1<< MODEL_PARAM) & model_selection) |
((1<< GRADIENT_PARAM) & gradient) |
((1<<HYPERPARAMETER) & hyperparameter)
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.
True, that would be more concise. Is it worth writing a static member that is the default, i.e. 0?
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.
If the default is 0, then we don't need that.
I would be explicit about a non-zero default, and add a private init
method that sets it and that is called by all ctors.
src/shogun/base/AnyParameter.h
Outdated
: m_description(), m_model_selection(MS_NOT_AVAILABLE), | ||
m_gradient(GRADIENT_NOT_AVAILABLE) | ||
static const int32_t HYPERPARAMETER = 0x00000001; | ||
static const int32_t GRADIENT_PARAM = 0x00000010; |
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 would call this GRADIENT_AVAILABLE
src/shogun/base/AnyParameter.h
Outdated
AnyParameterProperties() | ||
: m_description(), m_model_selection(MS_NOT_AVAILABLE), | ||
m_gradient(GRADIENT_NOT_AVAILABLE) | ||
static const int32_t HYPERPARAMETER = 0x00000001; |
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.
can we keep the naming self-consistent
HYPER_PARAM
or
HYPER_PARAMETER
or name the other one
MODELPARAMETER
or rather
MODEL_PARAMETER
Actually, in favour of short and concise names, why dont we use
HYPER
MODEL
GRADIENT
?
And then the constant itself can have a doxygen string to say what that means?
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.
OK! But hyperparameter is a term that could be misinterpreted if it written as HYPER, no?
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 think it is pretty clear since the class is ending with "Parameter", so everything in there is one ....
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.
The name MODEL causes conflicts if we use an enum, so will have to use something more specific, or just have these values as static class members of AnyParameterProperties.
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, please don't define them globally, but as a static member
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.
would it make sense to use an enum within the class scope?
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.
fine as well
src/shogun/base/AnyParameter.h
Outdated
{ | ||
} | ||
|
||
/** description getter */ |
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.
superflous doc, as the method name explains it, i would just remove it
src/shogun/base/AnyParameter.h
Outdated
{ | ||
} | ||
|
||
/** description getter */ | ||
std::string get_description() const |
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.
minor what about returning a const string reference
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, I think we could do that. It seems to be pretty much standard now to return const references for std::string. It can sometimes lead to odd behaviours, but I don't think we need to worry about those..
http://thesyntacticsugar.blogspot.com/2011/09/evil-side-of-returning-member-as-const.html
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, but the first rule of the C++ committee is:
"The C++ language does not prevent people from shooting themselves in their feet" ;)
src/shogun/base/AnyParameter.h
Outdated
std::string get_description() const | ||
{ | ||
return m_description; | ||
} | ||
|
||
/** model selection flag getter */ |
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, would remove the comment
src/shogun/base/SGObject.h
Outdated
@@ -676,7 +676,10 @@ class CSGObject | |||
void watch_param( | |||
const std::string& name, T* value, | |||
AnyParameterProperties properties = AnyParameterProperties( | |||
"Unknown parameter", MS_NOT_AVAILABLE, GRADIENT_NOT_AVAILABLE)) | |||
"Unknown parameter", | |||
AnyParameterProperties::HYPERPARAMETER | |
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.
instead of doing this here, I think it might be better to offer a constructor that does not take any properties, and then set the default values inside there. We need to discuss clever default values as well btw
src/shogun/base/SGObject.h
Outdated
@@ -715,7 +721,10 @@ class CSGObject | |||
void watch_param( | |||
const std::string& name, T** value, S* rows, S* cols, | |||
AnyParameterProperties properties = AnyParameterProperties( | |||
"Unknown parameter", MS_NOT_AVAILABLE, GRADIENT_NOT_AVAILABLE)) | |||
"Unknown parameter", |
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 with the description, I would offer a ctor that does not ask for one, and then set it to the empty string or "No description given" in the ctor, but not here in SGObject.h
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.
Great first start. We need a few changes and clean-ups, but this is essentially doing what we want already.
Maybe we can have a test that checks the bitmasking for one of the parameters ... just to be on the safe side ... :)
* also shorted class member names * changed default constructor to give default description
src/shogun/base/AnyParameter.h
Outdated
* -# GRADIENT_PARAM: the parameter is used in gradient updates | ||
* -# MODEL_PARAM: model parameter that is trained, e.g. weight and bias | ||
* vectors and bias | ||
* The parameter properties can be either true or false. |
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.
like!
Maybe also mention descriptions in the first sentence " ... of parameter meta information, such as properties and descriptions"
src/shogun/base/AnyParameter.h
Outdated
static const int32_t MODEL_PARAM = 0x00000100; | ||
static const int32_t HYPER = 1u << 0; | ||
static const int32_t GRADIENT = 1u << 1; | ||
static const int32_t MODEL = 1u << 2; |
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.
ah wait. the thing I had in mind was
static const int HYPER = 1;
and then
mask = (1 << HYPER) | (1 << MODEL);
This is now difference, as you are effectively storing masks themselves in these const members, not the number of bit shifts. Or am I wrong? Let me stare a bit more to understand
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.
Do you mean:
static const int HYPER = 0;
static const int GRADIENT = 1;
static const int MODEL = 2;
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, but start at 1
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.
OK, if we have:
static const int HYPER = 1;
static const int GRADIENT = 2;
static const int MODEL = 3;
bool model_selection = true;
bool gradient = true;
int mask = ((1 << HYPER) & model_selection) | ((1 << GRADIENT) & gradient);
Then the above mask will always be false/0 no?
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.
Do you mean more like this:
int mask = (model_selection << HYPER) | (gradient << GRADIENT)
which is the equivalent of: shift bit at position X (1,2 or 3 in this case) if the function parameter is true.
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.
Ah now I remember why I did it this way.. Basically if you want to provide an interface to create your own mask like this
AnyParameterProperties("My description",
AnyParameterProperties::MODEL |
AnyParameterProperties::GRADIENT)
you need to have the values of the static member with the shift. Unless I am missing something?
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.
Not sure what you mean, but what I have seen (and been doing) would result in
AnyParameterProperties("My description",
(1 << AnyParameterProperties::MODEL) |
(1 << AnyParameterProperties::GRADIENT))
It is not as neat, you are totally right!. BUT it can be hidden quite easily either.
I mean, it is your code, Ill leave it to you. Not sure whether it is possible to say what is better now, we will have to commit to something and then see if it works :) So go ahead with the above solution and then we see where we get
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.
OK! Btw why does it need to start at 1, instead of 0?
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.
ah, no that was nonsense (just needed to make sure that you never get an all zero mask)
BTW I also realised why the micro-controller people store the number of bits rather than the full masks: it uses less memory (which is not really relevant here, but still good to think about it for a moment).
byte HYPER = 2
...
byte BLA = 255
so with an 8bit integer, you can do 255 attributes if you generate the masks on the fly a la (1 << HYPER)
. If you wanted to store the mask for each attribute instead (i.e. HYPER = 0x00000010
(each mask is 8bit), you would have to store 255 * 8bit, which is quite a difference , especially if you have only 4k memory as on an AVR ;)
How many parameters are registered during a shogun run? Let's say 100 is realistic. Then we have 100 byte usage if we store only the offset, and 2.5kB if we use the masks. In a data heavy environment, and since we won't register many many more parameters this doesn't matter. For 1000 parameters it is still ok, 10^6 will result in some pretty big differences.
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.
Lets typedef the actual type so we can change later.
src/shogun/base/AnyParameter.h
Outdated
m_mask_attribute |= GRADIENT_PARAM; | ||
if (hyperparameter) | ||
m_mask_attribute |= HYPERPARAMETER; | ||
m_attribute_mask = (hyperparameter << 0 & HYPER) | |
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 was more thinking
(1 << HYPER) & model_selection | (1 << GRADIENT) && gradient
etc
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 that's too low-level. Can we introduce a constexpr function for that?
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.
yeah actually good point! :)
Any news here? :) |
I did another round of clean up, but I haven't pushed it yet. Got a bit distracted looking into constexpr, as I have never used them before in my own code.. As for using a typedef I am not sure how it would be used as we are storing data in a int mask..? |
I googled a bit and found this, maybe interesting? |
So do you think we could just copy this? https://www.justsoftwaresolutions.co.uk/files/bitmask_operators.hpp |
Yeah ok, I think this is cool (I think the first simple version is fine)
|
Is it worth overloading the enum operators in a separate header file or does it make sense to keep it all in |
Is it worth overloading the enum operators in a separate header file or does it make sense to keep it all in |
As it is general purpose code, I would copy it to |
E.g. |
Not too happy with:
but given the restrictions that are imposed in |
This comes from the fact that since it is now typesafe, you cannot |
// LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN | ||
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. |
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.
You could put "modified by G.F" here if you want
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.
Ah ok! I wasn't sure how it works with the boost software license!
!AnyParameterProperties::MODEL); | ||
ParameterProperties::HYPER | | ||
ParameterProperties::GRADIENT | | ||
ParameterProperties::MODEL); |
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.
the whitespaces seem weird here
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.
Yup, and should change it to default to false
instead of all true
.
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.
LGTM!
Ok, we might have to touch this again at some point, but I think we can move ahead now |
Shall we have a little test with a minimal mock class where we register some parameters with certain properties and make sure everything makes sense? Next step would be: Let's |
Yes, and adding |
* combined parameter flags in a single mask * added mask_attribute to keep track of parameter availabilities * updated constructors and getters * added some documentation * changed properties default * typesafe bitmasking * refactored code to use enum class and bitmask operators
* combined parameter flags in a single mask * added mask_attribute to keep track of parameter availabilities * updated constructors and getters * added some documentation * changed properties default * typesafe bitmasking * refactored code to use enum class and bitmask operators
This PR refactors the AnyParameterProperties class to use a mask (
m_mask_attribute
) to store parameter options:This makes the use of Enums in
AnyParameter.h
not necessary anymore, and the corresponding getter methods can be replaced withbool
return type.The mask uses bit shift operations to keep information in a hexadecimal representation.