Inteligencia Analítica de Datos con R

Redes neuronales

Msc. Roberto Trespalacios

Universidad Tecnológica de Bolivar

2023-07-24

Introducción

El ámbito de las redes neuronales y su hermano mayor, el deep learning, es complejo y amplio. Durante los últimos años, el interés y la aplicación de este tipo de modelos han experimentado tal expansión que se ha convertido en una disciplina por sí misma.

Si bien entender bien sus fundamentos requiere de una cantidad notable de tiempo y práctica, esto no significa que se necesiten adquirir todos ellos para empezar a sacarles partido.

Modelos de redes neuronales

  • Modelos de redes simples (perceptrón simple): estos modelos se caracterizan por tener arquitecturas relativamente sencillas por lo que los requerimientos computacionales no son elevados y no es necesario el uso el uso de GPUs. Dentro de este grupo destacan las implementaciones de nnet y neuralnet.

Deep learning: son modelos más complejos (perceptrón multicapa, redes convolucionales, redes recurrentes, LSTM…) cuyos requerimientos computacionales hacen necesario el uso de muy optimizado de CPUs o GPUs. Para este tipo de modelos se tiene que recurrir a frameworks especializados como H2O, Tensorflow-Keras o Torch.

En este documento se muestran ejemplos de modelos relativamente sencillos (perceptrón simple y perceptrón multicapa) haciendo uso del paquete H2O.

Redes neuronales

Estructura de la red

  • Las redes neuronales son modelos creados al ordenar operaciones matemáticas siguiendo una determinada estructura. La forma más común de representar la estructura de una red neuronal es mediante el uso de capas (layers), formadas a su vez por neuronas (unidades, units o neurons).
  • Cada neurona, realiza una operación sencilla y está conectada a las neuronas de la capa anterior y de la capa siguiente mediante pesos, cuya función es regular la información que se propaga de una neurona a otra.

Representación de una red neuronal

Representación de una red neuronal y sus elementos

  • La primera capa de la red neuronal (color verde) se conoce como capa de entrada o input layer y recibe los datos en bruto, es decir, el valor de los predictores.
  • La capa intermedia (color azul), conocida como capa oculta o hidden layer, recibe los valores de la capa de entrada, ponderados por los pesos (flechas grises).
  • La última capa, llamada output layer, combina los valores que salen de la capa intermedia para generar la predicción.

Modelo teórico de una red neuronal

Para facilitar la comprensión de la estructura de las redes, es útil representar una red equivalente a un modelo de regresión lineal.

\[y = w_1 x_1 + ... + w_d x_d + b\]

Cada neurona de la capa de entrada representa el valor de uno de los predictores. Las flechas representan los coeficientes de regresión, que en términos de redes se llaman pesos, y la neurona de salida representa el valor predicho. Para que esta representación equivalga a la ecuación de un modelo lineal, faltan dos cosas:

  • El bias(sesgo) del modelo.

  • Las operaciones de multiplicación y suma que combinan el valor de los predictores con los pesos del modelo.

Cada neurona de la capa intermedia tiene un valor de bias(sesgo), pero suele omitirse en las representaciones gráficas. En cuanto a las operaciones matemáticas, es el elemento clave que ocurre dentro de las neuronas y conviene verlo con detalle.

La neurona (unidad)

La neurona es la unidad funcional de los modelos de redes. Dentro de cada neurona ocurren simplemente dos operaciones: la suma ponderada de sus entradas y la aplicación de una función de activación.

  • En la primera parte, se multiplica cada valor de entrada \(x_i\) por su peso asociado \(w_i\) y se suman junto con el bias.
  • Este es el valor neto de entrada a la neurona. A continuación, este valor se pasa por una función, conocida como función de activación, que transforma el valor neto de entrada en un valor de salida.

Si bien el valor que llega a la neurona, siempre es una combinación lineal, gracias a la función de activación, se pueden generar salidas muy diversas. Es en la función de activación donde reside el potencial de los modelos de redes para aprender relaciones no lineales.

Modelación matemática de una red neuronal

El valor neto de entrada a una neurona es la suma de los valores que le llegan, ponderados por el peso de las conexiones, más el bias.

\[entrada=\sum_{i=1}^{n}x_iw_i+b =\textbf{XW}+b\]

  • \(X\): vector de los valores de entrada
  • \(W\): vector de pesos.

A la entrada se le aplica la función de activación (\(g\)) que lo transforma en en el valor de activación (\(a\)), (\(a\) es el valor final de la neurona).

\[a=g(entrada)=g(\textbf{XW}+b)\]

  • En la capa de entrada, únicamente se incorporar el valor de los predictores \(X_i\), la función de activación es la unidad, es decir, sale lo mismo que entra.
  • En la capa de salida, la función de activación, suele ser:
    • la identidad para problemas de regresión.
    • softmax (normalized exponential function) para clasificación.

Función de activación

Las funciones de activación controlan en gran medida qué información se propaga desde una capa a la siguiente (forward propagation). Estas funciones convierten el valor neto de entrada a la neuronal (combinación de los input, pesos y bias) en un nuevo valor.

  • Gracias a combinar funciones de activación no lineales con múltiples capas (ver más adelante), los modelos de redes son capaces de aprender relaciones no lineales.

  • La gran mayoría de funciones de activación convierten el valor de entrada neto de la neurona en un valor dentro del rango (0, 1) o (-1, 1).

  • Cuando el valor de activación de una neurona (salida de su función de activación) es cero, se dice que la neurona está inactiva, ya que no pasa ningún tipo de información a las siguientes neuronas. A continuación, se describen las funciones de activación más empleadas.

Rectified linear unit (ReLU)

La función de activación ReLu aplica una transformación no lineal muy simple, activa la neurona solo si el input está por encima de cero.

  • Mientras el valor de entrada está por debajo de cero, el valor de salida es cero, pero cuando es superior, el valor de salida aumenta de forma lineal con el de entrada.

\[ReLU(x)=max(x,0)\]

De esta forma, la función de activación retiene únicamente los valores positivos y descarta los negativos dándoles una activación de cero.

Observación

ReLU es con diferencia la función de activación más empleada por sus buenos resultados en aplicaciones diversas. La razón de esto reside en el comportamiento de su derivada (gradiente), que es cero o constante.

Sigmoide

La función sigmoide transforma valores en el rango de \((-\infty, +\infty)\) a valores en el rango (0, 1).

\[\operatorname{sigmoid}(x) = \frac{1}{1 + e^{-x}}\]

Observaciones

  • Aunque la función de activación sigmoide se utilizó mucho en los inicios de los modelos de redes, en la actualidad, suele preferirse la función ReLU.

  • Un caso en el que la función de activación sigmoide sigue siendo la función utilizada por defecto es en las neuronas de la capa de salida de los modelos de clasificación binaria (usada también en regresión logistica), ya que su salida puede interpretarse como probabilidad.

