2013-11-20 5 views
4

V (п, р) NumPy массив, как правило, размеры п ~ 10, р ~ 20000NumPy векторизации двойного Python для петли

Кода я теперь выгляжу

A = np.zeros(p) 
for i in xrange(n): 
    for j in xrange(i+1): 
     A += F[i,j] * V[i,:] * V[j,:] 

Как бы я переписывать это, чтобы избежать двойного питона для цикла?

+1

Какая форма 'F'? Это '(n, n)' или '(n, n, p)'? – wflynny

+0

Считаете ли вы, что вы пишете эти петли в C (поскольку они просты), и используя scipy.weave.blitz? – usethedeathstar

+0

Bill, F is (n, n) – user1984528

ответ

9

Хотя Исаакиевская ответ представляется перспективным, так как она удаляет те две вложенные для петель, вы вынуждены создать промежуточный массив M, который n раз превышает размер исходного V массива. Python для петель не дешев, но доступ к памяти не является бесплатным либо:

n = 10 
p = 20000 
V = np.random.rand(n, p) 
F = np.random.rand(n, n) 

def op_code(V, F): 
    n, p = V.shape 
    A = np.zeros(p) 
    for i in xrange(n): 
     for j in xrange(i+1): 
      A += F[i,j] * V[i,:] * V[j,:] 
    return A 

def isaac_code(V, F): 
    n, p = V.shape 
    F = F.copy() 
    F[np.triu_indices(n, 1)] = 0 
    M = (V.reshape(n, 1, p) * V.reshape(1, n, p)) * F.reshape(n, n, 1) 
    return M.sum((0, 1)) 

Если теперь принять как для испытательной поездки:

In [20]: np.allclose(isaac_code(V, F), op_code(V, F)) 
Out[20]: True 

In [21]: %timeit op_code(V, F) 
100 loops, best of 3: 3.18 ms per loop 

In [22]: %timeit isaac_code(V, F) 
10 loops, best of 3: 24.3 ms per loop 

Так Извлечение для петель обходятся вам ое замедление. Не очень хорошая вещь ... На данный момент вы можете даже подумать, нужна ли функция, занимающая около 3 мс для оценки, дальнейшая оптимизация. В случае, если вы делаете, есть небольшое улучшение, которое можно было с помощью np.einsum:

def einsum_code(V, F): 
    n, p = V.shape 
    F = F.copy() 
    F[np.triu_indices(n, 1)] = 0 
    return np.einsum('ij,ik,jk->k', F, V, V) 

А теперь:

In [23]: np.allclose(einsum_code(V, F), op_code(V, F)) 
Out[23]: True 

In [24]: %timeit einsum_code(V, F) 
100 loops, best of 3: 2.53 ms per loop 

Так что примерно на 20% ускорить, который вводит код, который может очень хорошо не быть таким же читаемым, как ваши петли. Я бы сказал, не стоит ...

+0

Не знал о 'einsum'; очень аккуратный! – Isaac

+2

Да. Одна общая эвристика для векторизации кода, когда немного хлопотно делать все это, - это векторизация над длинными осями и цикл над короткими, что дает вам почти все преимущества для части головной боли. Но вот, вот что сделал OP! – DSM

+0

Спасибо, я закончил работу с методом einsum после тестирования каждого с диапазоном входов (n = 5-30 & p = 100-20000), я не знал, что он был там. – user1984528

7

Сложная часть об этом заключается в том, что вы хотите только взять сумму элементов с помощью j <= i. Если бы не это, то вы можете сделать следующее:

M = (V.reshape(n, 1, p) * V.reshape(1, n, p)) * F.reshape(n, n, 1) 
A = M.sum(0).sum(0) 

Если F симметрично (если F[i,j] == F[j,i]), то вы можете использовать симметрию M над следующим:

D = M[range(n), range(n)].sum(0) 
A = (M.sum(0).sum(0) - D)/2.0 + D 

Это сказал, что это действительно не отличный кандидат для векторизации, так как у вас есть n << p, и поэтому ваши for -loops не окажут большого влияния на скорость этого вычисления.

Редактировать: Как сказано ниже Билл, вы можете просто убедиться, что элементы F, что вы не хотите использовать устанавливаются в ноле первый, а затем M.sum(0).sum(0) результата будет то, что вы хотите.

+3

Как насчет 'F [np.triu_indices (n, 1)] = 0'? Таким образом, верхняя половина «F» (смещение на 1) равна нулю и не будет способствовать сумме. – wflynny

+0

Кроме того, 'a.sum (0) .sum (0)' можно записать как 'np.sum (a, (0,1))' или 'a.sum ((0,1))' который isn ' t все быстрее, но я считаю его более читаемым. – askewchan

+1

Ваш код на самом деле примерно в 8 раз медленнее, чем OP для его использования ... – Jaime

1

Выражение может быть записано в виде

formula

и, таким образом, вы можете подвести его, как это с помощью np.newaxis -construct:

na = np.newaxis 
X = (np.tri(n)*F)[:,:,na]*V[:,na,:]*V[na,:,:] 
X.sum(axis=1).sum(axis=0) 

Здесь строится 3D-массив X[i,j,p], а затем суммируются две первые оси, что приводит к 1D-массиву A[p]. Дополнительно F умножилось на треугольную матрицу, чтобы ограничить суммирование по задаче.

Смежные вопросы