J'essaie de parcourir l'espace de paramètres d'une fonction à 6 paramètres pour étudier son comportement numérique avant d'essayer de faire quelque chose de complexe avec elle. Je recherche donc un moyen efficace de le faire.
Ma fonction prend des valeurs float avec un tableau numpy de 6 dim en entrée. Ce que j'ai essayé de faire initialement était ceci:
J'ai d'abord créé une fonction qui prend 2 tableaux et génère un tableau avec toutes les combinaisons de valeurs des deux tableaux
from numpy import *
def comb(a,b):
c = []
for i in a:
for j in b:
c.append(r_[i,j])
return c
Ensuite, j'ai utilisé reduce()
pour l'appliquer à m copies du même tableau:
def combs(a,m):
return reduce(comb,[a]*m)
Et puis j'évalue ma fonction comme ceci:
values = combs(np.arange(0,1,0.1),6)
for val in values:
print F(val)
Cela fonctionne mais c'est waaaay trop lent. Je sais que l'espace des paramètres est énorme, mais cela ne devrait pas être si lent. J'ai seulement échantillonné 106 (un million) de points dans cet exemple et il a fallu plus de 15 secondes pour créer le tableau values
.
Connaissez-vous un moyen plus efficace de le faire avec numpy?
Je peux modifier la façon dont la fonction F
prend ses arguments si nécessaire.
Dans les versions plus récentes de numpy
(> 1.8.x), numpy.meshgrid()
fournit une implémentation beaucoup plus rapide:
La solution de @ pv
In [113]:
%timeit cartesian(([1, 2, 3], [4, 5], [6, 7]))
10000 loops, best of 3: 135 µs per loop
In [114]:
cartesian(([1, 2, 3], [4, 5], [6, 7]))
Out[114]:
array([[1, 4, 6],
[1, 4, 7],
[1, 5, 6],
[1, 5, 7],
[2, 4, 6],
[2, 4, 7],
[2, 5, 6],
[2, 5, 7],
[3, 4, 6],
[3, 4, 7],
[3, 5, 6],
[3, 5, 7]])
numpy.meshgrid()
utilisait pour être 2D seulement, maintenant il est capable de ND. Dans ce cas, 3D:
In [115]:
%timeit np.array(np.meshgrid([1, 2, 3], [4, 5], [6, 7])).T.reshape(-1,3)
10000 loops, best of 3: 74.1 µs per loop
In [116]:
np.array(np.meshgrid([1, 2, 3], [4, 5], [6, 7])).T.reshape(-1,3)
Out[116]:
array([[1, 4, 6],
[1, 5, 6],
[2, 4, 6],
[2, 5, 6],
[3, 4, 6],
[3, 5, 6],
[1, 4, 7],
[1, 5, 7],
[2, 4, 7],
[2, 5, 7],
[3, 4, 7],
[3, 5, 7]])
Notez que l'ordre de la résultante finale est légèrement différent.
Voici une implémentation purement numérique. C'est ca. 5 × plus rapide que d'utiliser itertools.
import numpy as np
def cartesian(arrays, out=None):
"""
Generate a cartesian product of input arrays.
Parameters
----------
arrays : list of array-like
1-D arrays to form the cartesian product of.
out : ndarray
Array to place the cartesian product in.
Returns
-------
out : ndarray
2-D array of shape (M, len(arrays)) containing cartesian products
formed of input arrays.
Examples
--------
>>> cartesian(([1, 2, 3], [4, 5], [6, 7]))
array([[1, 4, 6],
[1, 4, 7],
[1, 5, 6],
[1, 5, 7],
[2, 4, 6],
[2, 4, 7],
[2, 5, 6],
[2, 5, 7],
[3, 4, 6],
[3, 4, 7],
[3, 5, 6],
[3, 5, 7]])
"""
arrays = [np.asarray(x) for x in arrays]
dtype = arrays[0].dtype
n = np.prod([x.size for x in arrays])
if out is None:
out = np.zeros([n, len(arrays)], dtype=dtype)
m = n / arrays[0].size
out[:,0] = np.repeat(arrays[0], m)
if arrays[1:]:
cartesian(arrays[1:], out=out[0:m,1:])
for j in xrange(1, arrays[0].size):
out[j*m:(j+1)*m,1:] = out[0:m,1:]
return out
itertools.combinations est en général le moyen le plus rapide d’obtenir des combinaisons d’un conteneur Python (si vous voulez en fait des combinaisons, c’est-à-dire des arrangements SANS répétitions et indépendantes de l’ordre). ; ce n'est pas ce que votre code semble faire, mais je ne peux pas dire si c'est parce que votre code est bogué ou parce que vous utilisez une terminologie incorrecte).
Si vous voulez quelque chose de différent des combinaisons, peut-être que d'autres itérateurs dans itertools, product
ou permutations
, pourraient mieux vous servir. Par exemple, il semble que votre code soit à peu près le même que:
for val in itertools.product(np.arange(0, 1, 0.1), repeat=6):
print F(val)
Tous ces itérateurs génèrent des n-uplets, et non des listes ou des tableaux numpy. Par conséquent, si votre F est difficile à obtenir un tableau numpy spécifique, vous devrez accepter la surcharge supplémentaire liée à la construction ou la suppression et le remplissage à chaque étape.
L'implémentation numpy suivante devrait être d'env. 2x la vitesse de la réponse donnée:
def cartesian2(arrays):
arrays = [np.asarray(a) for a in arrays]
shape = (len(x) for x in arrays)
ix = np.indices(shape, dtype=int)
ix = ix.reshape(len(arrays), -1).T
for n, arr in enumerate(arrays):
ix[:, n] = arrays[n][ix[:, n]]
return ix
Il semble que vous souhaitiez une grille pour évaluer votre fonction, auquel cas vous pouvez utiliser numpy.ogrid
(ouvert) ou numpy.mgrid
(étoffé):
import numpy
my_grid = numpy.mgrid[[slice(0,1,0.1)]*6]
Vous pouvez faire quelque chose comme ça
import numpy as np
def cartesian_coord(*arrays):
grid = np.meshgrid(*arrays)
coord_list = [entry.ravel() for entry in grid]
points = np.vstack(coord_list).T
return points
a = np.arange(4) # fake data
print(cartesian_coord(*6*[a])
qui donne
array([[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 2],
...,
[3, 3, 3, 3, 3, 1],
[3, 3, 3, 3, 3, 2],
[3, 3, 3, 3, 3, 3]])
vous pouvez utiliser np.array(itertools.product(a, b))
Voici encore un autre moyen d’utiliser NumPy pur, pas de récursion, pas de compréhension de liste et pas de boucles explicites. C'est environ 20% plus lent que la réponse d'origine, et c'est basé sur np.meshgrid.
def cartesian(*arrays):
mesh = np.meshgrid(*arrays) # standard numpy meshgrid
dim = len(mesh) # number of dimensions
elements = mesh[0].size # number of elements, any index will do
flat = np.concatenate(mesh).ravel() # flatten the whole meshgrid
reshape = np.reshape(flat, (dim, elements)).T # reshape and transpose
return reshape
Par exemple,
x = np.arange(3)
a = cartesian(x, x, x, x, x)
print(a)
donne
[[0 0 0 0 0]
[0 0 0 0 1]
[0 0 0 0 2]
...,
[2 2 2 2 0]
[2 2 2 2 1]
[2 2 2 2 2]]
Pour une implémentation numpy pure du produit cartésien de tableaux 1D (ou flat python listes)), utilisez simplement meshgrid()
, faites rouler les axes avec transpose()
, et remodeler à la sortie désirée:
def cartprod(*arrays):
N = len(arrays)
return transpose(meshgrid(*arrays, indexing='ij'),
roll(arange(N + 1), -1)).reshape(-1, N)
Notez que cela a la convention du dernier axe qui change le plus rapidement ("style C" ou "ligne-majeur").
In [88]: cartprod([1,2,3], [4,8], [100, 200, 300, 400], [-5, -4])
Out[88]:
array([[ 1, 4, 100, -5],
[ 1, 4, 100, -4],
[ 1, 4, 200, -5],
[ 1, 4, 200, -4],
[ 1, 4, 300, -5],
[ 1, 4, 300, -4],
[ 1, 4, 400, -5],
[ 1, 4, 400, -4],
[ 1, 8, 100, -5],
[ 1, 8, 100, -4],
[ 1, 8, 200, -5],
[ 1, 8, 200, -4],
[ 1, 8, 300, -5],
[ 1, 8, 300, -4],
[ 1, 8, 400, -5],
[ 1, 8, 400, -4],
[ 2, 4, 100, -5],
[ 2, 4, 100, -4],
[ 2, 4, 200, -5],
[ 2, 4, 200, -4],
[ 2, 4, 300, -5],
[ 2, 4, 300, -4],
[ 2, 4, 400, -5],
[ 2, 4, 400, -4],
[ 2, 8, 100, -5],
[ 2, 8, 100, -4],
[ 2, 8, 200, -5],
[ 2, 8, 200, -4],
[ 2, 8, 300, -5],
[ 2, 8, 300, -4],
[ 2, 8, 400, -5],
[ 2, 8, 400, -4],
[ 3, 4, 100, -5],
[ 3, 4, 100, -4],
[ 3, 4, 200, -5],
[ 3, 4, 200, -4],
[ 3, 4, 300, -5],
[ 3, 4, 300, -4],
[ 3, 4, 400, -5],
[ 3, 4, 400, -4],
[ 3, 8, 100, -5],
[ 3, 8, 100, -4],
[ 3, 8, 200, -5],
[ 3, 8, 200, -4],
[ 3, 8, 300, -5],
[ 3, 8, 300, -4],
[ 3, 8, 400, -5],
[ 3, 8, 400, -4]])
Si vous voulez changer le premier axe le plus rapide ("style FORTRAN" ou "colonne-majeur"), il suffit de changer le paramètre order
de reshape()
comme ceci: reshape((-1, N), order='F')