<jupyter_start><jupyter_text>División de datos Train-Validation-Test<jupyter_code>from sklearn.datasets import load_breast_cancer
import pandas as pd

data_breast = load_breast_cancer()<jupyter_output><empty_output><jupyter_text>Transformar el dataset en un dataframe:<jupyter_code>df_data = pd.DataFrame(data_breast['data'],columns=data_breast['feature_names'])
df_data['target'] = data_breast['target']
features = data_breast['feature_names']
target = 'target'<jupyter_output><empty_output><jupyter_text>Veamos qué tan balanceados están los datos:<jupyter_code>df_data['target'].value_counts(normalize=True)<jupyter_output><empty_output><jupyter_text>Dividamos los datos usando splits de entrenamiento, validacion y prueba. Hagamos 70, 15, 15.<jupyter_code>#Con sklearn podemos dividir nuestros datos en conjuntos de entrenamiento, validación y prueba.

from sklearn import tree
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(df_data[features].values, df_data[target].values, test_size=0.3, random_state=42)
x_val, x_test, y_val, y_test = train_test_split(x_test,y_test,test_size=0.5)<jupyter_output><empty_output><jupyter_text>Podemos ver ahora como quedo<jupyter_code>print('Entrenamiento: {}'.format(len(x_train)))
print('Validacion: {}'.format(len(x_val)))
print('Prueba: {}'.format(len(x_test)))<jupyter_output>Entrenamiento: 398
Validacion: 85
Prueba: 86<jupyter_text>Tambien podemos ver como quedaron balanceadas las clases en cada split:<jupyter_code>for split_name, split in zip(['Entrenamiento','Validacion','Prueba'],[y_train,y_val,y_test]):
  print('{}: {:.3f}'.format(split_name,pd.Series(split).value_counts(normalize=True)[0]))<jupyter_output>Entrenamiento: 0.374
Validacion: 0.341
Prueba: 0.395<jupyter_text>Se puede observar que en cada uno de los splits, la proporción de las clases, por hacerse aleatoriamente la división, es distinta. Esto podría llegar a afectarnos. Una posibilidad es hacer splits estratificados:<jupyter_code>x_train, x_test, y_train, y_test = train_test_split(df_data[features].values, df_data[target].values, test_size=0.3, random_state=42,stratify=df_data[target].values)
x_val, x_test, y_val, y_test = train_test_split(x_test,y_test,test_size=0.5,stratify=y_test)
print('Entrenamiento: {}'.format(len(x_train)))
print('Validacion: {}'.format(len(x_val)))
print('Prueba: {}'.format(len(x_test)))<jupyter_output>Entrenamiento: 398
Validacion: 85
Prueba: 86<jupyter_text>Ahora los splits tienen mas o menos el mismo balance<jupyter_code>for split_name, split in zip(['Entrenamiento','Validacion','Prueba'],[y_train,y_val,y_test]):
  print('{}: {:.3f}'.format(split_name,pd.Series(split).value_counts(normalize=True)[0]))<jupyter_output>Entrenamiento: 0.372
Validacion: 0.376
Prueba: 0.372<jupyter_text>Vamos a separarnos un set de evaluación<jupyter_code>x_train, x_test, y_train, y_test = train_test_split(df_data[features].values, df_data[target].values, test_size=0.3, random_state=42,stratify=df_data[target].values)<jupyter_output><empty_output><jupyter_text>Ejercicio 1 (20 minutos)Hacer 100 divisiones distintas train-val. Medir accuracy, precision, recall y F1. Hacer un gráfico de caja y bigotes de estas métricas.Comparar las gráficas de dos tipos de árboles:* Árbol sencillo: profundidad 10, ccp_alpha=0.2* Árbol complejo: profundidad > 20, min_samples_leaf<5, ccp_alpha=0¿Cómo varían las distribuciones? ¿Por qué?¿Para este problema, qué es preferible, un mayor precision o recall?Graficar el árbol sencillo con sus decisiones<jupyter_code>from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.tree import plot_tree
import seaborn as sns
import matplotlib.pyplot as plt

n_seeds = 100
accs = []
precisions = []
recalls = []
f1s = []

