jupyter | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
try:
import google.colab
IN_COLAB = True
except:
IN_COLAB = False
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
from collections import Counter
import sklearn
from sklearn.model_selection import train_test_split
#import sklearn as sk
#modelos y metricas
import seaborn as sns
from matplotlib import pyplot as plt
from joblib import dump, load
from os.path import exists
from sklearn.model_selection import StratifiedKFold, KFold,RandomizedSearchCV, train_test_split, cross_validate
from sklearn.metrics import confusion_matrix, classification_report , f1_score, make_scorer, precision_score, recall_score, accuracy_score,f1_score
#Xval
from sklearn.model_selection import train_test_split, RandomizedSearchCV, GridSearchCV, cross_val_score
#vectorizacion
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.ensemble import VotingClassifier
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_score, recall_score, accuracy_score,f1_score
from joblib import dump, load
from os.path import exists
from os import environ
import string
import nltk
from sklearn.pipeline import Pipeline
import sklearn ### ESTA NO SE BORRA ???? #TODO
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from os.path import exists
environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
if IN_COLAB == True:
!pip install nltk
import nltk
nltk.download('stopwords')
stopwords_es = nltk.corpus.stopwords.words('spanish')
import re
from unicodedata import normalize
import tensorflow as tfr
from tensorflow import keras
from keras.preprocessing.text import one_hot, Tokenizer
from keras.models import Sequential
from keras.layers.core import Activation, Dropout, Dense, SpatialDropout1D
from keras.layers import Flatten, GlobalMaxPooling1D, Embedding, Conv1D, LSTM, TextVectorization
from keras_preprocessing.sequence import pad_sequences
from keras.wrappers.scikit_learn import KerasClassifier
import string
#Random forest
from sklearn.ensemble import RandomForestClassifier
Constantes
# Constantes
SEED=9
JOBS=-2
reviewDfOriginal = pd.read_csv("./review_train.csv")
reviewDf = reviewDfOriginal.copy()
review_pruebasOriginal = pd.read_csv("./review_test.csv")
review_pruebas = review_pruebasOriginal.copy()
review_pruebas
reviewDf = reviewDf.drop(["ID"], axis='columns', inplace=False)
reviewDf
Filtramos y nos quedamos solo con las reviews en epanol para mejorar la efectividad del modelo. Hay 3 listas hechas a mano debido a que tuvimos que tener cuidado de no incluir palabras cuya raiz exitiera en español, o fueran parte de apellidos ya que en esos casos se veian contenidas (love-lace-, hate-r, excelent-e). Al tener 3 listas con ditintos grupos, fue mas facil armar la lista final. Reconocemos que no es el mejor filtro y que es posible que hayamos eliminado unas pocas reseñas en español y hayan quedado unas pocas en ingles. Sin embargo estamos seguros de que si saca la mayoria de las reseñas en ingles.
lista_palabras_comunes_en = ["have","scary","nothing","issue"]
lista_palabras_positivas_en =["interesting","amusing","intelligent","pretty","beautiful"]
lista_palabras_negativas_en =["boring","bullshit","risk","loss","poor","ugly"]
lista_completa = lista_palabras_comunes_en + lista_palabras_positivas_en + lista_palabras_negativas_en
reviewDf_espanol = reviewDf [~reviewDf["review_es"].str.contains('|'.join(lista_completa))]
reviewDf_espanol
Hacemos la particion en test y train con el dataset ya filtrado
reviewDf_x = reviewDf_espanol.drop(["sentimiento"], axis='columns', inplace=False)
reviewDf_y = reviewDf_espanol['sentimiento'].copy()
x_train, x_test, y_train, y_test = train_test_split(reviewDf_x,
reviewDf_y,
test_size=0.30,
random_state=SEED,
shuffle=True
)
Creamos un modelo Bayes Naives con el proposito de observar el comportamiento general del mismo y generamos una primera predicción
if not exists('modelos/TP2/naiveBayes-n.joblib'):
modeloBayesNaive = make_pipeline(TfidfVectorizer(), MultinomialNB())
modeloBayesNaive.fit(x_train.review_es, y_train)
dump(modeloBayesNaive, 'modelos/TP2/naiveBayes-n.joblib')
else:
modeloBayesNaive = load('modelos/TP2/naiveBayes-n.joblib')
prediccion = modeloBayesNaive.predict(x_test.review_es)
Analizamos las metricas del modelo para ver cual es el desempeño global del mismo, de manera que, se puede tener una referencia del comportamiento general del modelo a la hora de buscar hiperparametros
#performance
print(classification_report(y_test,prediccion))
#Creamos la matriz de confusión
tablota=confusion_matrix(y_test, prediccion)
#Grafico la matriz de confusión
sns.heatmap(tablota,cmap='GnBu',annot=True,fmt='g')
plt.xlabel('Predicho')
plt.ylabel('Verdadero')
Generamos una predicción para Kaggle con nuestro primer modelo, el cual tiene un desempeño aceptable
if not exists('submissions/TP2/naiveBayes-nuevo.csv'):
prediccionTesteo = modeloBayesNaive.predict(review_pruebas.review_es)
df_submission = pd.DataFrame({'id': review_pruebasOriginal['ID'], 'sentimiento': prediccionTesteo})
df_submission.to_csv('submissions/TP2/naiveBayes-nuevo.csv', index=False)
Hacemos un pequeño ajuste sobre los datos de entrada en esta ocación
y_test_transformed = y_test.map(lambda x: 0 if x == 'positivo' else 1)
y_train_transformed = y_train.map(lambda x: 0 if x == 'positivo' else 1)
Para la busqueda de hiperparametros del modelo bayes naives, se debe considerar realizar en simultaneo la optimización tanto del modelo BN como encontrar las condiciones para que la tecnica TF-IDF, implementada por el TfidfVectorizer, mejore las metricas en el analisis de sentimientos
Por lo tanto realizamos la busqueda de hiperparametros usando valores de prueba para intentar mejorar el modelo y por otro lado valores de prueba para el preprocesamiento del texto
if not exists('modelos/TP2/nb_hiper.joblib'):
frecuencia_maxima = [0.9, 0.95, 0.85, 0.98]
frecuencia_minima = [0.05, 0.07, 0.1, 0.03]
ngram_range = [(1,1), (1,2), (3,1)]
alpha = [0.1, 0.5, 1.0, 2.0, 0.8, 0.3, 1.5]
fit_prior = [True, False]
class_prior = [None, [0.2, 0.8], [0.5, 0.5]]
metrica = make_scorer(sklearn.metrics.f1_score)
folds = 5
modelo = Pipeline(steps=[('tfidfvectorizer', TfidfVectorizer()), ('multinomialnb', MultinomialNB())])
parametros = {
'tfidfvectorizer__stop_words': ['english', stopwords_es, None],
'tfidfvectorizer__max_df': frecuencia_maxima,
'tfidfvectorizer__min_df': frecuencia_minima,
'tfidfvectorizer__ngram_range': ngram_range,
'multinomialnb__alpha': alpha,
'multinomialnb__fit_prior': fit_prior,
'multinomialnb__class_prior': class_prior
}
modelo_rcv = RandomizedSearchCV(modelo, parametros, cv=folds, scoring=metrica)
modelo_rcv.fit(x_train.review_es, y_train_transformed)
dump(modelo_rcv, 'modelos/TP2/nb_hiper.joblib')
else:
modelo_rcv = load('modelos/TP2/nb_hiper.joblib')
modelo_rcv
Observamos como se comporta el modelo al estudiarlo a detalle usando la tecnica de random search cross validation
print("F1-Score del modelo: " + str(modelo_rcv.best_score_))
for param, value in modelo_rcv.best_params_.items():
if param == "tfidfvectorizer__stop_words":
continue
print(str(param) + ":" + str(value))
Se observa que el modelo tiene un F1-Score bastante parecido a la primera parte del analisis, más sin embargo, el añadido de estos hiperparametros puede ayudar en la simplificación del modelo y mejorar la generalización
Entrenamos el modelo con los nuevos hiperparametros y realizamos una validacion cruzada para verificar que no haya ningun problema con el modelo seleccionado
if not exists('modelos/TP2/nb_conhp.joblib'):
mejores_parametros = modelo_rcv.best_params_
modelo_final = Pipeline(steps=[
('tfidfvectorizer', TfidfVectorizer(stop_words=mejores_parametros['tfidfvectorizer__stop_words'],
max_df=mejores_parametros['tfidfvectorizer__max_df'],
min_df=mejores_parametros['tfidfvectorizer__min_df'],
ngram_range=mejores_parametros['tfidfvectorizer__ngram_range'])),
('multinomialnb', MultinomialNB(alpha = mejores_parametros['multinomialnb__alpha'],
fit_prior = mejores_parametros['multinomialnb__fit_prior'],
class_prior= mejores_parametros['multinomialnb__class_prior']))
])
modelo_final.fit(x_train.review_es, y_train_transformed)
dump(modelo_final, 'modelos/TP2/nb_conhp.joblib')
else:
modelo_final = load('modelos/TP2/nb_conhp.joblib')
if not exists('modelos/TP2/metricas_nb.joblib'):
kfoldcv =StratifiedKFold(n_splits=10)
resultados_nb = cross_validate(modelo_final, x_train.review_es, y_train, cv=kfoldcv,scoring=metrica ,return_estimator=True)
metricas_nb = resultados_nb['test_score']
modelo_final = resultados_bb['estimator'][np.where(metricas_nb==max(metricas_nb))[0][0]]
else:
metricas_nb = load('modelos/TP2/metricas_nb.joblib')
metric_labelsCV = ['F1 Score']*len(metricas_nb)
sns.set_context('talk')
sns.set_style("darkgrid")
plt.figure(figsize=(6,5))
sns.boxplot(metricas_nb)
plt.title("Modelo verificado con 10 folds")
Observamos que la validación cruzada reatifica el modelo generado y los hiperparametros usados por lo tanto realizamos la prediccion de peso para la competencia de kaggle
if not exists('submissions/TP2/NMmejorado8.csv'):
df_submission = pd.DataFrame({'id': review_pruebas['ID'], 'sentimiento': y_pred})
df_submission['sentimiento'] = df_submission['sentimiento'].map({0: 'positivo', 1: 'negativo'})
df_submission.to_csv('NMmejorado8.csv', index=False)
Creamnos el modelo con parametros arbitrarios parta obtener una prediccion inicial. Decidimos fijar los valores de samples_leaf y samples_split y n_estimators por nuestra cuenta ya luuego de varioas pruebas obsevamos que con valores por default (1, 2 y 100 respectictivamente) genera un modelo muy grande que no podremos subir a gitthub y cuya efectividad no mejorara mucho respecto a uno un poco mas chico
if exists('modelos/TP2/modeloRandomForest-sin-optimizar-final.joblib') == False:
#Creamos un clasificador con hiperparámetros arbitrarios
rfc = RandomForestClassifier(n_jobs=JOBS,
criterion="gini",
random_state=SEED,
min_samples_leaf=15,
min_samples_split=40,
n_estimators=40,
class_weight="balanced")
#rfc = RandomForestClassifier(random_state=SEED, n_jobs=-1)
modeloRandomForest = make_pipeline(TfidfVectorizer(), rfc)
modeloRandomForest.fit(x_train.review_es, y_train)
dump(modeloRandomForest, 'modelos/TP2/modeloRandomForest-sin-optimizar-final.joblib')
else:
modeloRandomForest = load('modelos/TP2/modeloRandomForest-sin-optimizar-final.joblib')
Miramos como fue en test
prediccion_rf = modeloRandomForest.predict(x_test.review_es)
#performance
print(classification_report(y_test,prediccion_rf))
#Creamos la matriz de confusión
tabla=confusion_matrix(y_test, prediccion_rf)
#Grafico la matriz de confusión
sns.heatmap(tabla,cmap='GnBu',annot=True,fmt='g')
plt.xlabel('Predicho')
plt.ylabel('Verdadero')
Hacemos la submission
if not exists('submissions/TP2/randomForest-sin-opti-final.csv'):
prediccionTesteo_rf = modeloRandomForest.predict(review_pruebas.review_es)
df_submission = pd.DataFrame({'id': review_pruebasOriginal['ID'], 'sentimiento': prediccionTesteo_rf})
df_submission.to_csv('submissions/TP2/randomForest-sin-opti-final.csv', index=False)
if exists('modelos/TP2/GsRandomForest_y_tdif-1.joblib') == False:
gsrf = RandomForestClassifier(class_weight="balanced")
modeloRandomForest_cv = Pipeline(steps= [ ('tfidfVectorizer', TfidfVectorizer() ), ('gsrf', gsrf) ] )
param_grid = { "gsrf__criterion" : ["gini", "entropy"],
"gsrf__min_samples_leaf" : [15, 30, 60], #Vamos a hacer muchas combinaciones ya que solo vamos
"gsrf__min_samples_split" : [40, 50, 60],#a correr este modelo 1 sola vez; ya que lo vamos a
"gsrf__n_estimators": [40, 50, 80], #guardar
#Veo si conviene usar stopwords o no.
"tfidfVectorizer__stop_words": [None, 'english', stopwords_es],
"tfidfVectorizer__strip_accents": [None, "ascii"],
"tfidfVectorizer__min_df": [0.01, 0.08, 0.1],
"tfidfVectorizer__max_df" :[ 0.10 , 0.20 , 0.5],
"tfidfVectorizer__lowercase" : [True, False],
"tfidfVectorizer__analyzer": ["word", "char", "char_wb"],
}
#Probamos entrenando sólo con 1 métrica: f1_scoree
rf_gs = GridSearchCV(estimator=modeloRandomForest_cv, param_grid=param_grid, scoring="f1", cv=5, n_jobs=JOBS) #Optimizamos f1_score
#rf_gs = RandomizedSearchCV (estimator=modeloRandomForest_cv, param_grid=param_grid, scoring="f1", cv=5, n_jobs=JOBS) #Optimizamos f1_score
gs_fit = rf_gs.fit(x_train.review_es, y_train)
#guardamos el grid search
dump(gs_fit, 'modelos/TP2/GsRandomForest_y_tdif-1.joblib')
else:
gs_fit = load('modelos/TP2/GsRandomForest_y_tdif-1.joblib')
gs_fit.best_params_
Podemos ver que para los hiperparametros del RF, Grid Search recomienda son los mismos que los habiamos usado arbitrariamente. Esto se debe a que, Grid search, en general, tiende a elegir
el numero mas bajo de sample_leafs, samples_split y n_estimators. Por ello como ya comentamos tomamos como valores mas chicos los usados en el RF sin optimizar para asegurarnos que en la optimizacion de hiperparametros no escoja valores muy pequeños generando un posible overfitting y un modelo muy complejo.
En cuanto a los hiperparametros del vectorizador, recomienda no usar stopwords (las de la lista de la librería NLTK que mandamos como parámetro), no quitar acentos, y usar como analyzer: "word". La principal diferencia es en cuanto al minimo y maximo numero de palabras a tener en cuenta para armar el vocabulario. Recomienda eliminar las palabras que representan menos del 1% del vocabulario y aquellas que representan más del 10% del vocabulario.
#Obtenemos el mejor modelo
mejor_modelo_rf = gs_fit.best_estimator_
#Predicción
prediccion_mejor_modelo_rf = mejor_modelo_rf.predict(x_test.review_es)
prediccion_mejor_modelo_rf
if not exists ('modelos/TP2/modelo_RandomForest_y_tdif-final.joblib'):
dump(mejor_modelo_rf, "modelos/TP2/modelo_RandomForest_y_tdif-final.joblib")
Vemos como le va en test
#performance
print(classification_report(y_test,prediccion_mejor_modelo_rf))
#Creamos la matriz de confusión
tabla=confusion_matrix(y_test, prediccion_mejor_modelo_rf)
#Grafico la matriz de confusión
sns.heatmap(tabla,cmap='GnBu',annot=True,fmt='g')
plt.xlabel('Predicho')
plt.ylabel('Verdadero')
Vemos que f1_score disminuyo. En el informe profundizamos en las posibles causa de esto.
Hacemos Cross validation con 5 folds
kfoldcv =StratifiedKFold(n_splits=5)
scorer_fn = make_scorer(sklearn.metrics.f1_score, pos_label='positivo' )
if not exists ('modelos/TP2/resultados_cv_randomForest-final'):
resultados_rf = cross_validate(mejor_modelo_rf, x_train.review_es, y_train, cv=kfoldcv,scoring=scorer_fn,return_estimator=True, n_jobs = JOBS)
dump(resultados_rf, "modelos/TP2/resultados_cv_randomForest-final")
else:
resultados_rf = load("modelos/TP2/resultados_cv_randomForest-final")
metricas_cv_rf = resultados_rf['test_score']
metric_labels_CV_rf = ['F1 Score']*len(metricas_cv_rf)
sns.set_context('talk')
sns.set_style("darkgrid")
plt.figure(figsize=(8,8))
sns.boxplot(metricas_cv_rf)
plt.title("Modelo entrenado con 5 folds")
Se puede ver que no hay mucha variacion en los valores obtenidos por lo cual podemos concluir que es un modelo bueno para generalizar.
if not exists('submissions/TP2/randomForest-y_tdif-final-.csv'):
prediccionTesteoMejorRf = mejor_modelo_rf.predict(review_pruebas.review_es)
df_submission = pd.DataFrame({'id': review_pruebasOriginal['ID'], 'sentimiento': prediccionTesteoMejorRf})
df_submission.to_csv('submissions/TP2/randomForest-y_tdif-final.csv', index=False)
La submission nos dio una disminucion de 4 ptos en Kaggle. Al igual que con las metricas en el ocnjunto de test, en el infomre profundizamos en la posibles razones de esto.
Realizamos los mismos ajustes hechos para la busqueda de hiperparametros de NB, en este caso, el modelo XGBoost no es capaz de entender las etiquetas positivo/megativo
y_test_transformed = y_test.map(lambda x: 0 if x == 'positivo' else 1)
y_train_transformed = y_train.map(lambda x: 0 if x == 'positivo' else 1)
Generamos un modelo base XGBoost que permitira observar un comportamiento global del modelo a la hora de analizar sentimientos en el texto de las reseñas
if not exists('modelos/TP2/xgb_base.joblib'):
xgb_base = make_pipeline(TfidfVectorizer(), XGBClassifier( random_state=0, n_estimators=100))
xgb_base.fit(x_train.review_es, y_train_transformed)
else:
xgb_base = load("modelos/TP2/xgb_base.joblib")
Evaluando nuestro modelo con las metricas que nos interese
y_pred = xgb_base.predict(x_test.review_es)
Verificamos la performance del modelo y observamos su matriz de confusión
#performance
print(classification_report(y_test_transformed,y_pred))
#Creamos la matriz de confusión
tabla=confusion_matrix(y_test_transformed, y_pred)
#Grafico la matriz de confusión
sns.heatmap(tabla,cmap='GnBu',annot=True,fmt='g')
plt.xlabel('Predicho')
plt.ylabel('Verdadero')
Observamos que el modelo tiene un buen desempeño a la hora de
Generamos una primera predicción para kaggle
if not exists('submissions/TP2/xgboost_base.csv'):
pred = xgb_base.predict(review_pruebas.review_es)
df_submission = pd.DataFrame({'id': review_pruebasOriginal['ID'], 'sentimiento': pred})
df_submission['sentimiento'] = df_submission['sentimiento'].map({0: 'positivo', 1: 'negativo'})
df_submission.to_csv('submissions/TP2/xgboost_base.csv', index=False)
En un intento de mejorar las metricas de prediccion del modelo, como tambien de simplificarlo y generar un modelo más simple con una capacidad de prediccion similar realizamos la busqueda de hiperparamtros del xgboost utilizando parametros conocidos, como también, modificando hiperparametros en el preprocesamiento del texto para mejorar el analisis de sentimientos y ajustar le posible overfit
if not exists('modelos/TP2/hipers_xgb.joblib'):
estimadores = [90, 100, 110, 150]
frecuencia_maxima = [0.9, 0.95, 0.98]
frecuencia_minima = [0.05, 0.07, 0.03]
profundidad_max = [7, 8, 9, 10, 15]
learning_rate = [0.01, 0.05, 0.1, 0.2]
metrica = make_scorer(sklearn.metrics.f1_score)
folds = 5
parametros = {
'tfidfvectorizer__stop_words': ['english', stopwords_es],
'tfidfvectorizer__max_df': frecuencia_maxima,
'tfidfvectorizer__min_df': frecuencia_minima,
'tfidfvectorizer__ngram_range': [(1,1), (1,2)],
'xgbclassifier__n_estimators': estimadores,
'xgbclassifier__max_depth': profundidad_max,
'xgbclassifier__learning_rate': learning_rate,
}
modelo = make_pipeline(TfidfVectorizer(), XGBClassifier())
modelo_rcv = RandomizedSearchCV(modelo, parametros, cv=folds, scoring = metrica)
modelo_rcv.fit(x_train.review_es, y_train)
dump(modelo_rcv, 'modelos/TP2/hipers_xgb.joblib')
else:
metrica = make_scorer(sklearn.metrics.f1_score)
modelo_rcv = load('modelos/TP2/hipers_xgb.joblib')
# print("F1-Score del modelo: " + modelo_rcv.best_score_)
# print("Hiperparametros del modelo con mejor F1-Score:" + modelo_rcv.best_params_)
Entrenamos un nuevo modelo usando los hiperparametros encontrados en la busqueda anterior
if not exists('modelos/TP2/xgb_tuneado.joblib'):
mejores_parametros = modelo_rcv.best_params_
modelo_final = Pipeline(steps=[('tfidfvectorizer', TfidfVectorizer(stop_words=mejores_parametros['tfidfvectorizer__stop_words'],
max_df=mejores_parametros['tfidfvectorizer__max_df'],
min_df=mejores_parametros['tfidfvectorizer__min_df'],
ngram_range=mejores_parametros['tfidfvectorizer__ngram_range'])),
('xgbclassifier', XGBClassifier(n_estimators=mejores_parametros['xgbclassifier__n_estimators'],
max_depth=mejores_parametros['xgbclassifier__max_depth'],
learning_rate=mejores_parametros['xgbclassifier__learning_rate']))])
modelo_final.fit(x_train.review_es, y_train_transformed)
else:
xgb_tuneado = load('modelos/TP2/xgb_tuneado.joblib')
Realizo validacion cruzada del modelo para observar su comportamiento nota TARDA MUCHISIMO
if not exists('modelos/TP2/xgb_metricas.joblib'):
kfoldcv =StratifiedKFold(n_splits=10)
resultados_xgb = cross_validate(xgb_tuneado, x_train.review_es,
y_train_transformed, cv=kfoldcv,
scoring=metrica ,return_estimator=True)
metricas_xgb = resultados_xgb['test_score']
dump(metricas_xgb, 'modelos/TP2/xgb_metricas.joblib')
xgb_tuneado = resultados_xgb['estimator'][np.where(metricas_xgb==max(metricas_xgb))[0][0]]
else:
metricas_xgb = load('modelos/TP2/xgb_metricas.joblib')
metric_labelsCV = ['F1 Score']*len(metricas_xgb)
sns.set_context('talk')
sns.set_style("darkgrid")
plt.figure(figsize=(6,5))
sns.boxplot(metricas_xgb)
plt.title("Modelo entrenado con 10 folds")
Notas sobre XGBoost
El XGBoost más efectivo con un score en kaggle de 0.72 fue generando usando unicamente el conjunto de hiperparametros correspondiente al XGboost y tan solo modificando las stopwords en el procesamiento del texto, sin embargo, se realizo multiples pruebas con multiples hiperparametros y esas busquedas son las reflejadas en el analisis
Antes de tokenizar las reviews, vamos a hacer una pequena limpieza adicional.
if not exists('reviews_filtradas.csv'):
frasesFiltradas = []
for index, value in reviewDf_espanol["review_es"].items():
#Ponemos todas las palabras en lowercase
value = value.lower()
#Saco las stopwords
valueFiltrado = [x for x in value.split() if x not in stopwords_es]
#Vuelvo a unir el texto
valueFiltrado = " ".join(valueFiltrado)
#Saca los diacriticos de letras como vocales, etc (la ñ se mantiene)
#Expresion regular obtenida de: https://es.stackoverflow.com/a/139811
valueFiltrado = re.sub(r"([^n\u0300-\u036f]|n(?!\u0303(?![\u0300-\u036f])))[\u0300-\u036f]+", r"\1",
normalize( "NFD", valueFiltrado), 0, re.I)
valueFiltrado = normalize( 'NFC', valueFiltrado)
#Saco los signos de puntuacion
#Funcion obtenida de: https://stackoverflow.com/a/266162/13683575
valueFiltrado = valueFiltrado.translate(str.maketrans('', '', string.punctuation))
valueFiltrado = valueFiltrado.translate(str.maketrans('', '', '¡'))
valueFiltrado = valueFiltrado.translate(str.maketrans('', '', '¿'))
#Anadimos la frase a la lista de frases filtradas
frasesFiltradas.append(valueFiltrado)
reviewDfFiltrado = pd.DataFrame(data={'review_es':frasesFiltradas, 'sentimiento':reviewDf_espanol['sentimiento']})
reviewDfFiltrado.to_csv('reviews_filtradas.csv', index=False)
else:
reviewDfFiltrado = pd.read_csv("./reviews_filtradas.csv")
reviewDfFiltrado
Transformamos la columna objetivo de "positivo" y "negativo" a 0 y 1 para poder usarlos en la red neuronal
y = reviewDfFiltrado['sentimiento']
y = np.array(list(map(lambda x: 1 if x=="positivo" else 0, y)))
y
Creamos set de train y test
x_train, x_test, y_train, y_test = train_test_split(reviewDfFiltrado["review_es"],
y,
test_size=0.3, #proporcion 70/30
random_state=SEED) #Semilla 9, como el Equipo !!
Creamos el vocabulario en base a nuestro set de train. Las palabras van a estar ordenadas por su frecuencia
vocabulary = Counter()
for fila in x_train:
fila = fila.split()
for i in range(0, len(fila)):
fila[i] = bytes(fila[i], 'utf-8')
vocabulary.update(list(fila))
Vamos a ver si el vocabulario se genero correctamente
vocabulary.most_common()[1:5]
Vemos que la palabra mas frecuente es "pelicula", cosa que hace sentido.
Si no hubiesemos hecho el proceso de remocion de stopwords, es probable que la palabra mas frecuente seria una preposicion
Para reducir el tamaño del vocabulario, vamos a quedarnos solamente con las 10000 palabras mas frecuentes.
vocab_size = 10000
truncated_vocabulary = [
word for word, count in vocabulary.most_common()[:vocab_size]]
Vamos a crear una tabla que asocia cada palabra del diccionario con su coordena (la cual representa su frecuencia en el dataset de entrenamiento)
table = {}
for i in range(0, len(truncated_vocabulary)):
table[truncated_vocabulary[i]] = i
table
Pasamos de la representacion en texto de la reviews a una version tokenizada. Vamos a usar los ids de la tabla para lograrlo
Todas tienen que tener la misma longitud, asique decidimos que todas tengan 300 palabras como maximo.
Si faltan palabras o sobran, le ponemos el valor 10001 (para indicar que esta fuera de nuestro vocabulario)
def pasarDePalabraANumero(conjuntosDePalabras, cantMaxPalabras=300):
listaDeNumeros = []
i=0
for fila in conjuntosDePalabras:
fila = fila.split()
fila = fila[:300]
tokenDeFrase = []
for word in fila:
tokenDeFrase.append(int(table.get(bytes(word, 'utf-8'),10001)))
while len(tokenDeFrase) < 300:
tokenDeFrase.append(10001)
listaDeNumeros.append(np.array(tokenDeFrase))
i+=1
return listaDeNumeros
numerico = pasarDePalabraANumero(x_train)
numericoTest = pasarDePalabraANumero(x_test)
numericoSubmit = pasarDePalabraANumero(review_pruebas['review_es'])
Ejemplo de como se ven:
numerico[66]
data_list = np.stack(numerico)
data_list_test = np.stack(numericoTest)
data_list_submit = np.stack(numericoSubmit)
Vamos a usar la layer intermedia "GRU", siguiendo la guia del libro de la bibiliografia. Esta tiene comportamiento muy similar a la LSTM (las cuales son optimas para el analisis de sentimiento)
embed_size = 128
redNeuronal = keras.models.Sequential([
keras.layers.Embedding(len(table), embed_size,
input_shape=[None]),
keras.layers.GRU(128, return_sequences=True),
keras.layers.GRU(128),
keras.layers.Dense(1, activation="sigmoid")
])
redNeuronal.compile(loss="binary_crossentropy", optimizer="adam",
metrics=["accuracy"])
redNeuronal.summary()
if exists('modelos/TP2/redNeuronalSentimiento2.joblib') == False:
historia_modelo=redNeuronal.fit(x = data_list,
y = y_train, epochs=4)
dump(redNeuronal, 'modelos/TP2/redNeuronalSentimiento2.joblib')
else:
redNeuronal = load('modelos/TP2/redNeuronalSentimiento2.joblib')
if exists('modelos/TP2/predTestRedSinOptimi.joblib') == False:
y_pred = redNeuronal.predict(data_list_test)
y_predCerteza = np.where(y_pred>0.7,1,0)
dump(y_predCerteza, 'modelos/TP2/predTestRedSinOptimi.joblib')
else:
y_predCerteza = load('modelos/TP2/predTestRedSinOptimi.joblib')
y_predCerteza
ds_validacion=pd.DataFrame(y_predCerteza,y_test).reset_index()
ds_validacion.columns=['y_pred','y_real']
tabla=pd.crosstab(ds_validacion.y_pred, ds_validacion.y_real)
grf=sns.heatmap(tabla,annot=True, cmap = 'Blues', fmt='g')
plt.show()
#Calculo las métricas en el conjunto de evaluación
accuracy=accuracy_score(y_test,y_predCerteza)
recall=recall_score(y_test,y_predCerteza)
f1=f1_score(y_test,y_predCerteza,)
precision=precision_score(y_test,y_predCerteza)
print("Accuracy: "+str(accuracy))
print("Recall: "+str(recall))
print("Precision: "+str(precision))
print("f1 score: "+str(f1))
if not exists('submissions/TP2/redesNeuronales2.csv'):
yEnEspanol =y_predCerteza
yEnEspanol = np.array(list(map(lambda x: "positivo" if x==1 else "negativo", y_predCerteza)))
df_submission = pd.DataFrame({'id': review_pruebasOriginal['ID'], 'sentimiento': yEnEspanol})
df_submission.to_csv('submissions/TP2/redesNeuronales2.csv', index=False)
Vamos a realizar la busqueda de hiperparametros:
loss='binary_crossentropy'
metrics=['accuracy']
optimizer="adam"
def creador_modelo(learning_rate = 0.1,
activation = 'sigmoid',
output = 2,
hidden_layers = 2
):
modeloHiper = keras.Sequential()
modeloHiper.add(keras.layers.Embedding(len(table), embed_size,
input_shape=[None]))
modeloHiper.add(keras.layers.GRU(128, return_sequences=True))
modeloHiper.add(keras.layers.GRU(128))
for i in range(hidden_layers):
modeloHiper.add(keras.layers.Dense(output, activation=activation))
modeloHiper.add(keras.layers.Dense(1, activation="sigmoid"))
modeloHiper.compile(
optimizer=optimizer,
loss=loss,
metrics=metrics,
)
return modeloHiper
model = KerasClassifier(build_fn=creador_modelo,
verbose=1)
param_grid = {
"hidden_layers" : [1, 5, 10, 15, 20],
"output" : [1, 2, 4, 8, 32, 64],
"batch_size" : [5, 10, 20],
"epochs" : [5, 10, 15],
"activation": ["sigmoid", "relu", "softmax", "softplus", "elu", ]
}
Vamos a realizar 5 random searches para buscar los mejores hiperparametros para la red
rs = RandomizedSearchCV(estimator=model,
param_distributions=param_grid,
n_jobs=JOBS, cv=3,
n_iter=8)
if exists('modelos/TP2/rs_redNeuronal.joblib') == False:
rs_redNeuronal=rs.fit(X = data_list,
y = y_train)
dump(rs_redNeuronal, 'modelos/TP2/rs_redNeuronal.joblib')
else:
rs_redNeuronal = load('modelos/TP2/rs_redNeuronal.joblib')
rs_redNeuronal.best_params_
embed_size = 128
modeloHiper = keras.models.Sequential()
modeloHiper.add(keras.layers.Embedding(len(table), embed_size,
input_shape=[None]))
modeloHiper.add(keras.layers.GRU(128, return_sequences=True))
modeloHiper.add(keras.layers.GRU(128))
for i in range(rs_redNeuronal.best_params_['hidden_layers']):
modeloHiper.add(keras.layers.Dense(rs_redNeuronal.best_params_['output'],
activation=rs_redNeuronal.best_params_['activation']))
modeloHiper.add(keras.layers.Dense(1, activation="sigmoid"))
modeloHiper.compile(loss="binary_crossentropy", optimizer="adam",
metrics=["accuracy"])
if exists('modelos/TP2/redNeuronalHiper.joblib') == False:
historia_modelo=modeloHiper.fit(x = data_list,
y = y_train, epochs=rs_redNeuronal.best_params_['epochs'])
dump(modeloHiper, 'modelos/TP2/redNeuronalHiper.joblib')
else:
modeloHiper = load('modelos/TP2/redNeuronalHiper.joblib')
if exists('modelos/TP2/predTestRedConOptimi.joblib') == False:
y_pred = modeloHiper.predict(data_list_test)
y_pred
y_predCerteza = np.where(y_pred>0.7,1,0)
y_predCerteza
dump(y_predCerteza, 'modelos/TP2/predTestRedConOptimi.joblib')
else:
y_predCerteza = load('modelos/TP2/predTestRedConOptimi.joblib')
y_predCerteza
ds_validacion=pd.DataFrame(y_predCerteza,y_test).reset_index()
ds_validacion.columns=['y_pred','y_real']
tabla=pd.crosstab(ds_validacion.y_pred, ds_validacion.y_real)
grf=sns.heatmap(tabla,annot=True, cmap = 'Blues', fmt='g')
plt.show()
#Calculo las métricas en el conjunto de evaluación
accuracy=accuracy_score(y_test,y_predCerteza)
recall=recall_score(y_test,y_predCerteza)
f1=f1_score(y_test,y_predCerteza,)
precision=precision_score(y_test,y_predCerteza)
print("Accuracy: "+str(accuracy))
print("Recall: "+str(recall))
print("Precision: "+str(precision))
print("f1 score: "+str(f1))
if exists('modelos/TP2/predTestRedConOptimi.joblib') == False:
y_pred = modeloHiper.predict(data_list_submit)
y_predCerteza = np.where(y_pred>0.7,1,0)
dump(y_predCerteza, 'modelos/TP2/predTestRedConOptimi.joblib')
else:
y_predCerteza = load('modelos/TP2/predTestRedConOptimi.joblib')
y_predCerteza
if not exists('submissions/TP2/redesNeuronales3.csv'):
yEnEspanol =y_predCerteza
yEnEspanol = np.array(list(map(lambda x: "positivo" if x==1 else "negativo", y_predCerteza)))
df_submission = pd.DataFrame({'id': review_pruebasOriginal['ID'], 'sentimiento': yEnEspanol})
df_submission.to_csv('submissions/TP2/redesNeuronales3.csv', index=False)
Armamos un ensamble sencillo de tipo voting con los modelos bases generados a lo largo del analisis.
La utilización de los modelos bases se debe a que utilizamos varios predictores debiles con el proposito de construir uno que en conjunto funcione mejor y asi aprovechar tambien los modelos diseñados previamente
if not exists('modelos/TP2/voting.joblib.gz'): #Tenemos el archivo comprimido porque era muy grande
naives = load("modelos/TP2/nb_conhp.joblib")
xgb = load("modelos/TP2/xgb_base.joblib")
rf = load("modelos/TP2/modeloRandomForest-sin-optimizar-final.joblib")
ensamble = VotingClassifier(estimators = [
('nb', naives),
('xgb', xgb),
('rf', rf)
])
ensamble.fit(x_train, y_train)
dump(ensamble, 'modelos/TP2/voting.joblib')
!gzip modelos/TP2/voting.joblib #Comprimimos
else:
!gzip -d -k modelos/TP2/voting.joblib.gz #Descomprimimos
ensamble = load('modelos/TP2/voting.joblib')
!rm modelos/TP2/voting.joblib
Entrenamos el modelo
ensamble
Sobre el ensamble realizamos una validacion cruzada para comprobar que su metricas al estar asociadas a las metricas individuales de los modelos siguie siendo optima
kfoldcv = StratifiedKFold(n_splits=5)
scorer_fn = make_scorer(sklearn.metrics.f1_score)
if not exists ('modelos/TP2/ensambleCrossvalidation.joblib.gz.part-aa'):
resultados_rf = cross_validate(ensamble, x_train,
y_train, cv=kfoldcv,
scoring=scorer_fn,
return_estimator=True, n_jobs = JOBS)
dump(resultados_rf, "modelos/TP2/ensambleCrossvalidation.joblib")
#Comprimimos el archivo
!gzip -v -9 modelos/TP2/ensambleCrossvalidation.joblib
#Separamos en varios archivos para que github no nos haga drama. #GithubDestruidoConElPoderDeUnix
!split -b 50m modelos/TP2/ensambleCrossvalidation.joblib.gz modelos/TP2/ensambleCrossvalidation.joblib.gz.part-
!rm modelos/TP2/ensambleCrossvalidation.joblib.gz
else:
#Reconstruimos el zip
!cat modelos/TP2/ensambleCrossvalidation.joblib.gz.part-a* > modelos/TP2/ensambleCrossvalidation.joblib.gz
#Descomprimimos
!gzip -d -k modelos/TP2/ensambleCrossvalidation.joblib.gz
resultados_rf = load('modelos/TP2/ensambleCrossvalidation.joblib')
!rm modelos/TP2/ensambleCrossvalidation.joblib
!rm modelos/TP2/ensambleCrossvalidation.joblib.gz
metricas_cv_rf = resultados_rf['test_score']
metricas_cv_rf
metric_labels_CV_rf = ['F1 Score']*len(metricas_cv_rf)
sns.set_context('talk')
sns.set_style("darkgrid")
plt.figure(figsize=(8,8))
sns.boxplot(metricas_cv_rf)
plt.title("Modelo entrenado con 5 folds")
if not exists('submissions/TP2/ensamble.csv'):
pred = ensamble.predict(review_pruebas.review_es)
df_submission = pd.DataFrame({'id': review_pruebasOriginal['ID'], 'sentimiento': pred})
df_submission['sentimiento'] = df_submission['sentimiento'].map({1: 'positivo', 0: 'negativo'})
df_submission.to_csv('submissions/TP2/ensamble.csv', index=False)
Nota sobre el ensamble hibrido:
El modelo de mejor performance en kaggle que elevo la posicion del equipo es un modelo incompleto puesto que esta armado unicamente con dos modelos, pero, el modelo expuesto en esta porción del analisis incluye los dos modelos usados en el armado del ensamble que genero la mejor predicción
De manera conclusiva es importante destacar que los ensambles son los modelos que mejor encaran la predicción en el analisis de sentimientos de las reviews de peliculas