GluonTS 多変量時系列の評価

import numpy as np
from matplotlib import pyplot as plt

from gluonts.dataset.common import ListDataset
from gluonts.model.deepar import DeepAREstimator
from gluonts.distribution.multivariate_gaussian import MultivariateGaussianOutput
from gluonts.trainer import Trainer

N = 20  # number of time series
T = 100  # number of timesteps
prediction_length = 10
freq = '1H'

custom_datasetx = np.random.normal(size=(N, 2, T))
custom_datasetx[:,1,:] = custom_datasetx[:,1,:]*10

train_ds = ListDataset(
    [
        {'target': x, 'start': '2019-01-01'}
        for x in custom_datasetx[0:17, :, :]
    ],
    freq=freq,
    one_dim_target=False,
)

test_ds = ListDataset(
    [
        {'target': x, 'start': '2019-01-01'}
        for x in custom_datasetx[17:, :, :]
    ],
    freq=freq,
    one_dim_target=False,
)

estimator = DeepAREstimator(
    prediction_length=prediction_length,
    freq=freq,
    trainer=Trainer(epochs=5),
    distr_output=MultivariateGaussianOutput(dim=2),
)

predictor = estimator.train(train_ds)

from gluonts.evaluation.backtest import make_evaluation_predictions

forecast_it, ts_it = make_evaluation_predictions(
    dataset=test_ds,  
    predictor=predictor,  
    num_samples=100
    )

#from gluonts.evaluation import Evaluator
from gluonts.evaluation import MultivariateEvaluator

evaluator = MultivariateEvaluator(quantiles=[0.1, 0.5, 0.9])
agg_metrics, item_metrics = evaluator(ts_it, forecast_it, num_series=len(test_ds))

#print(json.dumps(agg_metrics, indent=4))
#print(item_metrics)

item_metrics.plot(x='MSIS', y='MASE', kind='scatter', c=item_metrics.index, cmap='Accent')
plt.grid(which="both")
plt.show()

多変量時系列・GluonTSの動作確認(2019年11月29日)

はじめに

GluonTS 0.4.2が公開されているので過去のコードで動作確認してみた。

環境

Windows10 Pro
NVIDIA GeForce GTX1080
CUDA 10.1
Python 3.6.8

GluonTSのインストール

pipでGluonTSをインストールした。

バージョン確認

boto3==1.10.28
botocore==1.13.28
certifi==2019.11.28
chardet==3.0.4
cycler==0.10.0
dataclasses==0.7
docutils==0.15.2
gluonts==0.4.2
graphviz==0.8.4
holidays==0.9.11
idna==2.6
jmespath==0.9.4
kiwisolver==1.1.0
matplotlib==3.1.2
mxnet-cu101==1.6.0b20191125
numpy==1.16.5
pandas==0.25.3
pydantic==1.2
pyparsing==2.4.5
python-dateutil==2.8.0
pytz==2019.3
requests==2.18.4
s3transfer==0.2.1
six==1.13.0
tqdm==4.39.0
ujson==1.35
urllib3==1.22

サンプルコード

以下のコードが問題なく実行できた。

import numpy as np
from matplotlib import pyplot as plt

from gluonts.dataset.common import ListDataset
from gluonts.model.deepar import DeepAREstimator
from gluonts.distribution.multivariate_gaussian import MultivariateGaussianOutput
from gluonts.trainer import Trainer

N = 20  # number of time series
T = 100  # number of timesteps
prediction_length = 10
freq = '1H'

custom_datasetx = np.random.normal(size=(N, 2, T))
custom_datasetx[:,1,:] = custom_datasetx[:,1,:]*10

train_ds = ListDataset(
    [
        {'target': x, 'start': '2019-01-01'}
        for x in custom_datasetx[0:19, :, :]
    ],
    freq=freq,
    one_dim_target=False,
)

test_ds = ListDataset(
    [
        {'target': x, 'start': '2019-01-01'}
        for x in custom_datasetx[19:, :, :]
    ],
    freq=freq,
    one_dim_target=False,
)

estimator = DeepAREstimator(
    prediction_length=prediction_length,
    freq=freq,
    trainer=Trainer(epochs=5),
    distr_output=MultivariateGaussianOutput(dim=2),
)

predictor = estimator.train(train_ds)

from gluonts.evaluation.backtest import make_evaluation_predictions

forecast_it, ts_it = make_evaluation_predictions(
    dataset=test_ds,  
    predictor=predictor,  
    num_samples=100
    )

for x, y  in zip(ts_it, forecast_it):
    for i in range(2):
        plt.subplot(2,1,i+1)
        x[i].plot()
        y.copy_dim(i).plot(color='g', prediction_intervals=(50.0, 90.0))

plt.show()

追記

Windows7にもインストールしてみた。問題なく動作している。

Windows 7 Professional
GPUなし
Python 3.7.4
boto3==1.10.28
botocore==1.13.28
certifi==2019.11.28
chardet==3.0.4
cycler==0.10.0
docutils==0.15.2
gluonts==0.4.2
graphviz==0.8.4
holidays==0.9.11
idna==2.6
jmespath==0.9.4
kiwisolver==1.1.0
matplotlib==3.1.2
mxnet==1.6.0b20191125
numpy==1.16.5
pandas==0.25.3
pydantic==1.2
pyparsing==2.4.5
python-dateutil==2.8.0
pytz==2019.3
requests==2.18.4
s3transfer==0.2.1
six==1.13.0
tqdm==4.39.0
ujson==1.35
urllib3==1.22

PyQt5を使ってみる(2) ラジオボタン

環境

Windows 10 Pro(CPU only)
Python 3.7.5

バージョン

cycler==0.10.0
kiwisolver==1.1.0
matplotlib==3.1.1
numpy==1.17.4
pandas==0.25.3
pyparsing==2.4.5
PyQt5==5.13.2
PyQt5-sip==12.7.0
python-dateutil==2.8.1
pytz==2019.3
six==1.13.0

本文

CSVファイルを読み込んでラジオボタンの初期値にセットする。
冗長なコードだがシンプルで分かりやすく書いた。

  • コード
import sys
import pandas as pd
from PyQt5 import QtCore
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import *

'''
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import pyplot as plt
import numpy as np
'''

