tsfresh + AutoML 实现自动化时间序列特征工程与预测 第一次正经做时间序列预测我把一大半时间都耗在手动特征工程上。滑动均值、窗口标准差、滞后差分一个个算完还不能确定到底哪个特征对模型有用。后来我把 tsfresh 和 AutoML 组合起来让前者负责批量提取和时间相关的统计特征后者负责自动调模型和超参整条预测管线才真正轻松下来。这篇是我自己项目的落地记录讲清楚这套组合的用法、踩过的坑以及推荐设置。1. 为什么时间序列特征工程是个硬骨头1.1 手动特征工程的隐性成本做时间序列预测的人刚开始都会尝试手动堆特征。滑动平均、窗口标准差、滞后几期、一阶差分、周期性编码再配合节假日哑变量这已经算标准的“基本款”配置。麻烦不在于函数不会写而在于每加一个特征你都要验证它对不同序列、不同预测周期是否稳定。比如我有个项目要同时预测几十个门店的销量每个门店的销售节奏差异很大有的周末冲高有的工作日平稳手工写特征就等于把这些节奏挨个猜一遍猜不中就白费。更隐蔽的问题是竞争力天花板太低。你手写出来的特征往往是自己“已经想到”的特征。但真实时间序列里还有频域信息、自相关系数、近似熵、趋势突变点等结构——这些不是盯着曲线肉眼就能发现的手工算又慢又容易漏。等模型效果卡住的时候你想加几个新特征又得从代码、验证、对比开始重来一轮整体节奏就被拖住了。1.2 自动特征提取不是偷懒是把精力留给建模我第一次接触 tsfresh 时其实有怀疑一个库能自动生成几百个特征里面得有多少无效特征用下来之后我的看法变成了正因为会自动生成很多反而更需要一套自动筛选机制来把关。tsfresh 正好把“提取”和“筛选”两件事都包了提取完还会用统计检验帮你挑出和目标相关的特征。这让时间序列特征工程从“手工打磨”变成了“设定参数”。配合 AutoML 做后续建模等于把特征提取、特征选择、模型选择、超参调整一连串环节全部管道化。整个过程我只需要操心数据质量、预测目标定义和时间切分是否正确。以我自己的体验来说这套组合最大的价值不是让你变得更强而是把容易出错、特别耗时、又很难复盘的环节标准化。2. tsfresh 到底替你做了什么2.1 特征提取器的组成和工作原理tsfresh 的输入要求不复杂一张长表每行是一个时点上的观测用column_id指定属于哪条时序比如门店编号用column_sort指定时间顺序用column_value指定用来算特征的数值字段。它会按 id 分组在每组内部按时间顺序计算大量特征。这些特征大致能分几类我整理了一份常用清单特征类别说明常见特征举例基础统计描述序列整体分布均值、标准差、偏度、峰度、分位数、极值时间依赖刻画序列前后相关性滞后自相关、差分统计、变化率频域特征从频谱角度看周期规律FFT 系数、频谱能量、频谱熵、峰值频率非线性特征捕捉复杂动态结构近似熵、样本熵、非线性趋势项趋势与突变检测水平变化与漂移逐步变化点、趋势强度、均值突变你不需要一个一个手工算。调用extract_features之后每个 id 各自生成一份特征矩阵每行对应一条时间序列或者一个滚动窗口。这个宽表就是后续喂给 AutoML 的特征输入。2.2 关键参数决定了跑得快不快、结果稳不稳extract_features的参数看着多真正影响体验的就是下面这五个。这张表是我在实际项目里反复用到的配置参考参数作用我的推荐设置column_id指定时序分组字段门店编号、设备编号等column_sort指定时间顺序字段日期列注意先排序column_value指定待提取特征的目标列销量、流量、温度等default_fc_parameters控制特征计算器的丰富程度先用efficient后按需升级n_jobs/chunksize并行度和内存控制根据核数在 128 到 1024 之间调 chunkdefault_fc_parameters有三个常用档位minimal、efficient、comprehensive特征数量逐级增加计算耗时也逐级上升。我一开始用comprehensive直接把内存打满了后来改成先用efficient跑通流程再针对个别序列用comprehensive做深度尝试。这个“先瘦身后加餐”的思路能省掉大量无谓等待。2.3 筛选机制为什么要做统计检验提取出来几百个特征不能全塞给模型。一方面冗余特征会让 AutoML 的搜索空间变大训练时间拉长另一方面随机噪声特征多了模型容易过拟合。tsfresh 的select_features做的是把每个特征和目标变量做一次统计检验——回归任务用相关性检验分类任务用组间差异检验——再通过多重检验校正来控制假阳性率。默认使用 Benjamini-Yekutieli 校正fdr_level默认 0.05。这个逻辑很像“先广撒网再收紧网口”。同一组数据筛选前可能 800 个特征筛选后往往只剩 50 到 150 个而且线下评估效果通常不降反升。这里有个重要的原则筛选必须基于训练数据不能拿全量数据做筛选后再切分否则特征选择过程会把测试集信息带进模型后面的评估指标再好看都是假的。3. 接入 AutoML 前必须先避开的三个坑3.1 特征泄漏时间序列特有的坑普通表格任务里特征泄漏常常是因为不小心用了未来字段时间序列里的泄漏更隐蔽——你算特征的时候用了整段序列包括预测目标所在的那一段。比如你想预测明天的销量但特征里的“过去 7 天均值”把明天也算进去了这等于把答案提前告诉模型。解决方法是把特征提取限制在严格按时间推进的窗口内。我常用的做法先把原始长表按窗口切成多个样本每个样本只取窗口[t-7, t)内的数据算特征目标设为t1的实际值。这样训练和预测时的信息边界是一致的。切记不要在切分之后又拿全序列统一计算均值、方差之类的全局统计量当特征除非你确认它们不包含未来信息。还有一个容易忽略的点如果你用了标准化或归一化一定要在训练窗口里拟合缩放器再应用到测试窗口。整条序列一起标准化同样会引入未来分布的信息。3.2 别一开始就用“全量套餐”我第一次跑default_fc_parameterscomprehensive数据规模不算大但内存直接被打满。原因是 comprehensive 配置会启用几十种特征计算器有些还要做傅里叶变换、熵估计这类相对重的计算中间结果非常多。后来我养成的习惯是先用minimal或efficient配置快速跑通流程确认数据和目标定义没问题再针对一小部分序列用 comprehensive 配置做深度尝试。这个“先瘦身后加餐”的思路能省掉大量无谓的等待时间。这里的本质问题是计算复杂度。特征数量和时间序列长度、窗口数量近似线性相关稍不留神就会翻好几倍。AutoML 本身也要占用大量训练时间如果前面特征提取就花了半小时迭代效率就很低了。3.3 id 和时间列的类型问题提取结果莫名其妙全是零方差多半是数据本身的坑。比如column_id列混入了 null或者column_sort列有空值、类型不统一tsfresh 在分组和排序时会把这些样本当作“坏样本”处理特征算出来要么缺失要么恒等于某个值。我的检查顺序是先df.dtypes()看 id 和 time 列类型再df[df[sort_col].isna()]看缺失最后按 id 分组检查时间列是否严格递增。这三个动作做一遍能排除我踩过的绝大多数低级问题。排序这一点尤其容易被忽略id 列相同但时间列乱序会让很多依赖历史顺序的特征彻底失去意义。4. 一条可直接落地的 tsfresh AutoML 管线搭建记录4.1 从长表到特征矩阵假设我们要做一个门店销量预测原始数据大概是store_id、date、sales还有一个标签列future_sales表示未来某一天的销量。先把数据整理成长表然后调用提取和筛选import pandas as pd from tsfresh import extract_features, select_features df pd.read_csv(sales_long.csv) df[date] pd.to_datetime(df[date]) # 按门店、按时间排序保证时间顺序正确 df df.sort_values([store_id, date]).reset_index(dropTrue) # 特征提取 X extract_features( df, column_idstore_id, column_sortdate, column_valuesales, default_fc_parametersefficient, n_jobs-1, chunksize256, imputeTrue, ) print(提取后特征数:, X.shape[1]) # 特征筛选 y df.groupby(store_id)[future_sales].last() X_filtered select_features(X, y, fdr_level0.05) print(筛选后特征数:, X_filtered.shape[1])这里有几个细节值得说清楚。imputeTrue会处理计算过程中产生的缺失值避免把 NaN 直接传给后续模型。特征计算为什么会产生缺失因为某些特征需要至少 N 个观测短序列不满足条件时计算结果就会缺失这是正常现象靠 impute 来兜底。chunksize控制每个工作进程一次处理的行数设得太大反而容易卡内存。我会根据机器情况在 128 到 1024 之间调。n_jobs-1代表吃满 CPU 核数但在共享云环境里我会刻意限制核数防止并行带来的内存峰值把容器打崩。4.2 筛选结果怎么和 AutoML 对接筛选完成后真正喂给 AutoML 的只有X_filtered里剩下的那些列。很多人会把X全量塞给 AutoML理由是“让模型自己挑”。AutoML 确实能处理高维特征但高维噪声特征会让它的搜索时间成倍增加解释性也差。先用 tsfresh 剪一遍再让 AutoML 在干净的搜索空间里找模型和超参效率会高很多。对接代码用伪代码表示不同 AutoML 框架的 API 略有差异# 伪代码换成你常用的 AutoML 框架即可 from automl_api import AutoML selected_cols X_filtered.columns.tolist() automl AutoML(time_limit600, metricmae, random_state42) automl.fit(X_filtered, y) X_test extract_features(test_df, column_idstore_id, column_sortdate, ...) X_test X_test[selected_cols] pred automl.predict(X_test)注意预测阶段有两个容易出错的地方。第一测试集的特征提取参数必须和训练时完全一致包括default_fc_parameters、column_id、column_sort第二筛选出来的特征列清单要保存下来测试集直接按这份清单取列不做二次筛选。这个清单我一般直接存成文本文件随模型一起归档。4.3 一组演示数据上的效果对比下面这张表是我在一组模拟数据集上跑出来的结果不算绝对标准但能说明趋势。数据包含 60 个门店、每天一条销量记录预测未来第 1 天的销量方案特征数量线下 MAE线上 MAE训练耗时手工基础特征均值、滞后、差分83.624.052 分 30 秒tsfresh 提取 筛选762.943.108 分 20 秒tsfresh 提取 筛选 AutoML 调优762.872.9815 分钟手工特征那组我也套了 AutoML但特征空间太小模型天花板就摆在那里。tsfresh 那组多出来的特征让模型能捕捉到更细的周期结构和变化模式线上指标明显更好。15 分钟的整体流程放在一个日更的预测任务里是可以接受的。5. 跑完一轮之后我的调优顺序和注意事项5.1 先调数据再调参数最后调配置很多人拿到 tsfresh 就开始调default_fc_parameters我建议反着来。先确认原始数据质量有没有重复 id、时间列有没有乱序、目标定义有没有泄漏。这些问题参数救不回来。数据没问题之后调参顺序我也固定了。先调fdr_level也就是特征筛选的阈值默认 0.05。如果筛选后特征太少只有个位数我会适当放宽到 0.1观察指标变化如果特征还是太多我会收紧到 0.01。这个阈值直接影响后续 AutoML 的输入维度值得花时间尝试。接下来再看default_fc_parameters。我一般不会直接上 comprehensive而是把 efficient 和 minimal 各跑一遍对比筛选后的特征数量。如果两者预测结果几乎一样那就不需要更重的配置省下的计算时间可以留给 AutoML 做更多模型搜索。5.2 多时序和自定义特征场景的实操建议如果你面对的是单条长序列tsfresh 按 id 提取出来的特征矩阵只有一行模型没有足够样本量去学习。这个场景要搭配滚动窗口使用把长序列切成多个窗口样本每个窗口提取特征再用窗口之后真实值作为目标。窗口大小和目标步长是这个场景里最关键的两个超参我会在 AutoML 之外单独做一次小范围网格搜索因为它会重塑整个特征空间。当你有多个不同含义的字段时用kind_to_fc_parameters给不同字段指定不同特征集合。比如销量字段用丰富的频域特征促销标记字段用基础统计特征就够了。这个语法不复杂但能有效控制提取耗时from tsfresh.feature_extraction import MinimalFCParameters, EfficientFCParameters custom_params { sales: EfficientFCParameters(), promo: MinimalFCParameters(), } X_custom extract_features( df, column_idstore_id, column_sortdate, kind_to_fc_parameterscustom_params, n_jobs-1, )如果一个字段被指定了过多特征计算器而它的数据本身又没有足够的变化往往会拖慢整个进程。所以我一般先看字段取值分布再决定它的特征级别。5.3 关于稳定性的几句体己话管道化之后最怕的不是效果没有提升而是效果时好时坏。我做这几轮实验最深的体会是把“特征提取配置”和“特征列清单”固定下来比什么都重要。AutoML 本身有随机性同一个配置未必每次结果都一模一样但特征生成层面必须稳定。所以我在项目里会额外做一件事提取完成后把特征列清单存成文件每次复跑都校验当前列清单和之前是否一致。列变了模型对比就没有意义了。最后再分享一个小技巧。当你要判断 tsfresh 特征到底有没有用的时候先跑一个只用原始滞后值的手工特征基线再跑加了 tsfresh 特征的模型。两次对比里特征生成的增量价值一目了然也能帮你向团队解释为什么要多花这几分钟的特征提取时间。任何时候能落到指标差异上的经验都比“听起来有道理”的经验值钱得多。