時系列予測

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示ノートブックをダウンロードする

このチュートリアルは、TensorFlowを使用した時系列予測の概要です。畳み込みニューラルネットワーク(CNNおよびRNN)を含むいくつかの異なるスタイルのモデルを構築します。

これは、サブセクションで2つの主要な部分でカバーされています。

  • 単一のタイムステップの予測:
    • 単一の機能。
    • すべての機能。
  • 複数のステップを予測する:
    • 単発:一度に予測を行います。
    • 自己回帰:一度に1つの予測を行い、出力をモデルにフィードバックします。

セットアップ

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

気象データセット

このチュートリアルでは、 Max Planck Institute forBiogeochemistryによって記録された気象時系列データセットを使用します。

このデータセットには、気温、大気圧、湿度などの14の異なる機能が含まれています。これらは2003年から10分ごとに収集されました。効率を上げるために、2009年から2016年の間に収集されたデータのみを使用します。データセットのこのセクションは、FrançoisCholletが著書Deep Learning withPythonのために作成したものです

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)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/jena_climate_2009_2016.csv.zip
13574144/13568290 [==============================] - 0s 0us/step

このチュートリアルでは、 1時間ごとの予測のみを扱うため、10分間隔から1時間までデータをサブサンプリングすることから始めます。

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

データを見てみましょう。最初の数行は次のとおりです。

df.head()

これは、時間の経過に伴ういくつかの機能の進化です。

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)

png

png

検査とクリーンアップ

次に、データセットの統計を見てください。

df.describe().transpose()

風速

目立つべきものの1つは、風速のminwv (m/s)max. wv (m/s)列。この-9999はおそらく誤りです。別の風向列があるため、速度は>=0必要があり>=0 。ゼロに置き換えます。

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

機能工学

モデルを構築するために飛び込む前に、データを理解し、モデルに適切にフォーマットされたデータを渡していることを確認することが重要です。

データの最後の列であるwd (deg)は、風向を度単位で示します。角度は適切なモデル入力にはなりません。360°と0°は互いに近く、スムーズにラップアラウンドする必要があります。風が吹いていない場合は方向は関係ありません。

現在、風データの分布は次のようになっています。

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]')
Text(0, 0.5, 'Wind Velocity [m/s]')

png

ただし、風向と風速の列を風ベクトルに変換すると、モデルが解釈しやすくなります。

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)

風ベクトルの分布は、モデルが正しく解釈するためにはるかに簡単です。

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')
(-11.305513973134667, 8.24469928549079, -8.27438540335515, 7.7338312955467785)

png

時間

同様に、 Date Time列は非常に便利ですが、この文字列形式ではありません。それを秒に変換することから始めます:

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

風向と同様に、秒単位の時間は有用なモデル入力ではありません。気象データであるため、毎日および毎年の周期性が明確です。周期性に対処する方法はたくさんあります。

使用可能な信号に変換する簡単な方法は、 sincosを使用して時刻を変換し、「Timeofday」信号と「Timeofyear」信号をクリアすることです。

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')
Text(0.5, 1.0, 'Time of day signal')

png

これにより、モデルは最も重要な周波数機能にアクセスできます。この場合、どの周波数が重要であるかを事前に知っていました。

知らなかった場合は、 fftを使用してどの周波数が重要であるかを判断できます。仮定を確認するために、ここにtf.signal.rfft温度のtf.signal.rfftがあります。 1/yearおよび1/day付近の頻度で明らかなピークに注意してください。

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

png

データを分割する

トレーニング、検証、およびテストセットには、 (70%, 20%, 10%)分割を使用します。分割する前にデータがランダムにシャッフルされていないことに注意してください。これには2つの理由があります。

  1. これにより、データを連続サンプルのウィンドウに分割することが可能になります。
  2. これにより、モデルのトレーニング後に収集されたデータに基づいて評価され、検証/テスト結果がより現実的になります。
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]

データを正規化する

ニューラルネットワークをトレーニングする前に、特徴をスケーリングすることが重要です。正規化は、このスケーリングを行う一般的な方法です。平均を差し引き、各特徴の標準偏差で割ります。

モデルが検証セットとテストセットの値にアクセスできないように、平均と標準偏差はトレーニングデータを使用してのみ計算する必要があります。

また、トレーニング時にモデルがトレーニングセットの将来の値にアクセスできないようにする必要があり、この正規化は移動平均を使用して実行する必要があることも議論の余地があります。これはこのチュートリアルの焦点では​​ありません。検証とテストセットにより、(ある程度)正直なメトリックを確実に取得できます。したがって、簡単にするために、このチュートリアルでは単純な平均を使用します。

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

次に、機能の分布を確認します。一部の機能には長いテールがありますが、 -9999風速値のような明らかなエラーはありません。

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)

png

データウィンドウ処理

このチュートリアルのモデルは、データからの連続サンプルのウィンドウに基づいて一連の予測を行います。

入力ウィンドウの主な機能は次のとおりです。

  • 入力ウィンドウとラベルウィンドウの幅(タイムステップ数)
  • それらの間の時間オフセット。
  • 入力、ラベル、またはその両方として使用される機能。

このチュートリアルでは、さまざまなモデル(線形、DNN、CNN、RNNモデルを含む)を作成し、それらを両方に使用します。

  • 単一出力、および複数出力の予測。
  • シングルタイムステップおよびマルチタイムステップの予測。

このセクションでは、データウィンドウを実装して、それらすべてのモデルで再利用できるようにすることに焦点を当てます。

タスクとモデルのタイプに応じて、さまざまなデータウィンドウを生成することができます。ここではいくつかの例を示します。

  1. たとえば、24時間先の単一の予測を行うには、24時間の履歴が与えられた場合、次のようなウィンドウを定義できます。

    24時間先の1つの予測。

  2. 6時間の履歴が与えられた場合、1時間先の予測を行うモデルには、次のようなウィンドウが必要になります。

    1時間先の1つの予測。

このセクションの残りの部分では、 WindowGeneratorクラスを定義します。このクラスは次のことができます。

  1. 上の図に示すように、インデックスとオフセットを処理します。
  2. 機能のウィンドウを(features, labels)ペアに分割します。
  3. 結果のウィンドウのコンテンツをプロットします。
  4. tf.data.Datasetを使用して、トレーニング、評価、およびテストデータからこれらのウィンドウのバッチを効率的に生成します。

1.インデックスとオフセット

WindowGeneratorクラスを作成することからWindowGeneratorます。 __init__メソッドには、入力インデックスとラベルインデックスに必要なすべてのロジックが含まれています。

また、train、eval、およびtestのデータフレームを入力として受け取ります。これらは後でウィンドウのtf.data.Datasetに変換されます。

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

このセクションの冒頭の図に示されている2つのウィンドウを作成するコードは次のとおりです。

w1 = WindowGenerator(input_width=24, label_width=1, shift=24,
                     label_columns=['T (degC)'])
w1
Total window size: 48
Input indices: [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
Label indices: [47]
Label column name(s): ['T (degC)']
w2 = WindowGenerator(input_width=6, label_width=1, shift=1,
                     label_columns=['T (degC)'])
w2
Total window size: 7
Input indices: [0 1 2 3 4 5]
Label indices: [6]
Label column name(s): ['T (degC)']

2.分割

連続する入力のリストが与えられると、 split_windowメソッドはそれらを入力のウィンドウとラベルのウィンドウに変換します。

上記の例w2 、次のように分割されます。

初期ウィンドウはすべて連続したサンプルであり、これにより(入力、ラベル)ペアに分割されます

この図はデータのfeatures軸を示していませんが、このsplit_window関数はlabel_columnsも処理するため、単一出力と複数出力の両方の例で使用できます。

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

やってみて:

# 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}')
All shapes are: (batch, time, features)
Window shape: (3, 7, 19)
Inputs shape: (3, 6, 19)
labels shape: (3, 1, 1)

通常、TensorFlowのデータは、最も外側のインデックスが例全体にある配列にパックされます(「バッチ」ディメンション)。中央のインデックスは、「時間」または「スペース」(幅、高さ)の次元です。最も内側のインデックスは機能です。

上記のコードは、3、7タイムステップウィンドウのバッチを取り、各タイムステップに19の機能があります。それらを6タイムステップ、19の特徴入力、および1タイムステップの1特徴ラベルのバッチに分割します。 WindowGeneratorlabel_columns=['T (degC)']初期化されたため、ラベルには1つの機能しかありません。最初に、このチュートリアルでは、単一の出力ラベルを予測するモデルを構築します。

3.プロット

分割ウィンドウを簡単に視覚化できるプロット方法を次に示します。

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

このプロットは、アイテムが参照する時間に基づいて、入力、ラベル、および(後で)予測を調整します。

w2.plot()

png

他の列をプロットすることもできますが、ウィンドウw2構成の例には、 T (degC)列のラベルしかありません。

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

png

tf.data.Dataset作成します

最後に、このmake_dataset方法は、時系列取るDataFrameとに変換tf.data.Dataset(input_window, label_window)使用してペアpreprocessing.timeseries_dataset_from_array機能を。

def make_dataset(self, data):
  data = np.array(data, dtype=np.float32)
  ds = tf.keras.preprocessing.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

WindowGeneratorオブジェクトは、トレーニング、検証、およびテストデータを保持します。上記のmake_datasetメソッドを使用して、それらにアクセスするためのプロパティをtf.data.Datasetsとして追加します。また、簡単にアクセスしてプロットできるように、標準のサンプルバッチを追加します。

@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

これで、 WindowGeneratorオブジェクトを使用してtf.data.Datasetオブジェクトにアクセスできるようになったため、データを簡単に反復できます。

Dataset.element_specプロパティは、データセット要素の構造、 dtypes 、および形状を示します。

# Each element is an (inputs, label) pair
w2.train.element_spec
(TensorSpec(shape=(None, 6, 19), dtype=tf.float32, name=None),
 TensorSpec(shape=(None, 1, 1), dtype=tf.float32, name=None))

Dataset反復処理すると、具体的なバッチが生成されます。

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}')
Inputs shape (batch, time, features): (32, 6, 19)
Labels shape (batch, time, features): (32, 1, 1)

