sktime 分数阶差分(Fractional Differentiation)实战指南:基于 Fracdiff 的非平稳时间序列平稳化与记忆保持
2026/9/16 20:49:02 网站建设 项目流程

sktime 分数阶差分(Fractional Differentiation)实战指南:基于 Fracdiff 的非平稳时间序列平稳化与记忆保持

【免费下载链接】sktimeA unified framework for machine learning with time series项目地址: https://gitcode.com/GitHub_Trending/sk/sktime

导读

本文围绕 sktime 内置的Fracdiff库(路径sktime/libs/fracdiff/)展开,讲解如何对非平稳时间序列执行分数阶差分(Fractional Differentiation),在使其平稳化的同时最大限度保留原始序列中的记忆(memory)信息。读者将掌握fdiff函数、scikit-learn 风格转换器Fracdiff的完整参数用法、窗长(window)与模式(mode)对结果的影响,以及如何用统计检验验证平稳化效果,并能直接将 Fracdiff 嵌入机器学习 Pipeline 用于金融等领域的特征工程。

Fracdiff 是fracdiff包在 sktime 中的非官方 fork,其算法思想源自 Marcos López de Prado 的著作Advances in Financial Machine Learning(Wiley, 2018)。核心价值在于:对价格类序列,普通的一阶差分(d=1)虽然能消除非平稳性,却会"过度差分"丢掉长期记忆;而分数阶差分通过在0 < d < 1之间选取阶数,可在平稳性与信息保留之间取得平衡。

分数阶差分:为什么一阶差分会丢失记忆

传统的整数阶差分(如numpy.diff的一阶、二阶差分)是分数阶差分在d为整数时的特例。它的缺陷在于:对随机游走类型的金融价格序列,d=1虽然能使其平稳,但会抹掉序列中蕴含的长期趋势记忆(momentum 等特征),这在建模收益预测时会造成信息损失;反之,d=0完全不差分,序列保持非平稳。

分数阶差分在两者之间连续取值。其数学基础是差分算子的分数次幂:

  • 对整数d,差分算子(1 - B)^dB为后移算子)展开后项数有限;
  • 对分数d,展开是无穷级数,实际计算时通过截断取前window项来近似,截断项系数即为fracdiff 系数

在 sktime/libs/fracdiff/fdiff.py 中,系数序列由fdiff_coef生成:

from sktime.libs.fracdiff import fdiff_coef fdiff_coef(0.5, 4) # array([ 1. , -0.5 , -0.125 , -0.0625]) fdiff_coef(1.0, 4) # array([ 1., -1., 0., -0.]) fdiff_coef(1.5, 4) # array([ 1. , -1.5 , 0.375 , 0.0625])

其实现为(-1) ** np.arange(window) * binom(d, np.arange(window)),即"负二项式系数"。观察可知:d=0.5时系数序列衰减缓慢、永不归零,这正是分数阶差分保留记忆的来源;而d=1.0时系数只有前两项非零,等价于一阶差分。测试 sktime/libs/fracdiff/tests/test_fdiff.py 用 Pochhammer 符号(s * p / f)独立重算系数并与fdiff_coef对照,验证了该实现的正确性。

功能总览

Fracdiff 提供两类接口,覆盖 NumPy 函数式调用与 scikit-learn 对象式集成:

接口说明
fdiff(a, n, axis, ...)numpy.diff推广到分数阶的纯函数,输入输出均为 NumPy 数组
Fracdiff(d, window, mode, window_policy)scikit-learn Transformer,通过fit/transform计算分数阶差分,可嵌入 Pipeline
FracdiffStatFracdiff的扩展:自动搜索使序列平稳的最小差分阶数d_(注:当前 sktime fork 中未包含该类实现,原fracdiff包提供)
fdiff_coef(d, window)返回 fracdiff 算子系数序列

sklearn 层的公开导出见 sktime/libs/fracdiff/sklearn/init.py,模块顶层统一导出fdifffdiff_coefFracdiff(见 sktime/libs/fracdiff/init.py)。

快速上手:fdiff函数

一维序列

