Поэлементный максимум разреженной матрицы Scipy и вектора с трансляцией

Мне нужен быстрый поэлементный максимум, который сравнивает каждую строку scipy разреженной матрицы размером n на m поэлементно с разреженной матрицей 1 на m. Это отлично работает в Numpy, используя np.maximum(mat, vec) через трансляцию Numpy.

Однако у Scipy's .maximum() нет трансляции. Моя матрица большая, поэтому я не могу преобразовать ее в массив numpy.

Мой текущий обходной путь - перебрать множество рядов мата с помощью mat[row,:].maximum(vec). Этот большой цикл портит эффективность моего кода (его приходится повторять много раз). Мое медленное решение находится во втором фрагменте кода ниже. Есть ли лучшее решение?

# Example
import numpy as np
from scipy import sparse

mat = sparse.csc_matrix(np.arange(12).reshape((4,3)))

vec = sparse.csc_matrix([-1, 5, 100])

# Numpy's np.maximum() gives the **desired result** using broadcasting (but it can't handle sparse matrices):
numpy_result = np.maximum( mat.toarray(), vec.toarray() )
print( numpy_result )
# [[  0   5 100]
#  [  3   5 100]
#  [  6   7 100]
#  [  9  10 100]]

# Scipy only compares the top row of mat to vec (no broadcasting!):
scipy_result = mat.maximum(vec)
print( scipy_result.toarray() )
# [[  0   5 100]
#  [  3   4   5]
#  [  6   7   8]
#  [  9  10  11]]

#Reversing the order of mat and vec in the call to vec.maximum(mat) results in a single row output, and also frequently seg faults (!):

Более крупный пример и текущее решение для тестирования скорости

import numpy as np
from scipy import sparse
import timeit

mat = sparse.csc_matrix(  sparse.random(20000, 4000, density=.01, data_rvs=lambda s: np.random.randint(0, 5000, size=s))  )

vec = sparse.csc_matrix(  sparse.random(1, 4000, density=.01, data_rvs=lambda s: np.random.randint(0, 5000, size=s))  )

def sparse_elementwise_maximum(mat, vec):
    output = sparse.lil_matrix(mat.shape)
    for row_idx in range( mat.shape[0] ):
        output[row_idx] = mat[row_idx,:].maximum(vec)
    return output

# Time it
num_timing_loops = 3.0
starttime = timeit.default_timer()
for _ in range(int(num_timing_loops)):
    sparse_elementwise_maximum(mat, vec)
print('time per call is:', (timeit.default_timer() - starttime)/num_timing_loops, 'seconds')
# 15 seconds per call (way too slow!)

РЕДАКТИРОВАТЬ Я принимаю ответ Макса, так как вопрос был конкретно о высокопроизводительном решении, а решение Макса предлагает огромные ускорения в 1000-2500 раз на различных входах, которые я пробовал, за счет большего количества строк кода и Numba компиляция. Однако для общего использования однострочник Daniel F - отличное решение, предлагающее 10-50-кратное ускорение на примерах, которые я пробовал - я, вероятно, буду использовать для многих других вещей.


