Uso del aprendizaje automático para predecir el clima: Parte 3

Este es el último artículo sobre el uso del aprendizaje automático en Python para hacer predicciones de la temperatura media en función de los datos meteorológicos obtenidos de...

Este es el artículo final sobre el uso del aprendizaje automático en Python para hacer predicciones de la temperatura media en función de los datos meteorológicos obtenidos de Clima subterráneo como se describe en parte uno de esta serie.

El tema de este último artículo será construir un regresor de red neuronal utilizando la biblioteca de código abierto [TensorFlow] (https://www.tensorflow.org/) de Google. Para obtener una introducción general a TensorFlow, así como una discusión sobre los métodos de instalación, consulte la excelente publicación de Mihajlo Pavloski Tutorial de red neuronal TensorFlow.

Los temas que cubriré en este artículo incluyen:

  • Comprender la teoría de las redes neuronales artificiales
  • API de estimador de alto nivel de TensorFlow
  • Construyendo un DNNRegressor para predecir el clima

Comprensión de la teoría de las redes neuronales artificiales {#comprensión de la teoría de las redes neuronales artificiales}

En el último artículo (parte 2) describí el proceso de construcción de un modelo de regresión lineal, una venerable técnica de aprendizaje automático que subyace a muchos otros, para predecir la temperatura media diaria en Lincoln, Nebraska. Los modelos de regresión lineal son extremadamente potentes y se han utilizado para hacer predicciones numéricas y categóricas desde mucho antes de que se acuñara el término "aprendizaje automático". Sin embargo, la técnica tiene algunas críticas, principalmente en torno a su suposición rígida de una relación lineal entre la variable dependiente y la(s) variable(s) independiente(s).

Existe una cantidad incontable de otros algoritmos en la industria de la ciencia de datos y el aprendizaje automático que superan esta suposición de linealidad. Una de las áreas de enfoque más populares en los últimos años ha sido aplicar Redes neuronales a una amplia gama de problemas de aprendizaje automático. Las redes neuronales tienen una forma poderosa de utilizar técnicas de aprendizaje basadas en operaciones tanto lineales como no lineales.

Las redes neuronales están inspiradas en las neuronas biológicas del cerebro que funcionan en una red compleja de interacciones para transmitir, recopilar y aprender información basada en un historial de la información que ya se ha recopilado. Las redes neuronales computacionales que nos interesan son similares a las neuronas del cerebro en que son una colección de neuronas (nodos) que reciben señales de entrada (cantidades numéricas), procesan la entrada y transmiten las señales procesadas a otros agentes aguas abajo en la red El procesamiento de señales como cantidades numéricas que pasan a través de la red neuronal es una característica muy poderosa que no se limita a las relaciones lineales.

En esta serie me he centrado en un tipo específico de aprendizaje automático llamado aprendizaje supervisado, que simplemente significa que los modelos que se entrenan se construyen utilizando datos que se conocen resultados objetivo que el modelo está tratando de aprender a predecir. Además, el tipo de predicciones que se hacen son valores reales numéricos, lo que significa que estamos tratando con algoritmos de predicción regresor.

Gráficamente, en la siguiente imagen se muestra una red neuronal similar a la que se describe en este artículo.

Neural Network{.img-responsive}

La red neuronal que se muestra arriba contiene una capa de entrada en el extremo izquierdo que representa dos funciones, x1 y x2, que alimentan la red neuronal. Esas dos características se introducen en la red neuronal, que se procesan y transmiten a través de dos capas de neuronas, que se denominan capas ocultas. Esta representación muestra dos capas ocultas con cada capa que contiene tres neuronas (nodos). Luego, la señal sale de la red neuronal y se agrega a la capa de salida como un único valor numérico predicho.

Permítanme tomarme un momento para explicar el significado detrás de las flechas que indican que los datos se procesan de un nodo a otro a través de las capas. Cada flecha representa una transformación matemática de un valor, comenzando en la base de la flecha, que luego se multiplica por un peso específico para esa ruta. Cada nodo dentro de una capa recibirá un valor de esta manera. Luego se suman todos los valores que convergen en el nodo. Es este agregado de multiplicar por pesos y sumar los productos lo que define las operaciones lineales de una red neuronal que mencioné anteriormente.

Activation Function{.img-responsive}

Después de realizar la suma en cada nodo, se aplica una función especial, no lineal, a la suma, que se representa en la imagen de arriba como Fn(...). Esta función especial que introduce características no lineales en una red neuronal se denomina función de activación. Es esta característica no lineal provocada por las funciones de activación lo que le da su poder a las redes neuronales multicapa. Si no fuera por la no linealidad agregada al proceso, todas las capas se combinarían algebraicamente en una operación constante que consiste en multiplicar las entradas por algún valor de coeficiente plano (es decir, un modelo lineal).

Muy bien, todo está bien y elegante, pero espero que te estés preguntando en el fondo de tu mente… está bien, Adam, pero ¿cómo se traduce esto en un algoritmo de aprendizaje? Bueno, la respuesta más directa a eso es evaluar las predicciones que se están haciendo, la salida del modelo "y", a los valores reales esperados (los objetivos) y hacer una serie de ajustes a los pesos de una manera que mejore la precisión general de la predicción.

En el mundo de los algoritmos de aprendizaje automático de regresores, uno evalúa la precisión mediante el uso de una función de costo (también conocida como "pérdida" u "objetivo"), a saber, la [suma de errores al cuadrado] (https://en.wikipedia .org/wiki/Residual_sum_of_squares) (SSE). Tenga en cuenta que generalicé esa declaración a todo el continuo del aprendizaje automático, no solo a las redes neuronales. En el artículo anterior, el algoritmo de los mínimos cuadrados ordinarios logró precisamente eso, encontró las combinaciones de coeficientes que minimizaban la suma de los errores al cuadrado (es decir, los mínimos cuadrados).

Nuestro regresor de red neuronal hará exactamente lo mismo. Iterará sobre los datos de entrenamiento que alimentan los valores de las características, calculará la función de costo (usando SSE) y hará ajustes a los pesos de una manera que minimice la función de costo. Este proceso de pasar funciones iterativamente a través del algoritmo y evaluar cómo ajustar los pesos en función de la función de costo es, en esencia, lo que se conoce como optimización del modelo.

Los algoritmos de optimización de modelos son muy importantes en la construcción de redes neuronales robustas. A medida que los ejemplos se alimentan a través de la arquitectura de las redes (es decir, el ancho y la profundidad) y luego se evalúan frente a la función de costo, se ajustan los pesos. Se dice que los modelos están "aprendiendo" cuando la función del optimizador identifica que se realizó un ajuste de peso de manera que no mejora (baja) la función de costo, la cual se registra con el optimizador para que no ajuste los pesos en esa dirección de nuevo.

API de estimador de alto nivel de TensorFlow

La biblioteca TensorFlow de Google consta de algunas API, siendo la más popular la Core API, que brinda al usuario un conjunto de herramientas de bajo nivel para definir y entrenar esencialmente cualquier algoritmo de aprendizaje automático mediante operaciones simbólicas. Esto se conoce como TensorFlow Core. Si bien TensorFlow Core es una API increíble con una gran capacidad de aplicación, me centraré en una API más nueva y de mayor nivel desarrollada por el equipo de TensorFlow que se conoce colectivamente como la API Estimator.

El equipo de TensorFlow desarrolló la API Estimator para hacer que la biblioteca sea más accesible para el desarrollador cotidiano. Esta API de alto nivel proporciona una interfaz común para ’entrenar (…)’ modelos, ’evaluar (…)’ modelos y ‘predecir (…)’ resultados de casos desconocidos similares a (e influenciados por) el popular biblioteca Sci-Kit Learn, que se logra mediante la implementación de una interfaz común para varios algoritmos. Además, integrado en la API de alto nivel hay una gran cantidad de mejores prácticas de aprendizaje automático, abstracciones y capacidad de escalabilidad.

Toda esta bondad de aprendizaje automático genera un conjunto de herramientas implementadas en la clase básica Estimator, así como múltiples tipos de modelos predefinidos que reducen la barrera de entrada para usar TensorFlow para que pueda aplicarse a una gran cantidad de problemas cotidianos (u oportunidades). ). Al abstraer gran parte de los aspectos mundanos y manuales de cosas como escribir bucles de entrenamiento o manejar sesiones, el desarrollador puede concentrarse en cosas más importantes como probar rápidamente múltiples modelos y arquitecturas de modelos para encontrar el que mejor se adapte a sus necesidades.

En este artículo, describiré cómo usar uno de los estimadores de redes neuronales profundas más potentes, el DNNRegressor.

Creación de un regresor DNN para predecir el clima {#construir un regresor de adnn para predecir el clima}

Permítanme comenzar importando varias bibliotecas diferentes que usaré para construir el modelo:

1
2
3
4
5
6
7
import pandas as pd
import numpy as np
import tensorflow as tf
from sklearn.metrics import explained_variance_score, \
    mean_absolute_error, \
    median_absolute_error
from sklearn.model_selection import train_test_split

Ahora pongamos nuestras manos en los datos y tomemos un par de picos nuevamente para familiarizarnos con ellos. He colocado todo el código y los datos en mi repositorio de GitHub aquí para que los lectores puedan seguirlo.

1
2
3
4
5
# read in the csv data into a pandas data frame and set the date as the index
df = pd.read_csv('end-part2_df.csv').set_index('date')

# execute the describe() function and transpose the output so that it doesn't overflow the width of the screen
df.describe().T
                count   mean          std         min     25%      50%      75%       max

tiempo medio 997,0 13,129388 10,971591 -17,0 5,0 15,0 22,00 32,00 temperatura máxima 997,0 19,509529 11,577275 -12,0 11,0 22,0 29,00 38,00 mintempm 997,0 6,438315 10,957267 -27,0 -2,0 7,0 16,00 26,00 meantempm_1 997,0 13,109328 10,984613 -17,0 5,0 15,0 22,00 32,00 meantempm_2 997,0 13,088265 11,001106 -17,0 5,0 14,0 22,00 32,00 meantempm_3 997,0 13,066199 11,017312 -17,0 5,0 14,0 22,00 32,00 meandewptm_1 997,0 6,440321 10,596265 -22,0 -2,0 7,0 16,00 24,00 meandewptm_2 997,0 6,420261 10,606550 -22,0 -2,0 7,0 16,00 24,00 meandewptm_3 997,0 6,393180 10,619083 -22,0 -2,0 7,0 16,00 24,00 presión mediam_1 997,0 1016,139418 7,582453 989,0 1011,0 1016,0 1021,00 1040,00 presión mediam_2 997,0 1016,142427 7,584185 989,0 1011,0 1016,0 1021,00 1040,00 presión mediam_3 997,0 1016,151454 7,586988 989,0 1011,0 1016,0 1021,00 1040,00 humedad máx_1 997,0 88,107322 9,280627 47,0 83,0 90,0 93,00 100,00 humedad máx_2 997,0 88,106319 9,280152 47,0 83,0 90,0 93,00 100,00 humedad máx_3 997,0 88,093280 9,276775 47,0 83,0 90,0 93,00 100,00 humedad mínima_1 997,0 46,025075 16,108517 9,0 35,0 45,0 56,00 92,00 humedad mínima_2 997,0 46,021063 16,105530 9,0 35,0 45,0 56,00 92,00 humedad mínima_3 997,0 45,984955 16,047081 9,0 35,0 45,0 56,00 92,00 maxtempm_1 997,0 19,489468 11,588542 -12,0 11,0 22,0 29,00 38,00 maxtempm_2 997,0 19,471414 11,603318 -12,0 11,0 22,0 29,00 38,00 maxtempm_3 997,0 19,455366 11,616412 -12,0 11,0 22,0 29,00 38,00 mintempm_1 997,0 6,417252 10,974433 -27,0 -2,0 7,0 16,00 26,00 mintempm_2 997,0 6,394183 10,988954 -27,0 -2,0 7,0 16,00 26,00 mintempm_3 997,0 6,367101 11,003451 -27,0 -2,0 7,0 16,00 26,00 maxdewptm_1 997,0 9,378134 10,160778 -18,0 1,0 11,0 18,00 26,00 maxdewptm_2 997,0 9,359077 10,171790 -18,0 1,0 11,0 18,00 26,00 maxdewptm_3 997,0 9,336008 10,180521 -18,0 1,0 11,0 18,00 26,00 mindewptm_1 997,0 3,251755 11,225411 -28,0 -6,0 4,0 13,00 22,00 mindewptm_2 997,0 3,229689 11,235718 -28,0 -6,0 4,0 13,00 22,00 mindewptm_3 997,0 3,198596 11,251536 -28,0 -6,0 4,0 13,00 22,00 presiónmáxm_1 997,0 1019,913741 7,755590 993,0 1015,0 1019,0 1024,00 1055,00 presiónmáxm_2 997,0 1019,917753 7,757705 993,0 1015,0 1019,0 1024,00 1055,00 presiónmáxm_3 997,0 1019,927783 7,757805 993,0 1015,0 1019,0 1024,00 1055,00 presiónmínm_1 997,0 1012,317954 7,885743 956,0 1008,0 1012,0 1017,00 1035,00 presiónmínm_2 997,0 1012,319960 7,886681 956,0 1008,0 1012,0 1017,00 1035,00 presiónmínm_3 997,0 1012,326981 7,889511 956,0 1008,0 1012,0 1017,00 1035,00 precipm_1 997,0 2,593180 8,428058 0,0 0,0 0,0 0,25 95,76 precipm_2 997,0 2,593180 8,428058 0,0 0,0 0,0 0,25 95,76 precipitación_3 997,0 2,573049 8,410223 0,0 0,0 0,0 0,25 95,76

1
2
# execute the info() function
df.info()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<class 'pandas.core.frame.DataFrame'>
Index: 997 entries, 2015-01-04 to 2017-09-27
Data columns (total 39 columns):
meantempm          997 non-null int64
maxtempm           997 non-null int64
mintempm           997 non-null int64
meantempm_1        997 non-null float64
meantempm_2        997 non-null float64
meantempm_3        997 non-null float64
meandewptm_1       997 non-null float64
meandewptm_2       997 non-null float64
meandewptm_3       997 non-null float64
meanpressurem_1    997 non-null float64
meanpressurem_2    997 non-null float64
meanpressurem_3    997 non-null float64
maxhumidity_1      997 non-null float64
maxhumidity_2      997 non-null float64
maxhumidity_3      997 non-null float64
minhumidity_1      997 non-null float64
minhumidity_2      997 non-null float64
minhumidity_3      997 non-null float64
maxtempm_1         997 non-null float64
maxtempm_2         997 non-null float64
maxtempm_3         997 non-null float64
mintempm_1         997 non-null float64
mintempm_2         997 non-null float64
mintempm_3         997 non-null float64
maxdewptm_1        997 non-null float64
maxdewptm_2        997 non-null float64
maxdewptm_3        997 non-null float64
mindewptm_1        997 non-null float64
mindewptm_2        997 non-null float64
mindewptm_3        997 non-null float64
maxpressurem_1     997 non-null float64
maxpressurem_2     997 non-null float64
maxpressurem_3     997 non-null float64
minpressurem_1     997 non-null float64
minpressurem_2     997 non-null float64
minpressurem_3     997 non-null float64
precipm_1          997 non-null float64
precipm_2          997 non-null float64
precipm_3          997 non-null float64
dtypes: float64(36), int64(3)
memory usage: 311.6+ KB

Tenga en cuenta que tenemos poco menos de 1000 registros de datos meteorológicos y que todas las características son de naturaleza numérica. Además, debido a nuestro arduo trabajo en el primer artículo, todos los registros están completos en el sentido de que no faltan (no son nulos) ningún valor.

Ahora eliminaré las columnas "mintempm" y "maxtempm" ya que no tienen ningún significado para ayudarnos a predecir las temperaturas medias promedio. Estamos tratando de predecir el futuro por lo que obviamente no podemos tener datos sobre el futuro. También separaré las características (X) de los objetivos (y).

1
2
3
4
5
6
7
8
# First drop the maxtempm and mintempm from the dataframe
df = df.drop(['mintempm', 'maxtempm'], axis=1)

# X will be a pandas dataframe of all columns except meantempm
X = df[[col for col in df.columns if col != 'meantempm']]

# y will be a pandas series of the meantempm
y = df['meantempm']

Al igual que con todas las aplicaciones de aprendizaje automático supervisado, dividiré mi conjunto de datos en conjuntos de entrenamiento y prueba. Sin embargo, para explicar mejor el proceso iterativo de entrenamiento de esta red neuronal, usaré un conjunto de datos adicional al que me referiré como "conjunto de validación". Para el conjunto de entrenamiento, utilizaré el 80 % de los datos y para el conjunto de prueba y validación, cada uno será el 10 % de los datos restantes.

Para dividir estos datos, volveré a utilizar train_test_split(...) de Sci-Kit Learn.

1
2
# split data into training set and a temporary set using sklearn.model_selection.traing_test_split
X_train, X_tmp, y_train, y_tmp = train_test_split(X, y, test_size=0.2, random_state=23)
1
2
3
4
5
6
7
# take the remaining 20% of data in X_tmp, y_tmp and split them evenly
X_test, X_val, y_test, y_val = train_test_split(X_tmp, y_tmp, test_size=0.5, random_state=23)

X_train.shape, X_test.shape, X_val.shape
print("Training instances   {}, Training features   {}".format(X_train.shape[0], X_train.shape[1]))
print("Validation instances {}, Validation features {}".format(X_val.shape[0], X_val.shape[1]))
print("Testing instances    {}, Testing features    {}".format(X_test.shape[0], X_test.shape[1]))
1
2
3
Training instances   797, Training features   36
Validation instances 100, Validation features 36
Testing instances    100, Testing features    36

El primer paso a seguir cuando se construye un modelo de red neuronal es instanciar la clase tf.estimator.DNNRegressor(...). El constructor de clases tiene varios parámetros, pero me centraré en lo siguiente:

  • feature_columns: una estructura similar a una lista que contiene una definición del nombre y los tipos de datos para las características que se introducen en el modelo
  • hidden_units: una estructura similar a una lista que contiene una definición del ancho y la profundidad del número de la red neuronal
  • optimizer: una instancia de la subclase tf.Optimizer, que optimiza los pesos del modelo durante el entrenamiento; su valor predeterminado es el optimizador AdaGrad.
  • activation_fn: una función de activación utilizada para introducir no linealidad en la red en cada capa; el valor predeterminado es ReLU
  • model_dir: se creará un directorio que contendrá metadatos y otros puntos de control guardados para el modelo

Comenzaré definiendo una lista de columnas de características numéricas. Para hacer esto, uso la función tf.feature_column.numeric_column() que devuelve una instancia de FeatureColumn para características numéricas de valor continuo.

1
feature_cols = [tf.feature_column.numeric_column(col) for col in X.columns]

Con las columnas de características definidas, ahora puedo instanciar la clase DNNRegressor y almacenarla en la variable regresora. Especifico que quiero una red neuronal que tenga dos capas de profundidad donde ambas capas tengan un ancho de 50 nodos. También indico que quiero que los datos de mi modelo se almacenen en un directorio llamado tf_wx_model.

1
2
3
regressor = tf.estimator.DNNRegressor(feature_columns=feature_cols,
                                      hidden_units=[50, 50],
                                      model_dir='tf_wx_model')
1
2
INFO:tensorflow:Using default config.
INFO:tensorflow:Using config: {'_tf_random_seed': 1, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_model_dir': 'tf_wx_model', '_log_step_count_steps': 100, '_keep_checkpoint_every_n_hours': 10000, '_save_summary_steps': 100, '_keep_checkpoint_max': 5, '_session_config': None}

Lo siguiente que quiero hacer es definir una función reutilizable que se denomina genéricamente "función de entrada", a la que llamaré wx_input_fn(...). Esta función se utilizará para alimentar datos a mi red neuronal durante las fases de entrenamiento y prueba. Hay muchas formas diferentes de crear funciones de entrada, pero describiré cómo definir y usar una basada en tf.estimator.inputs.pandas_input_fn(...) ya que mis datos están en estructuras de datos pandas.

1
2
3
4
5
6
def wx_input_fn(X, y=None, num_epochs=None, shuffle=True, batch_size=400):
    return tf.estimator.inputs.pandas_input_fn(x=X,
                                               y=y,
                                               num_epochs=num_epochs,
                                               shuffle=shuffle,
                                               batch_size=batch_size)

Tenga en cuenta que esta función wx_input_fn(...) toma un parámetro obligatorio y cuatro opcionales que luego se entregan a una función de entrada de TensorFlow específicamente para datos de pandas, que se devuelve. Esta es una característica muy poderosa de la API de TensorFlow (y Python y otros lenguajes que tratan las funciones como ciudadanos de primera clase).

Los parámetros de la función se definen de la siguiente manera:

  • X: las características de entrada que se introducirán en uno de los tres métodos de interfaz DNNRegressor (train, evaluate y predict)
  • y: los valores objetivo de X, que son opcionales y no se proporcionarán a la llamada predict
  • num_epochs: Un parámetro opcional. Una época ocurre cuando el algoritmo se ejecuta sobre todo el conjunto de datos una vez.
  • shuffle: un parámetro opcional, especifica si seleccionar aleatoriamente un lote (subconjunto) del conjunto de datos cada vez que se ejecuta el algoritmo
  • batch_size: El número de muestras a incluir cada vez que se ejecuta el algoritmo

Con nuestra función de entrada definida, ahora podemos entrenar nuestra red neuronal en nuestro conjunto de datos de entrenamiento. Para los lectores que están familiarizados con la API de alto nivel de TensorFlow, probablemente noten que estoy siendo un poco poco convencional acerca de cómo estoy entrenando mi modelo. Es decir, al menos desde la perspectiva de los tutoriales actuales en el sitio web de TensorFlow y otros tutoriales en la web.

Normalmente, verá algo como lo siguiente cuando entrene uno de estos modelos preestablecidos de API de alto nivel.

1
2
3
4
5
regressor.train(input_fn=input_fn(training_data, num_epochs=None, shuffle=True), steps=some_large_number)

.....
lots of log info
....

Luego, el autor saltará directamente a la demostración de la función evaluar (...) y apenas insinuará la descripción de lo que hace o por qué existe esta línea de código.

1
2
3
4
5
regressor.evaluate(input_fn=input_fn(eval_data, num_epochs=1, shuffle=False), steps=1)

.....
less log info
....

Y después de esto, pasarán directamente a ejecutar la función predecir (...) asumiendo que todo es perfecto con el modelo entrenado.

1
predictions = regressor.predict(input_fn=input_fn(pred_data, num_epochs=1, shuffle=False), steps=1)

Para el recién llegado de ML que lee este tipo de tutorial, me estremezco. Hay mucho más pensamiento en esas tres líneas de código que merece más atención. Este, creo, es el único inconveniente de tener una API de alto nivel: se vuelve muy fácil armar un modelo sin comprender los puntos clave. Espero proporcionar una explicación razonable de cómo entrenar y evaluar esta red neuronal de una manera que minimice el riesgo de que este modelo se adapte demasiado o no a los datos de entrenamiento.

Entonces, sin más demora, permítanme definir un ciclo de entrenamiento simple para entrenar el modelo en los datos de entrenamiento y evaluarlo periódicamente en los datos de evaluación.

1
2
3
4
5
6
7
8
evaluations = []
STEPS = 400
for i in range(100):
    regressor.train(input_fn=wx_input_fn(X_train, y=y_train), steps=STEPS)
    evaluations.append(regressor.evaluate(input_fn=wx_input_fn(X_val,
                                                               y_val,
                                                               num_epochs=1,
                                                               shuffle=False)))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Saving checkpoints for 1 into tf_wx_model/model.ckpt.
INFO:tensorflow:step = 1, loss = 1.11335e+07
INFO:tensorflow:global_step/sec: 75.7886
INFO:tensorflow:step = 101, loss = 36981.3 (1.321 sec)
INFO:tensorflow:global_step/sec: 85.0322
... A WHOLE LOT OF LOG OUTPUT ...
INFO:tensorflow:step = 39901, loss = 5205.02 (1.233 sec)
INFO:tensorflow:Saving checkpoints for 40000 into tf_wx_model/model.ckpt.
INFO:tensorflow:Loss for final step: 4557.79.
INFO:tensorflow:Starting evaluation at 2017-12-05-13:48:43
INFO:tensorflow:Restoring parameters from tf_wx_model/model.ckpt-40000
INFO:tensorflow:Evaluation [1/1]
INFO:tensorflow:Finished evaluation at 2017-12-05-13:48:43
INFO:tensorflow:Saving dict for global step 40000: average_loss = 10.2416, global_step = 40000, loss = 1024.16
INFO:tensorflow:Starting evaluation at 2017-12-05-13:48:43
INFO:tensorflow:Restoring parameters from tf_wx_model/model.ckpt-40000
INFO:tensorflow:Finished evaluation at 2017-12-05-13:48:43
INFO:tensorflow:Saving dict for global step 40000: average_loss = 10.2416, global_step = 40000, loss = 1024.16

El ciclo anterior itera 100 veces. En el cuerpo del ciclo llamo al método train(...) del objeto regresor, pasándole mi reutilizable wx_input_fn(...) que a su vez pasa mi conjunto de funciones de entrenamiento y objetivos. A propósito, dejé los parámetros predeterminados num_epochs igual a Ninguno, que básicamente dice "No me importa cuántas veces pasas por encima del conjunto de entrenamiento, solo sigue entrenando el algoritmo contra cada tamaño de lote predeterminado de 400\ (aproximadamente la mitad del tamaño del conjunto de entrenamiento). También dejé el parámetro shuffle igual a su valor predeterminado de True para que, durante el entrenamiento, los datos se seleccionen aleatoriamente para evitar cualquier relación secuencial en los datos. El parámetro final del método train(...) es steps, que configuré en 400, lo que significa que el conjunto de entrenamiento se procesará por lotes 400 veces por ciclo.

Esto me da un buen momento para explicar de una forma numérica más concreta cuál es el significado de una época. Recuerde de las viñetas anteriores que una época ocurre cuando todos los registros de un conjunto de entrenamiento pasan a través de la red neuronal para entrenar exactamente una vez. Entonces, si tenemos alrededor de 800 (797 para ser exactos) registros en nuestro conjunto de entrenamiento y cada lote selecciona 400, entonces por cada dos lotes hemos logrado una época. Por lo tanto, si iteramos sobre el conjunto de entrenamiento para 100 iteraciones de 400 pasos cada una con un tamaño de lote de 400 (la mitad de una época por lote) obtenemos:

1
(100 x 400 / 2) = 20,000 epochs

Ahora puede que se pregunte por qué ejecuté el método evaluate(...) para cada iteración del ciclo y capturé su salida en una lista. Primero déjame explicarte lo que sucede cada vez que se activa el método train(...). Selecciona un lote aleatorio de registros de entrenamiento y los empuja a través de la red hasta que se realiza una predicción, y para cada registro se calcula la función de pérdida. Luego, en función de la pérdida calculada, los pesos se ajustan de acuerdo con la lógica del optimizador, que hace un buen trabajo al hacer ajustes hacia la dirección que reduce la pérdida general para la próxima iteración. Estos valores de pérdida, en general siempre que la tasa de aprendizaje sea lo suficientemente pequeña, disminuyen con el tiempo con cada iteración o paso.

Sin embargo, después de una cierta cantidad de estas iteraciones de aprendizaje, los pesos comienzan a verse influenciados no solo por las tendencias generales en los datos, sino también por el ruido no informativo heredado en prácticamente todos los datos reales. En este punto, la red está demasiado influenciada por las idiosincrasias de los datos de entrenamiento y se vuelve incapaz de generalizar predicciones sobre la población general de datos (es decir, datos que aún no ha visto).

Esto se relaciona con el problema que mencioné anteriormente, donde muchos otros tutoriales sobre la API TensorFlow de alto nivel se han quedado cortos. Es muy importante hacer pausas periódicamente durante el entrenamiento y evaluar cómo se generaliza el modelo a un conjunto de datos de evaluación o validación. Tomemos un momento para ver lo que devuelve la función evaluar (...) mirando la salida de evaluación de la primera iteración del bucle.

1
evaluations[0]
1
{'average_loss': 31.116383, 'global_step': 400, 'loss': 3111.6382}

Como puede ver, genera la pérdida promedio (Error cuadrático medio) y la pérdida total (Suma de errores cuadráticos) para el paso de entrenamiento, que para este es el paso 400. Lo que normalmente verá en una red altamente capacitada es una tendencia en la que tanto las pérdidas de capacitación como las de evaluación disminuyen más o menos constantemente en paralelo. Sin embargo, en un modelo sobreajustado en algún momento, en realidad en el punto donde comienza a ocurrir el sobreajuste, el conjunto de entrenamiento de validación dejará de ver reducciones en la salida de su método evaluate(...). Aquí es donde desea detener la capacitación adicional del modelo, preferiblemente justo antes de que ocurra ese cambio.

Ahora que tenemos una colección de evaluaciones para cada una de las iteraciones, representémoslas como una función de los pasos de entrenamiento para asegurarnos de que no hemos sobreentrenado nuestro modelo. Para hacerlo, usaré un diagrama de dispersión simple del módulo pyplot de matplotlib.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import matplotlib.pyplot as plt
%matplotlib inline

# manually set the parameters of the figure to and appropriate size
plt.rcParams['figure.figsize'] = [14, 10]

loss_values = [ev['loss'] for ev in evaluations]
training_steps = [ev['global_step'] for ev in evaluations]

plt.scatter(x=training_steps, y=loss_values)
plt.xlabel('Training steps (Epochs = steps / 2)')
plt.ylabel('Loss (SSE)')
plt.show()

Training Loss (SSE){.img-responsive}

¡Enfriar! Según el gráfico anterior, parece que después de todas esas iteraciones no he sobreajustado el modelo porque las pérdidas de evaluación nunca muestran un cambio significativo en la dirección hacia un valor creciente. Ahora puedo pasar con seguridad a hacer predicciones basadas en mi conjunto de datos de prueba restante y evaluar cómo el modelo predice las temperaturas climáticas medias.

Similar a los otros dos métodos de regresor que he demostrado, el método predict(...) requiere un input_fn que pasaré usando el wx_input_fn(...) reutilizable, entregándole el conjunto de datos de prueba, especificando el num_epochs para ser uno y shuffle para ser falso para que esté alimentando secuencialmente todos los datos para probar.

A continuación, doy un poco de formato a la iteración de los dictados que se devuelven desde el método predict(...) para tener una gran variedad de predicciones. Luego uso la matriz de predicciones con los métodos de sklearn explained_variance_score(...), mean_absolute_error(...) y median_absolute_error(...) para medir qué tan bien les fue a las predicciones en relación con lo conocido. objetivos y_test. Esto le dice al desarrollador cuáles son las capacidades predictivas del modelo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pred = regressor.predict(input_fn=wx_input_fn(X_test,
                                              num_epochs=1,
                                              shuffle=False))
predictions = np.array([p['predictions'][0] for p in pred])

print("The Explained Variance: %.2f" % explained_variance_score(
                                            y_test, predictions))  
print("The Mean Absolute Error: %.2f degrees Celcius" % mean_absolute_error(
                                            y_test, predictions))  
print("The Median Absolute Error: %.2f degrees Celcius" % median_absolute_error(
                                            y_test, predictions))
1
2
3
4
INFO:tensorflow:Restoring parameters from tf_wx_model/model.ckpt-40000
The Explained Variance: 0.88
The Mean Absolute Error: 3.11 degrees Celcius
The Median Absolute Error: 2.51 degrees Celcius

He usado las mismas métricas que en el artículo anterior sobre la técnica de regresión lineal para que no solo podamos evaluar este modelo, sino que también podamos compararlos. Como puede ver, los dos modelos funcionaron de manera bastante similar, siendo ligeramente mejor el modelo de regresión lineal más simple. Sin embargo, un practicante astuto sin duda realizaría varios experimentos variando los hiperparámetros (velocidad de aprendizaje, ancho y profundidad) de esta red neuronal para ajustarla un poco, pero en general esto probablemente se acerque bastante al modelo óptimo.

Esto trae a colación un punto que vale la pena mencionar, rara vez es el caso, y definitivamente no es recomendable, confiar simplemente en un modelo o el tema candente más reciente en la comunidad de aprendizaje automático. No hay dos conjuntos de datos idénticos y ningún modelo es el rey. La única forma de determinar el mejor modelo es probarlos. Luego, una vez que haya identificado el mejor modelo, hay otras compensaciones a tener en cuenta, como la interpretabilidad.

Recursos

¿Quiere aprender las herramientas, las técnicas de aprendizaje automático y el análisis de datos utilizados en este tutorial? Aquí hay algunos recursos excelentes para comenzar:

Conclusión

Este artículo ha demostrado cómo usar la API de alto nivel de TensorFlow para la subclase de Estimator preestablecida DNNRegressor. A lo largo del camino he descrito, en un sentido general, la teoría de las redes neuronales, cómo se entrenan y la importancia de ser consciente de los peligros de sobreajustar un modelo en el proceso.

Para demostrar este proceso de creación de redes neuronales, he creado un modelo que es capaz de predecir la temperatura media del día siguiente en función de las características numéricas recopiladas en el primer artículo de esta serie. Dicho esto, me gustaría tomarme un momento para aclarar mis intenciones para esta serie. Mi objetivo principal no ha sido construir modelos de pronóstico de última generación en el artículo de Regresión lineal o el actual sobre redes neuronales, pero mis objetivos han sido lograr lo siguiente:

  1. Demostrar el proceso general para emprender un proyecto de análisis (aprendizaje automático, ciencia de datos, lo que sea...) desde la recopilación de datos, el procesamiento de datos, el análisis exploratorio de datos, la selección de modelos, la construcción de modelos y la evaluación de modelos.
  2. Demostrar cómo seleccionar características significativas que no violen los supuestos clave de la técnica de regresión lineal utilizando dos bibliotecas populares de Python, StatsModels y Scikit Learn.
  3. Demostrar cómo usar la API de TensorFlow de alto nivel y brindar cierta intuición sobre lo que sucede bajo todas esas capas de abstracción.
  4. Discuta los problemas asociados con el ajuste excesivo de un modelo.
  5. Explique la importancia de experimentar con más de un tipo de modelo para resolver mejor un problema.

Gracias por leer. Espero que hayan disfrutado esta serie tanto como yo y, como siempre, agradezco comentarios y críticas. ios y críticas.