Skip to content

SparseLinearAlgebra/PageRankBenchmark

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PageRankBenchmark

Демонстрация использования алгоритма "PageRank" на графе со сложными атрибутами вершин и ребёр.

Руководство к запуску

Установка зависимостей

sudo apt install ccache
sudo apt install ninja-build

Сборка проекта

git clone https://github.com/SparseLinearAlgebra/PageRankBenchmark.git
cd PageRankBenchmark
git submodule init
git submodule update —recursive
make build

Запуск примера

./build/main

Минимальный пример

Полный код разбираемого примера можно увидеть в файле.

Цель примера --- показать, как в рамках GraphBLAS можно работать со сложными атрибутами вершин и рёбер графа.

В качестве примера возьмём следующую задачу. Пусть есть граф с двумя типами вершин: пользователи и карты. Также есть два типа ориентированных рёбер:

  • "Перевод": соединяет две карты (откуда и куда перевод)
  • "Владеет": соединяет пользователя и карту (от владельца карты к карте)

Каждый тип вершины имеет свой набор атрибутов.

  • "Пользователь": пол, возраст
  • "Карта": тип, лимит средств

Пусть будут следующие типы карт: МИР, VISA, MASTERCARD.

При этом рёбра типа "Перевод" в качестве атрибутов содержат общую сумму и "количество транзакций". Рёбра типа "Владеет" не имеют атрибутов.

Для примера возьмём следующий граф. Пример графа У каждой вершины есть уникальный идентификатор, синие рёбра отображают переводы, красные --- отношение владения, розовые вершины --- пользователи, синие --- карты.

Хотим выбрать по некоторому критерию пользователей и их карты, а затем для анализа переводов хотим посчитать PageRank на подграфе, заданном переводами между отобранными картами. Выбрать хотим все карты системы "МИР", которыми владеют люди старше заданного возраста. Покажем, как это можно сделать, используя матрично-векторные операции, в частности GraphBLAS.

GraphBLAS позволяет в качестве атрибутов использовать пользовательские типы (фиксированных размеров), потому объявим необходимый нам набор типов.

// Тип карты
typedef enum
{
    VISA,
    MIR,
    MASTERCARD,
} System;

// Пол
typedef enum
{
    MALE,
    FEMALE,
} Gender;

// Данные о пользователе
typedef struct
{
    Gender gender;
    uint8_t age;
} User;

// Данные о карте
typedef struct
{
    System system;
    double limit;
} Card;

// Данные о переводах
typedef struct
{
    double sum;
    uint32_t count;
} EdgeTX;

Граф представлен как набор матриц и векторов: по одной матрице на каждый тип рёбер и по одному вектору на каждый тип вершин. Матрицы и вектора в большинстве случаев будут разреженными и мы будем использовать символ '$.$' для обозначения отсутствующего элемента. Считаем при этом, что все вершины, вне зависимости от типа, занумерованы с 0 подряд (id вершин на рисунке). Таким образом, нам понадобятся две матрицы:

$$ \texttt{TX-Edges}= \begin{pmatrix} . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & \{23 \ 412; 6\} & . & . & \{13 \ 214.1; 5\} & . & \{99 \ 999.1; 5\} & . \\ . & . & . & . & \{13 \ 214.1; 5\} & . & \{81 \ 312; 7\} & . & \{92 \ 223; 9\} & . & \{19 \ 999.1; 6\} & . \\ . & . & . & . & . & . & . & . & . & . & . & \{8 \ 999.1; 7\} \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & \{16 \ 325.99; 5\} & . & . & . & . & . & . & \{49 \ 999.1; 12\} \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & \{79 \ 999.1; 15\} & \{69 \ 999.1; 16\} & . & . & . & . & . & . \\ . & . & . & . & . & . & \{59 \ 999.1; 12\} & . & \{999 \ 999.1; 9\} & . & . & . \\ \end{pmatrix} $$

$$ \texttt{Owns-Edges}= \begin{pmatrix} . & . & . & . & . & . & . & . & 1 & . & . & . \\ . & . & . & . & . & . & 1 & 1 & . & . & . & . \\ . & . & . & . & . & 1 & . & . & . & . & . & . \\ . & . & . & . & 1 & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & 1 & 1 \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ \end{pmatrix} $$

И два вектора:

$$ \texttt{Users} = [\{F; 52\} \ ; \ \{F; 25\} \ ; \ \{F; 40\} \ ; \ \{M; 42\} \ ; \ . \ ; \ . \ ; \ . \ ; \ . \ ; \ . \ ; \ \{M; 35\} \ ; \ . \ ; \ . ] $$

