<jupyter_start><jupyter_text>Unidad 3: Datos textuales de medios de prensa y clasificación Notebook 2 - Introducción a la vectorización de palabras: Word2Vec En los notebooks anteriores, hemos hablado de los vectores que se utilizan para representar nuestros datos, textuales o no, en una forma matemática, y así poder aplicar métodos de machine learning.En este notebook, vamos a llevar esta idea un paso más allá y generar reprentaciones vectoriales de las palabras. Esta idea es generalmente llamada __Word Embeddings__. __Word2Vec__ es un algoritmo popular para implementar esta idea.Esta técnica permite para representar mejor el significado de una palabra.Para más detalles sobre los fundamentos de Word2Vec, se puede leer: _Efficient Estimation of Word Representations in Vector Space_ [[Mikolov et al., 2013]](https://arxiv.org/pdf/1301.3781.pdf)  1. ¿Qué es una representación vectorial de una palabra?Supongamos que nuestro vocabulario tiene sólo cinco palabras: King, Queen, Man, Woman y Child. Podríamos codificar la palabra 'Queen' como:Usando tal codificación, no hay comparación interesante que podamos hacer entre vectores de palabras. Con word2vec, se utiliza una representación distribuida de una palabra. Cada palabra está representada por una distribución de pesos entre esos elementos. La representación de una palabra se extiende a través de todos los elementos en el vector, y cada elemento en el vector contribuye a la definición de muchas palabras.Tal vector llega a representar de alguna manera abstracta el 'significado' de una palabra. Y como veremos a continuación, simplemente examinando un corpus grande es posible aprender vectores de palabras que son capaces de capturar las relaciones entre palabras de una manera sorprendentemente expresiva.   Razonamiento con vectores de palabrasLos vectores son muy buenos para responder a problemas de analogía de tipo: '_A_ es a _B_ lo que _C_ es a ...?'. Por ejemplo, el _hombre_ es a la _mujer_ lo que el _tío_ es a la _tía_ usando un método de desplazamiento vectorial.Por ejemplo, el desplazamiento vectorial que ilustra la _relación de género_: Este tipo de composición vectorial también nos permite responder a la pregunta __King - Man + Woman = ?__ y llegar al resultado __Queen__.Estos modelos vectoriales de palabras están destacables ya que permiten representar ciertas relaciones entre palabras sin aportar información sobre la semántica de las palabras. Como vamos a verlo, se pueden aprender los vectores a partir de gran conjuntos de datos textuales.Algunos ejemples de relaciones entre palabras que se han podido calcular utilizando Word2Vec: También podemos utilizar la adición de vectores para hacer preguntas como "German + airlines" y mirando las palabras más cercanas al vector compuesto obtenemos respuestas impresionantes:  Construir una representación vectorial de una palabra _y_ considerando las palabras X que aparacen frecuentemente en su contextoPara construir sus vectores, Word2Vec utiliza un dataset de entrenamiento y algoritmos de aprendizaje basados en redes neuronales (__Continuous Bag of Words__ (CBOW), o modelo __Skip Gram__). El objetivo de esta fase de aprendizaje es aprender cuáles son las palabras _X_ más probables de aparecer en el contexto de una palabra _y_.Por ejemplo, ¿cuál es la probabilidad de tener la palabra 'perro' si aparece la palabra 'pelota' en el contexto?Los expertos explican que los __perros__ persiguen __pelotas__ en movimiento como parte de un comportamiento instintivo. Aunque no todos los perros tienen tan despiertos su instinto de caza, esto no impide que la mayoría de ellos sí disfruten, y mucho, de los juegos que incluyen persecuciones de una saltarina __pelota__ que bota delante de ellos. __Algoritmo CBOW__Las palabras de contexto forman la capa de entrada. Si el tamaño del vocabulario es V, estos serán vectores de dimensión V con sólo uno de los elementos establecido en uno, y el resto todos los ceros. Hay una sola capa oculta y una capa de salida.  2. Word2Vec: crear representaciones vectoriales de palabrasLa clase word2vec de Gensim permite entrenar modelos vectoriales de palabras (ver documentación: https://radimrehurek.com/gensim/models/word2vec.html).Esta clase tiene varios parametros, en particular:- sentences: una lista de palabras o de frases que sirve para entrenar el modelo- sg: define que algoritmos de aprendizaje utilizar (0=CBOW, 1=skip-gram)- size: define la dimensión de los vectores que se desea extraer- window: define el número de palabras considerar a la izquierda y a la derecha de una palabra- min_count: ignorar las palabras que aparecen menos de _min_count_y otros asociados a la parametrización de la fase de aprendizaje de la red neuronal (que no detallaremos en esta parte del curso):- alpha: el _learning rate_ utilizado para optimizar los parametros de la red neuronal.- iter: número de iteraciones (epocas) sobre el dataset para encontrar los parametreos que optimizan la red neuronal.<jupyter_code>from gensim.models import word2vec
sentences = word2vec.Text8Corpus('text8.txt')
model = word2vec.Word2Vec(sentences,size=200,hs=1)
print(model)<jupyter_output><empty_output><jupyter_text>Ahora que hemos aprendido nuestro modelo, tratemos de resolver la ecuación King - Man + Woman.En otras palabras buscamos cuál es el vector más similar al vector que adiciona positivamente 'King' y 'Woman' y negativamente 'Man'.<jupyter_code>model.wv.most_similar(positive=['woman','king'],negative=['man'],topn=5)
model.wv.most_similar(positive=["conflict"])
model.wv.most_similar(positive=["conflict","weapon"])<jupyter_output><empty_output><jupyter_text>Para entrenar nuestro modelo Word2Vec, podemos utilizar nuestros propios datasets o utilizar datasets genericos existentes. Para empezar, utilizaremos 100 MB de textos extraidos de Wikipedia en inglés, para generar vectores de 200 dimensiones.<jupyter_code>model.wv.most_similar(positive=["conflict"],negative=["weapon"])
model.wv.most_similar(positive=["life"])
model.wv.most_similar(positive=["life","love"])
model.wv.most_similar(positive=["life","money"])
model.wv.most_similar(positive=["life","happiness"])
model.wv.most_similar(positive=["life"],negative=["health"])<jupyter_output><empty_output><jupyter_text>Ver los parametros aprendidos por la red neuronal para una palabra dada:<jupyter_code>model.wv['computer']
model.save("text8_model")
model=word2vec.Word2Vec.load("text8_model")<jupyter_output><empty_output><jupyter_text>Otro ejemplo de aplicación: __buscar el vector más diferente de los otros__.Entre las palabras siguientes, ¿cuál es la palabra la más distinta?<jupyter_code>model.wv.doesnt_match("breakfast cereal dinner lunch".split())
model.wv.doesnt_match("brazil chile france peru argentina".split())
model.wv.doesnt_match("apple pear banana hammer".split())<jupyter_output><empty_output><jupyter_text>Podemos tambien medir la similaridad entre palabras:<jupyter_code>model.wv.similarity('man','woman')
model.wv.similarity('man','hammer')
model.wv.similarity('woman','hammer')
model.wv.similarity('man','engineer')
model.wv.similarity('woman','engineer')
model.wv.similarity('man','baby')
model.wv.similarity('woman','baby')<jupyter_output><empty_output><jupyter_text>El modelo aprendido con la red neuronal captura el significado de las palabras, pero también los sesgos humanos.Dado que los vectores obtenidos representan mejor el sentido de las palabras que las palabras mismas, sería interesante representar los documentos (textos, moticias) utilizando estos vectores.Es lo que vamos a observar en la parte siguiente, realizando un benchmark de Word2Vec en comparación con otros enfoques:  3. Benchmark de Word2Vec para tareas de clasificación de textos Realizaremos un benchmark de Word2Vec para resolver tareas de clasificación de textos, utilizando 2 datasets estándares: https://www.cs.umb.edu/~smimarog/textmining/datasets/- Reuters 8- Newsgroup 20<jupyter_code>from tabulate import tabulate
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from gensim.models.word2vec import Word2Vec
from collections import Counter, defaultdict
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.naive_bayes import BernoulliNB, MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import StratifiedShuffleSplit
TRAIN_SET_PATH = "r8-no-stop.txt"

X, y = [], []
with open(TRAIN_SET_PATH, "r") as infile:
    for line in infile:
        label, text = line.split("\t")
        # texts are already tokenized, just split on space
        # in a real case we would use e.g. spaCy for tokenization
        # and maybe remove stopwords etc.
        X.append(text.split())
        y.append(label)
X, y = np.array(X), np.array(y)
print ("total examples %s" % len(y))
# start with the classics - naive bayes of the multinomial and bernoulli varieties
# with either pure counts or tfidf features
mult_nb = Pipeline([("count_vectorizer", CountVectorizer(analyzer=lambda x: x)), ("multinomial nb", MultinomialNB())])
bern_nb = Pipeline([("count_vectorizer", CountVectorizer(analyzer=lambda x: x)), ("bernoulli nb", BernoulliNB())])
mult_nb_tfidf = Pipeline([("tfidf_vectorizer", TfidfVectorizer(analyzer=lambda x: x)), ("multinomial nb", MultinomialNB())])
bern_nb_tfidf = Pipeline([("tfidf_vectorizer", TfidfVectorizer(analyzer=lambda x: x)), ("bernoulli nb", BernoulliNB())])
# SVM - which is supposed to be more or less state of the art 
# http://www.cs.cornell.edu/people/tj/publications/joachims_98a.pdf
svc = Pipeline([("count_vectorizer", CountVectorizer(analyzer=lambda x: x)), ("linear svc", SVC(kernel="linear"))])
svc_tfidf = Pipeline([("tfidf_vectorizer", TfidfVectorizer(analyzer=lambda x: x)), ("linear svc", SVC(kernel="linear"))])


import gensim
# let X be a list of tokenized texts (i.e. list of lists of tokens)
model = gensim.models.Word2Vec(X, size=100)
w2v = dict(zip(model.wv.index2word, model.wv.syn0))

class MeanEmbeddingVectorizer(object):
    def __init__(self, word2vec):
        self.word2vec = word2vec
        # if a text is empty we should return a vector of zeros
        # with the same dimensionality as all the other vectors
        self.dim = len(word2vec.values())

    def fit(self, X, y):
        return self

    def transform(self, X):
        return np.array([
            np.mean([self.word2vec[w] for w in words if w in self.word2vec]
                    or [np.zeros(self.dim)], axis=0)
            for words in X
        ])

class TfidfEmbeddingVectorizer(object):
    def __init__(self, word2vec):
        self.word2vec = word2vec
        self.word2weight = None
        self.dim = len(word2vec.values())

    def fit(self, X, y):
        tfidf = TfidfVectorizer(analyzer=lambda x: x)
        tfidf.fit(X)
        # if a word was never seen - it must be at least as infrequent
        # as any of the known words - so the default idf is the max of 
        # known idf's
        max_idf = max(tfidf.idf_)
        self.word2weight = defaultdict(
            lambda: max_idf,
            [(w, tfidf.idf_[i]) for w, i in tfidf.vocabulary_.items()])

        return self

    def transform(self, X):
        return np.array([
                np.mean([self.word2vec[w] * self.word2weight[w]
                         for w in words if w in self.word2vec] or
                        [np.zeros(self.dim)], axis=0)
                for words in X
            ])

from sklearn.pipeline import Pipeline
from sklearn.ensemble import ExtraTreesClassifier

etree_w2v = Pipeline([
    ("word2vec vectorizer", MeanEmbeddingVectorizer(w2v)),
    ("extra trees", ExtraTreesClassifier(n_estimators=200))])
etree_w2v_tfidf = Pipeline([
    ("word2vec vectorizer", TfidfEmbeddingVectorizer(w2v)),
    ("extra trees", ExtraTreesClassifier(n_estimators=200))])

svc_w2v = Pipeline([
    ("word2vec vectorizer", MeanEmbeddingVectorizer(w2v)),
    ("svm", SVC(kernel="linear"))])
svc_w2v_tfidf = Pipeline([
    ("word2vec vectorizer", TfidfEmbeddingVectorizer(w2v)),
    ("Svm", SVC(kernel="linear"))])
all_models = [
    ("mult_nb", mult_nb),
    ("mult_nb_tfidf", mult_nb_tfidf),
    ("bern_nb", bern_nb),
    ("bern_nb_tfidf", bern_nb_tfidf),
    ("svc", svc),
    ("svc_tfidf", svc_tfidf),
    ("trees_w2v", etree_w2v),
    ("trees_w2v_tfidf", etree_w2v_tfidf),
    ("svc_w2v", svc_w2v),
    ("svc_w2v_tfidf", svc_w2v_tfidf),
]


unsorted_scores = [(name, cross_val_score(model, X, y, cv=5).mean()) for name, model in all_models]
scores = sorted(unsorted_scores, key=lambda x: -x[1])


print (tabulate(scores, floatfmt=".4f", headers=("model", 'score')))
plt.figure(figsize=(15, 6))
sns.barplot(x=[name for name, _ in scores], y=[score for _, score in scores])
def benchmark(model, X, y, n):
    test_size = 1 - (n / float(len(y)))
    scores = []
    sp = StratifiedShuffleSplit(n_splits=5, test_size=test_size)
    sp.get_n_splits(X, y)
    for train, test in sp.split(X, y):
        X_train, X_test = X[train], X[test]
        y_train, y_test = y[train], y[test]
        scores.append(accuracy_score(model.fit(X_train, y_train).predict(X_test), y_test))
    return np.mean(scores)
train_sizes = [10, 40, 160, 640, 3200, 6400]
table = []
for name, model in all_models:
    for n in train_sizes:
        table.append({'model': name, 
                      'accuracy': benchmark(model, X, y, n), 
                      'train_size': n})
df = pd.DataFrame(table)
plt.figure(figsize=(15, 6))
fig = sns.pointplot(x='train_size', y='accuracy', hue='model', 
                    data=df[df.model.map(lambda x: x in ["mult_nb", "svc_tfidf", "trees_w2v_tfidf", "svc_w2v","svc_w2v_tfidf" 
                                                        ])])
sns.set_context("notebook", font_scale=1.5)
fig.set(ylabel="accuracy")
fig.set(xlabel="labeled training examples")
fig.set(title="R8 benchmark")
fig.set(ylabel="accuracy")<jupyter_output><empty_output><jupyter_text>Hagamos lo mismo para el dataset Newsgroup 20:<jupyter_code>TRAIN_SET_PATH = "20ng-no-stop.txt"

X, y = [], []
with open(TRAIN_SET_PATH, "r") as infile:
    for line in infile:
        label, text = line.split("\t")
        # texts are already tokenized, just split on space
        # in a real case we would use e.g. spaCy for tokenization
        # and maybe remove stopwords etc.
        X.append(text.split())
        y.append(label)
X, y = np.array(X), np.array(y)
print ("total examples %s" % len(y))
# train word2vec on all the texts - both training and test set
# we're not using test labels, just texts so this is fine
model = Word2Vec(X, size=100, window=5, min_count=5, workers=2)
w2v = {w: vec for w, vec in zip(model.wv.index2word, model.wv.syn0)}
from sklearn.pipeline import Pipeline
from sklearn.ensemble import ExtraTreesClassifier

etree_w2v = Pipeline([
    ("word2vec vectorizer", MeanEmbeddingVectorizer(w2v)),
    ("extra trees", ExtraTreesClassifier(n_estimators=200))])
etree_w2v_tfidf = Pipeline([
    ("word2vec vectorizer", TfidfEmbeddingVectorizer(w2v)),
    ("extra trees", ExtraTreesClassifier(n_estimators=200))])

svc_w2v = Pipeline([
    ("word2vec vectorizer", MeanEmbeddingVectorizer(w2v)),
    ("svm", SVC(kernel="linear"))])
svc_w2v_tfidf = Pipeline([
    ("word2vec vectorizer", TfidfEmbeddingVectorizer(w2v)),
    ("Svm", SVC(kernel="linear"))])
all_models = [
    ("mult_nb", mult_nb),
    ("mult_nb_tfidf", mult_nb_tfidf),
    ("bern_nb", bern_nb),
    ("bern_nb_tfidf", bern_nb_tfidf),
    ("svc", svc),
    ("svc_tfidf", svc_tfidf),
    ("trees_w2v", etree_w2v),
    ("trees_w2v_tfidf", etree_w2v_tfidf),
    ("svc_w2v", svc_w2v),
    ("svc_w2v_tfidf", svc_w2v_tfidf)
]


unsorted_scores = [(name, cross_val_score(model, X, y, cv=5).mean()) for name, model in all_models]
scores = sorted(unsorted_scores, key=lambda x: -x[1])


print (tabulate(scores, floatfmt=".4f", headers=("model", 'score')))
train_sizes = [40, 160, 640, 3200, 6400, 9000]
table = []
for name, model in all_models:
    for n in train_sizes:
        table.append({'model': name, 
                      'accuracy': benchmark(model, X, y, n), 
                      'train_size': n})
df = pd.DataFrame(table)
plt.figure(figsize=(15, 6))
fig = sns.pointplot(x='train_size', y='accuracy', hue='model', 
                    data=df[df.model.map(lambda x: x in ["mult_nb", "svc_tfidf", "w2v_tfidf"
                                                        ])])
sns.set_context("notebook", font_scale=1.5)
fig.set(ylabel="accuracy")
fig.set(xlabel="labeled training examples")
fig.set(title="20 news groups benchmark")
fig.set(ylabel="accuracy")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10030268858685976, 'fraction_numerical': 0.012997804023977684, 'mean_word_length': 4.54641211323239, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 33, 'https://': 3, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '628365', 'license': 'MIT', 'n_tokens_mistral': 5888, 'n_tokens_neox': 5643, 'n_words': 1730, 'path': 'unidad3/Notebook2.ipynb', 'repo_name': 'phuijse/INFO343', 'size': '16849'}