Вычислить разреженное транзитивное закрытие scipy разреженной матрицы

Я хочу вычислить транзитивное замыкание разреженной матрицы в Python. . В настоящее время я использую разреженные матрицы scipy.

Степень матрицы (в моем случае **12) хорошо работает на очень разреженных матрицах, какими бы большими они ни были, но для направленных не очень разреженных случаев хотелось бы использовать более умный алгоритм.

Я нашел алгоритм Флойда-Уоршалла (немецкая страница лучше псевдокод) в scipy.sparse.csgraph, который делает немного больше, чем должен: нет функции только для алгоритма Уоршалла - это одно.

Основная проблема в том, что я могу передать в функцию разреженную матрицу, но это совершенно бессмысленно, так как функция всегда будет возвращать плотную матрицу, потому что то, что должно быть 0 в транзитивном замыкании, теперь является путем длины inf и кто-то это почувствовал необходимо хранить в явном виде.

Итак, мой вопрос: Есть ли какой-либо модуль Python, который позволяет вычислять транзитивное замыкание разреженной матрицы и сохраняет ее разреженной?

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


РЕДАКТИРОВАТЬ: Поскольку возник ряд недоразумений, я укажу на теоретическую подоплеку:

Я ищу транзитивное замыкание (не рефлексивное и не симметричное).

Я позабочусь о том, чтобы мое отношение, закодированное в булевой матрице, обладало требуемыми свойствами, т. е. симметрией или рефлексивностью.

У меня есть два случая отношения:

  1. рефлексивный
  2. рефлексивный и симметричный

введите здесь описание изображения введите здесь описание изображения

Я хочу применить транзитивное замыкание к этим двум отношениям. Это прекрасно работает с мощностью матрицы (только в некоторых случаях это слишком дорого):

>>> reflexive
matrix([[ True,  True, False,  True],
        [False,  True,  True, False],
        [False, False,  True, False],
        [False, False, False,  True]])
>>> reflexive**4
matrix([[ True,  True,  True,  True],
        [False,  True,  True, False],
        [False, False,  True, False],
        [False, False, False,  True]])
>>> reflexive_symmetric
matrix([[ True,  True, False,  True],
        [ True,  True,  True, False],
        [False,  True,  True, False],
        [ True, False, False,  True]])
>>> reflexive_symmetric**4
matrix([[ True,  True,  True,  True],
        [ True,  True,  True,  True],
        [ True,  True,  True,  True],
        [ True,  True,  True,  True]])

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

введите здесь описание изображения введите здесь описание изображения


person Radio Controlled    schedule 21.12.2017    source источник
comment
Что касается PS, я хотел бы увидеть несколько примеров (в другом вопросе?). Умножение разреженной матрицы на плотный массив возвращает плотный массив. Ради эффективности sparse.csr не меняет индекс разреженности каждый раз, когда вы меняете значение. Иногда вам нужно запустить eliminate_zeros, чтобы очистить его.   -  person hpaulj    schedule 21.12.2017
comment
Прошлые примеры: stackoverflow .com/questions/37674435/, stackoverflow.com/questions/43146968/   -  person hpaulj    schedule 21.12.2017
comment
Если умножение возвращает плотную матрицу, попробуйте сначала преобразовать массив other в разреженную матрицу. sparse*sparse производит sparse.   -  person hpaulj    schedule 21.12.2017
comment
floyd_warshall находится в sparse.csgraph.shortest_path.so, скомпилированном модуле.   -  person hpaulj    schedule 21.12.2017
comment
Вы правы, я только что собрал это в разделе «Разочарования в scipy» ... Я задам для этого новый вопрос.   -  person Radio Controlled    schedule 09.01.2018


Ответы (1)


Это было поднято в системе отслеживания проблем SciPy. Проблема не столько в формате вывода; реализация Флойда-Уоршалла состоит в том, чтобы начать с матрицы, полной бесконечностей, а затем вставить конечные значения, когда путь найден. Разреженность сразу теряется.