シングルステップモデル

この種のデータに基づいて構築できる最も単純なモデルは、現在の条件のみに基づいて、単一の機能の値、将来の1タイムステップ(1時間)を予測するモデルです。

したがって、1時間先のT (degC)値を予測するモデルを構築することから始めます。

次のタイムステップを予測する

これらのシングルステップ(input, label)ペアを生成するようにWindowGeneratorオブジェクトを構成します。

single_step_window = WindowGenerator(
    input_width=1, label_width=1, shift=1,
    label_columns=['T (degC)'])
single_step_window
Total window size: 2
Input indices: [0]
Label indices: [1]
Label column name(s): ['T (degC)']

windowオブジェクトは、トレーニング、検証、およびテストセットからtf.data.Datasetsを作成し、データのバッチを簡単に反復できるようにします。

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}')
Inputs shape (batch, time, features): (32, 1, 19)
Labels shape (batch, time, features): (32, 1, 1)

ベースライン

訓練可能なモデルを構築する前に、後のより複雑なモデルと比較するためのポイントとしてパフォーマンスベースラインを用意しておくとよいでしょう。

この最初のタスクは、すべての機能の現在の値を考慮して、将来の1時間の気温を予測することです。現在の値には、現在の温度が含まれます。

したがって、「変化なし」を予測して、現在の温度を予測として返すモデルから始めます。温度はゆっくり変化するため、これは妥当なベースラインです。もちろん、将来さらに予測を行うと、このベースラインはうまく機能しなくなります。

入力を出力に送信します

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]

このモデルをインスタンス化して評価します。

baseline = Baseline(label_index=column_indices['T (degC)'])

baseline.compile(loss=tf.losses.MeanSquaredError(),
                 metrics=[tf.metrics.MeanAbsoluteError()])

val_performance = {}
performance = {}
val_performance['Baseline'] = baseline.evaluate(single_step_window.val)
performance['Baseline'] = baseline.evaluate(single_step_window.test, verbose=0)
439/439 [==============================] - 1s 1ms/step - loss: 0.0128 - mean_absolute_error: 0.0785

それはいくつかのパフォーマンスメトリクスを印刷しましたが、それらはモデルがどれだけうまく機能しているかについての感覚をあなたに与えません。

WindowGeneratorにはplotメソッドがありますが、単一のサンプルだけではプロットはあまり面白くありません。したがって、 WindowGenerator 24時間連続して入力とラベルを生成するより幅の広いWindowGeneratorを作成します。

wide_windowは、モデルの動作方法を変更しません。モデルは、単一の入力時間ステップに基づいて、1時間先の予測を行います。ここで、 time軸はbatch軸のように機能します。各予測は、時間ステップ間の相互作用なしに独立して行われます。

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