class Window(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        self.setWindowTitle("PyQt5 sample")
        self.setGeometry(50, 50, 660, 660)

        self.button1 = QPushButton('Select File', self)
        self.button1.setGeometry(QtCore.QRect(20, 20, 100, 30))
        self.button1.clicked.connect(self.showDialog1)

        self.file_name = QLabel(self)
        self.file_name.setGeometry(130,20,400,30)
        self.file_name.setFrameStyle(QFrame.Box | QFrame.Plain)
        self.file_name.setFont(QFont('Meiryo',12))

        self.ESA_label = QLabel(self)
        self.ESA_label.setGeometry(130,60,400,30)
        self.ESA_label.setFrameStyle(QFrame.Box | QFrame.Plain)
        self.ESA_label.setFont(QFont('Meiryo',12))

        self.Fe_label = QLabel(self)
        self.Fe_label.setGeometry(130,100,400,30)
        self.Fe_label.setFrameStyle(QFrame.Box | QFrame.Plain)
        self.Fe_label.setFont(QFont('Meiryo',12))

        ESA_yoko = 100
        ESA_tate = 200

        Fe_yoko = 300
        Fe_tate = 200

        self.Fe_yes = QRadioButton('あり', self)
        self.Fe_yes.setFocusPolicy(QtCore.Qt.NoFocus)
        self.Fe_yes.setGeometry(Fe_yoko, Fe_tate, 100, 25)
        self.Fe_yes.setFont(QFont('Meiryo',14))
        
        self.Fe_no = QRadioButton('なし', self)
        self.Fe_no.setFocusPolicy(QtCore.Qt.NoFocus)
        self.Fe_no.setGeometry(Fe_yoko, Fe_tate + 40, 100, 25)
        self.Fe_no.setFont(QFont('Meiryo',14))

        self.groupFe = QButtonGroup()
        self.groupFe.addButton(self.Fe_yes, 1)
        self.groupFe.addButton(self.Fe_no, 0)
        
        self.ESA0 = QRadioButton('なし', self)
        self.ESA0.setFocusPolicy(QtCore.Qt.NoFocus)
        self.ESA0.setGeometry(ESA_yoko, ESA_tate, 100, 25)
        self.ESA0.setFont(QFont('Meiryo',14))

        self.ESA5 = QRadioButton('5μg', self)
        self.ESA5.setFocusPolicy(QtCore.Qt.NoFocus)
        self.ESA5.setGeometry(ESA_yoko, ESA_tate + 30, 100, 25)
        self.ESA5.setFont(QFont('Meiryo',14))

        self.ESA10 = QRadioButton('10μg', self)
        self.ESA10.setFocusPolicy(QtCore.Qt.NoFocus)
        self.ESA10.setGeometry(ESA_yoko, ESA_tate + 60, 100, 25)
        self.ESA10.setFont(QFont('Meiryo',14))
        
        self.ESA20 = QRadioButton('20μg', self)
        self.ESA20.setFocusPolicy(QtCore.Qt.NoFocus)
        self.ESA20.setGeometry(ESA_yoko, ESA_tate + 90, 100, 25)
        self.ESA20.setFont(QFont('Meiryo',14))

        self.ESA30 = QRadioButton('30μg', self)
        self.ESA30.setFocusPolicy(QtCore.Qt.NoFocus)
        self.ESA30.setGeometry(ESA_yoko, ESA_tate + 120, 100, 25)
        self.ESA30.setFont(QFont('Meiryo',14))

        self.ESA40 = QRadioButton('40μg', self)
        self.ESA40.setFocusPolicy(QtCore.Qt.NoFocus)
        self.ESA40.setGeometry(ESA_yoko, ESA_tate + 150, 100, 25)
        self.ESA40.setFont(QFont('Meiryo',14))

        self.ESA60 = QRadioButton('60μg', self)
        self.ESA60.setFocusPolicy(QtCore.Qt.NoFocus)
        self.ESA60.setGeometry(ESA_yoko, ESA_tate + 180, 100, 25)
        self.ESA60.setFont(QFont('Meiryo',14))

        self.ESA_list = [0,5,10,20,30,40,60]
        self.Fe_list = ['なし', 'あり']

        self.groupESA = QButtonGroup()
        self.groupESA.addButton(self.ESA0, 0)
        self.groupESA.addButton(self.ESA5, 1)
        self.groupESA.addButton(self.ESA10, 2)
        self.groupESA.addButton(self.ESA20, 3)
        self.groupESA.addButton(self.ESA30, 4)
        self.groupESA.addButton(self.ESA40, 5)
        self.groupESA.addButton(self.ESA60, 6)

        for button in self.groupESA.buttons():
            button.clicked.connect(self.clicked)
        
        for button in self.groupFe.buttons():
            button.clicked.connect(self.clicked)

    def showDialog1(self):

        fname = QFileDialog.getOpenFileName(self, 'Select File')

        if fname[0]:
            self.file_name.setText(fname[0])
            df = pd.read_csv(fname[0], index_col=0)
            self.groupFe.button(df.Fe[-1]).setChecked(True)
            self.groupESA.button(self.ESA_list.index(df.ESA[-1])).setChecked(True)

            self.clicked()
            
    def clicked(self):
        ESA = self.groupESA.checkedId()
        self.ESA_label.setText(str(self.ESA_list[ESA]))

        Fe = self.groupFe.checkedId()
        self.Fe_label.setText(self.Fe_list[Fe])

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex =Window()
    ex.show()
    sys.exit(app.exec_())

GluonTSの「to_pandas」(from gluonts.dataset.util)は多変量に対応していない!

対応させてみた。

def to_pandas_multi(instance: dict, dim: int) -> pd.Series:    
    target = instance["target"]
    start = instance["start"]
    freq = start.freqstr
    index = pd.date_range(start=start, periods=target.shape[1], freq=freq)
    return pd.Series(target[dim,:], index=index)

以下の様な使い方を想定している。

pred = predictor.predict(test_data)

plot_length = 48
prediction_intervals = (50.0, 90.0)
legend = ["observations", "median prediction"] + [f"{k}% prediction interval" for k in prediction_intervals][::-1]

for x, y in zip(test_data, pred):
    for i in range(target_num):
        plt.subplot(target_num,1,i+1)
        to_pandas_multi(x, i)[-plot_length:].dropna().plot()
        y.copy_dim(i).plot(color='g', prediction_intervals=prediction_intervals)
        plt.grid(which='both')
        plt.legend(legend, loc = 'upper left')
    
plt.show()

多変量時系列 GluonTS DeepAR TODOリスト

個人的メモ
随時追記しています

1

github.com

  • we can do transfer learning in forecasting.

4

github.com
将来の値が予測できない変数が多数ある時の方法

  • if you don't have future values you can transform your original features into something else, which you could then more easily set (instead of using the direct value). For example, instead of using sunlight directly, you could use the co-variate "absolute/relative change to yesterday".
  • another approach is to use mulit-variate forecasting techniques where you forecast everything jointly

5

github.com

  • We align timestamps. The idea is that all timestamps within the same range, are represented by the same value. In this case 2019-7-2 is aligned to 2019-06-27.

必要性がいまいち理解できないがそういうものらしい。

  • I think we should remove make_evaluation_predictions entirely for a better more understandable solution.

「make_evaluation_predictions」はなくなるらしい。
こちらで議論されている。

PyQt5を使ってみる

環境

Windows 10 Pro(CPU only)
Python 3.7.5

まずは最小画面の描画

  • バージョン
PyQt5==5.13.2
PyQt5-sip==12.7.0
  • コード
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import *

class Window(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("pyQt5 sample")
        self.setGeometry(50, 50, 660, 660)
        
        self.button1 = QPushButton('Open Img', self)
        self.button1.setGeometry(QtCore.QRect(20, 20, 100, 30))
    
        self.img_label1 = QLabel(self)
        self.img_label1.setGeometry(QtCore.QRect(74, 120, 512, 512))
        self.img_label1.setFrameStyle(QFrame.Box | QFrame.Plain)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex =Window()
    ex.show()
    sys.exit(app.exec_())
  • 結果

f:id:touch-sp:20191118161359p:plain:w400

つぎにボタンを押したときの動作を定義する(画像表示)

  1. ボタンを押すとファイル選択画面が開く
  2. ファイルを選択すると画面に表示する
  • 追加するコード
from PyQt5.QtGui import QImage, QPixmap
        self.button1.clicked.connect(self.showDialog1)
    def showDialog1(self):

        fname = QFileDialog.getOpenFileName(self, 'Open file')

        # fname[0]は選択したファイルのパス(ファイル名を含む)
        if fname[0]:
            # 画像の読み込み, サイズ変更
            image = QImage(fname[0]).scaled(512,512,QtCore.Qt.KeepAspectRatio) 
            # 画像の表示
            self.img_label1.setPixmap(QPixmap.fromImage(image))
  • 最終的なコード
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QImage, QPixmap

class Window(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("pyQt5 sample")
        self.setGeometry(50, 50, 660, 660)
        
        self.button1 = QPushButton('Open Img', self)
        self.button1.setGeometry(QtCore.QRect(20, 20, 100, 30))
    
        self.img_label1 = QLabel(self)
        self.img_label1.setGeometry(QtCore.QRect(74, 120, 512, 512))
        self.img_label1.setFrameStyle(QFrame.Box | QFrame.Plain)

        self.button1.clicked.connect(self.showDialog1)

    def showDialog1(self):

        fname = QFileDialog.getOpenFileName(self, 'Open file')

        # fname[0]は選択したファイルのパス(ファイル名を含む)
        if fname[0]:
            # 画像の読み込み, サイズ変更
            image = QImage(fname[0]).scaled(512,512,QtCore.Qt.KeepAspectRatio) 
            # 画像の表示
            self.img_label1.setPixmap(QPixmap.fromImage(image))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex =Window()
    ex.show()
    sys.exit(app.exec_())

matplotlibのグラフを表示させる

  • バージョン
cycler==0.10.0
kiwisolver==1.1.0
matplotlib==3.1.1
numpy==1.17.4
pyparsing==2.4.5
PyQt5==5.13.2
PyQt5-sip==12.7.0
python-dateutil==2.8.1
six==1.13.0
  • 最終コード
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import *

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import pyplot as plt
import numpy as np

class Window(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("pyQt5 sample")
        self.setGeometry(50, 50, 660, 660)
        
        self.button1 = QPushButton('Open Img', self)
        self.button1.setGeometry(QtCore.QRect(20, 20, 100, 30))
    
        self.FigureWidget = QWidget(self)
        self.FigureWidget.setGeometry(QtCore.QRect(74, 120, 512, 512))
        self.FigureLayout = QVBoxLayout(self.FigureWidget)
        
        self.figure = plt.figure()
        self.axes = self.figure.add_subplot(111)
        self.axes.axis('off')
        self.canvas = FigureCanvas(self.figure)
        
        self.FigureLayout.addWidget(self.canvas)

        self.button1.clicked.connect(self.plot)

    def plot(self):

        x1=np.arange(0, 4*np.pi, 0.1)
        y1=np.sin(x1)
        self.axes.plot(x1,y1,c='r')
        self.axes.axis('on')
        self.canvas.draw()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex =Window()
    ex.show()
    sys.exit(app.exec_())

  • グラフが二つになってもほぼ同様
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import *

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import pyplot as plt
import numpy as np

class Window(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("pyQt5 sample")
        self.setGeometry(50, 50, 660, 660)
        
        self.button1 = QPushButton('Open Img', self)
        self.button1.setGeometry(QtCore.QRect(20, 20, 100, 30))
    
        self.FigureWidget = QWidget(self)
        self.FigureWidget.setGeometry(QtCore.QRect(74, 120, 512, 512))
        self.FigureLayout = QVBoxLayout(self.FigureWidget)
        
        self.figure = plt.figure()
        self.axes1 = self.figure.add_subplot(211)
        self.axes1.axis('off')
        self.axes2 = self.figure.add_subplot(212)
        self.axes2.axis('off')
        self.canvas = FigureCanvas(self.figure)
        
        self.FigureLayout.addWidget(self.canvas)

        self.button1.clicked.connect(self.plot)

    def plot(self):

        x1=np.arange(0, 4*np.pi, 0.1)
        y1=np.sin(x1)
        self.axes1.plot(x1,y1,c='r')
        self.axes1.axis('on')
        self.axes2.plot(x1,y1,c='r')
        self.axes2.axis('on')
        self.canvas.draw()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex =Window()
    ex.show()
    sys.exit(app.exec_())

GluonTS の「LowrankMultivariateGaussianOutput」

使用例

estimator = DeepAREstimator(freq="7D", 
                            prediction_length=12, 
                            context_length=16,
                            use_feat_dynamic_real = True,
                            use_feat_static_cat = True,
                            cardinality = [30],
                            trainer=Trainer(
                                epochs=30,
                                 ),
                            distr_output=LowrankMultivariateGaussianOutput(dim=3, rank=2)
                            )

ハイパーパラメーターのrankはどうやって決める?

元論文

arxiv.org

As the rank hyperparameter r can typically be chosen to be much smaller than N, this leads to a significant speedup. 
N is the time series dimension. 

Supplementary materialに以下のようなグラフがある。
f:id:touch-sp:20191114124256p:plain:w300