4 octubre, 2014 Gregorio Mora

Sistemas recomendadores con Apache Mahout

Apache Mahout

En este post incluimos una breve introducción a una de las librerías (software libre) más utilizadas en la actualidad, que proporcionan implementación de algoritmos machine learning: Apache Mahout.

Mahout incluye algoritmos de clasificación, sistemas de recomendación, clustering (agrupación de vectores en base a ciertos criterios).

Desde nuestro punto de vista, es una buena solución para realizar filtrado colaborativo, por ejemplo para la implementar recomendadores pero, el resto de funcionalidades, principalmente las de clasificación y agrupación están muy enfocadas a clasificación/análisis de textos y, se echa en falta la implementación de algoritmos necesarios para soluciones de análisis productivo.

Recomendación basada en filtrado colaborativo

Los algoritmos de filtrado colaborativo, se basan en localizar puntos de similitud entre los elementos, generando conjuntos de elementos «similares». La recomendación se realiza a partir de puntos conocidos en los elementos del conjunto.

En la imagen de la derecha, podemos ver un modelo de datos sencillo, en el que se incluyen las valoraciones que realizan los usuarios sobre ciertos elementos (que podrían ser contenidos, libros, hoteles …).

Un paso importante a la hora de generar los conjuntos de elementos similares, es definir el umbral para considerar que una valoración es similar. Por ejemplo, en el gráfico vemos que el usuario 3 y el 1 han valorado el ítem 4, pero la opinión que tienen del mismo es muy distinta. Para salvar esta situación, definimos por ejemplo un umbral de 0.3, motivo por el cual, se han seleccionado los usuarios que han valorado el ítem 1 con un valor 5.1±03.

Para hacer una pequeña demostración de uso de Apache Mahout, implementaremos ejemplo de recomendación colaborativa (existen otras opciones basadas en contenidos, en utilidad, demográfica o basadas en conocimiento), por ser una de las opciones más fáciles para iniciarse en este tipo de sistemas. En concreto, utilizaremos el algoritmo de lo K vecinos más cercanos (k-NN Nearest Neighbour), que encaja perfectamente en el proceso descrito hasta el momento.

postmahout_imggrupos

Supongamos que queremos incorporar un recomendador para una página de reservas hoteleras, que tomará como parámetro de entrada el nombre de una ciudad (introducida por el usuario en el buscador) y que se basará en las valoraciones de hoteles realizadas por otros usuarios.

Para simplificar el ejemplo, no tendremos en cuenta muchos de los parámetros y fuentes de datos disponibles en este tipo de webs, como por ejemplo, búsquedas y compras anteriores realizadas por el usuario, así como muchos de los parámetros que se ofrecen en los buscadores (nº de habitaciones, tipo de servicios, etc.). En una implementación real, habría que diseñar soluciones para poder realizar recomendaciones de calidad a usuarios nuevos, que han interactuado poco con el sistema y, para los que no se cuenta con información suficiente para generar el conjunto de elementos «similares». Ocurre lo mismo con los nuevos hoteles que sean incorporados (¿cómo recomendar un hotel que nadie ha valorado?).

Para facilitar la prueba, utilizaremos un conjunto de datos bastante reducido. En el siguiente gráfico se muestra el flujo de trabajo esperado con el ejemplo:

usuarios similares

Para la implementación de este ejemplo, crearemos un objeto scala que obtendrá la información de valoraciones de los usuarios y, la información detallada de los hoteles.

 
//Identificador del usuario para el que se solicitan las recomendaciones
val userId: Int = args(1).toInt
//Número de elementos solicitados
val numeroRecomendaciones: Int = args(2).toInt
//Umbral
val threshold: Float = args(3).toFloat
//Localización del hotel
val localizacion: String = args(4)
 
lazy val conf = new SparkConf()

val context = new SparkContext(conf)
	.setMaster("local")
	.setAppName("Recomendador de Hoteles")
	.set("spark.executor.memory", "1g")
	.setJars(Array("hdfs://localhost:8020/apps/lib/spark-assembly-1.1.0-hadoop2.4.0.jar"))

//Fichero con las valoraciones de los usuarios
val modelo: DataModel = new FileDataModel(new File("/tmp/valoraciones.csv"))

