Generating combinations of specific values of a nested dict
I am developing a framework that allows to specify a machine learning model via a yaml
file with different parameters nested so the configuration files are easy to read for humans.
I would like to give users the option of instead of specifying a parameter giving a range of options to try via a list.
Then I have to take this and generate all the possible valid combinations for the parameters the user has given multiple values for.
To mark which parameters are in fact lists and which ones are multiple values, I have opted to choose that combination values begin with 'multi_' (though if you have a different take I would be interested to hear it!).
So for example an user could write:
config = {
'train_config': {'param1': 1, 'param2': [1,2,3], 'multi_param3':[2,3,4]},
'model_config': {'cnn_layers': [{'units':3},{'units':4}], 'multi_param4': [[1,2], [3,4]]}
}
Indicating that 6 configuration files must be generated, where the values of 'param3' and 'param4' take all the possible combinations.
I have written a generator function to do this:
from pandas.io.json.normalize import nested_to_record
import itertools
import operator
from functools import reduce
from collections import MutableMapping
from contextlib import suppress
def generate_multi_conf(config):
flat = nested_to_record(config)
flat = { tuple(key.split('.')): value for key, value in flat.items()}
multi_config_flat = { key[:-1] + (key[-1][6:],) : value for key, value in flat.items() if key[-1][:5]=='multi'}
if len(multi_config_flat) == 0: return # if there are no multi params this generator is empty
keys, values = zip(*multi_config_flat.items())
# delete the multi_params
# taken from https://stackoverflow.com/a/49723101/4841832
def delete_keys_from_dict(dictionary, keys):
for key in keys:
with suppress(KeyError):
del dictionary[key]
for value in dictionary.values():
if isinstance(value, MutableMapping):
delete_keys_from_dict(value, keys)
to_delete = ['multi_' + key[-1] for key, _ in multi_config_flat.items()]
delete_keys_from_dict(config, to_delete)
for values in itertools.product(*values):
experiment = dict(zip(keys, values))
for setting, value in experiment.items():
reduce(operator.getitem, setting[:-1], config)[setting[-1]] = value
yield config
Iterating over this with the example above gives:
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 2}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 2}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 3}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 3}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 4}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 4}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
Which is the result expected.
Any feedback on how to make this code more readable would be very much appreciated!
python python-3.x generator
add a comment |
I am developing a framework that allows to specify a machine learning model via a yaml
file with different parameters nested so the configuration files are easy to read for humans.
I would like to give users the option of instead of specifying a parameter giving a range of options to try via a list.
Then I have to take this and generate all the possible valid combinations for the parameters the user has given multiple values for.
To mark which parameters are in fact lists and which ones are multiple values, I have opted to choose that combination values begin with 'multi_' (though if you have a different take I would be interested to hear it!).
So for example an user could write:
config = {
'train_config': {'param1': 1, 'param2': [1,2,3], 'multi_param3':[2,3,4]},
'model_config': {'cnn_layers': [{'units':3},{'units':4}], 'multi_param4': [[1,2], [3,4]]}
}
Indicating that 6 configuration files must be generated, where the values of 'param3' and 'param4' take all the possible combinations.
I have written a generator function to do this:
from pandas.io.json.normalize import nested_to_record
import itertools
import operator
from functools import reduce
from collections import MutableMapping
from contextlib import suppress
def generate_multi_conf(config):
flat = nested_to_record(config)
flat = { tuple(key.split('.')): value for key, value in flat.items()}
multi_config_flat = { key[:-1] + (key[-1][6:],) : value for key, value in flat.items() if key[-1][:5]=='multi'}
if len(multi_config_flat) == 0: return # if there are no multi params this generator is empty
keys, values = zip(*multi_config_flat.items())
# delete the multi_params
# taken from https://stackoverflow.com/a/49723101/4841832
def delete_keys_from_dict(dictionary, keys):
for key in keys:
with suppress(KeyError):
del dictionary[key]
for value in dictionary.values():
if isinstance(value, MutableMapping):
delete_keys_from_dict(value, keys)
to_delete = ['multi_' + key[-1] for key, _ in multi_config_flat.items()]
delete_keys_from_dict(config, to_delete)
for values in itertools.product(*values):
experiment = dict(zip(keys, values))
for setting, value in experiment.items():
reduce(operator.getitem, setting[:-1], config)[setting[-1]] = value
yield config
Iterating over this with the example above gives:
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 2}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 2}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 3}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 3}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 4}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 4}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
Which is the result expected.
Any feedback on how to make this code more readable would be very much appreciated!
python python-3.x generator
add a comment |
I am developing a framework that allows to specify a machine learning model via a yaml
file with different parameters nested so the configuration files are easy to read for humans.
I would like to give users the option of instead of specifying a parameter giving a range of options to try via a list.
Then I have to take this and generate all the possible valid combinations for the parameters the user has given multiple values for.
To mark which parameters are in fact lists and which ones are multiple values, I have opted to choose that combination values begin with 'multi_' (though if you have a different take I would be interested to hear it!).
So for example an user could write:
config = {
'train_config': {'param1': 1, 'param2': [1,2,3], 'multi_param3':[2,3,4]},
'model_config': {'cnn_layers': [{'units':3},{'units':4}], 'multi_param4': [[1,2], [3,4]]}
}
Indicating that 6 configuration files must be generated, where the values of 'param3' and 'param4' take all the possible combinations.
I have written a generator function to do this:
from pandas.io.json.normalize import nested_to_record
import itertools
import operator
from functools import reduce
from collections import MutableMapping
from contextlib import suppress
def generate_multi_conf(config):
flat = nested_to_record(config)
flat = { tuple(key.split('.')): value for key, value in flat.items()}
multi_config_flat = { key[:-1] + (key[-1][6:],) : value for key, value in flat.items() if key[-1][:5]=='multi'}
if len(multi_config_flat) == 0: return # if there are no multi params this generator is empty
keys, values = zip(*multi_config_flat.items())
# delete the multi_params
# taken from https://stackoverflow.com/a/49723101/4841832
def delete_keys_from_dict(dictionary, keys):
for key in keys:
with suppress(KeyError):
del dictionary[key]
for value in dictionary.values():
if isinstance(value, MutableMapping):
delete_keys_from_dict(value, keys)
to_delete = ['multi_' + key[-1] for key, _ in multi_config_flat.items()]
delete_keys_from_dict(config, to_delete)
for values in itertools.product(*values):
experiment = dict(zip(keys, values))
for setting, value in experiment.items():
reduce(operator.getitem, setting[:-1], config)[setting[-1]] = value
yield config
Iterating over this with the example above gives:
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 2}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 2}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 3}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 3}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 4}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 4}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
Which is the result expected.
Any feedback on how to make this code more readable would be very much appreciated!
python python-3.x generator
I am developing a framework that allows to specify a machine learning model via a yaml
file with different parameters nested so the configuration files are easy to read for humans.
I would like to give users the option of instead of specifying a parameter giving a range of options to try via a list.
Then I have to take this and generate all the possible valid combinations for the parameters the user has given multiple values for.
To mark which parameters are in fact lists and which ones are multiple values, I have opted to choose that combination values begin with 'multi_' (though if you have a different take I would be interested to hear it!).
So for example an user could write:
config = {
'train_config': {'param1': 1, 'param2': [1,2,3], 'multi_param3':[2,3,4]},
'model_config': {'cnn_layers': [{'units':3},{'units':4}], 'multi_param4': [[1,2], [3,4]]}
}
Indicating that 6 configuration files must be generated, where the values of 'param3' and 'param4' take all the possible combinations.
I have written a generator function to do this:
from pandas.io.json.normalize import nested_to_record
import itertools
import operator
from functools import reduce
from collections import MutableMapping
from contextlib import suppress
def generate_multi_conf(config):
flat = nested_to_record(config)
flat = { tuple(key.split('.')): value for key, value in flat.items()}
multi_config_flat = { key[:-1] + (key[-1][6:],) : value for key, value in flat.items() if key[-1][:5]=='multi'}
if len(multi_config_flat) == 0: return # if there are no multi params this generator is empty
keys, values = zip(*multi_config_flat.items())
# delete the multi_params
# taken from https://stackoverflow.com/a/49723101/4841832
def delete_keys_from_dict(dictionary, keys):
for key in keys:
with suppress(KeyError):
del dictionary[key]
for value in dictionary.values():
if isinstance(value, MutableMapping):
delete_keys_from_dict(value, keys)
to_delete = ['multi_' + key[-1] for key, _ in multi_config_flat.items()]
delete_keys_from_dict(config, to_delete)
for values in itertools.product(*values):
experiment = dict(zip(keys, values))
for setting, value in experiment.items():
reduce(operator.getitem, setting[:-1], config)[setting[-1]] = value
yield config
Iterating over this with the example above gives:
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 2}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 2}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 3}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 3}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 4}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [1, 2]}}
{'train_config': {'param1': 1, 'param2': [1, 2, 3], 'param3': 4}, 'model_config': {'cnn_layers': [{'units': 3}, {'units': 4}], 'param4': [3, 4]}}
Which is the result expected.
Any feedback on how to make this code more readable would be very much appreciated!
python python-3.x generator
python python-3.x generator
edited Dec 24 at 17:16
alecxe
14.9k53478
14.9k53478
asked Dec 24 at 16:26
Jsevillamol
1574
1574
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
For non-trivial list comprehensions such as
multi_config_flat = { key[:-1] + (key[-1][6:],) : value for key, value in flat.items() if key[-1][:5]=='multi'}
You should split it onto multiple lines, i.e.
multi_config_flat = {key[:-1] + (key[-1][6:],): value
for key, value in flat.items()
if key[-1][:5]=='multi'}
This:
key[-1][:5]=='multi'
should be
key[-1].startswith('multi')
This:
if len(multi_config_flat) == 0: return
is equivalent (more or less) to
if not multi_config_flat:
return
The latter also catches the case of multi_config_flat
being None
, but that won't be possible in this context.
This:
for key, _ in multi_config_flat.items():
is not necessary; simply iterate over keys
:
for key in multi_config_flat:
This is fairly opaque:
reduce(operator.getitem, setting[:-1], config)[setting[-1]] = value
Probably you should assign the output of reduce
to a meaningfully named variable, so that your code is more clear.
1
for key in multi_config_flag.keys()
can simply befor key in multi_config_flag
as the default iterator iskeys()
– Stephen Rauch
Dec 24 at 21:30
@StephenRauch True - thanks!
– Reinderien
Dec 24 at 23:04
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210275%2fgenerating-combinations-of-specific-values-of-a-nested-dict%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
For non-trivial list comprehensions such as
multi_config_flat = { key[:-1] + (key[-1][6:],) : value for key, value in flat.items() if key[-1][:5]=='multi'}
You should split it onto multiple lines, i.e.
multi_config_flat = {key[:-1] + (key[-1][6:],): value
for key, value in flat.items()
if key[-1][:5]=='multi'}
This:
key[-1][:5]=='multi'
should be
key[-1].startswith('multi')
This:
if len(multi_config_flat) == 0: return
is equivalent (more or less) to
if not multi_config_flat:
return
The latter also catches the case of multi_config_flat
being None
, but that won't be possible in this context.
This:
for key, _ in multi_config_flat.items():
is not necessary; simply iterate over keys
:
for key in multi_config_flat:
This is fairly opaque:
reduce(operator.getitem, setting[:-1], config)[setting[-1]] = value
Probably you should assign the output of reduce
to a meaningfully named variable, so that your code is more clear.
1
for key in multi_config_flag.keys()
can simply befor key in multi_config_flag
as the default iterator iskeys()
– Stephen Rauch
Dec 24 at 21:30
@StephenRauch True - thanks!
– Reinderien
Dec 24 at 23:04
add a comment |
For non-trivial list comprehensions such as
multi_config_flat = { key[:-1] + (key[-1][6:],) : value for key, value in flat.items() if key[-1][:5]=='multi'}
You should split it onto multiple lines, i.e.
multi_config_flat = {key[:-1] + (key[-1][6:],): value
for key, value in flat.items()
if key[-1][:5]=='multi'}
This:
key[-1][:5]=='multi'
should be
key[-1].startswith('multi')
This:
if len(multi_config_flat) == 0: return
is equivalent (more or less) to
if not multi_config_flat:
return
The latter also catches the case of multi_config_flat
being None
, but that won't be possible in this context.
This:
for key, _ in multi_config_flat.items():
is not necessary; simply iterate over keys
:
for key in multi_config_flat:
This is fairly opaque:
reduce(operator.getitem, setting[:-1], config)[setting[-1]] = value
Probably you should assign the output of reduce
to a meaningfully named variable, so that your code is more clear.
1
for key in multi_config_flag.keys()
can simply befor key in multi_config_flag
as the default iterator iskeys()
– Stephen Rauch
Dec 24 at 21:30
@StephenRauch True - thanks!
– Reinderien
Dec 24 at 23:04
add a comment |
For non-trivial list comprehensions such as
multi_config_flat = { key[:-1] + (key[-1][6:],) : value for key, value in flat.items() if key[-1][:5]=='multi'}
You should split it onto multiple lines, i.e.
multi_config_flat = {key[:-1] + (key[-1][6:],): value
for key, value in flat.items()
if key[-1][:5]=='multi'}
This:
key[-1][:5]=='multi'
should be
key[-1].startswith('multi')
This:
if len(multi_config_flat) == 0: return
is equivalent (more or less) to
if not multi_config_flat:
return
The latter also catches the case of multi_config_flat
being None
, but that won't be possible in this context.
This:
for key, _ in multi_config_flat.items():
is not necessary; simply iterate over keys
:
for key in multi_config_flat:
This is fairly opaque:
reduce(operator.getitem, setting[:-1], config)[setting[-1]] = value
Probably you should assign the output of reduce
to a meaningfully named variable, so that your code is more clear.
For non-trivial list comprehensions such as
multi_config_flat = { key[:-1] + (key[-1][6:],) : value for key, value in flat.items() if key[-1][:5]=='multi'}
You should split it onto multiple lines, i.e.
multi_config_flat = {key[:-1] + (key[-1][6:],): value
for key, value in flat.items()
if key[-1][:5]=='multi'}
This:
key[-1][:5]=='multi'
should be
key[-1].startswith('multi')
This:
if len(multi_config_flat) == 0: return
is equivalent (more or less) to
if not multi_config_flat:
return
The latter also catches the case of multi_config_flat
being None
, but that won't be possible in this context.
This:
for key, _ in multi_config_flat.items():
is not necessary; simply iterate over keys
:
for key in multi_config_flat:
This is fairly opaque:
reduce(operator.getitem, setting[:-1], config)[setting[-1]] = value
Probably you should assign the output of reduce
to a meaningfully named variable, so that your code is more clear.
edited Dec 24 at 23:06
Stephen Rauch
3,76061530
3,76061530
answered Dec 24 at 20:08
Reinderien
3,115720
3,115720
1
for key in multi_config_flag.keys()
can simply befor key in multi_config_flag
as the default iterator iskeys()
– Stephen Rauch
Dec 24 at 21:30
@StephenRauch True - thanks!
– Reinderien
Dec 24 at 23:04
add a comment |
1
for key in multi_config_flag.keys()
can simply befor key in multi_config_flag
as the default iterator iskeys()
– Stephen Rauch
Dec 24 at 21:30
@StephenRauch True - thanks!
– Reinderien
Dec 24 at 23:04
1
1
for key in multi_config_flag.keys()
can simply be for key in multi_config_flag
as the default iterator is keys()
– Stephen Rauch
Dec 24 at 21:30
for key in multi_config_flag.keys()
can simply be for key in multi_config_flag
as the default iterator is keys()
– Stephen Rauch
Dec 24 at 21:30
@StephenRauch True - thanks!
– Reinderien
Dec 24 at 23:04
@StephenRauch True - thanks!
– Reinderien
Dec 24 at 23:04
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210275%2fgenerating-combinations-of-specific-values-of-a-nested-dict%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown