<jupyter_start><jupyter_text>TP 2 - algoritmos de ensambles  El objetivo de este trabajo práctico es analizar las particularidades de la utilización de algoritmos de ensambles aplicados en casos casi reales. El mismo pretende fijar conceptos estudiados en la teoría: métodos de ensamble; random forests; boosting; sobreajuste; tolerancia al ruido. El material básico para la elaboración del presente trabajo se encuentra en las teóricas y prácticas presentadas hasta el momento y en las próximas clases y en las referencias bibliográficas indicadas [1,2,3]. Podrá utilizarse cualquier otra fuente siempre que esté correctamente referenciada.El presente trabajo será grupal. El grupo deberá estar compuesto por exactamente tres integrantes. Se evaluarán los contenidos del Trabajo Práctico durante el coloquio posterior a la entrega del TP junto al material teórico y práctico enseñado en las clases de toda la materia. Todos los integrantes deben tener conocimiento del desarrollo del TP. **Opcionales (dan puntos extra)**:* Sumar al punto c) un modelo de perceptrón multicapa.* Evaluar el desempeño de los modelos al utilizar el conjunto de atributos ComParE 2016. **La fecha límite de entrega es el lunes 28 de junio a las 17:00 hs.** **tbd** * Sacar a otro notebook la búsqueda de hiperparámetros de adabost, randomforest y bagging* Sacar a otro notebook el entrenamiento del perceptron  Cargar librerias<jupyter_code>import datetime
import IPython
import math
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import sweetviz as sv
import time
from matplotlib import pyplot
from numpy import mean
from numpy import std
from urllib.parse import urlparse
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB, GaussianNB
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt
from scipy import stats
from scipy.stats import chi2_contingency
from sklearn.calibration import calibration_curve
from sklearn.impute import KNNImputer
from sklearn.feature_selection import RFE
from sklearn.metrics import (
    roc_curve,
    make_scorer,
    accuracy_score,
    fbeta_score,
    precision_score,
    recall_score,
    f1_score,
    classification_report,
    confusion_matrix,
)
from sklearn.model_selection import (
    train_test_split,
    cross_val_score,
    StratifiedKFold,
    KFold,
    GridSearchCV,
    RandomizedSearchCV,
    RepeatedStratifiedKFold,
)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn import tree
import pickle
from sklearn.model_selection import LeaveOneGroupOut

from sklearn.ensemble import BaggingClassifier
from sklearn.model_selection import KFold, cross_val_score
# # Observación: Descomentar en caso de que quieran versionar los modelos y la búsqueda de hiperparámetros.
# import mlflow
# import mlflow.sklearn
# from mlflow.tracking import MlflowClient<jupyter_output><empty_output><jupyter_text>Cargar dataset<jupyter_code>df_original = pd.read_csv("01.df.procesado.csv")
# df_original = pd.read_csv("https://raw.githubusercontent.com/blukitas/AA-2021/main/TPs/02.TP/01.df.procesado.csv")
df_original.head()
num_columns = [col for col in df_original.columns if df_original[col].dtype != "object"]
num_columns
drop_columns = [
    "file_path",
    "Unnamed: 0",
    "modality",
    "vocal_channel",
#     "emotion",
    "emotional_intensity",
    "statement",
    "repetition",
#     "actor",
]
num_columns = [x for x in num_columns if x not in drop_columns]
num_columns
df_proc = df_original[num_columns] #.head(10000)<jupyter_output><empty_output><jupyter_text>Split del dataset<jupyter_code>from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    df_proc.loc[:, df_proc.columns != "emotion"],
    df_proc["emotion"],
    stratify=df_proc["emotion"],
    random_state=66,
)<jupyter_output><empty_output><jupyter_text>Desarrollo de modelos de ensambleB) Elegir un modelo de ensamble visto durante las clases y entrenarlo para predecir emociones dividiendo el dataset de las siguientes maneras:    i) 12-fold cross validation armando los folds de forma aleatoria.    ii) Leave-2-speakers out. Es decir, 12 folds conteniendo cada uno 2 actores distintos. Se recomienda utilizar la función de sklearn LeaveOneGroupOutReportar los resultados obtenidos y realizar un análisis de los mismos, teniendo en cuenta las características del dataset utilizado.  Métricasf1Si no está desbalanceado accuracy puede ser bien. F1 es un poco más robusta, pensando en que tiene en cuenta recall y precision.No hay elementos que inclinen la balanza.<jupyter_code>plt.bar(
    df_proc.groupby(["emotion"]).loudness_sma3_amean.count().index,
    df_proc.groupby(["emotion"]).loudness_sma3_amean.count(),
)<jupyter_output><empty_output><jupyter_text>Setup mlflowMlflow es una herramienta para versionado de modelos. Permite registrar paramétros, métricas y guardar los modelos entrenados asociados a una iteración.En caso de no ser necesario comentar estas lineas.<jupyter_code># # now = datetime.datetime.now()
# mlflow.set_tracking_uri("http://mlflow_server:5000")
# tracking_uri = mlflow.get_tracking_uri()
# print("Current tracking uri: {}".format(tracking_uri))
# # No hace falta el create, el set lo crea si no existe
# experiment = mlflow.set_experiment(
#     "AA.TP2.Bagging.{0}".format(now.strftime("%Y-%m-%d"))
# )<jupyter_output>INFO: 'AA.TP2.RandomizedSearchCV.2021-06-24' does not exist. Creating a new experiment<jupyter_text>Método elegido -> BaggingBagging con n árboles Combinan strong learners, los cuales tienen varianza alta, para reducir su varianza. El bagging consiste en utilizar distintos subsets de los datos de entrenamiento para entrenar cada learner. Los subsets se generan tomando muestras con reemplazo. Si no se hace reemplazo se denomina **pasting**, y si en lugar de tomar subsets sobre los datos, los tomamos sobre los atributos, se denomina **random subspaces**. La combinación de bagging y random subspaces se denomina **random patches**.https://www.pluralsight.com/guides/ensemble-methods:-bagging-versus-boostingScikit Learn cuenta un tipo de objetos llamados meta-estimadores para representar a los ensambles. Este nombre se debe a que estos meta-estimadores necesitan definir estimadores base que van a ser justamente los modelos que se ajusten en cada uno de los pasos de bootstrapping. Como modelos/estimadores base podemos instanciar cualquier estimador de sklearn. Para el caso de Bagging, el meta-estimador para clasificación es:https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.BaggingClassifier.htmlEste objeto tiene como principal parámetro a base_estimator en el cual instanciaremos un modelo. Veamos un pequeño ejemplo: **tbd** Acomodarlo a los árboles que usamos<jupyter_code># Version 0 del árbol

