If you can't read please download the document
Upload
eduardo-castillejo-gil
View
361
Download
0
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