wide_window
Total window size: 25
Input indices: [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
Label indices: [ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]
Label column name(s): ['T (degC)']

この拡張されたウィンドウは、コードを変更せずに同じbaselineモデルに直接渡すことができます。これが可能なのは、入力とラベルのタイムステップ数が同じであり、ベースラインが入力を出力に転送するだけだからです。

1時間先、1時間ごとの1つの予測。

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

ベースラインモデルの予測をプロットすると、それが単にラベルであり、1時間右にシフトしていることがわかります。

wide_window.plot(baseline)

png

上記の3つの例のプロットでは、シングルステップモデルが24時間にわたって実行されています。これはいくつかの説明に値します:

  • 青い「入力」行は、各タイムステップでの入力温度を示しています。モデルはすべての特徴を受け取ります。このプロットは温度のみを示しています。
  • 緑の「ラベル」ドットは、ターゲット予測値を示します。これらのドットは、入力時間ではなく、予測時間に表示されます。そのため、ラベルの範囲は入力に対して1ステップシフトされます。
  • オレンジ色の「予測」の十字は、各出力時間ステップに対するモデルの予測です。モデルが完全に予測している場合、予測は「ラベル」に直接到達します。

線形モデル

このタスクに適用できる最も単純なトレーニング可能なモデルは、入力と出力の間に線形変換を挿入することです。この場合、タイムステップからの出力は、そのステップにのみ依存します。

シングルステップ予測

activationセットのないlayers.Denseは線形モデルです。レイヤーは、データの最後の軸を(batch, time, inputs)から(batch, time, units)に変換するだけで、 batch軸と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)
Input shape: (32, 1, 19)
Output shape: (32, 1, 1)

このチュートリアルでは多くのモデルをトレーニングするため、トレーニング手順を関数にパッケージ化します。

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.losses.MeanSquaredError(),
                optimizer=tf.optimizers.Adam(),
                metrics=[tf.metrics.MeanAbsoluteError()])

  history = model.fit(window.train, epochs=MAX_EPOCHS,
                      validation_data=window.val,
                      callbacks=[early_stopping])
  return history

モデルをトレーニングし、そのパフォーマンスを評価します。

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)
Epoch 1/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.2373 - mean_absolute_error: 0.2848 - val_loss: 0.0148 - val_mean_absolute_error: 0.0918
Epoch 2/20
1534/1534 [==============================] - 4s 2ms/step - loss: 0.0121 - mean_absolute_error: 0.0817 - val_loss: 0.0107 - val_mean_absolute_error: 0.0766
Epoch 3/20
1534/1534 [==============================] - 4s 2ms/step - loss: 0.0108 - mean_absolute_error: 0.0765 - val_loss: 0.0101 - val_mean_absolute_error: 0.0743
Epoch 4/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0102 - mean_absolute_error: 0.0744 - val_loss: 0.0097 - val_mean_absolute_error: 0.0734
Epoch 5/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0099 - mean_absolute_error: 0.0730 - val_loss: 0.0094 - val_mean_absolute_error: 0.0722
Epoch 6/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0097 - mean_absolute_error: 0.0722 - val_loss: 0.0096 - val_mean_absolute_error: 0.0729
Epoch 7/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0095 - mean_absolute_error: 0.0716 - val_loss: 0.0094 - val_mean_absolute_error: 0.0721
Epoch 8/20
1534/1534 [==============================] - 4s 2ms/step - loss: 0.0094 - mean_absolute_error: 0.0712 - val_loss: 0.0092 - val_mean_absolute_error: 0.0713
Epoch 9/20
1534/1534 [==============================] - 4s 2ms/step - loss: 0.0094 - mean_absolute_error: 0.0710 - val_loss: 0.0090 - val_mean_absolute_error: 0.0698
Epoch 10/20
1534/1534 [==============================] - 4s 2ms/step - loss: 0.0093 - mean_absolute_error: 0.0707 - val_loss: 0.0090 - val_mean_absolute_error: 0.0698
Epoch 11/20
1534/1534 [==============================] - 4s 2ms/step - loss: 0.0093 - mean_absolute_error: 0.0705 - val_loss: 0.0089 - val_mean_absolute_error: 0.0695
Epoch 12/20
1534/1534 [==============================] - 4s 2ms/step - loss: 0.0092 - mean_absolute_error: 0.0703 - val_loss: 0.0093 - val_mean_absolute_error: 0.0713
Epoch 13/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0092 - mean_absolute_error: 0.0703 - val_loss: 0.0089 - val_mean_absolute_error: 0.0694
439/439 [==============================] - 1s 2ms/step - loss: 0.0089 - mean_absolute_error: 0.0694

baselineモデルと同様に、線形モデルは広いウィンドウのバッチで呼び出すことができます。このように使用すると、モデルは連続するタイムステップで一連の独立した予測を行います。 time軸は別のbatch軸のように機能します。各タイムステップでの予測間に相互作用はありません。

シングルステップ予測

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

これは、 wide_windowでの予測例のプロットです。多くの場合、入力温度を返すよりも予測が明らかに優れていることに注意してください。ただし、場合によっては、それよりも悪い場合もあります。

wide_window.plot(linear)

png

線形モデルの利点の1つは、解釈が比較的簡単なことです。レイヤーの重みを引き出して、各入力に割り当てられた重みを確認できます。

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)

