![]() | ![]() | ![]() | ![]() |
このチュートリアルは、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の異なる機能が含まれています。これらは2003年から10分ごとに収集されました。効率を上げるために、2009年から2016年の間に収集されたデータのみを使用します。データセットのこのセクションは、FrançoisCholletが著書「DeepLearningwithPython」のために作成したものです。
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)
検査とクリーンアップ
次に、データセットの統計を見てください。
df.describe().transpose()
風速
目立つべきことの1つは、風速のmin
値wv (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]')
ただし、風向と風速の列を風ベクトルに変換すると、モデルが解釈しやすくなります。
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)
時間
同様に、 Date Time
列は非常に便利ですが、この文字列形式ではありません。それを秒に変換することから始めます:
timestamp_s = date_time.map(datetime.datetime.timestamp)
風向と同様に、秒単位の時間は有用なモデル入力ではありません。気象データであるため、毎日および毎年の周期性が明確です。周期性に対処する方法はたくさんあります。
それを使用可能な信号に変換する簡単なアプローチは、 sin
とcos
を使用して時刻を変換し、「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')
これにより、モデルは最も重要な周波数機能にアクセスできます。この場合、どの周波数が重要であるかを事前に知っていました。
知らなかった場合は、 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)')
データを分割する
トレーニング、検証、およびテストセットには、 (70%, 20%, 10%)
分割を使用します。分割する前にデータがランダムにシャッフルされていないことに注意してください。これには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)
データウィンドウ処理
このチュートリアルのモデルは、データからの連続サンプルのウィンドウに基づいて一連の予測を行います。
入力ウィンドウの主な機能は次のとおりです。
- 入力ウィンドウとラベルウィンドウの幅(タイムステップ数)
- それらの間の時間オフセット。
- 入力、ラベル、またはその両方として使用される機能。
このチュートリアルでは、さまざまなモデル(線形、DNN、CNN、RNNモデルを含む)を作成し、それらを両方に使用します。
- 単一出力、および複数出力の予測。
- シングルタイムステップおよびマルチタイムステップの予測。
このセクションでは、データウィンドウを実装して、これらすべてのモデルで再利用できるようにすることに焦点を当てます。
タスクとモデルのタイプに応じて、さまざまなデータウィンドウを生成することができます。ここではいくつかの例を示します。
たとえば、24時間先の単一の予測を行うには、24時間の履歴が与えられた場合、次のようなウィンドウを定義できます。
6時間の履歴が与えられた場合、1時間先の予測を行うモデルには、次のようなウィンドウが必要になります。
このセクションの残りの部分では、 WindowGenerator
クラスを定義します。このクラスは次のことができます。
- 上の図に示すように、インデックスとオフセットを処理します。
- 機能のウィンドウを
(features, labels)
ペアに分割します。 - 結果のウィンドウのコンテンツをプロットします。
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特徴ラベルのバッチに分割します。 WindowGenerator
がlabel_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()
他の列をプロットすることもできますが、ウィンドウw2
構成の例には、 T (degC)
列のラベルしかありません。
w2.plot(plot_col='p (mbar)')
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.0129 - mean_absolute_error: 0.0787
それはいくつかのパフォーマンスメトリクスを印刷しましたが、それらはモデルがどれほどうまく機能しているかについての感覚をあなたに与えません。
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
モデルに直接渡すことができます。これが可能なのは、入力とラベルのタイムステップ数が同じであり、ベースラインが入力を出力に転送するだけだからです。
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)
上記の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 [==============================] - 6s 4ms/step - loss: 0.2064 - mean_absolute_error: 0.2944 - val_loss: 0.0101 - val_mean_absolute_error: 0.0752 Epoch 2/20 1534/1534 [==============================] - 5s 3ms/step - loss: 0.0099 - mean_absolute_error: 0.0734 - val_loss: 0.0089 - val_mean_absolute_error: 0.0708 Epoch 3/20 1534/1534 [==============================] - 5s 3ms/step - loss: 0.0091 - mean_absolute_error: 0.0700 - val_loss: 0.0088 - val_mean_absolute_error: 0.0702 Epoch 4/20 1534/1534 [==============================] - 6s 4ms/step - loss: 0.0091 - mean_absolute_error: 0.0699 - val_loss: 0.0088 - val_mean_absolute_error: 0.0703 Epoch 5/20 1534/1534 [==============================] - 6s 4ms/step - loss: 0.0091 - mean_absolute_error: 0.0699 - val_loss: 0.0088 - val_mean_absolute_error: 0.0707 439/439 [==============================] - 1s 2ms/step - loss: 0.0088 - mean_absolute_error: 0.0707
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)
線形モデルの利点の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)
モデルが入力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 [==============================] - 7s 4ms/step - loss: 0.0701 - mean_absolute_error: 0.1282 - val_loss: 0.0090 - val_mean_absolute_error: 0.0719 Epoch 2/20 1534/1534 [==============================] - 7s 4ms/step - loss: 0.0081 - mean_absolute_error: 0.0653 - val_loss: 0.0069 - val_mean_absolute_error: 0.0592 Epoch 3/20 1534/1534 [==============================] - 7s 4ms/step - loss: 0.0075 - mean_absolute_error: 0.0621 - val_loss: 0.0069 - val_mean_absolute_error: 0.0607 Epoch 4/20 1534/1534 [==============================] - 7s 4ms/step - loss: 0.0073 - mean_absolute_error: 0.0610 - val_loss: 0.0067 - val_mean_absolute_error: 0.0590 Epoch 5/20 1534/1534 [==============================] - 7s 4ms/step - loss: 0.0070 - mean_absolute_error: 0.0597 - val_loss: 0.0065 - val_mean_absolute_error: 0.0571 Epoch 6/20 1534/1534 [==============================] - 7s 4ms/step - loss: 0.0069 - mean_absolute_error: 0.0593 - val_loss: 0.0067 - val_mean_absolute_error: 0.0590 Epoch 7/20 1534/1534 [==============================] - 7s 4ms/step - loss: 0.0069 - mean_absolute_error: 0.0592 - val_loss: 0.0064 - val_mean_absolute_error: 0.0564 Epoch 8/20 1534/1534 [==============================] - 7s 5ms/step - loss: 0.0069 - mean_absolute_error: 0.0588 - val_loss: 0.0074 - val_mean_absolute_error: 0.0628 Epoch 9/20 1534/1534 [==============================] - 7s 4ms/step - loss: 0.0067 - mean_absolute_error: 0.0580 - val_loss: 0.0067 - val_mean_absolute_error: 0.0587 439/439 [==============================] - 1s 3ms/step - loss: 0.0067 - mean_absolute_error: 0.0587
マルチステップ高密度
シングルタイムステップモデルには、入力の現在の値のコンテキストがありません。入力機能が時間の経過とともにどのように変化しているかを確認することはできません。この問題に対処するには、モデルは予測を行うときに複数のタイムステップにアクセスする必要があります。
baseline
、 linear
、およびdense
モデルは、各タイムステップを個別に処理しました。ここで、モデルは単一の出力を生成するために入力として複数の時間ステップを取ります。
3時間の入力と1時間のラベルのバッチを生成するWindowGenerator
を作成します。
Window
のshift
パラメータは、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.')
レイヤーを追加することで、複数入力ステップウィンドウで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.0062 - mean_absolute_error: 0.0559
conv_window.plot(multi_step_dense)
このアプローチの主な欠点は、結果のモデルが正確にこの形状の入力ウィンドウでのみ実行できることです。
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
れています。
変更点に注意してください。
-
layers.Flatten
と最初layers.Dense
置き換えられますlayers.Conv1D
。 - 畳み込みは時間軸を出力に保持するため、
layers.Reshape
は不要になりました。
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 [==============================] - 2s 3ms/step - loss: 0.0068 - mean_absolute_error: 0.0576
この違いconv_model
とmulti_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)
リカレントニューラルネットワーク
リカレントニューラルネットワーク(RNN)は、時系列データに適したニューラルネットワークの一種です。 RNNは時系列を段階的に処理し、時間ステップごとに内部状態を維持します。
詳細については、テキスト生成チュートリアルまたはRNNガイドをお読みください。
このチュートリアルでは、Long Short Term Memory( LSTM )と呼ばれるRNNレイヤーを使用します。
すべてのreturn_sequences
レイヤーの重要なコンストラクター引数はreturn_sequences
引数です。この設定では、2つの方法のいずれかでレイヤーを構成できます。
- デフォルトの
False
場合、レイヤーは最後のタイムステップの出力のみを返し、単一の予測を行う前にモデルに内部状態をウォームアップする時間を与えます。
-
True
の場合、レイヤーは入力ごとに出力を返します。これは次の場合に役立ちます。- RNNレイヤーのスタッキング。
- 複数のタイムステップで同時にモデルをトレーニングします。
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 [==============================] - 2s 4ms/step - loss: 0.0055 - mean_absolute_error: 0.0509
wide_window.plot(lstm_model)
パフォーマンス
このデータセットを使用すると、通常、各モデルは前のモデルよりもわずかに優れています。
x = np.arange(len(performance))
width = 0.3
metric_name = 'mean_absolute_error'
metric_index = lstm_model.metrics_names.index('mean_absolute_error')
val_mae = [v[metric_index] for v in val_performance.values()]
test_mae = [v[metric_index] for v in performance.values()]
plt.ylabel('mean_absolute_error [T (degC), normalized]')
plt.bar(x - 0.17, val_mae, width, label='Validation')
plt.bar(x + 0.17, test_mae, width, label='Test')
plt.xticks(ticks=x, labels=performance.keys(),
rotation=45)
_ = plt.legend()
for name, value in performance.items():
print(f'{name:12s}: {value[1]:0.4f}')
Baseline : 0.0852 Linear : 0.0686 Dense : 0.0581 Multi step dense: 0.0577 Conv : 0.0598 LSTM : 0.0516
マルチ出力モデル
これまでのモデルはすべて、単一の時間ステップで単一の出力特徴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.0883 - 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 [==============================] - 2s 3ms/step - loss: 0.0687 - mean_absolute_error: 0.1342
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 [==============================] - 2s 4ms/step - loss: 0.0614 - mean_absolute_error: 0.1198 CPU times: user 6min 10s, sys: 1min 25s, total: 7min 35s Wall time: 2min 55s
高度:残りの接続
以前の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.0623 - mean_absolute_error: 0.1180 CPU times: user 2min 25s, sys: 34.6 s, total: 3min Wall time: 1min 7s
パフォーマンス
これらのマルチ出力モデルの全体的なパフォーマンスは次のとおりです。
x = np.arange(len(performance))
width = 0.3
metric_name = 'mean_absolute_error'
metric_index = lstm_model.metrics_names.index('mean_absolute_error')
val_mae = [v[metric_index] for v in val_performance.values()]
test_mae = [v[metric_index] for v in performance.values()]
plt.bar(x - 0.17, val_mae, width, label='Validation')
plt.bar(x + 0.17, test_mae, width, label='Test')
plt.xticks(ticks=x, labels=performance.keys(),
rotation=45)
plt.ylabel('MAE (average over all outputs)')
_ = plt.legend()
for name, value in performance.items():
print(f'{name:15s}: {value[1]:0.4f}')
Baseline : 0.1638 Dense : 0.1349 LSTM : 0.1214 Residual LSTM : 0.1192
上記のパフォーマンスは、すべてのモデル出力で平均化されています。
マルチステップモデル
前のセクションの単一出力モデルと複数出力モデルの両方が、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
ベースライン
このタスクの単純なベースラインは、必要な数の出力タイムステップに対して最後の入力タイムステップを繰り返すことです。
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.6279 - mean_absolute_error: 0.5001
このタスクは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 3ms/step - loss: 0.4262 - mean_absolute_error: 0.3955
単発モデル
この問題に対する高レベルのアプローチの1つは、「シングルショット」モデルを使用することです。このモデルでは、シーケンス全体の予測が1つのステップで行われます。
これは、 layers.Dense
として効率的に実装できますOUT_STEPS*features
たlayers.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.2559 - mean_absolute_error: 0.3049
密集
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 3ms/step - loss: 0.2190 - mean_absolute_error: 0.2821
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 3ms/step - loss: 0.2171 - mean_absolute_error: 0.2814
RNN
反復モデルは、モデルが行っている予測に関連している場合、入力の長い履歴を使用することを学習できます。ここで、モデルは24時間内部状態を蓄積してから、次の24時間の単一の予測を行います。
このシングルショット形式では、LSTMは最後のタイムステップで出力を生成するだけでよいため、 return_sequences=False
設定します。
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.2170 - mean_absolute_error: 0.2856
高度:自己回帰モデル
上記のモデルはすべて、単一のステップで出力シーケンス全体を予測します。
場合によっては、モデルがこの予測を個々の時間ステップに分解すると役立つことがあります。次に、各モデルの出力を各ステップでそれ自体にフィードバックし、古典的なリカレントニューラルネットワークを使用したシーケンスの生成のように、前のモデルを条件として予測を行うことができます。
このスタイルのモデルの明らかな利点の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 7ms/step - loss: 0.2240 - mean_absolute_error: 0.2982
パフォーマンス
この問題のモデルの複雑さの関数として、収穫逓減が明らかにあります。
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()
このチュートリアルの前半のマルチ出力モデルのメトリックは、すべての出力機能にわたって平均されたパフォーマンスを示しています。これらのパフォーマンスは類似していますが、出力タイムステップ全体で平均化されています。
for name, value in multi_performance.items():
print(f'{name:8s}: {value[1]:0.4f}')
Last : 0.5157 Repeat : 0.3774 Linear : 0.2979 Dense : 0.2769 Conv : 0.2750 LSTM : 0.2797 AR LSTM : 0.2904
密なモデルから畳み込みモデルおよび反復モデルに移行することで達成される利益は、(もしあれば)ほんの数パーセントであり、自己回帰モデルのパフォーマンスは明らかに劣っています。したがって、これらのより複雑なアプローチは、この問題に取り組んでいる間は価値がないかもしれませんが、試さずに知る方法はありませんでした。これらのモデルは、問題に役立つ可能性があります。
次のステップ
このチュートリアルは、TensorFlowを使用した時系列予測の簡単な紹介でした。
- 詳細については、以下を参照してください。
- Scikit-Learn、Keras 、 TensorFlowを使用したハンズオン機械学習の第15章、第2版
- Pythonを使用したディープラーニングの第6章。
- ディープラーニングのためのUdacityのTensorFlow入門のレッスン8とエクササイズノート
- また、TensorFlowには任意の古典的な時系列モデルを実装できることを忘れないでください。このチュートリアルでは、TensorFlowの組み込み機能にのみ焦点を当てています。