Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/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.

Previsão de séries temporais

Este tutorial é uma introdução à previsão de séries temporais usando o TensorFlow. São criados alguns estilos de modelos diferentes, incluindo Redes Neurais Convolucionais e Recorrentes (CNN e RNN, nas siglas em inglês).

O tutorial é composto por duas partes principais com subseções:

  • Previsão com um passo temporal:

    • Uma única característica.

    • Todas as características.

  • Vários passos de previsão:

    • Single-shot (etapa única): faça todas as previsões de uma vez.

    • Autorregressivo: faça uma previsão de cada vez e alimente a saída de volta no modelo.

Configuração

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

Dataset do clima

Este tutorial usa um dataset de série temporal do clima registrado pelo Max Planck Institute for Biogeochemistry.

Este dataset contém 14 características diferentes, como temperatura do ar, pressão atmosférica e umidade, que foram coletadas a cada 10 minutos a partir de 2003. Por questões de eficiência, usaremos somente os dados coletados entre 2009 e 2016. Esse segmento do dataset foi preparado por François Chollet para seu livro Deep Learning with Python (Aprendizado profundo com o 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 tratará somente as previsões por hora, então comece fazendo a subamostragem dos dados, transformando os intervalos de 10 minutos em intervalos de uma 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')

Vamos dar uma olhada nos dados. Veja as primeiras linhas:

df.head()

Confira a evolução de algumas características ao longo do tempo:

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)

Inspeção e limpeza

Agora, veja as estatísticas do dataset:

df.describe().transpose()

Velocidade do vento

Um aspecto que deve se destacar são as colunas de valor mínimo min (wv (m/s)) e valor máximo (max. wv (m/s)) da velocidade do vento. Provavelmente, o valor -9999 é um erro.

Temos uma coluna separada para direção do vento, então a velocidade precisa ser maior do que zero (>=0). Substitua por zero:

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()

Engenharia de características

Antes de criar o modelo, é importante entender os dados e ter certeza de que você passará ao modelo dados formatados corretamente.

Vento

A última coluna dos dados, wd (deg), fornece a direção do vento em graus. Ângulos não são boas entradas para modelos: 360° e 0° devem estar próximos um do outro e fecharem um ciclo. A direção não é importante se o vento não estiver soprando.

No momento, veja como está a distribuição dos dados de vento:

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]')

Mas será mais fácil para o modelo interpretar se você converter as colunas de direção e velocidade em um vetor de vento:

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)

O modelo tem mais facilidade de interpretar corretamente a distribuição de vetores de vento:

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

De maneira similar, a coluna Date Time (Data e hora) é muito útil, mas não neste formato de string. Comece convertendo em segundos:

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

De maneira similar à direção do vento, a hora em segundos não é uma entrada muito útil para o modelo. Como são dados do clima, há uma periodicidade diária e anual clara. Há diversas maneiras de tratar a periodicidade.

Você consegue sinais úteis utilizando as transformadas de seno e cosseno para limpar os sinais "Hora do dia" and "Época do ano":

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')

Dessa forma, o modelo terá acesso às características de frequência mais importantes. Neste caso, você sabia de antemão quais frequências eram importantes.

Se você não tiver essas informações, pode determinar quais frequências são importantes por meio da extração de características usando a Transformada rápida de Fourier. Para verificar as hipóteses, aqui está a tf.signal.rfft da temperatura ao longo do tempo. Observe os picos óbvios nas frequências perto de 1/year (1/ano) e 1/day (1/dia):

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)')

Divisão dos dados

Você usará uma divisão igual a (70%, 20%, 10%) para os conjuntos de treinamento, validação e teste. Observe que os dados não estão sendo misturados aleatoriamente antes da divisão por dois motivos:

  1. Isso garante que ainda seja possível dividir os dados em janelas de amostras consecutivas.

  2. Isso garante que os resultados de validação/teste sejam mais realistas, já que a avaliação é feita para os dados coletados após o treinamento do 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]

Normalização dos dados

É importante mudar a escala das características antes de treinar uma rede neural. A normalização é uma estratégia comum para isso: subtraia a média e divida pelo desvio padrão de cada característica.

A média e o desvio padrão devem ser calculados usando-se somente os dados de treinamento para que os modelos não tenham acesso aos valores dos conjuntos de validação e teste.

Também podemos argumentar que o modelo não deve ter acesso aos valores futuros do conjunto de treinamento durante o treinamento e que essa normalização deve ser feita usando-se médias móveis. Este não é o foco deste tutorial, e os conjuntos de validação e teste garantem que você tenha métricas relativamente honestas. Portanto, por questões de simplicidade, este tutorial utiliza uma média simples.

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

Agora, confira a distribuição das características. Algumas delas parecem ter cauda longa, mas não há erros óbvios, como o valor -9999 para a velocidade do vento.

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)

Dados em janelas

Os modelos neste tutorial farão um conjunto de previsões com base em uma janela de amostras consecutivas dos dados.

As principais características das janelas de entrada são:

  • A largura (número de passos temporais) das janelas de entrada e rótulo.

  • A diferença de hora entre elas.

  • Quais características são usadas como entrada, rótulo ou ambos.

Este tutorial cria diversos modelos (incluindo modelos lineares, DNN, CNN e RNN) e utiliza-os para:

  • Fazer previsões de uma saída e várias saídas.

  • Fazer previsões com um passo temporal e vários passos temporais.

Esta seção se concentra na implementação das janelas de dados para que possam ser reutilizadas em todos os modelos.

Dependendo da tarefa e do tipo de modelo, você vai querer gerar diversas janelas de dados. Veja alguns exemplos:

  1. Por exemplo, para fazer uma única previsão para 24 horas à frente, dadas 24 horas de histórico, você pode definir uma janela desta forma:

One prediction 24 hours into the future.

  1. Um modelo que faz uma previsão para uma hora à frente, dadas seis horas de histórico, precisaria de uma janela como esta:

One prediction one hour into the future.

O restante desta seção define uma classe WindowGenerator. Esta classe pode:

  1. Tratar os índices e diferenças, conforme mostrado nos diagramas acima.

  2. Dividir as janelas de características em pares (features, labels) (características, rótulos).

  3. Plotar o conteúdo das janelas resultantes.

  4. Gerar lotes dessas janelas a partir dos dados de treinamento, avaliação e teste com eficiência usando tf.data.Datasets.

1. Índices e diferenças

Comece criando a classe WindowGenerator. O método __init__ inclui toda a lógica necessária para os índices de entrada e rótulo.

Ele também recebe DataFrames de treinamento, avaliação e teste como entrada, que serão convertidos em tf.data.Datasets de janelas posteriormente.

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}'])

Veja o código para criar as duas janelas exibidas nos diagramas no começo desta seção:

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. Divisão

Dada uma lista de entradas consecutivas, o método split_window as converterá em uma janela de entradas e outra de rótulos.

O exemplo w2 definido anteriormente será dividido assim:

The initial window is all consecutive samples, this splits it into an (inputs, labels) pairs

Esse diagrama não mostra o eixo features (características) dos dados, mas a função split_window também trata as label_columns (colunas de rótulos), então pode ser usada para exemplos com uma saída e várias saídas.

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

Faça um teste:

# 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}')

Tipicamente, os dados no TensorFlow são encapsulados em arrays, em que o índice mais externo é usado entre exemplos (a dimensão "lote"). Os índices do meio são as dimensões "hora" ou "espaço" (largura, altura). Os índices mais internos são as características.

O código acima recebeu um lote de três janelas com 7 passos temporais, com 19 características em cada passo temporal. Ele divide em um lote de entradas com 6 passos temporais e 19 características, além de um rótulo com 1 passo temporal e 1 característica. O rótulo tem somente uma característica porque o WindowGenerator (gerador de janelas) foi inicializado com label_columns=['T (degC)']. Inicialmente, este tutorial criará modelos que preveem rótulos com uma saída.