fdiffnumpy.diff在分数阶上的自然延伸。对整数n,两者在n:之后的位置结果一致,只是fdiff会把开头n个元素用广义差分算子的值填充(这是为了保持输出与输入等长):

import numpy as np from sktime.libs.fracdiff import fdiff a = np.array([1, 2, 4, 7, 0]) fdiff(a, 0.5) # array([ 1. , 1.5 , 2.875 , 4.6875 , -4.1640625]) np.array_equal(fdiff(a, n=1), np.diff(a, n=1)) # True

在 sktime/libs/fracdiff/fdiff.py 的 docstring 中给出了等价的断言:np.diff(a) == fdiff(a)[1:]np.diff(a, 2) == fdiff(a, 2)[2:]。测试 sktime/libs/fracdiff/tests/test_fdiff.py 进一步在 1~3 维随机数组上、沿所有轴验证了整数n情形下fdiff(a, n, axis=axis)[n:]np.diff完全一致,并在test_axis中确认负轴索引(axis=-4等价于axis=0)工作正常。

多维数组与轴向

金融面板数据通常是(n_samples, n_features)形状,axis参数决定沿哪个轴做差分。沿时间轴(axis=0)与沿特征轴(axis=-1)结果完全不同:

a = np.array([[1, 3, 6, 10], [0, 5, 6, 8]]) # 沿时间轴(行方向) fdiff(a, 0.5, axis=0) # array([[ 1. , 3. , 6. , 10. ], # [-0.5, 3.5, 3. , 3. ]]) # 沿最后一个轴(列方向) fdiff(a, 0.5, axis=-1) # array([[1. , 2.5 , 4.375 , 6.5625], # [0. , 5. , 3.5 , 4.375 ]])

参数速查表

参数类型 / 默认值作用
aarray_like输入数组,会被强制转为 NumPy 数组
nfloat,默认1.0差分阶数。整数时与numpy.diff对齐,非整数时为分数阶差分
axisint,默认-1沿哪个轴做差分,默认最后一个轴
prepend/appendarray_like,可选在差分前沿axis拼接的值,标量会自动广播为长度为 1 的数组。用于边界样本的处理
windowint,默认10计算输出中每个元素所使用的观测数量(fracdiff 算子截断项数)
mode{"same", "valid"},默认"same""same":输出与输入沿axis等长,序列开头存在边界效应;"valid":只返回所有系数都参与卷积的元素,输出长度减为L_in - window + 1,无边界效应

两点实现细节值得注意:

  • mode="full"已被弃用并重命名为"same",传入"full"会抛出ValueError(fdiff.py),测试 test_fdiff.py 也对此做了回归校验;
  • mode="valid"prepend组合使用,可以补齐因截断丢失的window-1个元素。例如:
fdiff(a, 0.5, window=3, mode="valid", prepend=[1, 1]) # array([ 0.375, 1.375, 2.875, 4.75 , -4. ])

测试文件中的test_prepend/test_append证明:fdiff(a, ..., prepend=p)与先把p拼到a上再差分的结果逐元素相等。

scikit-learn API:Fracdiff转换器

基本用法

把 2 维时间序列X(形状(n_samples, n_features))传给Fracdiff转换器即可完成差分:

from sktime.libs.fracdiff.sklearn import Fracdiff X = ... # 2d time-series with shape (n_samples, n_features) f = Fracdiff(0.5) X = f.fit_transform(X)

Fracdiff继承自TransformerMixin, BaseEstimator(见 sktime/libs/fracdiff/sklearn/fracdiff.py)。其构造参数与底层fdiff一一对应:

参数类型 / 默认值说明
dfloat,默认1.0差分阶数
windowint > 0,默认10计算每个输出元素所用观测数
mode{"same", "valid"},默认"same"fdiffmode
window_policy{"fixed"},默认"fixed"目前仅支持固定窗长法:fracdiff 算子(后移算子的多项式)截断到第window项;"expanding" 扩展窗方法尚未实现

fit阶段只做两件事:通过_sklearn_check_input做输入校验并记录n_features_in_,然后调用fdiff_coef(self.d, self.window)预计算系数存入coef_transform阶段校验特征数与fit一致后,调用fdiff(X, n=self.d, axis=0, window=self.window, mode=self.mode)完成实际差分(见 fracdiff.py)。也就是说,Fracdiff固定沿axis=0(时间轴)做差分,这与面板数据的常规布局一致。

