En este blog ya he comentado la teoría de las redes neuronales. Así que aquí vamos a hacer un ejercicio práctico. Vamos a construir un clasificador con cualquier dataset creado por nosotros mismos o descargado de internet. Aquí utilizaremos un dataset para calcular quinielas (1,X,2) con los resultados de la liga española, descargado de internet pero adaptado. Esta entrada es igual que otra que puse con el conjunto MNIST de imágenes, la única diferencia es que aquí utilizamos un dataset adaptado o creado, por nosotros mismos.
Vamos a utilizar Scikit-Learn,
TensorFlow aquí se puede ver cómo instalar TensorFlow si no lo tenemos instalado. keras y numpy
# Python ≥3.5
import sys
assert sys.version_info >= (3, 5)
# Scikit-Learn ≥0.20
import sklearn
assert sklearn.__version__ >= "0.20"
try:
# %tensorflow_version solo existe en Colab.
%tensorflow_version 2.x
except Exception:
pass
# TensorFlow ≥2.0
import tensorflow as tf
assert tf.__version__ >= "2.0"
# importaciones comunes
import numpy as np
import os
# para hacer este cuaderno estable
np.random.seed(42)
import tensorflow as tf
from tensorflow import keras
Para comprobar si está todo
correctamente instalado, preguntamos por la versión
tf.__version__
keras.__version__
Cargamos el
dataset
import pandas as pd
quinielas = pd.read_csv('Completo_Etiquetado_Puntuado_3.csv') #tiene goles quiniela y quinigol
quinielas.head()
Separamos
los datos de las etiquetas en nuestro dataset, en la X dejamos los datos y en
las y las etiquetas a predecir.
#las X contienen los datos relevantes para hacer predicciones quitamos
todas las etiquetas
X = quinielas.drop(columns=['idPartido','temporada','Q1','QX','Q2','QGC0','QGC1','QGC2','QGCM','QGF0','QGF1','QGF2','QGFM','timestamp','golesLocal','golesVisitante','fecha'])
#las Y son las etiquetas a predecir, en este caso quitamos todo excepto 'Q1','QX','Q2'
y = quinielas.drop(columns=['division','jornada','idPartido','temporada','PuntosLocal','PuntosVisitante','EquipoLocal','Puntos_Normalizados','EquipoVisitante','QGC0','QGC1','QGC2','QGCM','QGF0','QGF1','QGF2','QGFM','timestamp','golesLocal','golesVisitante','fecha','timestamp'])
Dividimos nuestro dataset en una
parte de entrenamiento y otra de testeo, como tenemos unas 37.000 filas,
utilizaremos las 29.718 primeras filas como entrenamiento y el resto para
testeo.
x_data = np.array(X)
y_data = np.array(y)
train_data = x_data[:29718]
test_data = x_data[29718:]
train_labels = y_data [:29718]
test_labels = y_data [29718:]
Crearemos una red neuronal secuencial, que es el tipo más
sencillo de red neuronal, compuesto de una pila de diferentes capas conectadas
secuencialmente. Utilizaremos capas densas con activación ReLU. Cada capa densa
gestiona su propia matriz de pesos y contiene también todos los pesos de las
conexiones entre las neuronas y sus entradas, también gestiona el vector de
sesgos (bias) uno por neurona. Para la capa de salida utilizaremos la función
de activación softmax. aquí tenemos documentación sobre todos los tipos de activación de que dispone el paquete keras.
Creamos una red neuronal sencilla con 14 neuronas de
entrada (una por cada parámetro de entrada, 100 ocultas y 3 de salida (una por
cada tipo diferente de salida 1, X, 2 para hacer quinielas)
model = keras.models.Sequential()
model.add(keras.layers.Dense(15, activation="relu"))
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.Dense(3,
activation="softmax"))
Compilar
el modelo
Después de crear nuestro
modelo, tenemos que compilarlo, para ello llamamos al método compile() para
especificar la función de pérdida y optimizador a utilizar.
#compilamos el modelo
model.compile(loss="categorical_crossentropy",
optimizer="sgd",
metrics=["accuracy"])
Utilizamos categorical_crossentropy (si
utilizamos sparse_categorical_crossentropy
da error) como función de pérdida para tener etiquetas (para cada instancia hay exactamente una
clase, 1,X, 2 en este caso) y las clases son exclusivas, en vez de mostrar las
probabilidades de cada clase nos dará una salida del tipo [0,1,0] para la X por
ejemplo. Si estuviéramos utilizando una clasificación binaria, de una sola salida,
podríamos utilizar una función de activación en la salida sigmoid en lugar de softmax.
_______________________________________________________________
Nota: Diferencia entre categorical_crossentropy y
sparse_categorical_crossentropy
CategoricalCrossentropy
CategoricalCrossentropy class
tf.keras.losses.CategoricalCrossentropy(
from_logits=False,
label_smoothing=0,
reduction="auto",
name="categorical_crossentropy",
)
Calcula la pérdida de entropía cruzada entre
las etiquetas y las predicciones.
Utilizaremos esta cuando haya dos o más clases
de etiquetas. Esperamos que las etiquetas proporcionen en una representación
one_hot. Si deseamos proporcionar etiquetas como números enteros, utilizaremos
la pérdida SparseCategoricalCrossentropy. Debe haber #
clases de valores de punto flotante por característica.
SparseCategoricalCrossentropy
tf.keras.losses.SparseCategoricalCrossentropy(
from_logits=False, reduction="auto",
name="sparse_categorical_crossentropy"
)
Calcula la pérdida de entropía cruzada entre las etiquetas y las
predicciones.
Utilizaremos esta función de pérdida de
entropía cruzada cuando haya dos o más clases de etiquetas. Esperamos que las
etiquetas se proporcionen como números enteros. Si deseamos proporcionar
etiquetas utilizando una representación en caliente, utilizaremos SparseCategoricalCrossentropy. Debe haber #
clases de valores de punto flotante por característica para y_pred y un único valor
de punto flotante por característica para y_true.
Ambas tienen la misma función de pérdida. La
única diferencia es el formato de etiquetas verdaderas. (En contraposición con
las etiquetas calculadas)
Si nuestras etiquetas tienen codificación
one-hot, utilizaremos categorical_crossentropy. Ejemplos (para una
clasificación de 3 clases): [1,0,0], [0,1,0], [0,0,1]
Pero nuestras etiquetas son enteros,
utilizaremos sparse_categorical_crossentropy. Ejemplos (para una clasificación
de 3 clases): [1], [2], [3]
El uso depende completamente de cómo carguemos
nuestro conjunto de datos. Una ventaja de usar sparse_categorical_crossentropy es
que ahorra tiempo en la memoria y en el cálculo porque simplemente usa un solo
entero para una clase, en lugar de un vector completo.
_________________________________________________
Aquí documentación de keras para pérdidas (loss)
Aquí documentación de keras para optimizadores
Aquí documentaciónde keras para métricas
El optimizador sgd significa que entrenaremos el modelo utilizando Gradiente de descenso estocástico
En otras palabras, keras ejecutará un
algoritmo de retropropagación. Finalmente ponemos accuracy para medir las
métricas de la red durante su entrenamiento.
Entrenando y evaluando el modelo
Ahora el modelo está listo para ser entrenado,
para ello basta con llamar al método fit().
#entrenamos y evaluamos el
modelo
history = model.fit(train_data,
train_labels, epochs=30,
validation_data=(test_data ,
test_labels))
Le indicamos el conjunto de datos de entrenamiento, train_data y sus
etiquetas correspondientes train_labels, así como el número de épocas a
entrenar. Por defecto sería 1, pero no es una buena idea, pues con una sóla
época los datos no tendrán tiempo Para converger en una solución óptima. A continuación le pasamos el conjunto de
datos de validación (esto es opcional). Keras medirá al final de cada época, la
diferencia entre los resultados del conjunto de entrenamiento y el de
validación, esto es útil para medir lo bien o mal que se ejecuta el modelo. Si
la ejecución del conjunto de entrenamiento es mejor que el de validación,
probablemente nuestro modelo tenga sobreajuste (overfitting) o haya una
discordancia (error) entre los datos de entrenamiento y validación.
Hecho esto, tendremos nuestro modelo entrenado. En cada época se han
mostrado algunos valores, y hemos visto como de una época a la siguiente han
ido mejorando. En la última época vemos:
Epoch 30/30
929/929 [==============================] - 1s 1ms/step - loss: 0.9873
- accuracy: 0.5218 - val_loss: 1.0137 - val_accuracy: 0.4944
El último valor 0.4944 nos indica que ha alcanzado una precisión del
49,44 % después de 30 épocas. No es una buena precisión (pero estamos tratando
datos quasi-aleatorios, así que es normal) Aquí lo que realmente nos interesa
es ver que no está muy alejada de la precisión del grupo de entrenamiento accuracy: 0.5218, lo que nos indica que no parece que
tengamos sobreajuste.
Podríamos haber utilizado también el argumento validation_split en lugar de validation_data. Por ejemplo validation_split =0.1 le
indica a Keras que debe utilizar el último 10% de los datos (después de
barajarlos) para la validación.
En el caso de que el conjunto de entrenamiento esté muy sesgado, de modo
que algunas clases estuvieran sub-representadas y otras sobre-representadas,
podríamos utilizar el argumento class_weigth en el método fit(). Este argumento nos dará
una idea de los pesos de las clases sub-representadas y de las
sobre-representadas. Estos pesos los utilizará Keras para computar la pérdida.
El método plot, nos permite visualizar los valores en cada época,
mostrados en una gráfica.
import pandas as pd
pd.DataFrame(history.history).plot(figsize=(8, 5))
Cuando la curva de entrenamiento y validación están tan cercanas,
significa que apenas existe sobreajuste.
Si no estamos satisfechos con la ejecución del modelo, probablemente
debamos reajustar algunos hiperparámetros y entrenarlo de nuevo. El primero que
debemos comprobar es la tasa de aprendizaje, si una vez modificada, el modelo
no mejora, debemos probar con otros hiperparámetros. Pero siempre reajustando
la tasa de aprendizaje después de cambiar cualquier hiperparámetro. Si el
modelo sigue sin funcionar bien, podemos variar el número de capas, o el número
de neuronas por capa, así como el tipo de activación a utilizar en cada capa
oculta. También podemos ajustar otros hiperparámetros, como el tamaño del
conjunto de datos, esto se puede hacer con el argumento batch_size, el cual por defecto se ejecuta con 32.
Una vez estemos satisfechos con la ejecución de nuestro modelo, debemos
evaluarlo con el conjunto de testeo, para estimar el error de generalización
antes de poner nuestro modelo en producción. Podemos hacer esto fácilmente con
el método evaluate() (también soporta
varios argumentos, como el tamaño de lote batch_size y el peso simple_weight.
Utilizando el modelo para hacer predicciones
A continuación utilizaremos el método pedict() para hacer predicciones
sobre nuevas instancias. Si no tenemos
nuevas instancias podemos utilizar las diez
primeras instancias del conjunto de testeo.
X_new = test_data[:10]
y_proba = model.predict(X_new)
y_proba.round(2)
En este caso devuelve
array([[0.28, 0.36, 0.36],
[0.43, 0.3 , 0.27],
[0.66, 0.21, 0.13],
[0.71, 0.21, 0.08],
[0.45, 0.25, 0.3 ],
[0.44, 0.26, 0.3 ],
[0.6 , 0.28, 0.12],
[0.28, 0.19, 0.53],
[0.43, 0.3 , 0.27],
[0.32, 0.29, 0.39]], dtype=float32)
Lo que nos indica las probabilidades de 1, X o 2 en los tres ejemplos,
en este caso la predicción es X o 2 en el primero (Con un 36 % de
probabilidades en ambos casos), 1 en el segundo (con un 43% de probabilidad de
acertar) y 1 en el tercero (con un 66% de probabilidad)
Podemos ver los resultados sin probabilidades con
np.argmax(model.predict(X_new),
axis=-1)
En este caso nos devuelve un array con los resultados
array([1, 0, 0, 0, 0, 0, 0, 2, 0, 2],
dtype=int64)
Aquí hay que darse
cuenta de que el 0 es el primer resultado de nuestro dataset (el 1), el 1 es la
X de las quinielas y el 2 es el 2 de las quinielas. En realidad nuestra
quiniela sería (X,1,1,1,1,1,1,2,1,2)
Salvar y restaurar un modelo
Cuando utilizamos la API secuencial y la API funcional salva un modelo entrenado es tan sencillo como hacer
model.save("mi_modelo_keras.h5")
Esta instrucción salva nuestro modelo en formato .HDF5 en el directorio
donde tenemos el cuaderno Jupyter. Salva la arquitectura del modelo, incluyendo
cada capa y sus hiperparámetros. Asi como los valores de todos los parámetros
del modelo por capa (como su conexión, pesos y sesgos). También salva el
optimizador, incluyendo sus hiperparámetros y sus estados si los tienen.
Normalmente tendremos un script que entrena un modelo y lo salva. Y otro script
(o un servicio web) que lo carga y lo utiliza para hacer predicciones.
Para restaurarlo basta con poner
model = keras.models.load_model("mi_modelo_keras.h5")
Esto funcionará para el API Sequencial o para
la Functional, pero no para la API Subclassing. En este caso podemos utilizar save_weights y load_weights para al menos
poder salvar y restaurar los pesos de nuestro modelo, pero el resto tendremos
que salvarlo y restaurarlo por nuestra cuenta.
Si el entrenamiento de nuestro modelo nos
lleva varias horas, podemos salvar los puntos intermedios del entrenamiento con
el argumento callbacks del método fit().
No hay comentarios:
Publicar un comentario