3. Gráfico

Veja um método para gerar gráficos que permite uma visualização simples da janela de divisão:

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

Esse gráfico alinha entradas, rótulos e (posteriormente) previsões com base na hora referente ao item:

w2.plot()

Você pode plotar as outras colunas, mas a configuração da janela de exemplo w2 tem somente rótulos para a coluna T (degC).

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

4. Criação de tf.data.Datasets

Por fim, o método make_dataset recebe um DataFrame de série temporal e converte-o em um tf.data.Dataset de pares (input_window, label_window) usando a função 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

O objeto WindowGenerator armazena dados de treinamento, validação e teste.

Adicione propriedades para acessá-los como tf.data.Datasets usando o método make_dataset definido anteriormente. Além disso, acrescente um lote de exemplos padrão para facilitar o acesso e plotar gráficos:

@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

Agora, o objeto WindowGenerator fornece acesso aos objetos tf.data.Dataset para que você possa fazer a iteração dos dados com facilidade.

A propriedade Dataset.element_spec indica a estrutura, os tipos de dados e os formatos dos elementos do dataset.

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

Ao fazer a iteração de um Dataset, são obtidos 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 com um passo

O modelo mais simples que você pode criar com esse tipo de dados prevê um único valor de característica — um passo temporal (uma hora) à frente com base somente nas condições atuais.

Então, comece criando modelos para prever o valor de T (degC) uma hora à frente.

Predict the next time step

Configure um objeto WindowGenerator para gerar esses pares (input, label) (entrada, rótulo) com um passo:

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

O objeto window cria tf.data.Datasets a partir dos conjuntos de treinamento, validação e teste, o que permite fazer a iteração dos lotes de dados com facilidade.

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}')

Linha de base

Antes de criar um modelo treinável, é bom ter uma linha de base de desempenho para fins de comparação com os modelos mais complicados que virão.

A primeira tarefa é prever a temperatura uma hora à frente, dado o valor atual de todas as características. Os valores atuais incluem a temperatura atual.

Portanto, comece com um modelo que retorne apenas a temperatura atual como a previsão, prevendo "Sem alteração". Essa é uma linha de base razoável, já que a temperatura muda aos poucos. Claro que essa linha de base terá desempenho inferior se você fizer uma previsão mais à frente no tempo.

Send the input to the output

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]

Instancie e avalie o 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)

Foram exibidas algumas métricas de desempenho, mas elas não indicam se o modelo está se saindo bem.

WindowGenerator tem um método de plotagem, mas os gráficos não serão muito interessantes com apenas uma amostra.

Portanto, crie um WindowGenerator mais abrangente, que crie janelas de 24 horas de entradas e rótulos consecutivos por vez. A nova variável wide_window não altera o funcionamento do modelo, que ainda faz previsões uma hora à frente com base em um único passo temporal de entrada. Aqui, o eixo time (hora) funciona como o eixo batch lote: cada previsão é feita de forma independente, sem interação entre os passos temporais:

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

Essa janela expandida pode ser passada diretamente para o mesmo modelo de linha de base (baseline) sem qualquer alteração no código, pois as entradas e os rótulos têm o mesmo número de passos temporais, e a linha de base simplesmente encaminha a entrada para a saída.

One prediction 1h into the future, ever hour.

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

Ao plotar as previsões do modelo de linha de base, observe que são simplesmente os rótulos deslocados em uma hora:

wide_window.plot(baseline)

