質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.34%

kerasを用いて時系列予測を行いたい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,322

score 2

前提・実現したいこと

kerasのlstmを用いて,将来の交通量予測を行うモデルを構築しているのですが,
作成したモデルが意図した予測を行っているかがわかりません。
モデルでは,30分先の交通量を予測するようにしているのですが,
問題点として,
(1)明らかに精度が高い(本当に意図した時間先を予測しているのか?)
(2)予測したい時間を変えたとき明らかに不自然な予測結果が得られる
特に12時間先のデータを用いて予測した際は予測結果(下記図2枚目)が不自然になり,
1日先のデータでの予測結果は(下記図3枚目)逆に精度が高すぎるといった結果になりました。
私の作成したプログラムが正しく意図した将来を予測できているのか判断がつきません。

追記
下記図に関して,
青線が予測値
オレンジ線が観測値
になります。
また,ソースコードにおける「pred_time」が予測したい時間先になっており,
pred_time=30で,正解データが30分先の値であることを意味しています。
この意味として,ある1時点のデータを学習データ,その時点から30分後の時点のデータを正解データとして
損失関数を小さくするような学習をしてほしいと考えています。

再追記
使用している交通量のデータは,
100日間1分間隔(144000行)で記録された時系列のデータになります。

該当のソースコード

#1変数のみを入力

import os 
import numpy as np
import pandas as pd 
import matplotlib.pyplot as plt 

import_file = 'C:/Users/temp/Desktop/week_day1.csv'          #データファイルの読み込み

#事前の決定
test_rate = 0.2                     #データの分割割合
pred_time = 720                      #何分先を予測するかの決定(データ:1分間隔/1時間先を予測:pred_time = 60)
time = 1440

#データのインポート
df = pd.read_csv(import_file)

#必要なデータの抽出
date = df.iloc[:,0].values          #データの1列目を抽出
Pt = df.iloc[:, 3:4].values           #データの4列目を抽出


#データの分割
from sklearn.model_selection import train_test_split
date_1, date_2 = train_test_split(date, test_size=test_rate, shuffle=False)
train, test = train_test_split(Pt,test_size=test_rate,shuffle=False)


#教師ありデータに変換
x_train = train[:-pred_time]                 #データの下からpred_time分を削って学習データに
t_train = train[pred_time:]                  #データの上からpred_time分を削って正解データに
x_test = test[:-pred_time]                 #データの下からpred_time分を削ってテストデータに
t_test = test[pred_time:]                  #データの上からpred_time分を削って正解データに



#2次元配列へ変換
x_train = x_train.reshape(-1, 1)
t_train = t_train.reshape(-1, 1)
x_test  = x_test .reshape(-1, 1)
t_test = t_test .reshape(-1, 1)


#0-1への正規化の定義
from sklearn.preprocessing import MinMaxScaler

def scale(x_train, x_test , t_train, t_test ):

    # change type
    x_train = x_train.astype(np.float32)
    x_test =   x_test .astype(np.float32)
    t_train = t_train.astype(np.float32)
    t_test =   t_test .astype(np.float32)

    # scale inputs
    sclr = MinMaxScaler()
    x_train = sclr.fit_transform(x_train)
    x_test    = sclr.transform(x_test )
    t_train = sclr.transform(t_train)
    t_test = sclr.transform(t_test )

    return x_train, x_test , t_train, t_test , sclr

#正規化
x_train, x_test , t_train, t_test , sclr = scale(x_train, x_test , t_train, t_test )

#成形(change shape)
x_train = np.reshape(x_train.astype("float32"), (x_train.shape[0],1,x_train.shape[1] ))
x_test  = np.reshape(x_test .astype("float32"), (x_test .shape[0],1,x_test .shape[1] ))


#各種インポート
import keras 
from keras.models import Sequential, Model
from keras.layers import Input, Dense, Activation, LSTM
from keras.callbacks import EarlyStopping
import math
from sklearn.metrics import mean_squared_error, mean_absolute_error


#モデルの定義
Inputs = Input(shape = (x_train.shape[1], x_train.shape[2]), dtype='float32')
lstm = LSTM(128)(Inputs)
Outputs = Dense(1, activation='linear')(lstm)

model = Model(inputs=[Inputs], outputs=[Outputs])
model.compile(loss='mean_squared_error', optimizer='adam', metrics=['mean_squared_error'])

es = EarlyStopping(monitor='val_loss',
                   patience=5,
                   verbose=1)

#モデルの学習
history = model.fit(x_train, t_train, epochs=10, batch_size=256, verbose=1, shuffle=False, validation_split = 0.1, callbacks=[es])


#予測
pred = model.predict(x_test )         #予測値
obs = t_test                         #観測値


#正規化を戻す
obs_unscale=sclr.inverse_transform(obs)
pred_unscale=sclr.inverse_transform(pred)


#モデルの評価指標
RMSE = math.sqrt(mean_squared_error(pred_unscale,obs_unscale))
ape = mean_absolute_error(pred_unscale, obs_unscale)
MAPE =ape * 100/len(obs_unscale)
print("RMSE=",RMSE)
print("MAPE=",MAPE)


#配列 → DataFrame
dfd = pd.DataFrame(date_2)                  #日付
dfp = pd.DataFrame(pred_unscale)            #予測値
dfo = pd.DataFrame(obs_unscale)             #観測値


#最初の1日を削除
dfd = dfd[int(time):]
dfp = dfp[int(time):]
dfo = dfo[int(time - pred_time):]


#predとobsのDataFrameを一つにまとめる
df = pd.concat([dfd, dfp, dfo], axis=1)         #axis=1で横方向に連結


