生成具有n个组合范围的随机和唯一索引

时间:2017-09-21 19:35:45

标签: python numpy random random-seed

我想制作一个随机的参数搜索器,但我不知道如何在一个范围内生成随机但唯一的索引组合。例如,我有这些参数:

    hyperparams  = {
        'size': [200, 300, 400],
        'min_count': [1, 2, 3, 4, 5],
        'iter': [50, 100],
        'window': [4, 5, 7, 10],
        'alpha': [0.025, 0.01],
        'min_alpha': [0.025, 1e-4],
    }

我想生成它们的唯一组合,每个索引的范围为n个时间。 假设它将生成500种可能的组合。我想随机抽取其中的100个,但在这100个中,其中任何一个都是重复的。 即。

random_and_unique_combination=[1,3,2,1,2,1]

...其中

  

索引0是大小。

     

索引1是min_count。

     

索引2就是它。

     

依旧......

稍后我用

访问字典
::
size = hyperparams['size'][1]
min_count = hyperparams['min_count'][3]
iter = hyperparams['iter'][2]
::
::

3 个答案:

答案 0 :(得分:1)

这是一种基于哈希的方法来生成唯一组合,并不会生成所有组合,只需要组合的数量 -

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:choiceMode="singleChoice"
        android:fastScrollEnabled="true"
        android:scrollbarThumbVertical="@drawable/scrollbar_white" />

</LinearLayout>

运行时测试

到目前为止发布的三种方法,用于解决不会生成所有组合且真正随机的问题 - @ norok2-Edit1,@ scnerd以及在此帖子中发布的三组输出长度 -

num_comb = 100 # number of combinations needed

# We need ordered keys to maintain the indexing needed :
# Index 0 is the size, Index 1 is min_count, Index 2 is iter...
ordered_keys = ['size', 'min_count', 'iter', 'window', 'alpha','min_alpha']
lens = np.array([len(hyperparams[i]) for i in ordered_keys])

prod_lens = lens.cumprod()
idx = np.random.choice(prod_lens[-1], num_comb, replace=0)

N = len(lens)
out = np.zeros((num_comb,N),dtype=int)
r = idx
for i in range(2,N+1):
    d = r//prod_lens[-i]
    r = r - d*prod_lens[-i]
    out[:,-i+1] = d
out[:,0] = r

答案 1 :(得分:1)

如果我理解正确,你需要在一定范围内的非重复序列的数字元组。

编辑 0

我认为你最好的选择是首先创建所有可能的组合,然后将它们洗牌:

import itertools
import random


def random_unique_combinations_k0(items, k):
    # generate all possible combinations
    combinations = list(itertools.product(*[item for item in items]))
    # shuffle them
    random.shuffle(combinations)
    for combination in itertools.islice(combinations, k):
        yield combination

编辑 1

如果生成所有组合在内存方面过于昂贵,您可能希望通过反复试验并拒绝非唯一组合。 一种方法是:

import itertools
import random
import functools


def prod(items):
    return functools.reduce(lambda x, y: x * y, items)


def random_unique_combinations_k1(items, k):
    max_lens = [len(list(item)) for item in items]
    max_num_combinations = prod(max_lens)

    # use `set` to ensure uniqueness
    index_combinations = set()
    # make sure that with the chosen number the next loop can exit
    # WARNING: if `k` is too close to the total number of combinations,
    # it may take a while until the next valid combination is found
    while len(index_combinations) < min(k, max_num_combinations):
        index_combinations.add(tuple(
            random.randint(0, max_len - 1) for max_len in max_lens))

    # make sure their order is shuffled
    # (`set` seems to sort its content)
    index_combinations = list(index_combinations)
    random.shuffle(index_combinations)
    for index_combination in itertools.islice(index_combinations, k):
        yield tuple(item[i] for i, item in zip(index_combination, items))

(在添加combination之前,这也可以仅使用列表并检查唯一性,同时渲染random.shuffle()也是多余的,但从我的测试中,这些比使用set慢。 )

编辑 2

可能最少耗费内存的方法是实际对服务器进行洗牌,然后对它们使用itertools.product()

import random
import itertools


def pseudo_random_unique_combinations_k(items, k):
    # randomize generators
    comb_gens = list(items)
    for i, comb_gen in enumerate(comb_gens):
        random.shuffle(list(comb_gens[i]))
    # get the first `num` combinations
    combinations = list(itertools.islice(itertools.product(*comb_gens), k))
    random.shuffle(combinations)
    for combination in itertools.islice(combinations, k):
        yield tuple(combination)

这显然会牺牲一些随机性。