Nos gráficos acima com três exemplos, o modelo com um passo é executado para 24 horas. Veja a explicação:

  • A linha azul Inputs (entradas) mostra a temperatura de entrada em cada passo temporal. O modelo recebe todas as características, mas esse gráfico mostra apenas a temperatura.

  • Os pontos verdes Labels (rótulos) mostram o valor de previsão alvo. Esses pontos são mostrados no momento da previsão, não no momento da entrada. É por isso que o intervalo de rótulos é deslocado em um passo em relação às entradas.

  • As cruzes laranjas Predictions (previsões) são a previsão do modelo para cada passo temporal de saída. Se o modelo estivesse prevendo perfeitamente, as previsões ficariam diretamente nos Labels (rótulos).

Modelo linear

O modelo treinável mais simples que você pode aplicar a esta tarefa é inserir a transformada linear entre a entrada e a saída. Neste caso, a saída de um passo temporal depende apenas desse passo:

A single step prediction

Uma camada tf.keras.layers.Dense com conjunto de ativação (activation) é um modelo linear. A camada apenas transforma o último eixo dos dados de (batch, time, inputs) (lote, hora, entradas) em (batch, time, units) (lote, hora, unidades); ela é aplicada de forma independente a cada item nos eixos batch (lote) e time (hora).

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 treina diversos modelos, então encapsule o procedimento de treinamento em uma função:

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

Treine o modelo e avalie o desempenho:

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)

Como o modelo de linha de base (baseline), o modelo linear pode ser chamado nos lotes das janelas largas. Quando usado desta forma, o modelo faz um conjunto de previsões independentes em passos temporais consecutivos. O eixo time (hora) atua como outro eixo batch (lote). Não há interações entre as previsões em cada passo temporal.

A single step prediction

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

Veja o gráfico das previsões de exemplo em wide_window. Observe como, em muitos casos, a previsão é claramente melhor do que apenas retornar a temperatura de entrada. Porém, em alguns casos, é pior:

wide_window.plot(linear)

Uma vantagem dos modelos lineares é que é relativamente simples interpretá-los. Você pode obter os pesos da camada e visualizar o peso atribuído 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)

Às vezes, o modelo não coloca o mesmo peso na entrada T (degC). Esse é um dos riscos da inicialização aleatória.

Modelo denso

Antes de aplicar modelos que podem operar com vários passos temporais, vale a pena verificar o desempenho de modelos com um passo de entrada mais profundos e poderosos.

Veja um modelo similar ao linear, mas que empilha algumas camadas Dense entre a entrada e a saída:

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)

Modelo denso com vários passos

Um modelo com um passo temporal não tem o contexto dos valores atuais de suas entradas. Ele não consegue ver como as características de entrada mudam ao longo do tempo. Para resolver esse problema, o modelo precisa de acesso a vários passos temporais ao fazer previsões:

Three time steps are used for each prediction.

Os modelos baseline, linear e dense trataram cada passo temporal de forma independente. Aqui, o modelo receberá vários passos temporais como entrada para gerar uma única saída.

Crie um WindowGenerator que gere lotes de entradas de três horas e de rótulos de uma hora:

Observe que o parâmetro shift de Window é relativo ao final das duas janelas.

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.")

Para treinar um modelo dense com uma janela de vários passos de entrada, basta adicionar tf.keras.layers.Flatten como a primeira camada do 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)

A principal desvantagem dessa estratégia é que o modelo resultante só pode ser executado em janelas de entrada com exatamente o mesmo formato.

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}')

Os modelos convolucionais da próxima seção corrigem esse problema.

Rede neural convolucional

Uma camada convolucional (tf.keras.layers.Conv1D) também recebe vários passos temporais como entrada para cada previsão.

Veja abaixo o mesmo modelo que multi_step_dense, reescrito com uma convolução:

Observe as alterações:

  • A camada tf.keras.layers.Flatten e a primeira camada tf.keras.layers.Dense são substituídas por uma camada tf.keras.layers.Conv1D.

  • A camada tf.keras.layers.Reshape não é mais necessária, já que a convolução mantém o eixo de hora em sua saída.

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), ])

Execute em um lote de exemplo para verificar se o modelo gera as saídas com o formato esperado:

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

