Как выбрать элемент в списке по определенной вероятности?

Предположим, у нас есть список, подобный приведенному ниже:

list = [[A,10,3],[B,5,2],[C,8,1]]

Для каждого элемента в списке есть вероятность, которую нужно выбрать, которую можно рассчитать с помощью softmax. например, для первого элемента (A) у нас есть:

from math import exp

A_probability = exp(list[0][2]/list[0][1] /
                     (exp(list[0][2]/list[0][1]) +
                      exp(list[1][2]/list[1][1]) +
                      exp(list[2][2]/list[2][1])))

Как я могу выбрать элементы в списке случайным образом в соответствии с рассчитанной вероятностью для каждого из них?


person Masoud Masoumi Moghadam    schedule 20.04.2017    source источник


Ответы (1)


Я предполагаю, что у вас есть предварительно рассчитанный список вероятностей (скажем, probs) для каждого из индексов в списке (скажем, data), из которого вы хотите выбрать.

Кроме того, probs и data, очевидно, должны иметь одинаковую длину, а записи probs должны быть неотрицательными числами, суммирующимися с 1.

Существует изящный, но простой способ случайного выбора индексов в data в соответствии с распределением в probs, который известен как колесо рулетки. Я считаю, что в Python это должно выглядеть примерно так

import random

data = ['A', 'B', 'C', 'D']

probs = [0.2, 0.4, 0.3, 0.1]

def roulette_wheel(probs):
    rand = random.random()
    for slot, prob in enumerate(probs):
        rand -= prob
        if rand < 0.0:
            return slot

Обратите внимание, что это можно обобщить до списка неотрицательных весов (который не должен суммироваться до 1), умножив rand на член sum(weights). Мне кажется, я впервые увидел эту симпатичную идею в книге о программировании на Паскале несколько эонов назад.

Изменить:

Как предложил MadPhysicist в comment это можно сделать намного более эффективным, если нужно многократно использовать одни и те же данные. В этом случае можно предварительно вычислить кумулятивную функцию распределения, а затем просто выполнить двоичный поиск индекса так, чтобы cumulative prob. <= rand ~ U(0, 1). В Python это могло бы выглядеть, например, как-то так:

from random import random
from bisect import bisect_right


def cdf(probs):
    cdf = []
    total = 0.0
    for p in probs:
        total += p
        cdf.append(total)
    return cdf


def roulette_wheel_bisect(cdf):
    return bisect_right(cdf, random())

# compute cdf
cumsum = cdf(probs)

# randomly draw 10 indexes 
for i in range(0, 10):
    print(roulette_wheel_bisect(cumsum))

Отказ от ответственности: я не программист на Python по профессии, поэтому приведенный выше код должен только иллюстрировать общую идею. Это может быть не очень надежным для практического использования. Вы всегда должны использовать хорошо протестированную стандартную библиотеку, например, numpy, если можете.

Edit2:

Я только что узнал, что numpy имеет numpy.random. выбор, который сделает именно то, что вам нужно. Пример:

from numpy import random

data = ['A', 'B', 'C', 'D']
probs = [0.2, 0.4, 0.3, 0.1]

# randomly draw 10 list elements with replacement
for i in range(0, 10):
    print(random.choice(data, p=probs))
person Stefan Zobel    schedule 21.04.2017
comment
Используя numpy, вы можете предварительно обработать probs с помощью np.cumsum, а затем просто выполнить двоичный поиск по результату. - person Mad Physicist; 22.04.2017
comment
На самом деле, если вы не возражаете, я хотел бы опубликовать ответ по этому поводу. Или вы можете включить его в свой собственный. - person Mad Physicist; 22.04.2017
comment
@ Безумный физик, пожалуйста, опубликуйте свой ответ, я не против. Я не программист на Python. Просто балуюсь здесь :) - person Stefan Zobel; 22.04.2017
comment
Честно говоря, я не ожидал, что кто-то, возможно, ответит на мой вопрос так подробно. Большое спасибо. - person Masoud Masoumi Moghadam; 26.04.2017
comment
@Masoud Masoumi Moghadam Всегда рад помочь. Что ж, если это решит вашу проблему, не забудьте принять и проголосовать :) - person Stefan Zobel; 26.04.2017