$$ \texttt{Cards} = [. \ ; \ . \ ; \ . \ ; \ . \ ; \ \{\text{MIR}; 60 \ 000\} \ ; \ \{\text{MIR}; 70 \ 000\} \ ; \ \{\text{VISA}; 80 \ 000\} \ ; \ \{\text{MASTERCARD}; 90 \ 000\} \ ; \ \{\text{VISA}; 10 \ 000 \ 000\} \ ; \ . \ ; \ \{\text{MIR}; 99 \ 000 \ 000\} \ ; \ \{\text{MIR}; 99 \ 000 \ 000\} ] $$

Первым делом фильтруем пользователей по возрасту. Скажем, нас будут интересовать пользователи старше 30 лет. Для этого в GraphBLAS есть функция Select, которая фильтрует коллекции, используя функцию-предикат принимаемую в качестве аргумента.

Так как нам предстоит работать с пользовательскими типами, то придётся написать собственный предикат.

void check_user_age(bool *z, const User *x, GrB_Index _i, GrB_Index _j, const uint8_t *y)
{
    *z = (x->age > *y);
}

Два дополнительных параметра типа GrB_Index позволяют, при необходимости, использовать в фильтре координаты рассматриваемого элемента.

Для того, чтобы выбрать карты, принадлежащие выбранным пользователям, нам необходимо получить "концы" рёбер типа Owns, исходящие из выбранных пользователей. Чтобы сделать это, выполним один шаг обхода в ширину, который в терминах линейной алгебры выражается через умножение вектора текущих вершин на матрицу смежности. Текущие вершины в нашем случае --- выбранные пользователи. То есть нам необходимо вычислить следующее произведение.

$$ \texttt{Filtered-Cards} = $$ $$ [\{F; 52\} \ ; \ . \ ; \ \{F; 40\} \ ; \ \{M; 42\} \ ; \ . \ ; \ . \ ; \ . \ ; \ . \ ; \ . \ ; \ \{M; 35\} \ ; \ . \ ; \ . ] \otimes \begin{pmatrix} . & . & . & . & . & . & . & . & 1 & . & . & . \\ . & . & . & . & . & . & 1 & 1 & . & . & . & . \\ . & . & . & . & . & 1 & . & . & . & . & . & . \\ . & . & . & . & 1 & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & 1 & 1 \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ \end{pmatrix} = $$ $$ [. \ ; \ . \ ; \ . \ ; \ . \ ; \ 1 \ ; \ 1 \ ; \ . \ ; \ . \ ; \ 1 \ ; \ . \ ; \ 1 \ ; \ 1] $$

Здесь нам впервые потребуется переопределить поэлементные операции для $\otimes$ (в терминах GraphBLAS необходимо сконструировать пользовательское полукольцо).

$$ + \ : \ \textit{bool} \times \textit{bool} \to \textit{bool} $$ $$ * \ : \ \textit{User} \times \textit{bool} \to \textit{bool} $$

В качестве конкретных реализаций для $+$ можно взять логическое "И", а в качестве $*$ операцию $\textit{second}$ (вернуть второй элемент из пары).

Мы получили не совсем карты, но вектор, который указывает, какие карты нас интересуют. Вспомним, что мы хотим взять только карты "МИР". Для этого снова будем использовать Select, а полученный вектор $\texttt{Filtered-Cards}$ будем использовать как маску, чтобы дополнительно отфильтровать результат.

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

То есть данном случае нам необходимо умножить на диагональную матрицу и слева, и справа (нам важно, чтобы все рёбра шли только между отобранными картами).