Treine e avalie em conv_window, e o desempenho deve ser similar ao 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)

A diferença entre conv_model e multi_step_dense é que o modelo conv_model pode ser executado em entradas de qualquer tamanho. A camada convolucional é aplicada a uma janela de entradas deslizante:

Executing a convolutional model on a sequence

Se você executar em uma entrada mais ampla, a saída gerada será mais ampla:

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 a saída é mais curta do que a entrada. Para que o treinamento ou a plotagem funcionem, os rótulos e a previsão devem ter o mesmo tamanho. Então, crie um WindowGenerator que produza janelas largas com alguns passos temporais de entrada adicionais para que o tamanho do rótulo seja igual ao da previsão:

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)

Agora, você pode plotar as previsões do modelo em uma janela maior. Observe os três passos temporais de entrada antes da primeira previsão. Cada previsão é baseada nos três passos temporais anteriores:

wide_conv_window.plot(conv_model)

Rede neural recorrente

Uma Rede Neural Recorrente (RNN, na sigla em inglês) é um tipo de rede neural adequada para dados de séries temporais. As RNNs processam uma série temporal passo a passo, mantendo o estado interno ao passar de um passo temporal para outro.

Saiba mais no tutorial Geração de texto com uma RNN e no guia Redes Neurais Recorrentes (RNNs) com o Keras.

Neste tutorial, você usará uma camada de RNN chamada Memória Longa de Período Curto (tf.keras.layers.LSTM – LSTM, na sigla em inglês).

Um argumento importante do construtor de todas as camadas de RNN do Keras, como tf.keras.layers.LSTM, é o argumento return_sequences, que pode configurar a camada de duas maneiras:

  1. Quando False, o padrão, a camada retorna somente a saída do passo temporal final, dando tempo para o modelo "aquecer" seu estado interno antes de fazer uma previsão:

An LSTM warming up and making a single prediction

  1. Quando True, a camada retorna uma saída para cada entrada. Isso é útil para:

    • Empilhar camadas de RNN.

    • Treinar um modelo com vários passos temporais simultaneamente.

An LSTM making a prediction after every time step

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) ])

Com return_sequences=True, o modelo pode ser treinado com 24 horas de dados de cada vez.

Observação: isso dará uma visão pessimista do desempenho do modelo. No primeiro passo temporal, o modelo não tem acesso aos passos anteriores e, portanto, não pode fazer nada melhor do que os modelos simples linear e 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)

Desempenho

Com este dataset, tipicamente, cada um dos modelos tem um desempenho ligeiramente melhor do que o 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 com várias saídas

Até agora, todos os modelos previram uma única característica de saída, T (degC), para um único passo temporal.

Todos esse modelos podem ser alterados para prever várias características, basta mudar o número de unidades na camada de saída e ajustar as janelas de treinamento de forma a incluírem todas as características em 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 que agora o eixo features (características) dos rótulos tem a mesma profundidade que as entradas, em vez de 1.

Linha de base

O mesmo modelo de linha de base (Baseline) pode ser usado aqui, porém, desta vez, repetindo todas as características em vez de selecionando um label_index específico:

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)

Modelo denso

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()

Avançado: conexões residuais

O modelo de linha de base (Baseline) anterior aproveitava o fato de que a sequência não muda dramaticamente de um passo temporal para outro. Cada modelo treinado neste tutorial até o momento foi inicializado aleatoriamente e depois teve que aprender que a saída é uma pequena alteração do passo temporal anterior.

Embora seja possível evitar esse problema com uma inicialização cuidadosa, é mais simples incorporar isso à estrutura do modelo.

Em análises de séries temporais, é comum criar modelos que, em vez de previrem o próximo valor, prevejam como o valor vai mudar no próximo passo temporal. De maneira similar, em aprendizado profundo, as redes residuais, ou ResNets, referem-se a arquiteturas em que cada camada acrescenta ao resultado cumulativo do modelo.

É assim que você pode aproveitar o fato de a mudança ser pequena.

