Как выразить это сложное выражение, используя кусочки

14

Я хочу реализовать следующее выражение в Python:

Иксязнак равноΣJзнак равно1я-1Кя-J,Jaя-JaJ,
где Икс и Y - массивы numpy размером N , а К - массив numpy размером N×N . Размер N может составлять примерно до 10000, и функция является частью внутреннего цикла, который будет оцениваться много раз, поэтому важна скорость.

В идеале я бы хотел вообще избежать цикла for, хотя, думаю, это еще не конец света, если он есть. Проблема в том, что я не могу понять, как это сделать, не имея пары вложенных циклов, и это, вероятно, сделает его довольно медленным.

Кто-нибудь может увидеть, как выразить вышеприведенное уравнение, используя numpy, таким образом, чтобы он был эффективным и желательно также читабельным? В целом, как лучше всего подойти к такого рода вещам?

Натаниель
источник
У меня был похожий вопрос пару дней назад. Я спросил об этом в stackoverflow. Проверьте этот пост . Я использую scipy.weave вместо cython. Кто-нибудь знает, имеет ли это какое-то (значительное) влияние на производительность?
СЕБ

Ответы:

17

Вот решение Numba. На моей машине версия Numba> в 1000 раз быстрее, чем версия Python без декоратора (для матрицы 200x200, k и вектора длиной 200 a). Вы также можете использовать декоратор @autojit, который добавляет около 10 микросекунд на вызов, чтобы один и тот же код работал с несколькими типами.

from numba import jit, autojit

@jit('f8[:](f8[:,:],f8[:])')
#@autojit
def looped_ver(k, a):
    x = np.empty_like(a)
    for i in range(x.size):
        sm = 0.0
        for j in range(0, i+1):
            sm += k[i-j,j] * a[i-j] * a[j]
        x[i] = sm
    return x

Раскрытие: я один из разработчиков Numba.

Трэвис Олифант
источник
Спасибо, это выглядит довольно просто. Я даже не знал о Нумбе! Cython, PyPy, Numba ... это запутанный мир.
Натаниэль
3
Трэвис, очень круто, не возражаешь ли ты добавить в конец ответа, что ты один из разработчиков Numba?
Арон Ахмадиа
1
При версия Cython также намного быстрее по сравнению с зацикленным Python (для меня ~ 700x). Мне было бы любопытно, как эта производительность изменяется с более крупными матрицами, и испытывают ли они те же самые (память?) Узкие места. Nзнак равно200
Нат Уилсон
@NatWilson - если вы зададите этот вопрос на scicomp, я с удовольствием попробую решить его для вас :)
Арон Ахмадиа,
4

Вот начало. Во-первых, мои извинения за любые ошибки.

Я экспериментировал с парой разных подходов. Меня немного смутили ограничения суммирования - должен ли верхний предел быть , а не i - 1 ?яя-1

Изменить: нет, верхний предел был правильным, как указано в вопросе. Я оставил все как есть, потому что другой ответ теперь использует тот же код, но исправить это просто.

Сначала зацикленная версия:

def looped_ver(k, a):
    x = np.empty_like(a)
    for i in range(x.size):
        sm = 0
        for j in range(0, i+1):
            sm += k[i-j,j] * a[i-j] * a[j]
        x[i] = sm
    return x

Я сделал это один цикл с кусочками:

def vectorized_ver(k, a):
    ktr = zeros_like(k)
    ar = zeros_like(k)
    sz = len(a)
    for i in range(sz):
        ktr[i,:i+1] = k[::-1].diagonal(-sz+i+1)
        a_ = a[:i+1]
        ar[i,:i+1] = a_[::-1] * a_
    return np.sum(ktr * ar, 1)

Цифровая версия с одним явным циклом примерно в 25 раз быстрее на моем компьютере, когда .Nзнак равно5000

Затем я написал версию (более читабельного) зацикленного кода на Cython.

import numpy as np
import cython
cimport numpy as np

@cython.boundscheck(False)
@cython.wraparound(False)
def cyth_ver(double [:, ::1] k not None,
              double [:] a not None):
    cdef double[:] x = np.empty_like(a)
    cdef double sm
    cdef int i, j

    for i in range(len(a)):
        sm = 0.0
        for j in range(i+1):
            sm = sm + k[i-j,j] * a[i-j] * a[j]
        x[i] = sm
    return x

На моем ноутбуке этот примерно в 200 раз быстрее, чем зацикленная версия (и в 8 раз быстрее, чем одноконтурная векторизованная версия). Я уверен, что другие могут сделать лучше.

Я играл с версией Julia, и она казалась (если я правильно рассчитал время) сопоставимой с кодом Cython.

Нат Уилсон
источник
Большое спасибо. Это убедило меня, что мне нужно приложить усилия для изучения Cython :) Я должен был упомянуть, чтоИкс0я-1
Ах я вижу. Я получил это из первоначального суммирования, но не был уверен, что это было намерение.
Нат Уилсон
1

То, что вы хотите, кажется, свертка; Я думаю, что самым быстрым способом достижения этой цели была бы numpy.convolveфункция.

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

import numpy as np
a = [1, 2, 3, 4, 5]
k = [2, 4, 6, 8, 10]

result = np.convolve(a, k*a[::-1])
Томас Барухель
источник