Эффективная стратифицированная случайная выборка в Julia - PullRequest
1 голос
/ 25 мая 2020

Я пытаюсь написать небольшую функцию для выполнения стратифицированной случайной выборки. То есть у меня есть вектор членства в группах для каждого элемента, и я хочу выбрать один элемент (индекс) для каждой группы. Таким образом, входными данными является количество желаемых элементов и членство в группе для каждого элемента. Результатом является список индексов.

Вот функция, которая у меня есть:

function stratified_sample(n::Int64, groups::Array{Int64})

    # the output vector of indices
    ind = zeros(Int64, n)

    # first select n groups from the total set of possible groups
    group_samp = sample(unique(groups), n, replace = false)

    # cycle through the selected groups
    for i in 1:n
        # for each group, select one index whose group matches the current target group
        ind[i] = sample([1:length(groups)...][groups.==group_samp[i]], 1, replace = false)[1]
    end

    # return the indices
    return ind
end

Когда я запускаю этот код на относительно большом векторе, например, 1000 различных групп и 40000 общих записей , Я получаю


julia> groups = sample(1:1000, 40000, replace = true)
40000-element Array{Int64,1}:
 221
 431
 222
 421
 714
 108
 751
 259
   ⋮
 199
 558
 317
 848
 271
 358

julia> @time stratified_sample(5, groups)
  0.022951 seconds (595.06 k allocations: 19.888 MiB)
5-element Array{Int64,1}:
 11590
 17057
 17529
 25103
 20651

И для сравнения с нормальной случайной выборкой пяти элементов из возможных 40000:

julia> @time sample(1:40000, 5, replace = false)
  0.000005 seconds (5 allocations: 608 bytes)
5-element Array{Int64,1}:
 38959
  5850
  3283
 19779
 30063

Итак, мой код работает почти в 50 тысяч раз медленнее и использует в 33 тысячи раз больше памяти! Что, черт возьми, я сделал не так, и есть ли способ ускорить этот код? Я предполагаю, что реальное замедление происходит на этапе подмножества, т.е. [1:length(groups)...][groups.==group_samp[i]], но я не могу найти лучшего решения.

Я бесконечно искал эту функцию в стандартных пакетах Julia, но безуспешно.

Есть предложения?


EDIT: Мне удалось значительно ускорить его, просто взяв случайную выборку и проверив, есть ли она удовлетворяет требованию, чтобы было выбрано n уникальных групп:

function stratified_sample_random(n::Int64, groups::Array{Int64}, group_probs::Array{Float32})
    ind = zeros(Int64, n)
    my_samp = []
    while true
        my_samp = wsample(1:length(groups), group_probs, n, replace = false)
        if length(unique(groups[my_samp])) == n
            break
        end
    end

    return my_samp

end

Здесь group_probs - это просто вектор вероятностей выборки, где элементы каждой группы имеют общую вероятность 1 / с, где s - это количество элементов в этой группе. Например, если groups = [1,1,1,1,2,3,3], соответствующие вероятности будут group_probs = [0.25, 0.25, 0.25, 0.25, 1, 0.5, 0.5]. Это помогает ускорить выборку, сводя к минимуму вероятность выбора нескольких элементов одной группы. В целом это работает довольно хорошо:

@time stratified_sample_random(5, groups, group_probs)
  0.000122 seconds (14 allocations: 1.328 KiB)
5-element Array{Int64,1}:
 32209
 10184
 30892
  4861
 30300

После небольшого экспериментирования взвешенная выборка по вероятности не обязательно быстрее стандартной выборки (), но это зависит от того, сколько уникальных групп и что требуется n значение есть.

Конечно, нет гарантии, что эта функция будет случайным образом выбирать уникальный набор объектов, и она может циклически повторяться бесконечно. Моя мысль состоит в том, чтобы добавить счетчик к while l oop, и если он попробует что-то вроде 10000 раз безуспешно, он вызовет исходную функцию stratified_sample, которую я представил, чтобы гарантировать, что она вернет уникальный результат. Мне не нравится это решение, и должен быть более элегантный и экономный подход, но это определенно улучшение.

1 Ответ

0 голосов
/ 25 мая 2020

Здесь, [1:length(groups)...], вы разбиваете и выделяете 40000 массив элементов n раз, вам следует избегать этого. Вот версия, которая в 33 раза быстрее, с использованием диапазона inds. Однако, зная реальное приложение, мы все равно могли бы придумать более быстрый метод.

function stratified_sample(n::Int64, groups::Array{Int64})

    # the output vector of indices
    ind = zeros(Int64, n)

    # first select n groups from the total set of possible groups
    group_samp = sample(unique(groups), n, replace = false)

    inds = 1:length(groups)
    # cycle through the selected groups
    for i in 1:n
        # for each group, select one index whose group matches the current target group
        ind[i] = sample(inds[groups.==group_samp[i]], 1, replace = false)[1]
    end

    # return the indices
    return ind
end
...