A model with a residual connection

Essencialmente, isso inicializa o modelo para que corresponda à linha de base (Baseline). Para esta tarefa, ajuda o fato de os modelos convergirem mais rápido, com um desempenho ligeiramente superior.

Essa estratégia pode ser usada em conjunto com qualquer modelo discutido neste tutorial.

Aqui, isso está sendo aplicado ao modelo LSTM. Observe o uso de tf.initializers.zeros para garantir que as mudanças iniciais previstas sejam pequenas e não dominem a conexão residual. Não há preocupações com a quebra de simetria para os gradientes, já que zeros são usados somente na última camada.

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()

Desempenho

Veja o desempenho geral desses modelos com várias saídas.

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}')

Os desempenhos acima são uma média para todas as saídas do modelo.

Modelos com vários passos

Tanto os modelos com uma saída quanto com várias saídas das seções anteriores faziam previsões com um passo temporal, uma hora à frente.

Nesta seção, veremos como expandir esses modelos para fazer previsões com vários passos temporais.

Em uma previsão com vários passos, o modelo precisa aprender a prever um intervalo de valores futuros. Portanto, diferentemente de um modelo com um passo, em que só é preciso prever um ponto futuro, um modelo com vários passos prevê uma sequência de valores futuros.

Veja duas estratégias gerais para isso:

  1. Previsões em etapa única (single-shot), em que toda a série temporal é prevista de uma só vez.

  2. Previsões autorregressivas, em que o modelo faz somente previsões com um passo, e sua saída é alimentada de volta como sua entrada.

Nesta seção, todos os modelos vão prever todas as características para todos os passos temporais de saída.

Para o modelo com vários passos, os dados de treinamento consistem novamente de amostras por hora. Porém, aqui, os modelos aprenderão a prever 24 horas à frente, dadas 24 horas passadas.

Aqui está um objeto Window que gera esses segmentos do dataset:

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

Linhas de base

Uma linha de base simples para essa tarefa é repetir o último passo temporal de entrada para o número exigido de passos temporais de saída:

Repeat the last input, for each output step

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)

Como esta tarefa é prever 24 horas à frente, dadas 24 horas passadas, outra estratégia simples é repetir o dia anterior, supondo que amanhã será similar:

Repeat the previous day

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 single-shot (etapa única)

Uma estratégia de alto nível para esse problema é usar um modelo "single-shot", em que ele faz a previsão de toda a sequência em um único passo.

É possível fazer uma implementação eficiente com uma camada tf.keras.layers.Dense, com OUT_STEPS*features unidades de saída. O modelo só precisa mudar o formato da saída para o (OUTPUT_STEPS, features) exigido.

Linear

Um modelo linear simples baseado no último passo temporal de entrada tem desempenho superior a qualquer linha de base, mas é pouco potente. O modelo precisa prever OUTPUT_STEPS passos temporais a partir de um único passo temporal de entrada com uma projeção linear. Ele consegue capturar somente um segmento de baixa dimensão do comportamento, provavelmente baseado principalmente na hora do dia e na época do ano.

Predict all timesteps from the last time-step

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)

Modelo denso

Ao adicionar uma camada tf.keras.layers.Dense à entrada e à saída, o modelo linear fica mais potente, mas ainda baseia-se somente em um único passo temporal 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

Um modelo convolucional faz previsões baseadas em um histórico de largura fixa, que pode ter um desempenho melhor do que o modelo denso, já que consegue ver como as características mudam ao longo do tempo:

A convolutional model sees how things change over time

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

Um modelo recorrente pode aprender a usar um histórico de entradas longo, se for relevante para as previsões sendo feitas. Aqui, o modelo acumulará o estado interno por 24 horas antes de fazer uma única previsão para as próximas 24 horas.

Nesse formato single-shot (etapa única), a LSTM só precisa gerar uma saída no último passo temporal, então defina return_sequences=False em tf.keras.layers.LSTM.