for seed in range(n_seeds):
  x_train_, x_val, y_train_, y_val = train_test_split(x_train, y_train, test_size=0.2, random_state=seed,stratify=y_train)
  arbol_sklearn = DecisionTreeClassifier(criterion='entropy',max_depth=5,min_samples_leaf=10, ccp_alpha=0.01,class_weight='balanced')
  arbol_sklearn.fit(x_train_,y_train_)

  y_pred_val = arbol_sklearn.predict(x_val)
  accs.append(accuracy_score(y_val,y_pred_val))
  precisions.append(precision_score(y_val,y_pred_val))
  recalls.append(recall_score(y_val,y_pred_val))
  f1s.append(f1_score(y_val,y_pred_val))

all_metrics = accs + precisions + recalls + f1s
metric_labels = ['Accuracy']*len(accs) + ['Precision']*len(precisions) + ['Recall']*len(recalls) + ['F1 Score']*len(f1s)

sns.set_context('talk')
plt.figure(figsize=(15,8))
sns.boxplot(metric_labels,all_metrics)
plt.figure(figsize=(20,10))
plot_tree(arbol_sklearn,filled=True,proportion=False);<jupyter_output><empty_output><jupyter_text>Búsqueda de hiperparámetros y validación cruzada Ahora queremos entrenar árboles de decisión. Teníamos varios **hiperparámetros** para decidir, como la profundidad, función de costo, número de instancias mínimo en una hoja, etc... Con sklearn, es posible explorar combinaciones de estos hiperparámetros asi elegimos la que mejores resultados den.Ahora bien, tenemos que elegir una métrica en base a la cual tomar la decisión, y tenemos que tener datos en los que evaluar. En lugar de usar splits de entrenamiento-validación, vamos a usar k-fold cross-validation.<jupyter_code>from sklearn.model_selection import StratifiedKFold, KFold, GridSearchCV, RandomizedSearchCV
from sklearn.metrics import make_scorer
import numpy as np

params_grid = {'criterion':['gini','entropy'],
               'max_depth':list(range(1,30)),
               'ccp_alpha':np.linspace(0,0.5,100)}

kfoldcv = StratifiedKFold(n_splits=10)
base_tree = DecisionTreeClassifier()
scorer_fn = make_scorer(f1_score)
randomcv = RandomizedSearchCV(estimator=base_tree,param_distributions = params_grid,scoring=scorer_fn,cv=kfoldcv,n_iter=100)
randomcv.fit(x_train,y_train);<jupyter_output><empty_output><jupyter_text>Podemos ver todas las combinaciones de parámetros y qué puntajes obtuvieron, junto a tiempos de entrenamiento y predicción.<jupyter_code>pd.DataFrame(randomcv.cv_results_).head()<jupyter_output><empty_output><jupyter_text>Podemos ver cuál fue la mejor combinación de hiperparámetros:<jupyter_code>randomcv.best_params_<jupyter_output><empty_output><jupyter_text>Podemos pedir el árbol correspondiente ya entrenado y ver cuán importante es cada atributo<jupyter_code>best_tree = randomcv.best_estimator_
feat_imps = best_tree.feature_importances_
feat_imps<jupyter_output><empty_output><jupyter_text>Se puede ver que una gran parte de los atributos fueron ignorados por el árbol y por eso valen cero. Es decir, el árbol no tomo ninguna decisión basándose en ellos. Ahora veamos a qué variables corresponden los índices con importancia mayor a 0<jupyter_code>for feat_imp,feat in sorted(zip(feat_imps,features)):
  if feat_imp>0:
    print('{}: {}'.format(feat,feat_imp))<jupyter_output>symmetry error: 0.014431039788937894
mean texture: 0.03261844914346901
worst texture: 0.03587911510268348
mean concavity: 0.05825023581759141
worst concave points: 0.18260314874957892
worst radius: 0.6762180113977393<jupyter_text>Metricas<jupyter_code>from sklearn.metrics import confusion_matrix, classification_report

best_model = randomcv.best_estimator_
y_pred = best_model.predict(x_test)

