Multiplicando em uma matriz numpy

87

Estou tentando multiplicar cada um dos termos em uma matriz 2D pelos termos correspondentes em uma matriz 1D. Isso é muito fácil se eu quiser multiplicar todas as colunas pelo array 1D, conforme mostrado na função numpy.multiply . Mas eu quero fazer o oposto, multiplicar cada termo na linha. Em outras palavras, quero multiplicar:

[1,2,3]   [0]
[4,5,6] * [1]
[7,8,9]   [2]

e pegue

[0,0,0]
[4,5,6]
[14,16,18]

mas ao invés eu recebo

[0,2,6]
[0,5,12]
[0,8,18]

Alguém sabe se existe uma maneira elegante de fazer isso com o numpy? Muito obrigado Alex

Alex S
fonte
3
Ah, descobri assim que enviei a pergunta. Primeiro transponha a matriz quadrada, multiplique e depois transponha a resposta.
Alex S
Melhor transpor a linha para uma matriz de coluna, então você não precisa transpor novamente a resposta. Se A * Bvocê tiver que fazer A * B[...,None]qual transpõe B, adicione um novo eixo ( None).
askewchan
Obrigado, é verdade. O problema é quando você tem um array 1D chamando .transpose () ou .T nele não o transforma em um array de coluna, ele o deixa como uma linha, então, pelo que eu sei, você deve defini-lo como uma coluna logo de cara. Tipo x = [[1],[2],[3]]ou algo assim.
Alex S

Respostas:

114

Multiplicação normal como você mostrou:

>>> import numpy as np
>>> m = np.array([[1,2,3],[4,5,6],[7,8,9]])
>>> c = np.array([0,1,2])
>>> m * c
array([[ 0,  2,  6],
       [ 0,  5, 12],
       [ 0,  8, 18]])

Se você adicionar um eixo, ele se multiplicará da maneira que você deseja:

>>> m * c[:, np.newaxis]
array([[ 0,  0,  0],
       [ 4,  5,  6],
       [14, 16, 18]])

Você também pode transpor duas vezes:

>>> (m.T * c).T
array([[ 0,  0,  0],
       [ 4,  5,  6],
       [14, 16, 18]])
Jterrace
fonte
Com o novo método de eixo, é possível multiplicar dois arrays 1D e gerar um array 2D. Ex [a,b] op [c,d] -> [[a*c, b*c], [a*d, b*d]].
kon psych
47

Eu comparei as diferentes opções de velocidade e descobri que - para minha surpresa - todas as opções (exceto diag) são igualmente rápidas. Eu pessoalmente uso

A * b[:, None]

(ou (A.T * b).T) porque é curto.

insira a descrição da imagem aqui


Código para reproduzir o gráfico:

import numpy
import perfplot


def newaxis(data):
    A, b = data
    return A * b[:, numpy.newaxis]


def none(data):
    A, b = data
    return A * b[:, None]


def double_transpose(data):
    A, b = data
    return (A.T * b).T


def double_transpose_contiguous(data):
    A, b = data
    return numpy.ascontiguousarray((A.T * b).T)


def diag_dot(data):
    A, b = data
    return numpy.dot(numpy.diag(b), A)


def einsum(data):
    A, b = data
    return numpy.einsum("ij,i->ij", A, b)


perfplot.save(
    "p.png",
    setup=lambda n: (numpy.random.rand(n, n), numpy.random.rand(n)),
    kernels=[
        newaxis,
        none,
        double_transpose,
        double_transpose_contiguous,
        diag_dot,
        einsum,
    ],
    n_range=[2 ** k for k in range(14)],
    logx=True,
    logy=True,
    xlabel="len(A), len(b)",
)
Nico Schlömer
fonte
2
Toque agradável fornecendo o código para o enredo. Obrigado.
rocksNwaves
17

Você também pode usar a multiplicação de matrizes (também conhecido como produto escalar):

