Hasta ahora hemos visto como definir una red neuronal y como entrenarla. El último paso que nos queda es saber si la red ha funcionado correctamente. Pero ¿Eso no se hacía con la función de coste? Pues no exactamente. La función de coste se usa para ayudar a ajustar los parámetros durante el entrenamiento mediante los datos de entrada pero no para saber si el modelo es bueno. Para saber si el modelo es bueno , se usan las métricas.
Las métricas son muy parecidas a las funciones de coste pero hay métricas que no existen como función de coste. El muchos casos la métrica será la misma que la función de coste.
En el método fit
de Keras tenemos un nuevo parámetro para indicar la métrica y luego nos retorna u objeto con el resultado de las métricas en cada época.
model.compile(loss="binary_crossentropy",optimizer=tf.keras.optimizers.SGD(learning_rate=0.01),metrics=[tf.keras.metrics.BinaryCrossentropy()]) history=model.fit(x,y_true,epochs=20,verbose=False)
Para obtener los valores de la métrica en cada época se usa la siguiente línea
history.history['binary_crossentropy']
history
hay una propiedad que también se llama history
.
Ahora que sabemos como obtener el coste de la red neuronal , vamos a mostrarla en una gráfica en función de la época.
figure=plt.figure() axes = figure.add_subplot() axes.plot(history.history['binary_crossentropy'],label="SGS lr=0.01") axes.legend() axes.set_xlabel('Época', fontsize=15,labelpad=20,color="#003B80") axes.set_ylabel('Valor métrica', fontsize=15,labelpad=20,color="#003B80") axes.set_title("Métricas en épocas", fontsize=20,pad=30,color="#003B80") axes.set_facecolor("#F0F7FF") axes.grid(b=True, which='major', axis='both',color="#FFFFFF",linewidth=1)
Vemos que la métrica tiene un valor aun está lejos de llegar a cero.
Lo siguiente que vamos a hacer ahora es mirar si podemos mejorar el valor de la métrica, buscando un mejor optimizador. Para ellos vamos a crear un par de funciones que nos van a ayudar en nuestro trabajo:
def compile_and_fit(optimizer,epochs): np.random.seed(5) tf.random.set_seed(5) random.seed(5) model=Sequential() model.add(Dense(3, input_dim=1,activation="sigmoid",kernel_initializer="glorot_normal")) model.add(Dense(1,activation="sigmoid",kernel_initializer="glorot_normal")) model.compile(loss="binary_crossentropy",optimizer=optimizer,metrics=[tf.keras.metrics.BinaryCrossentropy()]) history=model.fit(x,y_true,epochs=epochs,verbose=False) return history def format_axes(axes): axes.legend() axes.set_xlabel('Época', fontsize=15,labelpad=20,color="#003B80") axes.set_ylabel('Valor métrica', fontsize=15,labelpad=20,color="#003B80") axes.set_title("Métricas en épocas", fontsize=20,pad=30,color="#003B80") axes.set_facecolor("#F0F7FF") axes.grid(b=True, which='major', axis='both',color="#FFFFFF",linewidth=1)
Vamos a usarlas ahora para obtener la gráfica pero entrenando la red durante 100 épocas:
history=compile_and_fit(tf.keras.optimizers.SGD(learning_rate=0.01),500) figure=plt.figure() axes = figure.add_subplot() axes.plot(history.history['binary_crossentropy'],label="SGS lr=0.01") format_axes(axes)
Pero vemos que tampoco ha mejorado mucho.
¿Y si usamos el optimizador Nadam con el mismo learning_rate
de 0.01
y además los comparamos?
optimizers=[ [tf.keras.optimizers.SGD(learning_rate=0.01),"SGD lr=0.01"], [tf.keras.optimizers.Nadam(learning_rate=0.01),"Nadam lr=0.01"] ] figure=plt.figure(figsize=(8,6)) axes = figure.add_subplot() for optimizer in optimizers: history=compile_and_fit(optimizer[0],500) axes.plot(history.history['binary_crossentropy'],label=optimizer[1]) format_axes(axes)
¡¡¡Ahora si que ha mejorado mucho!!!!, ya hemos llegado a un valor cercano a cero y además no nos hacen falta tantas épocas.
Probemos ahora con el resto de optimizadores pero con 40 épocas y valores aun mayores de learning_rate
optimizers=[ [tf.keras.optimizers.Adagrad(learning_rate=0.1),"Adagrad lr=0.1"], [tf.keras.optimizers.RMSprop(learning_rate=0.1),"RMSprop lr=0.1"], [tf.keras.optimizers.Adadelta(learning_rate=0.1),"Adadelta lr=0.1"], [tf.keras.optimizers.Adam(learning_rate=0.1),"Adam lr=0.1"], [tf.keras.optimizers.Adamax(learning_rate=0.1),"Adamax lr=0.1"], [tf.keras.optimizers.Nadam(learning_rate=0.1),"Nadam lr=0.1"], [tf.keras.optimizers.Adam(learning_rate=0.1,amsgrad=True),"AMSGrad lr=0.1"] ] figure=plt.figure(figsize=(15,10)) axes = figure.add_subplot() for optimizer in optimizers: history=compile_and_fit(optimizer[0],40) axes.plot(history.history['binary_crossentropy'],label=optimizer[1]) format_axes(axes)
Por orden , éstos son los mejores optimizadores (de mejor a peor):
Como se puede ver, mostrar en una gráfica los valores de las métricas es de gran ayuda. Existen muchas métricas que veremos un poco después pero antes pasemos a ver un nuevo concepto que el la validación
Acabamos de ver que entrenando la red neuronal , el error se consigue bajar a prácticamente cero. Es decir que los valores de los parámetros , pesos (weight) y sesgos bias, debe ser muy buenos. No exactamente. Resulta que los parámetros se han ajustado a los datos que le hemos pasado, pero ¿Como es de bueno el modelo para nuevos datos que no ha visto? Realmente ver como se comporta con datos nuevos y con los datos que ha ya visto es lo que nos va a decir como es de bueno nuestro modelo. Así que pasemos a ver como sacar las métricas también con datos nuevos.
Lo primero es averiguar de donde obtenemos nuevos datos. Normalmente no tenemos nuevos datos así que lo que hacemos es que solo vamos a entrenar nuestra red neuronal con el 80% de los datos y el 20% restante los guardaremos para validar la red neuronal. Eso lo vamos a hacer con la función train_test_split de scikit-learn
from sklearn.model_selection import train_test_split x_train, x_test, y_train, y_test = train_test_split(x, y_true, test_size=0.2, random_state=42)
La función train_test_split
tiene los siguientes argumentos:
test_size
: La fracción de datos que se va a usar para la validación.Es un valor de 0.0 a 1.0. Siendo 0.0 que no hay datos para validación y 1.0 que todos sería para validación.random_state
: Es para que sea reproducible el generador de los números aleatorios. x_train
: Array con la x
de los datos de entrenamientox_test
: Array con la x
de los datos de validación y_train
: Array con la y
de los datos de entrenamientoy_test
: Array con la y
de los datos de validaciónY ahora a Keras se los tenemos que pasar así:
history=model.fit(x_train,y_train,validation_data=(x_test,y_test),epochs=epochs,verbose=False)
Lo datos de entrenamiento se pasan igual que antes pero los de validación se pasan en en un tupla en un parámetro llamado validation_data
.
Por último tenemos que obtener la métrica para los datos de validación. Se obtiene igual que antes pero el nombre de la métrica empieza por val_
history.history['val_binary_crossentropy']
Veamos un ejemplo completo:
import numpy as np import tensorflow as tf import numpy as np import pandas as pd import keras from keras.models import Sequential from keras.layers import Dense from sklearn.datasets import load_iris import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split iris=load_iris() x=iris.data[0:99,2] y_true=iris.target[0:99] np.random.seed(5) tf.random.set_seed(5) random.seed(5) x_train, x_test, y_train, y_test = train_test_split(x, y_true, test_size=0.2, random_state=42) model=Sequential() model.add(Dense(3, input_dim=1,activation="sigmoid",kernel_initializer="glorot_normal")) model.add(Dense(1,activation="sigmoid",kernel_initializer="glorot_normal")) model.compile(loss="binary_crossentropy",optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),metrics=[tf.keras.metrics.BinaryCrossentropy()]) history=model.fit(x_train,y_train,validation_data=(x_test,y_test),epochs=40,verbose=False) figure=plt.figure(figsize=(8,6)) axes = figure.add_subplot() axes.plot(history.history['binary_crossentropy'],label="Training") axes.plot(history.history['val_binary_crossentropy'],label="Test") axes.legend() axes.set_xlabel('Época', fontsize=15,labelpad=20,color="#003B80") axes.set_ylabel('Valor métrica', fontsize=15,labelpad=20,color="#003B80") axes.set_facecolor("#F0F7FF") axes.grid(b=True, which='major', axis='both',color="#FFFFFF",linewidth=1)
Podemos ver en el gráfico que la métrica es muy similar con los datos de test que con los de entrenamiento. Por lo que el modelo es bueno.
¿Cual es un valor aceptable de test_size
, pues lo normal es 0.2
y 0.3
.
Para acabar el tema vamos a ver las distintas métricas que existen. Lo primero es indicar nombres tanto en inglés como en español ya que vamos a usar los nombres en inglés
Inglés | Español |
---|---|
Precision | Precisión |
Recall | Exhaustividad |
F1-score | Valor-F |
Accuracy | Exactitud |
Sensitivity | Sensibilidad |
Confusion Matrix | Matriz de Confusión |
True Positive | Positivos Verdaderos |
True Negative | Negativos Verdaderos |
False Positive | Positivos Falsos |
False Negative | Negativos Falsos |
Hay métricas que son exactamente iguales a las funciones de coste como MEA o MSE en los problemas de regresión MAE, MSE. Si ya las usamos como función de coste y queremos usarlas como métricas no es necesario indicarlas como métricas, se puede acceder a ellas de la siguiente forma:
Para mostrar la función de coste en el entrenamiento:
history.history['loss']
Para mostrar la función de coste en la validación:
history.history['val_loss']
Son las métricas que se usan en problemas de regresión. Son casi las mismas que usábamos como funciones de coste.
Es igual que la función de coste de Mean Absolute Error (MAE), así que no explicaremos nada mas sobre ella excepto como se usa en Keras como métrica
Se define como:
metrics=[tf.keras.metrics.MeanAbsoluteError()] metrics=["mean_absolute_error"] metrics=["mae"]
y usarla como
history.history['mean_absolute_error'] history.history['val_mean_absolute_error'] history.history["mae"] history.history["val_mae"]
Mas información:
Es igual que la función de coste de Mean Squared Error (MSE), así que no explicaremos nada mas sobre ella excepto como se usa en Keras como métrica
Se define como:
metrics=[tf.keras.metrics.MeanSquaredError()] metrics=["mean_squared_error"] metrics=["mse"]
y usarla como
history.history['mean_squared_error'] history.history['val_mean_squared_error'] history.history["mse"] history.history["val_mse"]
Mas información:
Es igual que la función de coste de Distancia del coseno, así que no explicaremos nada mas sobre ella excepto como se usa en Keras como métrica
Se define como:
metrics=[tf.keras.metrics.CosineSimilarity()] metrics=["cosine_similarity"]
y usarla como
history.history['cosine_similarity'] history.history['val_cosine_similarity']
Mas información:
La Root Mean Squared Error (RMSE) o Raiz cuadrada del error cuadrático medio se calcula igual que el MSE pero se le aplica la raíz cuadrada.
Por lo tanto su fórmula es
$$RMSE = \sqrt{MSE}= \sqrt{\frac{1}{N} \sum\limits_{i=1}^{N}(y_{i} - \hat{y_{i}})^2}$$
Ahora vamos a explicar algunas cosas de RMSE.
Mas información:
El coeficiente de determinación o R² se calcula de la siguiente forma:
$$R^{2} = 1- \frac {\sum\limits_{i=1}^{N} (y_{i} - \hat{y_{i}})^2} {\sum\limits_{i=1}^{N} (y_{i} - \bar{y})^2}$$ $$\bar{y}=\frac {1}{N} \sum\limits_{i=1}^{N} y_{i} - \hat{y_{i}}$$
Siendo:
Ahora vamos a explicar algunas cosas de R²
num_regressors
a la clase RSquare
Mas información:
La elección de una métrica u otra se puede ver en MAE, MSE, RMSE, Coefficient of Determination, Adjusted R Squared — Which Metric is Better? y Know The Best Evaluation Metrics for Your Regression Model
Clasificación con 2 posibles valores es cuando la salida de nuestra red neuronal solo puede tener 2 posibles valores, que deben se obligatoriamente los valores 0 y 1 debido a que las funciones de Keras funcionan con esos valores.
Antes de entrar a ver las métricas , es necesario entender lo que son:
Predicción | |||
---|---|---|---|
1 | 0 | ||
Realidad | 1 | TP | FN |
0 | FP | TN |
Puesto con imágenes:
Para explicar todos estos conceptos véase los artículos:
Más información:
Es igual que la función de coste de Binary Crossentropy, así que no explicaremos nada mas sobre ella excepto como se usa en Keras como métrica excepto recordar que cuanto menor sea su valor es mejor la métrica.
Se define como:
metrics=[tf.keras.metrics.BinaryCrossentropy()] metrics=["binary_crossentropy"]
y usarla como
history.history['binary_crossentropy'] history.history['val_binary_crossentropy']
Mas información:
Accuracy nos indica la proporción de aciertos que ha tenido. Es decir el porcentaje (en tanto por uno) de TP y TN respecto al total
Predicción | |||
---|---|---|---|
1 | 0 | ||
Realidad | 1 | TP | FN |
0 | FP | TN |
$$Binary \: Accuracy = \frac{TN+TP}{TN+TP+FN+FP}$$
Su uso en Keras es
metrics=[tf.keras.metrics.BinaryAccuracy()] metrics=["binary_accuracy"]
y usarla como
history.history['binary_accuracy'] history.history['val_binary_accuracy']
tf.keras.metrics.BinaryAccuracy()
con tf.keras.metrics.Accuracy()
ya que esta última necesita que los valores de $y$ e $\hat{y}$ sean exactamente iguales
Un ejemplo:
y_true = np.array([0.0, 1.0, 0.0, 1.0]) y_pred = np.array([0.1, 0.9, 0.9, 0.1]) metric = tf.keras.metrics.BinaryAccuracy() metric(y_true, y_pred).numpy()
0.5
Más información:
Nos dice el porcentaje (en tanto por uno) de los que hemos acertado como verdaderos respecto a los predichos como verdaderos
Predicción | |||
---|---|---|---|
1 | 0 | ||
Realidad | 1 | TP | FN |
0 | FP | TN |
$$Precision = \frac{TP}{TP+FP}$$
La métrica de Precision es importante cuando nos interesa que no haya muchos falsos positivos. Un ejemplo sería un modelo que predice donde hay yacimientos de petroleo. Un falso positivo implicaría invertir mucho dinero en montar el sistema de extracción de petroleo para que luego no hubiera petroleo.
Su uso en Keras es
metrics=[tf.keras.metrics.Precision()] metrics=["Precision"]
y usarla como
history.history['precision'] history.history['val_precision']
Ejemplo:
y_true = np.array([0.0, 1.0, 1.0, 1.0]) y_pred = np.array([0.9, 0.9, 0.9, 0.1]) metric = tf.keras.metrics.Precision() metric(y_true, y_pred).numpy()
0.6666667
Más información:
Nos dice el porcentaje (en tanto por uno) de los que hemos acertado como verdaderos respecto a los que realmente eran verdaderos
Predicción | |||
---|---|---|---|
1 | 0 | ||
Realidad | 1 | TP | FN |
0 | FP | TN |
$$Precision = \frac{TP}{TP+FN}$$
La métrica de Recall es importante cuando nos interesa que no haya muchos falsos negativos. Un ejemplo sería un modelo que predice si tenemos cáncer. Un falso positivo implicaría que dejaremos sin tratar a una persona que si que tiene cancer.
Su uso en Keras es
metrics=[tf.keras.metrics.Recall()] metrics=["Recall"]
y usarla como
history.history['recall'] history.history['val_recall']
Ejemplo:
y_true = np.array([1.0, 1.0, 1.0, 0.0]) y_pred = np.array([0.9, 0.0, 0.0, 0.9]) metric = tf.keras.metrics.Recall() metric(y_true, y_pred).numpy()
0.33333334
Más información:
En la siguiente tabla se pueden ver las 5 principales métricas.
Es la media armónica entre Recall y Precision que permite combinar en una única métrica ambos valores. Se usa cuando nos interesa un compromiso entre los errores de tipo I (Falsos positivos) y los de tipo II (Falsos negativos). Se ha usado la media armónica ya que tiende atener un valor muy bajo para valores bajos. Por ello no se "cancelan" valores buenos de Recall con valores malos de Precision ya que en ese caso F1-score tenderá a ser bajo por lo que indicará que nuestro modelo es malo.
$$F1{\text -}score=\frac{2}{\frac{1}{Recall}+\frac{1}{Precision}}$$
Pero si sumamos las fracciones y hacemos la división:
$$F1{\text -}score=\frac{2}{\frac{1}{Recall}+\frac{1}{Precision}}= \frac{2}{\frac{Precision}{Recall \cdot Precision}+\frac{Recall}{Precision \cdot Recall}}= \frac{2}{\frac{Precision+Recall}{Recall \cdot Precision}} = \frac{2}{1}: \frac{Precision+Recall}{Recall \cdot Precision} = \frac { 2 \cdot Recall \cdot Precision}{Recall + Precision}$$
Siendo el resultado la formula que se ve como definición de F1-score:
$$F1{\text -}score=2 \cdot \frac {Recall \cdot Precision}{Recall + Precision}$$
Existe un paquete extra llamado "tensorflow-addons" que contiene la métrica F1-score
Lo primero es instalar el paquete "tensorflow-addons" con:
conda install --channel esri tensorflow-addons
Ahora ya podemos usarlo previa importación del módulo tensorflow_addons
import tensorflow_addons as tfa metrics=[tfa.metrics.F1Score()]
Mas información:
Es otra métrica pero que tiene en cuenta que los datos no estén balanceados. El Coeficiente Phi también es llamado Matthews Correlation Coefficient o MCC.
El MMC tiene un valor entre -1 a 1. Siendo:
$$MCC = \frac{ \mathit{TP} \times \mathit{TN} - \mathit{FP} \times \mathit{FN} } {\sqrt{ (\mathit{TP} + \mathit{FP}) ( \mathit{TP} + \mathit{FN} ) ( \mathit{TN} + \mathit{FP} ) ( \mathit{TN} + \mathit{FN} ) } }$$
Existe un paquete extra llamado "tensorflow-addons" que contiene la métrica Coeficiente Phi
Lo primero es instalar el paquete "tensorflow-addons" con:
conda install --channel esri tensorflow-addons
Ahora ya podemos usarlo previa importación del módulo tensorflow_addons
import tensorflow_addons as tfa metrics=[tfa.metrics.MatthewsCorrelationCoefficient(num_classes=2)]
Mas información:
La Area under the curve (AUC) es una métrica que nos dice el área de una curva ROC. Pero pasemos primero a explicar que es una curva ROC.
Lo primero es que cuando predecimos que ciertos valores son Positivos o Negativos, lo hacemos en base a un umbral. Normalmente si algo es menor o igual que 0.5 decimos que es Negativo
y si es mayor que 0.5 decimos que es Positivo
. Pero ese umbral es arbitrario.
La siguiente imagen muestra la distribución de valores que hemos definido como presuntamente Positivos y los presuntamente Negativos. Si superan ese umbral se convierten en Falsos Positivos o Falsos Negativos.
En las siguientes gráficas vamos a ver como afecta a nuestro modelo el variar el umbral.
Vamos a explicar cada columna de la imagen anterior:
\begin{align} True \: Positive \: Rate \: (TPR) &= \frac{TP}{TP+FN} \\ False \: Positive \: Rate \: (FPR) &= \frac{FP}{FP+TN} \end{align}
X
de la gráfica es el FPR y la Y
de la gráfica es el TPR.Cada una de las filas de la imagen son predicciones distintas, siendo:
Entonces, ¿Que es la Area under the curve (AUC)? Es el área de la curva ROC es decir el área rosa de las gráficas de la última columna. Si nos fijamos cuanto mejor es la predicción, mayor es el área rosa y por lo tanto mayor es la métrica de AUC.
En keras podemos usar la métrica de AUC de la siguiente forma: Su uso en Keras es
metrics=[tf.keras.metrics.AUC()] metrics=["AUC"]
y usarla como
history.history['auc'] history.history['val_auc']
Mas información:
Mas información:
Es igual que la función de coste de Categorical Crossentropy, así que no explicaremos nada mas sobre ella excepto como se usa en Keras como métrica excepto recordar que cuanto menor sea su valor es mejor la métrica.
Su uso en keras es:
metrics=[tf.keras.metrics.CategoricalCrossentropy()] metrics=["categorical_crossentropy"]
y usarla como
history.history['categorical_crossentropy'] history.history['val_categorical_crossentropy']
Mas información:
Accuracy nos indica la proporción de aciertos que ha tenido. Es decir el porcentaje (en tanto por uno) de verdaderos positivos y verdaderos negativos
Su uso en keras es:
metrics=[tf.keras.metrics.CategoricalAccuracy()] metrics=["categorical_accuracy"]
y usarla como
history.history['categorical_accuracy'] history.history['val_categorical_accuracy']
Mas información:
Suponemos que un coche hace un viaje de 300 Km y los primeros 100 km va a 70 km/h, los siguientes 100 km a 80 km/h y los últimos 100 km a 90 km/h.
Repite los cálculos pero ahora con las siguiente velocidades: 40, 80 y 90