# kfold = KFold(n_splits = 12,
#                        random_state = 42, shuffle=True)
  
# # initialize the base classifier
# # TBD: Inicializar con el mejor árbol
# #      ¿Se puede inicializar con n arboles?
# # TBD: Hacer un par y compararlos? Tiene sentido?
# base_cls = DecisionTreeClassifier()
  
# # no. of base classifier
# num_trees = 1000
  
# # bagging classifier
# model = BaggingClassifier(base_estimator = base_cls,
#                           n_estimators = num_trees,
#                           random_state = 42)
  
# # max_samples en float nos dice que porcentaje del training set queremos remuestrear
# # max_features nos da un grado mas de aleatorizacion, muestreo en las columnas/features del conjunto de datos
# results = cross_val_score(model, X_train, y_train, cv = kfold)

# # fit
# model.fit(X_train, y_train)
start = time.time()

kfold = StratifiedKFold(n_splits=12, random_state=42, shuffle=True)
scorer_fn = make_scorer(
    f1_score, average="weighted"
)  # seteamos una metrica apropiada al problema multiclase

base_cls = DecisionTreeClassifier(class_weight="balanced")

# no. of base classifier
num_trees = 1000

# bagging classifier
model = BaggingClassifier(
    base_estimator=base_cls,
    n_estimators=num_trees,
    random_state=123,
    # shuffle=True,
    n_jobs=10,
    verbose=True,
)

results_f1 = cross_val_score(
    estimator=model, X=X_train, y=y_train, cv=kfold, n_jobs=10, scoring=scorer_fn
)
model.fit(X_train, y_train)