person cataclysmic    schedule 17.11.2020    source источник
comment
Ладно, странно, выполнение этих действий с csr_matrix или coo_matrix приводит к сбою ядра. csc_matrix хотя работает. Кто-нибудь может повторить?   -  person Daniel F    schedule 17.11.2020
comment
Да - просто отредактировал, чтобы сделать их csc_matrix, чтобы пример мог работать для других. У меня также были сбои при использовании csr_matrix с функцией .maximum () (в частности, с ошибками seg).   -  person cataclysmic    schedule 17.11.2020
comment
Хорошо, может потребоваться несколько мастеров серверной части, чтобы выяснить, что происходит в скомпилированном scipy коде. Наберитесь терпения, одному из них может потребоваться время, чтобы это увидеть.   -  person Daniel F    schedule 17.11.2020
comment
Итак, насколько я могу судить, каждая форма разреженной матрицы от scipy, кроме csc_matrix, возвращается к csr_matrix для выполнения maximum, так что, по крайней мере, это только одна основная причина. Я не могу следить за _maxumum_minimum_ или как maximum это называет. Похоже, что вызов if dense_check(other): должен вызвать ValueError, поскольку это массив. Может там что-то закоротило   -  person Daniel F    schedule 17.11.2020
comment
scipy/sparse/compressed.py содержит код для таких методов, как maximum, с _binopt в основе. В результате получается разреженный массив с self.shape, построенный из массивов _5 _, _ 6_ и indices. Логика непростая. Имеет смысл, если other скалярный или соответствует self по форме.   -  person hpaulj    schedule 17.11.2020
comment
С формой (20000, 4000) массив indptr для csr будет иметь длину 20000 и 4000 для csc. Для (1,4000) csr будет более компактным. Иногда мы получаем скорость, выполняя итерацию indptr, получая данные строки (или столбца) и индексы напрямую. Я сделал это с одной матрицей, но не пробовал с двумя.   -  person hpaulj    schedule 17.11.2020
comment
Возможно ли преобразование в csr-матрицу, или матрица в реальном примере настолько велика, что вызывает проблемы? Является ли разреженность (в вашем примере 0,01) такой же в вашем реальном приложении?   -  person max9111    schedule 18.11.2020
comment
@ max9111 да - у меня они все изначально были как csr-матрицы. Я редактировал код, когда Даниэль Ф указал, что у него возникают ошибки (ошибки сегментов?), И они были у меня тоже время от времени - по какой-то причине переход на csc-matrices исправляет это. Но да - csr в полном порядке.   -  person cataclysmic    schedule 19.11.2020
comment
@ max9111 Re: разреженность, она варьируется, но разумные значения составляют от 0,001 до 0,005 доли ненулевых элементов.   -  person cataclysmic    schedule 19.11.2020


Ответы (3)


Подход низкого уровня

Как всегда, вы можете подумать о том, как построить правильный формат разреженной матрицы для этой операции, для csr-матриц основными компонентами являются shape, data_arr, index и ind_ptr. С этими частями объекта scipy.sparse.csr довольно просто, но, возможно, потребуется немного времени для реализации эффективного алгоритма на скомпилированном языке (C, C ++, Cython, Python-Numba). В его реализации я использовал Numba, но перенос его на C ++ должен быть легко возможным (изменения синтаксиса) и, возможно, избежать нарезки.

Реализация (первая попытка)

import numpy as np
import numba as nb

# get all needed components of the csr object and create a resulting csr object at the end
def sparse_elementwise_maximum_wrap(mat,vec):
    mat_csr=mat.tocsr()
    vec_csr=vec.tocsr()

    shape_mat=mat_csr.shape
    indices_mat=mat_csr.indices
    indptr_mat=mat_csr.indptr
    data_mat=mat_csr.data
    indices_vec=vec_csr.indices
    data_vec=vec_csr.data

    res=sparse_elementwise_maximum_nb(indices_mat,indptr_mat,data_mat,shape_mat,indices_vec,data_vec)
    res=sparse.csr_matrix(res, shape=shape_mat)
    return res

