Produto externo de vetores com numpy.outer()
Em álgebra linear, o produto externo de dois vetores u e v resulta em uma matriz onde cada elemento na posição (i, j) é o produto dos elementos correspondentes em u e v. Ou seja, cada elemento [i, j] da matriz resultante é o produto do i-ésimo elemento do primeiro vetor e do j-ésimo elemento do segundo vetor. O NumPy fornece a função outer() para calcular isso de forma eficiente. Exemplo:
import numpy as np
u = np.array([0, 1])
v = np.array([2, 3])
resultado = np.outer(u, v)
print(resultado)
# resultado:
# [[0 0]
# [2 3]]
Sintaxe da função numpy.outer()
A sintaxe da função numpy.outer() é bem simples:
numpy.outer(u, v, out=None)
onde u e v correspondem aos vetores que devem ser multiplicados. O vetor u é o primeiro e o v é o segundo. Os vetores são 1D ou são achatados durante a operação. O vetor resultante out é opcional.
Mais exemplos da função numpy.outer()
u = np.array([1, 2])
v = np.array([3, 4])
resultado = np.outer(u, v)
print(resultado)
# resultado:
# [[3 4]
# [6 8]]
Neste exemplo, np.outer(u, v) multiplica cada elemento de u por cada elemento de v para formar uma matriz 2×2. No exemplo abaixo, os arranjos u e v têm dimensões (5,), a matriz resultante é 5 x 5:
u = np.array([0, 1, 2, 3, 4])
v = np.array([5, 6, 7, 8, 9])
resultado = np.outer(u, v)
print(resultado)
# resultado:
# [[ 0 0 0 0 0]
# [ 5 6 7 8 9]
# [10 12 14 16 18]
# [15 18 21 24 27]
# [20 24 28 32 36]]
Com arranjos de dimensões maiores, a função np.outer() primeiro achata ambas as matrizes envolvidas na operação e então calcula o produto externo:
u = np.array([[0, 1], [3, 4]])
v = np.array([[5, 6], [7, 8]])
resultado = np.outer(u, v)
print(resultado)
# resultado:
# [[ 0 0 0 0]
# [ 5 6 7 8]
# [15 18 21 24]
# [20 24 28 32]]
Neste exemplo, o produto externo de duas matrizes 2 x 2 resultou em uma matriz 4×4. Note que essa nova dimensão reflete a multiplicação entre cada i-ésimo elemento do primeiro e o j-ésimo elemento do segundo vetor. O achatamento observado na matriz resultante fica mais evidente quando usamos a função np.outer() com arranjos de dimensões maiores ainda, como no exemplo abaixo:
u = np.array([[[0, 1], [3, 4]], [[5, 6], [7, 8]]])
v = np.array([[[9, 10], [11, 12]], [[13, 14], [15, 16]]])
resultado = np.outer(u, v)
print(resultado)
# resultado:
# [[ 0 0 0 0 0 0 0 0]
# [ 9 10 11 12 13 14 15 16]
# [ 27 30 33 36 39 42 45 48]
# [ 36 40 44 48 52 56 60 64]
# [ 45 50 55 60 65 70 75 80]
# [ 54 60 66 72 78 84 90 96]
# [ 63 70 77 84 91 98 105 112]
# [ 72 80 88 96 104 112 120 128]]
Enquanto os arranjos u e v têm dimensão 2 x 2 x 2, a matriz resultante tem dimensão 8 x 8.
Não confunda o achatamento que ocorre em numpy.outer() com numpy.flatten()
Se você está acostumado a usar a função np.flatten() deve estar se perguntando por que a função np.outer() não transforma arranjos multidimensionais em vetores 1D como na função np.flatten(). Veja no exemplo abaixo o que acontece se achatarmos 2 matrizes com np.flatten() e depois as multiplicarmos:
u = np.array([[[0, 1], [3, 4]], [[5, 6], [7, 8]]])
v = np.array([[[9, 10], [11, 12]], [[13, 14], [15, 16]]])
u2 = u.flatten()
v2 = v.flatten()
print(u2*v2)
# resultado: [ 0 10 33 48 65 84 105 128]
Agora uma comparação com o resultado obtido com np.outer():
resultado = np.outer(u, v)
print(resultado)
# resultado:
# [[ 0 0 0 0 0 0 0 0]
# [ 9 10 11 12 13 14 15 16]
# [ 27 30 33 36 39 42 45 48]
# [ 36 40 44 48 52 56 60 64]
# [ 45 50 55 60 65 70 75 80]
# [ 54 60 66 72 78 84 90 96]
# [ 63 70 77 84 91 98 105 112]
# [ 72 80 88 96 104 112 120 128]]
Por que a diferença? A razão é simples. O achatamento usado pela função np.outer() é equivalente a isso aqui:
import numpy as np
u = np.array([[[0, 1], [3, 4]], [[5, 6], [7, 8]]])
v = np.array([[[9, 10], [11, 12]], [[13, 14], [15, 16]]])
u2 = u.flatten() # matrix (8,)
v2 = v.flatten() # matrix (8,)
u3 = np.reshape(u2, (u2.shape[0], 1)) # matriz (8, 1)
v3 = np.reshape(v2, (1, v2.shape[0])) # matriz (1, 8)
print(u3*v3)
# resultado:
# [[ 0 0 0 0 0 0 0 0]
# [ 9 10 11 12 13 14 15 16]
# [ 27 30 33 36 39 42 45 48]
# [ 36 40 44 48 52 56 60 64]
# [ 45 50 55 60 65 70 75 80]
# [ 54 60 66 72 78 84 90 96]
# [ 63 70 77 84 91 98 105 112]
# [ 72 80 88 96 104 112 120 128]]
Ou seja, as matrizes u e v são achatadas, mas mantêm um eixo com dimensão 1. No exemplo acima, as dimensões dos novos vetores achatados u3 e v3 são 8 x 1 e 1 x 8, respectivamente. Quando multiplicamos essas duas matrizes, o NumPy usa a transmissão para produzir a matriz resultado com dimensão 8 x 8.