Master Telefónica Big Data & Analytics

Prueba de Evaluación del Tema 4:

Topic Modelling.

Date: 2016/04/10

Para realizar esta prueba es necesario tener actualizada la máquina virtual con la versión más reciente de MLlib.

Para la actualización, debe seguir los pasos que se indican a continuación:

Pasos para actualizar MLlib:

  1. Entrar en la vm como root:

    vagrant ssh

    sudo bash

    Ir a /usr/local/bin

  2. Descargar la última versión de spark desde la vm mediante

    wget http://www-eu.apache.org/dist/spark/spark-1.6.1/spark-1.6.1-bin-hadoop2.6.tgz

  3. Desempaquetar:

    tar xvf spark-1.6.1-bin-hadoop2.6.tgz (y borrar el tgz)

  4. Lo siguiente es un parche, pero suficiente para que funcione:

    Guardar copia de spark-1.3: mv spark-1.3.1-bin-hadoop2.6/ spark-1.3.1-bin-hadoop2.6_old

    Crear enlace a spark-1.6: ln -s spark-1.6.1-bin-hadoop2.6/ spark-1.3.1-bin-hadoop2.6

Librerías

Puede utilizar este espacio para importar todas las librerías que necesite para realizar el examen.


In [20]:
%matplotlib inline

import nltk

import time
import matplotlib.pyplot as plt
import pylab

# import nltk
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
from nltk.corpus import stopwords

#from test_helper import Test

import collections

from pyspark.mllib.clustering import LDA, LDAModel
from pyspark.mllib.linalg import Vectors

# import gensim
# import numpy as np

0. Adquisición de un corpus.

Descargue el contenido del corpus reuters de nltk.

import nltk
nltk.download()

Selecciona el identificador reuters.


In [2]:
#nltk.download()
mycorpus = nltk.corpus.reuters

Para evitar problemas de sobrecarga de memoria, o de tiempo de procesado, puede reducir el tamaño el corpus, modificando el valor de la variable n_docs a continuación.


In [3]:
n_docs = 500000

filenames = mycorpus.fileids()
fn_train = [f for f in filenames if f[0:5]=='train']

corpus_text = [mycorpus.raw(f) for f in fn_train]

# Reduced dataset:
n_docs = min(n_docs, len(corpus_text))
corpus_text = [corpus_text[n] for n in range(n_docs)]

print 'Loaded {0} files'.format(len(corpus_text))


Loaded 7769 files

A continuación cargaremos los datos en un RDD


In [4]:
corpusRDD = sc.parallelize(corpus_text, 4)
print "\nRDD created with {0} elements".format(corpusRDD.count())


RDD created with 7769 elements

1. Ejercicios

Ejercicio 1: Preprocesamiento de datos.

Prepare los datos para aplicar un algoritmo de modelado de tópicos en pyspark. Para ello, aplique los pasos siguientes:

  1. Tokenización: convierta cada texto a utf-8, y transforme la cadena en una lista de tokens.
  2. Homogeneización: pase todas las palabras a minúsculas y elimine todos los tokens no alfanuméricos.
  3. Limpieza: Elimine todas las stopwords utilizando el fichero de stopwords disponible en NLTK para el idioma inglés.

Guarde el resultado en la variable corpus_tokensRDD


In [5]:
def getTokenList(doc, stopwords_en):
    
    # scode: tokens = <FILL IN>   # Tokenize docs
    tokens = word_tokenize(doc.decode('utf-8'))    

    # scode: tokens = <FILL IN>   # Remove non-alphanumeric tokens and normalize to lowercase
    tokens = [t.lower() for t in tokens if t.isalnum()]
    # scode: tokens = <FILL IN>   # Remove stopwords
    tokens = [t for t in tokens if t not in stopwords_en]

    return tokens

stopwords_en = stopwords.words('english')
corpus_tokensRDD = (corpusRDD
                   .map(lambda x: getTokenList(x, stopwords_en))
                   .cache())   

# print "\n Let's check tokens after cleaning:"
print corpus_tokensRDD.take(1)[0][0:30]


[u'bahia', u'cocoa', u'review', u'showers', u'continued', u'throughout', u'week', u'bahia', u'cocoa', u'zone', u'alleviating', u'drought', u'since', u'early', u'january', u'improving', u'prospects', u'coming', u'temporao', u'although', u'normal', u'humidity', u'levels', u'restored', u'comissaria', u'smith', u'said', u'weekly', u'review', u'dry']

