4. Croston方法原始算法:参数初始化、更新公式、预测输出

Croston 方法(1972)是间歇需求预测的奠基性算法。其核心思想是将原始需求序列分解为非零需求大小与需求间隔两个独立子序列,分别对二者进行指数平滑,再通过组合计算得到每期的预测值。本章详细拆解原始算法的三个关键环节:参数初始化、递推更新公式、预测输出逻辑。

4.1 算法核心流程总览

下图展示了 Croston 原始算法的完整数据流与状态更新路径:

Croston 原始算法核心流程 原始需求序列 Y_t Y_t = 0 ? 间隔计数器 q_t 递增 q_t = q_{t-1} + 1 需求大小 & 间隔更新 z_t = Y_t, q_t = 1 间隔平滑 p_t = p_{t-1} 需求大小平滑 z_t = α·Y_t + (1-α)·z_{t-1} 间隔平滑 p_t = α·q_t + (1-α)·p_{t-1} 预测输出:Ŷ_t = z_t / p_t

4.2 参数初始化

Croston 方法需要为三个状态变量设置初始值:需求大小平滑值 \(z_0\)、间隔平滑值 \(p_0\)、间隔计数器 \(q_0\)。初始化策略直接影响前几期预测的稳定性。

参数 符号 推荐初始化方式 说明
需求大小平滑值 \(z_0\) 第一个非零需求值 若序列以零开头,则等待第一个非零值出现后再初始化
间隔平滑值 \(p_0\) 1.0 或前几个间隔的均值 常用 1.0(保守估计),也可用历史平均间隔
间隔计数器 \(q_0\) 1 从第一个非零需求出现时开始计数
平滑系数 \(\alpha\) 0.1 ~ 0.3 间歇需求场景推荐 0.1~0.2,避免过度响应噪声
实践建议:初始化时不要使用全零序列的前几期数据。建议跳过前导零段,从第一个非零需求出现时正式启动算法。若序列极短(<10期),可考虑使用全局均值作为初始值。

4.3 递推更新公式

在每个时间周期 \(t\),算法根据当前观测值 \(Y_t\) 是否为零,执行两套不同的更新逻辑。这是 Croston 方法区别于常规指数平滑的关键特征。

4.3.1 当 \(Y_t = 0\)(无需求发生)

  • 间隔计数器递增: \(q_t = q_{t-1} + 1\)
  • 需求大小平滑值保持不变: \(z_t = z_{t-1}\)
  • 间隔平滑值保持不变: \(p_t = p_{t-1}\)

零需求期间,算法仅记录间隔的延长,不更新需求大小的估计。

4.3.2 当 \(Y_t > 0\)(有需求发生)

  • 需求大小平滑更新: \(z_t = \alpha \cdot Y_t + (1 - \alpha) \cdot z_{t-1}\)
  • 间隔平滑更新: \(p_t = \alpha \cdot q_t + (1 - \alpha) \cdot p_{t-1}\)
  • 间隔计数器重置: \(q_t = 1\)

当需求发生时,算法同时更新需求大小和间隔的平滑估计,并将计数器重置为 1,开始记录下一次间隔。

关键区别:常规指数平滑在每个周期都更新平滑值;Croston 方法只在非零需求发生时更新需求大小平滑值,间隔平滑值也仅在非零需求发生时更新(使用当前累计的间隔计数)。零需求周期只递增计数器,不触发平滑更新。

4.4 预测输出

Croston 方法在每个周期 \(t\) 结束后,输出下一期的点预测值:

\[ \hat{Y}_{t+1} = \frac{z_t}{p_t} \]

其中:

  • \(z_t\):当前周期结束后,非零需求大小的平滑估计
  • \(p_t\):当前周期结束后,需求间隔的平滑估计

该预测值可以理解为“每期平均需求”。由于 \(p_t\) 通常大于 1,预测值会显著低于最近一次非零需求的大小,这符合间歇需求“大部分周期无需求”的客观规律。

4.5 完整算法伪代码

以下 Python 代码实现了 Croston 原始算法的完整递推过程:

def croston_original(series, alpha=0.1):
    """
    Croston 原始算法(1972)
    
    参数:
        series : list or np.array, 原始需求时间序列
        alpha  : float, 平滑系数 (0 < alpha < 1)
    
    返回:
        forecasts : list, 每期结束后对下一期的预测值
        z_vals    : list, 需求大小平滑值历史
        p_vals    : list, 间隔平滑值历史
    """
    n = len(series)
    forecasts = [None] * n
    z_vals = [None] * n
    p_vals = [None] * n
    
    # 初始化:找到第一个非零值
    z = None
    p = 1.0
    q = 1
    first_nonzero = -1
    
    for i in range(n):
        if series[i] > 0:
            first_nonzero = i
            z = series[i]
            break
    
    # 如果全零序列,返回零预测
    if first_nonzero == -1:
        return [0.0] * n, [0.0] * n, [1.0] * n
    
    # 第一个非零值之前的预测设为0
    for i in range(first_nonzero):
        forecasts[i] = 0.0
        z_vals[i] = 0.0
        p_vals[i] = p
    
    # 从第一个非零值开始递推
    z_vals[first_nonzero] = z
    p_vals[first_nonzero] = p
    forecasts[first_nonzero] = z / p
    
    for i in range(first_nonzero + 1, n):
        y = series[i]
        
        if y == 0:
            # 零需求:仅递增间隔计数器
            q += 1
            # z 和 p 保持不变
        else:
            # 非零需求:更新平滑值
            z = alpha * y + (1 - alpha) * z
            p = alpha * q + (1 - alpha) * p
            q = 1  # 重置计数器
        
        z_vals[i] = z
        p_vals[i] = p
        forecasts[i] = z / p
    
    return forecasts, z_vals, p_vals

4.6 算法特性总结

特性 描述
分解策略 将需求序列分解为“需求大小”和“需求间隔”两个独立子序列
更新触发 仅在非零需求发生时更新平滑参数;零需求周期只递增计数器
平滑系数 需求大小和间隔使用相同的 \(\alpha\),原始未区分
预测形式 点预测 = 平滑需求大小 / 平滑间隔,反映长期平均需求率
局限性 预测值始终为常数(在两次非零需求之间不变),无法反映需求波动
注意:Croston 原始算法存在一个已知的统计偏差——预测值 \(\hat{Y}_{t+1} = z_t / p_t\) 是需求率的有偏估计。后续改进方法(如 Syntetos-Boylan 近似)专门针对此偏差进行了修正,将在第 6 章详细讨论。