neo4j - recomendacion - filtrado colaborativo python
Cifrado de filtrado colaborativo con atributos en neo4j (2)
Estoy usando neo4j para configurar un sistema de recomendación. Tengo la siguiente configuración:
Nodos:
- Usuarios
- Películas
- Atributos de la película (por ejemplo, género)
Relaciones
(m:Movie)-[w:WEIGHT {weight: 10}]->(a:Attribute)
-
(u:User)-[r:RATED {rating: 5}]->(m:Movie)
Aquí hay un diagrama de cómo se ve:
Ahora estoy tratando de descubrir cómo aplicar un esquema de filtrado colaborativo que funciona de la siguiente manera:
- Comprueba qué atributos le han gustado al
user
(implícitamente por darle me gusta a las películas) - Encuentre
other users
similares aother users
que les hayan gustado estos atributos similares - Recomiende las mejores películas al
user
, que el usuario NO ha visto, pero queother users
similares han visto.
La condición es, obviamente, que cada atributo tiene un cierto peso para cada película. Por ejemplo, la adventure
género puede tener un peso de 10
para el Señor de los Anillos pero un peso de 5
para el Titanic.
Además, el sistema debe tener en cuenta las calificaciones de cada película. Por ejemplo, si other user
ha calificado a Lord of the Rings 5
, entonces sus atributos de Lord of Ranges se escalan por 5
y no por 10
. El user
que ha calificado los atributos implícitos también cerca de 5
debería entonces recomendar esta película en lugar de otro usuario que haya calificado atributos similares más altos.
Comencé simplemente recomendando solo otras películas que otros usuarios hayan calificado, pero no estoy seguro de cómo tomar en cuenta las relaciones RATING y WEIGHT. Tampoco funcionó:
MATCH (user:User)-[:RATED]->(movie1)<-[:RATED]-(ouser:User),
(ouser)-[:RATED]->(movie2)<-[:RATED]-(oouser:User)
WHERE user.uid = "user4"
AND NOT (user)-[:RATED]->(movie2)
RETURN oouser
Responda a su primera consulta:
Comprueba qué atributos le han gustado al usuario (implícitamente por darle me gusta a las películas)
MATCH (user:User)
OPTIONAL MATCH (user)-[r:RATED]->(m:movie)
OPTIONAL MATCH (m)-[r:RATED]->(a:Attribute)
WHERE user.uid = "user4"
RETURN user, collect ({ a:a.title })
Es un constructo de subconsulta donde se encuentran las películas clasificadas por el usuario y luego se encuentran los atributos de las películas y, finalmente, se devuelve la lista de atributos preferidos.
puede modificar la declaración de retorno para recopilar (a) como atributos si necesita un nodo completo
Lo que está buscando, matemáticamente hablando, es un índice de Jaccard simplificado entre dos usuarios. Es decir, qué tan similares se basan en cuántas cosas tienen en común. Digo simplificado porque no estamos teniendo en cuenta las películas sobre las que no están de acuerdo. Básicamente, y siguiendo su orden, sería:
1) Obtenga el peso total de cada atributo para cada usuario. Por ejemplo:
MATCH (user:User{name:''user1''})
OPTIONAL MATCH (user)-[r:RATED]->(m:Movie)->[w:WEIGHT]->(a:Attribute)
WITH user, r.rating * w.weight AS totalWeight, a
WITH user, a, sum(totalWeight) AS totalWeight
Necesitamos la última línea porque teníamos una fila para cada combinación de Película-Atributo
2) Luego, obtenemos usuarios con gustos similares. Esta es una zona de peligro de rendimiento, algunos filtros pueden ser necesarios. Pero al forzarlo brutalmente, obtenemos usuarios que les gusta cada atributo dentro de un error del 10% (por ejemplo)
WITH user, a, totalWeight*0.9 AS minimum, totalWeight*1.10 AS maximum
MATCH (a)<-[w:WEIGHT]-(m:Movie)<-[r:RATES]-(otherUser:User)
WITH user, a, otherUser
WHERE w.weight * r.rating > minimum AND w.weight * r.rating < maximum
WITH user, otherUser
Entonces ahora tenemos una fila (única debido a la última línea) con cualquier otro usuario que coincida. Aquí, para ser honesto, necesitaría tratar de estar seguro de si se incluirían otros usuarios con solo 1 coincidencia de género ... si es así, se necesitaría un filtro adicional. Pero creo que eso debería ir después de que esto funcione.
3) Ahora es fácil:
MATCH (otherUser)-[r:RATES]->(m:Movie)
WHERE NOT (user)-[:RATES]->(m)
RETURN m, sum(r.rating) AS totalRating ORDER BY totalRating DESC
Como se mencionó anteriormente, la parte difícil es 2), pero después de que sepamos cómo hacer que las matemáticas funcionen, debería ser más fácil. Ah, y sobre matemáticas, para que funcione correctamente, el peso total de una película debe sumar 1 ( normalizar ). En cualquier otro caso, la diferencia entre el peso total de las películas provocaría una comparación injusta.
Escribí esto sin un estudio adecuado (papel, lápiz, ecuaciones, estadísticas) y probando el código en un conjunto de datos de muestra. ¡Espero que pueda ayudarte de todos modos!
En caso de que quiera esta recomendación sin tener en cuenta las calificaciones de los usuarios o los pesos de los atributos, debería ser suficiente sustituir las líneas matemáticas en 1) y 2) con solo r.rating o w.weight, respectivamente. Se seguirán utilizando las relaciones RATES y WEIGHTS, por lo que, por ejemplo, un consumidor ávido de películas de Aventura sería recomendado Películas de consumidores de películas de Aventura, pero no modificadas por clasificaciones o por peso de atributo, como elegimos.
EDITAR: Código editado para corregir los errores de sintaxis discutidos en los comentarios.