The LSTM accumulates state over the input window, and makes a single prediction for the next 24 hours

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)

Avançado: modelo autorregressivo

Todos os modelos acima preveem toda a sequência de saídas em um único passo.

Em alguns casos, pode ser útil para o modelo decompor a previsão em passos temporais individuais. Em seguida, cada saída do modelo pode ser alimentada de volta em si mesma em cada passo, e as previsões podem ser feitas condicionadas à anterior, como no tutorial Como gerar sequências com Redes Neurais Recorrentes.

Uma vantagem clara desse estilo de modelo é que ele pode ser configurado para gerar saídas com um tamanho variável.

Você pode pegar qualquer um dos modelos com uma etapa e várias saídas treinados na primeira metade deste tutorial e executá-lo em um loop de feedback autorregressivo, mas aqui seu foco será em criar um modelo que foi treinado explicitamente para fazer isso.

Feedback a model's output to its input

RNN

Este tutorial apenas cria um modelo RNN autorregressivo, mas esse padrão pode ser aplicado a qualquer modelo desenvolvido para gerar um único passo temporal.

O modelo terá a mesma forma básica dos modelos LSTM com um passo anteriores: uma camada tf.keras.layers.LSTM, seguida por uma camada tf.keras.layers.Dense que converte as saídas da camada LSTM em previsões do modelo.

Uma camada tf.keras.layers.LSTM é uma camada tf.keras.layers.LSTMCell encapsulada na camada tf.keras.layers.RNN de alto nível que gerencia o estado e os resultados da sequência para você (confira mais detalhes no guia Redes Neurais Recorrentes (RNNs) com o Keras).

Neste caso, o modelo precisa gerenciar manualmente as entradas em cada passo, então ele usa uma camada tf.keras.layers.LSTMCell diretamente para a interface do passo temporal de nível mais baixo.

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)

O primeiro método que esse modelo precisa é um método de warmup ("aquecimento") para inicializar seu estado interno com base nas entradas. Após o treinamento, esse estado vai capturar as partes relevantes do histórico de entradas. Isso é equivalente ao modelo LSTM com um passo anterior:

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

Esse método retorna uma previsão de passo temporal e o estado interno da LSTM:

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

Com o estado da RNN e uma previsão inicial, agora você pode continuar fazendo a iteração do modelo, alimentando as previsões em cada passo de volta como a entrada.

A estratégia mais simples para coletar as previsões de saída é usar uma lista do Python e tf.stack após o loop.

Observação: empilhar uma lista do Python dessa forma funciona somente com a execução adiantada (eager), usando Model.compile(..., run_eagerly=True) para o treinamento ou uma saída de tamanho fixo. Para uma saída de tamanho dinâmico, você precisa usar uma array tf.TensorArray em vez de uma lista do Python e tf.range em vez do range do 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

Teste a execução desse modelo nas entradas de exemplo:

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

Agora treine o 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)

Desempenho

Neste problema, há claramente ganhos decrescentes como uma função da complexidade do 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()

As métricas dos modelos com várias saídas da primeira metade deste tutorial mostram o desempenho como uma média de todas as características de saída. Estes desempenhos são similares, mas também são feitos como uma média dos passos temporais de saída.

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

Os ganhos obtidos ao passar de um modelo denso para modelos convolucionais e recorrentes é de apenas alguns pontos percentuais (quando há ganhos), e o modelo autorregressivo teve desempenho claramente inferior. Portanto, talvez essas estratégias mais complexas não valham a pena para este problema, mas não tínhamos como saber sem tentar, e esses modelos podem ser úteis para o seu problema.

Próximos passos

Este tutorial foi uma introdução rápida a uma previsão de série temporal usando o TensorFlow.

Para saber mais, confira:

E lembre-se de que você pode implementar qualquer modelo clássico de série temporal no TensorFlow. O foco deste tutorial foi apenas a funcionalidade integrada do TensorFlow.