В этом посте мы возьмем сеть user-—fork-->repo / user—-star-->repo, которую мы создали в предыдущем посте, и запросим ее для создания сети с одним режимом repo<-->repo, называемой co_forked. Этот новый тип ребер будет представлять репозитории, которые были разветвлены одним и тем же пользователем, что указывает на прочную связь между ними с точки зрения интересов пользователей. Используя эти ссылки в следующем посте, мы вычислим метрику нашего нового проекта, прежде чем оценивать эффективность рейтинга по звездам.

Код для этой серии сообщений в блоге (и всех моих экспериментов с Github) находится здесь: https://github.com/rjurney/github_network. Если хотите, можете сразу перейти к следующему сообщению.

Переключение механизмов хранения

Во время работы с механизмом хранения Berkeley DB я неоднократно сталкивался с проблемами, когда база данных могла быть повреждена после прерывания сеанса gremlin. Я прервал многие сеансы Gremlin, потому что запросы никогда не возвращались или они автоматически прерывались из-за ограничений на накладные расходы сборщика мусора. В итоге я перезагрузил данные двадцать или тридцать раз, прежде чем сдался, настроил Cassandra и перешел на механизм хранения Cassandra.

Извлечение сети репо-репо

Как я уже упоминал в первом посте, мы начнем с графика репозиториев и пользователей. Пользователи ссылаются на репозитории, создавая их форк.

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

Это обычная задача при работе с графами свойств: извлечение различных форм сетей из главной сети, определенной новой и разной семантикой, а затем применение методов, разработанных для сетей с одним типом узлов. Набор инструментов анализа социальных сетей (SNA) велик, а алгоритмов графа свойств меньше и дальше друг от друга. Поэтому мы на лету создаем новые одномодовые сети для анализа различных типов ребер, которые мы определяем на лету с помощью запросов. Веселье!

Запрос для этого длинный (спасибо Роберту Дейлу из Списка пользователей Гремлина за помощь в разобраться), поэтому я буду объяснять его по частям (см. Create_co_forks.groovy):

// Add co-forked edges between nodes
g.V().
  hasLabel('repo').
  store('x').
  as('repo1').
  in('forked').
  out('forked').
  where(without('x')).
  as('repo2').
  addE('co_forked').
  to('repo1').
  iterate()

Мы начинаем с g.V().hasLabel('repo').store('x').as(‘repo1’), в котором мы начинаем с выбора всех вершин в графе, а затем фильтруем их только до узлов репо. Шаг hasLabel('repo') - это шаг имеет, который фильтрует вещи по их свойствам. В этом случае их лейбл (пользователь или репо). Мы устанавливаем эти метки при загрузке данных. Шаг store('x') в обходе - это побочный эффект, который сохраняет результаты (в данном случае вершины) на этом шаге для использования в более поздней операции. Наконец, мы помечаем их as('repo1'). Эти узлы сформируют левую часть каждого ребра репо-репо, и они будут доступны под переменной x на более позднем этапе.

Затем мы переходим из репозиториев через in-boundforked ссылки к пользователям, разветвившим репозитории с помощью .in('forked). Это приведет нас к пользовательским узлам, но нам нужна сеть репо-репо. Поэтому мы продолжаем .out(‘forked') исследовать любые другие репозитории, которые пользователь мог создать. Это возвращает нас к узлу репо, нашему конечному пункту назначения. Это дает нам структуру нашей сети репо-репо.

Но есть одна загвоздка ... если мы последуем раздвоенным, а затем разветвленным, мы вернемся по ссылке обратно к себе! Это нежелательно. Чтобы этого избежать, мы добавляем: .where(without('x')), а остальные узлы маркируем .as('repo2').

Теперь, когда у нас есть и repo1, и repo2, мы добавляем новое ребро совместного разветвления от repo2 обратно к repo1: addE('co_forked').to('repo1'). Это добавляет ребра как часть нашего запроса, что довольно круто! Первоначально я экспортировал JSON и загрузил его, чтобы создать новые края, что было намного сложнее, чем добавление края напрямую. Теперь, когда у нас есть совместные разветвленные ребра, мы готовы вычислить рейтинг нашего проекта через центральность собственных векторов co_forked ребер!

Обновление: использование SparkGraphComputer

Мне показалось, что с первой попытки выполнить этот запрос мне повезло, потому что после этого он не работал. Я был вынужден использовать SparkGraphComputer для создания списка краев. Поскольку GraphComputer графики не могут использовать Vertex.addEdge, мне пришлось выполнить co_forked запрос без последних трех строк.

Вместо этого я сначала создал SparkGraphComputer график OLAP и обход. После тестового подсчета всех вершин, чтобы убедиться, что все работает должным образом, я вычислил запрос co_forked - на этот раз без добавления ребер. Файл конфигурации, на который я ссылаюсь в приведенном ниже коде, изменен для использования пространства имен github_graph (см. Conf / read-cassandra-3.properties).

// Use SparkGraphComputer
//
:plugin use tinkerpop.hadoop
:plugin use tinkerpop.spark
// I edited the keyspace in this file to github_graph
olap_graph = GraphFactory.open('conf/hadoop-graph/read-cassandra-3.properties')
// Get a graph traverser
olap_g = olap_graph.traversal().withComputer(SparkGraphComputer)
// Test things out with a vertex count
assert(olap_g.V().count().next() == 8139595)
// Add co-forked edges between nodes
edgePairs = olap_g.V().
  hasLabel('repo').
  store('x').
  as('repo1').
  in('forked').
  out('forked').
  where(without('x')).
  as('repo2')

Затем я создал новый экземпляр графа OLTP и соответствующий обход. Затем я мог бы выполнить итерацию набора результатов графа OLAP, используя новый обход OLTP для поиска вершин по имени, а затем добавить ребро между ними. Это не идеально, потому что в нем используются два API-интерфейса Gremlin, которые нельзя смешивать, но это единственное решение, которое я смог найти благодаря природе GraphComputer только для чтения. (Снова посмотрите create_co_forks.groovy).

// Setup our OLTP graph instance and OLTP traversal
oltp_graph = JanusGraphFactory.open("conf/janusgraph-cassandra-es.properties")
oltp_g = oltp_graph.traversal()
for(edgePair : egdePairs) {
  repo1 = oltp_g.V().has('repoName', edgePair.repo1).next()
  repo2 = oltp_g.V().has('repoName', edgePair.repo2).next()
  repo1.addEdge("co_forked", repo2)
  graph.tx().commit()
  print("-")
}

Обратите внимание, что этот запрос занял ДОЛГОЕ время. С ночевкой.

Для этого необходимо установить и Hadoop, и Spark, что увеличивает сложность этого поста: (К счастью, у меня есть инструкции, как это сделать, как часть сценариев установки AWS для моей книги Agile Data Science 2.0, которые вы можете адаптировать для с этой целью.



Вывод

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

В нашем следующем посте мы создадим вычисление оценки центральности для каждого узла, используя наши co_forked ребер, а затем оценим нашу метрику по звездному рейтингу. Вы заметите, что Гремлин чрезвычайно силен ... но чрезвычайно плотен. Я использовал его время от времени годами, и до сих пор не могу свободно владеть языком. Не думаю, что проблема полностью во мне :)