2016-11-11 2 views
0

Я пытаюсь внедрить базовую систему рекомендаций на Neo4j. В принципе, пользователи и художники любят пользователей. Я хотел бы запросить «пользователей, которым нравился рисовый рис, также понравились эти художники». Это легко со следующими:Необычно распространенная рекомендация по Neo4j с Cypher

MATCH (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->(n2:Artist {artist_name: "damien rice"}) 
RETURN n.artist_name, COUNT(n) AS COUNT 
ORDER BY COUNT DESC 
LIMIT 30 

Хотя этот подход отчасти верно, то возвращается Coldplay, The Beatles (пользователи, которые пользуются популярностью для всех) следующим образом:

n.artist_name  COUNT 
coldplay    6193 
radiohead   5377 
the beatles   3998 
death cab for cutie 3647 
muse     3252 
the killers   3064 
jack johnson   2966 

Я, как правило, чтобы выяснить необычно общий предложения. Мой намеченный подход состоит в том, чтобы дать оценку для coldplay с вычислением (6193/totalNumberOfLikesForColdplay). Например, если всего 61930 человек понравилось coldplay, то у него есть оценка 9163/91630 = 0,1, и я хочу отсортировать всех исполнителей в зависимости от этого балла.

Я попытался следующие:

MATCH (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->(n2:Artist {artist_name: "damien rice"}) 
MATCH (n2:Artist {artist_name: "damien rice"})<-[:LIKES]-(p2:Person) 
RETURN n.artist_name, COUNT(n)/COUNT(n2) AS SCORE 
ORDER BY SCORE DESC 
LIMIT 30 

Но навсегда Наталья принимает. Какой запрос я должен ввести, чтобы получить результат наиболее эффективным способом?

Редактировать: Я только понял, что запрос, который я пробовал выше, не то, что я хочу. Он вычисляет numberOfPeopleBothLikedColdplay_DamienRice/numberOfPeopleLikedDamienRice numberOfPeopleBothLikedTheBeatles_DamienRice/numberOfPeopleLikedDamienRice и так далее

Однако я хочу, чтобы вычислить numberOfPeopleBothLikedColdplay_DamienRice/numberOfPeopleLikedColdplay numberOfPeopleBothLikedTheBeatles_DamienRice/numberOfPeopleLikedTheBeatles ...

Так может быть, я t может быть обновлен как

MATCH (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->(n2:Artist {artist_name: "damien rice"}) 
MATCH (n2:Artist {artist_name: n.name})<-[:LIKES]-(p2:Person) 
RETURN n.artist_name, COUNT(p)/COUNT(p2) AS SCORE 
ORDER BY SCORE DESC 
LIMIT 30 

Но теперь он возвращает меня «(нет строк)».

Edit2: Как предполагается, я обновил запрос следующим образом:

MATCH (p2:Person)-[:LIKES]->(n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]-> 
    (n2:Artist {artist_name: "damien rice"}) 
RETURN n.artist_name, COUNT(p)/COUNT(p2) AS SCORE 
ORDER BY SCORE DESC 
LIMIT 30 

Но он по-прежнему работает вечно. Кстати, у меня 292516 художников, 359347 человек, 17549962 ЛЮБЛЮ отношения между художником и людьми. И вы можете предположить: Лицо может понравиться только одному художнику: только один: Лицам может понравиться: Художники

+0

Если вы хотите рассчитать количество понравившихся вам, вы должны посчитать «лиц», которые понравились художнику, т. Е. Использовать «COUNT (p)/COUNT (p2)». –

ответ

0

Есть некоторые улучшения, которые мы можем сделать здесь.

Полезно понять, почему ваш запрос может занять так много времени. Напомним, что Neo4j возвращает количество строк столбцов данных, и оно создается по мере продвижения по вашему запросу. После вашего второго матча создаются строки, состоящие из n2, и каждая комбинация человека, которая любит n2 с каждым человеком, который любит n2 (так как ваш второй матч создает декартовую продукцию на этом же наборе людей) со всеми другими художниками понравились этим людям. Это очень неэффективный запрос (n^2, по крайней мере, по сложности), и ожидаемое длительное или никогда не заканчивающееся время выполнения.

Так что давайте исправим это.

Во-первых, мы можем избавиться от второго матча полностью для расчета количества понравившихся для n2. Вместо этого (при условии, что: Лицу может нравиться только один: Художник один раз, и это только: Лицам может понравиться: Художники), мы можем подсчитать количество: LIKES отношений напрямую. Переупорядочивая это сначала, мы также гарантируем, что эта операция выполняется только один раз для одной строки данных, а не для дублирования для большого количества строк. Затем мы можем запустить первый MATCH.

MATCH (n2:Artist {artist_name: "damien rice"}) 
WITH n2, SIZE((n2)<-[:LIKES]-()) as n2Likes 
MATCH (n:Artist)<-[:LIKES]-()-[:LIKES]->(n2) 
WITH n, toFloat(COUNT(n))/n2Likes AS SCORE 
ORDER BY SCORE DESC 
LIMIT 30 
RETURN n.artist_name, SCORE 

EDIT для уточнения требований. Кроме того, измененные запросы для использования значений float для count, поэтому итоговый результат - десятичный, а не int.

Мы можем использовать аналогичный подход получения SIZE() подобных каждому художнику.

MATCH (n:Artist)<-[:LIKES]-()-[:LIKES]->(n2:Artist {artist_name: "damien rice"}) 
WITH n, toFloat(COUNT(n)) as likesBothCnt 
WITH n, likesBothCnt, SIZE(()-[:LIKES]->(n)) as likesArtist 
WITH n, likesBothCnt/likesArtist as SCORE 
ORDER BY SCORE DESC 
LIMIT 30 
RETURN n.artist_name, SCORE 

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

+0

Спасибо за ваш ответ @InverseFalcon, ваш запрос довольно быстрый, однако я только что сделал редактирование на вопрос, не могли бы вы взглянуть на него? –

+0

В зависимости от вашего предложения и моего редактирования я обновил запрос следующим образом: MATCH (p2: Person) - [: LIKES] -> (n: Artist) <- [: LIKES] - (p: Person) - [ : ЛЮБИТ] -> (п2: Исполнитель {ARTIST_NAME: "Дэмиен риса"}) ВОЗВРАТ n.artist_name, COUNT (р)/COUNT (р2) КАК СЧЕТ ORDER BY DESC SCORE ПРЕДЕЛ 30 Но все еще работает навсегда –

+0

Обновлен мой ответ в соответствии с новыми требованиями. – InverseFalcon

0

Есть ли причина для использования двух отдельных статей MATCH? Использование двух предложений MATCH имеет другую семантику, чем использование одного, см. Примечания в документации Cypher по адресу uniqueness. В текущем случае использование двух статей MATCH позволяет p2 принять такое же значение, как p.

MATCH 
    (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]-> 
    (n2:Artist {artist_name: "damien rice"})<-[:LIKES]-(p2:Person) 
RETURN n.artist_name, COUNT(p)/COUNT(p2) AS SCORE 
ORDER BY SCORE DESC 
LIMIT 30 

Вы также можете повторить эту переменную в том же MATCH п и имеют один и тот же набор результатов.Например:

MATCH 
    (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->(n2:Artist {artist_name: "damien rice"}), 
    (n2)<-[:LIKES]-(p2:Person) 
RETURN n.artist_name, COUNT(p)/COUNT(p2) AS SCORE 
ORDER BY SCORE DESC 
LIMIT 30 
+0

Спасибо за ваш ответ. Однако я только что сделал редактирование на вопрос, не могли бы вы взглянуть на него? –