/*
 * Al tratarse de información cuantitativa, utilizamos el coeficiente de correlación de Pearson
 * como medida de similaridad.
 */			
val similarity: UserSimilarity = new CachingUserSimilarity(new PearsonCorrelationSimilarity(modelo), modelo)

/*
 * Obtenemos el conjunto de usuarios que ha valorado algunos de los hoteles valorados por el usuario
 * para el que se han solicitado las recomendaciones y, cuyas valoraciones están dentro del umbral
 * especificado.
 */			
val usuariosSimilares: UserNeighborhood = new ThresholdUserNeighborhood(threshold, similarity, modelo)

val recomendador: UserBasedRecommender = new GenericUserBasedRecommender(modelo, usuariosSimilares, similarity)
val recommendaciones: List[RecommendedItem] = recomendador.recommend(userId, numeroRecomendaciones)

/*
 * Información de cada hotel (hotelId, nombre y ubicación) y realizamos el
 * filtro para obtener aquellos que coinciden con la población solicitada
 * por el usuario.
 * 
 * Convertimos cada línea del fichero (un CSV) de hoteles en un 
 * mapa: (K=hotelID,V=(nombre,ubicación))
 */
val hoteles = context.textFile("hdfs://localhost:8020/samples/hoteles")
        .filter(line => line.contains(localizacion))
        .map(line => line.split(","))
	.map(item => (item(0).toInt, (item(1), item(2))))

if (recommendaciones.isEmpty()) {
    println("No existen recomendaciones");
} else {
    for (recomendacion < - recommendaciones) { 	hoteles.filter(_._1 == recomendacion.getItemID()) 	.foreach(hotel => println("Hotel recomendado: " + hotel._2._1 + ", ubicado en: " + hotel._2._2))
    }
}

Ejecutamos el código, solicitando 1 hotel recomendado para el usuario 1, con un umbral de 0.1 y, ubicado en Sevilla:

spark-submit --name "Recomendador Mahout" --class com.metadology.bigdata.core.Launcher ./bigdataSamples_2.10-1.0.0.jar ManoutRec 1 1 0.1 SEVILLA
.
.
.
INFO storage.MemoryStore: ensureFreeSpace(3416) called with curMem=166036, maxMem=278302556
INFO storage.MemoryStore: Block broadcast_1 stored as values in memory (estimated size 3.3 KB, free 265.2 MB)
INFO scheduler.DAGScheduler: Submitting 1 missing tasks from Stage 0 (FilteredRDD[5] at filter at ManoutRec.scala:85)
INFO scheduler.TaskSchedulerImpl: Adding task set 0.0 with 1 tasks
INFO scheduler.TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0, localhost, ANY, 1270 bytes)
INFO executor.Executor: Running task 0.0 in stage 0.0 (TID 0)
INFO executor.Executor: Fetching hdfs://localhost:8020/apps/lib/spark-assembly-1.1.0-hadoop2.4.0.jar with timestamp 1412261626822
INFO executor.Executor: Adding file:/var/folders/ck/qgwmfj951mbdbk22kn8q4zpm0000gn/T/spark-161c1d3d-c140-45b2-b838-73b25fa15e62/sp
INFO rdd.HadoopRDD: Input split: hdfs://localhost:8020/samples/hoteles:0+240
Hotel recomendado: "Sevilla La Posada", ubicado en: "SEVILLA"
INFO scheduler.TaskSchedulerImpl: Removed TaskSet 1.0, whose tasks have all completed, from pool 

Como hemos comentado anteriormente, este ejemplo no pretende ser más que una muestra de las funcionalidades de Mahout. Por lo general, salvo que en procesamiento por lotes, los datos estarían almacenados en bases de datos NoSQL o serían obtenidos desde un stream y, requerirían otro tipo de soluciones de procesamiento distribuido.

Tagged: , ,

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Uso de cookies

Las cookies son importantes para el correcto funcionamiento de este sitio. Para mejorar su experiencia, usamos cookies tanto propias como de terceros con la finalidad de permitir al usuario su navegación en esta página web y el uso de sus servicios así como para recordar las preferencias de usuario y recopilar estadísticas con el fin de optimizar la funcionalidad del sitio. Haga clic en Aceptar y continuar para aceptar las cookies e ir directamente al sitio. política de cookies

ACEPTAR
Aviso de cookies