Hola! 🙃
En el post anterior estudiamos como el sexo, edad y posición de una lesión dermatológica son estadísticamente útiles para inferir si la lesión es maligna o benigna. En este post desarrollaremos distintos tipos de modelos de Machine Learning que son usados para modelos de clasificación.
Usaremos la librería sklearn de python y los modelos :
1) K primeros vecinos: KNeighborsClassifier 2) Árboles de decisión: DecisionTreeClassifier
3) Bosques Aleatorios: RandomForestClassifier
En algún otro post nos ocuparemos de estudiar a fondo estos modelos. Por ahora no ocuparemos en como se usan para nuestro objetivo.
Lo primero que haremos es seleccionar las variables independientes (de entrada) y las dependientes (de salida) de nuestro modelo
X=data[['age_approx','anatom_site_general','sex']]
y=data['is_ca'].replace({ 'benign':0, 'malignant':1})
Estos datos deben pasar por un pre-proceso: las variables categóricas de texto (sitio de la lesión y sexo) se transforman en variables categóricas numéricas (o "dummies" en inglés) (0 y 1) para ser recibidas por los modelos. La variable de salida 'y' ya la hemos convertido en una variable binaria (0 o 1) o 'dummy'.
X=pd.concat([X.drop('anatom_site_general',axis=1),pd.get_dummies(X['anatom_site_general'])],axis=1)
X['sex']=X['sex'].replace({'female': 1, 'male': 0})
X.head()
La edad, por tratarse de una variable continua la hemos dejado sin tocar, la variable sexo se ha convertido en una variable categórica numérica con 1 para mujer y 0 para hombre, mientras que la variable de "posición anatómica" de la lesión se ha convertido en una variable de "one hot encoder" donde cada valor único se ha convertido en una nueva columna, el 1 indica que la lesión se encuentra en la posición (nombre de columna) dada, mientras que el 0 indica que la lesión no se encuentra en el sitio anatómico que indica la columna. Se llama a esta configuración "one hot encoder" ya que solo una de las columnas puede contener el valor 1, y las demás tendrán valor 0.
Como mencionamos en el primer post, nuestros datos no están balanceados para las categorías maligno y benigno. Es necesario que balanceemos los datos para que ninguna de las categorías tenga una preferencia en la predicción.
Importamos la función RandomUnderSampler para seleccionar aleatoriamente valores de la categoría con mayor número de ejemplos hasta que ambas categorías esten balanceadas.
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=0)
X_resampled, y_resampled = rus.fit_resample(X, y)
print(sum(y_resampled==1))
print(sum(y_resampled==0))
9058
9058
Como siguiente paso antes de hacer el entrenamiento, las variables de entrada deben ser reescaladas o estandarizadas, para que todas se encuentren en una misma escala de referencia.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_resampled=scaler.fit_transform(X_resampled)
X_resampled
Bien! ahora nuestros datos están balanceados y estandarizados 🤩.
Pero recordemos que nuestro objetivo no es solo entrenar un modelo, debemos comprobar que éste es efectivo. Por eso, antes de entrenar debemos separar nuestros datos en conjunto de entrenamiento y validación, generalmente esto se hace en conjuntos de 70% para entrenamiento y 30% para validación.
Crearemos los conjuntos de datos de entrenamiento y validación con la función "train_test_split" de la librería 'sklearn' de python
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_resampled, y_resampled, test_size=0.3, random_state=42,stratify=y_resampled)
#number of samples in train and test sets
print('train set number of samples:',X_train.shape[0])
print('test set number of samples:',X_test.shape[0])
train set number of samples: 12681
test set number of samples: 5435
Vamos a importar los modelos de K primeros vecinos "KNeighborsClassifier", árboles de decisión "DecisionTreeClassifier" y bosques aleatorios de la librería "sklearn"
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn import metrics
K primeros vecinos
El primer modelo que implementaremos es el de K primeros vecinos. No es obvio cuantos vecinos debemos tomar en cuenta para obtener el mejor modelo posible, para saber cual es el número óptimo de vecinos en el entrenamiento lo que haremos es iterar sobre el modelo para k entre 1 y 30 y evaluar el desempeño de cada modelo en la iteración, así hallaremos el K que nos brinde mejor accuracy.
#define empty array for accuracy
acc=[]
#iteration of knn training for k between 1 and 30
for k in range(1,30):
knn=KNeighborsClassifier(n_neighbors=k)
knn.fit(X_train, y_train)
y_pred3=knn.predict(X_test)
accu=metrics.accuracy_score(y_pred3, y_test)
acc.append(accu)
#plots accuracy vs k
plt.plot(acc)
plt.title('Accuracy vs k')
plt.ylabel('Accuracy')
plt.xlabel('k')
Vemos que escoger k alrededor de 20 es una buena aproximación para tener el mejor accuracy posible de el modelo knn. Haremos el entrenamiento y validaremos el accuracy.
#define model
knn=KNeighborsClassifier(n_neighbors=np.argmax(acc))
#fit model
knn.fit(X_train, y_train)
#predict over testing
y_knn=knn.predict(X_test)
#model metrics
print("Accuracy knn:",metrics.accuracy_score(y_knn, y_test))
print("Precision knn:",metrics.precision_score(y_test, y_knn))
print("Recall knn:",metrics.recall_score(y_test, y_knn))
Accuracy knn: 0.67
Precision knn: 0.69
Recall knn: 0.61
Existe una forma automática de hacer la búsqueda de los mejores parámetros para el modelo usando la función "GridSearchCV" de sklearn. Lo que haremos será buscar entre 1 y 30 vecinos y haciendo 5 validaciones cruzadas "CV" el modelo que tenga un mejor ajuste a los datos.
from sklearn.model_selection import GridSearchCV
#define model
knn=KNeighborsClassifier()
#define grid search parameters
params_knn = {'n_neighbors':list(range(1,30))}
#use gridsearch to test all values for n_estimators
knn = GridSearchCV(knn, params_knn, cv=5)
#fit model
knn.fit(X_train, y_train)
#test model
y_knn=knn.predict(X_test)
#model metrics
print("Accuracy knn:",metrics.accuracy_score(y_knn, y_test))
print("Precision knn:",metrics.precision_score(y_test, y_knn))
print("Recall knn:",metrics.recall_score(y_test, y_knn))
Accuracy knn: 0.67
Precision knn: 0.67
Recall knn: 0.68
Vemos que se ha obtenido una mejora en las métricas resultantes, sobre todo en el Recall.
Construimos una función para hacer la matriz de confusión del modelo
import seaborn as sns
def conf_matrix(matrix,pred):
class_names=[0,1] # name of classes
fig, ax = plt.subplots()
tick_marks = np.arange(len(class_names))
plt.xticks(tick_marks, class_names)
plt.yticks(tick_marks, class_names)
# create heatmap
sns.heatmap(pd.DataFrame(matrix), annot=True, cmap="YlGnBu" ,fmt='g')
ax.xaxis.set_label_position("top")
plt.tight_layout()
plt.title('Confusion matrix', y=1.1)
plt.ylabel('Actual label')
plt.xlabel('Predicted label')
plt.show()
cnf_matrix1 = metrics.confusion_matrix(y_test, y_knn)
conf_matrix(cnf_matrix1,y_knn)
Árboles de decisión
Usaremos un procedimiento similar al de knn para hacer un nuevo modelo de árboles de decisión para nuestros datos, iterando sobre es max_depth (profundidad máxima) entre 1 y 30.
acc=[]
for depth in range(1,30):
dt=DecisionTreeClassifier(max_depth = depth, random_state = 0)
dt.fit(X_train, y_train)
y_dt=dt.predict(X_test)
accu=metrics.accuracy_score(y_dt, y_test)
acc.append(accu)
plt.plot(acc)
plt.title('Accuracy vs k')
plt.ylabel('Accuracy')
plt.xlabel('k')
Vemos que hay un pico donde el accuracy se hace máximo para la curva accuracy vs k (max_depth)
Evaluamos nuevamente el resultado
#get DecisionTreeClassifier model
dt=DecisionTreeClassifier(max_depth =np.argmax(acc) , random_state = 0)
#fit model
dt.fit(X_train, y_train)
#predict over test set
y_dt=dt.predict(X_test)
#get model metrics
print("Accuracy dt:",metrics.accuracy_score(y_dt, y_test))
print("Precision dt:",metrics.precision_score(y_test, y_dt))
print("Recall dt:",metrics.recall_score(y_test, y_dt))
Accuracy dt: 0.69
Precision dt: 0.69
Recall dt: 0.70
Usaremos "grid search" para una búsqueda automática
#get model
dt=DecisionTreeClassifier(random_state = 0)
#define model parameters
params_dt = {'max_depth':list(range(1,30))}
#use gridsearch to test all values for n_estimators
dt = GridSearchCV(dt, params_dt, cv=5)
#fit over train data
dt.fit(X_train, y_train)
#predict over test data
y_dt=dt.predict(X_test)
#model metrics
print("Accuracy dt:",metrics.accuracy_score(y_dt, y_test))
print("Precision dt:",metrics.precision_score(y_test, y_dt))
print("Recall dt:",metrics.recall_score(y_test, y_dt))
Accuracy dt: 0.69
Precision dt: 0.69
Recall dt: 0.70
Bosques Aleatorios:
#create a new random forest classifier
rf = RandomForestClassifier()
#create a dictionary of all values we want to test for n_estimators
params_rf = {'n_estimators': [50, 100, 200,300],'max_depth':[10,20,30,40,50]}
#use gridsearch to test all values for n_estimators
rf = GridSearchCV(rf, params_rf, cv=5)
#fit model to training data
rf.fit(X_train, y_train)
#predict model on test data
y_rf=rf.predict(X_test)
#evaluate model metrics
print("Accuracy knn:",metrics.accuracy_score(y_rf, y_test))
print("Precision knn:",metrics.precision_score(y_test, y_rf))
print("Recall knn:",metrics.recall_score(y_test, y_rf))
Accuracy rf: 0.70
Precision rf: 0.70
Recall rf: 0.71
#show confusion matrixcnf_matrix1 = metrics.confusion_matrix(y_test, y_rf)conf_matrix(cnf_matrix1,y_rf)
Procederemos a evaluar los resultado de este modelo observando las métricas: Accuracy, precisión, recall, Positive predictive value (PPV) y Negative predictive value (NPV).
Dentro de la matriz de confusión podemos definir los valores en la cuadrícula como sigue:
En la matriz de confusión para nuestros modelos tenemos:
K vecinos:
Verdadero Positivo (VP):1716
Verdadero Negativo (VN):1756
Falso Positivo (FP):864
Falso Negativo (FN):827
Arboles de decisión:
Verdadero Positivo (VP):1805
Verdadero Negativo (VN):1779
Falso positivo (FP):801
Falso negativo (FN):775
Bosques Aleatorios:
Verdadero Positivo (VP):1821
Verdadero Negativo (VN):1781
Falso positivo (FP):799
Falso negativo (FN):759
Podemos calculas la sensitividad y especifidad de los modelos, así como también los valores predictivos positivo y negativo. Haremos uso de las fórmulas siguientes:
El resumen de métricas de evaluación para los modelos que hemos entrenado es el siguiente:
K vecinos:
Sensitividad: 0.67
Especifidad: 0.67
PV+: 0.67
PV-: 0.67
Accuracy knn: 0.67
Precision knn: 0.67
Recall knn: 0.68
Árboles de Decisión:
Sensitividad: 0.70
Especifidad: 0.69
PV+: 0.70
PV-: 0.69
Accuracy dt: 0.69
Precision dt: 0.69
Recall dt: 0.70
Bosques aleatorios:
Comentarios