Qual é a maneira mais eficiente de mapear uma função em uma matriz numpy? A maneira como eu faço isso no meu projeto atual é a seguinte:
import numpy as np
x = np.array([1, 2, 3, 4, 5])
# Obtain array of square of each element in x
squarer = lambda t: t ** 2
squares = np.array([squarer(xi) for xi in x])
No entanto, isso parece provavelmente muito ineficiente, pois estou usando uma compreensão de lista para construir a nova matriz como uma lista Python antes de convertê-la novamente em uma matriz numpy.
Podemos fazer melhor?
python
performance
numpy
Ryan
fonte
fonte
squarer(x)
?x = np.array([1, 2, 3, 4, 5]); x**2
funcionaRespostas:
Eu testei todos os métodos sugeridos mais
np.array(map(f, x))
comperfplot
(um pequeno projeto meu).Se a função que você está tentando vetorizar já estiver vetorizada (como o
x**2
exemplo na postagem original), usar isso é muito mais rápido do que qualquer outra coisa (observe a escala de log):Se você realmente precisa de vetorização, não importa muito qual variante você usa.
Código para reproduzir as parcelas:
fonte
f(x)
fora sua trama. Pode não ser aplicável a todosf
, mas é aplicável aqui, e é facilmente a solução mais rápida quando aplicável.vf = np.vectorize(f); y = vf(x)
ganhos para entradas breves.pip install -U perfplot
), vejo a mensagem:AttributeError: 'module' object has no attribute 'save'
ao colar o código de exemplo.Que tal usar
numpy.vectorize
.fonte
The vectorize function is provided primarily for convenience, not for performance. The implementation is essentially a for loop.
Em outras perguntas, descobri quevectorize
pode dobrar a velocidade de iteração do usuário. Mas a aceleração real é comnumpy
operações de matriz reais .squarer(x)
já funcionaria para matrizes não-1d.vectorize
só realmente tem alguma vantagem sobre a compreensão de uma lista (como a da questão), e não sobresquarer(x)
.TL; DR
Conforme observado por @ user2357112 , um método "direto" de aplicar a função é sempre a maneira mais rápida e simples de mapear uma função sobre matrizes Numpy:
Geralmente evite
np.vectorize
, pois não apresenta um bom desempenho e possui (ou teve) vários problemas . Se você estiver lidando com outros tipos de dados, convém investigar os outros métodos mostrados abaixo.Comparação de métodos
Aqui estão alguns testes simples para comparar três métodos para mapear uma função, usando este exemplo com Python 3.6 e NumPy 1.15.4. Primeiro, as funções de configuração para teste:
Teste com cinco elementos (classificados do mais rápido para o mais lento):
Com centenas de elementos:
E com milhares de elementos de matriz ou mais:
Versões diferentes do Python / NumPy e otimização do compilador terão resultados diferentes, portanto faça um teste semelhante para o seu ambiente.
fonte
count
argumento e uma expressão geradora,np.fromiter
será significativamente mais rápido.'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))'
f(x)
, que supera todo o resto em uma ordem de magnitude .f
tiver 2 variáveis e a matriz for 2D?Existem numexpr , numba e cython , o objetivo desta resposta é levar essas possibilidades em consideração.
Mas primeiro vamos declarar o óbvio: não importa como você mapeie uma função Python em um array numpy, ela permanece uma função Python, o que significa para todas as avaliações:
Float
).Portanto, quais máquinas são usadas para fazer um loop na matriz não desempenham um grande papel por causa da sobrecarga mencionada acima - ele permanece muito mais lento do que usar a funcionalidade incorporada do numpy.
Vamos dar uma olhada no seguinte exemplo:
np.vectorize
é escolhido como um representante da classe de abordagens da função python puro. Usandoperfplot
(veja o código no apêndice desta resposta) obtemos os seguintes tempos de execução:Podemos ver que a abordagem numpy é 10x-100x mais rápida que a versão python pura. A diminuição do desempenho para tamanhos de matriz maiores é provavelmente porque os dados não se ajustam mais ao cache.
Vale mencionar também, que
vectorize
também usa muita memória, e muitas vezes o uso da memória é o gargalo (consulte a pergunta SO relacionada ). Observe também que a documentação da numpynp.vectorize
afirma que é "fornecida principalmente por conveniência, não por desempenho".Outras ferramentas devem ser usadas, quando o desempenho é desejado, além de escrever uma extensão C a partir do zero, existem as seguintes possibilidades:
Ouve-se com frequência que o desempenho numpy é tão bom quanto ele ganha, porque é puro C sob o capô. No entanto, há muito espaço para melhorias!
A versão numpy vetorizada usa muita memória e acessos à memória adicionais. A biblioteca Numexp tenta agrupar as matrizes numpy e, assim, obter uma melhor utilização do cache:
Leva à seguinte comparação:
Não posso explicar tudo no gráfico acima: podemos ver uma sobrecarga maior para a biblioteca numexpr no início, mas como ela utiliza melhor o cache, é cerca de 10 vezes mais rápida para matrizes maiores!
Outra abordagem é compilar rapidamente a função e, assim, obter um UFunc C puro puro. Esta é a abordagem da numba:
É 10 vezes mais rápido que a abordagem numpy original:
No entanto, a tarefa é embaraçosamente paralelelizável, portanto, também poderíamos usar
prange
para calcular o loop em paralelo:Como esperado, a função paralela é mais lenta para entradas menores, mas mais rápida (quase fator 2) para tamanhos maiores:
Enquanto a numba se especializa em otimizar operações com matrizes numpy, o Cython é uma ferramenta mais geral. É mais complicado extrair o mesmo desempenho que o numba - geralmente é o llvm (numba) versus o compilador local (gcc / MSVC):
O Cython resulta em funções um pouco mais lentas:
Conclusão
Obviamente, testar apenas uma função não prova nada. Também devemos ter em mente que, para o exemplo de função escolhido, a largura de banda da memória era o gargalo para tamanhos maiores que 10 ^ 5 elementos - portanto, tivemos o mesmo desempenho para numba, numexpr e cython nessa região.
No final, a resposta definitiva depende do tipo de função, hardware, distribuição Python e outros fatores. Por exemplo Anaconda-de distribuição usa VML da Intel para funções de numpy e assim Supera numba (a menos que ele usa SVML, consulte este SO-post ) facilmente para funções transcendentais como
exp
,sin
,cos
e semelhante - ver, por exemplo o seguinte SO-post .No entanto, a partir desta investigação e da minha experiência até agora, eu afirmaria que o numba parece ser a ferramenta mais fácil com melhor desempenho, desde que nenhuma função transcendental esteja envolvida.
Plotando tempos de execução com perfplot -package :
fonte
As operações aritméticas em matrizes são aplicadas automaticamente de maneira elementar, com loops eficientes no nível C que evitam toda a sobrecarga do interpretador que se aplicaria a um loop ou compreensão no nível do Python.
A maioria das funções que você deseja aplicar a uma matriz NumPy elementwise funcionará, embora algumas possam precisar de alterações. Por exemplo,
if
não funciona de maneira elementar. Você deseja convertê-los para usar construções comonumpy.where
:torna-se
fonte
Eu acredito que na versão mais recente (eu uso o 1.13) do numpy, você pode simplesmente chamar a função passando a matriz numpy para a função que você escreveu para o tipo escalar, ela aplicará automaticamente a chamada de função a cada elemento na matriz numpy e retornará você outra matriz numpy
fonte
**
operador que aplica o cálculo a cada elemento t det
. Isso é entorpecido comum. Envolvê-lo nolambda
não faz nada extra.Em muitos casos, numpy.apply_along_axis será a melhor opção. Aumenta o desempenho em cerca de 100x em comparação com as outras abordagens - e não apenas para funções triviais de teste, mas também para composições de funções mais complexas, como numpy e scipy.
Quando adiciono o método:
para o código perfplot, obtenho os seguintes resultados:
fonte
Parece que ninguém mencionou um método de fábrica embutido para produzir
ufunc
em embalagens numpy:np.frompyfunc
que eu testei novamentenp.vectorize
e superei em cerca de 20 a 30%. Obviamente, ele funcionará bem como o código C prescrito ou mesmonumba
(que eu não testei), mas pode ser uma alternativa melhor do quenp.vectorize
Também testei amostras maiores e a melhoria é proporcional. Veja a documentação também aqui
fonte
Conforme mencionado neste post , basta usar expressões geradoras como estas :
fonte
Todas as respostas acima se comparam bem, mas se você precisar usar a função personalizada para mapeamento, e tiver
numpy.ndarray
, e precisar manter a forma da matriz.Comparei apenas dois, mas ele manterá a forma de
ndarray
. Eu usei a matriz com 1 milhão de entradas para comparação. Aqui eu uso a função quadrada, que também está embutida em numpy e tem um ótimo desempenho, já que, como havia necessidade de algo, você pode usar a função de sua escolha.Resultado
aqui você pode ver claramente que
numpy.fromiter
funciona muito bem considerando uma abordagem simples e, se a função embutida estiver disponível, use-a.fonte
Usar
numpy.fromfunction(function, shape, **kwargs)
Consulte " https://docs.scipy.org/doc/numpy/reference/generated/numpy.fromfunction.html "
fonte