@nb.njit(cache=True)
def sparse_elementwise_maximum_nb(indices_mat,indptr_mat,data_mat,shape_mat,vec_row_ind,vec_row_data):
    data_res=[]
    indices_res=[]
    indptr_mat_res=[]

    indptr_mat_=0
    indptr_mat_res.append(indptr_mat_)

    for row_idx in range(shape_mat[0]):
        mat_row_ind=indices_mat[indptr_mat[row_idx]:indptr_mat[row_idx+1]]
        mat_row_data=data_mat[indptr_mat[row_idx]:indptr_mat[row_idx+1]]

        mat_ptr=0
        vec_ptr=0
        while mat_ptr<mat_row_ind.shape[0] and vec_ptr<vec_row_ind.shape[0]:
            ind_mat=mat_row_ind[mat_ptr]
            ind_vec=vec_row_ind[vec_ptr]

            #value for both matrix and vector is present
            if ind_mat==ind_vec:
                data_res.append(max(mat_row_data[mat_ptr],vec_row_data[vec_ptr]))
                indices_res.append(ind_mat)
                mat_ptr+=1
                vec_ptr+=1
                indptr_mat_+=1

            #only value for the matrix is present vector is assumed 0
            elif ind_mat<ind_vec:
                if mat_row_data[mat_ptr] >0:
                    data_res.append(mat_row_data[mat_ptr])
                    indices_res.append(ind_mat)
                    indptr_mat_+=1
                mat_ptr+=1

            #only value for the vector is present matrix is assumed 0
            else:
                if vec_row_data[vec_ptr] >0:
                    data_res.append(vec_row_data[vec_ptr])
                    indices_res.append(ind_vec)
                    indptr_mat_+=1
                vec_ptr+=1

        for i in range(mat_ptr,mat_row_ind.shape[0]):
            if mat_row_data[i] >0:
                data_res.append(mat_row_data[i])
                indices_res.append(mat_row_ind[i])
                indptr_mat_+=1
        for i in range(vec_ptr,vec_row_ind.shape[0]):
            if vec_row_data[i] >0:
                data_res.append(vec_row_data[i])
                indices_res.append(vec_row_ind[i])
                indptr_mat_+=1
        indptr_mat_res.append(indptr_mat_)

    return np.array(data_res),np.array(indices_res),np.array(indptr_mat_res)

Реализация (оптимизирована)

При таком подходе списки заменяются массивом с динамически изменяемым размером. Я увеличил размер вывода с шагом 60 МБ. При создании csr-объекта также не создается копия данных, только ссылки. Если вы хотите избежать накладных расходов на память, вам нужно скопировать массивы в конце.

@nb.njit(cache=True)
def sparse_elementwise_maximum_nb(indices_mat,indptr_mat,data_mat,shape_mat,vec_row_ind,vec_row_data):
    mem_step=5_000_000
    #preallocate memory for 5M non-zero elements (60 MB in this example)
    data_res=np.empty(mem_step,dtype=data_mat.dtype)
    indices_res=np.empty(mem_step,dtype=np.int32)
    data_res_p=0

    indptr_mat_res=np.empty((shape_mat[0]+1),dtype=np.int32)
    indptr_mat_res[0]=0
    indptr_mat_res_p=1
    indptr_mat_=0

    for row_idx in range(shape_mat[0]):
        mat_row_ind=indices_mat[indptr_mat[row_idx]:indptr_mat[row_idx+1]]
        mat_row_data=data_mat[indptr_mat[row_idx]:indptr_mat[row_idx+1]]

        #check if resizing is necessary
        if data_res.shape[0]<data_res_p+shape_mat[1]:
            #add at least memory for another mem_step elements
            size_to_add=mem_step
            if shape_mat[1] >size_to_add:
                size_to_add=shape_mat[1]

            data_res_2   =np.empty(data_res.shape[0]   +size_to_add,data_res.dtype)
            indices_res_2=np.empty(indices_res.shape[0]+size_to_add,indices_res.dtype)
            for i in range(data_res_p):
                data_res_2[i]=data_res[i]
                indices_res_2[i]=indices_res[i]
            data_res=data_res_2
            indices_res=indices_res_2

        mat_ptr=0
        vec_ptr=0
        while mat_ptr<mat_row_ind.shape[0] and vec_ptr<vec_row_ind.shape[0]:
            ind_mat=mat_row_ind[mat_ptr]
            ind_vec=vec_row_ind[vec_ptr]

            #value for both matrix and vector is present
            if ind_mat==ind_vec:
                data_res[data_res_p]=max(mat_row_data[mat_ptr],vec_row_data[vec_ptr])
                indices_res[data_res_p]=ind_mat
                data_res_p+=1
                mat_ptr+=1
                vec_ptr+=1
                indptr_mat_+=1

            #only value for the matrix is present vector is assumed 0
            elif ind_mat<ind_vec:
                if mat_row_data[mat_ptr] >0:
                    data_res[data_res_p]=mat_row_data[mat_ptr]
                    indices_res[data_res_p]=ind_mat
                    data_res_p+=1
                    indptr_mat_+=1
                mat_ptr+=1

            #only value for the vector is present matrix is assumed 0
            else:
                if vec_row_data[vec_ptr] >0:
                    data_res[data_res_p]=vec_row_data[vec_ptr]
                    indices_res[data_res_p]=ind_vec
                    data_res_p+=1
                    indptr_mat_+=1
                vec_ptr+=1

        for i in range(mat_ptr,mat_row_ind.shape[0]):
            if mat_row_data[i] >0:
                data_res[data_res_p]=mat_row_data[i]
                indices_res[data_res_p]=mat_row_ind[i]
                data_res_p+=1
                indptr_mat_+=1
        for i in range(vec_ptr,vec_row_ind.shape[0]):
            if vec_row_data[i] >0:
                data_res[data_res_p]=vec_row_data[i]
                indices_res[data_res_p]=vec_row_ind[i]
                data_res_p+=1
                indptr_mat_+=1
        indptr_mat_res[indptr_mat_res_p]=indptr_mat_
        indptr_mat_res_p+=1

    return data_res[:data_res_p],indices_res[:data_res_p],indptr_mat_res