print(classification_report(y_test,y_pred))
cm = confusion_matrix(y_test,y_pred)
sns.heatmap(cm, cmap='Blues',annot=True,fmt='g')
plt.xlabel('Predicted')
plt.ylabel('True')<jupyter_output><empty_output><jupyter_text>A veces se utiliza la proporción de instancias en la hoja de la predicción como una probabilidad arrojada por el modelo. Si graficamos esas probabilidades para las instancias de ambas clases nos da algo asi:<jupyter_code>y_scores = best_model.predict_proba(x_test)
out_probs = y_scores[:,1]

plt.figure(figsize=(15,7))
sns.distplot(out_probs[y_test==0],color='r',label='Negative')
sns.distplot(out_probs[y_test==1],color='b',label='Positive')
plt.legend()<jupyter_output>/usr/local/lib/python3.7/dist-packages/seaborn/distributions.py:2557: FutureWarning: `distplot` is a deprecated function and will be removed in a future version. Please adapt your code to use either `displot` (a figure-level function with similar flexibility) or `histplot` (an axes-level function for histograms).
  warnings.warn(msg, FutureWarning)
/usr/local/lib/python3.7/dist-packages/seaborn/distributions.py:2557: FutureWarning: `distplot` is a deprecated function and will be removed in a future version. Please adapt your code to use either `displot` (a figure-level function with similar flexibility) or `histplot` (an axes-level function for histograms).
  warnings.warn(msg, FutureWarning)<jupyter_text>El modelo nunca tiene incertidumbre (las probabilidades siempre estan cerca de 0 o 1, nunca de 0.5). Esto es porque justamente todas las hojas quedan bastante puras. Sin embargo, hay errores (curva roja donde debia estar azul y curva azul donde debia ser roja). Además se pueden observar picos, esto es porque las probabilidades son discretas en vez de continuas. Hay tantas probabilidades posibles como hojas tenga el árbol.<jupyter_code>from sklearn.calibration import calibration_curve

