Трансляция Numpy/Scipy, вычисляющая скалярное произведение для определенных элементов

У меня есть разреженная матрица типа A

и кадр данных (df) со строками, которые следует использовать для вычисления скалярного произведения.

Row1 Row2  Value
2    147   scalar product of vectors at Row1 and Raw2 in matrix A

Могу ли я сделать это в режиме вещания без зацикливания и т. д.?

В моем случае A размером 1 м * 100 КБ и фреймом данных 10 МБ.


person sh1ng    schedule 15.05.2016    source источник
comment
Какой из классов разреженных матриц у вас есть?   -  person Vadim Shkaberda    schedule 15.05.2016
comment
в моем случае это не имеет значения, я могу легко преобразовать его в соответствующий тип.   -  person sh1ng    schedule 15.05.2016
comment
Продемонстрируйте с маленьким df и матрицей (может быть плотной).   -  person hpaulj    schedule 15.05.2016


Ответы (3)


Начните с небольшой «разреженной» матрицы (csr лучше всего подходит для математики):

In [167]: A=sparse.csr_matrix([[1, 2, 3],  # Vadim's example
               [2, 1, 4],
               [0, 2, 2],
               [3, 0, 3]])

In [168]: AA=A.A    # dense equivalent  

In [169]: idx=np.array([[1,1,0,3],[3,0,0,2]]).T  # indexes

Я буду придерживаться версии numpy (Pandas построен поверх numpy)

Мы могли бы взять все точечные произведения строк и выбрать подмножество, определяемое idx:

In [170]: (AA.dot(AA.T))[idx[:,0], idx[:,1]]
Out[170]: array([18, 16, 14,  6], dtype=int32)

