Python 使用验证窗口进行时间序列数据交叉验证

Python 使用验证窗口进行时间序列数据交叉验证,python,validation,scikit-learn,time-series,Python,Validation,Scikit Learn,Time Series,我希望对我的时间序列数据执行前向验证。关于如何执行滚动窗口,存在大量文档: 或展开窗口 但这种验证与我的生产系统中的情况并不相符:我想每天重新训练一个模型,在未来14天内做出预测。因此,我只会在我之前的培训周期中添加一天的数据(其他方法在接下来的培训周期中添加了一整套长度测试大小;在我的情况下为14天的数据)。因此,我想用滑动窗口验证我的模型: 我的问题是,我找不到一个能完成这项工作的Python库。从sklearn那里,我们别无选择。 基本上我想提供: test\u size,n\u f

我希望对我的时间序列数据执行前向验证。关于如何执行滚动窗口,存在大量文档:

展开窗口

但这种验证与我的生产系统中的情况并不相符:我想每天重新训练一个模型,在未来14天内做出预测。因此,我只会在我之前的培训周期中添加一天的数据(其他方法在接下来的培训周期中添加了一整套长度
测试大小
;在我的情况下为14天的数据)。因此,我想用滑动窗口验证我的模型:

我的问题是,我找不到一个能完成这项工作的Python库。从sklearn那里,我们别无选择。 基本上我想提供:
test\u size
n\u fold
min\u train\u size


如果
n\u fold>(n\u samples-min\u train\u size)%test\u size
,则下一步
training\u set
从上一个fold
test\u set

中提取数据看起来您的要求是使测试大小大于1倍。要进行更改,您需要调整线条

我已经做了这些更改,并添加了一个名为
n\u test\u folds
的新参数,以便可以对其进行自定义