png

モデルが入力T (degC)最大の重みを付けない場合もあります。これは、ランダムな初期化のリスクの1つです。

密集

実際に複数のタイムステップで動作するモデルを適用する前に、より深く、より強力な、単一入力ステップモデルのパフォーマンスを確認する価値があります。

これはlinearモデルに似たモデルですが、入力と出力の間にいくつかの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=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)
Epoch 1/20
1534/1534 [==============================] - 6s 4ms/step - loss: 0.0121 - mean_absolute_error: 0.0767 - val_loss: 0.0081 - val_mean_absolute_error: 0.0657
Epoch 2/20
1534/1534 [==============================] - 5s 3ms/step - loss: 0.0079 - mean_absolute_error: 0.0647 - val_loss: 0.0079 - val_mean_absolute_error: 0.0643
Epoch 3/20
1534/1534 [==============================] - 5s 3ms/step - loss: 0.0075 - mean_absolute_error: 0.0620 - val_loss: 0.0069 - val_mean_absolute_error: 0.0588
Epoch 4/20
1534/1534 [==============================] - 5s 3ms/step - loss: 0.0072 - mean_absolute_error: 0.0606 - val_loss: 0.0070 - val_mean_absolute_error: 0.0591
Epoch 5/20
1534/1534 [==============================] - 5s 3ms/step - loss: 0.0070 - mean_absolute_error: 0.0596 - val_loss: 0.0075 - val_mean_absolute_error: 0.0636
439/439 [==============================] - 1s 2ms/step - loss: 0.0075 - mean_absolute_error: 0.0636

マルチステップ高密度

シングルタイムステップモデルには、入力の現在の値のコンテキストがありません。入力機能が時間の経過とともにどのように変化しているかを確認することはできません。この問題に対処するには、モデルは予測を行うときに複数のタイムステップにアクセスする必要があります。

各予測には3つの時間ステップが使用されます。

baselinelinear 、およびdenseモデルは、各タイムステップを個別に処理しました。ここで、モデルは単一の出力を生成するために入力として複数の時間ステップを取ります。

3時間の入力と1時間のラベルのバッチを生成するWindowGeneratorを作成します。

Windowshiftパラメータは、2つのウィンドウの終わりを基準にしていることに注意してください。

CONV_WIDTH = 3
conv_window = WindowGenerator(
    input_width=CONV_WIDTH,
    label_width=1,
    shift=1,
    label_columns=['T (degC)'])

conv_window
Total window size: 4
Input indices: [0 1 2]
Label indices: [3]
Label column name(s): ['T (degC)']
conv_window.plot()
plt.title("Given 3h as input, predict 1h into the future.")
Text(0.5, 1.0, 'Given 3h as input, predict 1h into the future.')

png

レイヤーを追加することで、複数入力ステップウィンドウでdenseモデルをトレーニングできます。モデルの最初のレイヤーとしてlayers.Flatten化:

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)
Input shape: (32, 3, 19)
Output shape: (32, 1, 1)
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)
438/438 [==============================] - 1s 2ms/step - loss: 0.0068 - mean_absolute_error: 0.0597
conv_window.plot(multi_step_dense)

png

このアプローチの主な欠点は、結果のモデルが正確にこの形状の入力ウィンドウでのみ実行できることです。

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}')
Input shape: (32, 24, 19)

ValueError:Input 0 of layer dense_4 is incompatible with the layer: expected axis -1 of input shape to have value 57 but received input with shape (32, 456)

次のセクションの畳み込みモデルは、この問題を修正します。

畳み込みニューラルネットワーク

畳み込みレイヤー( layers.Conv1D )も、各予測への入力として複数のタイムステップを取ります。

以下はmulti_step_denseと同じモデルで、畳み込みでmulti_step_denseれています。

変更点に注意してください。

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

サンプルバッチで実行して、モデルが期待される形状の出力を生成することを確認します。

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

conv_windowして評価すると、 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)
438/438 [==============================] - 1s 2ms/step - loss: 0.0069 - mean_absolute_error: 0.0581

この違いconv_modelmulti_step_denseモデルがということですconv_model任意の長さの入力に実行することができます。畳み込み層は、入力のスライディングウィンドウに適用されます。

シーケンスで畳み込みモデルを実行する

より広い入力で実行すると、より広い出力が生成されます。

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)
Wide window
Input shape: (32, 24, 19)
Labels shape: (32, 24, 1)
Output shape: (32, 22, 1)