Произведение разреженной матрицы (A.dot(A.T) также работает:

In [171]: (A*A.T)[idx[:,0], idx[:,1]]
Out[171]: matrix([[18, 16, 14,  6]], dtype=int32)

Или мы можем сначала выбрать строки, а затем взять сумму произведений. Мы не хотим использовать здесь dot, так как мы не берем все комбинации.

In [172]: (AA[idx[:,0]]*AA[idx[:,1]]).sum(axis=1)
Out[172]: array([18, 16, 14,  6], dtype=int32)

Версия einsum этого калькулятора:

In [180]: np.einsum('ij,ij->i',AA[idx[:,0]],AA[idx[:,1]])
Out[180]: array([18, 16, 14,  6], dtype=int32)

sparse может сделать то же самое (* — матричное произведение, .multiply — поэлементное).

In [173]: (A[idx[:,0]].multiply(A[idx[:,1]])).sum(axis=1)
Out[173]: 
matrix([[18],
        [16],
        [14],
        [ 6]], dtype=int32)

С этим маленьким корпусом плотные версии работают быстрее. Индексация разреженных строк выполняется медленно.

In [181]: timeit np.einsum('ij,ij->i', AA[idx[:,0]], AA[idx[:,1]])
100000 loops, best of 3: 18.1 µs per loop

In [182]: timeit (A[idx[:,0]].multiply(A[idx[:,1]])).sum(axis=1)
1000 loops, best of 3: 1.32 ms per loop

In [184]: timeit (AA.dot(AA.T))[idx[:,0], idx[:,1]]
100000 loops, best of 3: 9.62 µs per loop

In [185]: timeit (A*A.T)[idx[:,0], idx[:,1]]
1000 loops, best of 3: 689 µs per loop

Чуть не забыл - итеративные версии:

In [191]: timeit [AA[i].dot(AA[j]) for i,j in idx]
10000 loops, best of 3: 38.4 µs per loop

In [192]: timeit [A[i].multiply(A[j]).sum() for i,j in idx]
100 loops, best of 3: 2.58 ms per loop

Индексация строк матрицы формата lil выполняется быстрее

In [207]: Al=A.tolil()

In [208]: timeit A[idx[:,0]]
1000 loops, best of 3: 476 µs per loop

In [209]: timeit Al[idx[:,0]]
1000 loops, best of 3: 234 µs per loop

Но к тому времени, когда он преобразуется обратно в csr для умножения, это может уже не сэкономить время.

===============

В других недавних вопросах SO я обсуждал более быстрые способы индексации разреженных строк или столбцов. Но в них конечной целью было суммирование по выбранному набору строк или столбцов. Для этого было на самом деле быстрее всего использовать матричное произведение - с матрицей из 1 и 0. Применение этой идеи здесь немного сложнее.

Глядя на функцию индексации csr.__getitem__, я обнаружил, что она на самом деле выполняет индексацию A[idx,:] с матричным произведением. Он создает матрицу extractor с такой функцией:

def extractor(indices,N):
    """Return a sparse matrix P so that P*self implements
    slicing of the form self[[1,2,3],:]
    """
    indptr = np.arange(len(indices)+1, dtype=int)
    data = np.ones(len(indices), dtype=int)
    shape = (len(indices),N)
    return sparse.csr_matrix((data,indices,indptr), shape=shape)

In [328]: %%timeit
   .....: A1=extractor(idx[:,0],4)*A
   .....: A2=extractor(idx[:,1],4)*A
   .....: (A1.multiply(A2)).sum(axis=1)
   .....: 
1000 loops, best of 3: 1.14 ms per loop

На этот раз время немного лучше, чем с A[idx[:,0],:] (In[182] выше) — предположительно потому, что оно немного упрощает действие. Он должен масштабироваться таким же образом.

Это работает, потому что idx0 — это логическая матрица, полученная из [1,1,0,3].

In [330]: extractor(idx[:,0],4).A
Out[330]: 
array([[0, 1, 0, 0],
       [0, 1, 0, 0],
       [1, 0, 0, 0],
       [0, 0, 0, 1]])

In [296]: A[idx[:,0],:].A
Out[296]: 
array([[2, 1, 4],
       [2, 1, 4],
       [1, 2, 3],
       [3, 0, 3]], dtype=int32)

In [331]: (extractor(idx[:,0],4)*A).A
Out[331]: 
array([[2, 1, 4],
       [2, 1, 4],
       [1, 2, 3],
       [3, 0, 3]], dtype=int32)

================

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

(A[idx[:,0]].multiply(A[idx[:,1]])).sum(axis=1)

Если это по-прежнему вызывает ошибки памяти, выполните итерацию, возможно, по группам массива idx (или кадра данных).

person hpaulj    schedule 16.05.2016
comment
Часть ваших примеров не имеет значения, я не могу рассчитать точечный продукт (A.dot (AT)), потому что в моем случае это приведет к нехватке памяти. Наконец, я решил это с помощью A[df.Row1].multiply(A[df.Row2]).sum(axis = 1). Также это можно сделать с суммой Эйнштейна, но я этого не пробовал. - person sh1ng; 16.05.2016

Если я правильно понимаю ваш вопрос, вы можете использовать функцию dot в Pandas для вычисления скалярного произведения между двумя сериями:

A['Row1'].dot(A['Row2'])

Документация: http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.dot.html

person Anthony E    schedule 15.05.2016
comment
Я не уверен, что матрица A представляет собой разреженную матрицу, содержащую координаты векторов. И dataframe(df) содержит позиции в этой матрице. - person sh1ng; 15.05.2016
comment
Это делает вещи еще более запутанными. Я не понимаю, как разреженная матрица может содержать «координаты векторов». Разреженная матрица — это матрица (2d) чисел. - person hpaulj; 15.05.2016

Я думаю, .assign() и .apply() (для панд> 0.16.0) подходят:

import numpy as np
from pandas import DataFrame
from scipy.sparse import bsr_matrix

df = DataFrame(np.random.randint(4, size=(4, 2)), columns=['Row1', 'Row2'])
A = bsr_matrix([[1, 2, 3],
               [2, 1, 4],
               [0, 2, 2],
               [3, 0, 3]])

A = A.tocsr() # Skip this if your matrix is csc_, csr_, dok_ or lil_matrix
df.assign(Value=df.apply(lambda row: A[row[0]].dot(A[row[1]].transpose())[0, 0], axis=1))

Out[15]: 
   Row1  Row2  Value
0     1     3     18
1     1     0     16
2     0     0     14
3     3     2      6
person Vadim Shkaberda    schedule 15.05.2016
comment
Разве df.apply не форма итерации? С медленной индексацией разреженной матрицы для каждой строки df? - person hpaulj; 15.05.2016
comment
Я не могу сказать, как именно работает df.apply, но это определенно быстрее, чем... например. df.iterrows: применяется к df с 10 ^ 4 строками df.apply(lambda row:... дает 3 loops, best of 3: 820 ms per loop, тогда как тот же результат с df.iterrows - 3 loops, best of 3: 921 ms per loop. Преобразование df в np.matrix и np.apply_along_axis еще лучше: 3 loops, best of 3: 709 ms per loop. И я не понимаю, как можно избежать медленной индексации разреженных матриц. - person Vadim Shkaberda; 15.05.2016
comment
Я знаком с итерацией apply_along_axis. Похоже, у панд немного больше накладных расходов. - person hpaulj; 15.05.2016