jueves, 7 de enero de 2021

Clasificador con Word2Vec

El siguiente ejemplo utiliza un dataset de productos de supermercado, y crear un clasificador para predecir la categoría del producto usando el nombre. 

El dataset proviene de un web-scraping realizado en una página de un supermercado, y tiene la siguiente estructura:












Para lograr esto se utiliza el algoritmo de word2vec para transformar el texto en una matriz que represente los features o variables. En este ejemplo no se explica la teoría de word2vec, luego haré otro post sobre eso.


El proceso sigue estos pasos:


PASO 1: Entrenar Word2vec

Se entrena un modelo usando Word2vec,  y se genera un diccionario (word vector) que tiene un vector numérico por cada palabra.  Conceptualmente sería así:



























PASO 2: Generar Features

Al entrenar Word2vec tenemos un vocabulario (word vector) donde cada palabra tiene un vector de igual tamaño. Ahora es necesario construir un vector único para cada producto, y para esto puede usarse el promedio de los vectores de cada palabra, que puede representarse así:














PASO 3: Entrenar un Clasificador

Cuando se genera el promedio de todos los vectores, se logra un dataset donde cada nombre de producto es definido por un vector que es el promedio de todas sus palabras, y entonces es posible entrenar un clasificador. 

Conceptualmente sería así:








Al entrenar el modelo y realizar las predicciones en el conjunto de test, tenemos los siguientes resultados:










Script en Python con todos los pasos:

Ejemplo realizado con la version 3.8.3 de la libreria gensim

(pip install gensim==3.8.3)

import random as rd
import pandas as pd 
import numpy as np

from nltk.tokenize import RegexpTokenizer
from nltk.corpus import stopwords
from sklearn.model_selection import train_test_split as tts
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
from gensim.models.word2vec import Word2Vec


class Word2VecClassification:

    def __init__(self, vector_size=10, window_size=3):
        self.vector_size = vector_size
        self.window_size = window_size
        self.tkr = RegexpTokenizer('[a-zA-Z]+')
        self.sw = stopwords.words('spanish')
        self.algorithm = RandomForestClassifier()
        self.model_w2v = None
        self.model_cls = None


    def buildWordVector(self, doc):
        vec_matrix = np.zeros(shape=(self.vector_size, len(doc)))

        for idw, word in enumerate(doc):
            if word not in self.sw:      
                try:
                    vec_matrix[:, idw] = self.model_w2v[word]
                except KeyError:
                    continue

        return np.mean(vec_matrix, axis=1)


    def get_feature_from_vec(self, tokenized_corpus):
        print('getting feature from vectores...')
        docs = []
        for doc in tokenized_corpus:
            doc =  [x for x in doc if x not in self.sw]
            vec = self.buildWordVector(doc)
            vec = np.nan_to_num(vec.astype(np.float32))
            docs.append(vec)
        return docs


    def get_tokenized_corpus(self, corpus):
        return [self.tkr.tokenize(text.strip().lower()) for text in corpus]


    def fit_w2v(self, tokenized_corpus):
        print('fitting word2vec...')
        return Word2Vec(sentences=tokenized_corpus,
                            size=self.vector_size,
                            window=self.window_size,
                            min_count=2,
                            negative=20,
                            hs=0,
                            ns_exponent=.5,
                            cbow_mean=1,
                            iter=150,
                            sg=0,                            
                            )


    def fit(self, corpus, y_train):
        # train w2ec
        tokenized_corpus = self.get_tokenized_corpus(corpus)
        self.model_w2v = self.fit_w2v(tokenized_corpus)
        # train classification
        x_train = self.get_feature_from_vec(tokenized_corpus)
        self.model_cls = self.algorithm.fit(x_train, y_train)


    def predict(self, corpus):
        tokenized_corpus = self.get_tokenized_corpus(corpus)
        x = self.get_feature_from_vec(tokenized_corpus)
        return self.model_cls.predict(x)


if __name__ == '__main__':

    # data
    target = 'category'
    corpus = 'product_name'
    url = 'https://www.dropbox.com/s/1n6tcxqptjysvvf/supermercado.csv?dl=1'
    data = pd.read_csv(url, encoding='utf-8')
    train, test = tts(data, train_size = 0.85)

    # train
    model = Word2VecClassification(vector_size=10, window_size=3)
    model.fit(train[corpus], train[target])

    # predict
    predictions = model.predict(test[corpus])
    report = classification_report(test[target], predictions)
    print(report) 

Referencias:

El link con imágenes se puede descargar aqui







2 comentarios:

  1. Que tal! Excelente explicación, solo me quedo la duda sobre los valores negativos que se obtuvieron en los vectores del paso 1, así como los valores negativos obtenidos en la paso 2, como se adquirieron esos datos? esos valores creo que están relacionados con el indice de similitud pero desconozco porque salen negativos. Espero y me puedas resolver esa duda

    Nuevamente muchas gracias por la explicación y el ejemplo

    Saludos

    ResponderEliminar
    Respuestas
    1. Hola Edgar. Me alegra que te sirva esta publicaciòn. Sobre los valores negativos, te dejo este link con algunos detalles, espero te sirva: https://www.quora.com/What-should-I-do-if-I-got-negative-values-for-vectors

      Suerte!

      Eliminar