top of page
Search
  • Writer's picturesiria sadeddin

Detección de Cancer de Piel con Deep Learning.TERCERA PARTE: Entrenamiento de Modelos

Updated: May 2, 2020

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': [50100200,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:


Sensitividad: 0.70

Especifidad: 0.70

PV+: 0.69

PV-: 0.70

Accuracy rf: 0.70

Precision rf: 0.70

Recall rf: 0.71



Nuestro trabajo es decidir cuales métricas son mas importantes en nuestro modelo. En efecto, predecir correctamente la mayor cantidad de lesiones malignas es mas importante que predecir correctamente las que no son malignas. Por eso elegiremos el resultado con mayor precisión y sensitividad, y de hecho los Bosques Aleatorios tiene mayor accuracy (desempeño general) y mayor precisión.


Bosques Aleatorios es el ganador! 😍


Con esto damos por terminada la primera parte del proyecto, en los siguientes posts estaremos trabajando el conjunto de imágenes disponibles. Usaremos las técnicas de procesamiento de imágenes y Transfer Learning para de predecir si una lesión es maligna o benigna.


Hasta pronto!!


109 views0 comments
Post: Blog2_Post
bottom of page