Векторизованный способ вычисления двух матриц строчного скалярного произведения с помощью Scipy

Я хочу как можно быстрее вычислить построчное скалярное произведение двух матриц одинаковой размерности. Вот как я это делаю:

import numpy as np
a = np.array([[1,2,3], [3,4,5]])
b = np.array([[1,2,3], [1,2,3]])
result = np.array([])
for row1, row2 in a, b:
    result = np.append(result, np.dot(row1, row2))
print result

и, конечно же, результат:

[ 26.  14.]

person Cupitor    schedule 25.03.2013    source источник


Ответы (5)


Ознакомьтесь с numpy.einsum для другого метода:

In [52]: a
Out[52]: 
array([[1, 2, 3],
       [3, 4, 5]])

In [53]: b
Out[53]: 
array([[1, 2, 3],
       [1, 2, 3]])

In [54]: einsum('ij,ij->i', a, b)
Out[54]: array([14, 26])

Похоже, einsum немного быстрее, чем inner1d:

In [94]: %timeit inner1d(a,b)
1000000 loops, best of 3: 1.8 us per loop

In [95]: %timeit einsum('ij,ij->i', a, b)
1000000 loops, best of 3: 1.6 us per loop

In [96]: a = random.randn(10, 100)

In [97]: b = random.randn(10, 100)

In [98]: %timeit inner1d(a,b)
100000 loops, best of 3: 2.89 us per loop

In [99]: %timeit einsum('ij,ij->i', a, b)
100000 loops, best of 3: 2.03 us per loop
person Warren Weckesser    schedule 25.03.2013
comment
Мне очень нравится эйнсум, и это правда, что с его помощью можно избежать петель. Однако, если ваша основная забота - производительность, а не стиль кода, вам все равно может быть лучше с точкой и циклом (в зависимости от ваших конкретных данных и системной среды). В отличие от einsum, точка может использовать BLAS и часто автоматически выполняет многопоточность. mail.scipy.org/pipermail/numpy-discussion/2012- Октябрь / - person PiQuer; 15.12.2014
comment
@Warren, хотел бы получить подробное объяснение таинственной записи индекса. - person CKM; 26.08.2019
comment
@PiQuer, нативный Python for-loop рекомендуется? Даже с прозрачной потоковой передачей (чего я здесь не понимаю) цикл все еще в Python. Я честно ожидаю, что внутренняя точка будет завершена до того, как верхний цикл в Python будет готов выполнить следующую точку. Вы можете уточнить? - person Narfanar; 21.07.2020
comment
Я видел, как встроенные циклы for выполняются на порядки медленнее, чем операции numpy, мне нужно увидеть некоторые доказательства и объяснение, почему, прежде чем я поверю, что использование native-for является эффективным решением. einsum кажется отличным инструментом, я рад, что наконец-то мне пришлось узнать о нем. - person Nathan Chappell; 04.08.2020

Простой способ сделать это:

import numpy as np
a=np.array([[1,2,3],[3,4,5]])
b=np.array([[1,2,3],[1,2,3]])
np.sum(a*b, axis=1)

который позволяет избежать цикла Python и работает быстрее в таких случаях, как:

def npsumdot(x, y):
    return np.sum(x*y, axis=1)

def loopdot(x, y):
    result = np.empty((x.shape[0]))
    for i in range(x.shape[0]):
        result[i] = np.dot(x[i], y[i])
    return result

timeit npsumdot(np.random.rand(500000,50),np.random.rand(500000,50))
# 1 loops, best of 3: 861 ms per loop
timeit loopdot(np.random.rand(500000,50),np.random.rand(500000,50))
# 1 loops, best of 3: 1.58 s per loop
person sim    schedule 24.06.2015
comment
Четкий и читаемый - person derchambers; 26.02.2021

Поигрался с этим и нашел inner1d самый быстрый. Однако эта функция является внутренней, поэтому более надежным подходом является использование

numpy.einsum("ij,ij->i", a, b)

Еще лучше выровнять свою память так, чтобы суммирование происходило в первом измерении, например,

