Pergunta

Eu estou tentando executar sobre o espaço parâmetros de uma função 6 parâmetro para estudar seu comportamento numérico antes de tentar fazer complexa nada com ele assim que eu estou procurando uma maneira eficiente de fazer isso.

Meu função toma valores float dadas uma matriz numpy 6-dim como entrada. O que eu tentei fazer, inicialmente, era o seguinte:

Primeiro, criou uma função que leva 2 matrizes e gerar uma matriz com todas as combinações de valores a partir das duas matrizes

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

Em seguida, utilizado para aplicar reduce() que a m cópias da mesma matriz:

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

E então eu avaliar minha função como esta:

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

Isso funciona, mas é muuuuito muito lento. Eu sei que o espaço de parâmetros é enorme, mas isso não deve ser tão lento. Eu só tenho amostradas 10 6 (um milhão) pontos neste exemplo e levou mais de 15 segundos apenas para criar o values matriz.

Você conhece alguma maneira mais eficiente de fazer isso com numpy?

Eu posso modificar a maneira como a função F leva-lo de argumentos se for necessário.

Foi útil?

Solução

Na versão mais recente do numpy (> 1.8.x), numpy.meshgrid() fornece uma implementação muito mais rápida:

@ do pv solução

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

numpy.meshgrid() ser 2D somente, agora é capaz de ND. Neste 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]])

Note que a ordem da resultante final é ligeiramente diferente.

Outras dicas

Aqui está uma implementação pura do numpy. É de ca. 5 × mais rápido do que usando 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 é, em geral, a maneira mais rápida de obter combinações de um recipiente Python (se você fizer em combinações Quer fato, ou seja, arranjos SEM repetições e independente da ordem; não é isso que o seu código parece estar fazendo, mas eu não posso dizer se isso é porque seu código é buggy ou porque você' está usando a terminologia errada).

Se você quiser diferente algo que combinações talvez outros iteradores em itertools, product ou permutations, pode servir melhor. Por exemplo, parece que o seu código é aproximadamente o mesmo que:

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

Todos estes iterators deu tuplas, não listas ou matrizes numpy, por isso, se o seu F é exigente sobre a obtenção especificamente uma matriz numpy você vai ter que aceitar a sobrecarga extra de construir ou compensação e um enchimento de re-em cada etapa.

A seguir implementação numpy deve ser de aprox. 2x a velocidade da resposta dada:

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

Parece que você quer uma grade para avaliar a sua função, caso em que você pode usar numpy.ogrid (aberto) ou numpy.mgrid (concretizada):

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

Você pode fazer algo como isto

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

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

Você pode usar np.array(itertools.product(a, b))

Aqui está ainda outra maneira, usando puro NumPy, sem recursão, nenhuma lista de compreensão, e não explícita para loops. É cerca de 20% mais lento do que a resposta original, e é baseado em 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

Por exemplo,

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

Para uma implementação numpy pura do produto cartesiano de matrizes 1D (ou listas de python planas), basta usar meshgrid(), rolo os eixos com transpose(), e remodelar a desejada ouput:

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

Note que este tem a convenção do último eixo mudando mais rápido ( "estilo C" ou "linha principal").

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 você quiser alterar o início eixo mais rápido ( "estilo Fortran" ou "coluna-major"), basta alterar o parâmetro order de reshape() assim: reshape((-1, N), order='F')

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top