Big Data: análisis de weblogs

Embed Size (px)

Citation preview

Caso Real Big Data

Anlisis de weblogs de empresa de contenidos multimedia online

Problemtica

Empresa que pretende mejorar su servicio de pelculas online mediante un motor de recomendaciones personalizadas al usuario, teniendo en cuenta su historial de descargas, eventos, etc.

La nica informacin con la que contamos son los logs del ltimo ao en formato JSON

{"created_at": "2013-05-08T08:00:00Z", "payload": {"item_id": "11086", "marker": 3540}, "session_id": "b549de69-a0dc-4b8a-8ee1-01f1a1f5a66e", "type": "Play", "user": 81729334, "user_agent": "Mozilla/5.0 (iPad; CPU OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9A405"}

Requisitos bsicos

El equipo desea:

Conocer cuales son los contenidos consumidos en mayor medida por usuarios jvenes

Identificar patrones de conducta de los usuarios en sus sesiones, con el objetivo de mejorar la experiencia/usabilidad de la pgina web

Aplicar un motor de recomendaciones a su web que ayude a los usuarios a encontrar el contenido que buscan, con el objetivo de que visiten ms el portal

Cmo abordamos el problema?

Como expertos en Big Data, dividimos el problema en:

Clasificacin binariaClsteringAnlisis predictivoSe clasifican los usuarios en dosgrupos (adulto, nio) para ofrecercontenidos acordes al grupo alque pertenecenAgrupacin de usuarios segnsu comportamientoRecomendador de pelculasbasado en la idoneidad de unitem concreto para un usuarioespecfico

Cmo funciona el servicio online?

Debemos entender cual es el funcionamiento natural del servicio ofertado por la empresaEl usuario paga una suscripcin para poder acceder a los contenidos online

Una vez logueado en el sistema, se le presenta al usuario una serie de recomendaciones y contenidos populares

El usuario puede reproducir el contenido online de su cola, buscar nuevos contenidos, y reproducirlos directamente o aadirlos a la cola de reproduccin

La pgina soporta puntuaciones y reviews

El usuario puede realizar otras operaciones como pagos y gestin de contraseas

1. Exploracin de los datos

Explorando los datos

Partimos de los weblogs

Adems, se nos dice que:Los logs contienen eventos de control parental que pueden ser utilizados para identificar si algunas cuentas son utilizadas por adultos o nios

El campo marker indica la posicin del reproductor en el contenido

Las puntuaciones van de 1 a 5 (5 es el mximo)

Identificadores de contenido que contienen una 'e' son series de television ('e' de episode)El nmero que le sigue indica el nmero de episodio

Explorando los datos

Cuando el contenido alcanza la marca end se registra un evento stopSi el usuario abandona la pgina o cambia de contenido puede que no se registre el evento

La mayor parte de los usuarios residen en EEUU