Максимальный объем памяти, выделенный в начале

Производительность и удобство использования этого подхода сильно зависят от входных данных. При таком подходе выделяется максимальная память (это может легко вызвать ошибки нехватки памяти).

@nb.njit(cache=True)
def sparse_elementwise_maximum_nb(indices_mat,indptr_mat,data_mat,shape_mat,vec_row_ind,vec_row_data,shrink_to_fit):
    max_non_zero=shape_mat[0]*vec_row_data.shape[0]+data_mat.shape[0]
    data_res=np.empty(max_non_zero,dtype=data_mat.dtype)
    indices_res=np.empty(max_non_zero,dtype=np.int32)
    data_res_p=0

    indptr_mat_res=np.empty((shape_mat[0]+1),dtype=np.int32)
    indptr_mat_res[0]=0
    indptr_mat_res_p=1
    indptr_mat_=0

    for row_idx in range(shape_mat[0]):
        mat_row_ind=indices_mat[indptr_mat[row_idx]:indptr_mat[row_idx+1]]
        mat_row_data=data_mat[indptr_mat[row_idx]:indptr_mat[row_idx+1]]

        mat_ptr=0
        vec_ptr=0
        while mat_ptr<mat_row_ind.shape[0] and vec_ptr<vec_row_ind.shape[0]:
            ind_mat=mat_row_ind[mat_ptr]
            ind_vec=vec_row_ind[vec_ptr]

            #value for both matrix and vector is present
            if ind_mat==ind_vec:
                data_res[data_res_p]=max(mat_row_data[mat_ptr],vec_row_data[vec_ptr])
                indices_res[data_res_p]=ind_mat
                data_res_p+=1
                mat_ptr+=1
                vec_ptr+=1
                indptr_mat_+=1

            #only value for the matrix is present vector is assumed 0
            elif ind_mat<ind_vec:
                if mat_row_data[mat_ptr] >0:
                    data_res[data_res_p]=mat_row_data[mat_ptr]
                    indices_res[data_res_p]=ind_mat
                    data_res_p+=1
                    indptr_mat_+=1
                mat_ptr+=1

            #only value for the vector is present matrix is assumed 0
            else:
                if vec_row_data[vec_ptr] >0:
                    data_res[data_res_p]=vec_row_data[vec_ptr]
                    indices_res[data_res_p]=ind_vec
                    data_res_p+=1
                    indptr_mat_+=1
                vec_ptr+=1

        for i in range(mat_ptr,mat_row_ind.shape[0]):
            if mat_row_data[i] >0:
                data_res[data_res_p]=mat_row_data[i]
                indices_res[data_res_p]=mat_row_ind[i]
                data_res_p+=1
                indptr_mat_+=1
        for i in range(vec_ptr,vec_row_ind.shape[0]):
            if vec_row_data[i] >0:
                data_res[data_res_p]=vec_row_data[i]
                indices_res[data_res_p]=vec_row_ind[i]
                data_res_p+=1
                indptr_mat_+=1
        indptr_mat_res[indptr_mat_res_p]=indptr_mat_
        indptr_mat_res_p+=1

    if shrink_to_fit==True:
        data_res=np.copy(data_res[:data_res_p])
        indices_res=np.copy(indices_res[:data_res_p])
    else:
        data_res=data_res[:data_res_p]
        indices_res=indices_res[:data_res_p]

    return data_res,indices_res,indptr_mat_res