出力は入力よりも短いことに注意してください。トレーニングまたはプロットを機能させるには、ラベルと予測が同じ長さである必要があります。したがって、 WindowGeneratorを構築して、ラベルと予測の長さが一致するように、いくつかの追加の入力時間ステップで幅の広いウィンドウを生成します。

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
Total window size: 27
Input indices: [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24 25]
Label indices: [ 3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]
Label column name(s): ['T (degC)']
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)
Wide conv window
Input shape: (32, 26, 19)
Labels shape: (32, 24, 1)
Output shape: (32, 24, 1)

これで、モデルの予測をより広いウィンドウにプロットできます。最初の予測の前の3つの入力時間ステップに注意してください。ここでのすべての予測は、先行する3つのタイムステップに基づいています。

wide_conv_window.plot(conv_model)

png

リカレントニューラルネットワーク

リカレントニューラルネットワーク(RNN)は、時系列データに適したニューラルネットワークの一種です。 RNNは時系列を段階的に処理し、時間ステップごとに内部状態を維持します。

詳細については、テキスト生成チュートリアルまたはRNNガイドをお読みください

このチュートリアルでは、Long Short Term Memory( LSTM )と呼ばれるRNNレイヤーを使用します。

すべてのreturn_sequencesレイヤーの重要なコンストラクター引数はreturn_sequences引数です。この設定では、2つの方法のいずれかでレイヤーを構成できます。

  1. デフォルトのFalse場合、レイヤーは最終タイムステップの出力のみを返し、単一の予測を行う前にモデルに内部状態をウォームアップする時間を与えます。

lstmのウォーミングアップと単一の予測

  1. Trueの場合、レイヤーは入力ごとに出力を返します。これは次の場合に役立ちます。
    • RNNレイヤーのスタッキング。
    • 複数のタイムステップで同時にモデルをトレーニングします。

すべてのタイムステップの後に予測を行うlstm

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

return_sequences=Trueすると、モデルはreturn_sequences=Trueに24時間のデータでトレーニングできます。

print('Input shape:', wide_window.example[0].shape)
print('Output shape:', lstm_model(wide_window.example[0]).shape)
Input shape: (32, 24, 19)
Output shape: (32, 24, 1)
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)
438/438 [==============================] - 1s 2ms/step - loss: 0.0055 - mean_absolute_error: 0.0510
wide_window.plot(lstm_model)

png

パフォーマンス

このデータセットを使用すると、通常、各モデルは前のモデルよりもわずかに優れています。

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

png

for name, value in performance.items():
  print(f'{name:12s}: {value[1]:0.4f}')
Baseline    : 0.0852
Linear      : 0.0675
Dense       : 0.0639
Multi step dense: 0.0578
Conv        : 0.0602
LSTM        : 0.0515

マルチ出力モデル

これまでのモデルはすべて、単一の時間ステップで単一の出力特徴T (degC)予測していました。

これらのモデルはすべて、出力レイヤーのユニット数を変更し、トレーニングウィンドウを調整してすべての特徴を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}')
Inputs shape (batch, time, features): (32, 24, 19)
Labels shape (batch, time, features): (32, 24, 19)

ラベルのfeatures軸の深さが1ではなく入力と同じになっていることに注意してください。

ベースライン

ここでは同じベースラインモデルを使用できますが、今回は特定のlabel_indexを選択する代わりに、すべての機能を繰り返します。

baseline = Baseline()
baseline.compile(loss=tf.losses.MeanSquaredError(),
                 metrics=[tf.metrics.MeanAbsoluteError()])
val_performance = {}
performance = {}
val_performance['Baseline'] = baseline.evaluate(wide_window.val)
performance['Baseline'] = baseline.evaluate(wide_window.test, verbose=0)
438/438 [==============================] - 1s 1ms/step - loss: 0.0886 - mean_absolute_error: 0.1589

密集

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)
439/439 [==============================] - 1s 2ms/step - loss: 0.0683 - mean_absolute_error: 0.1313

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()
438/438 [==============================] - 1s 2ms/step - loss: 0.0617 - mean_absolute_error: 0.1208

CPU times: user 3min 34s, sys: 49.9 s, total: 4min 24s
Wall time: 1min 24s

高度:残りの接続

以前のBaselineモデルは、シーケンスがタイムステップごとに大幅に変化しないという事実を利用していました。これまでにこのチュートリアルでトレーニングされたすべてのモデルはランダムに初期化され、出力が前のタイムステップからの小さな変更であることを学習する必要がありました。

注意深く初期化することでこの問題を回避できますが、これをモデル構造に組み込む方が簡単です。

時系列分析では、次の値を予測する代わりに、次のタイムステップで値がどのように変化するかを予測するモデルを構築するのが一般的です。同様に、ディープラーニングの「残差ネットワーク」または「ResNets」は、各レイヤーがモデルの累積結果に追加されるアーキテクチャを指します。

