Domanda

Sto cercando di scorrere lo spazio dei parametri di una funzione a 6 parametri per studiarne il comportamento numerico prima di provare a fare qualcosa di complesso con esso, quindi sto cercando un modo efficiente per farlo.

La mia funzione accetta valori float dati come array numpy 6 dim come input. Quello che ho provato a fare inizialmente era questo:

Per prima cosa ho creato una funzione che accetta 2 array e genera un array con tutte le combinazioni di valori dai due array

from numpy import *
def comb(a,b):
    c = []
    for i in a:
        for j in b:
            c.append(r_[i,j])
    return c

Quindi ho usato reduce () per applicarlo a m copie dello stesso array:

def combs(a,m):
    return reduce(comb,[a]*m)

E quindi valuto la mia funzione in questo modo:

values = combs(np.arange(0,1,0.1),6)
for val in values:
    print F(val)

Funziona ma è troppo lento. So che lo spazio dei parametri è enorme, ma non dovrebbe essere così lento. Ho solo campionato 10 6 (un milione) di punti in questo esempio e ci sono voluti più di 15 secondi solo per creare l'array valori .

Conosci un modo più efficiente di farlo con numpy?

Posso modificare il modo in cui la funzione F accetta i suoi argomenti se è necessario.

È stato utile?

Soluzione

Nella versione più recente di numpy (> 1.8.x), numpy.meshgrid () fornisce un'implementazione molto più veloce:

La soluzione di @ 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 () usa solo 2D, ora è in grado di ND. In questo caso, 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]])

Nota che l'ordine del risultato finale è leggermente diverso.

Altri suggerimenti

Ecco un'implementazione puramente insensibile. Sono ca. 5 volte più veloce dell'uso di 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 è in genere il modo più veloce per ottenere combinazioni da un contenitore Python (se in effetti vuoi combinazioni, ovvero accordi SENZA ripetizioni e indipendenti dall'ordine; non è quello che sembra fare il tuo codice, ma non posso dire se è perché il tuo codice è difettoso o perché tu ' utilizzando una terminologia errata).

Se vuoi qualcosa di diverso dalle combinazioni forse altri iteratori in itertools, product o permutations , potrebbero esserti utili. Ad esempio, sembra che il tuo codice sia approssimativamente lo stesso di:

for val in itertools.product(np.arange(0, 1, 0.1), repeat=6):
    print F(val)

Tutti questi iteratori producono tuple, non elenchi o matrici intorpidite, quindi se la tua F è esigente di ottenere specificamente un array intorpidito, dovrai accettare l'overhead aggiuntivo di costruire o cancellare e riempire di nuovo uno ad ogni passaggio.

La seguente implementazione numpy dovrebbe essere di ca. 2x la velocità della risposta data:

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

Sembra che tu voglia che una griglia valuti la tua funzione, nel qual caso puoi usare numpy.ogrid (aperto) o numpy.mgrid (arricchito):

import numpy
my_grid = numpy.mgrid[[slice(0,1,0.1)]*6]

Puoi fare qualcosa del genere

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])

che dà

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]])

puoi usare np.array (itertools.product (a, b))

Ecco ancora un altro modo, usando NumPy puro, nessuna ricorsione, nessuna comprensione dell'elenco e nessun esplicito per i loop. È circa il 20% più lento della risposta originale ed è basato su 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

Ad esempio,

x = np.arange(3)
a = cartesian(x, x, x, x, x)
print(a)

[[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]]

Per un'implementazione insensibile del prodotto cartesiano di array 1D (o elenchi di pitone piatti), basta usare meshgrid () , ruotare gli assi con transpose () e rimodellare all'uscita desiderata:

 def cartprod(*arrays):
     N = len(arrays)
     return transpose(meshgrid(*arrays, indexing='ij'), 
                      roll(arange(N + 1), -1)).reshape(-1, N)

Nota che questo ha la convenzione dell'ultimo asse che cambia più velocemente ("stile C" o "riga maggiore").

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]])

Se vuoi cambiare il primo asse più veloce (" FORTRAN style " o " column-major "), modifica semplicemente il parametro order di reshape () in questo modo: reshape ((- 1, N), order = 'F')

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top