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])
3 Réponses :
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.
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
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]])
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é.
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 depoids
, vous pouvez générer ceux-ci avec un seul appelnp.random.choice ()
et le placer dans le à droiteret
place après. Vous pouvez également changer:for i in range (len (poids)): ...
enfor 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