Ejercicio 2: Stemming

Aplique un procedimiento de stemming al corpus, utilizando el SnowballStemmer de NLTK. Guarde el resultado en corpus_stemRDD.


In [6]:
# Select stemmer.
stemmer = nltk.stem.SnowballStemmer('english')

# scode: corpus_stemRDD = <FILL IN>
corpus_stemRDD = corpus_tokensRDD.map(lambda x: [stemmer.stem(token) for token in x])

print "\nLet's check the first tokens from document 0 after stemming:"
print corpus_stemRDD.take(1)[0][0:30]


Let's check the first tokens from document 0 after stemming:
[u'bahia', u'cocoa', u'review', u'shower', u'continu', u'throughout', u'week', u'bahia', u'cocoa', u'zone', u'allevi', u'drought', u'sinc', u'earli', u'januari', u'improv', u'prospect', u'come', u'temporao', u'although', u'normal', u'humid', u'level', u'restor', u'comissaria', u'smith', u'said', u'week', u'review', u'dri']

Ejercicio 3: Vectorización

En este punto cada documento del corpus es una lista de tokens.

Calcule un nuevo RDD que contenga, para cada documento, una lista de tuplas. La clave (key) de cada lista será un token y su valor el número de repeticiones del mismo en el documento.

Imprima una muestra de 20 tuplas uno de los documentos del corpus.


In [60]:
# corpus_wcRDD = <FILL IN>
corpus_wcRDD = (corpus_stemRDD
                .map(collections.Counter)
                .map(lambda x: [(t, x[t]) for t in x]))

print corpus_wcRDD.take(1)[0][0:20]


[(u'humid', 1), (u'obtain', 1), (u'cruzado', 1), (u'held', 1), (u'publish', 1), (u'kilo', 2), (u'go', 1), (u'still', 3), (u'27', 1), (u'zone', 1), (u'much', 2), (u'smith', 5), (u'late', 1), (u'340', 1), (u'farmer', 2), (u'total', 3), (u'argentina', 1), (u'earli', 1), (u'good', 1), (u'march', 1)]

Ejercicio 4: Cálculo del diccionario de tokens

Construya, a partir de corpus_wcRDD, un nuevo diccionario con todos los tokens del corpus. El resultado será un diccionario python de nombre wcDict, cuyas entradas serán los tokens y sus valores el número de repeticiones del token en todo el corpus.

wcDict = {token1: valor1, token2, valor2, ...}

Imprima el número de repeticiones del token interpret


In [63]:
# scode: wcRDD = < FILL IN >
wcRDD = (corpus_wcRDD
         .flatMap(lambda x: x)
         .reduceByKey(lambda x, y: x + y))

wcDict = dict(wcRDD.collect())

print wcDict['interpret']


17

Ejercicio 5: Número de tokens.

Determine el número total de tokens en el diccionario. Imprima el resultado.


In [9]:
print wcRDD.count()


18418

Ejercicio 6: Términos demasiado frecuentes:

Determine los 5 tokens más frecuentes del corpus. Imprima el resultado.


In [55]:
print wcRDD.takeOrdered(5, lambda x: -x[1])


[(u'said', 18843), (u'mln', 13089), (u'vs', 9287), (u'dlrs', 8781), (u'pct', 7394)]

Ejercicio 7: Número de documentos del token más frecuente.

Determine en qué porcentaje de documentos aparece el token más frecuente.


In [65]:
tokenmasf = 'said'
ndocs = corpus_stemRDD.filter(lambda x: tokenmasf in x).count()
print ndocs


5017

Ejercicio 8: Filtrado de términos.

Elimine del corpus los dós términos más frecuentes. Guarde el resultado en un nuevo RDD denominado corpus_wcRDD2, con la misma estructura que corpus_wcRDD (es decir, cada documento una lista de tuplas).


In [11]:
corpus_wcRDD2 = corpus_wcRDD.map(lambda x: [tupla for tupla in x if tupla[0] 
                                 not in ['said', 'mln']])
                                            
# scode: wcRDD = < FILL IN >
wcRDD2 = (corpus_wcRDD2
         .flatMap(lambda x: x)
         .reduceByKey(lambda x, y: x + y)
         .sortBy(lambda x: -x[1]))

print wcRDD2.takeOrdered(10, lambda x: -x[1])