#DataFrameの行名の変更
df.columns = ['date_time', 'Pt_pred', 'Pt_obs']


#csvに書き出し
df.to_csv('C:/Users/temp/Desktop/5_30_output.csv')


30分後を予測
半日後を予測
1日後を予測

試したこと

ハイパーパラメータの調整,スケーリングの調整,予測先の変更等を試してみましたが,うまくいきません。
コード自体が見当はずれなのでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

予測以降の処理において、入力に対する正解の時間差pred_timeの扱いに間違っている部分があるようです。

以下のコードにおいて、predは予測値ですので、x_testをもとにpred_time後の予測となっています。
また、t_testはもともとpred_time後の正解値です。よって、predとobsの時間差は既に無い状態です。

pred = model.predict(x_test )         #予測値
obs = t_test

にもかかわらず、以下のコードにおいてobsだけをpred_timeずらしています。この行為は意味がなく、これが結果に影響していると考えられます。

#最初の1日を削除
dfd = dfd[int(time):]
dfp = dfp[int(time):]
dfo = dfo[int(time - pred_time):]

ただし、この後にconcatしていることで上記の「ずらし」が複雑に作用します。Pandasでは明示的にindex振り直しを指示しない限り、元のindexを維持する形で処理されます。よってconcatの時点で「ずらし」は補正されてしまいます。結局「ずらし」は、頭に余計にNanを作る効果しかもたらせていません。

擬似的な交通量を生成して、質問者様のコードを実行した結果、concat後のデータフレームは以下の形でした。(date_timeは日を単位とするfloatにしています)

       date_time  Pt_pred       Pt_obs
720          NaN      NaN  4211.390625
721          NaN      NaN  4310.817383
722          NaN      NaN  4800.063477
723          NaN      NaN  3912.796387
724          NaN      NaN  5271.840820
...          ...      ...          ...
28795  99.996528      NaN          NaN
28796  99.997222      NaN          NaN
28797  99.997917      NaN          NaN
28798  99.998611      NaN          NaN
28799  99.999306      NaN          NaN

上記にもかかわらず、質問者様の半日後の結果がズレてみえるのは、csv保存以降の処理で上記の、Pt_predとPt_obsの頭のズレが影響しているように思われます。この点はコードが明示されていないので推測ですが、少なくとも意味の無い「ズラし」が入っているため、これが原因の可能性が高いです。

実際、擬似データをもとに、質問者様のコードに

plt.plot(df['date_time'], df[['Pt_pred', 'Pt_obs']])
plt.show()


を付け加えると、半日後であっても、それなりに予測〜観測が一致したグラフが出ます。

なお、本来あるべきコードは以下です。

#最初の1日を削除
dfd = dfd[int(time + pred_time):].reset_index(drop=True)
dfp = dfp[int(time):].reset_index(drop=True)
dfo = dfo[int(time):].reset_index(drop=True)

#predとobsのDataFrameを一つにまとめる
df = pd.concat([dfd, dfp, dfo], axis=1).dropna() 


「ズラし」はdfdのところだけを後ろに、dfpとdfoは余計にズラしません。またズラしが無効にならないようにreset_indexでindexを振りなおします。concatでまとめる時に、nanがある行は捨てるほうがよいです。

なお、全体の結果を擬似データで確認すると、30分後の予測、1日後の予測は観測との差が少なく、半日後の予測はそれなりではあるものの観測との差はやや大きい結果になるでしょう。これは当たり前のことで、LSTMはRNNよりは改善されていますが、それでも、昔よりは最近のことにより強く影響されるモデルだからです。1日単位の周期性があるデータは、半日後よりも1日後の方が「今」と類似しています。そのため、半日後予測よりも1日後予測の方が、正確に予測しているようにみえるわけです。

100日もデータがあるわけですので、単純なLSTMを適用するのではなく、モデルを工夫してチューニングしてみることをオススメします。個人的には、周期性を考えて、30分後、1日後、5日後(五十日?の周期)、1週間後(平日週末の周期)、などをアンサンブルしてみるとよいかと思います。

以下補足として、私が擬似データを作成するために使ったコードを示します。

import numpy as np
from scipy.interpolate import interp1d

# 擬似的にdays日分の1分間隔の交通量を生成
days = 100
# まずは1日分作成、1日4つの時点のみ、質問者様のグラフを見て目分量でプロット
linear_interp = interp1d([0, 4, 7, 15, 24], [5000, 3000, 13000, 14000, 5000])
# 上記を直線で補間して1分間隔のデータにする
linear_results = linear_interp(np.linspace(0, 24, 24*60))[:-1]
# それを単純結合してdays日分にする
linear_results_days = np.array(linear_results.tolist() * days)
# ノイズを加える
noise = np.random.normal(0, 500, linear_results_days.shape)
Pt = linear_results_days + noise
# dateは1日を1.0のスケールにする
date = np.linspace(0, days, 24*60*days)[:-1]

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/10/24 18:24

    最後の方のpandasのconcatのところで、その前で頭を削っていますがdfoだけ削り幅変えていますね。pandasだとindexを持っているので、せっかく削り幅を変えても、元のindex同士が連結されることになります。削り幅変えた後が揃うわけではありません。その点はご理解されていますか?

    キャンセル

  • 2020/10/24 20:51

    csvをどう表示されているのか不明なので一部推測ですが、ほぼこれで当たりだろうという回答が作れました。ご確認ください。

    キャンセル

  • 2020/10/24 21:38

    こちらの質問が至らない部分が多い中とても丁寧な対応をしていただきありがとうございました。大変助かりました。
    回答いただいた内容確認してみようと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る