如何在Scikit-learn框架中对面板数据进行时间序列交叉验证式超参数调优?
面板数据的时间序列交叉验证超参数调优方案
问题背景
我们有多组多实体的时间序列观测数据(面板数据),需要为单个模型做超参数调优,并且采用时间序列交叉验证的方式划分数据。但Scikit-learn内置的TimeSeriesSplit仅适用于单时间序列场景,无法直接处理多实体的面板数据。
举个简单的面板数据示例(代码生成的DataFrame):
from itertools import product import pandas as pd import numpy as np # 创建面板数据 countries = ['ESP','FRA'] periods = list(range(10)) df = pd.DataFrame(list(product(countries,periods)), columns = ['country','period']) df['target'] = np.concatenate((np.repeat(1, 10), np.repeat(0, 10))) df['a_feature'] = np.random.randn(20, 1)
生成的数据结构如下:
| country | period | target | a_feature |
|---|---|---|---|
| ESP | 0 | 1 | 0.08 |
| ESP | 1 | 1 | -2.0 |
| ESP | 2 | 1 | 0.1 |
| ESP | 3 | 1 | -0.59 |
| ESP | 4 | 1 | -0.83 |
| ESP | 5 | 1 | 0.05 |
| ESP | 6 | 1 | 0.05 |
| ESP | 7 | 1 | 0.42 |
| ESP | 8 | 1 | 0.04 |
| ESP | 9 | 1 | 2.17 |
| FRA | 0 | 0 | -0.44 |
| FRA | 1 | 0 | -0.48 |
| FRA | 2 | 0 | 0.82 |
| FRA | 3 | 0 | -1.64 |
| FRA | 4 | 0 | 0.19 |
| FRA | 5 | 0 | 0.6 |
| FRA | 6 | 0 | -0.73 |
| FRA | 7 | 0 | -0.5 |
| FRA | 8 | 0 | 1.11 |
| FRA | 9 | 0 | -0.75 |
核心需求是:用所有实体某一时期之前的全部数据训练统一模型,再预测这两个实体的下一时期数据,通过这种交叉验证逻辑选出最优超参数。
解决方案
核心思路是自定义符合面板数据逻辑的交叉验证拆分器,再结合Scikit-learn的超参数搜索工具使用。
步骤1:自定义面板时间序列交叉验证拆分器
这个拆分器会按时间周期划分训练集和测试集:训练集包含所有实体period <= t的数据,测试集包含所有实体period = t+1的数据,循环迭代覆盖可用周期。
import numpy as np from sklearn.model_selection import BaseCrossValidator class PanelTimeSeriesSplit(BaseCrossValidator): def __init__(self, n_splits=5): self.n_splits = n_splits def get_n_splits(self, X=None, y=None, groups=None): return self.n_splits def split(self, X, y=None, groups=None): # 提取并排序所有唯一时间周期 periods = np.sort(X['period'].unique()) # 计算拆分点,确保训练集和测试集有足够数据 split_points = np.linspace(1, len(periods)-1, self.n_splits, dtype=int) for split in split_points: # 训练集:所有period <= periods[split-1]的数据 train_mask = X['period'] <= periods[split-1] # 测试集:所有period == periods[split]的数据 test_mask = X['period'] == periods[split] train_idx = X[train_mask].index test_idx = X[test_mask].index yield train_idx, test_idx
步骤2:结合超参数搜索工具使用
以随机森林分类器为例,用RandomizedSearchCV配合自定义拆分器完成调优:
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import RandomizedSearchCV import scipy.stats as stats # 定义模型与超参数空间 model = RandomForestClassifier(random_state=42) param_dist = { 'n_estimators': stats.randint(50, 200), 'max_depth': [3, 5, 7, None], 'min_samples_split': stats.randint(2, 10) } # 初始化自定义交叉验证拆分器 tscv = PanelTimeSeriesSplit(n_splits=6) # 拆分数量需小于总周期数-1,示例有10个周期,最多可做8次拆分 # 初始化超参数搜索器 search = RandomizedSearchCV( model, param_distributions=param_dist, n_iter=20, cv=tscv, scoring='accuracy', n_jobs=-1, random_state=42 ) # 预处理特征:对类别特征(country)做独热编码 X = df[['country', 'period', 'a_feature']] X_encoded = pd.get_dummies(X, columns=['country']) y = df['target'] # 执行超参数搜索 search.fit(X_encoded, y) # 输出结果 print("最优超参数:", search.best_params_) print("交叉验证最优得分:", search.best_score_)
关键注意事项
- 数据排序:确保数据按实体和时间周期排序,避免训练集混入未来数据。
- 类别特征处理:实体标签(如示例中的country)需做编码(独热编码、目标编码等),否则模型无法处理。
- 拆分数量限制:
n_splits不能超过总周期数-1,每个拆分需要至少1个训练周期和1个测试周期。 - 模型兼容性:该方案适用于所有Scikit-learn兼容的模型,包括回归、分类模型。
内容的提问来源于stack exchange,提问作者Slash
相关产品推荐
相关产品推荐

