Случайное блуждание по двудольному графу с Гремлином

Я хотел бы ранжировать элементы в соответствии с предпочтениями пользователя (элементы, которые нравятся пользователю) на основе случайного блуждания по ориентированному двудольному графу с использованием gremlin в Groovy.

Граф имеет следующую базовую структуру:

[User1] --- 'лайки' ---> [ItemA] ‹--- 'лайки' --- [User2] --- 'лайки' ---> [ItemB]

Далее запрос, который я придумал:

def runRankQuery(def userVertex) {
    def m = [:]
    def c = 0
    while (c < 1000) {
        userVertex
            .out('likes')   // get all liked items of current or similar user
            .shuffle[0]     // select randomly one liked item
            .groupCount(m)  // update counts for selected item
            .in('likes')    // get all users who also liked item
            .shuffle[0]     // select randomly one user that liked item
            .loop(5){Math.random() < 0.5}   // follow liked edge of new user (feed new user in loop) 
                                            // OR abort query (restart from original user, outer loop)      
            .iterate()
        c++
    }
    m = m.sort {a, b -> b.value <=> a.value}
    println "intermediate result $m"
    m.keySet().removeAll(userVertex.out('likes').toList())
    // EDIT (makes no sense - remove): m.each{k,v -> m[k] = v / m.values().sum()}
    // EDIT (makes no sense - remove): m.sort {-it.value }
    return m.keySet() as List;
}

Однако этот код не находит новые элементы ([ItemB] в примере выше), а только понравившиеся элементы данного пользователя (например, [ItemA]).

  • Что мне нужно изменить, чтобы скормить новому пользователю (например, [User2]) шаг цикла назад к шагу 'out (' like ')', чтобы продолжить прогулку?

  • Как только этот код заработает, можно ли его рассматривать как реализацию «Персонализированного рейтинга страниц»?


Вот код для запуска примера:

g = new TinkerGraph()

user1 = g.addVertex()
user1.name ='User1'
user2 = g.addVertex()
user2.name ='User2'
itemA = g.addVertex()
itemA.name ='ItemA'
itemB = g.addVertex()
itemB.name ='ItemB'

g.addEdge(user1, itemA, 'likes')
g.addEdge(user2, itemA, 'likes')
g.addEdge(user2, itemB, 'likes')

println runRankQuery(user1)

И вывод:

intermediate result [v[2]:1000]
[]
==>null
gremlin> g.v(2).name
==>ItemA
gremlin> 

person Faber    schedule 16.07.2014    source источник


Ответы (1)


Я обнаружил, что это действительно странная проблема. Я обнаружил несколько очень странных проблем, которые нелегко объяснить, и, в конце концов, я не уверен, почему они такие, какие есть. Две большие вещи, которые мне кажутся странными:

  1. Я не уверен, есть ли проблема с шагом shuffle. В вашем случае здесь не похоже, что это рандомизируется должным образом. Кажется, я не могу воссоздать проблему вне этого случая, поэтому я не уверен, связано ли это как-то с размером ваших данных или чем-то еще.
  2. У меня возникли странные проблемы с использованием Math.random() для выхода из цикла.

В любом случае, я думаю, что я уловил суть вашего кода здесь с моими изменениями, которые, кажется, делают то, что вы хотите:

runRankQuery = { userVertex ->
    def m = [:]
    def c = 0
    def rand = new java.util.Random()
    while (c < 1000) {
        def max = rand.nextInt(10) + 1
        userVertex._().as('x')
            .out('likes')   
            .gather.transform{it[rand.nextInt(it.size())]}
            .groupCount(m) 
            .in('likes')    
            .gather.transform{it[rand.nextInt(it.size())]}
            .loop('x'){it.loops < max}  
            .iterate()
        c++
    }
    println "intermediate result $m"
    m.keySet().removeAll(userVertex.out('likes').toList())
    m.each{k,v -> m[k] = v / m.values().sum()}
    m.sort {-it.value }
    return m.keySet() as List;
}

Я заменил shuffle на мою собственную марку «перемешивания», случайным образом выбрав одну вершину из собранного списка. Я также случайно выбрал max петель, а не полагался на Math.random(). Когда я запускаю это сейчас, я думаю, что получаю те результаты, которые вы ищете:

gremlin> runRankQuery(user1)                                       
intermediate result [v[2]:1787, v[3]:326]
==>v[3]
gremlin> runRankQuery(user1)
intermediate result [v[2]:1848, v[3]:330]
==>v[3]
gremlin> runRankQuery(user1)
intermediate result [v[2]:1899, v[3]:339]
==>v[3]
gremlin> runRankQuery(user1)
intermediate result [v[2]:1852, v[3]:360]
==>v[3]

Вы еще можете заставить Math.random() работать, так как он в некоторых итерациях работы с этим вел себя для меня предсказуемо.

person stephen mallette    schedule 17.07.2014
comment
Большое спасибо за ваше решение, Стивен. Он как бы делает то, что я хочу. Однако ваши изменения оставляют у меня ряд других вопросов: 1) Зачем нам нужен ._()? 2) Итак, проблема с shuffle ошибкой, которую нужно где-то отслеживать? 3) Почему после gather не требуется scatter? - person Faber; 17.07.2014
comment
4) Если я изменю команду loop на .loop('x'){println "gremlinLoopCount: ${it.loops} / $max"; it.loops < max}, я увижу, что счетчик циклов всегда начинается с 3 (!) И впоследствии увеличивается на +2. Я даже вижу такие результаты, как gremlinLoopCount: 3 / 0. Как такое возможно? 5) Итак, если мы перейдем от переменной длины прогулки (Math.random() < 0.5) к фиксированному количеству шагов (it.loops < max), может ли алгоритм по-прежнему рассматриваться как случайное блуждание с телепортацией / перезапуском? Я так не думаю. - person Faber; 17.07.2014
comment
Я просто использовал _(), чтобы отметить шаг, к которому нужно вернуться, с помощью x. Я не думал, что можно использовать as сразу с вершины. Я не могу воспроизвести проблему shuffle, поэтому я не совсем уверен, что это ошибка. Если у вас есть шаги воспроизведения с более простым случаем, вы можете создать проблему в Pipes. Я случайным образом выбираю один элемент из канала, поэтому я развернул List в transform. loop сначала в ширину, поэтому нельзя ожидать, что println it.loop будет печатать что-либо по порядку. Может показаться, что он увеличивается вдвое, но это не так. - person stephen mallette; 17.07.2014
comment
Также обратите внимание, что счет технически начинается с 2 - я думаю, вы можете подумать, что он отключен на единицу. Это исправлено в TinkerPop3. Технически это означает, что мы должны видеть результат, который всегда начинается с 2, чего, я должен признать, я не вижу в данный момент - нужно еще немного исследовать. - person stephen mallette; 17.07.2014
comment
Я думаю, что 0 - допустимое значение для max, так как rand должен дать вам значение от 0 до 9 (10 является исключительным). Редактирую свой ответ, чтобы добавить 1. Не уверен, что использование этого подхода нарушает классическую концепцию случайного блуждания. Это зависит от вас :) Вы можете попробовать снова использовать rand var, чтобы выйти из цикла. Я просто знаю, что у меня были с этим проблемы, которые я не исследовал дальше. Возможно, мои проблемы были связаны с shuffle. - person stephen mallette; 17.07.2014