Operações Avançadas com Einsum
Agora que estamos confortáveis com as operações básicas do einsum, vamos explorar algumas aplicações mais avançadas. Essas operações demonstram o verdadeiro poder e flexibilidade da função einsum.
Extrair os elementos da diagonal de uma matriz é uma operação comum em álgebra linear. Para uma matriz A, seus elementos diagonais formam um vetor d onde:
d_i = A_{ii}
Aqui está como extrair a diagonal usando einsum:
## Crie uma matriz quadrada aleatória
A = np.random.rand(4, 4)
print("Matrix A:")
print(A)
## Extract diagonal using einsum
diagonal = np.einsum('ii->i', A)
print("\nDiagonal elements using einsum:")
print(diagonal)
## Verify with NumPy's diagonal function
numpy_diagonal = np.diagonal(A)
print("\nDiagonal elements using np.diagonal():")
print(numpy_diagonal)
A notação 'ii->i' significa:
ii representa o índice repetido para os elementos da diagonal de A
i significa que extraímos esses elementos em um array 1D
Traço da Matriz
O traço de uma matriz é a soma de seus elementos da diagonal. Para uma matriz A, seu traço é:
\text{trace}(A) = \sum_i A_{ii}
Aqui está como calcular o traço usando einsum:
## Using the same matrix A from above
trace = np.einsum('ii->', A)
print("Trace of matrix A using einsum:", trace)
## Verify with NumPy's trace function
numpy_trace = np.trace(A)
print("Trace of matrix A using np.trace():", numpy_trace)
A notação 'ii->' significa:
ii representa o índice repetido para os elementos da diagonal
- O índice de saída vazio significa que somamos todos os elementos da diagonal para obter um escalar
Multiplicação de Matrizes em Lote (Batch)
einsum realmente se destaca ao realizar operações em arrays de dimensões superiores. Por exemplo, a multiplicação de matrizes em lote envolve a multiplicação de pares de matrizes de dois lotes.
Se tivermos um lote de matrizes A com forma (n, m, p) e um lote de matrizes B com forma (n, p, q), a multiplicação de matrizes em lote nos dá um resultado C com forma (n, m, q):
C_{ijk} = \sum_l A_{ijl} \times B_{ilk}
Aqui está como realizar a multiplicação de matrizes em lote usando einsum:
## Create batches of matrices
n, m, p, q = 5, 3, 4, 2 ## Batch size and matrix dimensions
A = np.random.rand(n, m, p) ## Batch of 5 matrices, each 3x4
B = np.random.rand(n, p, q) ## Batch of 5 matrices, each 4x2
print("Shape of batch A:", A.shape)
print("Shape of batch B:", B.shape)
## Batch matrix multiplication using einsum
C = np.einsum('nmp,npq->nmq', A, B)
print("\nShape of result batch C:", C.shape) ## Should be (5, 3, 2)
## Let's check the first matrix multiplication in the batch
print("\nFirst result matrix from batch using einsum:")
print(C[0])
## Verify with NumPy's matmul function
numpy_batch_matmul = np.matmul(A, B)
print("\nFirst result matrix from batch using np.matmul:")
print(numpy_batch_matmul[0])
A notação 'nmp,npq->nmq' significa:
nmp representa os índices do lote A (n para lote, m para linhas, p para colunas)
npq representa os índices do lote B (n para lote, p para linhas, q para colunas)
nmq representa os índices do lote de saída C (n para lote, m para linhas, q para colunas)
- O índice repetido
p é somado (multiplicação de matrizes)
Por que Usar Einsum?
Você pode se perguntar por que devemos usar einsum quando o NumPy já fornece funções especializadas para essas operações. Aqui estão algumas vantagens:
- Interface Unificada:
einsum fornece uma única função para muitas operações em arrays
- Flexibilidade: Pode expressar operações que, de outra forma, exigiriam várias etapas
- Legibilidade: Uma vez que você entende a notação, o código se torna mais conciso
- Desempenho: Em muitos casos, as operações
einsum são otimizadas e eficientes
Para operações complexas com tensores, einsum geralmente fornece a implementação mais clara e direta.