Библиотека networkx предлагает альтернативу с ее all_pairs_shortest_path_length. Его вывод — итератор, возвращающий кортежи вида

(source, dictionary of reachable targets) 

для преобразования в разреженную матрицу SciPy требуется небольшая работа (формат csr здесь естественен). Полный пример:

import numpy as np
import networkx as nx
import scipy.stats as stats
import scipy.sparse as sparse

A = sparse.random(6, 6, density=0.2, format='csr', data_rvs=stats.randint(1, 2).rvs).astype(np.uint8)
G = nx.DiGraph(A)       # directed because A need not be symmetric
paths = nx.all_pairs_shortest_path_length(G)
indices = []
indptr = [0]
for row in paths:
  reachable = [v for v in row[1] if row[1][v] > 0]
  indices.extend(reachable)
  indptr.append(len(indices))
data = np.ones((len(indices),), dtype=np.uint8)
A_trans = A + sparse.csr_matrix((data, indices, indptr), shape=A.shape)
print(A, "\n\n", A_trans)

Причина добавления А обратно следующая. Выход Networkx включает в себя пути длины 0, которые сразу заполнили бы диагональ. Мы не хотим, чтобы это произошло (вы хотели транзитивного закрытия, а не рефлексивно-транзитивного закрытия). Отсюда строка reachable = [v for v in row[1] if row[1][v] > 0]. Но тогда мы вообще не получаем никаких диагональных записей, даже там, где они были у A (пустой путь нулевой длины лучше, чем путь длиной 1, образованный петлей). Поэтому я добавляю A обратно к результату. Теперь в нем есть записи 1 или 2, но имеет значение только тот факт, что они отличны от нуля.

Пример запуска вышеизложенного (я выбираю размер 6 на 6 для удобочитаемости вывода). Исходная матрица:

  (0, 3)    1
  (3, 2)    1
  (4, 3)    1
  (5, 1)    1
  (5, 3)    1
  (5, 4)    1
  (5, 5)    1 

Транзитивное закрытие:

  (0, 2)    1
  (0, 3)    2
  (3, 2)    2
  (4, 2)    1
  (4, 3)    2
  (5, 1)    2
  (5, 2)    1
  (5, 3)    2
  (5, 4)    2
  (5, 5)    1

Вы можете видеть, что это сработало правильно: добавлены записи (0, 2), (4, 2) и (5, 2), все они получены по пути (3, 2).


Кстати, у networkx тоже есть floyd_warshall, но его документация говорит

Этот алгоритм наиболее подходит для плотных графов. Время выполнения — O(n^3), а рабочее пространство — O(n^2), где n — количество узлов в G.

Выход снова плотный. У меня складывается впечатление, что этот алгоритм просто считается плотным по своей природе. Похоже, что all_pairs_shortest_path_length является своего рода алгоритмом Дейкстры.

Транзитивный и рефлексивный

Если вместо транзитивного замыкания (которое является наименьшим транзитивным отношением, содержащим заданное) вы хотите транзитивное и рефлексивное замыкание (наименьшее транзитивное и рефлексивное отношение, содержащее данное) , код упрощается, поскольку мы больше не беспокоимся о путях нулевой длины.

for row in paths:
  indices.extend(row[1])
  indptr.append(len(indices))
data = np.ones((len(indices),), dtype=np.uint8)
A_trans = sparse.csr_matrix((data, indices, indptr), shape=A.shape)

Транзитивный, рефлексивный и симметричный

Это означает нахождение наименьшего отношения эквивалентности, содержащего заданное. Эквивалентно делению вершин на компоненты связности. Для этого вам не нужно заходить на networkx, есть connected_components метода SciPy. Установите directed=False там. Пример:

import numpy as np
import scipy.stats as stats
import scipy.sparse as sparse
import itertools

