時間窓切り出し処理#
はじめに#
多変量時系列データを用いた予測モデルを作る時、未来の一時点での目的の値を予測するために、入力として一定の時間幅で切り取ったデータを用いることが多くあります。とくに、ある多変量時系列データから一変数の時系列データを予測するモデルの学習、予測を行う場合、元の多変量時系列データから、各時刻において切り出したデータを予め作成しておく必要がある場合があります。本章では、多変量時系列データがデータフレームの形で与えられた時に、一定の時間幅で切り出す処理について述べます。
どこで使われるか#
多変量時系列データを用いた回帰や分類のモデルを作成するときのデータとして使います。典型的な例としては、多変量の時系列データ \({\bf X} = ({\bf x}_0, {\bf x}_1, ...)\) と一変数の時系列データ \({\bf y} = (y_0, y_1, ...)\) があった時に、時刻 \((t-m)\)〜\((t-1)\) までの説明変数、すなわち \(({\bf x}_{t-m}, {\bf x}_{t-m+1}, ..., {\bf x}_{t-1})\) を用いて、 \(y_t\) を予測(回帰)するような場合が相当します。(Lakshminarayanan et al., 1997; 切通 et al., 2017)。1ステップ先の予測でなく、\(n\) ステップ先 \(y_{t+n-1}\)の予測にする場合もあります。ここで、\({\bf x}_t\) は時刻 \(t\) における多変量の説明変数を表す縦ベクトル、\(y_t\) は時刻 \(t\) における目的変数の値(スカラー)を表します。
データ準備#
酢酸ビニルプラントのシミュレータ (VAMSim)のデータを用いて処理の説明をしていきます。まず、説明変数(多変量時系列: dfX
)、目的変数(一変数時系列: dfY
)をそれぞれ用意します。各行間の時間間隔は定数となっている前提で進めます。この間隔は必ずしも1秒、1分などきりのいい値になっているとは限らないので、以降、各時刻のことを時刻ステップ(または単にステップ)と呼び、何時刻ステップ目か t
で表します。
# データ読み込み
import pandas as pd
import numpy as np
fname = '../data/2020413.csv'
df = pd.read_csv(fname, index_col=0, parse_dates=True, encoding='cp932')
dfY = df.iloc[:, [0]]
dfX = df.iloc[:, 1:]
display(dfX.head())
print("時刻ステップの間隔(各行間の時間間隔): {}".format(dfX.index[1]-dfX.index[0]))
print("データサイズ")
print("[説明変数] サンプル数: {}, カラム数: {}".format(*dfX.shape))
print("[目的変数] サンプル数: {}, カラム数: {}".format(*dfY.shape))
気化器圧力_SV | 気化器圧力_MV | 気化器液面レベル_PV | 気化器液面レベル_SV | 気化器液面レベル_MV | 気化器温度_PV | 気化器ヒータ出口温度_PV | 気化器ヒータ出口温度_SV | 気化器ヒータ熱量_MV | 反応器出口温度_PV | ... | 反応器流入組成(CO2)_PV | 反応器流入組成(C2H4)_PV | 反応器流入組成(C2H6)_PV | 反応器流入組成(VAc)_PV | 反応器流入組成(H2O)_PV | 反応器流入組成(HAc)_PV | セパレータ蒸気排出量_MV (Fixed) | アブソーバスクラブ流量_MV (Fixed) | アブソーバ還流流量_MV (Fixed) | 気化器液体流入量_MV (Fixed) | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2020-04-13 00:00:00 | 127.883920 | 18.728267 | 0.700204 | 0.7 | 21876.987772 | 120.159558 | 151.274003 | 150.0 | 9008.540694 | 160.387755 | ... | 0.006273 | 0.585715 | 0.214016 | 0.001370 | 0.008524 | 0.109444 | 16.1026 | 15.1198 | 0.766 | 2.1924 |
2020-04-13 00:00:01 | 127.807691 | 18.728267 | 0.700865 | 0.7 | 21876.987772 | 120.072575 | 151.218620 | 150.0 | 9008.540694 | 160.368978 | ... | 0.006286 | 0.585342 | 0.213945 | 0.001368 | 0.008512 | 0.109441 | 16.1026 | 15.1198 | 0.766 | 2.1924 |
2020-04-13 00:00:02 | 127.991074 | 19.079324 | 0.699740 | 0.7 | 21918.359526 | 120.305327 | 151.201067 | 150.0 | 8886.272509 | 160.440578 | ... | 0.006281 | 0.584384 | 0.215043 | 0.001373 | 0.008541 | 0.109871 | 16.1026 | 15.1198 | 0.756 | 2.1924 |
2020-04-13 00:00:03 | 127.990697 | 18.772228 | 0.699970 | 0.7 | 21892.166478 | 120.219488 | 151.220816 | 150.0 | 8806.115271 | 160.382615 | ... | 0.006289 | 0.584467 | 0.214126 | 0.001372 | 0.008573 | 0.109775 | 16.1026 | 15.1198 | 0.746 | 2.1924 |
2020-04-13 00:00:04 | 127.903788 | 18.605173 | 0.700939 | 0.7 | 21885.688813 | 120.302267 | 151.165624 | 150.0 | 8750.295310 | 160.378797 | ... | 0.006313 | 0.584698 | 0.214002 | 0.001374 | 0.008547 | 0.109868 | 16.1026 | 15.1198 | 0.766 | 2.1924 |
5 rows × 90 columns
時刻ステップの間隔(各行間の時間間隔): 0 days 00:00:01
データサイズ
[説明変数] サンプル数: 86401, カラム数: 90
[目的変数] サンプル数: 86401, カラム数: 1
基本編1: 説明変数の時間窓切り出し処理#
ここでは、多変量時系列データ dfX
(nrow
列 ncol
行)から、各行においてサイズ M
の窓でデータを切り出します。ここでは例として M
= 60 ステップ(60秒) とします。
1回切り出したデータは M
x ncol
の行列となる。これを全ステップ繰り返すと nrow - M
枚の行列が得られ、これらを重ねると、 (nrow-M)
x M
x ncol
の3次元配列が得られます。(図1)
pandas では3次元配列を扱うための pandas.Panel
が存在しているが、最近は非推奨のようなので、 numpy.array
でデータを取り扱います。
以下のコードでは窓幅 M
ステップを例として見ていきます。
M = 60
nrow, ncol = dfX.shape
nsample = nrow - M + 1 # 出力のサンプル数
Xm = np.zeros((nsample, M, ncol), dtype=float) # 出力の格納先
X = dfX.values
for i in range(nsample):
st = i
en = i+M
Xm[i, :, :] = X[st:en, :]
# index も一応対応させておく
idx_Xm = dfX.index[M-1:]
print("出力のサイズ: {} x {} x {}".format(*(Xm.shape)))
print("インデクス Xm: {} - {}".format(idx_Xm[0], idx_Xm[-1]))
出力のサイズ: 86342 x 60 x 90
インデクス Xm: 2020-04-13 00:00:59 - 2020-04-14 00:00:00
基本編2: 目的変数を含めた時間窓切り出し処理#
基本編1で幅 M
の窓を切り出すことが出来ました。最終的にはこのデータに対して、回帰モデルや分類モデルを適用していきます。典型的なタスクとして、ある変数の未来の値を予測したいというものがあります。ここでは、時刻ステップ t-M+1
~ t
までの値(説明変数)を入力して、N
ステップ先である t+N
の dfY
(目的変数)の値の予測値を出力するようなモデルを想定します。基本編1と同様の方法で説明変数を切り出し、行番号が合った説明変数と目的変数のセットを作成します。基本編1に比べて使える行数が N
分だけ減ります。(図2)
以下、N
= 60 (60秒) の場合を例を見てみます。
M, N = 60, 60
nrow, ncol = dfX.shape
nsample = nrow - M + 1 - N # 出力のサンプル数
# 説明変数
Xmn = np.zeros((nsample, M, ncol), dtype=float) # 出力の格納先
X = dfX.values
for i in range(nsample):
st = i
en = i+M
Xmn[i, :, :] = X[st:en, :]
# index も一応対応させておく
idx_Xmn = dfX.index[M-1:-N]
# 目的変数
tmpY = dfY.iloc[M+N-1:,:]
Ymn = tmpY.values
# index も一応対応させておく
idx_Ymn = tmpY.index
print("説明変数 X")
print("出力のサイズ: {} x {} x {}".format(*(Xmn.shape)))
print("インデクス: {} - {}".format(idx_Xmn[0], idx_Xmn[-1]))
print("目的変数 Y")
print("出力のサイズ: {} x {}".format(*(Ymn.shape)))
print("インデクス: {} - {}".format(idx_Ymn[0], idx_Ymn[-1]))
説明変数 X
出力のサイズ: 86282 x 60 x 90
インデクス: 2020-04-13 00:00:59 - 2020-04-13 23:59:00
目的変数 Y
出力のサイズ: 86282 x 1
インデクス: 2020-04-13 00:01:59 - 2020-04-14 00:00:00
応用編: 説明変数の複数行を一行に集約する場合#
元データのカラム数が多かったり、考慮すべき M
のサイズが大きい場合、切り出した行列のサイズが大きすぎて、メモリーサイズやCPU時間の制約から学習モデルの適用ないし十分な実験が困難となる場合があります。特に、1ステップ単位の細かい動きを反映したモデルが実用上(お客さま要望など)求められられない場合などは、窓内のデータを荒く取り直して切り出した行列のサイズを小さくする場合があります。最も単純なやり方としては、長さM
ステップ分の幅をL
ステップごとに短冊状に切って分離し、各カラムについて短冊状領域内の値の平均値で代表させるやり方があります。
SQLなどを用いてデータベースで処理を行ったことがある方には、「基本編1(または2)で切り出された窓を、さらに時間方向にLごとにグルーピングして、平均関数を使って集約する」という言い方をした方がわかりやすいかもしれません。また、深層学習においては、おもに畳み込みニューラルネットワークなどで使われているプーリング(平均値を使う場合には平均プーリング)と呼ばれる機構がこれに相当します [Yang2015] 。
この処理により、各窓のサイズは M
→ M/L
になります。ここでは単純化のため、L
は M
の約数となっている(M/L
が整数)ことを前提とします。(図3)
以下、L
= 10 (10秒) の場合の例を見てみます。
L, M, N = 10, 60, 60
nrow, ncol = dfX.shape
nsample = nrow - M + 1 - N # 出力のサンプル数(基本編2と同じ)
nsplit, rem = divmod(M, L)
if rem != 0:
print("LはMの約数に設定してください")
raise()
#説明変数
Xlmn = np.zeros((nsample, nsplit, ncol), dtype=float) # 出力の格納先
X = dfX.values
for i in range(nsample):
st = i
en = i+M
tmpX = X[st:en, :]
# L ごとに切って配列の次元を増やしたあと平均して次元をもとに戻す
Xlmn[i,:,:] = tmpX.reshape(nsplit, L, ncol).mean(axis=1)
# index も一応対応させておく
idx_Xlmn = dfX.index[M-1:-N]
# 目的変数は基本編2と同じ
tmpY = dfY.iloc[M+N-1:,:]
Ylmn = tmpY.values
# indexも一応対応させておく
idx_Ylmn = tmpY.index
print("説明変数 X")
print("出力のサイズ: {} x {} x {}".format(*(Xlmn.shape)))
print("インデクス: {} - {}".format(idx_Xlmn[0], idx_Xlmn[-1]))
print("目的変数 Y")
print("出力のサイズ: {} x {}".format(*(Ylmn.shape)))
print("インデクス: {} - {}".format(idx_Ylmn[0], idx_Ylmn[-1]))
説明変数 X
出力のサイズ: 86282 x 6 x 90
インデクス: 2020-04-13 00:00:59 - 2020-04-13 23:59:00
目的変数 Y
出力のサイズ: 86282 x 1
インデクス: 2020-04-13 00:01:59 - 2020-04-14 00:00:00
発展編:窓をスキップさせる場合#
基本編1、2、および応用編でで示した時間窓切り出し処理で作成したデータの時刻ステップ幅(コード例では元データと同じステップ幅の1秒)と、予測モデルを学習したり、テストしたりするときの時刻ステップ幅が異なる場合があります。例えば、
運用時(デプロイ後)に想定される予測周期が時刻ステップ幅と異なる(例えば、データの時刻ステップは1秒だが、予測の周期は1分)場合、運用時と同じ条件でテストをしたい。
学習時、データ量が多くなりすぎてしまうので、減らしたい。
など。
このような場合、幅M
の時間窓を移動させるときに、S
ステップずつスキップします。(図4)
以下の例ではスキップ幅 S
= 30 (30秒) の場合で見ていきます。
L, M, N, S = 10, 60, 60, 30
nrow, ncol = dfX.shape
nsample_org = nrow - M + 1 - N # 元データの切り出しに使われる範囲のサイズ
nsample = (nrow - M - N ) // S + 1 # 出力のサンプル数
nsplit, rem = divmod(M, L)
if rem != 0:
print("LはMの約数に設定してください")
raise()
#説明変数
Xlmns = np.zeros((nsample, nsplit, ncol), dtype=float) # 出力の格納先
X = dfX.values
for i, st in enumerate(range(0, nsample_org, S)):
en = st + M
tmpX = X[st:en, :]
# L ごとに切って配列の次元を増やしたあと平均して次元をもとに戻す
Xlmns[i,:,:] = tmpX.reshape(nsplit, L, ncol).mean(axis=1)
# index も一応対応させておく
idx_Xlmns = dfX.index[M-1:-N:S]
# 目的変数は基本編2と同じ
tmpY = dfY.iloc[M+N-1::S,:]
Ylmns = tmpY.values
# indexも一応対応させておく
idx_Ylmns = tmpY.index
print("説明変数 X")
print("出力のサイズ: {} x {} x {}".format(*(Xlmns.shape)))
print("インデクス: {} - {}".format(idx_Xlmns[0], idx_Xlmns[-1]))
print("目的変数 Y")
print("出力のサイズ: {} x {}".format(*(Ylmns.shape)))
print("インデクス: {} - {}".format(idx_Ylmns[0], idx_Ylmns[-1]))
説明変数 X
出力のサイズ: 2877 x 6 x 90
インデクス: 2020-04-13 00:00:59 - 2020-04-13 23:58:59
目的変数 Y
出力のサイズ: 2877 x 1
インデクス: 2020-04-13 00:01:59 - 2020-04-13 23:59:59
利用時#
説明変数は、(サンプル x 窓幅M x カラム)の形の3次元配列の形をしております。通常の回帰分析を行う場合、(サンプル x カラム)の形に直す必要があります。(フラット化)
フラット化の方法は2通り考えられます。表示したりする時に便利な方を選べば良いと思います。どちらを選ぶかは余り重要なところではありませんが、フラット化した後に2次元に戻す場合には同じ方式で戻さないとデータがおかしくなってしまいますので注意が必要です。
1つ目はカラム方向(横)を優先に取っていく方法(図5)
nrow, nwin, ncol = Xlmns.shape
print("Data size")
print("nrow={}, window_size={}, ncol={}".format(nrow, nwin, ncol))
Xlmns_flat = Xlmns.reshape(nrow, nwin*ncol)
print("Output data size")
print("nrow={}, ncol={}".format(*(Xlmns_flat.shape)))
Data size
nrow=2877, window_size=6, ncol=90
Output data size
nrow=2877, ncol=540
# 戻し方
print("戻すと…")
tmpX = Xlmns_flat.reshape(nrow, nwin, ncol)
# 戻ったか確認
print("フラット化前と値が異なる要素数 = {}".format((tmpX != Xlmns).sum()))
戻すと…
フラット化前と値が異なる要素数 = 0
# インデクスも揃えて DataFrame化
cols = dfX.columns
col_array = np.array(
[["{}-{:d}".format(c, i+1) for c in cols] for i in range(nwin)]
)
cols_new = col_array.reshape(-1).tolist()
dfXlmns = pd.DataFrame(Xlmns_flat, index=idx_Xlmns, columns=cols_new)
dfXlmns.head()
気化器圧力_SV-1 | 気化器圧力_MV-1 | 気化器液面レベル_PV-1 | 気化器液面レベル_SV-1 | 気化器液面レベル_MV-1 | 気化器温度_PV-1 | 気化器ヒータ出口温度_PV-1 | 気化器ヒータ出口温度_SV-1 | 気化器ヒータ熱量_MV-1 | 反応器出口温度_PV-1 | ... | 反応器流入組成(CO2)_PV-6 | 反応器流入組成(C2H4)_PV-6 | 反応器流入組成(C2H6)_PV-6 | 反応器流入組成(VAc)_PV-6 | 反応器流入組成(H2O)_PV-6 | 反応器流入組成(HAc)_PV-6 | セパレータ蒸気排出量_MV (Fixed)-6 | アブソーバスクラブ流量_MV (Fixed)-6 | アブソーバ還流流量_MV (Fixed)-6 | 気化器液体流入量_MV (Fixed)-6 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2020-04-13 00:00:59 | 127.909983 | 18.781628 | 0.699975 | 0.7 | 21877.899469 | 120.192375 | 151.214941 | 150.0 | 8783.176376 | 160.337144 | ... | 0.006254 | 0.585205 | 0.213673 | 0.001376 | 0.008559 | 0.109978 | 16.1026 | 15.1198 | 0.768 | 2.1924 |
2020-04-13 00:01:29 | 127.883110 | 18.707861 | 0.699761 | 0.7 | 21851.361788 | 120.271352 | 150.985248 | 150.0 | 8673.521741 | 159.715623 | ... | 0.006250 | 0.584887 | 0.213617 | 0.001376 | 0.008582 | 0.110158 | 16.1026 | 15.1198 | 0.774 | 2.1924 |
2020-04-13 00:01:59 | 128.127225 | 18.676354 | 0.699774 | 0.7 | 21867.921023 | 120.388793 | 150.830708 | 150.0 | 8694.259313 | 159.574386 | ... | 0.006229 | 0.584836 | 0.213814 | 0.001374 | 0.008564 | 0.110104 | 16.1026 | 15.1198 | 0.779 | 2.1924 |
2020-04-13 00:02:29 | 128.316836 | 18.721889 | 0.699782 | 0.7 | 21830.585917 | 120.340812 | 150.693544 | 150.0 | 8711.633173 | 159.559703 | ... | 0.006217 | 0.584621 | 0.213844 | 0.001373 | 0.008574 | 0.110192 | 16.1026 | 15.1198 | 0.775 | 2.1924 |
2020-04-13 00:02:59 | 128.425931 | 18.642201 | 0.699758 | 0.7 | 21832.974000 | 120.432407 | 150.649890 | 150.0 | 8707.040541 | 159.609768 | ... | 0.006205 | 0.584788 | 0.214105 | 0.001370 | 0.008570 | 0.110007 | 16.1026 | 15.1198 | 0.771 | 2.1924 |
5 rows × 540 columns
これで、サンプル x カラム の説明変数データを作ることが出来ました。
もう一つは窓方向(縦)を優先に取っていく方法(図6) reshape()
関数にオプション order='F'
を追加することで、縦方向にデータを取得してくことができます。
nrow, nwin, ncol = Xlmns.shape
print("Data size")
print("nrow={}, window_size={}, ncol={}".format(nrow, nwin, ncol))
#
Xlmns_flat = Xlmns.reshape(nrow, nwin*ncol, order='F')
print("Output data size")
print("nrow={}, ncol={}".format(*(Xlmns_flat.shape)))
Data size
nrow=2877, window_size=6, ncol=90
Output data size
nrow=2877, ncol=540
# 戻し方
print("戻すと…")
tmpX = Xlmns_flat.reshape(nrow, nwin, ncol, order='F')
# 戻ったか確認
print("フラット化前と値が異なる要素数 = {}".format((tmpX != Xlmns).sum()))
戻すと…
フラット化前と値が異なる要素数 = 0
# インデクスも揃えて DataFrame化
cols = dfX.columns
col_array = np.array(
[["{}-{:d}".format(c, i+1) for i in range(nwin)] for c in cols]
)
cols_new = col_array.reshape(-1).tolist()
dfXlmns = pd.DataFrame(Xlmns_flat, index=idx_Xlmns, columns=cols_new)
dfXlmns.head()
気化器圧力_SV-1 | 気化器圧力_SV-2 | 気化器圧力_SV-3 | 気化器圧力_SV-4 | 気化器圧力_SV-5 | 気化器圧力_SV-6 | 気化器圧力_MV-1 | 気化器圧力_MV-2 | 気化器圧力_MV-3 | 気化器圧力_MV-4 | ... | アブソーバ還流流量_MV (Fixed)-3 | アブソーバ還流流量_MV (Fixed)-4 | アブソーバ還流流量_MV (Fixed)-5 | アブソーバ還流流量_MV (Fixed)-6 | 気化器液体流入量_MV (Fixed)-1 | 気化器液体流入量_MV (Fixed)-2 | 気化器液体流入量_MV (Fixed)-3 | 気化器液体流入量_MV (Fixed)-4 | 気化器液体流入量_MV (Fixed)-5 | 気化器液体流入量_MV (Fixed)-6 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2020-04-13 00:00:59 | 127.909983 | 127.877539 | 127.852105 | 127.883110 | 127.970990 | 128.094478 | 18.781628 | 18.704659 | 18.770864 | 18.707861 | ... | 0.763 | 0.766 | 0.772 | 0.768 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 |
2020-04-13 00:01:29 | 127.883110 | 127.970990 | 128.094478 | 128.127225 | 128.181010 | 128.250331 | 18.707861 | 18.694039 | 18.734425 | 18.676354 | ... | 0.768 | 0.771 | 0.773 | 0.774 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 |
2020-04-13 00:01:59 | 128.127225 | 128.181010 | 128.250331 | 128.316836 | 128.315941 | 128.379271 | 18.676354 | 18.756099 | 18.656986 | 18.721889 | ... | 0.774 | 0.775 | 0.773 | 0.779 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 |
2020-04-13 00:02:29 | 128.316836 | 128.315941 | 128.379271 | 128.425931 | 128.509006 | 128.564964 | 18.721889 | 18.751977 | 18.713842 | 18.642201 | ... | 0.779 | 0.778 | 0.778 | 0.775 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 |
2020-04-13 00:02:59 | 128.425931 | 128.509006 | 128.564964 | 128.540683 | 128.612384 | 128.586519 | 18.642201 | 18.771358 | 18.668903 | 18.777656 | ... | 0.775 | 0.774 | 0.774 | 0.771 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 | 2.1924 |
5 rows × 540 columns
関数化#
基本編から発展編までで説明してきた時間窓切り出し処理は、多変量時系列データを用いたモデリングを行う場合によく使われますので、関数化しておきましょう。ここでは、 time_window.py
というファイルで定義します。time_window_transform()
関数は、numpy.array 形式の説明変数、目的変数の行列を入力すると、時間窓切り出し処理を行い、結果を出力します。説明変数のみ、目的変数のみの場合はそれぞれ time_window_x()
, time_window_y()
を使います。
# time_window.py
import numpy as np
# L が M の約数になっていなかった場合は例外処理に回す
class LMUndivisibleError(Exception):
pass
def time_window_x(x, l, m, n, s):
nrow, ncol = x.shape
nsample_org = nrow - m + 1 - n # 元データの切り出しに使われる範囲のサイズ
nsample = (nrow - m - n ) // s + 1 # 出力のサンプル数
# l が m の約数になっていなかった場合は例外を発生
nsplit, rem = divmod(m, l)
if rem != 0:
raise LMUndivisibleError
#説明変数
x_transformed = np.zeros((nsample, nsplit, ncol), dtype=float) # 出力の格納先
for i, st in enumerate(range(0, nsample_org, s)):
en = st + m
tmpX = x[st:en, :]
# L ごとに切って配列の次元を増やしたあと平均して次元をもとに戻す
x_transformed[i,:,:] = tmpX.reshape(nsplit, l, ncol).mean(axis=1)
return x_transformed
def time_window_y(y, m, n, s):
res = y[n+m-1::s,:].reshape((-1,1))
return res
def time_window_transform(x, y, l, m, n, s):
if m % l != 0:
raise LMUndivisibleError
x_transformed = time_window_x(x, l, m, n, s)
nrow = x_transformed.shape[0]
y_transformed = time_window_y(y, m, n, s)[:nrow,:]
return x_transformed, y_transformed
以下に、説明変数および目的変数に対して、本稿で説明した時間窓切り出しおよびフラット化の処理をtime_window.py
を用いて行うコードの例を示します。ここでは、VAMSimのデータセットに対して、時間窓幅(M
)を60ステップ、集約の時間幅(L
)を10ステップ、スキップ幅(S
)を60ステップとして、60ステップ後(N
)の予測を行うケースを想定します。
from time_window import time_window_transform
L, M, N, S = 10, 60, 60, 30
X = dfX.values
Y = dfY.values
# 入力ファイルXのサイズ
print("時間窓切り出し処理前データサイズ")
print("[説明変数] サンプル数={}, カラム数 ={}".format(*X.shape))
print("[目的変数] サンプル数={}\n".format(X.shape[0]))
Xlmns, Ylmns = time_window_transform(X, Y, l=L, m=M, n=N, s=S)
nrow, nwin, ncol = Xlmns.shape
print("時間窓切り出し処理後データサイズ")
print("[説明変数]サンプル数={}, windowサイズ={}, カラム数={}".format(*Xlmns.shape))
print("[目的変数]サンプル数={}\n".format(Ylmns.shape[0]))
# フラット化
Xlmns_flat = Xlmns.reshape(nrow, nwin*ncol)
print("説明変数フラット化後データサイズ")
print("サンプル数={}, カラム数={}".format(*(Xlmns_flat.shape)))
時間窓切り出し処理前データサイズ
[説明変数] サンプル数=86401, カラム数 =90
[目的変数] サンプル数=86401
時間窓切り出し処理後データサイズ
[説明変数]サンプル数=2877, windowサイズ=6, カラム数=90
[目的変数]サンプル数=2877
説明変数フラット化後データサイズ
サンプル数=2877, カラム数=540
実際に回帰分析を用いて予測モデルを学習する場合は、ここで最終的に得られた Xlmns_flat
を説明変数、 Ylmns
を目的変数として扱います。
小括#
多変量時系列データを用いた回帰・分類などを行う場合のデータ切り出し方法について述べました。本稿と類似の処理はさまざまな理論や応用分野で使われており、例えば、データエンジニアリングの分野では、時間窓を移動させながらデータをグルーピング・集約する処理を Sliding window aggregation と呼ぶことがあります [Arasu2006][Bou2021]。また、一次元(テーブルデータにおける1カラム分)の時系列データについての言及が主となりますが、動的システムなどの研究分野では、時系列データの注目する時刻 \(t\) からさかのぼり、時間的に等間隔に固定数の値を抽出してベクトルを作る操作ことを、Time delay embedding (もしくは delay embedding)と呼びます [Abarbanel1994]。
参考文献#
[Lakshminarayanan1997] Lakshminarayanan, S., Shah, S. L., & Nandakumar, K. (1997). Modeling and control of multivariable processes: Dynamic PLS approach. AIChE Journal, 43(9), 2307-2322.
[切通2017] 切通恵介, 泉谷知範 (1997). 機械学習を用いた工場機器の故障予測. DEIM2017.
[Yang2015] Yang, J., Nguyen, M. N., San, P. P., Li, X. L., & Krishnaswamy, S. (2015, June). Deep convolutional neural networks on multichannel time series for human activity recognition. In Twenty-fourth international joint conference on artificial intelligence.
[Arasu2006] Arasu, A., Babu, S., & Widom, J. (2006). The CQL continuous query language: semantic foundations and query execution. The VLDB Journal, 15(2), 121-142.
[Bou2021] Bou, S., Kitagawa, H., & Amagasa, T. (2021). Cpix: real-time analytics over out-of-order data streams by incremental sliding-window aggregation. IEEE Transactions on Knowledge and Data Engineering, 34(11), 5239-5250.
[Abarbanel1994] Abarbanel, H. D., Carroll, T. A., Pecora, L. M., Sidorowich, J. J., & Tsimring, L. S. (1994). Predicting physical variables in time-delay embedding. Physical Review E, 49(3), 1840.