窗长与模式对输出的影响

windowmode的组合决定了输出的长度与边界质量。以形状(5, 2)的数组为例:

import numpy as np X = np.arange(10).reshape(5, 2) # mode="same":输出与输入等长 fracdiff = Fracdiff(0.5, window=3) fracdiff.fit_transform(X) # array([[0. , 1. ], # [2. , 2.5 ], # [3. , 3.375], # [3.75 , 4.125], # [4.5 , 4.875]]) fracdiff.coef_ # array([ 1. , -0.5 , -0.125]) # mode="valid":开头 window-1=2 行被剔除 fracdiff = Fracdiff(0.5, window=3, mode="valid") fracdiff.fit_transform(X) # array([[3. , 3.375], # [3.75 , 4.125], # [4.5 , 4.875]])

测试 sktime/libs/fracdiff/tests/sklearn/test_fracdiff.py 验证了Fracdiff(d, window, mode).fit_transform(X)与直接调用fdiff(X, n=d, axis=0, window=window, mode=mode)逐元素相等;sktime/libs/fracdiff/tests/sklearn/test_sklearn.py 则用 sklearn 官方parametrize_with_checksFracdiff(1)做全套兼容性检查(仅豁免"排列不变性"检查,因为差分天然与样本顺序有关),并端到端跑通了包含StandardScaler + Fracdiff + LinearRegression的 Pipeline。

嵌入机器学习 Pipeline

由于实现了完整的 sklearn Transformer 协议,Fracdiff可以无缝插入sklearn.pipeline.Pipeline,先标准化再差分再回归:

from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline X, y = ... # Dataset pipeline = Pipeline([ ('scaler', StandardScaler()), ('fracdiff', Fracdiff(0.5)), ('regressor', LinearRegression()), ]) pipeline.fit(X, y)

顺序上有讲究:StandardScaler放在Fracdiff之前,先消除量纲影响,再做差分特征工程。这一组合模式正是 examples/transformation/fracdiff/example_howto.ipynb 中演示的标准用法。

在保留记忆的同时实现平稳化

最小阶数搜索的思路

Fracdiff需要人工指定d。对于未知数据,最稳妥的做法是"在保持平稳的前提下取尽可能小的差分阶数",因为阶数越低,保留的长期记忆越多。原fracdiff包为此提供了FracdiffStat:它在一个区间内搜索使序列通过平稳性检验的最小差分阶数,搜索结果保存在属性d_(数组,每列特征一个阶数)中:

from fracdiff.sklearn import FracdiffStat X = ... # 2d time-series with shape (n_samples, n_features) f = FracdiffStat() X = f.fit_transform(X) f.d_ # array([0.71875 , 0.609375, 0.515625])

说明:FracdiffStat及其d_属性是上游fracdiff包提供的功能;在 sktime 当前的 fork(sktime/libs/fracdiff/init.py)中,顶层仅导出fdifffdiff_coefFracdiffFracdiffStat尚未被同步进来。若需自动定阶,可安装原版fracdiff使用,或按下一节思路自行搜索。

用 ADF 检验验证平稳化效果

分数阶差分的"记忆-平稳"权衡可以用 ADF 单位根检验量化。在 examples/transformation/fracdiff/example_prado.ipynb 中,作者对 S&P 500 序列做了完整实验:

  1. 多阶数扫描:对d在 0.1~0.9 之间取 9 个值分别做fdiff(spx, d, mode="valid")并绘图,直观看到差分后序列随d增大而波动加剧、趋势被逐步剥离;
  2. 平稳性-记忆权衡曲线:以 ADF 统计量(越负越平稳)为左轴、与原始序列的相关系数(记忆的代理指标)为右轴,绘制d ∈ [0, 1]上的变化曲线,并画出 5% 临界值虚线。可以看到:d越大 ADF 统计量越低(越平稳),但相关系数越低(记忆越少);
  3. 定阶与验证
