1
votes

Comment améliorer l'efficacité de la boucle np.random.choice ()

J'essaie d'appliquer np.random.choice à un grand tableau avec des poids différents, et je me demande comment éviter les boucles et améliorer les performances? Ici, len (poids) pourrait être des millions.

weights = [[0.1, 0.5, 0.4],
           [0.2, 0.4, 0.4],
           ...
           [0.3, 0.3, 0.4]]

choice = [1, 2, 3]
ret = np.zeros((len(weights), 20))
for i in range(len(weights)):
    ret[i] = np.random.choice(choice, 20, p=weights[i])


4 commentaires

J'ai peur que vous ne puissiez pas vous empêcher de boucler. Peut-être pourriez-vous encore accélérer les choses en utilisant numba ? Si vous avez beaucoup de doublons de poids , vous pouvez générer ceux-ci avec un seul appel np.random.choice () et le placer dans le à droite ret place après. Vous pouvez également changer: for i in range (len (poids)): ... en for i, weight in enumerate (weights): ... (mais cela ne va pas vous acheter de la vitesse, c'est juste une bonne pratique stylistique).


@WarrenWeckesser Comment incorporeraient-ils le nombre d'éléments dans l'élément lié?


@Divakar, eh bien, vous savez, la diffusion, etc. :) OK, ce n'est pas une copie exacte.


Telle est la forme des «poids»? Cela peut être utile pour l'analyse comparative


3 Réponses :


0
votes

Il semble que chaque ligne du tableau résultant est indépendante des autres lignes. Je ne sais pas à quel point la performance est mauvaise maintenant. Si c'est vraiment un problème, j'essaierais d'utiliser le module multiprocessing de python pour exécuter des générations de nombres aléatoires avec plusieurs processus en parallèle. Cela devrait aider.


0 commentaires

2
votes

Idée empruntée à Vectorisation de numpy.random.choice pour un tableau 2D donné de probabilités le long d'un axe a > avec idée de searchsorted vectorisé, voici une méthode vectorisée -

In [28]: weights = [[0.1, 0.5, 0.4],
    ...:            [0.2, 0.4, 0.4],
    ...:            [0.3, 0.3, 0.4]]
    ...: 
    ...: choice = [1, 2, 3]
    ...: num_items = 20000

In [29]: out = random_choice_prob_vectorized(weights, num_items, choice)

# Use 2D bincount to get per average occurences and verify against weights
In [75]: bincount2D_vectorized(out)/num_items
Out[75]: 
array([[0.     , 0.09715, 0.4988 , 0.40405],
       [0.     , 0.1983 , 0.40235, 0.39935],
       [0.     , 0.30025, 0.29485, 0.4049 ]])

Exemple d'exécution pour vérifier à l'aide de 2D bincount -

def random_choice_prob_vectorized(weights, num_items, choice=None):
    weights = np.asarray(weights)

    w = weights.cumsum(1)
    r = np.random.rand(len(weights),num_items)

    m,n = w.shape
    o = np.arange(m)[:,None]
    w_o = (w+o).ravel()
    r_o = (r+o).ravel()
    idx = np.searchsorted(w_o,r_o).reshape(m,-1)%n
    if choice is not None:
        return np.asarray(choice)[idx]
    else:
        return idx


0 commentaires

4
votes

Voici une généralisation de ma réponse dans Sélection pondérée aléatoire rapide sur toutes les lignes d'une matrice stochastique :

In [321]: p = np.random.rand(1000, 120)

In [322]: p /= p.sum(axis=1, keepdims=True)

In [323]: %timeit vectorized_choice(p, 20, items=np.arange(1, p.shape[1]+1))
6.41 ms ± 20.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [324]: %timeit random_choice_prob_vectorized(p, 20, choice=np.arange(1, p.shape[1]+1))
6.29 ms ± 342 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

p devrait être un tableau bidimensionnel dont les lignes sont des vecteurs de probabilité. n est le nombre d'échantillons à tirer de la distribution définie par chaque ligne. Si items vaut None, les échantillons sont des entiers dans range (0, p.shape [1]) . Si items n'est pas None, il devrait s'agir d'une séquence de longueur p.shape[1 .

Exemple:

XXX

Timing pour p avec la forme (1000000, 3) :

In [320]: %timeit random_choice_prob_vectorized(p, 20, choice=np.arange(1, p.shape[1]+1))
7.33 s ± 43.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Voici le timing de la fonction de Divakar:

In [317]: p = np.random.rand(1000000, 3)

In [318]: p /= p.sum(axis=1, keepdims=True)

In [319]: %timeit vectorized_choice(p, 20, items=np.arange(1, p.shape[1]+1))
1.89 s ± 28.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

La différence sera moins prononcée si vous augmentez le nombre de colonnes dans p , et si vous faites le nombre de colonnes assez grand, la fonction de Divakar sera plus rapide. Par exemple

In [258]: p = np.array([[0.1, 0.5, 0.4], [0.75, 0, 0.25], [0, 0, 1], [1/3, 1/3, 1/3]])                                                   

In [259]: p                                                                                                                              
Out[259]: 
array([[0.1       , 0.5       , 0.4       ],
       [0.75      , 0.        , 0.25      ],
       [0.        , 0.        , 1.        ],
       [0.33333333, 0.33333333, 0.33333333]])

In [260]: vectorized_choice(p, 20)                                                                                                       
Out[260]: 
array([[1, 1, 2, 1, 1, 2, 2, 2, 1, 2, 1, 1, 1, 2, 2, 0, 1, 2, 2, 2],
       [0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0],
       [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
       [1, 0, 2, 2, 0, 1, 2, 1, 0, 0, 0, 0, 2, 2, 0, 0, 2, 1, 1, 2]])

In [261]: vectorized_choice(p, 20, items=[1, 2, 3])                                                                                      
Out[261]: 
array([[2, 1, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 2, 3, 2, 2],
       [1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, 1, 3, 3, 1, 3, 1, 1, 1],
       [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
       [3, 3, 3, 1, 3, 2, 1, 2, 3, 1, 2, 2, 3, 2, 1, 2, 1, 2, 2, 2]])


5 commentaires

C'est bon de voir les horaires! Ça a du sens.


J'ai mis à jour ma solution. Pas aussi bon que le vôtre, mais bien meilleur que mon précédent. Pourriez-vous prendre la peine de mettre à jour les horaires? Merci!


@Divakar, OK, mais dans mes tests, la nouvelle version est en fait un peu plus lente que l'ancienne version. :(


Merci, mais dans vectorized_choice, prob_matrix devrait être p, non?


@Nick, oui, corrigé.