编辑 3

继续采用@Divakar方法,我写了另一个版本,看起来效率相对较高,但可能会受到random.sample()功能的限制。

import random
import functools


def prod(items):
    return functools.reduce(lambda x, y: x * y, items)


def random_unique_combinations_k3(items, k):
    max_lens = [len(list(item)) for item in items]
    max_num_combinations = prod(max_lens)
    for i in random.sample(range(max_num_combinations), k):
        index_combination = []
        for max_len in max_lens:
            index_combination.append(i % max_len)
            i = i // max_len
        yield tuple(item[i] for i, item in zip(index_combination, items))

<强> TESTS

在请求的输入上,他们执行速度相当快,0方法最快(比2pseudo方法更快),{{1}方法最慢,而1方法介于两者之间。 3方法的速度与方法sklearn.model_selection.ParameterSampler的速度相当。

1

作为旁注,我会确保您items = [v for k, v in hyperparams.items()] num = 100 %timeit list(random_unique_combinations_k0(items, num)) 615 µs ± 4.87 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit list(random_unique_combinations_k1(items, num)) 2.51 ms ± 33.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit list(pseudo_random_unique_combinations_k(items, num)) 179 µs ± 1.41 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) %timeit list(random_unique_combinations_k3(items, num)) 570 µs ± 35.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) # the `sklearn` method which is slightly different in that it is # also accessing the underling dictiornary import from sklearn.model_selection import ParameterSampler %timeit list(ParameterSampler(hyperparams, n_iter=num)) 2.86 ms ± 171 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) hyperparams因为collections.OrderedDict不能保证在不同版本的Python中排序。

对于稍大的物体,我们开始看到限制:

dict

对于较大的物体更是如此:

items = [range(50)] * 5
num = 1000

%timeit list(random_unique_combinations_k0(items, num))
# Memory Error

%timeit list(random_unique_combinations_k1(items, num))
19.3 ms ± 273 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit list(pseudo_random_unique_combinations_k(items, num))
1.82 ms ± 14.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit list(random_unique_combinations_k3(items, num))
2.31 ms ± 28.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

<强>概要

方法items = [range(50)] * 50 num = 1000 %timeit list(random_unique_combinations_k0(items, num)) # Memory Error %timeit list(random_unique_combinations_k1(items, num)) 149 ms ± 3.45 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) %timeit list(pseudo_random_unique_combinations_k(items, num)) 4.92 ms ± 20.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit list(random_unique_combinations_k3(items, num)) # OverflowError 可能不适合内存,方法0最慢,但它可能更强大,方法1如果不是,则会提供最佳效果遇到溢出问题,而方法32)是最快且占用内存最少的方法,但它会产生一定程度的随机性#34;组合

答案 2 :(得分:0)

scikit-learn需要解决此问题以实现RandomizedSearchCV,并且他们有一个单独的类ParameterSampler可以使用它来执行此操作:

In [1]: from sklearn.model_selection import ParameterSampler

In [2]: list(ParameterSampler({'a': [1,2,3], 'b': ['x', 'y', 'z'], 'c': [0.1, 0.2, 0.3]}, n_iter=5))
Out[2]:
[{'a': 3, 'b': 'z', 'c': 0.2},
 {'a': 3, 'b': 'y', 'c': 0.1},
 {'a': 3, 'b': 'z', 'c': 0.1},
 {'a': 3, 'b': 'x', 'c': 0.2},
 {'a': 1, 'b': 'y', 'c': 0.3}]

它不是索引,但你可以通过用索引列表替换你的值列表来轻松解决这个小问题:

In [1]: from sklearn.model_selection import ParameterSampler

In [2]: params = {'a': [1,2,3], 'b': ['x', 'y', 'z'], 'c': [0.1, 0.2, 0.3]}

In [3]: param_idxs = {key: list(range(len(vals))) for key, vals in params.items()}

In [4]: list(ParameterSampler(param_idxs, n_iter=5))
Out[4]:
[{'a': 1, 'b': 1, 'c': 0},
 {'a': 1, 'b': 0, 'c': 2},
 {'a': 0, 'b': 2, 'c': 1},
 {'a': 1, 'b': 0, 'c': 1},
 {'a': 2, 'b': 0, 'c': 0}]

而且according to the documentation,你不会得到任何重复:

  

如果所有参数都显示为列表,则不进行采样   更换已经完成。

快速浏览current source code表示,当所有参数都以列表形式给出时,它会生成所有可能的选项并从中执行随机抽样。在大多数情况下,这不是一个问题,但如果你有大量的超参数选项,它可能会吸收相当多的内存。