这个字典条目的笛卡尔乘积可以用递归完成吗



我正在研究一种简单的方法,以紧凑的方式定义要自动实例化和评估的不同分类实验,以获得特定问题的最佳算法和参数组合。

以下是代码的特定部分,它使笛卡尔乘积产生所有可能的参数值组合:

def unpack_parameters(parameters_list):
parameters = []
for parameter_name, parameter_values in parameters_list.items():
if len(parameters) == 0:
parameters = [{parameter_name: parameter_value} for parameter_value in parameter_values]
else:
parameters = [dict(parameter.items() + {parameter_name: parameter_value}.items())
for parameter_value in parameter_values for parameter in parameters]
return parameters

?使用递归可以得到相同的结果吗

上面的代码可以工作并产生所需的结果。我也知道我可以使用itertools.product来获得相同的结果。但这是一个需要学习的问题,如果递归可以在这里使用,而不是它是否是解决特定问题的正确工具(尽管我不介意对此发表评论(。

如果有人感兴趣,下面是如何使用此代码:

experiment_definitions = {
'sklearn.tree.DecisionTreeClassifier':
{'criterion': ['entropy', 'gini'],
'min_samples_split': [2, 4, 8, 16, 32, 64]}
}
classifiers = {}
for classifier_class, parameters_list in experiment_definitions.items():
classifiers[classifier_class] = unpack_parameters(parameters_list)

生产:

{'sklearn.tree.DecisionTreeClassifier': 
[{'min_samples_split': 2, 'criterion': 'entropy'}, {'min_samples_split': 4, 'criterion': 'entropy'}, {'min_samples_split': 8, 'criterion': 'entropy'}, {'min_samples_split': 16, 'criterion': 'entropy'}, {'min_samples_split': 32, 'criterion': 'entropy'}, {'min_samples_split': 64, 'criterion': 'entropy'}, {'min_samples_split': 2, 'criterion': 'gini'}, {'min_samples_split': 4, 'criterion': 'gini'}, {'min_samples_split': 8, 'criterion': 'gini'}, {'min_samples_split': 16, 'criterion': 'gini'}, {'min_samples_split': 32, 'criterion': 'gini'}, {'min_samples_split': 64, 'criterion': 'gini'}]}

因此,技巧是能够编写一个函数cartesian_product(),该函数以数字列表为输入,并返回索引的笛卡尔乘积,考虑到数字可能是列表的大小。

例如,如果我们有两个大小为2和6的列表(根据您的示例(,那么cartesian_product([2, 6])应该生成输出:

[[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2], [0, 3], [1, 3], [0, 4], [1, 4], [0, 5], [1, 5]]

def cartesian_product(sizes, start=0):
if not sizes:
return []
if start == len(sizes) - 1:
return [[i] for i in range(sizes[-1])]
sub_answer_lists = cartesian_product(sizes, start+1)
ans = []
for sub_ans_list in sub_answer_lists:
for j in range(sizes[start]):
list_copy = [j]
list_copy.extend(sub_ans_list)
ans.append(list_copy)
return ans

def unpack_parameters(params_dict):
param_keys = []
param_lists = []
for key, lst in params_dict.items():
param_keys.append(key)
param_lists.append(lst)
param_lists_sizes = [len(lst) for lst in param_lists]
cartesian_product_indices = cartesian_product(param_lists_sizes)
param_map_list = []
for indices in cartesian_product_indices:
param_map = {}
for i, index in enumerate(indices):
param_key = param_keys[i]
param_map[param_key] = param_lists[i][index]
param_map_list.append(param_map)
return param_map_list

experiment_definitions = {
'sklearn.tree.DecisionTreeClassifier':
{'criterion': ['entropy', 'gini'],
'min_samples_split': [2, 4, 8, 16, 32, 64]}
}
classifiers = {}
for classifier_class, params_dict in experiment_definitions.items():
classifiers[classifier_class] = unpack_parameters(params_dict)
print(classifiers)

输出:

{'sklearn.tree.DecisionTreeClassifier': [{'criterion': 'entropy', 'min_samples_split': 2}, {'criterion': 'gini', 'min_samples_split': 2}, {'criterion': 'entropy', 'min_samples_split': 4}, {'criterion': 'gini', 'min_samples_split': 4}, {'criterion': 'entropy', 'min_samples_split': 8}, {'criterion': 'gini', 'min_samples_split': 8}, {'criterion': 'entropy', 'min_samples_split': 16}, {'criterion': 'gini', 'min_samples_split': 16}, {'criterion': 'entropy', 'min_samples_split': 32}, {'criterion': 'gini', 'min_samples_split': 32}, {'criterion': 'entropy', 'min_samples_split': 64}, {'criterion': 'gini', 'min_samples_split': 64}]}

编辑

使用更少内存的生成器版本:

def cartesian_product(sizes, start=0):
if not sizes:
yield []
return
if start == len(sizes) - 1:
for i in range(sizes[-1]):
yield [i]
return
for sub_ans_list in cartesian_product(sizes, start+1):
for j in range(sizes[start]):
list_copy = [j]
list_copy.extend(sub_ans_list)
yield list_copy

是的,类似于以下内容:

def product(names, values, current=[])
pos = len(current)
if pos >= len(names):
# a solution
solutions.append(current.copy())
return
for v in values[pos]:
current.append((names[pos], v)
product(names, values, current)
current.pop()