このようにして、変化は小さいはずであるという知識を活用します。

接続が残っているモデル

基本的に、これはBaselineに一致するようにモデルを初期化します。このタスクでは、モデルの収束が速くなり、パフォーマンスがわずかに向上します。

このアプローチは、このチュートリアルで説明する任意のモデルと組み合わせて使用​​できます。

ここでは、LSTMモデルに適用されていますtf.initializers.zerosを使用して、最初に予測される変化が小さく、残りの接続を圧倒しないようにすることに注意してください。 zerosは最後のレイヤーでのみ使用されるため、ここでは勾配に関する対称性の破れの懸念はありません。

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 timestep 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
        # So 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()
438/438 [==============================] - 1s 2ms/step - loss: 0.0620 - mean_absolute_error: 0.1178

CPU times: user 1min 50s, sys: 25.7 s, total: 2min 16s
Wall time: 44.1 s

パフォーマンス

これらのマルチ出力モデルの全体的なパフォーマンスは次のとおりです。

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

png

for name, value in performance.items():
  print(f'{name:15s}: {value[1]:0.4f}')
Baseline       : 0.1638
Dense          : 0.1331
LSTM           : 0.1220
Residual LSTM  : 0.1194

上記のパフォーマンスは、すべてのモデル出力で平均化されています。

マルチステップモデル

前のセクションの単一出力モデルと複数出力モデルの両方が、1時間先の単一時間ステップ予測を行いました。

このセクションでは、これらのモデルを拡張して複数のタイムステップ予測を行う方法について説明します。

マルチステップ予測では、モデルは将来の値の範囲を予測することを学習する必要があります。したがって、単一の将来のポイントのみが予測されるシングルステップモデルとは異なり、マルチステップモデルは一連の将来の値を予測します。

これには2つの大まかなアプローチがあります。

  1. 時系列全体が一度に予測されるシングルショット予測。
  2. モデルがシングルステップ予測のみを行い、その出力が入力としてフィードバックされる自己回帰予測。

このセクションでは、すべてのモデルがすべての出力時間ステップにわたってすべての機能を予測します

マルチステップモデルの場合、トレーニングデータも1時間ごとのサンプルで構成されます。ただし、ここでは、過去24時間の場合、モデルは将来の24時間を予測することを学習します。

データセットからこれらのスライスを生成するWindowオブジェクトを次に示します。

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

multi_window.plot()
multi_window
Total window size: 48
Input indices: [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
Label indices: [24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47]
Label column name(s): None

png

ベースライン

このタスクの単純なベースラインは、必要な数の出力タイムステップに対して最後の入力タイムステップを繰り返すことです。

出力ステップごとに、最後の入力を繰り返します

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.losses.MeanSquaredError(),
                      metrics=[tf.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)
437/437 [==============================] - 1s 1ms/step - loss: 0.6285 - mean_absolute_error: 0.5007

png

このタスクは24時間を予測することであるため、別の簡単なアプローチは、明日が同様であると仮定して、前日を繰り返すことです。

前日を繰り返す

class RepeatBaseline(tf.keras.Model):
  def call(self, inputs):
    return inputs

repeat_baseline = RepeatBaseline()
repeat_baseline.compile(loss=tf.losses.MeanSquaredError(),
                        metrics=[tf.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)
437/437 [==============================] - 1s 1ms/step - loss: 0.4270 - mean_absolute_error: 0.3959

png

単発モデル

この問題に対する高レベルのアプローチの1つは、「シングルショット」モデルを使用することです。このモデルでは、シーケンス全体の予測が1つのステップで行われます。

これは、 layers.Denseとして効率的に実装できますOUT_STEPS*featureslayers.Dense出力ユニット。モデルは、その出力を必要な(OUTPUT_STEPS, features)に再形成する必要が(OUTPUT_STEPS, features)

線形

最後の入力時間ステップに基づく単純な線形モデルは、どちらのベースラインよりも優れていますが、パワーが不足しています。モデルは、線形射影を使用した単一の入力時間ステップからOUTPUT_STEPS時間ステップを予測する必要があります。これは、主に時刻と時刻に基づいて、動作の低次元のスライスのみをキャプチャできます。

最後のタイムステップからのすべてのタイムステップを予測します

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)
437/437 [==============================] - 1s 2ms/step - loss: 0.2557 - mean_absolute_error: 0.3055

png

密集

layers.Dense追加入力と出力の間にlayers.Denseと、線形モデルの出力が向上しますが、それでも単一の入力タイムステップのみに基づいています。

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)
437/437 [==============================] - 1s 2ms/step - loss: 0.2203 - mean_absolute_error: 0.2829