# get all needed components of the csr object and create a resulting csr object at the end
def sparse_elementwise_maximum_wrap(mat,vec,shrink_to_fit=True):
    mat_csr=mat.tocsr()
    vec_csr=vec.tocsr()

    shape_mat=mat_csr.shape
    indices_mat=mat_csr.indices
    indptr_mat=mat_csr.indptr
    data_mat=mat_csr.data
    indices_vec=vec_csr.indices
    data_vec=vec_csr.data

    res=sparse_elementwise_maximum_nb(indices_mat,indptr_mat,data_mat,shape_mat,indices_vec,data_vec,shrink_to_fit)
    res=sparse.csr_matrix(res, shape=shape_mat)
    return res

Время

У Numba есть накладные расходы на компиляцию или некоторые накладные расходы для загрузки функции из кеша. Не рассматривайте первый вызов, если вы хотите получить время выполнения, а не компиляцию + время выполнения.

import numpy as np
from scipy import sparse

mat = sparse.csr_matrix(  sparse.random(20000, 4000, density=.01, data_rvs=lambda s: np.random.randint(0, 5000, size=s))  )
vec = sparse.csr_matrix(  sparse.random(1, 4000, density=.01, data_rvs=lambda s: np.random.randint(0, 5000, size=s))  )

%timeit output=sparse_elementwise_maximum(mat, vec)
#for csc input
37.9 s ± 224 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
#for csr input
10.7 s ± 90.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

#Daniel F
%timeit sparse_maximum(mat, vec)
164 ms ± 1.74 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

#low level implementation (first try)
%timeit res=sparse_elementwise_maximum_wrap(mat,vec)
89.7 ms ± 2.51 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

#low level implementation (optimized, csr)
%timeit res=sparse_elementwise_maximum_wrap(mat,vec)
16.5 ms ± 122 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

#low level implementation (preallocation, without copying at the end)
%timeit res=sparse_elementwise_maximum_wrap(mat,vec)
16.5 ms ± 122 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

#low level implementation (preallocation, with copying at the end)
%timeit res=sparse_elementwise_maximum_wrap(mat,vec)
16.5 ms ± 122 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit res=sparse_elementwise_maximum_wrap(mat,vec,shrink_to_fit=False)
14.9 ms ± 110 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit res=sparse_elementwise_maximum_wrap(mat,vec,shrink_to_fit=True)
21.7 ms ± 399 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