来自sklearn.model\u选择的
。\u分割导入时间序列分割
从sklearn.utils.validation导入\u不推荐\u位置参数
从sklearn.utils导入可索引
从sklearn.utils.validation导入\u num\u示例
类WindowedTestTimeSeriesSplit(TimeSeriesSplit):
"""
参数
----------
n_测试折叠:int
每次迭代时用作测试的折叠数。
默认情况下,1。
"""
@_弃用位置参数
定义初始(自,n个拆分=5,*,最大列大小=无,n个测试折叠=1):
super().\uuuu init\uuuu(n\u拆分,
最大列车尺寸=最大列车尺寸)
self.n\u test\u folds=n\u test\u folds
def拆分(自身、X、y=无,组=无):
“”“生成索引以将数据拆分为训练集和测试集。”。
参数
----------
X:形状的阵列状(n个样本,n个特征)
训练数据,其中n_samples是样本数
n_features是特征的数量。
y:形状的数组状(n_个样本,)
始终忽略,因为兼容性而存在。
组:形状的阵列状(n_个样本)
始终忽略,因为兼容性而存在。
产量
------
火车:Ndaray
该分割的训练集索引。
测试:Ndaray
测试设置了该拆分的索引。
"""
十、 y,组=可索引(X,y,组)
n_样本=_num_样本(X)
n_splits=self.n_splits
n_折叠=n_拆分+自测试n_折叠
如果n_折叠>n_样本:
升值误差(
(“折叠次数不能大于{0}”
“比样本数:{1}.”。格式(n_倍,
n(样本)
指数=np.arange(n_样本)
折叠大小=(n个样本//n个折叠)
测试大小=折叠大小*self.n测试折叠测试窗口
测试开始=范围(折叠大小+n个样本%n个折叠,
n_samples-test_size+1,fold_size)#基于fold_size而不是test_size进行拆分
对于测试启动中的测试启动:
如果self.max\u系列尺寸和self.max\u系列尺寸<测试开始:
产量(指标[测试启动-自身最大列车大小:测试启动],
索引[测试开始:测试开始+测试大小])
其他:
收益率(指数[:测试开始],
索引[测试开始:测试开始+测试大小])
例如:

将numpy导入为np
X=np.数组([[1,2],[3,4],[1,2],[3,4],[1,2],[3,4])
y=np.数组([1,2,3,4,5,6])
tscv=WindowedTestTimesPeriesSplit(n_分割=4,n_测试折叠=2)
印刷品(tscv)
对于列车索引,在tscv.split(X)中测试列车索引:
打印(“列车:,列车索引,测试:,测试索引)
X_列,X_测试=X[列索引],X[测试索引]
y_列,y_测试=y[列指数],y[测试指数]
#WindowedTestTimesPeriesSplit(最大列大小=无,n个拆分=4,n个测试折叠=2)
#列车:[0]测试:[1 2]
#列车:[0 1]测试:[2 3]
#列车:[0 1 2]测试:[3 4]
#列车:[0 1 2 3]测试:[4 5]
注:未生成序列:[0 1 2 3 4]测试:[5],因为它不满足测试折叠次数的要求

使用函数,我们可以可视化CV的不同分割

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
np.random.seed(1338)
cmap_data = plt.cm.Paired
cmap_cv = plt.cm.coolwarm
n_splits = 4


# Generate the class/group data
n_points = 100
X = np.random.randn(100, 10)

percentiles_classes = [.1, .3, .6]
y = np.hstack([[ii] * int(100 * perc)
               for ii, perc in enumerate(percentiles_classes)])

# Evenly spaced groups repeated once
groups = np.hstack([[ii] * 10 for ii in range(10)])

fig, ax = plt.subplots()
cv = WindowedTestTimeSeriesSplit(n_splits=n_splits, n_test_folds=2)
plot_cv_indices(cv, X, y, groups, ax, n_splits)
plt.show()

以下是我的解决方案,允许用户指定测试范围和用于培训的最小数据样本:

from sklearn.model_selection import TimeSeriesSplit
from sklearn.utils import indexable
from sklearn.utils.validation import _num_samples

class TimeSeriesSplitCustom(TimeSeriesSplit):
    def __init__(self, n_splits=5, max_train_size=None,
                 test_size=1,
                 min_train_size=1):
        super().__init__(n_splits=n_splits, max_train_size=max_train_size)
        self.test_size = test_size
        self.min_train_size = min_train_size

    def overlapping_split(self, X, y=None, groups=None):
        min_train_size = self.min_train_size
        test_size = self.test_size

        n_splits = self.n_splits
        n_samples = _num_samples(X)

        if (n_samples - min_train_size) / test_size >= n_splits:
            print('(n_samples -  min_train_size) / test_size >= n_splits')
            print('default TimeSeriesSplit.split() used')
            yield from super().split(X)

        else:
            shift = int(np.floor(
                (n_samples - test_size - min_train_size) / (n_splits - 1)))

            start_test = n_samples - (n_splits * shift + test_size - shift)

            test_starts = range(start_test, n_samples - test_size + 1, shift)

            if start_test < min_train_size:
                raise ValueError(
                    ("The start of the testing : {0} is smaller"
                     " than the minimum training samples: {1}.").format(start_test,
                                                                        min_train_size))

            indices = np.arange(n_samples)

            for test_start in test_starts:
                if self.max_train_size and self.max_train_size < test_start:
                    yield (indices[test_start - self.max_train_size:test_start],
                           indices[test_start:test_start + test_size])
                else:
                    yield (indices[:test_start],
                           indices[test_start:test_start + test_size])

(要获得相同的结果,请确保更改
对于枚举(**cv.overlapping_split**(X=X,y=y,groups=group))中的ii(tr,tt):

plot\u cv\u index
函数中


干杯!

我的回答解决了你的问题吗?谢谢你的回答!它确实很有帮助;特别是可视化。我想出了一个稍微不同的解决方案,满足了我的要求,特别是关于
min\u training\u size
test\u size
,我想控制它,这很有帮助。我瘦了k、 测试大小n\u测试折叠已捕获的内容。滑动窗口视觉中未捕获您的最小训练大小。因此,我没有添加它。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
from ModelEvaluation import TimeSeriesSplitCustom
np.random.seed(1338)
cmap_data = plt.cm.Paired
cmap_cv = plt.cm.coolwarm
n_splits = 13

# Generate the class/group data
n_points = 100
X = np.random.randn(100, 10)

percentiles_classes = [.1, .3, .6]
y = np.hstack([[ii] * int(100 * perc)
               for ii, perc in enumerate(percentiles_classes)])

# Evenly spaced groups repeated once
groups = np.hstack([[ii] * 10 for ii in range(10)])

fig, ax = plt.subplots()

cv = TimeSeriesSplitCustom(n_splits=n_splits, test_size=20, min_train_size=12)
plot_cv_indices(cv, X, y, groups, ax, n_splits)
plt.show()