a = numpy.random.rand(3, n)
b = numpy.random.rand(3, n)

numpy.einsum("ij,ij->j", a, b)

Для 10 ** 3 <= n <= 10 ** 6 это самый быстрый метод и почти вдвое быстрее, чем его нетранспонированный эквивалент. Максимум происходит, когда кеш уровня 2 исчерпан, примерно на 2 * 10 ** 4.

Также обратите внимание, что транспонированная sum обработка выполняется намного быстрее, чем ее нетранспонированный эквивалент.

введите описание изображения здесь

введите описание изображения здесь

Сюжет был создан с помощью perfplot (мой небольшой проект)

import numpy
from numpy.core.umath_tests import inner1d
import perfplot


def setup(n):
    a = numpy.random.rand(n, 3)
    b = numpy.random.rand(n, 3)
    aT = numpy.ascontiguousarray(a.T)
    bT = numpy.ascontiguousarray(b.T)
    return (a, b), (aT, bT)


b = perfplot.bench(
    setup=setup,
    n_range=[2 ** k for k in range(1, 25)],
    kernels=[
        lambda data: numpy.sum(data[0][0] * data[0][1], axis=1),
        lambda data: numpy.einsum("ij, ij->i", data[0][0], data[0][1]),
        lambda data: numpy.sum(data[1][0] * data[1][1], axis=0),
        lambda data: numpy.einsum("ij, ij->j", data[1][0], data[1][1]),
        lambda data: inner1d(data[0][0], data[0][1]),
    ],
    labels=["sum", "einsum", "sum.T", "einsum.T", "inner1d"],
    xlabel="len(a), len(b)",
)

b.save("out1.png")
b.save("out2.png", relative_to=3)
person Nico Schlömer    schedule 23.09.2016
comment
К сожалению, inner1d устарел (или, скорее, удален, а не является частью API) - см. github.com/numpy / numpy / issues / 10815 - person Mr_and_Mrs_D; 02.11.2018

Вам лучше будет избегать append, но я не могу придумать способ избежать цикла Python. Возможно, пользовательский Ufunc? Я не думаю, что numpy.vectorize вам здесь поможет.

import numpy as np
a=np.array([[1,2,3],[3,4,5]])
b=np.array([[1,2,3],[1,2,3]])
result=np.empty((2,))
for i in range(2):
    result[i] = np.dot(a[i],b[i]))
print result

ИЗМЕНИТЬ

На основании этого ответа, похоже, inner1d может работать, если векторы в вашем реальном- мировая проблема 1D.

from numpy.core.umath_tests import inner1d
inner1d(a,b)  # array([14, 26])
person Paul    schedule 25.03.2013

Я наткнулся на этот ответ и повторно проверил результаты с Numpy 1.14.3, работающим на Python 3.5. По большей части приведенные выше ответы верны для моей системы, хотя я обнаружил, что для очень больших матриц (см. Пример ниже) все методы, кроме одного, настолько близки друг к другу, что разница в производительности бессмысленна.

Для матриц меньшего размера я обнаружил, что einsum был самым быстрым со значительным отрывом, в некоторых случаях до двух раз.

Мой пример большой матрицы:

import numpy as np
from numpy.core.umath_tests import inner1d
a = np.random.randn(100, 1000000)  # 800 MB each
b = np.random.randn(100, 1000000)  # pretty big.

def loop_dot(a, b):
    result = np.empty((a.shape[1],))
    for i, (row1, row2) in enumerate(zip(a, b)):
        result[i] = np.dot(row1, row2)
%timeit inner1d(a, b)
# 128 ms ± 523 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit np.einsum('ij,ij->i', a, b)
# 121 ms ± 402 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit np.sum(a*b, axis=1)
# 411 ms ± 1.99 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit loop_dot(a, b)  # note the function call took negligible time
# 123 ms ± 342 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Таким образом, einsum по-прежнему является самым быстрым на очень больших матрицах, но на небольшую величину. Однако это кажется статистически значимой (крошечной) суммой!

person Engineero    schedule 24.09.2018