A = sparse.random(20, 20, density=0.02, format='csr', data_rvs=stats.randint(1, 2).rvs).astype(np.uint8)
components = sparse.csgraph.connected_components(A, directed=False)
nonzeros = []
for k in range(components[0]):
  idx = np.where(components[1] == k)[0]
  nonzeros.extend(itertools.product(idx, idx))
  row = tuple(r for r, c in nonzeros)
  col = tuple(c for r, c in nonzeros)
  data = np.ones_like(row)
B = sparse.coo_matrix((data, (row, col)), shape=A.shape)

Вот как выглядит результат print(B.toarray()) для случайного примера, 20 на 20:

[[1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0]
 [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0]
 [0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0]
 [0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0]
 [1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0]
 [1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]]
person Community    schedule 21.12.2017
comment
Меня несколько беспокоят накладные расходы на создание словаря Python в качестве вывода, но, возможно, действительно нет реализации на основе C, которая возвращает разреженную матрицу scipy... - person Radio Controlled; 09.01.2018
comment
Возможно, я неправильно понял смысл транзитивного замыкания. На самом деле я хочу того, что вы называете рефлексивно-транзитивным закрытием. - person Radio Controlled; 09.01.2018
comment
node_connected_component(G,n) также может быть вариантом, но мне бы хотелось иметь решение, которое будет работать с обычными зависимостями numy или scipy - person Radio Controlled; 09.01.2018
comment
Связанные компоненты также выполняют симметричное замыкание, поскольку отношение a и b в одном и том же компоненте симметрично. Пожалуйста, объясните себе и другим, ищете ли вы транзитивное замыкание, транзитивно-рефлексивное замыкание, транзитивно-рефлексивно-симметричное замыкание (то есть отношение эквивалентности, порожденное данным) или что-то еще . - person ; 09.01.2018
comment
Для подключенных компонентов вам не нужен networkx, У SciPy их мало - person ; 09.01.2018
comment
Возвраты: n_components: int Количество связанных компонентов. labels: ndarray Массив длины N меток подключенных компонентов. Я не мог понять, для чего этот вывод полезен. Мне нужно знать, находятся ли два узла в одном компоненте. - person Radio Controlled; 09.01.2018
comment
Похоже, что есть 5 компонентов, например, метки 1,2,3,4,5. Ок, отлично... - person Radio Controlled; 09.01.2018
comment
Добавлен полный пример отношения эквивалентности с использованиемconnected_components. - person ; 10.01.2018
comment
Это здорово! Я думаю, вам нужно изменить отступ в вашем примере. Кроме того, вы можете использовать zip для получения строк и столбцов. - person Radio Controlled; 11.01.2018
comment
Теперь я понимаю, что в какой-то момент мне также нужно получить для каждого узла набор достижимых узлов (поэтому, если вход направлен, предки недоступны). К сожалению, directed=True в connected_components() означает что-то другое. У вас есть предложение? - person Radio Controlled; 11.01.2018
comment
поскольку вывод dijkstra плотный, я попытался использовать аргумент indices для вычисления путей только для нескольких строк за раз, затем сжать в csr и добавить к окончательному решению, но кажется, что функция dijkstra просто не учитывает разреженность ввода, поскольку потребление памяти взрывается.... - person Radio Controlled; 11.01.2018
comment
Я уже говорил, что концепция связных компонентов по своей сути симметрична: если a находится в том же компоненте, что и b, то, конечно, b находится в том же компоненте, что и a. Поэтому, если отношение должно быть асимметричным, компоненты — неправильный инструмент. Во всяком случае, я дал три ответа, и вы все еще не знаете, чего вы на самом деле хотите. Я выхожу. - person ; 11.01.2018
comment
Я очень благодарен за все усилия, которые вы приложили к этому, и за объяснение функциональности компонентов + ваш код очень помог. Несимметричная задача является дополнительной задачей. Здесь я больше не говорю о компонентах и ​​не хочу использовать networkx. Вероятно, ответ заключается в том, что это невозможно. - person Radio Controlled; 15.01.2018