{"auth": "1208d4c:279737f7", "createdAt": "2013-05-12T00:00:11-08:00", "payload": {"itemId"": "3702e4", "marker": 780}, "refId": "7586e549", "sessionID": "d4a244cb-d502-4c94-a80d-3d26ca54a449", "type": "Play", "user": 18910540, "userAgent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.2; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2)"}

Explorando los datos

A parte del Play que hemos visto en el ejemplo, qu otros eventos existen?

Usamos Hadoop streaming para poder pasarle un script que filtre los resultados

grep: filtra los resultados de acuerdo al patrn indicadoE: activa las expresiones regulareso: hace que 'grep' saque nicamente (output) la porcin del input que cumple el patrn

La expresin regular est dividida en 3 partes por el elemento pipe | "\"$1\": [^,]+" $1 hace referencia al argumento 1 que le pasemos (en este caso ser type para comprobar qu eventos existen)cut -d: -f2- cortar delimitando (-d) por ':' el campo 2tr -d '" ' borra (-d) el caracter

#!/bin/bash grep -Eo "\"$1\": [^,]+" | cut -d: -f2- | tr -d '" '

Explorando los datos

Lanzamos el script con Hadoop Streaming

-D: evitamos que grep devuelva 1 si no encuentra nada-stream.non.zero.exit.is.failure=false: Por defecto es true-input: datos de entrada (directorio o ficheros) para el mapper-ouptut: directorio de salida para el reducer-mapper/-reducer: qu ejecutar como map/reduce-file: indica a Hadoop que se asegure de que el fichero es accesible desde todos los nodos que van a ejcutar la tareauniq -c es un reducer que agrega los campos y nos devuelve la cantidad de repeticiones

$ hadoop jar $STREAMING -D stream.non.zero.exit.is.failure=false -input data/heckle/ -input data/jeckle/ -output types -mapper "grep_field.sh type" -file grep_field.sh -reducer "uniq -c"

Filtrado por campo type

Explorando los datos

Visualizamos la salida19 eventos

Antes dijimos que el fichero de salida se iba a llamar type. As es como HDFS gestiona los resultados

Explorando los datos

Visualizamos un ejemplo de weblog de cada eventoSaldrn 19 elementos JSON completos, cada uno con un type diferente

$ hadoop fs -cat types/part\* | awk '{ print $2 }' | xargs -n 1 grep -Rhm 1 data -e

-cat y awk para extraer los type nicosXargs y grep para encontrar una ocurrencia de una lnea que contengan cada tipo nico

Explorando los datos

Comprobamos la estructura de los datos de los que disponemosComparten la misma estructura?Necesitamos un MapReduce que pueda manejar datos JSON

summary_map.py

#!/usr/bin/python import json import sys # Read all lines from stdin for line in sys.stdin: data = json.loads(line) for field in data.keys(): print field

Parseamos el JSONMostramos cada campo

Explorando los datos

Ejecutamos el script

Problema: hay datos con campos que no coinciden

Explorando los datos

Algunos JSON estn mal formados y tienen varias dobles comillas consecutivasArreglamos el script

#!/usr/bin/python import json import sys

for line in sys.stdin: try: data = json.loads(line.replace('""', '"')) for field in data.keys(): print field except ValueError: # Log the error so we can see it sys.stderr.write("%s\n" % line) exit(1)

Explorando los datos

Resultado

Problemas: - Campos iguales con diferente nombre- Campos que no aparecen siempre

Explorando los datos

Volvemos a editar el script para evitar confusionesSimplemente normalizamos los campos problemticos

#!/usr/bin/python import json import sys for line in sys.stdin: try: data = json.loads(line.replace('""', '"')) for field in data.keys(): if field == 'type': print "%s" % (data[field]) else: # Normalize the file name real = field if real == 'user_agent': real = 'userAgent' elif real == 'session_id': real = 'sessionID' elif real == 'created_at' or real == 'craetedAt': real = 'createdAt' # Emit the normalized field print "%s:%s" % (data['type'], real) # Emit all subfields, if there are any if type(data[field]) is dict: for subfield in data[field]: print "%s:%s:%s" % (data['type'], real, subfield) except ValueError: sys.stderr.write("%s\n" % line) exit(1)

Explorando los datos

Volvemos a ejecutar el script

Explorando los datos

Comprobamos...

Ms problemas: - Subcampos con diferente nombre

Explorando los datos

Volvemos a editar el script

...for line in sys.stdin: try: ... elif real == 'created_at' or real == 'craetedAt': real = 'createdAt'

# Emit all subfields, if there are any if type(data[field]) is dict: print "%s:%s" % (data['type'], real) # Normalize and print the subfields for subfield in data[field]: subreal = subfield if subreal == 'item_id': subreal = 'itemId' print "%s:%s:%s\t%s" % (data['type'], real, subreal, data[field][subfield]) else: # Emit the normalized field print "%s:%s\t%s" % (data['type'], real, data[field]) except ValueError: sys.stderr.write("%s\n" % line) exit(1)

Este cambio aborda dos problemas.1. Normaliza los subcampos2. Aade el valor del campo y del subcampo a la salida

Explorando los datos

Tenemos que hacer un resumen de los datos REDUCEResumir los valores de campos y subcampos

Identificar cada valor de campo (numrico, fecha)

Valores categricos (pocas posibilidades para un campo)

Identificadores sin patrn aparente

Explorando los datos

Ver summary_reduce.py

Explorando los datos

Resumen de los datosIdentificadores de usuario entre 1000000 y 1000000

No todos los Item ids son numricos (de lo contrario no apaeceran como identificadores)

La media para los eventos de puntuaciones (Rating) es de 3.5, y para las reviews (WriteReview) de 4

Los timestamps no estn todos en la misma zona. Algunos estn en UTC otros en UTC-8. Puede que haya otros. Tener en cuenta a la hora de limpiar los datos

Account tiene 3 posibles sub-acciones: updatePassword, updatePaymentInfo, parentalControl

Account:playload no hay control parental que deshabilite opciones (extrao)

Play:playload Faltan algunos valores de ytemID y marker (en ambos casos, aparecen 543129)

Explorando los datos

Nmero de usuarios diferentes y sesiones2195 usuarios

5308 sesiones

$ hadoop jar $STREAMING -D stream.non.zero.exit.is.failure=false -input data/heckle/ -input data/jeckle/ -output users -mapper "grep_field.sh user" -file grep_field.sh -reducer 'bash -c "uniq | wc -l"' ... $ hadoop fs -cat users/part\* 2195

$ hadoop jar $STREAMING -D stream.non.zero.exit.is.failure=false -input data/heckle/ -input data/jeckle/ -output sessions -mapper 'grep_field.sh session(ID|_id)' -file grep_field.sh -reducer 'bash -c "uniq | wc -l"' $ hadoop fs -cat sessions/part\* 5308

2. Limpieza de datos

Limpiando los datos

Tenemos que solucionar los problemas que hemos ido identificando en la fase de preparacin

Vamos a reducir el volumen de los datos para hacerlos ms manejables

La idea es tener un nico fichero con un nico registro agregado por reducerMapper

Limpiando los datos

Mapper clean_map.py

Reducer clean_reduce.py (con modificacin de playload por estar vaco)

Limpiamos el directorio (se supone que hemos lanzado antes un reduce con error)

Volvemos a lanzar nuestro MapReduce personalizado

xito! Ahora tenemos un nico fichero limpio y agregado con cada registro conteniendo sesiones completas

Limpiando los datos

Visualizamos el resultado

Qu hemos conseguido?Logs reducidos a 4 MB

3. Clasificando usuarios

3.1 Extrayendo contenido de los items reproducidos

Necesitamos extraer el contenido reproducido, puntuado o revisado (review) por cada usuarioMapper que nos permita agregar por usuario pero tambin diferenciar por fechaSacaremos una clave compuesta que contendr el id del usuario, la fecha de inicio de sesin, y la fecha de fin

ReducerLas claves que recibe el Reducer estn agregadas por usuario y agrupadas por fecha

Tratamiento:Si vemos una etiqueta nunca vista adoptamos la nueva

Si vemos una etiqueta repetida nada cambia

Si vemos una etiqueta conflictiva lo tratamos como un usuario diferente

Si no vemos nunca una etiqueta de un usuario usuario sin etiqueta

Para usuarios etiquetados como adultos aadimos una 'a'

Para usuarios etiquetados como nios aadimos una 'k'

3.1 Extrayendo contenido de los items reproducidos

Mapperkid_map.py

#!/usr/bin/python import json import sys

def main(): # Read all lines from stdin for line in sys.stdin: data = json.loads(line)

# Collect all items touched items = set() items.update(data['played'].keys()) items.update(data['rated'].keys()) items.update(data['reviewed'].keys())

# Generate a comma-separated list if items: itemstr = ','.join(items) else: itemstr = ','

# Emit a compound key and compound value print "%s,%010d,%010d\t%s,%s" % (data['user'], long(data['start']), long(data['end']), data['kid'], itemstr)

if __name__ == '__main__': main()

3.1 Extrayendo contenido de los items reproducidos

ReducerVer kid_reduce.py

3.1 Extrayendo contenido de los items reproducidos

Ejecutamos...

3.2 Preparando el algoritmo SimRank

En nuestro caso tenemos:2000 usuarios que han visto 0 o ms contenidos

8500 items que han sido vistos por 0 o ms usuariosGrafo bipartido

Solucin basada en grafos

Las etiquetas conocidas para los usuarios se propagarn por los items que han visualizado, y de ah a otros usuarios que hayan visualizado el mismo contenidoLo mismo para comentarios y puntuaciones

Progagacin influenciada SimRank

3.2 Preparando el algoritmo SimRank

SimRank necesita una matriz de adyacencia y una lista de nodos

3.3 Construyendo la matriz

Mapper item_map.py

#!/usr/bin/python import sys def main(): # Read all lines from stdin for line in sys.stdin: key, value = line.strip().split('\t') items = value.split(',')

# Emit every item in the set paired with the user ID for item in items: print "%s\t%s" % (item, key) if __name__ == '__main__': main()

3.3 Construyendo la matriz

Reducer item_reduce.py

#!/usr/bin/python import sys def main(): last = None

# Read all lines from stdin for line in sys.stdin: item, user = line.strip().split('\t')

if item != last: if last != None: # Emit the previous key print "%s\t%s" % (last, ','.join(users))

last = item users = set() users.add(user) # Emit the last key print "%s\t%s" % (last, ','.join(users)) if __name__ == '__main__': main()

3.3 Construyendo la matriz

Lanzamos el jobTenemos una matriz de adyacencia que implementa SimRank

3.4 Implementacin de SimRank

Mapper simrank_map.pyLo nico que tiene que hacer es leer el vector SimRank y calcular el producto de la matriz con la matriz de adyacenciaPrimero lee del vector SimRank de HDFS y lo guarda en un diccionario obteniendo las columnas de la matriz de adyacencia como registros de entrada

Para cada entrada distinta de cero en una columna, se multiplica por la correspondiente entrada en el vector SimRank y se emite el resultado con la etiqueta de fila como clave

3.4 Implementacin de SimRank

Mapper simrank_map.py

#!/usr/bin/python import sys

def main(): if len(sys.argv) < 2: sys.stderr.write("Missing args: %s\n" % ":".join(sys.argv)) sys.exit(1)

v = {}

# Read in the vector with open(sys.argv[1]) as f: for line in f: (key, value) = line.strip().split("\t") v[key] = float(value)

# Now read the matrix from the mapper and do the math for line in sys.stdin: col, value = line.strip().split("\t") rows = value.split(',')

for row in rows: try: # Add the product to the sum print "%s\t%.20f" % (row, v[col] / float(len(rows))) except KeyError: # KeyError equates to a zero, which we don't need to output. pass if __name__ == '__main__': main()

3.4 Implementacin de SimRank

Reducer simrank_reduce.pySe suman los valores intermedios de cada fila, se suma la teleport contribution y se emite el resultado final como el valor de la fila en un nuevo vector SimRankEl teleport contribution se obtiene extrayendo del training set

3.4 Implementacin de SimRank

Testeando la convergencia simrank_diff.py

Lanzador simrank.sh

$ chmod a+x simrank.sh $ ./simrank.sh adults_train rm: v: No such file or directory rm: `v': No such file or directory Beginning pass 1 /tmp/hadoop-training/hadoop-unjar2754804698549065604/]... Beginning pass 2...

As hasta 24 iteraciones

Ojo a los permisos de ejecucin

3.4 Implementacin de SimRank

$ hadoop fs -cat simrank24/part\* | head 10081e1 0.00001761487440064474 10081e10 0.00000278115643514088 10081e11 0.00000278115643514088 10081e2 0.00001761487440064474 10081e3 0.00001761487440064474 10081e4 0.00001761487440064474 10081e5 0.00001761487440064474 10081e7 0.00000278115643514088 10081e8 0.00000278115643514088 10081e9 0.00000278115643514088 cat: Unable to write to output stream.

Vector SimRank final

3.4 Implementacin de SimRank

Movemos el resultado a adult_final y repetimos el proceso, esta vez con kids

$ hadoop fs -mv simrank24 adult_final $ hadoop fs -rm -R simrank\*

$ ./simrank.sh kids_train rm: v: No such file or directory rm: `v': No such file or directory Beginning pass 1

$ hadoop fs -mv simrank24 kid_final

As hasta 24 iteraciones

3.4 Implementacin de SimRank

Interpretamos y comparamos los vectores SimRank obtenidos en 2 pasos:

Normalizamos ambos vectores

Comparamos cada entrada y asignamos una etiqueta basada en el valor ms alto

3.4 Implementacin de SimRank

Normalizando vectores

Mapper adult_map.py

#!/usr/bin/python import sys

def main(): if len(sys.argv) < 3: sys.stderr.write("Missing args: %s\n" % sys.argv)

# Calculate conversion factor num_adults = float(sys.argv[1]) num_kids = float(sys.argv[2]) factor = -num_adults / num_kids

# Apply the conversion to every record and emit it for line in sys.stdin: key, value = line.strip().split('\t')

print "%s\t%.20f" % (key, float(value) * factor)

if __name__ == "__main__": main()

3.4 Implementacin de SimRank

Normalizando vectores

Reducer combine_reduce.py

3.4 Implementacin de SimRank

Normalizando vectores

Lanzamos los jobs

$ hadoop fs -cat adults_train | wc -l 84 $ hadoop fs -cat kids_train | wc -l 96 $ hadoop jar $STREAMING -D mapred.reduce.tasks=0 -input adult_final -output adult_mod -mapper "adult_map.py 84 96" -file adult_map.py ... $ hadoop jar $STREAMING -D mapred.textoutputformat.separator=, -input adult_mod -input kid_final -output final -reducer combine_reduce.py -file combine_reduce.py -inputformat org.apache.hadoop.mapred.KeyValueTextInputFormat

Formato separado por comas

3.4 Implementacin de SimRank

Normalizando vectores

Testeamos los resultados Los IDs de los adultos terminan con 'a'

Los IDs de los nios terminan con 'k'

$ hadoop fs -cat final/part\* | grep a, | grep -v ,0 $ hadoop fs -cat final/part\* | grep k, | grep -v ,1

Haciendo grep por cada entrada de adulto ('a') y a continuacin por aquellos que no incluyen el valor ',0' encontramos todas las entradas de adulto mal etiquetadas.Lo mismo para kids

Los comandos no devuelven nada, luego hemos obtenido el resultado esperado

3.4 Implementacin de SimRank

Normalizando vectores

Re-lanzamos el job con datos combinados

$ hadoop fs -rm -R adult_final adult_mod kid_final final simrank\* $ hadoop fs -cat kid/part-\* | cut -f1 | grep a | hadoop fs -put - adults_all $ hadoop fs -cat kid/part-\* | cut -f1 | grep k | hadoop fs -put - kids_all $ ./simrank.sh adults_all $ hadoop fs -mv simrank24 adult_final $ hadoop fs -rm -R simrank\* $ ./simrank.sh kids_all $ hadoop fs -mv simrank24 kid_final $ hadoop fs -cat adults_all | wc -l 104 $ hadoop fs -cat kids_all | wc -l 120 $ hadoop jar $STREAMING -D mapred.reduce.tasks=0 -input adult_final -output adult_mod -mapper "adult_map.py 104 120" -file adult_map.py ... $ hadoop jar $STREAMING -input adult_mod -input kid_final -output final -reducer combine_reduce.py -file combine_reduce.py -inputformat org.apache.hadoop.mapred.KeyValueTextInputFormat $ hadoop fs -getmerge final Task1Solution.csv

3.4 Implementacin de SimRank

Normalizando vectores

Using the scoring methodology from the challenge, this solution scores an accuracy of 99.64%, mislabeling a total of 9 records.

4. Clusterizando sesiones

4. Clusterizando sesiones

El segundo desafo es clusterizar las sesiones de usuario basadas en propiedades (features) en los datos

Mediante el clstering obtenemos qu grupos de sesiones son notablemente ms similares entre ellos que hacia otras sesiones

Sper importante elegir bien las propiedades o features

El anlisis de clster nos aportarEl nmero de grupos naturales existentes en los datos y las propiedades

Qu sesiones pertenecen a cada grupo

4. Clusterizando sesiones

El proceso a seguir para anlisis de clster es bsicamente heurstico (prueba y error)Iremos probando algunas propiedades y acotando aquellas que mejores resultados arrojen

4.1 Eligiendo propiedades

Qu propiedades podemos sacar de los datos disponibles?

$ hadoop fs -cat clean/part\* | head -1 {"session": "2b5846cb-9cbf-4f92-a1e7-b5349ff08662", "hover": ["16177", "10286", "8565", "10596", "29609", "13338"], "end": "1368189995", "played": {"16316": "4990"}, "browsed": [], "recommendations": ["13338", "10759", "39122", "26996", "10002", "25224", "6891", "16361", "7489", "16316", "12023", "25803", "4286e89", "1565", "20435", "10596", "29609", "14528", "6723", "35792e23", "25450", "10143e155", "10286", "25668", "37307"], "actions": ["login"], "reviewed": {}, "start": "1368189205", "recommended": ["8565", "10759", "10002", "25803", "10286"], "rated": {}, "user": "10108881", "searched": [], "popular": ["16177", "26365", "14969", "38420", "7097"], "kid": null, "queued": ["10286", "13338"], "recent": ["18392e39"]} cat: Unable to write to output stream.

Propiedades extrables:Actions (a feature for each, except login and logout)

Number of items hovered over

Session duration (end)

Number of items played

Number of items browsed

Number of items reviewed

Number of items rated

Number of items searched

Number of recommendations that were reviewed

Kid (parental controls)

Number of items queued

4.1 Eligiendo propiedades

Podemos calcular otras propiedades menos directas:Mean play time

Shortest play time

Longest play time

Total play time

Total play time as fraction of session duration

Longest play: less than 5 minutes, between 5 and 60 minutes, more than 60 minutes

Shortest play: less than 5 minutes, between 5 and 60 minutes, more than 60 minutes

Number of items played less than 5 minutes

Number of items played more than 60 minutes

Number of items hovered over that were played

Number of browsed items that were played

Number of reviewed items that were played

Number of recommended items that were played

Number of rated items that were played

Number of searched items that were played

Number of popular items that were played

Number of queued items that were played

Number of recent items that were played

Number of recent items that were reviewed

Number of recent items that were rated

4.2 Fusionando los datos

Tenemos que parsear los datos y generar los vectores de propiedadesUsaremos Python de nuevo

Vamos a volver a unir los datos de sesin separados en el apartado anterior

4.2 Fusionando los datos

Para volver a fusionar los datos:Mapper merge_map.py

#!/usr/bin/python import re import sys

def main(): p = re.compile('.*"session": "([^"]+)".*')

# Read all the lines from stdin for line in sys.stdin: m = p.match(line)

if m: print "*%s*\t*%s*" % (m.group(1), line.strip()) else: sys.strerr.write("Failed to find ID in line: *%s*" % line)

if __name__ == '__main__': main()

Utilizamos una expresin regular en lugar de parsear el JSON porque la expresin consume menos recursos

4.2 Fusionando los datos

Para volver a fusionar los datos:Reducer merge_reduce.py

4.2 Fusionando los datos

Lanzamos el job

$ hadoop jar $STREAMING -mapper merge_map.py -file merge_map.py -reducer merge_reduce.py -file merge_reduce.py -input clean -output merged

4.3 Generamos los vectores de prop

Mapper features_map.py

#!/usr/bin/env python import sys import json

def main(): # Read all lines from stdin for line in sys.stdin: session = json.loads(line) fields = []

fields.append(session['session']) fields.append('updatePassword' in session['actions']) fields.append('updatePaymentInfo' in session['actions']) fields.append('verifiedPassword' in session['actions']) fields.append('reviewedQueue' in session['actions']) fields.append(session['kid'])

played = set(session['played'].keys()) fields.append(len(played)) print ','.join(map(str, fields)) if __name__ == '__main__': main()

4.3 Generamos los vectores de prop

Lo lanzamos

$ hadoop jar $STREAMING -D mapred.reduce.tasks=0 -D mapred.textoutputformat.separator=, -D stream.map.output.field.separator=, -mapper features_map.py -file features_map.py -input merged -output features0 ... $ hadoop fs -getmerge features0 features.csv $ hadoop fs -put features.csv

4.4 Construimos el workflow de ML de Cloudera

Generamos el fichero de cabecerasheaders.csv

session_id,identifier updatePassword,categorical updatePaymentInfo,categorical verifiedPassword,categorical reviewedQueue,categorical kid,categorical num_plays

4.4 Construimos el workflow de ML de Cloudera

Creamos el fichero de resumen utilizando las cabeceras generadas

Normalizamos las caractersticas/propiedades2 opciones: unit normal o rango

Salida en Avro porque vamos a acceder a los datos normalizados programticamente

$ ml summary --summary-file summary.json --header-file header.csv --format text --input-paths features.csv

$ ml normalize --summary-file summary.json --format text --id-column 0 --transform Z --input-paths features.csv --output-path part2normalized --output-type avro

4.5 K-means++ sketch

Utilizando ls datos normalizados

$ ml ksketch --format avro --input-paths part2normalized --output-file part2sketch.avro --points-per-iteration 1000 --iterations 10 --seed 1729

4.5 K-means++ sketch

Especificamos la semilla (seed)De esta forma podemos comparar los resultados a travs de mltiples ejecucionesSin una semilla en cada ejecucin se seleccionara un conjunto de centroides aleatorios, produciendo resultados que podran ser arbitrariamente mejores o peores que anteriores ejecuciones

4.5 K-means++ sketch

Especificamos la semilla (seed)Especificamos la semilla para poder comparar los resultados y ejecutamos el algoritmo paralelo k-means en los sketch de datos

$ ml kmeans --input-file part2sketch.avro --centers-file part2centers.avro --clusters 40,60,80,100,120,140,160,180,200 --best-of 3 --seed 1729 --num-threads 1 --eval-details-file part2evaldetails.csv --eval-stats-file part2evalstats.csv

La lista de clsters indica los diferentes tamaos de clster que va a intentar el algoritmo

El parmetro best-of le indica a Cloudera ML que ejecute el algoritmo 3 veces por cada tamao del clster

Indicamos 1 nico hilo, puesto que la MV est configurada con un procesador nicamente

La ejecucin puede tardar bastante, mejor asignar a la MV otro procesador y poner 2 hilos en paralelo

4.5 K-means++ sketch

Analizamos el resultadoBuscamos una predictive strength de al menos 0.8 con buenos nmeros de estabilizacin

Predective strength es una mtrica que indica cunto de bien describen los datos los clsters

Las mtricas de estabilidad indican cunto cambian los clsters y los puntos de datos entre los datos de entrenamiento y los de testNos da 1.0, lo cual es perfecto

4.5 K-means++ sketch

Aadimos caractersticas al mapper

#!/usr/bin/env python

import sys import json

def main(): # Read all lines from stdin for line in sys.stdin: session = json.loads(line) fields = []

fields.append(session['session'])

fields.append('updatePassword' in session['actions']) fields.append('updatePaymentInfo' in session['actions']) fields.append('verifiedPassword' in session['actions']) fields.append('reviewedQueue' in session['actions']) fields.append(session['kid'])

session_duration = (long(session['end']) - long(session['start'])) fields.append(session_duration)

played = set(session['played'].keys()) browsed = set(session['browsed']) hovered = set(session['hover']) queued = set(session['queued']) recommendations = set(session['recommendations']) rated = set(session['rated'].keys()) reviewed = set(session['reviewed'].keys()) searched = set(session['searched']) fields.append(len(played)) fields.append(len(browsed)) fields.append(len(hovered)) fields.append(len(queued)) fields.append(len(recommendations)) fields.append(len(rated)) fields.append(len(reviewed)) fields.append(len(searched))

print ','.join(map(str, fields))

if __name__ == '__main__': main()

4.5 K-means++ sketch

Aadimos caractersticas a las cabeceras

session_id,identifier updatePassword,categorical updatePaymentInfo,categorical verifiedPassword,categorical reviewedQueue,categorical kid,categorical session_durationnum_plays num_browsednum_hoverednum_queuednum_recommendationsnum_ratednum_reviewednum_searched

4.5 K-means++ sketch

Ejecutamos...

$ hadoop jar $STREAMING -D mapred.reduce.tasks=0 -D mapred.textoutputformat.separator=, -D stream.map.output.field.separator=, -mapper features_map.py -file features_map.py -input merged -output features1 ... $ hadoop fs -getmerge features1 features.csv $ hadoop fs -rm features.csv Deleted features.csv $ hadoop fs -put features.csv $ ml summary --summary-file summary.json --header-file header.csv --format text --input-paths features.csv ... $ ml normalize --summary-file summary.json --format text --id-column 0 --transform Z --input-paths features.csv --output-path part2normalized --output-type avro ... $ ml ksketch --format avro --input-paths part2normalized --output-file part2sketch.avro --points-per-iteration 1000 --iterations 10 --seed 1729 ... $ ml kmeans --input-file part2sketch.avro --centers-file part2centers.avro --clusters 40,60,80,100,120,140,160,180,200 --best-of 3 --seed 1729 --num-threads 1 --eval-details-file part2evaldetails.csv --eval-stats-file part2evalstats.csv

4.5 K-means++ sketch

Analizamos el resultadoTenemos una predective strength muy bajaInfinity es malo

Esto indica que los clsters no son suficientemente diferentes

Prueba y error!!Quitamos algunas caractersticas aadidas en el paso anterior y volvemos a probar

4.5 K-means++ sketch

Comentamos caractersticas del mapper

#!/usr/bin/env python

import sys import json

def main(): # Read all lines from stdin for line in sys.stdin: session = json.loads(line) fields = []

fields.append(session['session'])

fields.append('updatePassword' in session['actions']) fields.append('updatePaymentInfo' in session['actions']) fields.append('verifiedPassword' in session['actions']) fields.append('reviewedQueue' in session['actions']) fields.append(session['kid'])

# session_duration = (long(session['end']) - long(session['start'])) # fields.append(session_duration)

played = set(session['played'].keys()) # browsed = set(session['browsed']) # hovered = set(session['hover']) # queued = set(session['queued']) recommendations = set(session['recommendations']) rated = set(session['rated'].keys()) reviewed = set(session['reviewed'].keys()) searched = set(session['searched']) fields.append(len(played)) # fields.append(len(browsed)) # fields.append(len(hovered)) # fields.append(len(queued)) fields.append(len(recommendations)) fields.append(len(rated)) fields.append(len(reviewed)) fields.append(len(searched))

print ','.join(map(str, fields))

if __name__ == '__main__': main()

4.5 K-means++ sketch

Eliminamos algunas caractersticas del fichero de cabeceras

session_id,identifier updatePassword,categorical updatePaymentInfo,categorical verifiedPassword,categorical reviewedQueue,categorical kid,categorical num_plays num_recommendations num_rated num_reviewed num_searched

4.5 K-means++ sketch

Ejecutamos de nuevo...

$ hadoop jar $STREAMING -D mapred.reduce.tasks=0 -D mapred.textoutputformat.separator=, -D stream.map.output.field.separator=, -mapper features_map.py -file features_map.py -input merged -output features2 ... $ hadoop fs -getmerge features2 features.csv $ hadoop fs -rm features.csv Deleted features.csv $ hadoop fs -put features.csv $ ml summary --summary-file summary.json --header-file header.csv --format text --input-paths features.csv ... $ ml normalize --summary-file summary.json --format text --id-column 0 --transform Z --input-paths features.csv --output-path part2normalized --output-type avro ... $ ml ksketch --format avro --input-paths part2normalized --output-file part2sketch.avro --points-per-iteration 1000 --iterations 10 --seed 1729 ... $ ml kmeans --input-file part2sketch.avro --centers-file part2centers.avro --clusters 40,60,80,100,120,140,160,180,200 --best-of 3 --seed 1729 --num-threads 1 --eval-details-file part2evaldetails.csv --eval-stats-file part2evalstats.csv

4.5 K-means++ sketch

Analizamos el resultadoMejores resultados que antes

Podemos probar a volver a aadir alguna de las caractersticas que hemos borrado en el paso anterior

5. Prediciendo las valoraciones de los usuarios
(construyendo un recomendador)

5. Recomendador

Tenemos que predecir valoraciones para un conjunto dado de usuarios e items

Qu opciones tenemos?Construirnos nuestro recomendador matemticamente

Taste (Mahout) para experimentar con sus algoritmos

5. Recomendador

Revisando los datos tenemos:926 valoracionesDe 751 usuarios sobre 757 items

Unos 500.000 eventos que pueden considerarse como PlayEl propio Play

Aadir a la cola de reproduccin

Si los sumamos tenemos 2193 usuarios y 6504 items reproducidos

Vamos a trabajar nicamente con las valoraciones explcitas e implcitas

5.1 Preparando los datos

Mahout solo acepta entradas en .csvVamos a necesitar 2 entradasValoraciones explcitas

Valoraciones implcitas Eventos tipo play para cada usuario e item

Generamos los ficheros (3 opciones):

JSON loader en Pig para cargar los datos y extraer los campos correctos

Definir una tabla Hive usando JSON SerDe y seleccionar los campos deseados de la tabla

Tarea de streaming map-only en Hadoop

Es la opcin ms sencilla por la facilidad de parsear JSON en Python sin necesidad de definir un schema

5.1 Preparando los datos

Mapper para datos explcitosRecorremos todas las valoraciones y extraemos el userID, itemID y tripleta de valoracin y las emitimos si cumplen el criterio dado

#!/usr/bin/python

import datetime import dateutil.parser import json import sys

def main(): before = sys.argv[1] == 'before' cutoff = dateutil.parser.parse(sys.argv[2]) epoch = datetime.datetime(1970,1,1,tzinfo=cutoff.tzinfo)

# Read all lines from stdin for line in sys.stdin: data = json.loads(line) start = cutoff - datetime.timedelta(seconds=long(data['start']))

if (before and start > epoch) or (not before and start create external table explicit_train (user int, item bigint, rating int) row format delimited fields terminated by ',' location '/user/cloudera/explicit_train_clean'; OK Time taken: 0.215 seconds hive> create external table explicit_test (user int, item bigint, rating int) row format delimited fields terminated by ',' location '/user/cloudera/explicit_test_clean'; OK Time taken: 0.191 seconds

5.2 Establecer los promedios base

Una vez tenemos las tablas, podemos utilizar las funciones matemticas de Hive para calcular el promedio y el RMSE (root-mean-square-deviation)

hive> select avg(rating) from explicit_train; ... OK 3.597826086956522 Time taken: 6.132 seconds hive> select sqrt(sum(pow(rating - 3.597826086956522, 2))/count(*)) from explicit_test; ... OK 1.2733209628271343 Time taken: 4.222 seconds

5.2 Establecer los promedios base

Comparamos el promedio con las puntuaciones de los usuariosEl promedio global es muy superior a los promedios de los usuarios datos muy dispersos

hive> create table baseline (user int, item bigint, rating float) row format delimited fields terminated by ','; OK Time taken: 1.099 seconds hive> insert into table baseline select explicit_test.user, explicit_test.item, if (avg.avg_rating > 0, avg.avg_rating, 3.597826086956522) from (select user, avg(rating) as avg_rating from explicit_train group by user) avg full outer join explicit_test on explicit_test.user == avg.user; ... OK Time taken: 7.462 seconds hive> select sqrt(sum(pow(e.rating - b.rating, 2))/count(*)) from baseline b join explicit_test e on b.user == e.user and b.item == e.item; OK 1.362417948479424 Time taken: 4.768 seconds

5.3 Recomendadores de Mahout

Dos clases de recomendadores en Mahout:Basados en similaridadEl algoritmo determina la similaridad entre usuarios e items y estima valoraciones desconocidas basndose en esas similitudes y valoraciones conocidas

Factorizacin matricial

Cual escogemos?Prueba y error

Se puede utilizar la lnea de comandos, pero es ms recomendable usar el API Java de Mahout

5.3 Recomendadores de Mahout

Trabajamos en local Nos descargamos los datos

$ hadoop fs -getmerge implicit_clean implicit.csv $ hadoop fs -cat explicit_train_clean/part\* explicit_test_clean/part\* > explicit.csv

Con merge volvemos a juntar ambos conjuntos de datos (train y test)

5.3 Recomendadores de Mahout

Preparamos el proyecto Maven

Configuramos el pom.xml

$ mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=ccp.challenge1.recommend -DartifactId=recommend

Debemos tener especial cuidado en donde se quedan descargados los csv, ya que la clase Java que los acceder hace un ../fichero.csv

final DataModel ratingDataModel = new FileDataModel(new File("../explicit.csv"));final DataModel allDataModel = new FileDataModel(new File("../implicit.csv"));

org.apache.mahout mahout-core 0.7

org.apache.mahout mahout-math 0.7

org.apache.mahout mahout-math 0.7 test-jar test

org.apache.mahout mahout-utils 0.5

5.3 Recomendadores de Mahout

Compilamos y ejecutamos

$ mvn compile BUILD SUCCESS ... $ mvn exec:java -Dexec.mainClass="ccp.challenge1.recommender.Recommend" Item-item with Tanimoto: 1.2142 Item-item with log-likelihood: 1.2151 SVD with EM: 1.2284SVD with ALS: 1.9169 SVD with implicit linear regression: NaN Slope One: 0.7175 User-user n-nearest neighbor with Tanimoto: NaN User-user n-nearest neighbor with log-likelihood: NaN User-user threshold with Tanimoto: NaN User-user threshold with log-likelihood: NaN

NaN: not a number (no hay suficientes datos)

Mejor que el promedio base que obtuvimos anteriormente

Mejor resultado

5.3 Recomendadores de Mahout

Refinamos los algoritmos

5.4 Generando ratings

Refinamos los algoritmosAhora que hemos elegido el algoritmo (Slope One) podemos generar las valoraciones

Recalculamos el promedio global para todo el dataset

$ hive hive> SELECT avg(IF(a.rating IS NULL, b.rating, a.rating)) AS avg FROM explicit_train a FULL OUTER JOIN explicit_test b ON (a.user == b.user and a.item == b.item); ... OK 3.683585313174946 Time taken: 7.881 seconds

5.4 Generando ratings

Creamos el recomendadorFinalRecommend.java

public class FinalRecommend { private static final Float GLOBAL_AVERAGE = 3.6836f;

public static void main(String[] args) throws Exception { DataModel explicitDataModel = new FileDataModel(new File("../explicit.csv")); Recommender recommender = new SlopeOneRecommender(explicitDataModel); PrintWriter out = new PrintWriter("../Task3Solution.csv"); File in = new File("../rateme.csv");

for (String testDatum : new FileLineIterable(in)) { String[] tokens = testDatum.split(","); String itemIdString = tokens[1]; long userId = Long.parseLong(tokens[0]); long itemId = Long.parseLong(itemIdString); float estimate;

try { estimate = recommender.estimatePreference(userId, itemId);

} catch(NoSuchUserException e) { estimate = GLOBAL_AVERAGE; } catch(NoSuchItemException e) { estimate = GLOBAL_AVERAGE; }

if (Float.isNaN(estimate)) { estimate = GLOBAL_AVERAGE; } if (itemId > 50000) { int i = itemIdString.lastIndexOf("00"); itemIdString = itemIdString.substring(0, i) + 'e' + itemIdString.substring(i+2); } out.printf("%d,%s,%.4f\n", userId, itemIdString, estimate); } out.close(); } }

5.4 Generando ratings

Ejecutamos

$ cd $ mvn compile BUILD SUCCESS ... $ mvn exec:java -Dexec.mainClass="ccp.challenge1.recommender.FinalRecommend"...

5.4 Generando ratings

Analizamos los resultados

$ wc -l ../Task3Solution.csv 1000 ../Task3Solution.csv $ head ../Task3Solution.csv 91059173,6155,3.6836 34025317,39419,3.6836 15309904,11248,3.6836 60633959,18963,3.6836 31917316,36814,3.6836 53736706,26212,3.6836 33961447,22606,3.6836 93036062,39815,3.6836 93165942,32989,3.6836 91328973,26806,3.6836

Los 10 primeros resultados utilizan el promedio global calculado antes en Hive

5.4 Generando ratings

Para saber cuantos

Sobre el 85% de los ratings generados utilizan el promedio global calculadoDado el esparcimiento de los datos no es un mal resultado

$ grep 3.6836 ../Task3Solution.csv | wc -l 852