fs = FracdiffStat(mode="valid") Xdiff = fs.fit_transform(X) _, pvalue, _, _, _, _ = stattools.adfuller(Xdiff.reshape(-1)) corr = np.corrcoef(X[-Xdiff.size:, 0], Xdiff.reshape(-1))[0][1] print(f"* Order: {fs.d_[0]:.2f}") print(f"* ADF p-value: {100 * pvalue:.2f} %") print(f"* Correlation with the original time-series: {corr:.2f}")
  • ADF p 值低于显著性水平(如 5%)说明差分后序列平稳;
  • 与原始序列的相关性越高,说明保留的记忆越多。

同样,examples/transformation/fracdiff/example_exercise.ipynb(书中第 5 章习题的求解示例)给出了完整的搜索模式:先扫d ∈ {0.2, 0.4, ..., 1.0}看 p 值,再用FracdiffStat(window=100, mode="valid", precision=10e-8, lower=0.9)在高精度区间内逼近最小平稳阶数,最后用Fracdiff(f.d_[0] - precision, mode="valid")反向验证"阶数再低一档就不平稳"。

窗长选择:基于容差的自动截断

window决定 fracdiff 算子截断到第几项,直接影响计算精度与速度。手动调窗比较繁琐,上游fracdiff包提供了两个基于容差的工具函数,sktime fork 中对应实现位于 sktime/libs/fracdiff/sklearn/tol.py:

  • window_from_tol_coef(n, tol_coef, max_window=2**12):取使"第window个系数的绝对值首次小于tol_coef"的最小整数。示例:window_from_tol_coef(0.5, 0.1)返回 4,因为fdiff_coef(0.5, 3)[-1] = -0.125fdiff_coef(0.5, 4)[-1] = -0.0625,后者首次小于 0.1;
  • window_from_tol_memory(n, tol_memory, max_window=2**12):取使"从第window+1项起系数的绝对值之和小于tol_memory"的最小整数,即显式控制"记忆损失量"。示例:window_from_tol_memory(0.5, 0.2)返回 9,对应尾部系数累积和约-0.19073
from sktime.libs.fracdiff.sklearn.tol import window_from_tol_coef, window_from_tol_memory window_from_tol_coef(0.5, 1e-5) # 允许系数截断误差不超过 1e-5 时的窗长 window_from_tol_memory(0.5, 0.2) # 允许记忆损失不超过 20% 时的窗长

注意 tol.py 中的提醒:当d很小或容差很小时,窗长会变得极大,量级约随tol_coef ** (-1 / d)增长。上述示例用法与测试断言(test_sklearn.py)一一对应,可直接照搬。

速度优势:NumPy 引擎与基准数据

README 中给出了与"官方实现"(指按定义逐点累加的朴素实现)的对比基准,强调两点:

  1. 得益于 NumPy 引擎,执行时间随时间步数n_samples增长非常缓慢;
  2. 在特定规模下可快约 4 个数量级。

不同n_samples(每列单特征)的执行时间(毫秒,Ubuntu 20.04 / Intel Xeon E5-2673 v3 @ 2.40GHz):

n_samplesfracdiffofficial
1000.675 ± 0.08620.008 ± 1.472
10005.081 ± 0.426135.613 ± 3.415
1000050.644 ± 0.5741310.033 ± 17.708
100000519.969 ± 8.16613113.457 ± 105.274

不同n_features(固定 1000 个时间步)的执行时间(毫秒):

n_featuresfracdiffofficial
15.081 ± 0.426135.613 ± 3.415
106.146 ± 0.2471350.161 ± 15.195
1006.903 ± 0.65413675.023 ± 193.960
100013.783 ± 0.700136610.030 ± 540.572

从实现看,速度来自fdiff将差分转化为一次卷积运算:mode="valid"时直接np.convolve(coef, a, mode="valid")mode="same"时做mode="full"卷积后截取尾部等长部分(fdiff.py)。卷积是高度向量化的 NumPy 操作,因此耗时几乎与窗长和样本数的乘积成正比而非平方增长。

上述数据来自原 README 的基准表,硬件环境与版本以当时测试为准;具体数字会因机器、NumPy 版本与数据规模而异,请以实际复测为准。

