Python, Scipy: построение триплетов с использованием большой матрицы смежности

Я использую матрицу смежности для представления сети друзей, которую можно визуально интерпретировать как

Mary     0        1      1      1

Joe      1        0      1      1

Bob      1        1      0      1

Susan    1        1      1      0 

         Mary     Joe    Bob    Susan

Используя эту матрицу, я хочу составить список всех возможных треугольников дружбы с условием, что пользователь 1 дружит с пользователем 2, а пользователь 2 дружит с пользователем 3. Для моего списка не требуется, чтобы пользователь 1 дружил с пользователем пользователь 3.

(joe, mary, bob)
(joe, mary, susan)
(bob, mary, susan)
(bob, joe, susan)

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

from numpy import *
from scipy import *

def buildTriangles(G):
    # G is a sparse adjacency matrix
    start = time.time()
    ctr = 0
    G = G + G.T          # I do this to make sure it is symmetric
    triples = []
    for i in arange(G.shape[0] - 1):  # for each row but the last one
        J,J = G[i,:].nonzero()        # J: primary friends of user i
                                      # I do J,J because I do not care about the row values
        J = J[ J < i ]                # only computer the lower triangle to avoid repetition
        for j in J:
            K, buff = G[:,j].nonzero() # K: secondary friends of user i
            K = K[ K > i ]             # only compute below i to avoid repetition
            for k in K:
                ctr = ctr + 1
                triples.append( (i,j,k) )
    print("total number of triples: %d" % ctr)
    print("run time is %.2f" % (time.time() - start())
    return triples

Мне удалось запустить код на csr_matrix примерно за 21 минуту. Матрица имела размер 1032570 x 1032570 и содержала 88910 хранимых элементов. Всего было сгенерировано 2178893 троек.

Мне нужно сделать что-то подобное с разреженной матрицей 1968654 x 1968654 с 9428596 сохраненными элементами.

Я новичок в python (чуть меньше месяца опыта) и не лучший в линейной алгебре, поэтому мой код не использует преимущества операций с матрицами. Может ли кто-нибудь внести какие-либо предложения по улучшению или сообщить мне, реалистична ли моя цель?


person will    schedule 03.08.2011    source источник
comment
Я не думаю, что присвоение одного и того же значения дважды в операторе (J,J=) имеет какое-либо гарантированное значение в Python. Я нахожу это очень запутанным, и вы тоже, судя по вашему комментарию, так что вы можете избавиться от него.   -  person Fred Foo    schedule 04.08.2011
comment
@larsmans Мои извинения. nonzero () возвращает индексы матрицы как двумерный массив. В качестве альтернативы я мог бы сделать row, col = G[i,:].nonzero(), а затем J = col. Я использовал подход J,J=, потому что беспокоился об использовании памяти и хотел съесть массив строк, поскольку он не нужен.   -  person will    schedule 04.08.2011
comment
Не извиняйся, я не хотел быть резким. Это просто не идиома Pythonic, и я думаю, что Гвидо может изменить значение этой конструкции между версиями Python, поэтому вы не можете полагаться на ее работу. Лучше del переменную, если это действительно важно, хотя в этом случае J = G[i, :].nonzero()[1] тоже будет работать.   -  person Fred Foo    schedule 04.08.2011
comment
Спасибо за предложения. Это определенно немного очистило код. Работа, которую вы выполняли со статьями Википедии, - это именно то, что я пытаюсь сделать. Я подробнее рассмотрю подход к проблеме с помощью линейной алгебры.   -  person will    schedule 04.08.2011


Ответы (2)


Думаю, треугольники можно найти только в строках или столбцах. Например:

Susan    1        1      1      0 
        Mary     Joe    Bob    Susan

это означает, что Мэри, Джо и Боб - все друзья Сьюзен, поэтому используйте комбинации, чтобы выбрать двух человек из [Мэри, Джо, Боб], и объедините их со Сьюзен, чтобы получить один треугольник. itertools.combinations () делают это быстро.

Вот код:

import itertools
import numpy as np

G = np.array(   # clear half of the matrix first
    [[0,0,0,0],
     [1,0,0,0],
     [1,1,0,0],
     [1,1,1,0]])
triples = []     
for i in xrange(G.shape[0]):
    row = G[i,:]
    J = np.nonzero(row)[0].tolist() # combinations() with list is faster than NumPy array.
    for t1,t2 in itertools.combinations(J, 2):
        triples.append((i,t1,t2))
print triples
person HYRY    schedule 03.08.2011
comment
Спасибо за Ваш ответ. Я даже не рассматривал этот подход, но он имеет большой смысл. Вы в основном сводите проблему к поиску перестановок двух. Были бы все тройки уникальными? - person will; 04.08.2011
comment
@will: Чтобы уточнить, вы имеете в виду, что (Мэри, Сьюзен, Джо) и (Джо, Сьюзен, Мэри) считаются разными или идентичными? - person Iterator; 04.08.2011
comment
@Iterator Я хочу считать их идентичными. Я считаю, что этот метод действительно работает в этом отношении. Посмотрев на это дальше, я теперь понимаю, что каждая новая строка гарантированно не входила в предыдущие перестановки. - person will; 04.08.2011
comment
+1 пользователю 772649. Отлично. Я хочу найти эту функцию на других языках, на которых я работаю. Мне всегда приходилось писать ее самому. - person Iterator; 04.08.2011

Вот несколько предложений по оптимизации:

K = K[ K > i ]             # only compute below i to avoid repetition
for k in K:
    ctr = ctr + 1
    triples.append( (i,j,k) )

Не увеличивайте цикл, это ужасно медленно. Просто ctr += K.shape[0] подойдет. Затем полностью удалите самый глубоко вложенный цикл, заменив append на

triples += ((i, j, k) for k in K[K > i])

Теперь, если вы хотите реального выполнения этой задачи, вам придется заняться какой-нибудь линейной алгеброй. «Я хочу составить список всех возможных треугольников дружбы» означает, что вы хотите возвести матрицу смежности в квадрат, что вы можете сделать с помощью простого **2.

Затем поймите, что 1.968.654² означает очень большую матрицу, и хотя она очень разреженная, ее площадь будет намного меньше, и потребуется много памяти. (Однажды я занимался аналогичной проблемой, когда рассматривал ссылки между статьями Википедии на расстоянии два, решение которых заняло 20 минут, на узле кластера суперкомпьютера, на C ++. Это нетривиальная проблема. Матрица смежности Википедии была на несколько порядков плотнее.)

person Fred Foo    schedule 03.08.2011
comment
Когда вы упоминаете реальную производительность - можете ли вы подробнее рассказать, как перемножить две матрицы и получить список (а не количество) двухэтапных пар? - person Iterator; 04.08.2011
comment
@Iterator: умножение квадратной матрицы на себя дает вам новую матрицу того же ранга, которая имеет значение ›0 для всех i, j, которые соединены на расстоянии шага 2. Умножение матриц - это сильно оптимизированная операция в SciPy (реализованная на C, я думаю, или, возможно, даже на Фортране). Затем вы можете извлечь список самостоятельно, значительно упростив поиск в матрице. - person Fred Foo; 04.08.2011
comment
Да, вы получаете счет на шаге 2, о чем я уже говорил: вы можете подсчитать количество пар (i, *, k). Идентификаторы промежуточных узлов j теряются. Я понимаю (и изложил) все, что вы сказали, но вы не продемонстрировали ускорения именования полного триплета. Я думаю, ты не думаешь об этом до конца. - person Iterator; 04.08.2011