end = time.time()
print(f"Tiempo: { round(end - start, 2) } segundos")
# Ref: https://machinelearningmastery.com/save-load-machine-learning-models-python-scikit-learn/
pickle.dump(model, open('03.02.Bagging_stratified_balanced.pkl', 'wb'))<jupyter_output><empty_output><jupyter_text>Comparación de modelosC) Realizar la división de datos de la manera que considere más adecuada, teniendo en cuenta el análisis realizado en b), y comparar el desempeño de Random Forests y AdaBoost. Realizar una búsqueda de hiperparámetros para ambos modelos. Para esto, deberá elegirse una métrica de evaluación y justificar la elección. Reportar y analizar la matriz de confusión del mejor modelo, tanto para el caso de audios cantados como hablados.1. Train/Test2. Métrica3. Busqueda hiperparámetros (Cuáles son los hiperparametros? Cual el espacio de búsqueda acotado?)    * Random forest    * Adaboost4. Comparación de modelos    * Matriz de confusión5. Comprar ambos modelos    * Cantados    * Hablados Métrica f1  Random ForestsRandom Forest es una implementación de un algoritmo de *Bagging* que propone como estimadores base a **n_estimators** árboles de decisión. Cada árbol verá un subset distinto de los datos de entrenamiento (bagging, llamado **bootstrap** en los parámetros de sklearn). A su vez, para elegir el mejor split en cada nivel, se tienen en cuenta solo **max_features** atributos, seleccionados al azar. Estas 2 técnicas combinadas llevan a obtener árboles que sean distintos entre si y estén decorrelacionados, lo cual es bueno ya que si un árbol comete errores, probablemente otros no lo cometerán y se compensarán. Cuanto más independientes sean los árboles entre si más reduciremos la varianza. random forest pelado<jupyter_code>from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_estimators=1000, random_state=42)
rf.fit(X_train, y_train)
print("Accuracy training : {:.3f}".format(rf.score(X_train, y_train)))
print("Accuracy test: {:.3f}".format(rf.score(X_test, y_test)))
# Ref: https://machinelearningmastery.com/save-load-machine-learning-models-python-scikit-learn/
pickle.dump(rf, open('03.03.Random_forest.pkl', 'wb'))
# now = datetime.datetime.now()
# mlflow.set_tracking_uri("http://mlflow_server:5000")
# tracking_uri = mlflow.get_tracking_uri()
# print("Current tracking uri: {}".format(tracking_uri))

# # No hace falta el create, el set lo crea si no existe
# experiment = mlflow.set_experiment(
#     "AA.TP2.RandomForest.{0}".format(now.strftime("%Y-%m-%d"))
# )<jupyter_output>Current tracking uri: http://mlflow_server:5000<jupyter_text>Busqueda de hiperparametros random forest<jupyter_code>from sklearn.model_selection import RandomizedSearchCV, GridSearchCV
start = time.time()

parameters = {
    "n_estimators": range(50, 5000, 500),
    "bootstrap": [True],
#     'max_depth': [80, 90, 100, 110],
#     'max_features': [2, 3],
#     'min_samples_leaf': [3, 4, 5],
}
kfoldcv = StratifiedKFold(n_splits=12)
clf = RandomizedSearchCV(
    RandomForestClassifier(max_features=None),
    parameters,
    n_jobs=10,
    scoring=scorer_f1,
    cv=kfoldcv,
    n_iter=100,
    verbose=10,
    random_state=42
)
clf.fit(X_train, y_train)
end = time.time()
# Tiempo de búsqueda de hiperparámetros
print(f"Tiempo: { round(end - start, 2) } segundos")
rf = clf.best_estimator_
print(clf.best_score_, clf.best_params_)
# Visualizar las opciones que nos devolvió
aux = pd.DataFrame(clf.cv_results_).head()
aux.to_csv('03.04.AA.TP2.RandomForest.{0}.csv'.format(now.strftime("%Y-%m-%d")))
# # Explorar la columnas para registrarlas en mlflow
# params = [x for x in aux.columns if "param_" in x]
# print(params)
# score = [x for x in aux.columns if "score" in x]
# print(score)
# col_time = [x for x in aux.columns if "time" in x]
# print(time)
# # Registro y versionado de modelo en mlflow
# #   Comentar en caso de no estar utilizandolo

# start = time.time()
# for index, row in aux.iterrows():
#     with mlflow.start_run(experiment):
#         mlflow.log_param('Busqueda', 'RandomizedSearchCV')
#         for i in params:
#             # print('param: {0}, valor: {1}'.format(i, row[i]))
#             mlflow.log_param(i, row[i])

#         for i in score:
#             # print('param: {0}, valor: {1}'.format(i, row[i]))
#             mlflow.log_metric(i, row[i])

#         for i in col_time:
#             # print('param: {0}, valor: {1}'.format(i, row[i]))
#             mlflow.log_metric(i, row[i])
# end = time.time()
# print(f"Tiempo: { end - start }")
# Ref: https://machinelearningmastery.com/save-load-machine-learning-models-python-scikit-learn/
pickle.dump(clf, open('03.04.Random_forest_search.pkl', 'wb'))
# Ref: https://machinelearningmastery.com/save-load-machine-learning-models-python-scikit-learn/
pickle.dump(rf, open('03.04.Random_forest.pkl', 'wb'))
# En caso de estar reejecutando => load model
# loaded_model = pickle.load(open('03.02.Bagging_balanced.pkl', 'rb'))
loaded_model = pickle.load(open('03.04.Random_forest.pkl', 'rb'))
features = [x for i, x in enumerate(df_proc.columns) if i != 8]
feat_importances = loaded_model.feature_importances_

plt.figure(figsize=(100, 60))
graph_importance = sns.barplot(
    y=features,
    x=feat_importances,
    orient="h",
    order=reversed(np.array(features)[np.argsort(feat_importances)]),
)


