C# 中的时间序列预测库






4.87/5 (15投票s)
一组函数,接受时间序列数据并返回预测值和误差分析,允许进行预留集测试和 n 期延伸。
引言
此代码提供了一组基本函数,这些函数接受逗号分隔的时间序列值字符串、预测期数以及用于附加测试的“预留集”的期数(例如,在不知道实际值的情况下,根据已知数值验证预测值)。
背景
时间序列预测方法仅使用历史信息来生成未来值的估计。时间序列预测技术假设数据的过去模式将持续到未来,并包含特定的误差度量,可以帮助用户了解预测的准确性。一些技术试图识别潜在的模式,如趋势或季节性调整;其他技术则试图通过将过去期间的误差计算纳入未来预测来实现自我纠正。
此处包含的代码解决了几种最常见的时间序列预测技术,包括朴素/贝叶斯、简单移动平均、加权移动平均、指数平滑和自适应速率平滑。
在朴素/贝叶斯方法中,当前期间的值被用作下一期间的预测。
在简单移动平均中,将先前 n 期值平均在一起,赋予相等的权重,以生成下一期的值。
在加权移动平均中,通过将权重乘以值并对结果求和来对 n 期先前值应用一定百分比的权重,以生成下一期的值。通过应用不同的权重(总和为 1.0),可以对过去期间赋予不同的重点。如果用户希望更重视早期期间,他们可以使用 0.5、0.3 和 0.2 的权重。如果用户希望更重视近期期间,他们可以颠倒这些参数的顺序。
指数平滑是加权移动平均的一种变体,它赋予近期值比早期值更大的权重。但是,与加权移动平均不同,它只需要三个输入:前一期的预测值、当前期间的值以及平滑因子(alpha),其值介于 0 和 1.0 之间。
自适应速率平滑通过包含跟踪信号来修改每个期间预测的 alpha 平滑参数,从而改进了指数平滑技术。这项技术被证明可以更快地响应阶跃变化,同时保留过滤随机噪声的能力。有关此技术的完整讨论,请参阅 Trigg 和 Leach,1967(参见下方参考文献)。
Using the Code
提供的代码包含一个类,其中包含分析函数本身和一个演示窗体,该窗体接收逗号分隔的值,并在网格中显示它们,并附带预测和实例特定误差度量。五个按钮提供了函数库如何被调用并传递参数的示例,这些参数虽然合理,但应根据用户目的进行修改。除了测试集和误差外,网格还显示了未来若干期的预测值。最后,计算并显示了几个特定的误差度量。这些函数的用户应该对各个误差度量指示的内容有扎实的掌握,以便正确解释这些函数和结果。
我在函数中使用的一些术语可能 reader 一时难以理解
- 延伸 - 函数应尝试预测的未来整数期数。我们尝试预测的未来期数越多(数字越大),出错的概率就越高。
- 预留集 - 从可测试的观测值集合(从末尾开始)中保留的整数期数。函数在生成预测后计算这些值的预测,而不查看观测值。这样,就可以在不麻烦等待未来期数发生的情况下,验证未来若干期的预测与观测值。
通常,可以通过传入时间序列数据的十进制数组(提供示例)、一个整数延伸期数和一个整数预留期数来调用这些函数。其他参数已在代码中记录。
//
// Pass an array of time series data {1,2,3} and get a DataTable of forecasts and error
ForecastTable dt = TimeSeries.simpleMovingAverage(new decimal[3] {1,2,3}, 5, 3, 0);
grdResults.DataSource = dt;
simpleMovingAverage
的结果(与该类中的其他函数一样)是根据一个广为人知的公式计算的,该公式包含在注释中。
//
//Simple Moving Average
//
// ( Dt + D(t-1) + D(t-2) + ... + D(t-n+1) )
// F(t+1) = -----------------------------------------
// n
public static ForecastTable simpleMovingAverage(decimal[] values,
int Extension, int Periods, int Holdout)
{
ForecastTable dt = new ForecastTable();
for (Int32 i = 0; i < values.Length + Extension; i++)
{
//Insert a row for each value in set
DataRow row = dt.NewRow();
dt.Rows.Add(row);
row.BeginEdit();
//assign its sequence number
row["Instance"] = i;
if (i < values.Length)
{//processing values which actually occurred
row["Value"] = values[i];
}
//Indicate if this is a holdout row
row["Holdout"] = (i > (values.Length - Holdout)) && (i < values.Length);
if (i == 0)
{//Initialize first row with its own value
row["Forecast"] = values[i];
}
else if (i <= values.Length - Holdout)
{//processing values which actually occurred, but not in holdout set
decimal avg = 0;
DataRow[] rows = dt.Select("Instance>=" + (i - Periods).ToString() +
" AND Instance < " + i.ToString(), "Instance");
foreach (DataRow priorRow in rows)
{
avg += (Decimal)priorRow["Value"];
}
avg /= rows.Length;
row["Forecast"] = avg;
}
else
{//must be in the holdout set or the extension
decimal avg = 0;
//get the Periods-prior rows and calculate an average actual value
DataRow[] rows = dt.Select("Instance>=" + (i - Periods).ToString() +
" AND Instance < " + i.ToString(), "Instance");
foreach (DataRow priorRow in rows)
{
if ((Int32)priorRow["Instance"] < values.Length)
{//in the test or holdout set
avg += (Decimal)priorRow["Value"];
}
else
{//extension, use forecast since we don't have an actual value
avg += (Decimal)priorRow["Forecast"];
}
}
avg /= rows.Length;
//set the forecasted value
row["Forecast"] = avg;
}
row.EndEdit();
}
dt.AcceptChanges();
return dt;
}
每个预测函数(naive()
、simpleMovingAverage()
、weightedMovingAverage()
、exponentialSmoothing()
和 adaptiveRateSmoothing()
)的工作方式类似:首先使用默认值初始化早期行(因为之前的数据尚不可用),然后为可测试集中的每个值计算预测。最后,计算预留值和延伸值。
误差分析是通过实现多个度量来完成的:MeanSignedError()
、MeanAbsoluteError()
、MeanPercentError()
、MeanAbsolutePercentError()
、TrackingSignal()
、MeanSquaredError()
、CumulativeSignedError()
和 CumulativeAbsoluteError()
。
预测误差中最有用的度量之一是 MeanAbsolutePercentError
(MAPE)。该值是通过对每个期间预测值的百分比误差的绝对值求和,然后除以测试的期间数来计算的。
//MeanAbsolutePercentError = Sum( |PercentError| ) / n
public static decimal MeanAbsolutePercentError
(ForecastTable dt, bool Holdout, int IgnoreInitial)
{
string Filter = "AbsolutePercentError Is Not Null AND Instance > "
+ IgnoreInitial.ToString();
if (Holdout)
Filter += " AND Holdout=True";
if (dt.Select(Filter).Length == 0)
return 1;
return (Decimal)dt.Compute("AVG(AbsolutePercentError)", Filter);
}
参考文献
- Krajewski 和 Ritzman,《运营管理流程与价值链》第 7 版(2004),Pearson Prentice Hall,Upper Saddle River,NJ,第 535-581 页
- Trigg 和 Leach,“具有自适应响应速率的指数平滑”,《运筹学》,第 18 卷,第 1 期(1967 年 3 月),第 53-59 页
历史
此代码未经彻底测试,可能包含错误。它旨在对预测技术进行教学,不应用于实际决策。它没有针对性能或效率进行优化,并且代码编写得并不特别优雅。我的目标是使其易于阅读和理解,以便其他人可以创建实现时间序列预测概念的函数。尽管如此,如果您发现错误或明显的遗漏,或者您认为我本可以更清晰地解释某些内容,我非常欢迎建设性的反馈。