Tangente hiperbólica (Tanh)

La función de activación Tanh, se comporta de forma similar a la función sigmoide, pero su salida está acotada en el rango (-1, 1).

\[\operatorname{tanh}(x) = \frac{1 - \exp(-2x)}{1 + \exp(-2x)}\]

Sin las funciones de activación, las redes neuronales solo pueden aprender relaciones lineales.

Otras función de activación

A continuación, na tabla que contiene otras funciones de activación.

Función de coste (loss function)

La función de coste (l), también llamada función de pérdida, loss function o cost function, es la encargada de cuantificar la distancia entre el valor real y el valor predicho por la red, en otras palabras, mide cuánto se equivoca la red al realizar predicciones.

  • En la mayoría de casos, la función de coste devuelve valores positivos.

    • cuanto más próximo a cero es el valor de coste, mejor son las predicciones de la red (menor error)
    • siendo cero cuando las predicciones se corresponden exactamente con el valor real.
  • La función de coste puede calcularse para:

    1. una única observación o
    2. para un conjunto de datos (normalmente promediando el valor de todas las observaciones).
  • Es el segundo caso el que se utiliza para dirigir el entrenamiento de los modelos.

  • Dependiendo del tipo de problema, regresión o clasificación, tenemos las funciones de coste:

    • en regresión: error cuadrático medio y el error absoluto medio.
    • en clasificación: suele emplearse la función log loss, también llamada logistic loss o cross-entropy loss.

Error cuadrático medio

El error cuadrático medio (mean squared error, MSE) es con diferencia la función de coste más utilizada en problemas de regresión. El error cuadrático se calcula como la diferencia al cuadrado entre el valor predicho \(\hat{y}\) y el valor real \(y\).

\[L(\mathbf{w}, b) =\frac{1}{n}\sum_{i=1}^n l_i(\mathbf{w}, b) = \frac{1}{n}\sum_{i=1}^n \left(\hat{y}_i - y_i\right)^2\]

Las funciones de coste suelen escribirse con la notación \(l(\textbf{w},b)\) para hacer referencia a que su valor depende de los pesos y bias del modelo, ya que son estos los que determinan el valor de las predicciones \(y_i\).

Observación

Cuando un modelo se entrena utilizando el error cuadrático medio como función de coste, está aprendiendo a predecir la media de la variable respuesta.

Error medio absoluto

El error medio absoluto (mean absolute error, MAE) consiste en promediar el error absoluto de las predicciones.

\[L(\mathbf{w}, b) =\frac{1}{n}\sum_{i=1}^n |\hat{y}_i - y_i|\]

  • El error medio absoluto es más robusto frente a outliers que el error cuadrático medio.
  • Esto significa que, el entrenamiento del modelo, se ve menos influenciado por datos anómalos que pueda haber en el conjunto de entrenamiento.
  • Cuando un modelo se entrena utilizando el error absoluto medio como función de coste, está aprendiendo a predecir la mediana de la variable respuesta.

Log loss, logistic loss o cross-entropy loss

En problemas de clasificación, la capa de salida utiliza como función de activación la función softmax.

  • Gracias a esta función, la red devuelve una serie de valores que pueden interpretarse como la probabilidad de que la observación predicha pertenezca a cada una de las posibles clases.

Cuando la clasificación es de tipo binaria, donde la variable respuesta es 1 o 0, y \(p=Pr(y=1)\), la función de coste log-loss se define como:

\[L_{\log}(y, p) = -\log \operatorname{Pr}(y|p) = -(y \log (p) + (1 - y) \log (1 - p))\] Para problemas de clasificación con más de dos clases, esta fórmula se generaliza a:

\[L_{\log}(Y, P) = -\log \operatorname{Pr}(Y|P) = - \frac{1}{N} \sum_{i=0}^{N-1} \sum_{k=0}^{K-1} y_{i,k} \log p_{i,k}\]

En ambos casos, minimizar esta la función equivale a que la probabilidad predicha para la clase correcta tienda a 1 y a 0 en las demás clases.

Múltiples capas

El modelo de red neuronal con una única capa (single-layer perceptron), aunque supuso un gran avance en el campo del machine learning, solo es capaz de aprender patrones sencillos. Para superar esta limitación, los investigadores descubrieron que, combinando múltiples “capas ocultas”, la red puede aprender relaciones mucho más complejas entre los predictores y la variable respuesta. A esta estructura se le conoce como perceptrón multicapa o multilayer perceptron (MLP), y puede considerarse como el primer modelo de deep learning.

  • La estructura de un perceptrón multicapa consta de varias capas ocultas.
  • Cada neurona está conectada a todas las neuronas de la capa anterior y a las de la capa posterior.
  • Aunque no es estrictamente necesario, todas las neuronas que forman parte de una misma capa suelen emplear la misma función de activación.

Observaciones

  • Combinando múltiples capas ocultas y funciones de activación no lineales, los modelos de redes pueden aprender prácticamente cualquier patrón.
  • De hecho, está demostrado que, con suficientes neuronas, un MLP es un aproximador universal para cualquier función.

Red neuronal de múltiples capas

Entrenamiento

El proceso de entrenamiento de una red neuronal consiste en ajustar el valor de los pesos y bias de tal forma que, las predicciones que se generen, tengan el menor error posible. Gracias a esto, el modelo es capaz de identificar qué predictores tienen mayor influencia y de qué forma están relacionados entre ellos y con la variable respuesta.

La idea intuitiva de cómo entrenar una red neuronal es la siguiente:

  • Iniciar la red con valores aleatorios de los pesos y bias.

  • Para cada observación de entrenamiento, calcular el error que comete la red al hacer su predicción. Promediar los errores de todas las observaciones.

  • Identificar la responsabilidad que ha tenido cada peso y bias en el error de las predicciones.

  • Modificar ligeramente los pesos y bias de la red (de forma proporcional a su responsabilidad en el error) en la dirección correcta para que se reduzca el error.

  • Repetir los pasos 2, 3, 4 y 5 hasta que la red sea suficientemente buena.

  • Si bien la idea parece sencilla, alcanzar una forma de implementarla ha requerido la combinación de múltiples métodos matemáticos, en concreto, el algoritmo de retropropagación (backpropagation) y la optimización por descenso de gradiente (gradient descent).

Algoritmos de óptimización de redes neuronales

Si bien la idea del entrenamiento parece sencilla, alcanzar una forma de implementarla ha requerido la combinación de múltiples métodos matemáticos, en concreto, el algoritmo de retropropagación (backpropagation) y la optimización por descenso de gradiente (gradient descent).

