Почему матрица 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 ко всему массиву.