Python é conveniente e flexível, mas notavelmente mais lento que outras linguagens em termos de velocidade computacional bruta. O ecossistema Python compensou com ferramentas que tornam a análise de números em escala em Python rápida e conveniente.
NumPy é uma das ferramentas Python mais comuns que desenvolvedores e cientistas de dados usam para assistência com computação em escala. Ele fornece bibliotecas e técnicas para trabalhar com arrays e matrizes, todas apoiadas por código escrito em linguagens de alta velocidade como C, C++ e Fortran. E todas as operações do NumPy acontecem fora o tempo de execução do Python, para que não sejam restringidos pelas limitações do Python.
Usando NumPy para matemática de arrays e matrizes em Python
Muitas operações matemáticas, especialmente em aprendizado de máquina ou ciência de dados, envolvem trabalhar com matrizesou listas de números. A maneira ingênua de fazer isso em Python é armazenar os números em uma estrutura, normalmente uma estrutura Python. list
, em seguida, faça um loop na estrutura e execute uma operação em cada elemento dela. Isso é lento e ineficiente, já que cada elemento deve ser traduzido de um objeto Python para um número nativo da máquina.
NumPy fornece um tipo de array especializado que é otimizado para funcionar com tipos numéricos nativos de máquina, como inteiros ou flutuantes. Os arrays podem ter qualquer número de dimensões, mas cada array usa um tipo de dados uniforme, ou tipo dpara representar seus dados subjacentes.
Aqui está um exemplo simples:
import numpy as np
np.array((0, 1, 2, 3, 4, 5, 6))
Isso cria um array NumPy unidimensional da lista fornecida. Não especificamos um dtype
para esta matriz, portanto, é inferido automaticamente a partir dos dados fornecidos que será um número inteiro assinado de 32 ou 64 bits (dependendo da plataforma). Se quiséssemos ser explícitos sobre o dtype, poderíamos fazer o seguinte:
np.array((0, 1, 2, 3, 4, 5, 6), dtype=np.uint32)
np.uint32
é, como o nome indica, o dtype
para um inteiro não assinado de 32 bits.
Isto é possível usar objetos Python genéricos como dtype
para um array NumPy, mas se você fizer isso, não obterá melhor desempenho com NumPy do que obteria com Python em geral. NumPy funciona melhor para nativo da máquina tipos numéricos (int
é, float
s) em vez de Nativo de Python tipos (números complexos, o Decimal
tipo).
Como NumPy acelera a matemática de array em Python
Uma grande parte da velocidade do NumPy vem do uso de tipos de dados nativos da máquina, em vez dos tipos de objetos do Python. Mas o outro grande motivo pelo qual o NumPy é rápido é porque ele fornece maneiras de trabalhar com arrays sem a necessidade de endereçar cada elemento individualmente.
Os arrays NumPy têm muitos dos comportamentos dos objetos Python convencionais, por isso é tentador usar metáforas comuns do Python para trabalhar com eles. Se quiséssemos criar um array NumPy com os números de 0 a 1000, poderíamos, em teoria, fazer o seguinte:
x = np.array((_ for _ in range(1000)))
Isso funciona, mas seu desempenho é limitado pelo tempo que o Python leva para criar uma lista e para o NumPy converter essa lista em um array.
Por outro lado, podemos fazer a mesma coisa com muito mais eficiência dentro do próprio NumPy:
x = np.arange(1000)
Você pode usar muitos outros tipos de operações integradas do NumPy para criar novos arrays sem loop: criar arrays de zeros (ou qualquer outro valor inicial) ou usar um conjunto de dados, buffer ou outra fonte existente.
Outra maneira importante de o NumPy acelerar as coisas é fornecer maneiras de não precisar abordar os elementos do array individualmente para trabalhar neles em escala.
Conforme observado acima, os arrays NumPy se comportam de maneira muito semelhante a outros objetos Python, por uma questão de conveniência. Por exemplo, eles podem ser indexados como listas; arr(0)
acessa o primeiro elemento de uma matriz NumPy. Isso permite definir ou ler elementos individuais em uma matriz.
No entanto, se você deseja modificar todos os elementos de um array, é melhor usar as funções de “transmissão” do NumPy – maneiras de executar operações em um array inteiro, ou uma fatia, sem loop em Python. Novamente, isso ocorre para que todo o trabalho sensível ao desempenho possa ser feito no próprio NumPy.
Aqui está um exemplo:
x1 = np.array(
(np.arange(0, 10),
np.arange(10,20))
)
Isso cria uma matriz NumPy bidimensional, cada dimensão consistindo em um intervalo de números. (Podemos criar arrays de qualquer número de dimensões simplesmente usando listas aninhadas no construtor.)
(( 0 1 2 3 4 5 6 7 8 9)
(10 11 12 13 14 15 16 17 18 19))
Se quiséssemos transpor os eixos deste array em Python, precisaríamos escrever algum tipo de loop. NumPy nos permite fazer este tipo de operação com um único comando:
x2 = np.transpose(x1)
A saída:
(( 0 10)
( 1 11)
( 2 12)
( 3 13)
( 4 14)
( 5 15)
( 6 16)
( 7 17)
( 8 18)
( 9 19))
Operações como essas são a chave para usar bem o NumPy. NumPy oferece um amplo catálogo de rotinas integradas para manipulação de dados de array. Integrados para álgebra linear, transformadas discretas de Fourier e geradores de números pseudoaleatórios evitam que você tenha que rolar essas coisas sozinho também. Na maioria dos casos, você pode realizar o que precisa com um ou mais recursos integrados, sem usar operações Python.
Funções universais NumPy (ufuncs)
Outro conjunto de recursos que o NumPy oferece que permitem executar técnicas de computação avançadas sem loops Python são chamados de funções universais, ou ufunc
é para abreviar. ufunc
s pegam um array, executam alguma operação em cada elemento do array e enviam os resultados para outro array ou fazem a operação no local.
Um exemplo:
x1 = np.arange(1, 9, 3)
x2 = np.arange(2, 18, 6)
x3 = np.add(x1, x2)
Aqui, np.add
pega cada elemento de x1
e adiciona-o a x2
com os resultados armazenados em um array recém-criado, x3
. Isso rende ( 3 12 21)
. Toda a computação real é feita no próprio NumPy.
ufunc
s também possuem métodos de atributos que permitem aplicá-los com mais flexibilidade e reduzem a necessidade de loops manuais ou lógica do lado do Python. Por exemplo, se quiséssemos pegar x1
E use np.add
para somar o array, poderíamos usar o .add
método np.add.accumulate(x1)
em vez de fazer um loop em cada elemento da matriz para criar uma soma.
Da mesma forma, digamos que queremos realizar uma função de redução – isto é, aplicar .add
ao longo de um eixo de uma matriz multidimensional, com os resultados sendo uma nova matriz com uma dimensão a menos. Poderíamos fazer um loop e criar um novo array, mas isso seria lento. Ou poderíamos usar np.add.reduce
para conseguir a mesma coisa sem loop:
x1 = np.array(((0,1,2),(3,4,5)))
# ((0 1 2) (3 4 5))
x2 = np.add.reduce(x1)
# (3 5 7)
Também podemos realizar reduções condicionais, usando um where
argumento:
x2 = np.add.reduce(x1, where=np.greater(x1, 1))
Isso retornaria x1+x2
mas apenas nos casos em que os elementos em x1
O primeiro eixo do é maior que 1
; caso contrário, apenas retorna o valor dos elementos do segundo eixo. Novamente, isso nos poupa de ter que iterar manualmente o array em Python. NumPy fornece mecanismos como este para filtrar e classificar dados por algum critério, então não precisamos escrever loops – ou pelo menos, os loops que fazer escrever são reduzidos ao mínimo.
NumPy e Cython: usando NumPy com C
A biblioteca Cython em Python permite escrever código Python e convertê-lo em C para obter velocidade, usando tipos C para variáveis. Essas variáveis podem incluir matrizes NumPy, portanto, qualquer código Cython que você escrever pode funcionar diretamente com matrizes NumPy.
Usar Cython com NumPy confere alguns recursos poderosos:
- Acelerando loops manuais: às vezes você não tem escolha a não ser fazer um loop em um array NumPy. Escrever a operação de loop em um módulo Cython fornece uma maneira de executar o loop em C, em vez de Python, e, portanto, permite acelerações drásticas. Observe que isso só é possível se os tipos de todas as variáveis em questão forem matrizes NumPy ou tipos C nativos da máquina.
- Usando arrays NumPy com bibliotecas C: Um caso de uso comum para Cython é escrever wrappers Python convenientes para bibliotecas C. O código Cython pode atuar como uma ponte entre uma biblioteca C existente e arrays NumPy.
Cython permite duas maneiras de trabalhar com arrays NumPy. Uma é por meio de um memoryview digitado, uma construção Cython para acesso rápido e seguro a um array NumPy. Outra é obter um ponteiro bruto para os dados subjacentes e trabalhar diretamente com ele, mas isso tem o custo de ser potencialmente inseguro e exigir que você conheça antecipadamente o layout da memória do objeto.
NumPy e Numba: código Python com aceleração JIT para NumPy
Outra maneira de usar Python com bom desempenho com arrays NumPy é usar Numba, um compilador JIT para Python. Numba traduz código interpretado em Python em código nativo de máquina, com especializações para coisas como NumPy. Loops em Python sobre arrays NumPy podem ser otimizados automaticamente desta forma. Mas as otimizações do Numba são automáticas apenas até certo ponto e podem não manifestar melhorias significativas de desempenho para todos os programas.