#For comparison, copying the result takes
%%timeit
np.copy(res.data)
np.copy(res.indices)
np.copy(res.indptr)
7.8 ms ± 47.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
person max9111    schedule 19.11.2020
comment
Это круто. На больших входах я получаю ускорение ~ 2000x. Вчера я впервые попытался написать это на Numba (никогда раньше не использовал Numba) и в итоге получил что-то гораздо более медленное, чем оригинал - очевидно, это было вопросом умения. Вопрос: основываясь на моих очень ограниченных знаниях C ++, могу ли я пойти еще быстрее, используя многопоточность по строкам мата, поскольку каждая строка независима? - person cataclysmic; 20.11.2020
comment
@cataclysmic Да, это должно быть возможно, но я думаю, что необходимы сложные знания C ++, чтобы получить действительно эффективный код. Основная проблема реализации Numba выше не в отсутствии распараллеливания, а в сборе результатов в Numba-Lists. Может быть, я найду время вечером, чтобы оптимизировать это (должен дать еще два или более множителя) - person max9111; 20.11.2020
comment
Удивительно, но я думаю, что, должно быть, делаю что-то не так - я не могу воспроизвести ваше время. После компиляции как исходной, так и оптимизированной версий я получаю 0,0643 с для оригинала и 0,0488 с для оптимизированного, что лучше, но не 5-кратное ускорение, которое вы получаете для оптимизированного по сравнению с оригиналом. Я вижу, что ваш код вызывает sparse_elementwise_maximum_wrap (), но я вижу sparse_elementwise_maximum_ 2 _wrap из исходной версии выше. Возможно, эта функция изменилась? Еще раз спасибо за эти замечательные реализации. - person cataclysmic; 21.11.2020
comment
Также я должен был добавить к исходному коду вызов np.random.seed (12345) прямо перед генерацией mat и vec, чтобы они всегда были одинаковыми для согласованности по времени. - person cataclysmic; 21.11.2020
comment
@cataclysmic Memstep был слишком маленьким. Это значение обычно следует адаптировать для реальной проблемы. Обертка остается одинаковой для обеих функций. - person max9111; 21.11.2020
comment
АА, вижу. Теперь я могу увеличить скорость в 3 раза по сравнению с оригиналом, который и без того был невероятно быстрым. Насколько я понимаю, блок, который начинается с #check if resiting is required, просто проверяет, не исчерпывается ли предварительно выделенное пространство. Итак, я могу понять, почему бы не использовать, например, сумма длин входных размеров mat + размеры для vec вместо memstep (в худшем случае количество ненулевых элементов на выходе представляет собой сумму ненулевых в mat + ненулевых в vec)? Может быть, это слишком расточительно и требует дополнительных вызовов len ()? Еще раз спасибо - я многому научился, глядя на ваш код. - person cataclysmic; 21.11.2020
comment
@cataclysmic Наихудший случай - ненулевые элементы mat + ненулевые элементы vec * mat.shape [0]. В зависимости от структуры разреженности это может быть намного больше, чем фактический результат. Вы можете попробовать это, но я бы рекомендовал проверить это на вашем реальном примере. В этом случае я бы также определенно рекомендовал копию data_res и index_res в конце, чтобы избежать постоянного превышения доступности больших объемов ОЗУ. - person max9111; 21.11.2020
comment
@cataclysmic Я добавил версию, которая выделяет максимально необходимую память. - person max9111; 23.11.2020
comment
Потрясающие. Я многое узнаю о Numba, читая ваши решения - это полностью отличается от моего грубого подхода к большинству проблем. Спасибо! - person cataclysmic; 24.11.2020
comment
Святая корова. Невозможно даже злиться, потеряв галочку при таком ответе. Это вдохновенная работа. - person Daniel F; 01.12.2020
comment
@DanielF Спасибо за награду. Может быть, я попробую и версию C, если найду время. - person max9111; 03.12.2020
comment
@ max9111 Я бы хотел увидеть версию C. Увидев производительность вашей версии Numba, становится ясно, что я, вероятно, попробую написать весь алгоритм (где ваш ответ здесь является наиболее сложным шагом) на C в какой-то момент. - person cataclysmic; 11.12.2020

scipy.sparse матрицы не транслируются. Вообще. Так что, если вы не сможете придумать способ работы с indices и inpts (я не знал), вы застряли в стеке. Лучшее, что я могу придумать, - это просто vstack ваши vec, пока они не станут той же формы, что и mat. Кажется, это дает хорошее ускорение, хотя не объясняет странностей segfault с csr.