真实数据示例与可视化

S&P 500 的 0.5 阶差分

以下脚本来自 examples/transformation/fracdiff/fig/spx.py,演示了从 Yahoo Finance 拉取 S&P 500 数据、用Fracdiff(0.5, window=100, mode="valid")做差分并绘制的完整流程:

import pandas as pd import pandas_datareader from sktime.libs.fracdiff import Fracdiff s = pandas_datareader.data.DataReader( "^GSPC", "yahoo", "1999-10-01", "2020-09-30" )["Adj Close"] f = Fracdiff(0.5, window=100, mode="valid") d = f.fit_transform(s.values.reshape(-1, 1)).reshape(-1) s = s[100 - 1:] # valid 模式丢掉前 window-1 个样本,索引对齐 d = pd.Series(d, index=s.index)

图中蓝线为原始 S&P 500 收盘价(左轴),橙线为 0.5 阶差分后的序列(右轴):原始序列呈现长期上升趋势,差分序列围绕零轴剧烈波动,趋势被削弱而波动信息被保留,且差分序列与价格水平的量纲已解耦。

Nikkei 225 的自适应定阶差分

examples/transformation/fracdiff/example_howto.ipynb 展示了另一条路径——不手动指定阶数,而是让FracdiffStat自动搜索使序列平稳的最小阶数(此处对 Nikkei 225 得到约 0.27 阶):

from fracdiff import FracdiffStat nky = fetch_yahoo("^N225") # 日经 225 指数 fs = FracdiffStat(window=100, mode="valid") diff = fs.fit_transform(nky.values.reshape(-1, 1))

与固定 0.5 阶相比,自动定阶得到的阶数更低,意味着差分后的序列保留了更多原始记忆,同时通过了平稳性检验——这正是"保持记忆的平稳化"(stationarity while preserving memory)的实际效果。

边界与注意事项

  • 轴语义fdiffmode="same"保证输出与输入等长,但序列开头的元素只用了部分系数(边界效应);mode="valid"无边界效应但会缩短序列,使用时需自行对齐索引(见上面 S&P 500 示例中的s = s[window-1:])。
  • 特征数一致性Fracdifftransform时会校验输入特征数与fit时一致,不一致会抛出ValueError(fracdiff.py)。
  • 输入维度fdiff要求至少一维输入,0 维标量会抛ValueError;传入非法mode同样抛错(test_fdiff.py)。
  • 线性性质:分数阶差分是线性算子——测试test_linearity_addtest_linearity_mul验证了fdiff(a0) + fdiff(a1) == fdiff(a0 + a1)const * fdiff(a0) == fdiff(const * a0),这保证了它作为线性特征变换嵌入模型时的可解释性。
  • 窗长取舍window过小会引入较大的截断误差(阶数越高越明显),过大则浪费计算量;在要求精度时,优先用window_from_tol_coef/window_from_tol_memory按容差自动定窗。

参考资源

  • 核心实现:sktime/libs/fracdiff/fdiff.py(fdifffdiff_coef
  • sklearn 转换器:sktime/libs/fracdiff/sklearn/fracdiff.py(Fracdiff
  • 窗长工具:sktime/libs/fracdiff/sklearn/tol.py(window_from_tol_coefwindow_from_tol_memory
  • 单元测试:sktime/libs/fracdiff/tests/test_fdiff.py、sktime/libs/fracdiff/tests/sklearn/test_fracdiff.py、sktime/libs/fracdiff/tests/sklearn/test_sklearn.py
  • 可交互示例:examples/transformation/fracdiff/example_howto.ipynb、examples/transformation/fracdiff/example_prado.ipynb、examples/transformation/fracdiff/example_exercise.ipynb
  • 理论出处:Marcos López de Prado,Advances in Financial Machine Learning, Wiley, 2018(分数阶差分章节)

以上所有代码与参数说明均以当前仓库内的实现与测试为准;其中引用FracdiffStat的示例来自上游fracdiff包及其 notebook,实际以所在环境安装的包为准。

【免费下载链接】sktimeA unified framework for machine learning with time series项目地址: https://gitcode.com/GitHub_Trending/sk/sktime

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询