png

CNN

畳み込みモデルは、固定幅の履歴に基づいて予測を行います。これにより、時間の経過とともに状況がどのように変化するかを確認できるため、高密度モデルよりもパフォーマンスが向上する可能性があります。

畳み込みモデルは、物事が時間とともにどのように変化するかを確認します

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)
437/437 [==============================] - 1s 2ms/step - loss: 0.2141 - mean_absolute_error: 0.2802

png

RNN

反復モデルは、モデルが行っている予測に関連している場合、入力の長い履歴を使用することを学習できます。ここで、モデルは24時間内部状態を蓄積してから、次の24時間の単一の予測を行います。

このシングルショット形式では、LSTMは最後のタイムステップで出力を生成するだけでよいため、 return_sequences=False設定します。

lstmは入力ウィンドウ全体に状態を蓄積し、次の24時間の単一の予測を行います

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)
437/437 [==============================] - 1s 2ms/step - loss: 0.2157 - mean_absolute_error: 0.2861

png

高度:自己回帰モデル

上記のモデルはすべて、単一のステップで出力シーケンス全体を予測します。

場合によっては、モデルがこの予測を個々の時間ステップに分解すると役立つことがあります。次に、各モデルの出力を各ステップでそれ自体にフィードバックし、古典的なリカレントニューラルネットワークを使用したシーケンスの生成のように、前のモデルを条件として予測を行うことができます。

このスタイルのモデルの明らかな利点の1つは、さまざまな長さの出力を生成するように設定できることです。

このチュートリアルの前半でトレーニングされたシングルステップマルチ出力モデルのいずれかを使用して、自己回帰フィードバックループで実行できますが、ここでは、それを行うように明示的にトレーニングされたモデルの構築に焦点を当てます。

モデルの出力を入力にフィードバックする

RNN

このチュートリアルでは、自己回帰RNNモデルのみを構築しますが、このパターンは、単一のタイムステップを出力するように設計された任意のモデルに適用できます。

モデルは、シングルステップと同じ基本形状を有することになるLSTMモデル:アンLSTM続いlayers.Dense変換LSTMモデル予測に出力します。

layers.LSTMは、上位レベルのlayers.LSTMCellラップされたlayers.RNNであり、状態とシーケンスの結果を管理します(詳細については、 layers.LSTMCell RNNを参照してください)。

この場合、モデルは各ステップの入力を手動で管理する必要があるため、 layers.LSTMCellを下位レベルの単一タイムステップインターフェイスに直接使用します。

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)

このモデルに必要な最初の方法は、入力に基づいて内部状態を初期化するwarmup方法です。トレーニングが完了すると、この状態は入力履歴の関連部分をキャプチャします。これは、以前のシングルステップLSTMモデルと同等です。

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

このメソッドは、単一のタイムステップ予測とLSTMの内部状態を返します。

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

RNNの状態と初期予測を使用して、モデルの反復を続行し、各ステップで予測を入力としてフィードバックすることができます。

出力予測を収集する最も簡単なアプローチは、Pythonリストを使用し、ループの後にtf.stackを使用することです。

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

入力例でこのモデルをテスト実行します。

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

次に、モデルをトレーニングします。

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)
437/437 [==============================] - 3s 6ms/step - loss: 0.2253 - mean_absolute_error: 0.3008

png

パフォーマンス

この問題のモデルの複雑さの関数として、収穫逓減が明らかにあります。

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

png

このチュートリアルの前半のマルチ出力モデルのメトリックは、すべての出力機能にわたって平均されたパフォーマンスを示しています。これらのパフォーマンスは類似していますが、出力タイムステップ全体で平均化されています。

for name, value in multi_performance.items():
  print(f'{name:8s}: {value[1]:0.4f}')
Last    : 0.5157
Repeat  : 0.3774
Linear  : 0.2985
Dense   : 0.2766
Conv    : 0.2756
LSTM    : 0.2792
AR LSTM : 0.2914

密なモデルから畳み込みモデルおよび反復モデルに移行することで達成される利益は、(もしあれば)ほんの数パーセントであり、自己回帰モデルのパフォーマンスは明らかに劣っています。したがって、これらのより複雑なアプローチは、この問題に取り組んでいる間は価値がないかもしれませんが、試さずに知る方法はありませんでした。これらのモデルは問題に役立つ可能性があります。

次のステップ

このチュートリアルは、TensorFlowを使用した時系列予測の簡単な紹介でした。