a = [[1,2,3],[4,5,6],[7,8,9]]
b = [0,1,2]
c = numpy.diag(b)

numpy.dot(c,a)

O que é mais elegante é provavelmente uma questão de gosto.

James K
fonte
2
bom, +1, não pensei nisso
jterrace
10
doté realmente um exagero aqui. Você está apenas fazendo multiplicação desnecessária por 0 e adições a 0.
Bi Rico
2
isso também pode desencadear problemas de memória no caso de você querer multipy um vetor nx1 para uma matriz nxd onde d é maior que n.
Jonasson
Downvoting porque isso é lento e usa muita memória ao criar a diagmatriz densa .
Nico Schlömer
16

Mais um truque (a partir de v1.6)

A=np.arange(1,10).reshape(3,3)
b=np.arange(3)

np.einsum('ij,i->ij',A,b)

Eu sou proficiente com a transmissão entorpecida ( newaxis), mas ainda estou encontrando meu caminho em torno dessa nova einsumferramenta. Então, eu tentei um pouco para encontrar essa solução.

Timings (usando Ipython timeit):

einsum: 4.9 micro
transpose: 8.1 micro
newaxis: 8.35 micro
dot-diag: 10.5 micro

Aliás, a mudança de uma ipara j, np.einsum('ij,j->ij',A,b)produz a matriz que Alex não quer. E np.einsum('ji,j->ji',A,b)faz, com efeito, a dupla transposição.

hpaulj
fonte
1
Se você cronometrar isso no computador com matrizes grandes o suficiente para levar pelo menos alguns milissegundos e postar os resultados aqui junto com as informações relevantes do sistema, será muito apreciado.
Daniel
1
com uma matriz maior (100x100), os números relativos são praticamente os mesmos. einsumm(25 micro) é duas vezes mais rápido que os outros (o ponto-diag desacelera mais). Este é o np 1.7, compilado recentemente com 'libatlas3gf-sse2' e 'libatlas-base-dev' (Ubuntu 10.4, processador único). timeitdá o melhor de 10.000 loops.
hpaulj
1
Essa é uma ótima resposta e acho que deveria ter sido aceita. No entanto, o código escrito acima fornece, de fato, a matriz que Alex estava tentando evitar (na minha máquina). Aquele que hpaulj disse que está errado é, na verdade, o certo.
Yair Daon
Os tempos são enganosos aqui. dot-diag realmente é muito pior do que as outras três opções, e o einsum também não é mais rápido do que as outras.
Nico Schlömer
@ NicoSchlömer, minha resposta tem quase 5 anos e muitas numpyversões anteriores.
hpaulj
1

Para aquelas almas perdidas no google, usar numpy.expand_dimsentão numpy.repeatfuncionará, e também funcionará em casos de dimensões superiores (isto é, multiplicar uma forma (10, 12, 3) por a (10, 12)).

>>> import numpy
>>> a = numpy.array([[1,2,3],[4,5,6],[7,8,9]])
>>> b = numpy.array([0,1,2])
>>> b0 = numpy.expand_dims(b, axis = 0)
>>> b0 = numpy.repeat(b0, a.shape[0], axis = 0)
>>> b1 = numpy.expand_dims(b, axis = 1)
>>> b1 = numpy.repeat(b1, a.shape[1], axis = 1)
>>> a*b0
array([[ 0,  2,  6],
   [ 0,  5, 12],
   [ 0,  8, 18]])
>>> a*b1
array([[ 0,  0,  0],
   [ 4,  5,  6],
   [14, 16, 18]])
Christopher Pratt
fonte
-4

Por que você apenas não faz

>>> m = np.array([[1,2,3],[4,5,6],[7,8,9]])
>>> c = np.array([0,1,2])
>>> (m.T * c).T

??

Panos
fonte
6
Essa abordagem exata já é mostrada na resposta aceita, não vejo como isso acrescenta algo.
Baum mit Augen