graph_importance.figure.savefig(
    "03.04.Random_forest.feature_importante.png", bbox_inches='tight'
)
# df_proc.loc[:, df_proc.columns != "emotion"],
#     df_proc["emotion"],
    
importancia_predictores = pd.DataFrame(
    {"Predictor": df_proc.loc[:, df_proc.columns != "emotion"], "Importancia": loaded_model.feature_importances_}
)
print("Importancia de los predictores en el modelo")
print("-------------------------------------------")
importancia_predictores.sort_values("Importancia", ascending=False)
y_pred_test = model.predict(X_test)
y_pred_train = model.predict(X_train)
# evaluación
print("Accuracy training : {:.3f}".format(clf.score(X_train, y_train)))
print("Accuracy test: {:.3f}".format(clf.score(X_test, y_test)))
print("Train acc: {}".format(accuracy_score(y_train, y_pred_train)))
print("Test acc: {}".format(accuracy_score(y_test, y_pred_test)))
print("Test recall: {}".format(recall_score(y_test, y_pred_test, average='weighted')))
print("Test F1: {}".format(fbeta_score(y_test, y_pred_test, beta=1, average='weighted')))
print("Test F2: {}".format(fbeta_score(y_test, y_pred_test, beta=2, average='weighted')))
cm = confusion_matrix(y_test, y_pred_test)
matriz_n_aria = sns.heatmap(cm, cmap="Blues", annot=True, fmt="g")
plt.xlabel("Predicted")
plt.ylabel("True")


matriz_n_aria .figure.savefig(
    "03.04.Random_forest.matriz.png", bbox_inches='tight'
)<jupyter_output><empty_output><jupyter_text>AdaBoostEn cada iteración construye un stump nuevo y le da más peso a las muestras que fueron clasificadas mal por el anterior stump. De esta manera, en cada iteración se intenta corregir los errores que cometió el anterior clasificador. Una vez armado el ensamble, cada stump tendrá asociado un peso en sus predicciones de acuerdo a qué tan bueno fue clasificando durante el entrenamiento. A continuación les dejamos un video explicativo del funcionamiento del algoritmo:https://www.youtube.com/watch?v=LsK-xG1cLYA&ab_channel=StatQuestwithJoshStarmerStatQuestwithJoshStarmerVerifiedLa implementación de AdaBoost de sklearn nos provee de un meta-estimador para Boosting, al cual le podemos pasar cualquier estimador (por defecto usa un arbol de profundidad 1, un stump):https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.AdaBoostClassifier.html?highlight=adaboostsklearn.ensemble.AdaBoostClassifierPaper original:https://web.stanford.edu/~hastie/Papers/samme.pdf<jupyter_code>start = time.time()
kfold = StratifiedKFold(n_splits=12, random_state=42, shuffle=True)
from sklearn.ensemble import AdaBoostClassifier

parameters = {
    "n_estimators": range(10, 3000, 5),
    "learning_rate": np.arange(0, 3, 0.001),
}
clf = RandomizedSearchCV(
    AdaBoostClassifier(DecisionTreeClassifier(class_weight="balanced")),
    parameters,
    n_jobs=12,
    scoring=scorer_f1,
    cv=kfold,
    n_iter=1000,
    verbose=10
)

clf.fit(X_train, y_train)
ab = clf.best_estimator_
print(clf.best_score_, clf.best_params_)
print("F1-score training : {:.3f}".format(ab.score(X_train, y_train)))
print("F1-score test: {:.3f}".format(ab.score(X_test, y_test)))

end = time.time()
print(f"Tiempo: { end - start }")
import pickle

now = datetime.datetime.now()

pickle.dump(ab, open('03.05.tst.AdaBoost_clf.pkl', 'wb'))
pickle.dump(ab, open('03.05.tst.AdaBoost.pkl', 'wb'))

clf_results = pd.DataFrame(clf.cv_results_)

clf_results.to_csv('03.05.tst.AdaBoost.csv')
clf_results

# matriz de confusión y classification_report
y_pred_test = ab.predict(X_test)
confusion_matrix(y_test, y_pred_test)
# heat map
cm = confusion_matrix(y_test, y_pred_test)
sns.heatmap(cm, cmap="Blues", annot=True, fmt="g")
plt.xlabel("Predicted")
plt.ylabel("True")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10199075922651303, 'fraction_numerical': 0.014260452911984484, 'mean_word_length': 5.142957252978276, 'pattern_counts': {'":': 6, '<': 35, '<?xml version=': 0, '>': 37, 'https://': 10, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '520336', 'license': 'MIT', 'n_tokens_mistral': 6237, 'n_tokens_neox': 5979, 'n_words': 1641, 'path': 'TPs/02.TP/03.Ensambles.ipynb', 'repo_name': 'blukitas/AA-2021', 'size': '17531'}