Nhân với nhau trong một mảng phức tạp


88

Tôi đang cố nhân từng số hạng trong mảng 2D với các số hạng tương ứng trong mảng 1D. Điều này rất dễ dàng nếu tôi muốn nhân mọi cột với mảng 1D, như được hiển thị trong hàm numpy.multiply . Nhưng tôi muốn làm ngược lại, nhân từng số hạng trong hàng. Nói cách khác, tôi muốn nhân lên:

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

và lấy

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

nhưng thay vào đó tôi nhận được

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

Có ai biết nếu có một cách thanh lịch để làm điều đó với numpy? Cảm ơn rất nhiều, Alex


3
Ah, tôi đã tìm ra nó ngay khi tôi gửi câu hỏi. Đầu tiên chuyển ma trận vuông, nhân, sau đó chuyển câu trả lời.
Alex S

Tốt hơn để chuyển hàng thành ma trận cột thì bạn không phải chuyển lại câu trả lời. Nếu A * Bbạn phải làm điều A * B[...,None]đó chuyển vị Bbằng cách thêm một trục mới ( None).
askewchan

Cảm ơn, đó là sự thật. Vấn đề là khi bạn có một mảng 1D gọi .transpose () hoặc .T trên nó không biến nó thành một mảng cột, nó để nó dưới dạng một hàng, theo như tôi biết thì bạn phải xác định nó là một cột ngay lập tức. Thích x = [[1],[2],[3]]hay gì đó.
Alex S

Câu trả lời:


115

Phép nhân bình thường như bạn đã trình bày:

>>> 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]])

Nếu bạn thêm một trục, nó sẽ nhân theo cách bạn muốn:

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

Bạn cũng có thể chuyển đổi hai lần:

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

Với phương pháp trục mới, có thể nhân hai mảng 1D và tạo ra một mảng 2D. Vd [a,b] op [c,d] -> [[a*c, b*c], [a*d, b*d]].
kon psych

47

Tôi đã so sánh các tùy chọn khác nhau về tốc độ và nhận thấy rằng - khiến tôi ngạc nhiên - tất cả các tùy chọn (ngoại trừ diag) đều nhanh như nhau. Cá nhân tôi sử dụng

A * b[:, None]

(hoặc (A.T * b).T) vì nó ngắn.

nhập mô tả hình ảnh ở đây


Mã để tái tạo cốt truyện:

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)",
)

2
Cảm ứng tốt khi cung cấp mã cho cốt truyện. Cảm ơn.
stoneNwaves

17

Bạn cũng có thể sử dụng phép nhân ma trận (hay còn gọi là tích số chấm):

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

numpy.dot(c,a)

Cái nào tao nhã hơn có lẽ là vấn đề về hương vị.


2
đẹp, +1, không nghĩ về điều đó
jterrace

10
dotthực sự là quá mức cần thiết ở đây. Bạn chỉ cần thực hiện phép nhân không cần thiết bởi 0, bổ sung 0.
Bi Rico

2
điều này cũng có thể gây ra sự cố bộ nhớ trong trường hợp bạn muốn ghép một vectơ nx1 thành một ma trận nxd trong đó d lớn hơn n.
Jonasson

Từ chối vì điều này là chậm sử dụng nhiều bộ nhớ khi tạo diagma trận dày đặc .
Nico Schlömer

16

Tuy nhiên, một thủ thuật khác (kể từ v1.6)

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

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

Tôi thành thạo với phát sóng numpy ( newaxis), nhưng tôi vẫn đang tìm cách sử einsumdụng công cụ mới này . Vì vậy, tôi đã chơi xung quanh một chút để tìm ra giải pháp này.

Thời gian (sử dụng Ipython timeit):

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

Tình cờ, thay đổi a ithành j, np.einsum('ij,j->ij',A,b)tạo ra ma trận mà Alex không muốn. Và trên np.einsum('ji,j->ji',A,b)thực tế, chuyển vị kép.


1
Nếu bạn tính thời gian này trên máy tính với các mảng đủ lớn để mất ít nhất vài mili giây và đăng kết quả ở đây cùng với thông tin hệ thống liên quan của bạn thì điều đó sẽ được đánh giá cao.
Daniel

1
với một mảng lớn hơn (100x100), các con số tương đối giống nhau. einsumm(25 micro) nhanh gấp đôi so với những cái khác (dot-cross làm chậm hơn). Đây là np 1.7, mới được biên dịch bằng 'libatlas3gf-sse2' và 'libatlas-base-dev' (Ubuntu 10.4, bộ xử lý đơn). timeitcung cấp tốt nhất 10000 vòng lặp.
hpaulj

1
Đây là một câu trả lời tuyệt vời và tôi nghĩ rằng đó là câu trả lời đáng lẽ phải được chấp nhận. Tuy nhiên, trên thực tế, đoạn mã được viết ở trên đưa ra ma trận mà Alex đang cố gắng tránh (trên máy của tôi). Điều mà hpaulj nói là sai thực ra lại là điều đúng.
Yair Daon

Thời gian bị sai lệch ở đây. dot-Diag thực sự kém hơn nhiều so với ba tùy chọn còn lại và einsum cũng không nhanh hơn các tùy chọn khác.
Nico Schlömer

@ NicoSchlömer, câu trả lời của tôi đã cũ gần 5 năm và có nhiều numpyphiên bản trở lại.
hpaulj

1

Đối với những linh hồn đã mất trên google, sử dụng numpy.expand_dimsthen numpy.repeatsẽ hoạt động và cũng sẽ hoạt động trong các trường hợp chiều cao hơn (tức là nhân một hình dạng (10, 12, 3) với 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]])

-4

Tại sao bạn không làm

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

??


6
Cách tiếp cận chính xác đó đã được hiển thị trong câu trả lời được chấp nhận, tôi không thấy cách điều này bổ sung thêm bất cứ điều gì.
Baum mit Augen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.