fraction_of_positives, mean_predicted_value = calibration_curve(y_test,out_probs,n_bins=10)
plt.figure(figsize=(10,10))
plt.plot(mean_predicted_value,fraction_of_positives)
plt.plot(mean_predicted_value,mean_predicted_value,'k--')
plt.ylim(0,1)
plt.xlim(0,1)<jupyter_output><empty_output><jupyter_text>[ROC Curve](http://arogozhnikov.github.io/2015/10/05/roc-curve.html)<jupyter_code>from sklearn.metrics import roc_curve

fpr,tpr,th = roc_curve(y_test,out_probs)

plt.title('ROC decision tree')
plt.plot(fpr,tpr)
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.plot(fpr,fpr,'--k')<jupyter_output><empty_output><jupyter_text>El principal problema de los árboles de decisión es que overfitean facilmente y son muy sensibles a los datos de entrada. Más adelante en la teoría, se verán los random forests, que basicamente son muchos árboles juntos. Podemos probar mirar las probabilidades que devuelve un random forest.<jupyter_code>from sklearn.ensemble import RandomForestClassifier

forest = RandomForestClassifier(n_estimators=200)
forest.fit(x_train,y_train)
y_pred_rf = forest.predict_proba(x_test)
out_probs = y_pred_rf[:,1]

plt.figure(figsize=(15,7))
sns.distplot(out_probs[y_test==0],color='r')
sns.distplot(out_probs[y_test==1],color='b')<jupyter_output>/usr/local/lib/python3.7/dist-packages/seaborn/distributions.py:2557: FutureWarning: `distplot` is a deprecated function and will be removed in a future version. Please adapt your code to use either `displot` (a figure-level function with similar flexibility) or `histplot` (an axes-level function for histograms).
  warnings.warn(msg, FutureWarning)
/usr/local/lib/python3.7/dist-packages/seaborn/distributions.py:2557: FutureWarning: `distplot` is a deprecated function and will be removed in a future version. Please adapt your code to use either `displot` (a figure-level function with similar flexibility) or `histplot` (an axes-level function for histograms).
  warnings.warn(msg, FutureWarning)<jupyter_text>Al menos tiene mejor pinta... Veamos si está calibrado:<jupyter_code>fraction_of_positives, mean_predicted_value = calibration_curve(y_test,out_probs,n_bins=10)
plt.figure(figsize=(10,10))
plt.plot(mean_predicted_value,fraction_of_positives)
plt.plot(mean_predicted_value,mean_predicted_value,'k--')
plt.ylim(0,1)
plt.xlim(0,1)
fpr,tpr,th = roc_curve(y_test,out_probs)

plt.title('ROC random forest')
plt.plot(fpr,tpr)
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.plot(fpr,fpr,'--k')<jupyter_output><empty_output><jupyter_text>Efecto del balanceMétricas y desbalancehttps://machinelearningmastery.com/tour-of-evaluation-metrics-for-imbalanced-classification/Oversampling con SMOTE/ADASYN:https://machinelearningmastery.com/smote-oversampling-for-imbalanced-classification/  Ejercicio 2 (20 minutos)i) Crear datasets aleatorio en el que las proporciones de la clase 0 sean: [0.01,0.1,0.25,0.5,0.75,0.9,0.99].ii) Usar StratifiedKFold con K=5, entrenar y validar en cada fold un árbol de decisiones. Utilizar el metodo split de StratifiedKFold. Realizar lo mismo para cada dataset creado en i). Reportar los valores de accuracy, recall, precision y F1.<jupyter_code>DATASET_SIZE = 10000
N_FEATURES = 30
x_random = np.random.uniform(size=(DATASET_SIZE,N_FEATURES))
y_randoms = []
props = [0.01,0.1,0.25,0.5,0.75,0.9,0.99]
for prop in props:
  y_i = np.ones(shape=(int(DATASET_SIZE,)))
  y_i[:int(DATASET_SIZE*(1-prop))] = 0
  y_randoms.append(y_i)
from sklearn.metrics import matthews_corrcoef

metrics = []
proportions = []
vals = []
for prop,y in zip(props,y_randoms):
  tree = randomcv.best_estimator_
  kfoldcv = StratifiedKFold(n_splits=5)
  for idx_train, idx_val in kfoldcv.split(x_random,y):
    
    x_train = x_random[idx_train]
    y_train = y[idx_train]
    x_val = x_random[idx_val]
    y_val = y[idx_val]
    tree.fit(x_train,y_train)
    y_pred = tree.predict(x_val)
    acc = accuracy_score(y_val,y_pred)
    f1 = f1_score(y_val,y_pred)
    rec = recall_score(y_val,y_pred)
    pr = precision_score(y_val,y_pred)
    matthews = matthews_corrcoef(y_val,y_pred)
    vals.extend([acc,f1,rec,pr,matthews])
    proportions.extend([prop]*5)
    metrics.extend(['Accuracy','F1-Score','Recall','Precision','Matthews'])

metric_results = pd.DataFrame({'metric':metrics,'proportion':proportions,'value':vals})
plt.figure(figsize=(20,10))
sns.barplot(x='metric',y='value',hue='proportion',data=metric_results)<jupyter_output>/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1272: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))
/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:900: RuntimeWarning: invalid value encountered in double_scalars
  mcc = cov_ytyp / np.sqrt(cov_ytyt * cov_ypyp)
/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1272: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))
/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:900: RuntimeWarning: invalid value encountered in double_scalars
  mcc = cov_ytyp / np.sqrt(cov_ytyt * cov_ypyp)
/usr/local/lib/python3.7/dist-packages/sklearn[...]<jupyter_text>TP/(TP+FN)Positivos = 0.99CLAS = POSFN = 0REC = 10.01CLAS = NEGFN = 0.01%TP =<jupyter_code><jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10973881352500506, 'fraction_numerical': 0.026996018087332117, 'mean_word_length': 7.457762557077626, 'pattern_counts': {'":': 0, '<': 86, '<?xml version=': 0, '>': 87, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, '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': '23', 'dir': 'jupyter-structured-clean-dedup', 'id': '569790', 'license': 'MIT', 'n_tokens_mistral': 5702, 'n_tokens_neox': 5353, 'n_words': 1140, 'path': 'Clases/09.Practica.Metricas.Modelos/Practica_Datos_y_Metricas_Soluciones.ipynb', 'repo_name': 'blukitas/AA-2021', 'size': '14817'}