Когда я должен использовать 'sparse'?
Я просматривал Matlab's sparse
документация пытаясь найти, есть ли какие-либо рекомендации, когда имеет смысл использовать разреженное представление, а не полное представление.
например, у меня есть матрица data
около 30% ненулевых записей. Я могу проверить используемую память.
whos data
Name Size Bytes Class Attributes
data 84143929x11 4394073488 double sparse
data = full(data);
whos data
Name Size Bytes Class Attributes
data 84143929x11 7404665752 double
здесь я явно сохраняю память, но будет ли это верно для любой матрицы с 30% ненулевыми записями? Что о 50% ненулевые записи? Есть правило в какой процент я должен переключиться на полную матрицу?
как насчет вычислений? Как правило, медленнее или быстрее делать матричное умножение с разреженной матрицей? Разреженные Матричные Операции говорит, что
вычислительная сложность разреженных операций пропорционально nnz, число ненулевых элементов в матрице. Вычислительный сложность также линейно зависит от строки размер m и размер столбца n матрицы, но не зависит от произведения m*n, общее число нулевых и ненулевых элементов.
это трудно сравнить с полной матрицей не зная всех подробностей.
разреженная матричная библиотека Scipy объясняет плюсы и минусы каждого разреженного формата. Например,csc_matrix
преимущества формата CSC
- эффективные арифметические операции CSC + CSC, CSC * CSC и т. д.
- эффективная нарезка столбцов
- быстрые матричные векторные продукты (CSR, BSR могут быть быстрее)
недостатки формата CSC
- медленные операции нарезки строк (рассмотрим CSR)
- изменения в структуре разреженности дороги (рассмотрим LIL или DOK)
делает аналогичную информацию о Matlab's sparse
реализация существовать? Если да, то где можно Я нашел его?
3 ответов
многие операции с полными матрицами используют вызовы библиотеки BLAS/LAPACK, которые безумно оптимизированы и трудно победить. На практике операции с разреженными матрицами будут превосходить операции с полными матрицами только в специализированных ситуациях, которые могут в достаточной степени использовать I) разреженность и ii) специальную матричную структуру.
просто случайно используя разреженный, вероятно, сделает вас хуже. Пример: что быстрее, добавив полную матрицу 10000x10000 к полной матрице 10000x10000? Или добавление 10000x10000 полная матрица к полностью разреженной (т. е. все равно ноль) матрице 10000x10000? попробуй! В моей системе full + full быстрее!
Каковы некоторые примеры ситуаций, когда разреженные давит полный?
Пример 1: решение линейной системы A*x=b, где A-5000x5000, но является блочной диагональной матрицей, построенной из 500 блоков 5x5. Код установки:
As = sparse(rand(5, 5));
for(i=1:999)
As = blkdiag(As, sparse(rand(5,5)));
end; %As is made up of 500 5x5 blocks along diagonal
Af = full(As); b = rand(5000, 1);
затем вы можете проверить разницу в скорости:
As \ b % operation on sparse As takes .0012 seconds
Af \ b % solving with full Af takes about 2.3 seconds
вообще, переменная 5000 линейная система несколько сложна, но 1000 отдельных 5 переменных линейных систем тривиальны. Последнее в основном то, что решается в разреженном случае.
общая история заключается в том, что если у вас есть специальная матричная структура и вы можете умело использовать разреженность, можно решить безумно большие проблемы, которые в противном случае были бы неразрешимыми. Если у вас есть специализированная задача, которая достаточно велика, имейте матрицу, которая достаточно разрежена, и умны с линейной алгеброй (чтобы сохранить разреженность), разреженная типизированная матрица может быть чрезвычайно мощной.
С другой стороны, случайный бросок в разреженном без глубокой, тщательной мысли почти наверняка сделает ваш код медленнее.
Я не эксперт в использовании sparse
матрицы, однако Mathworks имеет документация относящийся к эффективности деятельности и вычисления.
их описание сложности вычислений:
вычислительная сложность разреженных операций пропорционально nnz, число ненулевых элементов в матрице. Вычислительный сложность также линейно зависит от размера строки m и размера столбца n матрицы, но независимо от продукта m*n, общее число нулевых и ненулевых элементов.
сложность довольно сложных операций, таких как решение разреженных линейных уравнений, включает в себя такие факторы, как порядок и fill-in, которые обсуждаются в предыдущем разделе. В общем, однако для операции с разреженной матрицей требуется компьютерное время пропорционально числу арифметических операций над ненулевыми помногу.
без них скучно вы с алгоритмическими деталями,другого ответа предполагает, что вы не должны беспокоиться о разреженном массиве, который составляет всего 25% ненулей. Они предлагают какой-то код для тестирования. См. их сообщение для деталей.
A = sprand(2000,2000,0.25);
tic,B = A*A;toc
Elapsed time is 1.771668 seconds.
Af = full(A);
tic,B = Af*Af;toc
Elapsed time is 0.499045 seconds.
если у вас есть матрица фиксированного измерения, то лучший способ установить надежный ответ-это просто проб и ошибок. Однако, если вы не знаете размеров ваших матриц / векторов, то эмпирические правила являются
ваши разреженные векторы должны иметь фактически постоянное количество ненулевых записей
что для матриц означает
код
N x N
разреженная матрица должна иметь<= c * N
ненулевые записи, гдеc
является константой "намного меньше", чемN
.
давайте дадим псевдо-теоретическое объяснение этому правилу. Рассмотрим достаточно простую задачу создания скалярного (или точечного) произведения двух векторов с двузначными координатами. Теперь, если у вас есть два плотных вектора одинаковой длины N
, ваш код будет выглядеть как
//define vectors vector, wector as double arrays of length N
double sum = 0;
for (int i = 0; i < N; i++)
{
sum += vector[i] * wector[i];
}
этой суммы в N
дополнения N
умножений и N
conditinal ветви (операции цикла). Самый дорогостоящая операция здесь-условная ветвь, настолько дорогостоящая, что мы можем пренебречь умножениями и тем более добавлениями. Причина, почему это так дорого, объясняется в ответе на этот вопрос.
UPD: на самом деле, в
for
цикл, вы рискуете выбрать неправильную ветвь только один раз, в конце вашего цикла, так как по определению ветвь по умолчанию для выбора будет идти в цикл. Это составляет не более 1 перезапуска конвейера на скалярный продукт операция.
Давайте теперь посмотрим, как разреженные векторы реализуются в BLAS. Там каждый вектор кодируется двумя массивами: одним из значений и одним из соответствующих индексов, что-то вроде
1.7 -0.8 3.6
171 83 215
(плюс одно целое число, указывающее предполагаемую длину N
). В документации BLAS указано, что порядок индексов здесь не играет никакой роли, так что данные
-0.8 3.6 1.7
83 215 171
кодирует тот же вектор. Это замечание дает достаточно информация для восстановления алгоритма скалярного произведения. Учитывая два разреженных вектора, закодированных данными int[] indices, double[] values
и int[] jndices, double[] walues
, одно будет вычислить их скалярное произведение в строках этого кода:
double sum = 0;
for (int i = 0; i < indices.length; i++)
{
for (int j = 0; j < jndices.length; j++)
{
if(indices[i] == jndices[j])
{
sum += values[indices[i]] * walues[jndices[j]];
}
}
}
что дает нам общую сумму indices.length * jndices.length * 2 + indices.length
условных ветвей. Это означает, что только для того, чтобы справиться с плотным алгоритм, векторы должны иметь не более sqrt(N)
ненулевые элементы. Дело здесь в зависимости от N
уже нелинейно, так что есть нет смысла спрашивать, нужна ли вам 1% или 10% или 25% заполнением. 10% идеально подходит для векторов длины 10, все еще в порядке для длины 50 и уже полное разрушение для длины 100.
UPD. В этом фрагменте кода, у вас есть
if
ветвь, и вероятность принять неправильный путь 50%. Таким образом, скалярное произведение двух разреженных векторов будет примерно в 0,5-1 раз превышать среднее число ненулевых записей на разреженный вектор) конвейер перезапускается, в зависимости от того, насколько разрежен ваши векторы . Числа должны быть скорректированы: вif
заявление безelse
, самая короткая инструкция будет принята сперва, которая "ничего не делать", но все же.
обратите внимание, что наиболее эффективной операцией является скалярное произведение разреженного и плотного вектора. Учитывая разреженный вектор indices
и values
и плотный вектор dense
, ваш код будет выглядеть как
double sum = 0;
for (int i = 0; i < indices.length; i++)
{
sum += values[indices[i]] * dense[indices[i]];
}
то есть у вас будет indices.length
условных ветвлений, это хорошо.
UPD. Еще раз, я уверен, что у вас будет не более одного перезапуска трубопровода за операцию. Обратите внимание также, что afaik в современных многоядерных процессорах обе альтернативы выполняются параллельно на двух разных ядрах, так что в альтернативных ветвях вам нужно только дождаться завершения самой длинной.
теперь, при умножении матрицы на вектор, вы в основном берете скалярные произведения # строк векторов. Матрица умножения c матричными суммами в взятие # ((ненулевых) столбцов во второй матрице) матрицы векторными умножениями. Вы можете выяснить сложность самостоятельно.
и так вот где все то черная магия начинается глубокая теория хранения различных матриц. Вы можете хранить свою разреженную матрицу как плотный массив разреженных строк, как разреженный массив плотных строк или разреженный массив разреженных строк. То же самое касается колонок. Все смешные аббревиатуры от Scipy, приведенные в вопросе, имеют отношение к что.
вы "всегда" будете иметь преимущество в скорости, если вы умножите матрицу, построенную из разреженных строк с плотной матрицей, или матрицу плотных столбцов. Вы можете сохранить свои разреженные матричные данные как плотные векторы диагоналей-так и в случае сверточные нейронные сети - и тогда вам понадобятся совершенно другие алгоритмы. Возможно, вы захотите сделать свою матрицу блочной матрицей - как и BLAS - и получить разумный импульс вычислений. Вы можете хранить ваши данные в виде двух матриц-скажем, диагональной и разреженной, что имеет место для метод конечных элементов. Вы можете использовать разреженность для общих нейронных сетей (например. перемотай вперед, экстремальная обучающая машина или echo state network) если вы всегда умножаете матрицу, хранящуюся в строке, на вектор столбца, но избегаете умножения матриц. И вы" всегда " получите преимущество, используя разреженные матрицы, если будете следовать эмпирическому правилу - это справедливо для конечных элемент и сверточные сети, но не удается для вычисления резервуара.