$$ \texttt{Filtered-Transactions} = \begin{pmatrix} . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & 1 & . & . & . & . & . & . & . \\ . & . & . & . & . & 1 & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & 1 & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & 1 & . \\ . & . & . & . & . & . & . & . & . & . & . & 1 \\ \end{pmatrix} \otimes_1 \begin{pmatrix} . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & \{23 \ 412; 6\} & . & . & \{13 \ 214.1; 5\} & . & \{99 \ 999.1; 5\} & . \\ . & . & . & . & \{13 \ 214.1; 5\} & . & \{81 \ 312; 7\} & . & \{92 \ 223; 9\} & . & \{19 \ 999.1; 6\} & . \\ . & . & . & . & . & . & . & . & . & . & . & \{8 \ 999.1; 7\} \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & \{16 \ 325.99; 5\} & . & . & . & . & . & . & \{49 \ 999.1; 12\} \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & \{79 \ 999.1; 15\} & \{69 \ 999.1; 16\} & . & . & . & . & . & . \\ . & . & . & . & . & . & \{59 \ 999.1; 12\} & . & \{999 \ 999.1; 9\} & . & . & . \\ \end{pmatrix} \otimes_2 \begin{pmatrix} . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & 1 & . & . & . & . & . & . & . \\ . & . & . & . & . & 1 & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & 1 & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & 1 & . \\ . & . & . & . & . & . & . & . & . & . & . & 1 \\ \end{pmatrix} = \begin{pmatrix} . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & \{23 \ 412; 6\} & . & . & \{13 \ 214.1; 5\} & . & \{99 \ 999.1; 5\} & . \\ . & . & . & . & \{13 \ 214.1; 5\} & . & . & . & \{92 \ 223; 9\} & . & \{19 \ 999.1; 6\} & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & \{16 \ 325.99; 5\} & . & . & . & . & . & . & \{49 \ 999.1; 12\} \\ . & . & . & . & . & . & . & . & . & . & . & . \\ . & . & . & . & \{79 \ 999.1; 15\} & \{69 \ 999.1; 16\} & . & . & . & . & . & . \\ . & . & . & . & . & . & . & . & \{999 \ 999.1; 9\} & . & . & . \\ \end{pmatrix} $$

Здесь операции $\otimes_1$ и $\otimes_2$ также требует задания специфичных поэлементных операций $+$ и $*$. Например, для $\otimes_1$:

$$ + \ : \ \textit{bool} \times \textit{bool} \to \textit{bool} $$ $$ * \ : \ \textit{bool} \times \textit{EdgeTX} \to \textit{EdgeTX} $$

В качестве конкретных реализаций для $+$ можно взять логическое "И", а в качестве $*$ операцию $\textit{second}$ (вернуть второй элемент из пары).

Для $\otimes_2$ ситуация аналогичная, Необходимо только проследить за тем, в какие моменты надо брать первый элемент из пары, а в какие второй, чтобы в результате получилась матрица с элементами типа $\texttt{EdgeTX}$.

Подграф готов. Теперь необходимо сконструировать матрицу, по которой непосредственно будем считать PageRank. Сейчас метки рёбер --- структуры, хранящие информацию о переводах, а мы хотим получить одно число. При этом важно, чтобы сумма весов всех исходящих рёбер была равна единице. Для примера действовать будем следующим образом: возьмём "средний размер транзакции" (вычислим как $\frac{\textit{Sum}}{\textit{Count}}$), поделим на 1000 (на всякий случай, чтобы избежать слишком больших значений) и затем построчно применим Softmax. Иными словами, будем использовать идею функции Softmax, которая задаётся следующим образом.

$$ {\displaystyle \sigma (\overrightarrow{z})_{i}={\frac {e^{z_{i}}}{\displaystyle \sum _{k\mathop {=} 1}^{K}e^{z_{k}}}}} $$

В нашем случае подобная функция должна быть применена к каждой строке матрицы, задающей переводы. При этом должна быть определена функция $f$, которая получает вес ребра по его атрибутам (вычисляет $\frac{\textit{Sum}}{\textit{Count} * 1000}$). То есть итоговая формула выглядит следующим образом.

$$ {\displaystyle \sigma (\overrightarrow{z})_{i}={\frac {e^{f(z_{i})}}{\displaystyle \sum _{k\mathop {=} 1}^{K}e^{f(z_{k})}}}} $$

Вычисления построим следующим образом. Сперва выполним редукцию по колонкам с использованием функции $f$: таким образом получим знаменатель дроби. После этого сконструируем две квадратные матрицы: в одной нулевой столбец --- это полученный вектор, а остальные нули, в другой --- нулевая строка --- единицы, остальное --- нули. Перемножим эти две матрицы, использую исходную матрицу $\texttt{Filtered-Transactions}$ в качестве маски. Таким образом получим матрицу, в которой знаменатель стоит на необходимых местах. Также нам нужна будет матрица, содержащая числители дробей (получается поэлементным применением соответствующей функции к $\texttt{Filtered-Transactions}$) После чего поэлементно поделим эти две матрицы.

Далее на полученной матрице запускаем классический алгоритм PageRank (правда, без "телепортации" в несвязанные вершины), который в терминах линейной алгебры реализуется по определению: итеративное умножение исходной матрицы на вектор.

About

Stand for PageRank algorithm benchmark

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •