Introdução ao NumPy
Prof. Dr. Anselmo R. Pitombeira Neto
OPL - Pesquisa Operacional em Produção e Logística, Departamento de Engenharia de Produção,
Universidade Federal do Ceará
ver 0.2
O Numerical Python (NumPy) é uma biblioteca para a linguagem Python com funções para se trabalhar
com computação numérica. Seu principal objeto é o vetor n-dimensional, ou ndarray. Um vetor n-
dimensional também é conhecido pelo nome tensor. A principal característica do ndarray é que ele
deve ser homogêneo, ou seja, diferentemente do objeto lista, todos os seus elementos devem ser do
mesmo tipo.
Criação de tensores
Pode-se criar um ndarray passando um container como inicializador para um objeto da classe array:
import numpy as np
v = [Link]([1,2,3,4])
print(v)
[1 2 3 4]
O tipo dos elementos de um ndarray pode ser acessado por meio do atributo dtype
print([Link])
int64
Neste caso, ao criar o vetor, o tipo default assumido foi int64, mas o tipo desejado pode ser
especi cado na criação do tensor:
v = [Link]([1,2,3,4], dtype='float64')
print([Link])
float64
No caso, se utilizarmos números com pontos decimais, o tipo será automaticamente float:
v = [Link]([1.0,2.0,3.0,4.0])
print([Link])
float64
Os tensores NumPy também possuem um atributo chamado shape. Esse atributo indica a forma do
tensor, por exemplo:
print([Link])
(4,)
Neste caso, o tensor v possui 1 dimensão (ou eixo) com 4 elementos. Um tensor unidimensional
corresponde a um vetor. Podemos também criar um tensor bidimensional (uma matriz) usando o
atributo shape:
v = [Link]([1,2,3,4])
[Link] = (2,2)
print(v)
[[1 2]
[3 4]]
Outra forma útil de mudar o shape de um tensor é simplesmente utilizando a função reshape:
v = [Link]([1,2,3,4]).reshape(2,2)
print(v)
[[1 2]
[3 4]]
A função reshape aceita o uso de -1 como dimensão na especi cação do shape. Neste caso, a função
reshape determinará automaticamente o número de elementos em cada dimensão de forma a manter
inalterado o número total de elementos no tensor. No exemplo abaixo, especi camos o tamanho da
primeira dimensão como igual a 2. A função reshape então determinará automaticamente qual o
tamanho da segunda dimensão:
v = [Link]([1,2,3,4,5,6,7,8])
v = [Link](2, -1)
print(v)
[[1 2 3 4]
[5 6 7 8]]
O número de eixos (ou dimensões) de um tensor é dado pelo atributo ndim, enquanto o número total de
elementos é dado por size:
v = [Link](range(50)).reshape(2,5,5)
print('Shape = ', [Link])
print('Número de dimensões = ', [Link])
print('Número de elementos = ', [Link])
print('Tensor v = \n', v)
Shape = (2, 5, 5)
Número de dimensões = 3
Número de elementos = 50
Tensor v =
[[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]
[20 21 22 23 24]]
As funções zeros, ones e diag são muito convenientes para a criação de tensores:
[[25 26 27 28 29]
[30 31 32 33 34]
[35 36 37 38 39]
V = [Link]((3,3))
print('V[40 41 42
= \n', V) 43 44]
[45 46 47 48 49]]]
U = [Link]((3,3))
print('U = \n', U)
D = [Link]([10, 10, 10])
print('D = \n', D)
V =
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
U =
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
D =
[[10 0 0]
[ 0 10 0]
[ 0 0 10]]
As funções arange e linspace permitem a criação de sequências como tensores NumPy:
v = [Link](0, 5, 0.5)
u = [Link](0, 5, 10)
print("v =", v)
print("u =", u)
v = [0. 0.5 1. 1.5 2. 2.5 3. 3.5 4. 4.5]
u = [0. 0.55555556 1.11111111 1.66666667 2.22222222 2.77777778
3.33333333 3.88888889 4.44444444 5. ]
O método flatten retorna uma cópia do tensor com todos os elementos em apenas uma dimensão:
v = [Link](range(50)).reshape(2,5,5)
print([Link]())
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
48 49]
O atrubuto flat retorna um iterador nos elementos de um tensor:
v_iter = [Link]
for i in v_iter:
print(i, end=' ')
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
Indexação de tensores
O acesso aos elementos de um tensor pode ser feito utilizando colchetes e o índice do elemento:
print(v[1])
[[25 26 27 28 29]
[30 31 32 33 34]
[35 36 37 38 39]
[40 41 42 43 44]
[45 46 47 48 49]]
Tensores com rank maior ou igual a 2, por exemplo, acessar o elemento com índice 0 no primeiro eixo e
índice 1 no segundo eixo:
v = [Link]([10, 20, 30, 40]).reshape(2,2)
print(v[0,1])
20
Tensor de rank 3:
v = [Link](8).reshape(2,2,2)
print("v =\n", v)
print("\n v[0,0,1] =", v[0,0,1])
v =
[[[0 1]
[2 3]]
[[4 5]
[6 7]]]
v[0,0,1] = 1
Subtensores contíguos de um tensor podem ser acessados utilizando o operador " : ". Note que neste
caso o NumPy não faz uma cópia dos dados, mas apenas cria uma view dos dados:
v = [Link](10)
u = v[1:3]
print(u)
[1 2]
No caso de uma matriz (um tensor de rank 2), pode-se acessar submatrizes:
A = [Link](15).reshape(3,5)
print(A)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
Vamos selecionar somente a submatriz correspondente às linhas 0 e 1, e colunas 2 e 3:
subA = A[0:2, 2:4]
print(subA)
[[2 3]
[7 8]]
A indexação pode ser realiza também passando uma lista ou outro tensor que contém os índices dos
elementos que queremos selecionar, por exemplo, abaixo queremos selecionar somente os elementos
com índice 2 e 3. Observe que, neste caso, o tensor v resultante é um novo objeto criado a partir do
tensor u e alocado na memória. Ocorre cópia dos dados.
u = [Link]([2.0, 3.5, 4.0, -10.1])
v = u[[2,3]]
print(v)
[ 4. -10.1]
Filtrando as colunas 2 e 4 de uma matriz:
A = [Link](10).reshape(2,5)
B = A[:, [2,4]]
print("A =\n", A)
print("B =\n", B)
A =
[[0 1 2 3 4]
[5 6 7 8 9]]
B =
[[2 4]
[7 9]]
Operações sobre tensores
As operações sobre tensores NumPy são realizada elemento a elemento, por exemplo:
v = [Link]([10,20,30])
u = [Link]([2,2,2])
w = u+v
print(w)
[12 22 32]
Note que no caso dos operadores * e / a operação também ocorre elemento a elemento:
w = u*v
print("w =", w)
x = u/v
print("x =", x)
w = [20 40 60]
x = [0.2 0.1 0.06666667]
Elevar todos os elementos de um vetor a uma potência:
x = [Link]([10, 20])
y = x**2
print(y)
[100 400]
Alguns operadores unários como a média, menor valor, maior valor, etc possuem funções especí cas:
x = [Link](10)
media = [Link]()
menor_valor = [Link](x)
arg_max = [Link](x)
print("Média =", media)
print("Menor valor =", menor_valor)
print("Arg max =", arg_max)
Média = 4.5
Menor valor = 0
Arg max = 9
Em tensores com rank maior ou igual a 2 pode-se identifcar o eixo em que se deseja aplicar as funções
min, max, etc. Caso o eixo não seja especi cado, a funções retornará o menor/maior valor entre todos
incluindo todos os eixos:
A = [Link]([10, 30, 40, 20]).reshape(2,2)
menor= [Link]()
menor_colunas = [Link](axis=0)
print("A = \n", A)
print("Menor valor = ", menor)
print("Menor valor em cada coluna =", menor_colunas)
A =
[[10 30]
[40 20]]
Menor valor = 10
Menor valor em cada coluna = [10 20]
A operação de produto interno (ou produto escalar) pode ser realizada pela função dot:
w = [Link](u, v)
print("w =", w)
x = [Link](v)
print("x =", x)
w = 120
x = 120
Multiplicação de um escalar por um vetor:
print("w =", w)
x = 10*w
print("x =", x)
w = 120
x = 1200
Um erro muito comum no uso do NumPy é não explorar sua e ciência computacional por meio das
operações vetoriais. Dentro do possível, deve-se sempre organizar implementação de um algoritmo
utilizando somente operações sobre tensores. Por exemplo, abaixo é feita a comparação entre o tempo
computacional em usar a operação dot e uma implementação da mesma operação utilizando loops:
def produto_interno(u, v):
prod = 0
for i in range([Link]):
prod+=u[i]*v[i]
return prod
u = [Link](10000)
v = [Link](10000)
%timeit produto_interno(u,v)
%timeit [Link](u,v)
100 loops, best of 3: 3.22 ms per loop
The slowest run took 14.86 times longer than the fastest. This could mean that an i
100000 loops, best of 3: 3.39 µs per loop
Observe que a operação dot é muito mais rápida. Isso ocorre porque a operação dot é implementada
em linguagem C e realiza a operação de maneira vetorizada.
Matrizes
Em NumPy, matrizes são tensores com 2 dimensões (tensores de rank 2). Embora exista um objeto
matrix, em geral é mais prático tratar matrizes como tensores de rank 2:
A = [Link]([[10, 20], [30, 40]])
print(A)
[[10 20]
[30 40]]
Criar uma matriz identidade:
I = [Link](5)
print(I)
[[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]
Matrizes diagonais:
D = [Link]([Link](5))
print(D)
[[0 0 0 0 0]
[0 1 0 0 0]
[0 0 2 0 0]
[0 0 0 3 0]
[0 0 0 0 4]]
Operações sobre matrizes
Multilplicação de vetores e matrizes é realizada por meio da função dot:
v = [Link]([10, 10])
A = [Link](4).reshape(2,2)
u = [Link](v)
print(u)
[10 50]
Multiplicação de matrizes:
A = [Link]((2,2))
B = 10*[Link]((2,2))
C= [Link](A,B)
print(C)
[[20. 20.]
[20. 20.]]
A multiplicação de matrizes também pode ser realizada por meio do operador @:
C = A @ B
print(C)
[[20. 20.]
[20. 20.]]
Transposição de matrizes pode ser feita com a função transpose ou simplesmente com a letra " T ":
A = [Link](4).reshape(2,2)
print("Transposta de A =\n", [Link]())
print("Transposta de A =\n", A.T)
Transposta de A =
[[0 2]
[1 3]]
Transposta de A =
[[0 2]
[1 3]]
Funções universais
O NumPy oferece diversas funções matemáticas clássicas, como exponencial, logaritmo, etc. Essas
funções são aplicadas a todos os elementos de um tensor. Exemplo de aplicação da função
exponencial:
u = [Link](5)
v = [Link](u)
print(v)
[ 1. 2.71828183 7.3890561 20.08553692 54.59815003]
Função seno:
v = [Link](u)
print(v)
[ 0. 0.84147098 0.90929743 0.14112001 -0.7568025 ]
Operadores lógicos
As operações lógicas entre tensores ocorrem elemento a elemento. O resultado da operação é um
tensor booleano com valores True ou False que indicam se a condição lógica é verdadeira ou falsa:
u = [Link](4).reshape(2,2)
v = 2*[Link]((2,2))
w = u > v
print(w)
[[False False]
[False True]]
Os tensores booleanos podem ser usados para ltrar valores em um tensor, por exemplo:
u = [Link]([-1, 2, -3])
v = [Link]([True, False, True])
print(u[v])
[-1 -3]
Note que podemos obter o mesmo efeito anterior de ltrar somente os valores negativos fazendo:
w = u[u < 0]
print(w)
[-1 -3]
E podemos também atribuir um valor especí co aos elementos de um tensor que satisfazem uma
certa condições, por exemplo zerar todos os valores negativos:
print("u =", u)
u[u < 0] = 0
print("u =", u)
u = [-1 2 -3]
u = [0 2 0]
As funções all e any são muito úteis em comparações lógicas. all retorna True se todos os
elementos de um tensor booleano forem True:
v = [Link](5)
u = v >= 0
print('u =', u)
print([Link](u))
u = [ True True True True True]
True
enquanto any retorna True se pelo menos um elemento de um tensor booleano for True:
v = [Link]([-1, 1, 2, 3])
u = v > 0
print('u =',u)
print([Link](u))
u = [False True True True]
True
Geração de números aleatórios
O NumPy possui um submódulo chamado random que possui diversas funções para a geração de
números (pseudo) aleatórios. Embora o Python possua uma biblioteca padrão também chamada
random, a biblioteca do NumPy tem mais funcionalidas e gera diretamente tensores aleatórios.
Criação de um tensor segundo uma distribuição uniforme [0,1):
import [Link] as rd
v = [Link](4,4)
print(v)
[[0.9831372 0.79841392 0.4437526 0.09605361]
Criação[0.74452737 0.66893323
de um tensor em 0.24795263
que cada elemento 0.22560301]
segue uma distribuição normal com μ = 10.0 e
[0.37761419
σ = 1.0:
0.92980357 0.05430976 0.10674321]
[0.49004413 0.40426064 0.05184504 0.21665041]]
v = [Link](10, 1, (4,4))
print(v)
[[10.77054463 10.72893742 11.28370603 11.30094852]
[ 9.51623206 11.65177357 9.52369916 10.95664214]
[ 9.5996557 9.55722951 10.21735062 11.32174649]
[11.37334574 10.24518484 12.00974426 8.76172723]]
Note que toda vez que rodarmos o código, os tensores terão valores diferentes. Podemos evitar esse
comportamento, de forma que toda vez que o código é executado o tensor aleatório tenha o mesmo
valor por meio da função seed, cujo argumento é a semente para o gerador de números aleatórios do
Python:
[Link](1000)
v = [Link](4)
print(v)
[Link](1000)
v = [Link](4)
print(v)
[0.65358959 0.11500694 0.95028286 0.4821914 ]
[0.65358959 0.11500694 0.95028286 0.4821914 ]
Álgebra Linear
O NumPy possui um submódulo especí co para a realização de operações típicas da álgebra linear
chamado linalg. Com este módulo é possível resolver sistemas lineares, obter inversas de matrizes,
calcular autovalores e autovetores, etc.
Solução de sistemas lineares
A solução de sistemas de equações lineares do tipo Ax = b pode ser realizada por meio da função
solve:
A = [Link]([10, 20, 30, 40]).reshape(2,2)
b = [Link]([5,10])
x = [Link](A, b)
print(x)
[0. 0.25]
Caso b = [b1 , b2 , . . . , bp ]em que cada bj , j = 1, . . . , pé um vetor coluna, a função solve retorna
uma matriz x = [x1 , x2 , . . . , xp ]em que cada xj é solução do sistema linear Axj = bj .
b1 = [Link]([5, 10]).reshape(2,1)
b2 = [Link]([10, 12]).reshape(2,1)
b = [Link]([b1, b2])
print("b =\n", b)
x = [Link](A, b)
print("x =\n", x)
b =
[[ 5 10]
[10 12]]
x =
[[ 0. -0.8 ]
[ 0.25 0.9 ]]
Note que resolver o sistema linear simultaneamente para b1 e b2 é mais e ciente que resolver cada
sistema individualmente:
def fun(A, b1, b2):
x1 = [Link](A,b1)
x2 = [Link](A,b2)
A = [Link](1000,1000)
b1 = [Link](1000).reshape(1000,1)
b2 = [Link](1000).reshape(1000,1)
b = [Link]([b1,b2])
%timeit fun(A,b1,b2)
%timeit [Link](A,b)
10 loops, best of 3: 74.3 ms per loop
10 loops, best of 3: 37.7 ms per loop
Matriz inversa
O cálculo da inversa A−1 de uma matriz A pode ser realizado pela função inv:
A = [Link]([10, 20, 30, 40]).reshape(2,2)
inv_A = [Link](A)
print(inv_A)
[[-0.2 0.1 ]
[ 0.15 -0.05]]
Rank e determinante
rank_A = [Link].matrix_rank(A)
print("Rank de A =",rank_A)
det_A = [Link](A)
print("Determinante de A = %.2f" % det_A)
Rank de A = 2
Determinante de A = -200.00
Broadcasting
Em princípio, operações sobre tensores com shapes incompatíveis são inde nidas. Por exemplo,
podemos somar dois tensores com shapes (4, 4), resultando em um tensor com também com shape
(4,4), uma vez que a operação de soma será aplicada elemento a elemento. No entanto, o que acontece
se tentarmos somar um tensor com shape (4,4) e outro com shape (1,4) ? Neste caso, o NumPy
realizará o broadcasting do vetor (1,4) para poder realizar a operação. Dentro de certas regras de
broadcasting, o NumPy permite a aplicações de operações sobre tensores com shapes diferentes.
As dimensões de dois tensores são consideradas compatíveis quando elas são a mesma ou uma delas
é 1. Por exemplo, as dimensões dos tensores u = (4,4) e v = (1,4) são compatíveis pois a primeira
dimensão do tensor v é 1 e a segunda dimensão de ambos é 4. Neste caso, o tensor v será "estendido"
até car com shape (4,4) por meio da replicação dos seus elementos:
u = [Link]((4,4))
v = [Link]([10, 20, 30, 40]).reshape(1,4)
x = u*v
print('u =\n',u)
print('v =\n',v)
print('x =\n',x)
u =
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]
v =
[[10 20 30 40]]
x =
[[10. 20. 30. 40.]
[10. 20. 30. 40.]
[10. 20. 30. 40.]
[10. 20. 30. 40.]]