Redes neuronales¶


Clase1: Introducción a Keras¶


Autor: Luis Fernando Apáez Álvarez

Contenido¶

  • División de los datos
  • Definición de la arquitectura
  • Entrenamiento y evaluación

Keras es una API de aprendizaje profundo de alto nivel. En clases anteriores estuvimos comparando a Keras con Tensorflow (el cual es una API de bajo nivel); asimismo, en clases anteriores programamos una red neuronal utilizando tensorflow y keras. A partir de esta clase veremos cómo programar una red neuronal utilizando plenamente la librería Keras.

Para ello considerares el siguiente conjunto de datos

In [3]:
import pandas as pd
data = pd.read_csv('DatosTumoresBM.csv')
data.head()
Out[3]:
id diagnosis radius_mean texture_mean perimeter_mean area_mean smoothness_mean compactness_mean concavity_mean concave points_mean ... texture_worst perimeter_worst area_worst smoothness_worst compactness_worst concavity_worst concave points_worst symmetry_worst fractal_dimension_worst Unnamed: 32
0 842302 M 17.99 10.38 122.80 1001.0 0.11840 0.27760 0.3001 0.14710 ... 17.33 184.60 2019.0 0.1622 0.6656 0.7119 0.2654 0.4601 0.11890 NaN
1 842517 M 20.57 17.77 132.90 1326.0 0.08474 0.07864 0.0869 0.07017 ... 23.41 158.80 1956.0 0.1238 0.1866 0.2416 0.1860 0.2750 0.08902 NaN
2 84300903 M 19.69 21.25 130.00 1203.0 0.10960 0.15990 0.1974 0.12790 ... 25.53 152.50 1709.0 0.1444 0.4245 0.4504 0.2430 0.3613 0.08758 NaN
3 84348301 M 11.42 20.38 77.58 386.1 0.14250 0.28390 0.2414 0.10520 ... 26.50 98.87 567.7 0.2098 0.8663 0.6869 0.2575 0.6638 0.17300 NaN
4 84358402 M 20.29 14.34 135.10 1297.0 0.10030 0.13280 0.1980 0.10430 ... 16.67 152.20 1575.0 0.1374 0.2050 0.4000 0.1625 0.2364 0.07678 NaN

5 rows × 33 columns

en el cual se tiene la información de distintas medidas hechas a tumores y donde la columna de interés, variable objetivo o de respuesta, es diagnosis que indica si el tumor es maligno M o benigno B.

Veamos cuántos elementos tenemos de cada clase:

In [5]:
data.diagnosis.value_counts()
Out[5]:
B    357
M    212
Name: diagnosis, dtype: int64

Notamos además que la última columna tiene valores nulos, por lo cual la eliminaremos:

In [6]:
# Todas las entradas son nulas
data['Unnamed: 32'].unique()
Out[6]:
array([nan])
In [9]:
# Realizamos la eliminacion de dicha columna
data = data.drop('Unnamed: 32', axis=1)

Además eliminaremos la primer columna pues no aporta informacion relevante

In [38]:
# Realizamos la eliminacion de dicha columna
data = data.drop('id', axis=1)

Y realizaremos el cambio de M a 0 y de B a 1, pues requerimos que la etiqueta a predecir sea numérica:

In [51]:
data['diagnosis'] = data.diagnosis.apply(lambda x: 1 if x == 'B' else 0)
data['diagnosis']
Out[51]:
0      0
1      0
2      0
3      0
4      0
      ..
564    0
565    0
566    0
567    0
568    1
Name: diagnosis, Length: 569, dtype: int64

División de los datos ¶

Para poder hacer predicciones con la red neuronal que más adelante programaremos, requerirmos entrenar primero el modelo y después ver el desempeño de dicho modelo. Esto es, del conjunto de datos anterior requerimos un subconjunto de datos para realizar el entrenamiento de la red neuronal y requerimos un subconjunto para evaluar el poder predictivo de la red. Así, requerimos del conjunto de entrenamiento y prueba. Por lo general, del conjunto de datos completo, se toman el 75% de los datos para destinarlos al entrenamiento y el 25% restante para el conjunto de pruebas.

