Использование numba для случайной выборки возможных комбинаций категорий - PullRequest
0 голосов
/ 21 марта 2020

Я пытаюсь ускорить функцию, которая случайным образом выбирает количество записей с возможными комбинациями ряда категорий для ряда записей и обеспечивает их уникальность (т. Е. Предположим, что есть 3 записи, любая из которых может быть либо 0, либо 1, и я хочу 10 случайных выборок уникальных возможных комбинаций записей).

Если бы я не использовал numba, я мог бы сделать что-то вроде этого:

import numpy as np

def myfunc(categories, NumberOfRecords, maxsamples):
  return np.unique( np.random.choice(np.arange(categories), size=(maxsamples*10, NumberOfRecords), replace=True), axis=0 )[0:maxsamples]

Раздражающе, numba не поддерживает оси в np.unique, поэтому я могу сделать что-то подобное, но некоторые записи могут оказаться неуникальными.

from numba import njit, int64
import numpy as np

@njit(int64[:,:](int64, int64, int64), cache=True)
def myfunc(categories, NumberOfRecords, maxsamples):
  return np.random.choice(np.arange(categories), size=(maxsamples, NumberOfRecords), replace=True) 

myfunc(categories=2, NumberOfRecords=3, maxsamples=10)

Например, за один вызов (очевидно, есть некоторая случайность здесь), я получил ниже (для которых индексы 1 и 6, а также 3 и 4, и 7 и 9 являются одинаковыми строками):

array([[0, 1, 1],
       [1, 1, 0],
       [0, 1, 0],
       [1, 0, 1],
       [1, 0, 1],
       [1, 1, 1],
       [1, 1, 0],
       [1, 0, 0],
       [0, 0, 0],
       [1, 0, 0]])

Мои вопросы:

  1. Это то, где я бы даже ожидал, что скорость увеличится от нумбы?
  2. Если так, как я могу получить уникальные строки (это кажется довольно трудным для numba, но, вероятно, есть способ)?
  3. Возможно, есть способ добиться этого более эффективно (возможно, без создать больше случайных выборок, чем мне нужно в конце)?

1 Ответ

0 голосов
/ 22 марта 2020

В дальнейшем я не использую numba, но все операции используют векторизованные numpy функции.

Каждая строка сгенерированного вами результата может интерпретироваться как целое число, выраженное в базе N, где N - количество категорий. С этой интерпретацией вы хотите произвести выборку без замены целых чисел [0, 1, ... N ** R-1], где R - количество «записей». Для этого вы можете использовать функцию choice с аргументом replace=False. Получив это, вам нужно преобразовать выбранные целые числа в основание N. Для этого я использую функцию int2base, которая является урезанной версией функции, которую я написал в другом ответе .

Вот код:

import numpy as np


def int2base(x, base, ndigits):
    # x = np.asarray(x)  # Uncomment this line for general purpose use.
    powers = base ** np.arange(ndigits)
    digits = (x.reshape(x.shape + (1,)) // powers) % base
    return digits


def makesample(ncategories, nrecords, nsamples, rng=None):
    if rng is None:
        rng = np.random.default_rng()
    n = ncategories ** nrecords
    choices = rng.choice(n, replace=False, size=nsamples)
    return int2base(choices, ncategories, nrecords)

В makesample я включил необязательный аргумент rng. Позволяет указать объект, который содержит функцию choice. Если не предоставлено, используется np.random.default_rng().

Пример:

In [118]: makesample(2, 3, 6)                                                   
Out[118]: 
array([[0, 1, 1],
       [0, 0, 1],
       [1, 0, 1],
       [0, 0, 0],
       [1, 1, 0],
       [1, 1, 1]])

In [119]: makesample(5, 4, 12)                                                  
Out[119]: 
array([[3, 4, 0, 1],
       [2, 0, 2, 0],
       [4, 2, 4, 3],
       [0, 1, 0, 4],
       [0, 2, 0, 1],
       [1, 2, 0, 1],
       [0, 3, 0, 4],
       [3, 3, 0, 3],
       [3, 4, 1, 4],
       [2, 4, 1, 1],
       [3, 4, 1, 0],
       [1, 1, 4, 4]])

makesample вызовет исключение, если вы запросите слишком много образцов:

In [120]: makesample(2, 3, 10)                                                  
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-120-80044e78a60a> in <module>
----> 1 makesample(2, 3, 10)

~/code_snippets/python/numpy/random_samples_for_so_question.py in makesample(ncategories, nrecords, nsamples, rng)
     17         rng = np.random.default_rng()
     18     n = ncategories ** nrecords
---> 19     choices = rng.choice(n, replace=False, size=nsamples)
     20     return int2base(choices, ncategories, nrecords)

_generator.pyx in numpy.random._generator.Generator.choice()

ValueError: Cannot take a larger sample than population when 'replace=False'
...