#using `mat` and `vec` from the speed test
def sparse_maximum(mat, vec):
    vec1 = sparse.vstack([vec for _ in range(mat.shape[0])])
    return mat.maximum(vec1)

# Time it
num_timing_loops = 3.0
starttime = timeit.default_timer()
sparse_maximum(mat, vec)
print('time per call is:', (timeit.default_timer() - starttime)/num_timing_loops, 'seconds')
# I was getting 11-12 seconds on your original code
time per call is: 0.514533479333295 seconds

Доказательство того, что он работает с исходными матрицами:

vec = sparse.vstack([vec for _ in range(4)])

print(mat.maximum(vec).todense())
[[  0   5 100]
 [  3   5 100]
 [  6   7 100]
 [  9  10 100]]
person Daniel F    schedule 17.11.2020
comment
Это дает мне 15-кратное ускорение в примере, приведенном в моем вопросе, и больше похоже на 50-кратное ускорение на более крупной (50k X 10k) матрице. Спасибо за публикацию! Я согласен с тем, что создание vstack размером с коврик кажется мне "неправильным" (с точки зрения эффективности), но, возможно, это невозможно обойти. - person cataclysmic; 18.11.2020

Глядя на метод maximum и код, особенно на метод _binopt в /scipy/sparse/compressed.py, становится очевидно, что он может работать со скаляром other. Для разреженного other он создает новую разреженную матрицу (того же формата и формы), используя значения indptr и т. Д. Если другой имеет такую ​​же форму, он работает:

In [55]: mat = sparse.csr_matrix(np.arange(12).reshape((4,3)))
In [64]: mat.maximum(mat)
Out[64]: 
<4x3 sparse matrix of type '<class 'numpy.int64'>'
    with 11 stored elements in Compressed Sparse Row format>

Это не удается, это другая разреженная матрица 1d:

In [65]: mat.maximum(mat[0,:])
Segmentation fault (core dumped)

mat.maximum(mat[:,0]) работает без ошибок, хотя я не уверен в значениях. mat[:,0] будет иметь тот же размер indptr.

Я думал, что mat.maximum(mat[:,0]) будет давать такую ​​же ошибку, если mat был csc, но это не так.

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

person hpaulj    schedule 17.11.2020
comment
Спасибо. Я использую матрицы scipy.sparse для умножения матриц. Эта часть кода, очевидно, очень быстрая (намного быстрее, чем массивы numpy). Однако матрица, которая представляет собой поэлементный максимум (mat, vec), необходима для одного из скалярных произведений. - person cataclysmic; 18.11.2020
comment
Существенно ли добавляют ненулевые значения vec к ненулевым результатам? Если vec имеет нули в том же месте, что и mat, maximum потенциально намного проще, чем if can превращает 0 в mat в ненулевое значение. Изменения в разреженности csr/csc, говоря относительнее, дорого обходятся. - person hpaulj; 18.11.2020
comment
У меня была такая же идея! Да, нули в vec также являются нулями в mat (т.е. 0-столбцы), и у меня уже есть индексы этих нулей из другой операции. Я написал версию numpy, которая намного быстрее, чем моя предыдущая версия numpy. Все еще не уверен, как написать оптимальную scipy версию (я относительно новичок в scipy, поэтому все еще пытаюсь понять, как делать что-то эффективно) - person cataclysmic; 18.11.2020
comment
Метод numpy: `np.maximum (mat [:, nonzero_cols_booleanvec], vec [nonzero_cols_boolean_vec]) 'Прямо сейчас я пробую версию, которая выполняет нарезку в scipy, а затем преобразует ее в массивы numpy (что делает mat и vec очень уже = меньше памяти, чем у плотных версий full mat и full vec, потому что они разреженные, и мы сохраняем только ненулевые значения после нарезки), затем использует np.maximum. Наверное, есть что-то намного лучше без этого уродливого кастинга, но я этого не знаю. - person cataclysmic; 18.11.2020