Backpropagation

Backpropagation es el algoritmo que permite cuantificar la influencia que tiene cada peso y bias en las predicciones de la red. Para conseguirlo, hace uso de la regla de la cadena (chain rule) para calcular el gradiente, que no es más que el vector formado por las derivadas parciales de una función.

En el caso de las redes, la derivada parcial del error respecto a un parámetro (peso o bias) mide cuánta “responsabilidad” ha tenido ese parámetro en el error cometido. Gracias a esto, se puede identificar qué pesos de la red hay que modificar para mejorarla. El siguiente paso necesario, es determinar cuánto y cómo modificarlos (optimización).

Descenso de gradiente

Descenso de gradiente (gradient descent) es un algoritmo de optimización que permite minimizar una función haciendo actualizaciones de sus parámetros en la dirección del valor negativo de su gradiente. Aplicado a las redes neuronales, el descenso de gradiente permite ir actualizando los pesos y bias del modelo para reducir su error.

Preprocesado

  • A la hora de entrenar modelos basados en redes neuronales, es necesario aplicar a los datos, al menos, dos tipos de transformaciones.

  • Binarización (one hot encoding) de las variables categóricas

  • La binarización (one-hot-encoding) consiste en crear nuevas variables dummy con cada uno de los niveles de las variables cualitativas. Por ejemplo, una variable llamada color que contenga los niveles rojo, verde y azul, se convertirá en tres nuevas variables (color_rojo, color_verde, color_azul), todas con el valor 0 excepto la que coincide con la observación, que toma el valor 1.

Estandarización y escalado de variables numéricas

  • Cuando los predictores son numéricos, la escala en la que se miden, así como la magnitud de su varianza pueden influir en gran medida en el modelo. Existen principalmente 2 estrategias para escalar:

  • Centrado: consiste en restarle a cada valor la media del predictor al que pertenece. Si los datos están almacenados en un dataframe, el centrado se consigue restándole a cada valor la media de la columna en la que se encuentra. Como resultado de esta transformación, todos los predictores pasan a tener una media de cero, es decir, los valores se centran en torno al origen.

  • Normalización (estandarización): consiste en transformar los datos de forma que todos los predictores estén aproximadamente en la misma escala.

  • Normalización Z-score: dividir cada predictor entre su desviación típica después de haber sido centrado, de esta forma, los datos pasan a tener un rango de \((-\infty, +\infty)\)

  • Estandarización max-min: transformar los datos de forma que estén dentro del rango [0, 1].

Nota: Nunca se deben estandarizar las variables después de ser categorizadas (por niveles numericos).

¿Qué es el sobreajuste?

El sobreajuste es un comportamiento de aprendizaje automático no deseado que se produce cuando el modelo de aprendizaje automático ofrece predicciones precisas para los datos de entrenamiento, pero no para los datos nuevos.

  • Cuando los científicos de datos usan modelos de aprendizaje automático para hacer predicciones, primero entrenan el modelo en un conjunto de datos conocido.
  • Luego, con base en esta información, el modelo intenta predecir resultados para nuevos conjuntos de datos.
  • Un modelo sobreajustado puede dar predicciones inexactas y no puede funcionar bien para todos los tipos de datos nuevos.

¿Por qué se produce el sobreajuste?

Solo obtiene predicciones precisas si el modelo de aprendizaje automático se generaliza a todos los tipos de datos dentro de su dominio. El sobreajuste ocurre cuando el modelo no puede generalizar y se ajusta demasiado al conjunto de datos de entrenamiento. El sobreajuste ocurre por varios motivos, como:

  • El tamaño de los datos de entrenamiento es demasiado pequeño y no contiene suficientes muestras de datos para representar con precisión todos los valores de datos de entrada posibles.
  • Los datos de entrenamiento contienen grandes cantidades de información irrelevante, denominada datos ruidosos.
  • El modelo entrena durante demasiado tiempo en un único conjunto de datos de muestra.
  • La complejidad del modelo es alta, por lo que aprende el ruido dentro de los datos de entrenamiento.

Ejemplos de sobreajuste

  1. Considere un caso de uso en el que un modelo de aprendizaje automático tiene que analizar fotos e identificar las que contienen perros. Si el modelo de aprendizaje automático se entrenó en un conjunto de datos que contenía la mayoría de las fotos que mostraban perros afuera en los parques, es posible que aprenda a usar el césped como una característica para la clasificación y es posible que no reconozca a un perro dentro de una habitación.

  2. Otro ejemplo de sobreajuste es un algoritmo de aprendizaje automático que predice el rendimiento académico y el resultado de la graduación de un estudiante universitario mediante el análisis de varios factores, como los ingresos familiares, el rendimiento académico anterior y las calificaciones académicas de los padres. Sin embargo, los datos de la prueba solo incluyen candidatos de un género o grupo étnico específico. En este caso, el sobreajuste hace que la precisión de la predicción del algoritmo disminuya para los candidatos con género o etnia fuera del conjunto de datos de prueba.

Hiperparámetros del modelo de redes neuronales

La gran “flexibilidad” que tienen las redes neuronales es un arma de doble filo. Por un lado, son capaces de generar modelos que aprenden relaciones muy complejas, sin embargo, tenemos los siguiente problema que se presentas a veces:

Sobreajuste (overfitting)

Lo que los incapacita al tratar de predecir nuevas observaciones. La forma de minimizar este problema y conseguir modelos útiles pasa por configurar de forma adecuada sus hiperparámetros. Son muchos los hiperparámetros de un modelo basado en redes y su nomenclatura varía de unas implementaciones a otras, sin embargo, los de mayor impacto siempre están presentes:

  • Número y tamaño de capas
  • Learning rate (tasa de aprendizaje)
  • Algoritmo de optimización
  • Regularización

Número y tamaño de capas

La arquitectura de una red, el número de capas y el número de neuronas que forman parte de cada capa, determinan en gran medida la complejidad del modelo y con ello su potencial capacidad de aprendizaje.

  • La capa de entrada y salida son sencillas de establecer.
  • La capa de entrada tiene tantas neuronas como predictores y la capa de salida tiene una neurona en problemas de regresión y tantas como clases en problemas de clasificación.
  • En la mayoría de implementaciones, estos valores se establecen automáticamente en función del conjunto de entrenamiento.
  • El usuario suele especificar únicamente el número de capas intermedias (ocultas) y el tamaño de las mismas.

Cuantas más neuronas y capas, mayor la complejidad de las relaciones que puede aprender el modelo. Sin embargo, dado que cada neurona está conectada por pesos al resto de neuronas de las capas adyacentes, el número de parámetros a aprender aumenta y con ello el tiempo de entrenamiento.

Regularización