Cabe la pena mencionar que el conjunto de pruebas son datos que la red no ha visto, en lo cual radica que dicho conjunto nos sirva para evaluar el poder predictivo de la red. Después de evaluar la red, y una vez que hemos obtenido buenas métricas de evaluación (lo cual veremos más adelante), entonces nuestro modelo esta listo para hacer predicciones con nuevos datos. De tal manera, lo anterior lo podemos resumir en los siguientes pasos:

  1. División de los datos en conjuntos de entrenamiento y pruebas.
  2. Definición de la arquitectura de la red neuronal (número de capas, número de neuronas por capa, valores de los hiperparámetros). Por lo general se suelen definir dos capas ocultas en las redes neuronales, pues con tres podríamos ya caer en peligro del sobreajuste (lo que significa que la red memoriza los datos, lo cual no buscamos, más bien buscamos que la red detecte patrones que después se puedan generalizar para hacer las predicciones). Por otro lado, los hiperparámetros puedes ser, por ejemplo, el número de épocas del entrenamiento, el número de lotes que se toman, etcétera.
  3. Entrenamiento de la red neuronal utilizando el conjunto de entrenamiento.
  4. Evaluación de la red neuronal. Para ello podemos utilizar, para problemas de clasificación, la precición del modelo, donde comparamos la etiqueta predicha por la red y la etiqueta real (que proviene del conjunto de pruebas). Existen muchas más métricas que veremos más adelante.
  5. Si es necesario, en busca del mejor modelo, deberemos regresar al punto 2. En realidad hay diversas estrategía, donde para hallar el mejor modelo podemos regresar hasta la base de los datos. Por ejemplo, estandarizar variables, agregar más información, tomar muestras equilibradas, etcétera, lo cual también veremos en clases posteriores.

Lo que haremos entonces será realizar la división de los datos, pero primero debemos definir la variable X correspondiente a las características y la variable y corespondiente a la variable predictoria

In [53]:
# caracteristicas
X = data.drop('diagnosis', axis=1)

# variable objetivo
y = data['diagnosis']

Después será necesario convertir dichas variables a arrays. Tenemos dos, en principio, opciones para ello:

  • Convertirlos directamente a arrays numpy: np.array()
  • Utilizar el atributo values que extraer la información de los dataframes como valores de un array.

Lo que haremos será:

In [54]:
X = X.values
y = y.values

Luego veamos la forma del array y:

In [55]:
print(y.shape)
print(y)
(569,)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 1 0 0 0 0 0 0 0 0 1 0 1 1 1 1 1 0 0 1 0 0 1 1 1 1 0 1 0 0 1 1 1 1 0 1 0 0
 1 0 1 0 0 1 1 1 0 0 1 0 0 0 1 1 1 0 1 1 0 0 1 1 1 0 0 1 1 1 1 0 1 1 0 1 1
 1 1 1 1 1 1 0 0 0 1 0 0 1 1 1 0 0 1 0 1 0 0 1 0 0 1 1 0 1 1 0 1 1 1 1 0 1
 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 0 1 1 0 0 1 1 0 0 1 1 1 1 0 1 1 0 0 0 1 0
 1 0 1 1 1 0 1 1 0 0 1 0 0 0 0 1 0 0 0 1 0 1 0 1 1 0 1 0 0 0 0 1 1 0 0 1 1
 1 0 1 1 1 1 1 0 0 1 1 0 1 1 0 0 1 0 1 1 1 1 0 1 1 1 1 1 0 1 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 1 1 1 1 1 1 0 1 0 1 1 0 1 1 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1
 1 0 1 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 1 1 0 0 0 1 1
 1 1 0 1 0 1 0 1 1 1 0 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 1 0 0
 0 1 0 0 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 0 1 1 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1
 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0 1 0 1 1 1 1 1 0 1 1
 0 1 0 1 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 1
 1 1 1 1 1 1 0 1 0 1 1 0 1 1 1 1 1 0 0 1 0 1 0 1 1 1 1 1 0 1 1 0 1 0 1 0 0
 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 0 0 0 0 0 0 1]

