Как векторизовать расширенную индексацию со списком списков в NumPy?

Следующий код выполняется за 45 секунд при использовании чистого Python.

for iteration in range(maxiter):
    for node in range(n):
        for dest in adjacency_list[node]:
            rs[iteration + 1][dest] += beta * rs[iteration][node] / len(adjacency_list[node])

Но, просто инициализируя rs как numpy ndarray вместо списка списков python, код выполняется за 145 секунд. Я действительно не знаю, почему numpy занимает в 3 раза больше времени с индексацией этого массива.

Моя идея заключалась в том, чтобы векторизовать как можно больше вещей, но мне удалось векторизовать только умножение beta/len(adjacency_list[node]). Этот код работает за 77 секунд.

beta_over_out_degree = np.array([beta / len(al) for al in adjacency_list])
for iteration in range(1, maxiter + 1):
    r_next = np.full(shape=n, fill_value=(1 - beta) / n)
    f = beta_over_out_degree * r
    for i in range(n):
        r_next[adjacency_list[i]] += f[i]

    r = np.copy(r_next)
    rs[iteration] = np.copy(r)

Проблема в том, что adjacency_list — это список списков с разным размером столбцов, со 100 000 строк и 1-15 столбцов. Более стандартный подход с матрицей смежности, по крайней мере, как обычный ndarray, не вариант, так как для n=100 000 его форма (n,n) слишком велика для размещения в памяти.

Есть ли способ векторизовать, используя его индексы для расширенного индексирования numpy (возможно, превратив его в numpy ndarray)?

Я также был бы очень признателен за любые другие советы по скорости. Заранее спасибо!

РЕДАКТИРОВАТЬ: благодаря @stevemo мне удалось создать adjacency_matrix с функциональностью csr_matrix и использовать его для итеративного умножения. Теперь программа работает всего за 2 секунды!

for iteration in range(1, 101):
    rs[iteration] += rs[iteration - 1] * adjacency_matrix

person sukisule    schedule 20.05.2020    source источник
comment
Не могли бы вы поделиться размерами массивов? Возможно, создать фиктивный пример?   -  person yatu    schedule 20.05.2020
comment
Вам нужны значения на всех итерациях или только на последней?   -  person Igor Rivin    schedule 20.05.2020
comment
Если вы имеете в виду значения adjacency_list, поскольку они используются в самом внутреннем цикле, они мне нужны на всех итерациях.   -  person sukisule    schedule 20.05.2020
comment
с n = 100000 формы: r-> (1, n), rs (100, n), adjacency_list-> (n, между 1 и 15)   -  person sukisule    schedule 20.05.2020
comment
Индексация отдельных элементов массива выполняется медленнее.   -  person hpaulj    schedule 20.05.2020
comment
С таким вопросом нам нужен как небольшой демонстрационный случай (для проверки альтернатив), так и оценки размера реальной проблемы. Тот факт, что у вас есть списки разного размера, значительно усложняет поиск быстрого решения numpy (которое оптимизировано для «прямоугольных» многомерных массивов).   -  person hpaulj    schedule 20.05.2020
comment
Честно говоря, я не знаю простого способа показать вам демонстрацию без предоставления полного набора данных/текстового файла и полного сценария.   -  person sukisule    schedule 20.05.2020


Ответы (1)


Если я правильно вас понял, это можно сделать с помощью однострочной формулы, используя матричные степени матрицы смежности.

Основываясь на исходном фрагменте кода, кажется, что у вас есть некоторая сеть из n узлов с информацией о смежности, хранящейся в виде списка списков в adjacency, и у вас есть значение r, связанное с каждым узлом, такое его значение на итерации k+1 в beta раз превышает сумма r каждого из его соседей на итере k. (Ваш цикл строит это в противоположном направлении, но то же самое.)

Если вы не возражаете преобразовать свой adjacency список списков в более стандартную матрицу смежности , так что A_ij = 1, если ij являются соседями, и 0 в противном случае, то вы можете выполнить два внутренних цикла с помощью простого матричного произведения r[k+1] = beta * (A @ r[k]).

И следуя этой логике, r[k+2] = beta * (A @ (beta * (A @ r[k]))) = (beta * A)**2 @ r[k] или вообще,

r[k] = (beta * A)**k @ r[0]

Давайте попробуем это в небольшой сети:

# adjacency matrix
A = np.array([
    [0, 1, 1, 0, 0],
    [1, 0, 1, 0, 0],
    [1, 1, 0, 1, 0],
    [0, 0, 1, 0, 1],
    [0, 0, 0, 1, 0]
])

# initial values
n = 5
beta = 0.5
r0 = np.ones(n)
maxiter = 10

# after one iteration
print(beta * (A @ r0))
# [1.  1.  1.5 1.  0.5]

# after 10 iterations
print(np.linalg.matrix_power((beta * A), maxiter) @ r0)
# [2.88574219 2.88574219 3.4921875  1.99414062 0.89257812]
person stevemo    schedule 21.05.2020
comment
Спасибо за очень подробный и полезный ответ. Я нуб в stackoverflow, поэтому забыл еще одну деталь: матрица смежности, по крайней мере, как обычный ndarray, не вариант, так как для n = 100 000 ее форма (n, n) слишком велика, чтобы быть выделенной для объем памяти. - person sukisule; 21.05.2020
comment
С разреженными матрицами в Scipy не должно быть проблем, и они хорошо работают с NumPy. Они могут показаться немного пугающими для нового пользователя, но уверяю вас, они того стоят:) - person stevemo; 21.05.2020