Los métodos de regularización tienen el objetivo de reducir el sobreajuste (overfitting) de los modelos. Un modelo con sobreajuste memoriza los datos de entrenamiento pero es incapaz de predecir correctamente nuevas observaciones. Entre las muchas que existen, se destacan la regularización L1 y L2 (weight decay) y el dropout.

Regularización L1 y L2

El objetivo de la regularización L1 y L2, esta última también conocida como weight decay, es evitar que los pesos tomen valores excesivamente elevados. De esta forma, se evita que unas pocas neuronas dominen el comportamiento de la red y se fuerza a que las características poco informativas (ruido) tengan pesos próximos o iguales a cero.

Regularización Dropout

Este proceso consiste en desactivar aleatoriamente una serie de neuronas durante el proceso de entrenamiento. En concreto, durante cada iteración del entrenamiento, se ponen a cero los pesos de una fracción aleatoria de neuronas por capa. El método de dropout, descrito por Srivastava et al. en 2014, se ha convertido en un estándar para entrenar redes neuronales. El porcentaje de neuronas que suele desactivarse por capa (dropout rate) suele ser un valor entre 0.2 y 0.5.

Modelos de redes neuronales con H2O

¿Qué es H2O?

H2O es un conjunto de herramientas y librerias que tienen el objetivo de combinar los principales algoritmos de machine learning y aprendizaje estadístico con el Big Data.

Ventajas de H2O

  • Comprimir y almacenar los datos: H2O es capaz de trabajar con millones de registros en un único ordenador (emplea todos sus cores) o en un cluster de muchos ordenadores.

  • Escalabilidad: sus algoritmos son igualmente útiles cuando se trabaja con un volumen de datos reducido.

Comunicación entre H2O y R

  • El manejo de H2O puede hacerse íntegramente desde R:
    • iniciar el cluster, carga de datos, entrenamiento de modelos, predicción de nuevas observaciones, etc.
    • Es importante tener en cuenta que, aunque los comandos se ejecuten desde R, los datos se encuentran en el cluster de H2O, no en memoria.
    • Solo cuando los datos se cargan en memoria, se les pueden aplicar funciones propias de R.
    • Las funciones as.data.frame() y as.h2o() permiten transferir los datos de la sesión de R al cluster H2O y viceversa.
Observación importante:
  • Hay que tener especial precaución cuando el movimiento de datos se hace desde H2O a R, ya que esto implica cargar en RAM todos los datos y, si son muchos, pueden ocupar toda la memoria.
  • Para evitar este tipo de problemas, conviene realizar todos los pasos posibles (filtrado, agregaciones, cálculo de nuevas columnas…) con las funciones de H2O antes de transferir los datos.

Modelos

  • Para crear modelos basados en redes neuronales con H2O, se utiliza la función h2o.deeplearning().
  • Son muchos los argumentos que controlan el comportamiento de este tipo de modelos.
  • Afortunadamente, los responsables de su implementación han establecido valores por defecto que suelen funcionar adecuadamente en muchos escenarios. A continuación, se muestran los más influyentes:

Arquitectura

Hidden:

  • número de capas ocultas y número de neuronas por capa.
  • La estructura se define mediante un vector de números enteros. Por ejemplo, una red con una única capa de 100 neuronas se crea con hidden = c(100) o hidden = 100, y una red con dos capas ocultas de 10 neuronas cada una con hidden = c(10, 10).
  • Las capas de entrada y salida se crean automáticamente.
  • La capa de entrada tiene tantas neuronas como predictores (tras codificar las variables categóricas).
  • La capa de salida tiene una única neurona en problemas de regresión, y tantas neuronas como clases en los problemas de clasificación.

Preprocesado

  • standardize: por defecto, se estandarizan los valores para que tengan media cero y varianza uno. Los modelos basados en redes no son independientes de la escala de los predictores, por lo que es fundamental estandarizarlos.

  • missing_values_handling: las redes neuronales no aceptan observaciones con valores ausentes. H2O ofrece la opción de excluirlos Skip o imputarlos con la media MeanImputation.

  • shuffle_training_data: mezclar aleatoriamente las observaciones antes de entrenar el modelo.

  • categorical_encoding: codificado de las variables categóricas.

Aprendizaje

  • activation: función de activación de las neuronas de las capas intermedias (Tahn, Tahn with dropout, Rectifier, Rectifier with dropout, Maxout, Maxout with dropout). La función de activación de la capa de salida se selecciona automáticamente dependiendo de si es un problema de regresión o clasificación.

  • loss: función de coste que se intenta minimizar durante el entrenamiento de la red. Esta es la forma en que se cuantifica el error que comete la red y en función de ello aprende, por lo tanto, es importante escoger la función de coste más adecuada para el problema en cuestión (esto es así para todos los algoritmos, no solo para redes).

  • epochs: número de iteraciones de aprendizaje durante el entrenamiento de la red. Encontrar el valor óptimo de épocas es muy importante ya que, si son muy pocas, la red puede no aprender lo suficiente, pero si son demasiadas, se produce overfitting.

  • adaptive_rate: si se indica esta opción (activada por defecto), se emplea el algoritmo (ADADELTA) como algoritmo de aprendizaje. Con él, se consigue que el learning rate se adapte automáticamente (reduciéndose) a medida que el entrenamiento avanza. Es recomendable probar en primer lugar esta opción, ya que consigue un balance entre velocidad de aprendizaje y resultados bastante bueno. Este algoritmo está controlado por dos parámetros:

    • rho: controla el ratio en el que se va reduciendo el learning rate en cada iteración de aprendizaje.
    • epsilon: corrector para evitar divisiones entre cero.

Regularización

  • input_dropout_ratio: porcentaje de dropout en la capa de entrada. Este tipo de regularización controla que ningún predictor influya en exceso en la red. Se recomiendan valores de 0.1 o 0.2.
  • hidden_dropout_ratios: porcentaje de dropout en las capas intermedias. Solo es aplicable si se selecciona como función de activación TanhWithDropout, RectifierWithDropout, o MaxoutWithDropout. Por defecto, el valor es 0.5.
    • l1: penalización l1.
    • l2: penalización l2.

Ejemplo de clasificación

En este primer ejemplo se muestra cómo, dependiendo de la arquitectura (capas ocultas y tamaño de las mismas), un modelo basado en redes neuronales puede aprender funciones no lineales con gran facilidad. Para evitar problemas de overfitting, es importante identificar la combinación de hiperparámetros que consigue un equilibrio adecuado en el aprendizaje. Se trata de un ejemplo muy sencillo, cuyo objetivo es que el lector se familiarice con la flexibilidad que tienen este tipo de modelos y en cómo realizar una búsqueda de hiperparámetros mediante grid search y validación cruzada.

Librerías usadas

Los paquetes utilizadas en este ejemplo son:

# datos y gráficos
library(tidyverse)
library(readr)
library(ggthemes)
library(ggpubr)
library(h2o) # redes neuronales

Iniciación del cluster

Una vez que la librería H2O ha sido cargada, hay que iniciar el cluster. Para este ejemplo, se emplea un único ordenador del que se utilizan todos sus cores en paralelo.

# Creación de un cluster local con todos los cores disponibles.
h2o.init(
  ip = "localhost", # cluster local 
  nthreads = -1, # -1 indica que se empleen todos los cores disponibles.
  max_mem_size = "6g" # Máxima memoria disponible para el cluster.
  )

Eliminan los datos del cluster

# Se eliminan los datos del cluster por si ya había sido iniciado.
h2o.removeAll()
# Para que no se muestre la barra de progreso.
h2o.no_progress()

Los Datos

Se simulan observaciones en dos dimensiones, pertenecientes a tres grupos, cuya separación no es lineal.

# Datos simulados
datos = read_csv("~/Documents/utb_diplomado/sem_5.4/datos/blobs.csv")

head(datos)

Grafiquemos los datos

datos <- datos %>% mutate(y = as.factor(y))

ggplot(data = datos, aes(x = x_1, y = x_2, fill = y)) + 
  geom_point(shape = 21, size = 2) +
  theme_bw()

Creación de los datos y la partición (train y test) de estos en H2O

datos_h2o   <- as.h2o(datos)
particiones <- h2o.splitFrame(data = datos_h2o, ratios = c(0.6, 0.2), seed = 123)
datos_train <- h2o.assign(data = particiones[[1]], key = "datos_train")
datos_validation  <- h2o.assign(data = particiones[[2]], key = "datos_validacion")
datos_test  <- h2o.assign(data = particiones[[3]], key = "datos_test")

Arquitectura de la red

Se procede a crear 4 modelos en orden creciente de complejidad (número de neuronas y capas), para comprobar cómo la arquitectura de la red afecta a su capacidad de aprendizaje.

Modelo 1

modelo_1 <- h2o.deeplearning(x               = c("x_1", "x_2"),
                            y               = "y",
                            distribution    = "multinomial",
                            training_frame  = datos_train,
                            standardize     = TRUE,
                            activation      = "Rectifier",
                            adaptive_rate   = FALSE,
                            hidden          = 1,
                            stopping_rounds = 0,
                            epochs          = 1000,
                            seed            = 123,
                            model_id        = "modelo_1")

Modelo 2

modelo_2 <- h2o.deeplearning(x               = c("x_1", "x_2"),
                            y               = "y",
                            distribution    = "multinomial",
                            training_frame  = datos_train,
                            standardize     = TRUE,
                            activation      = "Rectifier",
                            adaptive_rate   = FALSE,
                            hidden          = 10,
                            stopping_rounds = 0,
                            epochs          = 1000,
                            seed            = 123,
                            model_id        = "modelo_2")

Modelo 3

modelo_3 <- h2o.deeplearning( x               = c("x_1", "x_2"),
                              y               = "y",
                              distribution    = "multinomial",
                              training_frame  = datos_train,
                              standardize     = TRUE,
                              activation      = "Rectifier",
                              adaptive_rate   = FALSE,
                              hidden          = c(10, 10),
                              stopping_rounds = 0,
                              epochs          = 1000,
                              seed            = 123,
                              model_id        = "modelo_3")

Modelo 4

modelo_4 <- h2o.deeplearning(x               = c("x_1", "x_2"),
                             y               = "y",
                             distribution    = "multinomial",
                             training_frame  = datos_train,
                             standardize     = TRUE,
                             activation      = "Rectifier",
                             adaptive_rate   = FALSE,
                             hidden          = c(50, 50, 50),
                             stopping_rounds = 0,
                             epochs          = 1000,
                             seed            = 123,
                             model_id        = "modelo_4")

Predicciones de cada modelo

# Predicciones de cada modelo

grid_predicciones <- expand.grid(
                        x_1 = seq(from = min(datos$x_1), to = max(datos$x_1), length = 75),
                        x_2 = seq(from = min(datos$x_2), to = max(datos$x_2), length = 75)
                     )

grid_predicciones_h2o <- as.h2o(grid_predicciones)

Asignamos a cada modelo los datos grid_predicciones_h2o

predicciones_1 <- h2o.predict(
                    object  = modelo_1,
                    newdata = grid_predicciones_h2o
                  )

predicciones_2 <- h2o.predict(
                    object  = modelo_2,
                    newdata = grid_predicciones_h2o
                  )
predicciones_3 <- h2o.predict(
                    object  = modelo_3,
                    newdata = grid_predicciones_h2o
                  )

predicciones_4 <- h2o.predict(
                    object  = modelo_4,
                    newdata = grid_predicciones_h2o
                  )

Predicciones y gráfico de las predicciones de cada modelo

Predicciones

# predicciones de cada modelo agregadas a los datos
grid_predicciones$modelo_1 <- as.vector(predicciones_1$predict)
grid_predicciones$modelo_2 <- as.vector(predicciones_2$predict)
grid_predicciones$modelo_3 <- as.vector(predicciones_3$predict)
grid_predicciones$modelo_4 <- as.vector(predicciones_4$predict)

Gráficos

# Gráfico de predicciones
# ==============================================================================
p1 <- ggplot(data = grid_predicciones, aes(x = x_1, y = x_2, color = modelo_1)) + 
      geom_point(size = 0.5) +
      theme_fivethirtyeight() +  
      labs(title = "Arquitectura: (5)") +
      theme_bw()

p2 <- ggplot(data = grid_predicciones, aes(x = x_1, y = x_2, color = modelo_2)) + 
      geom_point(size = 0.5) +
      labs(title = "Arquitectura: (10)") +
      theme_bw()

p3 <- ggplot(data = grid_predicciones, aes(x = x_1, y = x_2, color = modelo_3)) + 
      geom_point(size = 0.5) +
      labs(title = "Arquitectura: (20, 20)") +
      theme_bw()

p4 <- ggplot(data = grid_predicciones, aes(x = x_1, y = x_2, color = modelo_4)) + 
      geom_point(size = 0.5) +
      labs(title = "Arquitectura: (50, 50, 50)") +
      theme_bw()

ggarrange(p1, p2, p3, p4, nrow = 2, ncol = 2)

Puede observarse cómo, a medida que aumenta la complejidad de la red (más neuronas y más capas), las fronteras de decisión se adaptan más y más a los datos de entrenamiento.

Optimización de hiperparámetros

En este apartado, se muestra cómo afectan al aprendizaje algunos de los hiperparámetros más influyentes. Para ello, se combinan los métodos de grid search, random search y early stopping.

Búsqueda cartesiana probando todos los valores definidos por el usuario.

# Número de neuronas
hiperparametros <- list(hidden = c(1, 5, 10, 15, 25, 50, 100, 300, 500))

grid_dl <- h2o.grid(
              algorithm      = "deeplearning",
              activation     = "Rectifier",
              adaptive_rate  = FALSE,
              epochs         = 100,
              # Variable respuesta y predictores
              x              = c("x_1", "x_2"),
              y              = "y",
              training_frame = datos_train,
              # validation_frame = datos_validation, # Para validación simple
              nfolds        = 3, # validación cruzada
              standardize   = TRUE,
              hyper_params  = hiperparametros,
              search_criteria = list(strategy = "Cartesian"),
              seed          = 123,
              grid_id       = "grid_dl"
          )

Resultados de la optimización de hiperparámetros

# Se muestran los modelos ordenados de mayor a menor accuracy
resultados_grid <- h2o.getGrid(
                     sort_by = 'accuracy',
                     grid_id = "grid_dl",
                     decreasing = TRUE
                   )

data.frame(resultados_grid@summary_table) %>% 
  mutate(
    accuracy = as.numeric(accuracy),
    hidden  = str_remove_all(hidden, pattern = "\\[|\\]"),
    hidden  = as.numeric(hidden),
 ) %>%
  ggplot(aes(x=hidden, y=accuracy, group=1)) +
  geom_line() + 
  geom_point() +
  labs(title="Accuracy del modelo vs número de neuronas") + 
  theme_bw()

Tasa de aprendisaje

# Learning rate (Tasa de aprendisaje)
hiperparametros <- list(rate = c(0.00001, 0.0001, 0.0001, 0.001, 0.01, 0.1, 1, 10))

grid_dl_2 <- h2o.grid(
              algorithm      = "deeplearning",
              activation     = "Rectifier",
              adaptive_rate  = FALSE,
              rate_annealing = 0,
              rate_decay     = 0,
              nesterov_accelerated_gradient = FALSE,
              hidden         = 10,
              epochs         = 50,
              x              = c("x_1", "x_2"),
              y              = "y",
              training_frame = datos_train,
              # validation_frame = datos_validation, # Para validación simple
              nfolds         = 3, # validación cruzada
              standardize    = TRUE,
              hyper_params   = hiperparametros,
              search_criteria = list(strategy = "Cartesian"),
              seed           = 123,
              grid_id        = "grid_dl_2"
          )
# Se muestran los modelos ordenados de mayor a menor accuracy
resultados_grid <- h2o.getGrid(
                     sort_by = 'accuracy',
                     grid_id = "grid_dl_2",
                     decreasing = TRUE
                   )

data.frame(resultados_grid@summary_table) %>% select(-model_ids) %>%
  mutate(accuracy = as.numeric(accuracy),
         rate    = as.numeric(rate)) %>%
  ggplot(aes(x=rate, y=accuracy, group=1)) +
  scale_x_continuous(trans='log10') +
  geom_line() + 
  geom_point() +
  labs(title="Accuracy del modelo vs learning rate") + 
  theme_bw()

Si bien los dos ejemplos anteriores sirven para tener una idea intuitiva de cómo afecta cada hiperparámetro, no es posible optimizarlos de forma individual, ya que el impacto final que tiene cada uno depende de qué valor tomen los demás. La búsqueda de hiperparámetros debe hacerse en conjunto.

Óptimizando: Tasa de aprendisaje + número de neuronas

# Learning rate + número de neuronas
hiperparametros <- list(
                      rate = c(0.00001, 0.0001, 0.0001, 0.001, 0.01, 0.1, 1),
                      hidden = c(1, 5, 10, 15, 25, 50, 100, 300, 500)
                    )

grid_dl_3 <- h2o.grid(
              algorithm      = "deeplearning",
              activation     = "Rectifier",
              adaptive_rate  = FALSE,
              rate_annealing = 0,
              rate_decay     = 0,
              nesterov_accelerated_gradient = FALSE,
              epochs         = 50,
              x              = c("x_1", "x_2"),
              y              = "y",
              training_frame = datos_train,
              # validation_frame = datos_validation, # Para validación simple
              nfolds         = 3, # validación cruzada
              standardize    = TRUE,
              hyper_params   = hiperparametros,
              search_criteria = list(strategy = "Cartesian"),
              seed           = 123,
              grid_id        = "grid_dl_3"
          )

resultados_grid <- h2o.getGrid(
                     sort_by = 'accuracy',
                     grid_id = "grid_dl_3",
                     decreasing = TRUE
                   )

data.frame(resultados_grid@summary_table)

Detención temprana del entrenamiento (Early Stopping)

Una de las características de los modelos de deep learning es que, con el número suficiente de épocas, el modelo final tiende a ajustarse perfectamente a los datos de entrenamiento causando overfitting. Este comportamiento implica que el analista tiene que encontrar el número adecuado de épocas y, para ello, suele tener que entrenar el modelo con cientos o miles de épocas hasta identificar el momento en el que empieza el overfitting. Esto suele ser poco eficiente en términos de tiempo, ya que, posiblemente, se estén realizando iteraciones innecesarias.

De nuevo, la experiencia de los creadores de H2O queda reflejada en su herramienta, esta vez incluyendo toda una serie de estrategias para detener el proceso de ajuste de un modelo Deeplearning a partir del momento en el que este deja de mejorar. Por defecto, la métrica empleada se calcula con los datos de validación. Tres argumentos controlan la estrategia de parada:

  • stopping_metric: métrica empleada para cuantificar cuánto mejora el modelo.

  • stopping_tolerance: porcentaje mínimo de mejora entre dos mediciones consecutivas por debajo del cual se considera que el modelo no ha mejorado.

  • stopping_rounds: número de mediciones consecutivas en las que no se debe superar el stopping_tolerance para que el algoritmo se detenga.

Ejemplos de criterios de parada

  1. Detener el entrenamiento de un modelo cuando el error de clasificación no se reduce más de un 1% durante dos mediciones consecutivas: stopping_rounds=2, stopping_tolerance=0.01 y stopping_metric=“misclassification”.

  2. Detener el entrenamiento de un modelo cuando el logloss no se reduce nada durante 3 mediciones consecutivas: stopping_rounds=3, stopping_tolerance=0 y stopping_metric=“logloss”.

  3. Detener el entrenamiento de un modelo cuando la media de AUC no aumenta más de un 0.1% durante cinco mediciones consecutivas: stopping_rounds=5, stopping_tolerance=0.001 y stopping_metric=“AUC”.

  4. También es posible detener el entrenamiento del modelo cuando se supera un tiempo máximo especificado con el argumento max_runtimesecs > 0.

modelo <- h2o.deeplearning(
                  x = c("x_1", "x_2"),
                  y = "y",
                  distribution    = "multinomial",
                  training_frame  = datos_train,
                  validation_frame = datos_validation,
                  standardize     = TRUE,
                  activation      = "Rectifier",
                  adaptive_rate   = FALSE,
                  rate            = 0.1,
                  hidden          = 10,
                  stopping_rounds = 5,
                  stopping_metric = "misclassification",
                  stopping_tolerance = 0.001,
                  score_validation_samples = 1000,
                  epochs          = 100000000,#Múchas epocas
                  seed            = 123)

modelo@model$scoring_history 

plot(modelo)

Random search (busqueda aleatoria)

Dado el elevado número de hiperparámetros que tienen los modelos de redes neuronales, la combinación de posibles configuraciones es muy elevada. Esto hace que la búsqueda de hiperparámetros por grid search cartesiano (todas las combinaciones) sea poco práctica. En su lugar, suele emplearse random grid search, que hace una búsqueda de combinaciones aleatorias.

Algunas combinaciones aleatorias pueden ser muy poco favorables, por esta razón es conveniente activar la parada temprana.

# Hiperparámetros que se quieren optimizar mediante búsqueda aleatoria.
# Se definen los posibles valores de cada hiperparámetro, entre los que se
# escoge aleatoriamente.

hiperparametros <- list(
                     activation = c("Rectifier", "Maxout", "Tanh", "RectifierWithDropout"), 
                     hidden = list(c(5), c(10), c(50), c(10, 10)),
                     l1 = c(0, 0.00001, 0.0001), 
                     l2 = c(0, 0.00001, 0.0001),
                     rate = c(0, 01, 0.005, 0.001),
                     rate_annealing = c(1e-8, 1e-7, 1e-6),
                     rho = c(0.9, 0.95, 0.99, 0.999),
                     epsilon = c(1e-10, 1e-8, 1e-6, 1e-4),
                     momentum_start = c(0, 0.5),
                     momentum_stable = c(0.99, 0.5, 0),
                     input_dropout_ratio = c(0, 0.1, 0.2),
                     max_w2 = c(10, 100, 1000, 3.4028235e+38)
                  )

# Al ser una búsqueda aleatoria, hay que indicar criterios de parada.
search_criteria <- list(
                    strategy = "RandomDiscrete",      
                    max_runtime_secs   = 5*60, # Tiempo máximo de búsqueda (5 minutos)
                    max_models         = 100,  # Número máximo de modelos
                    stopping_tolerance = 0.01,
                    stopping_rounds    = 5,
                    seed               = 1234               
                  )


grid_dl_4 <- h2o.grid(
              algorithm = "deeplearning",
              epochs    = 5000, 
              x         = c("x_1", "x_2"),
              y         = "y",
              training_frame = datos_train,
              # validation_frame = datos_validation, # Validación simple
              nfolds           = 3, # validación cruzada
              standardize      = TRUE,
              hyper_params     = hiperparametros,
              search_criteria  = search_criteria,
              seed             = 123,
              grid_id          = "grid_dl_4"
            )

resultados_grid <- h2o.getGrid(
                     sort_by    = 'accuracy',
                     grid_id    = "grid_dl_4",
                     decreasing = TRUE
                   )

data.frame(resultados_grid@summary_table)

Ejemplo de regresión: predicción de precios vivienda

El set de datos SaratogaHouses del paquete mosaicData contiene información sobre la precio de 1728 viviendas situadas en Saratoga County, New York, USA en el año 2006. Además del precio, incluye 15 variables adicionales:

  • price: precio de la vivienda.
  • lotSize: metros cuadrados de la vivienda.
  • age: antigüedad de la vivienda.
  • landValue: valor del terreno.
  • livingArea: metros cuadrados habitables.
  • pctCollege: porcentaje del vecindario con título universitario.
  • bedrooms: número de dormitorios.
  • firplaces: número de chimeneas.
  • bathrooms: número de cuartos de baño (el valor 0.5 hace referencia a cuartos de baño sin ducha).
  • rooms: número de habitaciones.
  • heating: tipo de calefacción.
  • fuel: tipo de alimentación de la calefacción (gas, electricidad o diesel).
  • sewer: tipo de desagüe.
  • waterfront: si la vivienda tiene vistas al lago.
  • newConstruction: si la vivienda es de nueva construcción.
  • centralAir: si la vivienda tiene aire acondicionado.

El objetivo es obtener un modelo de red neuronal capaz de predecir el precio de la vivienda.

Librerías y datos que se usarán

# Tratamiento de datos y gráficos
library(tidymodels)
library(tidyverse)
library(skimr)
library(DataExplorer)
library(ggpubr)
library(mosaicData)

# Modelado
library(h2o)

Los datos

# Datos simulados
datos = read_csv("~/Documents/utb_diplomado/sem_5.4/datos/SaratogaHouses.csv")

head(datos)

Renombramos las columnas para que sean más descriptivas

# Se renombran las columnas para que sean más descriptivas
colnames(datos) <- c("precio", "metros_totales", "antiguedad", "precio_terreno",
                     "metros_habitables", "universitarios",
                     "dormitorios", "chimenea", "banyos", "habitaciones",
                     "calefaccion", "consumo_calefacion", "desague",
                     "vistas_lago","nueva_construccion", "aire_acondicionado")

Análisis exploratorio

Antes de entrenar un modelo predictivo, o incluso antes de realizar cualquier cálculo con un nuevo conjunto de datos, es importante realizar una exploración descriptiva de los mismos. Este proceso permite entender mejor qué información contiene cada variable, así como detectar posibles errores.

Tabla resumen

# Tabla resumen
skim(datos)

Todas las columnas tienen el tipo adecuado.

# Número de datos ausentes por variable
datos %>% map_dbl(.f = function(x){sum(is.na(x))})

Gráfico de los missing values

plot_missing(
  data    = datos, 
  title   = "Porcentaje de valores ausentes",
  ggtheme = theme_bw(),
  theme_config = list(legend.position = "none")
)

Las columnas están completas, no hay valores ausentes.

Análisis gráfico de las variables

Distribuciónd de la variable respuesta

# Distribución variable respuesta
ggplot(data = datos, aes(x = precio)) +
  geom_density(fill = "steelblue", alpha = 0.8) +
  geom_rug(alpha = 0.1) +
  labs(title = "Distribución original") +
  theme_bw() 

Summary de los datos

# Tabla de estadísticos principales 
summary(datos$precio)

Gráfico de distribución para cada variable numérica

# Gráfico de distribucion para cada variable numerica
datos %>% select(-precio) %>%
  keep(is.numeric) %>% 
  gather() %>% 
    ggplot(aes(value)) +
        geom_density(fill = "steelblue", alpha = 0.8) +
        labs(title   = "Distribución variables continuas", x = "Value", y = "Density")+
        facet_wrap(~ key, scales = "free")

La variable chimenea, aunque es de tipo numérico, apenas toma unos pocos valores y la gran mayoría de observaciones pertenecen a solo dos de ellos. En casos como este, suele ser conveniente tratar la variable como cualitativa.

Valores observados de chimenea

# Valores observados de chimenea
table(datos$chimenea)

Se convierte la variable chimenea a factor

# Se convierte la variable chimenea a factor.
datos <- datos %>%
         mutate(chimenea = as.factor(chimenea))
# Grafico para cada variable cualitativa
plot_bar(
  datos,
  ncol    = 3,
  title   = "Número de observaciones por grupo",
  ggtheme = theme_bw(),
  theme_config = list(
                   plot.title = element_text(size = 14, face = "bold"),
                   strip.text = element_text(colour = "black", size = 8, face = 2),
                   legend.position = "none"
                  )
)

Recodificación de la variable chimenea

Si alguno de los niveles de una variable cualitativa tiene muy pocas observaciones en comparación a los otros niveles, puede ocurrir que, durante la validación cruzada o bootstrapping, algunas particiones no contengan ninguna observación de dicha clase (varianza cero), lo que puede dar lugar a errores. Para este caso, hay que tener precaución con la variable chimenea. Se unifican los niveles de 2, 3 y 4 en un nuevo nivel llamado “2_mas”.

Recodificamos la variable chimenea

datos <- datos %>%
         mutate(
           chimenea = recode_factor(
                        chimenea,
                        `2` = "2_mas",
                        `3` = "2_mas",
                        `4` = "2_mas"
                      )
         )

table(datos$chimenea)

División de los datos: train y test

Con el objetivo de poder estimar el error que comete el modelo al predecir nuevas observaciones, se dividen los datos en dos grupos, uno de entrenamiento y otro de test (80%, 20%).

# Reparto de datos en train y test
set.seed(123)
split_inicial <- initial_split(
                    data   = datos,
                    prop   = 0.8,
                    strata = precio
                 )

datos_train <- training(split_inicial)
datos_test  <- testing(split_inicial)

Preprocesado de los datos

Los modelos de redes neuronales requieren como mínimo de dos tipos de preprocesado: binarización (one hot encoding) de las variables categóricas y estandarización de las variables continuas.

# Se almacenan en un objeto `recipe` todos los pasos de preprocesado y, finalmente,
# se aplican a los datos.
transformer <- recipe(
                  formula = precio ~ .,
                  data =  datos_train
               ) %>%
               step_naomit(all_predictors()) %>%
               step_nzv(all_predictors()) %>%
               step_center(all_numeric(), -all_outcomes()) %>%
               step_scale(all_numeric(), -all_outcomes()) %>%
               step_dummy(all_nominal(), -all_outcomes())

transformer

Una vez que se ha definido el objeto recipe, con la función prep() se aprenden las transformaciones con los datos de entrenamiento y se aplican a los dos conjuntos con bake().

# Se entrena el objeto recipe
transformer_fit <- prep(transformer)

# Se aplican las transformaciones al conjunto de entrenamiento y de test
datos_train_prep <- bake(transformer_fit, new_data = datos_train)
datos_test_prep  <- bake(transformer_fit, new_data = datos_test)

glimpse(datos_train_prep)

Tras el preprocesado de los datos, se han generado un total de 19 variables (18 predictores y la variable respuesta).

Modelado

Inicialización del cluster

# Inicialización del cluster
h2o.init(
  nthreads = -1,
  max_mem_size = "4g"
)

Se eliminan los datos del cluster por si ya había sido iniciado

# Se eliminan los datos del cluster por si 
#ya había sido iniciado.
h2o.removeAll()
h2o.no_progress()

Se transfieren los datos al cluster de H2O.

datos_train  <- as.h2o(datos_train_prep, key = "datos_train")
datos_test   <- as.h2o(datos_test_prep, key = "datos_test")

Búsqueda de los hiperparámetros

# Espacio de búsqueda de cada hiperparámetro
hiperparametros <- list(
                      epochs = c(50, 100, 500),
                      hidden = list(5, 10, 25, 50, c(10, 10))
                    )

Búsqueda por validación cruzada

# Búsqueda por validación cruzada
variable_respuesta <- 'precio'
predictores <- setdiff(colnames(datos_train), 
                       variable_respuesta)

grid <- h2o.grid(
              algorithm    = "deeplearning",
              activation   = "Rectifier",
              x            = predictores,
              y            = variable_respuesta,
              training_frame  = datos_train,
              nfolds       = 3, #validacion cruzada
              standardize  = FALSE,
              hyper_params = hiperparametros,
              search_criteria = list(strategy = "Cartesian"),
              seed         = 123,
              grid_id      = "grid"
          )

Resultados del grid

# Resultados del grid
resultados_grid <- h2o.getGrid(
                     sort_by = 'rmse',
                     grid_id = "grid",
                     decreasing = FALSE
                   )
data.frame(resultados_grid@summary_table)

Mejor modelo encontrado

# Mejor modelo encontrado
modelo_final <- h2o.getModel(resultados_grid@model_ids[[1]])

modelo_final

Error de predicción del test

Aunque mediante los métodos de validación se consiguen buenas estimaciones del error que tiene un modelo al predecir nuevas observaciones, la mejor forma de evaluar un modelo final es prediciendo un conjunto test, es decir, un conjunto de observaciones que se ha mantenido al margen del proceso de entrenamiento y optimización.

#predicción del test h2o to R
predicciones_h2o <- h2o.predict(
                  object  = modelo_final,
                  newdata = datos_test
                )

predicciones_R <- predicciones %>%
                as_tibble() %>%
                mutate(valor_real = as.vector(datos_test$precio))
predicciones_R %>% head(5)
rmse(predicciones_R, truth = valor_real, estimate = predict, na_rm = TRUE)

Conclusión

La combinación de hiperparámetros con la que se obtienen mejores resultados acorde a las métricas de validación cruzada es:

modelo_final@allparameters

Ejercicio

Descripción del conjunto de datos german_credit

  • Es un dato de crédito de un banco alemán que consta de 21 variables y 1000 registros.
  • La variable dependiente o objetivo es la “credibilidad” que explica si se debe otorgar un préstamo a un cliente en función de sus perfiles.

https://raw.githubusercontent.com/deepanshu88/Datasets/master/UploadedFiles/german_credit.csv

  1. descargue los datos
  2. generar el train y el test
  3. ajustar el modelo de redes neuronales usando la libreria H2O
  4. crear las predicciones
  5. construya la matriz de confusión