使用DotNet ForecastBySsa预测简单序列结果异常的问题排查
问题概述
使用ML.NET的Microsoft.ML.Transforms.TimeSeries模块实现时间序列预测,以11000的递增序列为训练集,尝试预测1524后续值时得到完全错误的负数值结果,调整训练数据量、windowSize/seriesLength/horizon等参数后问题仍存在。
核心错误原因
数据逻辑完全错误
训练集是11000的递增序列,评估却用1524的片段——SSA模型依赖连续的历史序列上下文生成预测,训练完成后模型的历史窗口停留在1000,此时输入15~24这种和训练末尾脱节的片段,模型无法生成合理预测。预测流程逻辑错误
原代码中Forecast方法直接调用forecaster.Predict()后,用预测数组索引对应评估数据的每一行,但TimeSeriesPredictionEngine.Predict()是基于当前维护的历史窗口生成预测,而非一次性匹配评估数据集的所有条目;且未先将训练集的历史数据灌入预测引擎,导致模型用训练初期的窗口状态生成预测。评估方法逻辑错误
Evaluate方法中调用model.Transform(testData)完全不符合SSA模型的评估逻辑——SSA的Transform需要输入连续的历史序列,而非孤立的片段,无法生成对应评估数据的有效预测值。
修正方案
1. 调整数据逻辑,使用连续序列评估
训练完成后,应基于训练集的末尾序列做预测,比如预测1001~1010的数值,再和真实值对比,而非回头预测训练集开头的片段。
2. 修正预测流程
TimeSeriesPredictionEngine需要先灌入历史数据更新滑动窗口,再生成预测。关键修正如下:
public void TimeSeries() { string rootDir = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../")); string modelPath = Path.Combine(rootDir, "MLModel.zip"); List<TestInputData> training = new List<TestInputData>(); for (int i = 1; i <= 1000; i++) training.Add(new TestInputData(i)); // 评估用训练集后续的连续真实值:1001-1010 List<TestInputData> evaluation = new List<TestInputData>(); for (int i = 1001; i <= 1010; i++) evaluation.Add(new TestInputData(i)); MLContext mLContext = new MLContext(); IDataView trainingData = mLContext.Data.LoadFromEnumerable<TestInputData>(training); var forecastingPipeline = mLContext.Forecasting.ForecastBySsa( outputColumnName: "result", inputColumnName: "input", windowSize: 20, seriesLength: 50, trainSize: training.Count, horizon: 10, confidenceLevel: 0.95f, confidenceLowerBoundColumn: "confidenceLowerBound", confidenceUpperBoundColumn: "confidenceUpperBound"); SsaForecastingTransformer forecaster = forecastingPipeline.Fit(trainingData); var forecastEngine = forecaster.CreateTimeSeriesEngine<TestInputData, TestResultData>(mLContext); // 将训练集所有数据灌入引擎,更新滑动窗口到训练集末尾 foreach (var dataPoint in training) { forecastEngine.Predict(dataPoint); } forecastEngine.CheckPoint(mLContext, modelPath); // 生成10步预测 TestResultData forecast = forecastEngine.Predict(); // 对比预测值与真实值 Console.WriteLine("Forecast vs Actual"); Console.WriteLine("---------------------"); for (int i = 0; i < 10; i++) { float actualValue = evaluation[i].input; float predictedValue = forecast.result[i]; Console.WriteLine($"Actual: {actualValue}, Forecast: {predictedValue:F2}"); } }
3. 参数设置建议
seriesLength:对于递增趋势序列,建议设置为30~100,让模型有效捕捉趋势;若存在周期,设置为周期长度windowSize:一般取seriesLength的1/3~1/2,比如seriesLength=50时设置为20horizon:不要超过seriesLength的1/2,避免预测偏差过大
4. 修正评估逻辑
正确的评估流程应该是:
- 用训练集的前N个数据训练模型
- 灌入训练集的后续数据到预测引擎,生成预测值
- 将预测值与对应位置的真实值对比计算指标
示例评估方法:
private void Evaluate(List<TestInputData> training, List<TestInputData> evaluation, TimeSeriesPredictionEngine<TestInputData, TestResultData> forecastEngine) { // 灌入训练集数据到引擎 foreach (var data in training) { forecastEngine.Predict(data); } List<float> actualValues = evaluation.Select(d => d.input).ToList(); List<float> predictedValues = new List<float>(); // 逐步预测并记录结果 for (int i = 0; i < evaluation.Count; i++) { var forecast = forecastEngine.Predict(); predictedValues.Add(forecast.result[0]); // 将真实值灌入引擎,用于下一次预测 forecastEngine.Predict(evaluation[i]); } // 计算指标 var metrics = actualValues.Zip(predictedValues, (actual, pred) => actual - pred); var MAE = metrics.Average(error => Math.Abs(error)); var RMSE = Math.Sqrt(metrics.Average(error => Math.Pow(error, 2))); Console.WriteLine("Evaluation Metrics"); Console.WriteLine("---------------------"); Console.WriteLine($"Mean Absolute Error: {MAE:F3}"); Console.WriteLine($"Root Mean Squared Error: {RMSE:F3}\n"); }
内容的提问来源于stack exchange,提问作者Andreas

