このページは Cloud Translation API によって翻訳されました。
Switch to English

時系列予測

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

気象データセット

このチュートリアルでは、マックスプランク生物地球化学研究所によって記録された気象時系列データセットを使用します。

このデータセットには、気温、大気圧、湿度などの14の異なる特徴が含まれています。これらは効率のために、あなただけのデータがデータセットのこのセクションでは、彼の本のためにフランソワ・Cholletすることにより調製した2009年から2016年の間の収集に使用されます2003年に始まり、10分ごとに収集したの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)
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(datetime.datetime.timestamp)

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

これを使用可能な信号に変換する簡単な方法は、 sincosを使用して時間を変換し、「Time of day」と「Time of year」信号をクリアすることです。

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.インデックスとオフセット

WindowGeneratorWindowGeneratorクラスを作成します。 __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(3, 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 2ms/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時間先の予測。

print('Input shape:', single_step_window.example[0].shape)
print('Output shape:', baseline(single_step_window.example[0]).shape)
Input shape: (32, 1, 19)
Output shape: (32, 1, 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 [==============================] - 5s 3ms/step - loss: 0.3819 - mean_absolute_error: 0.3607 - val_loss: 0.0224 - val_mean_absolute_error: 0.1148
Epoch 2/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0179 - mean_absolute_error: 0.0967 - val_loss: 0.0098 - val_mean_absolute_error: 0.0737
Epoch 3/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0096 - mean_absolute_error: 0.0720 - val_loss: 0.0088 - val_mean_absolute_error: 0.0694
Epoch 4/20
1534/1534 [==============================] - 5s 3ms/step - loss: 0.0091 - mean_absolute_error: 0.0696 - val_loss: 0.0089 - val_mean_absolute_error: 0.0694
Epoch 5/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0090 - mean_absolute_error: 0.0696 - val_loss: 0.0086 - val_mean_absolute_error: 0.0681
Epoch 6/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0091 - mean_absolute_error: 0.0697 - val_loss: 0.0086 - val_mean_absolute_error: 0.0684
Epoch 7/20
1534/1534 [==============================] - 4s 3ms/step - loss: 0.0090 - mean_absolute_error: 0.0696 - val_loss: 0.0086 - val_mean_absolute_error: 0.0679
439/439 [==============================] - 1s 2ms/step - loss: 0.0086 - mean_absolute_error: 0.0679

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.0178 - mean_absolute_error: 0.0821 - val_loss: 0.0085 - val_mean_absolute_error: 0.0693
Epoch 2/20
1534/1534 [==============================] - 6s 4ms/step - loss: 0.0079 - mean_absolute_error: 0.0645 - val_loss: 0.0073 - val_mean_absolute_error: 0.0617
Epoch 3/20
1534/1534 [==============================] - 6s 4ms/step - loss: 0.0075 - mean_absolute_error: 0.0625 - val_loss: 0.0069 - val_mean_absolute_error: 0.0598
Epoch 4/20
1534/1534 [==============================] - 6s 4ms/step - loss: 0.0073 - mean_absolute_error: 0.0611 - val_loss: 0.0071 - val_mean_absolute_error: 0.0611
Epoch 5/20
1534/1534 [==============================] - 6s 4ms/step - loss: 0.0071 - mean_absolute_error: 0.0603 - val_loss: 0.0066 - val_mean_absolute_error: 0.0576
Epoch 6/20
1534/1534 [==============================] - 6s 4ms/step - loss: 0.0069 - mean_absolute_error: 0.0592 - val_loss: 0.0072 - val_mean_absolute_error: 0.0611
Epoch 7/20
1534/1534 [==============================] - 6s 4ms/step - loss: 0.0068 - mean_absolute_error: 0.0589 - val_loss: 0.0066 - val_mean_absolute_error: 0.0583
439/439 [==============================] - 1s 3ms/step - loss: 0.0066 - mean_absolute_error: 0.0583

マルチステップ高密度

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

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

baselineモデル、 linearモデル、 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

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

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.0065 - mean_absolute_error: 0.0581

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)

InvalidArgumentError:Matrix size-incompatible: In[0]: [32,456], In[1]: [57,32] [Op:MatMul]

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

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

畳み込み層( 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.0062 - mean_absolute_error: 0.0541

この違い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 3ms/step - loss: 0.0056 - mean_absolute_error: 0.0511

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.0671
Dense       : 0.0602
Multi step dense: 0.0596
Conv        : 0.0545
LSTM        : 0.0519

マルチ出力モデル

これまでのモデルはすべて、単一の時間ステップに対して単一の出力特徴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 2ms/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.0678 - mean_absolute_error: 0.1312

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 3ms/step - loss: 0.0618 - mean_absolute_error: 0.1208

CPU times: user 4min 6s, sys: 1min 4s, total: 5min 11s
Wall time: 1min 53s

上級:残留接続

以前の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 3ms/step - loss: 0.0622 - mean_absolute_error: 0.1178

CPU times: user 1min 42s, sys: 26.7 s, total: 2min 9s
Wall time: 47.6 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.1340
LSTM           : 0.1224
Residual LSTM  : 0.1192

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

マルチステップモデル

前のセクションの単一出力モデルと複数出力モデルはどちらも、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 2ms/step - loss: 0.6285 - mean_absolute_error: 0.5007

png

このタスクは24時間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 2ms/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.3050

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.2208 - mean_absolute_error: 0.2826

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.2163 - mean_absolute_error: 0.2811

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 3ms/step - loss: 0.2139 - mean_absolute_error: 0.2850

png

高度:自己回帰モデル

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

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

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

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

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

RNN

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

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

layers.LSTMは、上位レベルのlayers.RNNでラップされた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.2268 - mean_absolute_error: 0.2988

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.2984
Dense   : 0.2760
Conv    : 0.2749
LSTM    : 0.2761
AR LSTM : 0.2905

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

次のステップ

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