Почему матрица lil и матрица dok так медленны по сравнению с общим диктом диктов?

Я хочу итеративно строить разреженные матрицы и заметил, что есть два подходящих варианта для этого в соответствии с документацией SciPy:

матрица Лил:

класс scipy.редкий.lil_matrix(arg1, shape=нет, dtype=нет, copy=False) [source] разреженная матрица связанного списка на основе строк

Это эффективная структура для построения разреженных матриц пошагово.

док матрица:

класс scipy.редкий.dok_matrix(arg1, shape=None, dtype=None, copy=False) [source] словарь ключей на основе разреженной матрицы.

Это эффективная структура для построения разреженных матриц пошагово.

но когда я запускаю бенчмарки по сравнению с созданием словаря словаря значений (который позже может быть легко преобразован в разреженную матрицу), последний оказывается примерно в 10-20 раз быстрее, чем использование любой из разреженных матричных моделей:

from scipy.sparse import dok_matrix, lil_matrix
from timeit import timeit
from collections import defaultdict

def common_dict(rows, cols):
    freqs = defaultdict(lambda: defaultdict(int))
    for row, col in zip(rows, cols):
        freqs[row][col] += 1

    return freqs

def dok(rows, cols):
    freqs = dok_matrix((1000,1000))
    for row, col in zip(rows, cols):
        freqs[row,col] += 1

    return freqs

def lil(rows, cols):
    freqs = lil_matrix((1000,1000))
    for row, col in zip(rows, cols):
        freqs[row,col] += 1

    return freqs


def benchmark():
    cols = range(1000)
    rows = range(1000)

    res = timeit("common_dict({},{})".format(rows, cols), 
                 "from __main__ import common_dict", 
                 number=100)

    print("common_dict: {}".format(res))

    res = timeit("dok({},{})".format(rows, cols), 
                 "from __main__ import dok", 
                 number=100)

    print("dok: {}".format(res))

    res = timeit("lil({},{})".format(rows, cols), 
                 "from __main__ import lil", 
                 number=100)

    print("lil: {}".format(res))

результаты:

benchmark()

common_dict: 0.11778324202168733
dok: 2.2927695910912007
lil: 1.3541790939634666

что вызывает такие накладные расходы для матричных моделей, и есть ли способ ускорить его? Существуют ли случаи использования, когда dok или lil должны предпочесть общий дикт диктов?

2 ответов


когда я меняю ваш += просто = для 2 разреженных массивов:

for row, col in zip(rows, cols):
    #freqs[row,col] += 1
    freqs[row,col] = 1

их соответствующее время сокращается вдвое. Больше всего времени уходит на индексацию. С += это связано как __getitem__ и __setitem__.

когда врачи говорят, что dok и lil лучше для итеративного построения они означают, что легче расширить их базовые структуры данных, чем для других форматов.

когда я пытаюсь сделать csr матрица с вашим кодом, я получаю:

/ usr/lib / python2.7 / dist-пакеты/scipy/разреженные/сжатые.py: 690: SparseEfficiencyWarning: изменение структуры разреженности csr_matrix дорого. lil_matrix является более эффективным. SparseEfficiencyWarning)

и более медленная скорость 30x.

таким образом, претензии скорости относительно форматов, таких как csr, не относительно чистого Python или numpy структур.

вы можете посмотреть код Python для dok_matrix.__get_item__ и dok_matrix.__set_item__ чтобы увидеть, что происходит, когда вы делаете freq[r,c].


более быстрый способ построить свой dok будет:

freqs = dok_matrix((1000,1000))
d = dict()
for row, col in zip(rows, cols):
    d[(row, col)] = 1
freqs.update(d)

воспользовавшись тем, что a dok является подклассом словаря. Обратите внимание, что dok matrix не является словарем словарей. Его ключи кортежи, как (50,50).

еще один быстрый способ построения того же разреженного массива есть:

freqs = sparse.coo_matrix((np.ones(1000,int),(rows,cols)))

другими словами, поскольку у вас уже есть rows и cols массивы (или диапазоны), вычислить соответствующий data массив, а затем построить разреженный массив.

но если вы должны выполнять разреженные операции над Матрицей между шагами инкрементного роста, то dok или lil может быть ваш лучший выбор.


разреженные матрицы были разработаны для задач линейной алгебры, таких как решение линейного уравнения с большая разреженная матрица. Я использовал их много лет назад в MATLAB для решения конечно-разностных задач. Для этой работы расчет дружественный csr формат является конечной целью, и coo format был удобным форматом инициализации.

теперь многие из так scipy редких вопросов возникают из scikit-learn и проблемы анализа текста. Они также используются в файлах биологической базы данных. Но все же (data),(row,col) метод определения работает лучше всего.

так разреженные матрицы были никогда не предназначался для быстрого инкрементного создания. Традиционные структуры Python, такие как словари и списки, намного лучше для этого.


вот быстрее dok итерация, которая использует свои методы словаря. update кажется, работает так же быстро, как в обычном словаре. get о 3x быстрее эквивалентно индексации (freq[row,col]). Индексирование, вероятно, использует get, но должно быть много накладных расходов.

def fast_dok(rows, cols):
    freqs = dok_matrix((1000,1000))
    for row, col in zip(rows,cols):
         i = freqs.get((row,col),0)
         freqs.update({(row,col):i+1})
    return freqs

пропуск get, и просто делаю

         freqs.update({(row,col): 1)

даже быстрее-быстрее, чем defaultdict примера defaultdict, и почти так же быстро, как простая инициализация словаря ({(r, c):1 for r,c in zip(rows, cols)})


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

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