Usualmente se requiere que la variable de respuesta tenga la forma (n, 1). Notemos que la forma de y es de (`569,) por lo cual debemos utilizar reshape() como sigue

In [56]:
y = y.reshape(-1,1)
y.shape
Out[56]:
(569, 1)

Ahora sí ya podemos realizar la división de los datos:

In [58]:
# Importacion necesaria
from sklearn.model_selection import train_test_split

# Definimos 4 variables: 2 para el conjunto de entrenamiento
# y 2 para las pruebas

X_train, X_test, y_train, y_test = train_test_split(X, y, # conjunto completo
                                       test_size=0.25,    # 25% para pruebas
                                       random_state=123)  # semilla aleatoria  

Definición de la arquitectura ¶

Una vez que hemos definidos los conjuntos de entrenamiento y pruebas, definiremos la arquitectura de la red. Como se mencionó al inicio, por lo general se trabajan con redes neuronales de dos capas ocultas. Luego, para la capa de salida de la red definiremos una neurona, pues sólo queremos predecir una valor (etiqueta), ya sea M o B.

Notemos cuántas características consideramos:

In [59]:
X.shape[1]
Out[59]:
30

De tal manera, la red neuronal deberá de recibir 31 datos, esto es, la red neuronal tendrá 31 neuronas iniciales que recibirán dicha información. Con lo que tenemos hasta ahora, respecto a la arquitectura de la red, nos falta determinar el número de neuronas para las capas ocultas. No hay una estrategia precisa para definir ese número de neuronas, pero nos podemos ayudar ingresando, inicialmente, al menos el doble del número de entradas. Esto es, para la primer capa oculta consideraremos 62 neuronas. Luego, para la siguiente capa oculta disminuimos ese número de neuronas, digamos por ejemplo 40. Con base en ello:

In [60]:
# importaciones necesarias:
from keras.models import Sequential
from keras.layers import Dense

# Instanciamos el modelo
model = Sequential()

# Capa de entrada de 31 neuronas y capa oculta de 62 neuronas.
# Para la capa oculta utilizamos la funcion de activacion relu
model.add(Dense(62, activation='relu', input_shape=(30,)))

# Capa oculta 2:
# 40 neuronas y funcion de activacion relu
model.add(Dense(40, activation='relu'))

# Capa de salida. Dado que es un problema de clasificacion binaria
# utilizamos la funcion de activacion sigmoide
model.add(Dense(1, activation='sigmoid'))

Finalmente realizaremos la compilación del modelo, donde:

  • utilizaremos el optimizador adam;
  • la función de pérdida será binary_crossentropy pues estamos en un problema de clasificación binaria;
  • colocaremos metrics=['accuracy'] para observar a la hora de entrenar la precisión obtenida del modelo para cada época.

De tal manera:

In [61]:
model.compile('adam', loss='binary_crossentropy', metrics=['accuracy'])
In [47]:
# Vemos un resumen sobre nuestro modelo
model.summary()
Model: "sequential_3"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_9 (Dense)             (None, 62)                1922      
                                                                 
 dense_10 (Dense)            (None, 40)                2520      
                                                                 
 dense_11 (Dense)            (None, 1)                 41        
                                                                 
=================================================================
Total params: 4,483
Trainable params: 4,483
Non-trainable params: 0
_________________________________________________________________

Entrenamiento del modelo ¶

Ahora, lo que haremos será realizar el entrenamiento del modelo, pero estaremos variando los valores de las épocas y los lotes. Cabe la pena mencionar que si configuramos un número alto de épocas, corremos peligro del sobreajuste. Luego, configuramos un número de lote para el entrenamiento para que la convergencia del "descenso del gradiente" sea más suave.

Así, realizaremos el entrenamiento de un primer modelo:

In [62]:
# epocas: 5
modelo_0 = model.fit(X_train, y_train, epochs=5)
Epoch 1/5
14/14 [==============================] - 1s 8ms/step - loss: 22.4257 - accuracy: 0.5000
Epoch 2/5
14/14 [==============================] - 0s 2ms/step - loss: 4.7998 - accuracy: 0.5141
Epoch 3/5
14/14 [==============================] - 0s 2ms/step - loss: 1.4431 - accuracy: 0.8122
Epoch 4/5
14/14 [==============================] - 0s 2ms/step - loss: 0.6079 - accuracy: 0.8826
Epoch 5/5
14/14 [==============================] - 0s 1ms/step - loss: 0.4495 - accuracy: 0.8967

Vemos cómo la precisión del modelo, en el entrenamiento, partió del 50% en la primer época y después llegó al 89% en la última. Veamos ahora la precisión del modelo anterior sobre el conjunto de pruebas:

In [63]:
scores = model.evaluate(X_test, y_test)
print('%s: %.4f%%' % (model.metrics_names[1], scores[1] * 100))
5/5 [==============================] - 0s 2ms/step - loss: 0.3015 - accuracy: 0.9231
accuracy: 92.3077%

Lo cual nos arroja una precisión del 92% en el conjunto de pruebas, lo cual nos deja en un muy buen modelo.

Para comparar los distintos resultados obtenidos de variar el número de épocas y el tamañio del lote, definiremos la siguiente función:

In [67]:
def model_result(num_epocas, num_lote):
    """num_epocas: número de épocas
       num_lote: tamañio de los lotes"""
    # Instanciamos el modelo
    model = Sequential()
    # Capa de entrada de 31 neuronas y capa oculta de 62 neuronas.
    # Para la capa oculta utilizamos la funcion de activacion relu
    model.add(Dense(62, activation='relu', input_shape=(30,)))
    # Capa oculta 2:
    # 40 neuronas y funcion de activacion relu
    model.add(Dense(40, activation='relu'))
    # Capa de salida. Dado que es un problema de clasificacion binaria
    # utilizamos la funcion de activacion sigmoide
    model.add(Dense(1, activation='sigmoid'))
    # Compilacion
    model.compile('adam', loss='binary_crossentropy', metrics=['accuracy'])
    # Entrenamiento
    modelo = model.fit(X_train, y_train, epochs=num_epocas, 
                       batch_size=num_lote, verbose=False)
    # Evaluacion sobre el conjunto de pruebas
    scores = model.evaluate(X_test, y_test)
    print('%s: %.4f%%' % (model.metrics_names[1], scores[1] * 100))
    # Retornamos el modelo obtenido
    return model

donde verbose=False hace que no se muestre la información que se desplega en el entrenamiento.

Probaremos un modelo como model_0 pero agregando un número para el tamañio de los lotes:

In [68]:
model_1 = model_result(5, 32)
5/5 [==============================] - 0s 2ms/step - loss: 0.1736 - accuracy: 0.9371
accuracy: 93.7063%

Notamos una mejora en la precisión. Probemos más modelos:

In [69]:
# Aumentamos el numero de epocas
model_2 = model_result(10, 32)
5/5 [==============================] - 0s 2ms/step - loss: 0.2761 - accuracy: 0.9231
accuracy: 92.3077%
In [70]:
# Aumentamos el numero de epocas
model_3 = model_result(15, 32)
5/5 [==============================] - 0s 2ms/step - loss: 0.2720 - accuracy: 0.9091
accuracy: 90.9091%

Notamos que la precisión disminuye aumentando el número de épocas. En ese caso

In [71]:
# Aumentamos el batch_size
model_4 = model_result(5, 50)
5/5 [==============================] - 0s 1ms/step - loss: 0.4691 - accuracy: 0.8322
accuracy: 83.2168%
In [72]:
# Aumentamos el batch_size
model_5 = model_result(5, 40)
5/5 [==============================] - 0s 1ms/step - loss: 0.5381 - accuracy: 0.8671
accuracy: 86.7133%
In [73]:
# Disminuimos el batch_size
model_6 = model_result(5, 25)
5/5 [==============================] - 0s 2ms/step - loss: 0.5218 - accuracy: 0.9161
accuracy: 91.6084%
In [74]:
# Disminuimos el batch_size
model_6 = model_result(5, 15)
5/5 [==============================] - 0s 2ms/step - loss: 0.2255 - accuracy: 0.9371
accuracy: 93.7063%
In [75]:
# Disminuimos el batch_size
model_7 = model_result(5, 10)
5/5 [==============================] - 0s 2ms/step - loss: 0.2579 - accuracy: 0.9231
accuracy: 92.3077%
In [77]:
# Consideramos de nuevo un modelo sin batch_size
def model_result_2(num_epocas):
    """num_epocas: número de épocas
       num_lote: tamañio de los lotes"""
    # Instanciamos el modelo
    model = Sequential()
    # Capa de entrada de 31 neuronas y capa oculta de 62 neuronas.
    # Para la capa oculta utilizamos la funcion de activacion relu
    model.add(Dense(62, activation='relu', input_shape=(30,)))
    # Capa oculta 2:
    # 40 neuronas y funcion de activacion relu
    model.add(Dense(40, activation='relu'))
    # Capa de salida. Dado que es un problema de clasificacion binaria
    # utilizamos la funcion de activacion sigmoide
    model.add(Dense(1, activation='sigmoid'))
    # Compilacion
    model.compile('adam', loss='binary_crossentropy', metrics=['accuracy'])
    # Entrenamiento
    modelo = model.fit(X_train, y_train, epochs=num_epocas, verbose=False)
    # Evaluacion sobre el conjunto de pruebas
    scores = model.evaluate(X_test, y_test)
    print('%s: %.4f%%' % (model.metrics_names[1], scores[1] * 100))
    # Retornamos el modelo obtenido
    return model

model_8 = model_result_2(5)
5/5 [==============================] - 0s 2ms/step - loss: 0.3034 - accuracy: 0.9231
accuracy: 92.3077%
In [78]:
# Aumentamos el numero de epocas
model_9 = model_result_2(10)
5/5 [==============================] - 0s 2ms/step - loss: 0.4106 - accuracy: 0.9091
accuracy: 90.9091%
In [79]:
# Aumentamos el numero de epocas
model_10 = model_result_2(7)
5/5 [==============================] - 0s 2ms/step - loss: 0.2106 - accuracy: 0.9231
accuracy: 92.3077%
In [82]:
# Aumentamos el numero de epocas
model_11 = model_result_2(6)
5/5 [==============================] - 0s 2ms/step - loss: 0.3800 - accuracy: 0.9441
accuracy: 94.4056%
In [84]:
# Disminuimos el numero de epocas
model_12 = model_result_2(4)
5/5 [==============================] - 0s 2ms/step - loss: 0.2157 - accuracy: 0.9161
accuracy: 91.6084%

Después de las pruebas anteriores, elegimos el modelo model_11 el cual nos arrojo el mayor número de precisión.

Veamos en una tabla qué tan bueno fue nuestro modelo:

In [85]:
# dataframe con las etiquetas reales
df_prueba = pd.DataFrame(y_test).rename(columns={0:'y_test'})
df_prueba.head()
Out[85]:
y_test
0 1
1 1
2 0
3 1
4 0

Después, utilizando X_test realizaremos la predicción de las etiquetas para esos datos y los compararemos con los valores de y_test

In [86]:
# Prediccion
y_pred = model_11.predict(X_test)
y_pred
5/5 [==============================] - 0s 3ms/step
Out[86]:
array([[9.99629617e-01],
       [9.99710381e-01],
       [8.63962322e-02],
       [9.99994159e-01],
       [1.06726653e-13],
       [9.82931316e-01],
       [9.99853730e-01],
       [8.69353831e-01],
       [9.23000634e-01],
       [9.98036981e-01],
       [9.99999821e-01],
       [6.58910843e-11],
       [1.39187932e-01],
       [9.99969900e-01],
       [9.99986172e-01],
       [8.55791092e-01],
       [9.99936879e-01],
       [9.99996364e-01],
       [9.99983430e-01],
       [9.96641040e-01],
       [5.17817876e-18],
       [1.45146308e-11],
       [9.95960355e-01],
       [9.99478638e-01],
       [9.95986521e-01],
       [2.79043252e-05],
       [7.43328656e-06],
       [9.99994695e-01],
       [2.23713811e-10],
       [9.99969661e-01],
       [1.44468842e-03],
       [9.99979615e-01],
       [9.99978840e-01],
       [9.93004739e-01],
       [1.79321960e-05],
       [9.99913216e-01],
       [7.41055906e-01],
       [9.99977231e-01],
       [9.99983430e-01],
       [2.59015195e-07],
       [6.09234674e-04],
       [9.98266339e-01],
       [2.06825760e-07],
       [4.41129774e-01],
       [1.82594045e-14],
       [7.95789003e-01],
       [3.57348142e-08],
       [7.70056587e-08],
       [9.99994218e-01],
       [4.49250592e-08],
       [2.59436399e-01],
       [0.00000000e+00],
       [9.95823324e-01],
       [9.98494148e-01],
       [9.99994695e-01],
       [9.99935687e-01],
       [9.99867558e-01],
       [1.26144720e-19],
       [6.67011208e-37],
       [9.99928296e-01],
       [8.93563703e-02],
       [9.99996006e-01],
       [9.89149690e-01],
       [9.99961019e-01],
       [9.98798788e-01],
       [3.20648524e-17],
       [9.99981463e-01],
       [9.99999106e-01],
       [9.99881923e-01],
       [9.99541104e-01],
       [9.99997020e-01],
       [9.99970257e-01],
       [3.35543789e-02],
       [9.99927998e-01],
       [4.28891921e-13],
       [9.99928653e-01],
       [9.99396384e-01],
       [2.25313717e-32],
       [1.15472794e-07],
       [2.31203786e-03],
       [9.97031808e-01],
       [6.58919974e-10],
       [4.03703347e-18],
       [9.97907400e-01],
       [9.99956071e-01],
       [9.99839306e-01],
       [5.98823503e-02],
       [9.99967515e-01],
       [4.08994838e-09],
       [9.99979258e-01],
       [1.05037849e-07],
       [9.99986053e-01],
       [9.99973595e-01],
       [6.71994239e-02],
       [9.99994338e-01],
       [9.99991536e-01],
       [9.97489572e-01],
       [9.99567568e-01],
       [9.99318659e-01],
       [9.99916971e-01],
       [9.99982297e-01],
       [9.99883890e-01],
       [9.99913454e-01],
       [9.99994218e-01],
       [9.97247100e-01],
       [9.99717712e-01],
       [9.99998212e-01],
       [9.99969602e-01],
       [9.99924541e-01],
       [9.99984920e-01],
       [9.99981403e-01],
       [9.99999166e-01],
       [9.99251842e-01],
       [1.17731209e-24],
       [9.99187887e-01],
       [1.39396871e-12],
       [9.99987364e-01],
       [2.43601309e-13],
       [9.99993682e-01],
       [9.99853492e-01],
       [9.99971688e-01],
       [9.99484539e-01],
       [9.99991179e-01],
       [9.99991298e-01],
       [2.51834322e-26],
       [9.99913633e-01],
       [2.91506467e-07],
       [8.73163819e-01],
       [9.95315373e-01],
       [9.99803483e-01],
       [0.00000000e+00],
       [9.99996424e-01],
       [9.99275982e-01],
       [1.33840206e-09],
       [2.76559358e-03],
       [9.99956846e-01],
       [9.99996364e-01],
       [9.99398947e-01],
       [6.18399419e-02],
       [1.70473269e-09],
       [7.02149493e-12],
       [9.99543250e-01],
       [0.00000000e+00]], dtype=float32)

Notamos que se han predicho probabilidades, lo cual tiene sentido pues la función de activación en la última capa fue la sigmoide. Luego, podemos redondear esos valores y los agregamos al dataframe df_pruebas:

In [88]:
df_prueba['y_pred'] = y_pred.round()
df_prueba.head()
Out[88]:
y_test y_pred
0 1 1.0
1 1 1.0
2 0 0.0
3 1 1.0
4 0 0.0
In [90]:
df_prueba['y_pred'] = df_prueba.y_pred.apply(lambda x: int(x))
df_prueba.head()
Out[90]:
y_test y_pred
0 1 1
1 1 1
2 0 0
3 1 1
4 0 0

Realizamos una resta entre esas dos columnas, si obtenemos cero entonces la predicción ha sido correcta, caso contrario la predicción ha errado

In [91]:
df_prueba['diff'] = df_prueba.y_test - df_prueba.y_pred
df_prueba.head()
Out[91]:
y_test y_pred diff
0 1 1 0
1 1 1 0
2 0 0 0
3 1 1 0
4 0 0 0

Veamos el número de aciertos (ceros) y el número de errores

In [93]:
df_prueba.value_counts('diff')
Out[93]:
diff
 0    135
-1      7
 1      1
dtype: int64

El modelo ha errado 8 veces y ha acertado 135, lo cual representa:

In [95]:
total = 135 + 8

# Mostramos el porcentaje de aciertos redondeado a 4 decimales
print(f'Porcentaje de aciertos: {round(135 / total, 4)}%')
Porcentaje de aciertos: 0.9441%

La cual es justamente la precisión que habíamos obtenido.

Finalmente regresamos a las etiquetas M y B en vez de ceros y unos

In [96]:
df_prueba['y_test']= df_prueba.y_test.apply(lambda x: 'B' if x == 1 else 'M')
df_prueba['y_pred']= df_prueba.y_pred.apply(lambda x: 'B' if x == 1 else 'M')
df_prueba.head()
Out[96]:
y_test y_pred diff
0 B B 0
1 B B 0
2 M M 0
3 B B 0
4 M M 0

Adicional¶

Recordemos que

In [98]:
data.value_counts('diagnosis')
Out[98]:
diagnosis
1    357
0    212
dtype: int64

Podríamos manejar un mismo número de muestras para la etiqueta 1 y la etiqueta 0, para que sea más equilibrado el entrenamiento, para ello:

In [99]:
# tomamos una muestra aleatoria de 200 filas para cada categoria
data_1 = data[data.diagnosis == 1].sample(200)
data_0 = data[data.diagnosis == 0].sample(200)

# unimos esos dataframes en uno solo
data_muestra = pd.concat([data_0, data_1])
data_muestra.head()
Out[99]:
diagnosis radius_mean texture_mean perimeter_mean area_mean smoothness_mean compactness_mean concavity_mean concave points_mean symmetry_mean ... radius_worst texture_worst perimeter_worst area_worst smoothness_worst compactness_worst concavity_worst concave points_worst symmetry_worst fractal_dimension_worst
372 0 21.37 15.10 141.30 1386.0 0.10010 0.1515 0.19320 0.12550 0.1973 ... 22.69 21.84 152.1 1535.0 0.1192 0.2840 0.4024 0.1966 0.2730 0.08666
202 0 23.29 26.67 158.90 1685.0 0.11410 0.2084 0.35230 0.16200 0.2200 ... 25.12 32.68 177.0 1986.0 0.1536 0.4167 0.7892 0.2733 0.3198 0.08762
16 0 14.68 20.13 94.74 684.5 0.09867 0.0720 0.07395 0.05259 0.1586 ... 19.07 30.88 123.4 1138.0 0.1464 0.1871 0.2914 0.1609 0.3029 0.08216
70 0 18.94 21.31 123.60 1130.0 0.09009 0.1029 0.10800 0.07951 0.1582 ... 24.86 26.58 165.9 1866.0 0.1193 0.2336 0.2687 0.1789 0.2551 0.06589
23 0 21.16 23.04 137.20 1404.0 0.09428 0.1022 0.10970 0.08632 0.1769 ... 29.17 35.59 188.0 2615.0 0.1401 0.2600 0.3155 0.2009 0.2822 0.07526

5 rows × 31 columns

Otro punto a mejor es realizar un escalado de las características. Notemos que los valores de las distintas columnas son, algunas, muy distantes entre sí, por lo cual algunas características tienen más peso sobre otras. Así, lo que haremos será llevar todos esos número mediante una transformación al intervalo $(0,1)$ como sigue

In [100]:
# Importacion necesaria
from sklearn.preprocessing import MinMaxScaler

# Instanciamos y configuramos
sc = MinMaxScaler(feature_range = (0,1))

# caracteristicas
X = data_muestra.drop('diagnosis', axis=1)
# variable objetivo
y = data_muestra['diagnosis']
# Conversion a arrays
X = X.values
y = y.values

# Escalado sobre la variable X
X = sc.fit_transform(X)

Después realizamos la división

In [101]:
X_train, X_test, y_train, y_test = train_test_split(X, y, # conjunto completo
                                       test_size=0.25,    # 25% para pruebas
                                       random_state=123,  # semilla aleatoria 
                                       shuffle=True)

donde shuffle=True mezcla los datos antes de dividirlos.

In [104]:
model_11 = model_result_2(15)
4/4 [==============================] - 0s 2ms/step - loss: 0.2298 - accuracy: 0.9000
accuracy: 90.0000%
In [106]:
model_11 = model_result(15, 32)
4/4 [==============================] - 0s 3ms/step - loss: 0.2570 - accuracy: 0.8900
accuracy: 89.0000%
In [107]:
model_12 = model_result(15, 12)
4/4 [==============================] - 0s 2ms/step - loss: 0.1905 - accuracy: 0.9200
accuracy: 92.0000%
In [108]:
model_13 = model_result(15, 7)
4/4 [==============================] - 0s 2ms/step - loss: 0.2022 - accuracy: 0.9200
accuracy: 92.0000%
In [109]:
model_14 = model_result(25, 7)
4/4 [==============================] - 0s 2ms/step - loss: 0.2491 - accuracy: 0.9300
accuracy: 93.0000%
In [110]:
model_15 = model_result(30, 7)
4/4 [==============================] - 0s 3ms/step - loss: 0.2175 - accuracy: 0.9400
accuracy: 94.0000%
In [111]:
model_16 = model_result(30, 12)
4/4 [==============================] - 0s 2ms/step - loss: 0.1954 - accuracy: 0.9400
accuracy: 94.0000%
In [113]:
model_17 = model_result(30, 20)
4/4 [==============================] - 0s 2ms/step - loss: 0.1682 - accuracy: 0.9500
accuracy: 95.0000%
In [115]:
model_18 = model_result(45, 32)
4/4 [==============================] - 0s 2ms/step - loss: 0.1774 - accuracy: 0.9500
accuracy: 95.0000%
In [117]:
model_19 = model_result(45, 40)
4/4 [==============================] - 0s 3ms/step - loss: 0.1897 - accuracy: 0.9400
accuracy: 94.0000%
In [122]:
model_20 = model_result(45, 32)
4/4 [==============================] - 0s 1ms/step - loss: 0.1918 - accuracy: 0.9500
accuracy: 95.0000%

Con lo cual no hemos conseguido una mejora significativa del 1%.