[(u'vs', 9287), (u'dlrs', 8781), (u'pct', 7394), (u'lt', 6302), (u'year', 5969), (u'cts', 5776), (u'net', 4566), (u'loss', 4312), (u'share', 4180), (u'billion', 4109)]

Ejercicio 9: Lista de tokens y diccionario inverso.

Determine la lista de topicos de todo el corpus, y construya un dictionario inverso, cuyas entradas sean los números consecutivos de 0 al número total de tokens, y sus salidas cada uno de los tokens, es decir

invD = {0: token0, 1: token1, 2: token2, ...}

In [25]:
# Token Dictionary:
n_tokens = wcRDD2.count()
TD = wcRDD2.takeOrdered(n_tokens, lambda x: -x[1])

D = map(lambda x: x[0], TD)
token_count = map(lambda x: x[1], TD)   

# Compute inverse dictionary
invD = dict(zip(D, xrange(n_tokens)))

Ejercicio 10: Algoritmo LDA.

Para aplicar el algoritmo LDA, es necesario reemplazar las tuplas (token, valor) de wcRDD por tuplas del tipo (token_id, value), sustituyendo cada token por un identificador entero.

El código siguiente se encarga de completar este proceso:


In [26]:
# Compute RDD replacing tokens by token_ids
corpus_sparseRDD = corpus_wcRDD2.map(lambda x: [(invD[t[0]], t[1]) for t in x])

# Convert list of tuplas into Vectors.sparse object.
corpus_sparseRDD = corpus_sparseRDD.map(lambda x: Vectors.sparse(n_tokens, x))
corpus4lda = corpus_sparseRDD.zipWithIndex().map(lambda x: [x[1], x[0]]).cache()

Aplique el algoritmo LDA con 4 tópicos sobre el corpus obtenido en corpus4lda, para un valor de topicConcentration = 2.0 y docConcentration = 3.0. (Tenga en cuenta que estos parámetros de entrada deben de ser tipo float).


In [53]:
print "Training LDA: this might take a while..."
start = time.time()
n_topics = 4
ldaModel = LDA.train(corpus4lda, k=n_topics, topicConcentration=2.0, docConcentration=3.0)
print "Modelo LDA entrenado en: {0} segundos".format(time.time()-start)


Training LDA: this might take a while...
Modelo LDA entrenado en: 42.7436800003 segundos

Ejercicio 11: Tokens principales.

Imprima los dos tokens de mayor peso de cada tópico. (Debe imprimir el texto del token, no su índice).


In [45]:
n_topics = 4
ldatopics = ldaModel.describeTopics(maxTermsPerTopic=2)
ldatopicnames = map(lambda x: x[0], ldatopics)
print ldatopicnames
for i in range(n_topics):
    print "Topic {0}: {1}, {2}".format(i, D[ldatopicnames[i][0]], D[ldatopicnames[i][1]])


[[14, 2], [0, 5], [11, 17], [9, 2]]
Topic 0: trade, pct
Topic 1: vs, cts
Topic 2: bank, market
Topic 3: billion, pct

Ejercicio 12: Pesos de un token.

Imprima el peso del token bank en cada tópico.


In [59]:
# Output topics. Each is a distribution over words (matching word count vectors)
iBank = invD['bank']
topicMatrix = ldaModel.topicsMatrix()
print topicMatrix[iBank]


[   33.44637319  3539.29099346   278.00162952     5.26100383]

Test 13: Indique cuáles de las siguientes afirmaciones se puede asegurar que son verdaderas:

  1. En LSI, cada documento se asigna a un sólo tópico.
  2. De acuerdo con el modelo LDA, todos los tokens de un documento han sido generados por el mismo tópico
  3. LSI descompone la matriz de datos de entrada en el producto de 3 matrices cuadradas.
  4. Si el rango de la matriz de entrada a un modelo LSI es igual al número de tópicos. La descomposición SVD del modelo LSI es exacta (no es una aproximación).

In [ ]:

Test 14: Indique cuáles de las siguientes afirmaciones se puede asegurar que son verdaderas:

  1. En un modelo LDA, la distribución de Dirichlet se utiliza para generar distribuciones de probabilidad de tokens.
  2. Si una palabra aparece en pocos documentos del corpus, su IDF es mayor.
  3. El resultado de la lematización de una palabra es una palabra
  4. El resultado del stemming de una palabra es una palabra

In [ ]: