Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/tutorials/structured_data/time_series.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Predicción de series temporales

Este tutorial es una introducción a la predicción de series temporales usando TensorFlow. Construye algunos estilos diferentes de modelos, incluyendo redes neuronales convolucionales y recurrentes (CNNs y RNNs).

Se trata en dos partes principales, con subsecciones:

  • Predicción para un único paso de tiempo:

    • Una sola característica.

    • Todas las características.

  • Predicción de múltiples pasos:

    • De una sola vez: Realice todas las predicciones a la vez.

    • Autorregresivo: Realiza una predicción cada vez y retroalimenta el modelo con el resultado.

Preparación

import os import datetime import IPython import IPython.display import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns import tensorflow as tf mpl.rcParams['figure.figsize'] = (8, 6) mpl.rcParams['axes.grid'] = False

El conjunto de datos meteorológicos

Este tutorial utiliza un conjunto de datos de series temporales meteorológicas registrados por el Instituto Max Planck de Biogeoquímica.

Este conjunto de datos contiene 14 características diferentes, como la temperatura del aire, la presión atmosférica y la humedad. Se recopilaron cada 10 minutos, a partir de 2003. Para mayor eficacia, sólo se usarán los datos recopilados entre 2009 y 2016. Esta sección del conjunto de datos fue preparada por François Chollet para su libro Aprendizaje Profundo con Python.

zip_path = tf.keras.utils.get_file( origin='https://storage.googleapis.com/tensorflow/tf-keras-datasets/jena_climate_2009_2016.csv.zip', fname='jena_climate_2009_2016.csv.zip', extract=True) csv_path, _ = os.path.splitext(zip_path)

Este tutorial sólo se ocupará de las predicciones horarias, así que empiece por submuestrear los datos de intervalos de 10 minutos a intervalos de una hora:

df = pd.read_csv(csv_path) # Slice [start:stop:step], starting from index 5 take every 6th record. df = df[5::6] date_time = pd.to_datetime(df.pop('Date Time'), format='%d.%m.%Y %H:%M:%S')

Miremos los datos. Aquí están las primeras filas:

df.head()

He aquí la evolución de algunas características a lo largo del tiempo:

plot_cols = ['T (degC)', 'p (mbar)', 'rho (g/m**3)'] plot_features = df[plot_cols] plot_features.index = date_time _ = plot_features.plot(subplots=True) plot_features = df[plot_cols][:480] plot_features.index = date_time[:480] _ = plot_features.plot(subplots=True)

Inspeccionar y despejar

Luego, observe las estadísticas del conjunto de datos:

df.describe().transpose()

Velocidad del viento

Algo que debería destacar son las columnas min del valor de la velocidad del viento (wv (m/s)) y el valor máximo (max. wv (m/s)). Este -9999 es probablemente erróneo.

Existe por separado una columna de dirección del viento, por lo que la velocidad debería ser mayor que cero (>=0). Reemplácela con ceros:

wv = df['wv (m/s)'] bad_wv = wv == -9999.0 wv[bad_wv] = 0.0 max_wv = df['max. wv (m/s)'] bad_max_wv = max_wv == -9999.0 max_wv[bad_max_wv] = 0.0 # The above inplace edits are reflected in the DataFrame. df['wv (m/s)'].min()

Ingeniería de características

Antes de meternos de lleno en la construcción de un modelo, debemos entender nuestros datos y asegurarnos de que le estamos pasando al modelo datos con el formato adecuado.

Viento

La última columna de los datos, wd (deg), da la dirección del viento en grados. Los ángulos no son un buen tipo de dato para el modelo: 360° y 0° deberían ser próximos entre sí y poder transicionar entre sí sin problemas. La dirección no debería importar si el viento no sopla.

En estos momentos, la distribución de los datos sobre el viento tiene este aspecto:

plt.hist2d(df['wd (deg)'], df['wv (m/s)'], bins=(50, 50), vmax=400) plt.colorbar() plt.xlabel('Wind Direction [deg]') plt.ylabel('Wind Velocity [m/s]')

Pero esto será más fácil de interpretar para el modelo si convierte las columnas de dirección y velocidad del viento en un vector de viento:

wv = df.pop('wv (m/s)') max_wv = df.pop('max. wv (m/s)') # Convert to radians. wd_rad = df.pop('wd (deg)')*np.pi / 180 # Calculate the wind x and y components. df['Wx'] = wv*np.cos(wd_rad) df['Wy'] = wv*np.sin(wd_rad) # Calculate the max wind x and y components. df['max Wx'] = max_wv*np.cos(wd_rad) df['max Wy'] = max_wv*np.sin(wd_rad)

La distribución de los vectores de viento es mucho más sencilla de interpretar correctamente por el modelo:

plt.hist2d(df['Wx'], df['Wy'], bins=(50, 50), vmax=400) plt.colorbar() plt.xlabel('Wind X [m/s]') plt.ylabel('Wind Y [m/s]') ax = plt.gca() ax.axis('tight')

Hora

Del mismo modo, la columna Date Time es muy útil, pero no en esta forma de cadena. Empiece por convertirla a segundos:

timestamp_s = date_time.map(pd.Timestamp.timestamp)

Al igual que la dirección del viento, la hora en segundos no es un dato de entrada útil para el modelo. Al ser datos meteorológicos, tiene una clara periodicidad diaria y anual. Hay muchas formas de tratar la periodicidad.

Puede obtener señales utilizables al usar transformadas de seno y coseno para despejar las señales de "Hora del día" y "Hora del año":

day = 24*60*60 year = (365.2425)*day df['Day sin'] = np.sin(timestamp_s * (2 * np.pi / day)) df['Day cos'] = np.cos(timestamp_s * (2 * np.pi / day)) df['Year sin'] = np.sin(timestamp_s * (2 * np.pi / year)) df['Year cos'] = np.cos(timestamp_s * (2 * np.pi / year))
plt.plot(np.array(df['Day sin'])[:25]) plt.plot(np.array(df['Day cos'])[:25]) plt.xlabel('Time [h]') plt.title('Time of day signal')

Esto permite al modelo acceder a las frecuencias más importantes. En este caso, usted sabía de antemano qué frecuencias eran importantes.

Si no dispone de esa información, puede determinar qué frecuencias son importantes extrayendo las características con Transformada rápida de Fourier. Para comprobar las premisas, aquí tiene el tf.signal.rfft de la temperatura a lo largo del tiempo. Observe los picos obvios en las frecuencias cercanas a 1/año y 1/día:

fft = tf.signal.rfft(df['T (degC)']) f_per_dataset = np.arange(0, len(fft)) n_samples_h = len(df['T (degC)']) hours_per_year = 24*365.2524 years_per_dataset = n_samples_h/(hours_per_year) f_per_year = f_per_dataset/years_per_dataset plt.step(f_per_year, np.abs(fft)) plt.xscale('log') plt.ylim(0, 400000) plt.xlim([0.1, max(plt.xlim())]) plt.xticks([1, 365.2524], labels=['1/Year', '1/day']) _ = plt.xlabel('Frequency (log scale)')

Dividir los datos

Usará una subdivisión (70%, 20%, 10%) para los conjuntos de entrenamiento, validación y prueba. Observe que los datos no se barajan aleatoriamente antes de la subdivisión. Esto es por dos motivos:

  1. Garantiza que el troceado de los datos en ventanas de muestreos consecutivos siga siendo posible.

  2. Garantiza que los resultados de la validación/prueba sean más realistas, al evaluarse sobre los datos recopilados después del entrenamiento del modelo.

column_indices = {name: i for i, name in enumerate(df.columns)} n = len(df) train_df = df[0:int(n*0.7)] val_df = df[int(n*0.7):int(n*0.9)] test_df = df[int(n*0.9):] num_features = df.shape[1]

Normalizar los datos

Es importante escalar las características antes de entrenar una red neuronal. La normalización es una forma habitual de realizar este escalado: resta la media y divide por las desviaciones estándares de cada característica.

La media y la desviación estándar sólo deben calcularse usando los datos de entrenamiento para que los modelos no tengan acceso a los valores de los conjuntos de validación y prueba.

También puede argumentarse que el modelo no debería tener acceso a los valores futuros del conjunto de entrenamiento cuando se está entrenando, y que esta normalización debería usarse usando promedios móviles. Ese no es el enfoque de este tutorial, y los conjuntos de validación y prueba garantizan que se obtengan métricas (algo) honestas. Así que, en aras de la simplicidad, este tutorial usa un promedio simple.

train_mean = train_df.mean() train_std = train_df.std() train_df = (train_df - train_mean) / train_std val_df = (val_df - train_mean) / train_std test_df = (test_df - train_mean) / train_std

Ahora, observe la distribución de las características. Algunas características tienen colas largas, pero no hay errores obvios como el valor -9999 de la velocidad del viento.

df_std = (df - train_mean) / train_std df_std = df_std.melt(var_name='Column', value_name='Normalized') plt.figure(figsize=(12, 6)) ax = sns.violinplot(x='Column', y='Normalized', data=df_std) _ = ax.set_xticklabels(df.keys(), rotation=90)

Generar ventanas de datos

Los modelos de este tutorial configurarán un conjunto de predicciones basadas en una ventana de muestreos consecutivos de los datos.

Las principales características de las ventanas de entrada son:

  • La anchura (número de pasos de tiempo) de las ventanas de entrada y de etiqueta.

  • La compensación horaria entre ellos.

  • Qué características se usan como entradas, etiquetas o ambas.

Este tutorial construye una variedad de modelos (incluyendo modelos lineales, DNN, CNN y RNN), y los usa en ambos casos:

  • Predicciones de salida única, y de salida múltiple.

  • Predicciones de un solo paso y un solo tiempo y paso de varios tiempos.

Esta sección se centra en implementar la ventana de datos para que pueda reutilizarse en todos esos modelos.

Según la tarea y el tipo de modelo, es posible que desee generar una variedad de ventanas de datos. He aquí algunos ejemplos:

  1. Por ejemplo, para hacer una única predicción 24 horas en el futuro, dadas 24 horas de historia, podría definir una ventana como ésta:

Una predicción 24 horas en el futuro.

  1. Un modelo que haga una predicción una hora en el futuro, dadas seis horas de historia, necesitaría una ventana como ésta:

Una predicción una hora en el futuro.

El resto de esta sección define una clase WindowGenerator. Esta clase puede:

  1. Maneje los índices y las compensaciones como se muestra en los diagramas anteriores.

  2. Divida las ventanas de características en pares (características, etiquetas).

  3. Trace el contenido de las ventanas resultantes.

  4. Genere eficazmente lotes de estas ventanas a partir de los datos de entrenamiento, evaluación y prueba, usando tf.data.Datasets.

1. Índices y compensaciones

Comience creando la clase WindowGenerator. El método __init__ incluye toda la lógica necesaria para los índices de entrada y etiqueta.

También toma como entrada los DataFrames de entrenamiento, evaluación y prueba. Estos se convertirán en tf.data.Datasets de ventanas más tarde.

class WindowGenerator(): def __init__(self, input_width, label_width, shift, train_df=train_df, val_df=val_df, test_df=test_df, label_columns=None): # Store the raw data. self.train_df = train_df self.val_df = val_df self.test_df = test_df # Work out the label column indices. self.label_columns = label_columns if label_columns is not None: self.label_columns_indices = {name: i for i, name in enumerate(label_columns)} self.column_indices = {name: i for i, name in enumerate(train_df.columns)} # Work out the window parameters. self.input_width = input_width self.label_width = label_width self.shift = shift self.total_window_size = input_width + shift self.input_slice = slice(0, input_width) self.input_indices = np.arange(self.total_window_size)[self.input_slice] self.label_start = self.total_window_size - self.label_width self.labels_slice = slice(self.label_start, None) self.label_indices = np.arange(self.total_window_size)[self.labels_slice] def __repr__(self): return '\n'.join([ f'Total window size: {self.total_window_size}', f'Input indices: {self.input_indices}', f'Label indices: {self.label_indices}', f'Label column name(s): {self.label_columns}'])

Aquí está el código para crear las 2 ventanas mostradas en los diagramas al principio de esta sección:

w1 = WindowGenerator(input_width=24, label_width=1, shift=24, label_columns=['T (degC)']) w1
w2 = WindowGenerator(input_width=6, label_width=1, shift=1, label_columns=['T (degC)']) w2

2. División

Dada una lista de entradas consecutivas, el método split_window las convertirá en una ventana de entradas y una ventana de etiquetas.

El ejemplo w2 que definió anteriormente se dividirá así:

La ventana inicial son todos los muestreos consecutivos, esto la divide en pares (entradas, etiquetas)

Este diagrama no muestra el eje features de los datos, pero esta función split_window también gestiona las label_columns, por lo que puede usarse tanto para los ejemplos de salida única como para los de salida múltiple.

def split_window(self, features): inputs = features[:, self.input_slice, :] labels = features[:, self.labels_slice, :] if self.label_columns is not None: labels = tf.stack( [labels[:, :, self.column_indices[name]] for name in self.label_columns], axis=-1) # Slicing doesn't preserve static shape information, so set the shapes # manually. This way the `tf.data.Datasets` are easier to inspect. inputs.set_shape([None, self.input_width, None]) labels.set_shape([None, self.label_width, None]) return inputs, labels WindowGenerator.split_window = split_window

Haga la prueba:

# Stack three slices, the length of the total window. example_window = tf.stack([np.array(train_df[:w2.total_window_size]), np.array(train_df[100:100+w2.total_window_size]), np.array(train_df[200:200+w2.total_window_size])]) example_inputs, example_labels = w2.split_window(example_window) print('All shapes are: (batch, time, features)') print(f'Window shape: {example_window.shape}') print(f'Inputs shape: {example_inputs.shape}') print(f'Labels shape: {example_labels.shape}')

Típicamente, los datos en TensorFlow se empaquetan en arreglos donde el índice más externo recorre los ejemplos (la dimensión "lote"). Los índices intermedios son la(s) dimensión(es) "tiempo" o "espacio" (anchura, altura). Los índices más internos son las características.

El código anterior toma un lote de tres ventanas de 7 pasos de tiempo con 19 características en cada uno. Las divide en un lote de entradas de 6 pasos de tiempo con 19 características y una etiqueta de 1 paso de tiempo con 1 característica. La etiqueta sólo tiene una característica porque el WindowGenerator se inicializó con label_columns=['T (degC)']. Inicialmente, este tutorial construirá modelos que predicen etiquetas de salida únicas.

3. Graficar

He aquí un método de graficado que permite una visualización sencilla de la ventana dividida:

w2.example = example_inputs, example_labels
def plot(self, model=None, plot_col='T (degC)', max_subplots=3): inputs, labels = self.example plt.figure(figsize=(12, 8)) plot_col_index = self.column_indices[plot_col] max_n = min(max_subplots, len(inputs)) for n in range(max_n): plt.subplot(max_n, 1, n+1) plt.ylabel(f'{plot_col} [normed]') plt.plot(self.input_indices, inputs[n, :, plot_col_index], label='Inputs', marker='.', zorder=-10) if self.label_columns: label_col_index = self.label_columns_indices.get(plot_col, None) else: label_col_index = plot_col_index if label_col_index is None: continue plt.scatter(self.label_indices, labels[n, :, label_col_index], edgecolors='k', label='Labels', c='#2ca02c', s=64) if model is not None: predictions = model(inputs) plt.scatter(self.label_indices, predictions[n, :, label_col_index], marker='X', edgecolors='k', label='Predictions', c='#ff7f0e', s=64) if n == 0: plt.legend() plt.xlabel('Time [h]') WindowGenerator.plot = plot

Este gráfico alinea las entradas, las etiquetas y las predicciones (posteriores) en función del tiempo al que se refiere el artículo:

w2.plot()

Puede graficar las otras columnas, pero la configuración de la ventana de ejemplo w2 sólo tiene etiquetas para la columna T (degC).

w2.plot(plot_col='p (mbar)')

4. Crear tf.data.Datasets

Por último, este método make_dataset tomará un DataFrame de series temporales y lo convertirá en un tf.data.Dataset de pares (input_window, label_window) utilizando la función tf.keras.utils.timeseries_dataset_from_array:

def make_dataset(self, data): data = np.array(data, dtype=np.float32) ds = tf.keras.utils.timeseries_dataset_from_array( data=data, targets=None, sequence_length=self.total_window_size, sequence_stride=1, shuffle=True, batch_size=32,) ds = ds.map(self.split_window) return ds WindowGenerator.make_dataset = make_dataset

El objeto WindowGenerator contiene los datos de entrenamiento, validación y prueba.

Añada propiedades para accesarlas como tf.data.Datasets usando el método make_dataset que definió anteriormente. Añada también un lote de ejemplo estándar para facilitar el acceso y la graficación:

@property def train(self): return self.make_dataset(self.train_df) @property def val(self): return self.make_dataset(self.val_df) @property def test(self): return self.make_dataset(self.test_df) @property def example(self): """Get and cache an example batch of `inputs, labels` for plotting.""" result = getattr(self, '_example', None) if result is None: # No example batch was found, so get one from the `.train` dataset result = next(iter(self.train)) # And cache it for next time self._example = result return result WindowGenerator.train = train WindowGenerator.val = val WindowGenerator.test = test WindowGenerator.example = example

Ahora, el objeto WindowGenerator le da acceso a los objetos tf.data.Dataset, por lo que puede iterar fácilmente sobre los datos.

La propiedad Dataset.element_spec le indica la estructura, los tipos de datos y las formas de los elementos del conjunto de datos.

# Each element is an (inputs, label) pair. w2.train.element_spec

Al iterar sobre un Dataset se obtienen lotes concretos:

for example_inputs, example_labels in w2.train.take(1): print(f'Inputs shape (batch, time, features): {example_inputs.shape}') print(f'Labels shape (batch, time, features): {example_labels.shape}')

Modelos de un solo paso

El modelo más sencillo que se puede construir con este tipo de datos es el que predice el valor de una sola característica en 1 paso de tiempo (una hora) en el futuro basándose únicamente en las condiciones actuales.

Así pues, empiece construyendo modelos para predecir el valor de T (degC) una hora en el futuro.

Predecir el siguiente paso de tiempo

Configure un objeto WindowGenerator para producir estos pares (input,label) en un solo paso:

single_step_window = WindowGenerator( input_width=1, label_width=1, shift=1, label_columns=['T (degC)']) single_step_window

El objeto window crea tf.data.Datasets a partir de los conjuntos de entrenamiento, validación y prueba, lo que le permite iterar fácilmente sobre lotes de datos.

for example_inputs, example_labels in single_step_window.train.take(1): print(f'Inputs shape (batch, time, features): {example_inputs.shape}') print(f'Labels shape (batch, time, features): {example_labels.shape}')

Linea de referencia

Antes de construir un modelo entrenable sería bueno tener una línea de referencia de rendimiento como punto de comparación con los modelos posteriores más complicados.

Esta primera tarea consiste en predecir la temperatura una hora en el futuro, dado el valor actual de todas las características. Los valores actuales incluyen la temperatura actual.

Así pues, empiece con un modelo que sólo devuelva la temperatura actual como predicción, pronosticando "Sin cambios". Se trata de una línea de referencia razonable, ya que la temperatura cambia lentamente. Por supuesto, esta línea de referencia funcionará peor si hace una predicción más lejana en el futuro.

Enviar la entrada a la salida

class Baseline(tf.keras.Model): def __init__(self, label_index=None): super().__init__() self.label_index = label_index def call(self, inputs): if self.label_index is None: return inputs result = inputs[:, :, self.label_index] return result[:, :, tf.newaxis]

Instanciar y evaluar este modelo:

baseline = Baseline(label_index=column_indices['T (degC)']) baseline.compile(loss=tf.keras.losses.MeanSquaredError(), metrics=[tf.keras.metrics.MeanAbsoluteError()]) val_performance = {} performance = {} val_performance['Baseline'] = baseline.evaluate(single_step_window.val) performance['Baseline'] = baseline.evaluate(single_step_window.test, verbose=0)

Eso imprimió algunas métricas de rendimiento, pero no le dan una idea de lo bien que va el modelo.

El WindowGenerator tiene un método de graficación, pero los gráficos no serán muy interesantes con un solo muestreo.

Por tanto, cree un WindowGenerator más amplio que genere ventanas de 24 horas de entradas y etiquetas consecutivas a la vez. La nueva variable wide_window no cambia el funcionamiento del modelo. El modelo sigue haciendo predicciones una hora en el futuro basándose en un único paso de tiempo de entrada. Aquí, el eje time actúa como el eje batch: cada predicción se realiza de forma independiente sin interacción entre los pasos de tiempo:

wide_window = WindowGenerator( input_width=24, label_width=24, shift=1, label_columns=['T (degC)']) wide_window

Esta ventana ampliada puede pasarse directamente al mismo modelo baseline sin ningún cambio de código. Esto es posible porque las entradas y las etiquetas tienen el mismo número de pasos de tiempo, y la línea de referencia sólo reenvía la entrada a la salida:

Una predicción 1 hora en el futuro, cada hora.

print('Input shape:', wide_window.example[0].shape) print('Output shape:', baseline(wide_window.example[0]).shape)

Al graficar las predicciones del modelo base, observe que se trata simplemente de las etiquetas desplazadas una hora hacia la derecha:

wide_window.plot(baseline)

En los gráficos anteriores de tres ejemplos, el modelo de un solo paso se ejecuta en el transcurso de 24 horas. Esto merece una explicación:

  • La línea azul Inputs muestra la temperatura de entrada en cada paso de tiempo. El modelo recibe todas las características, este gráfico sólo muestra la temperatura.

  • Los puntos verdes Labels muestran el valor de predicción objetivo. Estos puntos se muestran en el momento de la predicción, no en el momento de la entrada. Por eso el rango de etiquetas está desplazado 1 paso con respecto a las entradas.

  • Las cruces naranjas Predictions son las predicciones del modelo para cada paso de tiempo de salida. Si el modelo predijera a la perfección, las predicciones caerían directamente sobre las Labels.

Modelo lineal

El modelo entrenable más sencillo que puede aplicar a esta tarea es insertar una transformación lineal entre la entrada y la salida. En este caso, la salida de un paso temporal sólo depende de ese paso:

Una predicción en un solo paso

Una capa tf.keras.layers.Dense sin un conjunto de activation es un modelo lineal. La capa sólo transforma el último eje de los datos de (batch, time, inputs) a (batch, time, units); se aplica independientemente a cada artículo a través de los ejes batch y time.

linear = tf.keras.Sequential([ tf.keras.layers.Dense(units=1) ])
print('Input shape:', single_step_window.example[0].shape) print('Output shape:', linear(single_step_window.example[0]).shape)

Este tutorial entrena muchos modelos, así que empaquete el procedimiento de entrenamiento en una función:

MAX_EPOCHS = 20 def compile_and_fit(model, window, patience=2): early_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=patience, mode='min') model.compile(loss=tf.keras.losses.MeanSquaredError(), optimizer=tf.keras.optimizers.Adam(), metrics=[tf.keras.metrics.MeanAbsoluteError()]) history = model.fit(window.train, epochs=MAX_EPOCHS, validation_data=window.val, callbacks=[early_stopping]) return history

Entrene el modelo y evalúe su rendimiento:

history = compile_and_fit(linear, single_step_window) val_performance['Linear'] = linear.evaluate(single_step_window.val) performance['Linear'] = linear.evaluate(single_step_window.test, verbose=0)

Al igual que el modelo baseline, el modelo lineal puede invocarse en lotes de ventanas amplias. Usado de esta manera, el modelo configura un conjunto de predicciones independientes en pasos de tiempo consecutivos. El eje time actúa como otro eje batch. No hay interacciones entre las predicciones en cada paso de tiempo.

Una predicción de un solo paso

print('Input shape:', wide_window.example[0].shape) print('Output shape:', linear(wide_window.example[0]).shape)

Este es el gráfico de sus predicciones de ejemplo en la wide_window, observe cómo en muchos casos la predicción es claramente mejor que devolver simplemente la temperatura de entrada, pero en unos pocos casos es peor:

wide_window.plot(linear)

Una ventaja de los modelos lineales es que son relativamente sencillos de interpretar. Puede extraer las ponderaciones de las capas y visualizar la ponderación asignada a cada entrada:

plt.bar(x = range(len(train_df.columns)), height=linear.layers[0].kernel[:,0].numpy()) axis = plt.gca() axis.set_xticks(range(len(train_df.columns))) _ = axis.set_xticklabels(train_df.columns, rotation=90)

A veces, el modelo ni siquiera pone la mayor ponderación en la entrada T (degC). Este es uno de los riesgos de la inicialización aleatoria.

Dense

Antes de aplicar modelos que operen realmente en múltiples pasos de tiempo, vale la pena comprobar el rendimiento de modelos más profundos y potentes de un solo paso de entrada.

Éste es un modelo similar al linear, salvo que apila varias capas Dense entre la entrada y la salida:

dense = tf.keras.Sequential([ tf.keras.layers.Dense(units=64, activation='relu'), tf.keras.layers.Dense(units=64, activation='relu'), tf.keras.layers.Dense(units=1) ]) history = compile_and_fit(dense, single_step_window) val_performance['Dense'] = dense.evaluate(single_step_window.val) performance['Dense'] = dense.evaluate(single_step_window.test, verbose=0)

Dense multipaso

Un modelo de un solo paso de tiempo no tiene contexto para los valores actuales de sus entradas. No puede ver cómo cambian las características de entrada a lo largo del tiempo. Para resolver este problema, el modelo necesita tener acceso a múltiples pasos de tiempo a la hora de hacer predicciones:

Para cada predicción se usan tres pasos de tiempo.

Los modelos baseline, linear y dense tratan cada paso de tiempo de forma independiente. Aquí el modelo tomará múltiples pasos de tiempo como entrada para producir una única salida.

Cree un WindowGenerator que producirá lotes de entradas de tres horas y etiquetas de una hora:

Fíjese en que el parámetro Window de shift es relativo al final de las dos ventanas.

CONV_WIDTH = 3 conv_window = WindowGenerator( input_width=CONV_WIDTH, label_width=1, shift=1, label_columns=['T (degC)']) conv_window
conv_window.plot() plt.title("Given 3 hours of inputs, predict 1 hour into the future.")

Puede entrenar un modelo dense en una ventana de múltiples pasos de entrada añadiendo un tf.keras.layers.Flatten como primera capa del modelo:

multi_step_dense = tf.keras.Sequential([ # Shape: (time, features) => (time*features) tf.keras.layers.Flatten(), tf.keras.layers.Dense(units=32, activation='relu'), tf.keras.layers.Dense(units=32, activation='relu'), tf.keras.layers.Dense(units=1), # Add back the time dimension. # Shape: (outputs) => (1, outputs) tf.keras.layers.Reshape([1, -1]), ])
print('Input shape:', conv_window.example[0].shape) print('Output shape:', multi_step_dense(conv_window.example[0]).shape)
history = compile_and_fit(multi_step_dense, conv_window) IPython.display.clear_output() val_performance['Multi step dense'] = multi_step_dense.evaluate(conv_window.val) performance['Multi step dense'] = multi_step_dense.evaluate(conv_window.test, verbose=0)
conv_window.plot(multi_step_dense)

El principal inconveniente de este enfoque es que el modelo resultante sólo puede ejecutarse en ventanas de entrada de exactamente esta forma.

print('Input shape:', wide_window.example[0].shape) try: print('Output shape:', multi_step_dense(wide_window.example[0]).shape) except Exception as e: print(f'\n{type(e).__name__}:{e}')

Los modelos convolucionales de la siguiente sección solucionan este problema.

Red neuronal de convolución

Una capa de convolución (tf.keras.layers.Conv1D) también toma múltiples pasos de tiempo como entrada para cada predicción.

A continuación se muestra el mismo modelo que multi_step_dense, reescrito con una convolución.

Observe los cambios:

  • El tf.keras.layers.Flatten y el primer tf.keras.layers.Dense son sustituidos por un tf.keras.layers.Conv1D.

  • El tf.keras.layers.Reshape deja de ser necesario puesto que la convolución conserva el eje temporal en su salida.

conv_model = tf.keras.Sequential([ tf.keras.layers.Conv1D(filters=32, kernel_size=(CONV_WIDTH,), activation='relu'), tf.keras.layers.Dense(units=32, activation='relu'), tf.keras.layers.Dense(units=1), ])

Ejecútelo en un lote de ejemplo para verificar que el modelo produce salidas con la forma esperada:

print("Conv model on `conv_window`") print('Input shape:', conv_window.example[0].shape) print('Output shape:', conv_model(conv_window.example[0]).shape)

Entrénelo y evalúelo en el modelo conv_window y debería dar un rendimiento similar al modelo multi_step_dense.

history = compile_and_fit(conv_model, conv_window) IPython.display.clear_output() val_performance['Conv'] = conv_model.evaluate(conv_window.val) performance['Conv'] = conv_model.evaluate(conv_window.test, verbose=0)

La diferencia entre este modelo conv_model y el modelo multi_step_dense es que el modelo conv_model puede ejecutarse con entradas de cualquier longitud. La capa convolucional se aplica a una ventana deslizante de entradas:

Ejecución de un modelo convolucional en una secuencia

Si lo ejecuta en una entrada más amplia, produce una salida más amplia:

print("Wide window") print('Input shape:', wide_window.example[0].shape) print('Labels shape:', wide_window.example[1].shape) print('Output shape:', conv_model(wide_window.example[0]).shape)

Observe que la salida es más corta que la entrada. Para que el entrenamiento o la graficación funcionen, es necesario que las etiquetas y la predicción tengan la misma longitud. Así que construya un WindowGenerator para producir ventanas amplias con algunos pasos de tiempo de entrada adicionales para que las longitudes de la etiqueta y la predicción coincidan:

LABEL_WIDTH = 24 INPUT_WIDTH = LABEL_WIDTH + (CONV_WIDTH - 1) wide_conv_window = WindowGenerator( input_width=INPUT_WIDTH, label_width=LABEL_WIDTH, shift=1, label_columns=['T (degC)']) wide_conv_window
print("Wide conv window") print('Input shape:', wide_conv_window.example[0].shape) print('Labels shape:', wide_conv_window.example[1].shape) print('Output shape:', conv_model(wide_conv_window.example[0]).shape)

Ahora, puede graficar las predicciones del modelo en una ventana más amplia. Observe los 3 pasos de tiempo de entrada antes de la primera predicción. Cada predicción aquí se basa en los 3 pasos de tiempo precedentes:

wide_conv_window.plot(conv_model)

Red neuronal recurrente

Una red neuronal recurrente (RNN) es un tipo de red neuronal muy adecuada para los datos de series temporales. Las RNN procesan una serie temporal paso a paso, manteniendo un estado interno de paso de tiempo en paso de tiempo.

Puede obtener más información en el tutorial Generación de texto con una RNN y en la guía Redes neuronales recurrentes (RNN) con Keras.

En este tutorial, usará una capa RNN llamada Memoria a corto-largo plazo (tf.keras.layers.LSTM).

Un argumento importante del constructor para todas las capas RNN de Keras, como tf.keras.layers.LSTM, es el argumento return_sequences. Este parámetro puede configurar la capa de dos maneras:

  1. Si es False, el predeterminado, la capa sólo devuelve la salida del último paso de tiempo, dando tiempo al modelo para calibrar su estado interno antes de hacer una sola predicción:

Un LSTM calibrándose y haciendo una única predicción

  1. Si es True, la capa devuelve una salida para cada entrada. Esto es útil para:

    • Apilar capas RNN.

    • Entrenar un modelo en múltiples pasos de tiempo simultáneamente.

Un LSTM haciendo una predicción después de cada paso de tiempo

lstm_model = tf.keras.models.Sequential([ # Shape [batch, time, features] => [batch, time, lstm_units] tf.keras.layers.LSTM(32, return_sequences=True), # Shape => [batch, time, features] tf.keras.layers.Dense(units=1) ])

Con return_sequences=True, el modelo puede entrenarse con 24 horas de datos cada vez.

Nota: Esto dará una visión pesimista del rendimiento del modelo. En el primer paso de tiempo, el modelo no tiene acceso a los pasos anteriores y, por lo tanto, no puede hacerlo mejor que los modelos simples linear y dense mostrados anteriormente.

print('Input shape:', wide_window.example[0].shape) print('Output shape:', lstm_model(wide_window.example[0]).shape)
history = compile_and_fit(lstm_model, wide_window) IPython.display.clear_output() val_performance['LSTM'] = lstm_model.evaluate(wide_window.val) performance['LSTM'] = lstm_model.evaluate(wide_window.test, verbose=0)
wide_window.plot(lstm_model)

Rendimiento

Con este conjunto de datos, normalmente cada uno de los modelos obtiene resultados ligeramente mejores que el anterior:

x = np.arange(len(performance)) width = 0.3 metric_name = 'mean_absolute_error' metric_index = lstm_model.metrics_names.index('mean_absolute_error') val_mae = [v[metric_index] for v in val_performance.values()] test_mae = [v[metric_index] for v in performance.values()] plt.ylabel('mean_absolute_error [T (degC), normalized]') plt.bar(x - 0.17, val_mae, width, label='Validation') plt.bar(x + 0.17, test_mae, width, label='Test') plt.xticks(ticks=x, labels=performance.keys(), rotation=45) _ = plt.legend()
for name, value in performance.items(): print(f'{name:12s}: {value[1]:0.4f}')

Modelos multisalida

Hasta aquí, todos los modelos predijeron una única característica de salida, T (degC), para un único paso de tiempo.

Todos estos modelos pueden convertirse para predecir múltiples características simplemente cambiando el número de unidades en la capa de salida y ajustando las ventanas de entrenamiento para incluir todas las características en el labels (example_labels):

single_step_window = WindowGenerator( # `WindowGenerator` returns all features as labels if you # don't set the `label_columns` argument. input_width=1, label_width=1, shift=1) wide_window = WindowGenerator( input_width=24, label_width=24, shift=1) for example_inputs, example_labels in wide_window.train.take(1): print(f'Inputs shape (batch, time, features): {example_inputs.shape}') print(f'Labels shape (batch, time, features): {example_labels.shape}')

Observe arriba que el eje features de las etiquetas tiene ahora la misma profundidad que las entradas, en lugar de 1.

Linea de referencia

Aquí se puede usar el mismo modelo de línea de referencia (Baseline), pero esta vez repitiendo todas las características en lugar de seleccionar una label_index específica:

baseline = Baseline() baseline.compile(loss=tf.keras.losses.MeanSquaredError(), metrics=[tf.keras.metrics.MeanAbsoluteError()])
val_performance = {} performance = {} val_performance['Baseline'] = baseline.evaluate(wide_window.val) performance['Baseline'] = baseline.evaluate(wide_window.test, verbose=0)

Dense

dense = tf.keras.Sequential([ tf.keras.layers.Dense(units=64, activation='relu'), tf.keras.layers.Dense(units=64, activation='relu'), tf.keras.layers.Dense(units=num_features) ])
history = compile_and_fit(dense, single_step_window) IPython.display.clear_output() val_performance['Dense'] = dense.evaluate(single_step_window.val) performance['Dense'] = dense.evaluate(single_step_window.test, verbose=0)

RNN

%%time wide_window = WindowGenerator( input_width=24, label_width=24, shift=1) lstm_model = tf.keras.models.Sequential([ # Shape [batch, time, features] => [batch, time, lstm_units] tf.keras.layers.LSTM(32, return_sequences=True), # Shape => [batch, time, features] tf.keras.layers.Dense(units=num_features) ]) history = compile_and_fit(lstm_model, wide_window) IPython.display.clear_output() val_performance['LSTM'] = lstm_model.evaluate( wide_window.val) performance['LSTM'] = lstm_model.evaluate( wide_window.test, verbose=0) print()

Advanced: Residual connections

El modelo Baseline utilizado anteriormente se aprovechó del hecho de que la secuencia no cambia drásticamente de un paso de tiempo a otro. Cada modelo entrenado en este tutorial hasta ahora se inicializó aleatoriamente, y luego tuvo que aprender que la salida es un pequeño cambio desde el paso de tiempo anterior.

Aunque puede evitar este problema con una inicialización cuidadosa, es más sencillo incorporarlo a la estructura del modelo.

En el análisis de series temporales es habitual construir modelos que, en lugar de predecir el valor siguiente, predicen cómo cambiará el valor en el siguiente paso de tiempo. Del mismo modo, las redes residuales (o ResNets) en el aprendizaje profundo se refieren a arquitecturas en las que cada capa contribuye al resultado acumulativo del modelo.

Así es como se aprovecha el conocimiento de que el cambio debiera ser pequeño.

Un modelo con una conexión residual

Esencialmente, esto inicializa el modelo para que coincida con la Baseline. Para esta tarea, ayuda a que los modelos converjan más rápido, con un rendimiento ligeramente mejor.

Puede usar este enfoque junto con cualquier modelo analizado en este tutorial.

Aquí, se está aplicando al modelo LSTM; nótese el uso de los tf.initializers.zeros para asegurar que los cambios predichos iniciales son pequeños, y no saturan la conexión residual. Aquí no hay problemas de ruptura de simetría para los gradientes, ya que los zeros sólo se usan en la última capa.

class ResidualWrapper(tf.keras.Model): def __init__(self, model): super().__init__() self.model = model def call(self, inputs, *args, **kwargs): delta = self.model(inputs, *args, **kwargs) # The prediction for each time step is the input # from the previous time step plus the delta # calculated by the model. return inputs + delta
%%time residual_lstm = ResidualWrapper( tf.keras.Sequential([ tf.keras.layers.LSTM(32, return_sequences=True), tf.keras.layers.Dense( num_features, # The predicted deltas should start small. # Therefore, initialize the output layer with zeros. kernel_initializer=tf.initializers.zeros()) ])) history = compile_and_fit(residual_lstm, wide_window) IPython.display.clear_output() val_performance['Residual LSTM'] = residual_lstm.evaluate(wide_window.val) performance['Residual LSTM'] = residual_lstm.evaluate(wide_window.test, verbose=0) print()

Rendimiento

Aquí se muestra el rendimiento global de estos modelos multisalida.

x = np.arange(len(performance)) width = 0.3 metric_name = 'mean_absolute_error' metric_index = lstm_model.metrics_names.index('mean_absolute_error') val_mae = [v[metric_index] for v in val_performance.values()] test_mae = [v[metric_index] for v in performance.values()] plt.bar(x - 0.17, val_mae, width, label='Validation') plt.bar(x + 0.17, test_mae, width, label='Test') plt.xticks(ticks=x, labels=performance.keys(), rotation=45) plt.ylabel('MAE (average over all outputs)') _ = plt.legend()
for name, value in performance.items(): print(f'{name:15s}: {value[1]:0.4f}')

Los rendimientos anteriores se promedian entre todas las salidas del modelo.

Modelos multipaso

Tanto los modelos de salida única como los de salida múltiple de las secciones anteriores hicieron predicciones de un solo paso de tiempo, una hora en el futuro.

En esta sección se estudia cómo ampliar estos modelos para realizar pronósticos de múltiples pasos de tiempo.

En una predicción multipaso, el modelo necesita aprender a predecir una serie de valores futuros. Así, a diferencia de un modelo de un solo paso, en el que sólo se predice un único punto futuro, un modelo multipaso predice una secuencia de los valores futuros.

A grandes rasgos, existen dos enfoques:

  1. Predicciones en un único impulso, en las que se predice toda la serie temporal de una sola vez.

  2. Predicciones autorregresivas en las que el modelo sólo realiza predicciones de un paso y su salida se realimenta como su entrada.

En esta sección, todos los modelos predecirán todas las características a lo largo de todos los pasos de tiempo de salida.

Para el modelo multipaso, los datos de entrenamiento vuelven a consistir en muestreos horarios. Sin embargo, aquí los modelos aprenderán a predecir 24 horas en el futuro, dadas 24 horas del pasado.

Este es un objeto Window que genera estos cortes a partir del conjunto de datos:

OUT_STEPS = 24 multi_window = WindowGenerator(input_width=24, label_width=OUT_STEPS, shift=OUT_STEPS) multi_window.plot() multi_window

Lineas de referencia

Una línea de referencia sencilla para esta tarea consiste en repetir el último paso de tiempo de entrada durante el número necesario de pasos de tiempo de salida:

Repita la última entrada, para cada paso de salida

class MultiStepLastBaseline(tf.keras.Model): def call(self, inputs): return tf.tile(inputs[:, -1:, :], [1, OUT_STEPS, 1]) last_baseline = MultiStepLastBaseline() last_baseline.compile(loss=tf.keras.losses.MeanSquaredError(), metrics=[tf.keras.metrics.MeanAbsoluteError()]) multi_val_performance = {} multi_performance = {} multi_val_performance['Last'] = last_baseline.evaluate(multi_window.val) multi_performance['Last'] = last_baseline.evaluate(multi_window.test, verbose=0) multi_window.plot(last_baseline)

Dado que esta tarea consiste en predecir 24 horas en el futuro, dadas 24 horas del pasado, otro enfoque sencillo es repetir el día anterior, suponiendo que mañana será similar:

Repetir el día anterior

class RepeatBaseline(tf.keras.Model): def call(self, inputs): return inputs repeat_baseline = RepeatBaseline() repeat_baseline.compile(loss=tf.keras.losses.MeanSquaredError(), metrics=[tf.keras.metrics.MeanAbsoluteError()]) multi_val_performance['Repeat'] = repeat_baseline.evaluate(multi_window.val) multi_performance['Repeat'] = repeat_baseline.evaluate(multi_window.test, verbose=0) multi_window.plot(repeat_baseline)

Modelos de un solo impulso

Un enfoque de alto nivel para este problema es usar un modelo de "impulso único", en el que el modelo realiza toda la predicción de la secuencia en un solo paso.

Esto puede implementarse eficientemente como una tf.keras.layers.Dense con OUT_STEPS*features unidades de salida. El modelo sólo tiene que reestructurar esa salida a la (OUTPUT_STEPS, features) requerida.

Lineal

Un modelo lineal simple basado en el último paso de tiempo de entrada funciona mejor que cualquiera de las líneas de referencia, pero tiene poca potencia. El modelo necesita predecir OUTPUT_STEPS pasos de tiempo, a partir de un único paso de tiempo de entrada con una proyección lineal. Sólo puede capturar un fragmento de baja dimensión del comportamiento, probablemente basado principalmente en la hora del día y la época del año.

Predecir todos los pasos de tiempo desde el último paso de tiempo

multi_linear_model = tf.keras.Sequential([ # Take the last time-step. # Shape [batch, time, features] => [batch, 1, features] tf.keras.layers.Lambda(lambda x: x[:, -1:, :]), # Shape => [batch, 1, out_steps*features] tf.keras.layers.Dense(OUT_STEPS*num_features, kernel_initializer=tf.initializers.zeros()), # Shape => [batch, out_steps, features] tf.keras.layers.Reshape([OUT_STEPS, num_features]) ]) history = compile_and_fit(multi_linear_model, multi_window) IPython.display.clear_output() multi_val_performance['Linear'] = multi_linear_model.evaluate(multi_window.val) multi_performance['Linear'] = multi_linear_model.evaluate(multi_window.test, verbose=0) multi_window.plot(multi_linear_model)

Dense

Si se añade un tf.keras.layers.Dense entre la entrada y la salida, el modelo lineal adquiere más potencia, pero sigue basándose en un único paso de tiempo de entrada.

multi_dense_model = tf.keras.Sequential([ # Take the last time step. # Shape [batch, time, features] => [batch, 1, features] tf.keras.layers.Lambda(lambda x: x[:, -1:, :]), # Shape => [batch, 1, dense_units] tf.keras.layers.Dense(512, activation='relu'), # Shape => [batch, out_steps*features] tf.keras.layers.Dense(OUT_STEPS*num_features, kernel_initializer=tf.initializers.zeros()), # Shape => [batch, out_steps, features] tf.keras.layers.Reshape([OUT_STEPS, num_features]) ]) history = compile_and_fit(multi_dense_model, multi_window) IPython.display.clear_output() multi_val_performance['Dense'] = multi_dense_model.evaluate(multi_window.val) multi_performance['Dense'] = multi_dense_model.evaluate(multi_window.test, verbose=0) multi_window.plot(multi_dense_model)

CNN

Un modelo convolucional hace predicciones basándose en un historial de ancho fijo, lo que puede conducir a un mejor rendimiento que el modelo denso, ya que puede ver cómo cambian las cosas con el tiempo:

Un modelo convolucional ve cómo cambian las cosas con el tiempo

CONV_WIDTH = 3 multi_conv_model = tf.keras.Sequential([ # Shape [batch, time, features] => [batch, CONV_WIDTH, features] tf.keras.layers.Lambda(lambda x: x[:, -CONV_WIDTH:, :]), # Shape => [batch, 1, conv_units] tf.keras.layers.Conv1D(256, activation='relu', kernel_size=(CONV_WIDTH)), # Shape => [batch, 1, out_steps*features] tf.keras.layers.Dense(OUT_STEPS*num_features, kernel_initializer=tf.initializers.zeros()), # Shape => [batch, out_steps, features] tf.keras.layers.Reshape([OUT_STEPS, num_features]) ]) history = compile_and_fit(multi_conv_model, multi_window) IPython.display.clear_output() multi_val_performance['Conv'] = multi_conv_model.evaluate(multi_window.val) multi_performance['Conv'] = multi_conv_model.evaluate(multi_window.test, verbose=0) multi_window.plot(multi_conv_model)

RNN

Un modelo recurrente puede aprender a usar un largo historial de entradas, si es relevante para las predicciones que el modelo está haciendo. Aquí el modelo acumulará el estado interno durante 24 horas, antes de hacer una única predicción para las 24 horas siguientes.

En este formato de un solo impulso, el LSTM sólo necesita producir una salida en el último paso de tiempo, así que configure return_sequences=False en tf.keras.layers.LSTM.

El LSTM acumula estado a lo largo de la ventana de entrada, y realiza una única predicción para las próximas 24 horas

multi_lstm_model = tf.keras.Sequential([ # Shape [batch, time, features] => [batch, lstm_units]. # Adding more `lstm_units` just overfits more quickly. tf.keras.layers.LSTM(32, return_sequences=False), # Shape => [batch, out_steps*features]. tf.keras.layers.Dense(OUT_STEPS*num_features, kernel_initializer=tf.initializers.zeros()), # Shape => [batch, out_steps, features]. tf.keras.layers.Reshape([OUT_STEPS, num_features]) ]) history = compile_and_fit(multi_lstm_model, multi_window) IPython.display.clear_output() multi_val_performance['LSTM'] = multi_lstm_model.evaluate(multi_window.val) multi_performance['LSTM'] = multi_lstm_model.evaluate(multi_window.test, verbose=0) multi_window.plot(multi_lstm_model)

Avanzado: Modelo autorregresivo

Todos los modelos anteriores predicen la secuencia de salida completa en un solo paso.

En algunos casos puede ser útil que el modelo desglose esta predicción en pasos de tiempo individuales. Entonces, la salida de cada modelo puede retroalimentarse a sí misma en cada paso y las predicciones pueden hacerse condicionadas por la anterior, como en el clásico Generación de secuencias con redes neuronales recurrentes.

Una clara ventaja de este estilo de modelo es que puede configurarse para producir una salida con una longitud variable.

Podría tomar cualquiera de los modelos multisalida de un solo paso entrenados en la primera mitad de este tutorial y ejecutarlo en un bucle de retroalimentación autorregresiva, pero aquí centrará su atención en construir un modelo que haya sido entrenado explícitamente para ello.

Retroalimentación de la salida de un modelo a su entrada

RNN

Este tutorial sólo construye un modelo RNN autorregresivo, pero este patrón podría aplicarse a cualquier modelo diseñado para dar salida a un único paso de tiempo.

El modelo tendrá la misma forma básica que los modelos LSTM de un solo paso que vimos anteriormente: una capa tf.keras.layers.LSTM seguida de una capa tf.keras.layers.Dense que convierte las salidas de la capa LSTM en predicciones del modelo.

Una tf.keras.layers.LSTM es una tf.keras.layers.LSTMCell envuelta en la tf.keras.layers.RNN de nivel superior que administra el estado y secuencia los resultados por usted (Para más detalles, consulte la guía Redes Neuronales Recurrentes (RNN) con Keras).

En este caso, el modelo tiene que administrar manualmente las entradas de cada paso, por lo que usa tf.keras.layers.LSTMCell directamente para la interfaz de nivel inferior, de un solo paso de tiempo.

class FeedBack(tf.keras.Model): def __init__(self, units, out_steps): super().__init__() self.out_steps = out_steps self.units = units self.lstm_cell = tf.keras.layers.LSTMCell(units) # Also wrap the LSTMCell in an RNN to simplify the `warmup` method. self.lstm_rnn = tf.keras.layers.RNN(self.lstm_cell, return_state=True) self.dense = tf.keras.layers.Dense(num_features)
feedback_model = FeedBack(units=32, out_steps=OUT_STEPS)

El primer método que necesita este modelo es un método warmup para inicializar su estado interno en función de las entradas. Una vez entrenado, este estado capturará las partes relevantes del historial de entradas. Esto es equivalente al modelo LSTM de un solo paso visto anteriormente:

def warmup(self, inputs): # inputs.shape => (batch, time, features) # x.shape => (batch, lstm_units) x, *state = self.lstm_rnn(inputs) # predictions.shape => (batch, features) prediction = self.dense(x) return prediction, state FeedBack.warmup = warmup

Este método devuelve una predicción de un solo paso de tiempo y el estado interno de la LSTM:

prediction, state = feedback_model.warmup(multi_window.example[0]) prediction.shape

Con el estado de la RNN y una predicción inicial, puede ahora continuar iterando el modelo retroalimentando las predicciones en cada paso como entrada.

El enfoque más sencillo para recopilar las predicciones de salida es usar una lista Python y un tf.stack después del bucle.

Nota: Apilar una lista Python como ésta sólo funciona con eager execution, usando Model.compile(..., run_eagerly=True) para el entrenamiento, o con una salida de longitud fija. Para una longitud de salida dinámica, tendría que usar un tf.TensorArray en lugar de una lista de Python, y tf.range en lugar del range de Python.

def call(self, inputs, training=None): # Use a TensorArray to capture dynamically unrolled outputs. predictions = [] # Initialize the LSTM state. prediction, state = self.warmup(inputs) # Insert the first prediction. predictions.append(prediction) # Run the rest of the prediction steps. for n in range(1, self.out_steps): # Use the last prediction as input. x = prediction # Execute one lstm step. x, state = self.lstm_cell(x, states=state, training=training) # Convert the lstm output to a prediction. prediction = self.dense(x) # Add the prediction to the output. predictions.append(prediction) # predictions.shape => (time, batch, features) predictions = tf.stack(predictions) # predictions.shape => (batch, time, features) predictions = tf.transpose(predictions, [1, 0, 2]) return predictions FeedBack.call = call

Haga una prueba de este modelo con las entradas del ejemplo:

print('Output shape (batch, time, features): ', feedback_model(multi_window.example[0]).shape)

Ahora entrene el modelo:

history = compile_and_fit(feedback_model, multi_window) IPython.display.clear_output() multi_val_performance['AR LSTM'] = feedback_model.evaluate(multi_window.val) multi_performance['AR LSTM'] = feedback_model.evaluate(multi_window.test, verbose=0) multi_window.plot(feedback_model)

Rendimiento

En este problema existen claramente rendimientos decrecientes en función de la complejidad del modelo:

x = np.arange(len(multi_performance)) width = 0.3 metric_name = 'mean_absolute_error' metric_index = lstm_model.metrics_names.index('mean_absolute_error') val_mae = [v[metric_index] for v in multi_val_performance.values()] test_mae = [v[metric_index] for v in multi_performance.values()] plt.bar(x - 0.17, val_mae, width, label='Validation') plt.bar(x + 0.17, test_mae, width, label='Test') plt.xticks(ticks=x, labels=multi_performance.keys(), rotation=45) plt.ylabel(f'MAE (average over all times and outputs)') _ = plt.legend()

Las métricas de los modelos multisalida de la primera mitad de este tutorial muestran el rendimiento promediado en todas las características de salida. Estos rendimientos son similares pero también están promediados a través de los pasos de tiempo de salida.

for name, value in multi_performance.items(): print(f'{name:8s}: {value[1]:0.4f}')

Las ganancias conseguidas al pasar de un modelo denso a modelos convolucionales y recurrentes son sólo de unos pocos puntos porcentuales (si acaso), y el modelo autorregresivo funcionó claramente peor. Así que estos enfoques más complejos pueden no valer la pena en este problema, pero no había forma de saberlo sin probar y, además, estos modelos podrían ser útiles para su problema.

Siguientes pasos

Este tutorial fue una rápida introducción a la predicción de series temporales usando TensorFlow.

Para saber más, consulte:

Además, recuerde que puede implementar cualquier modelo clásico de series temporales en TensorFlow; este tutorial sólo se enfoca en la funcionalidad incorporada de TensorFlow.