准确预测电力消耗对于电力系统的规划和运营至关重要。本文将详细介绍如何使用 ML.NET 构建时序预测模型,以预测全局有功功率(Global_active_power)的变化。
项目概述
- 目标:预测未来24小时的电力消耗
- 技术栈:ML.NET、C#
- 算法:单变量时序分析(SSA)
- 数据源:家庭用电量数据集
环境准备
- 创建新的 C# 控制台应用程序
- 安装必要的 NuGet 包:
<PackageReference Include="Microsoft.ML" Version="2.0.0" />
<PackageReference Include="Microsoft.ML.TimeSeries" Version="2.0.0" />
<PackageReference Include="CsvHelper" Version="30.0.1" />
完整代码实现
1. 定义数据模型
// 原始数据模型
public class PowerConsumptionRawData
{
[LoadColumn(0)]
public string Date { get; set; }
[LoadColumn(1)]
public string Time { get; set; }
[LoadColumn(2)]
public float Global_active_power { get; set; }
}
public class PowerConsumptionData
{
public DateTime Timestamp { get; set; }
public float Global_active_power { get; set; }
}
public class PowerPrediction
{
public float[] ForecastedPower { get; set; }
public float[] LowerBoundPower { get; set; }
public float[] UpperBoundPower { get; set; }
}
2. 主程序实现
using Microsoft.ML;
using Microsoft.ML.Transforms.TimeSeries;
using System.Globalization;
namespace App11
{
internal class Program
{
static void Main(string[] args)
{
// 初始化 ML.NET 上下文
MLContext mlContext = new MLContext(seed: 0);
// 加载数据
// 加载原始数据
IDataView rawDataView = mlContext.Data.LoadFromTextFile<PowerConsumptionRawData>(
path: "household_power_consumption.txt",
hasHeader: true,
separatorChar: ';'
);
// 转换数据:合并日期时间并处理格式
var transformedData = mlContext.Data.CreateEnumerable<PowerConsumptionRawData>(rawDataView, reuseRowObject: false)
.Select(row => new PowerConsumptionData
{
Timestamp = ParseDateTime(row.Date + " " + row.Time),
Global_active_power = row.Global_active_power
})
.OrderBy(x => x.Timestamp)
.ToList();
// 将处理后的数据转换回 IDataView
IDataView dataView = mlContext.Data.LoadFromEnumerable(transformedData);
// 定义预测管道
var pipeline = mlContext.Forecasting.ForecastBySsa(
outputColumnName: "ForecastedPower",
inputColumnName: nameof(PowerConsumptionData.Global_active_power),
windowSize: 24, // 24小时窗口
seriesLength: 72, // 使用3天的数据进行分析
trainSize: 8760, // 使用一年的数据训练
horizon: 24, // 预测未来24小时
confidenceLevel: 0.95f,
confidenceLowerBoundColumn: "LowerBoundPower",
confidenceUpperBoundColumn: "UpperBoundPower"
);
// 训练模型
var model = pipeline.Fit(dataView);
Console.WriteLine("模型训练完成!");
// 评估模型
// 评估模型
IDataView predictions = model.Transform(dataView);
// 获取预测值和实际值
var forecastingEngine = model.CreateTimeSeriesEngine<PowerConsumptionData, PowerPrediction>(mlContext);
var forecast = forecastingEngine.Predict();
// 手动计算评估指标
IEnumerable<float> actualValues = mlContext.Data.CreateEnumerable<PowerConsumptionData>(dataView,true)
.Select(x => x.Global_active_power);
IEnumerable<float> predictedValues = mlContext.Data.CreateEnumerable<PowerPrediction>(predictions, true)
.Select(x => x.ForecastedPower[0]);
// 首先打印数据数量
Console.WriteLine($"实际值数量: {actualValues.Count()}");
Console.WriteLine($"预测值数量: {predictedValues.Count()}");
// 检查是否有无效值
var hasInvalidActual = actualValues.Any(x => float.IsNaN(x) || float.IsInfinity(x));
var hasInvalidPredicted = predictedValues.Any(x => float.IsNaN(x) || float.IsInfinity(x));
Console.WriteLine($"实际值中包含无效值: {hasInvalidActual}");
Console.WriteLine($"预测值中包含无效值: {hasInvalidPredicted}");
// 计算差异时过滤掉无效值
var metrics = actualValues.Zip(predictedValues, (actual, predicted) => new { Actual = actual, Predicted = predicted })
.Where(pair => !float.IsNaN(pair.Actual) && !float.IsInfinity(pair.Actual) &&
!float.IsNaN(pair.Predicted) && !float.IsInfinity(pair.Predicted))
.Select(pair => (double)pair.Actual - (double)pair.Predicted)
.ToList();
// 计算评估指标
double mse = metrics.Select(x => x * x).Average();
double rmse = Math.Sqrt(mse);
double mae = metrics.Select(x => Math.Abs(x)).Average();
// 输出评估指标
Console.WriteLine($"均方误差 (MSE): {mse:F3}");
Console.WriteLine($"均方根误差 (RMSE): {rmse:F3}");
Console.WriteLine($"平均绝对误差 (MAE): {mae:F3}");
// 保存模型
string modelPath = "PowerPredictionModel.zip";
mlContext.Model.Save(model, dataView.Schema, modelPath);
// 加载模型并预测
var loadedModel = mlContext.Model.Load(modelPath, out var modelInputSchema);
var forecastEngine = loadedModel.CreateTimeSeriesEngine<PowerConsumptionData, PowerPrediction>(mlContext);
var fforecast = forecastEngine.Predict();
// 输出预测结果
Console.WriteLine("\n未来24小时的电力消耗预测:");
for (int i = 0; i < fforecast.ForecastedPower.Length; i++)
{
Console.WriteLine($"第 {i + 1} 小时: {fforecast.ForecastedPower[i]:F3} kW " +
$"(置信区间: {fforecast.LowerBoundPower[i]:F3} - {fforecast.UpperBoundPower[i]:F3} kW)");
}
}
static DateTime ParseDateTime(string dateString)
{
string[] formats = { "d/M/yyyy H:mm:ss", "dd/MM/yyyy H:mm:ss" }; // 支持多种格式
DateTime date;
DateTime.TryParseExact(dateString, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out date);
return date;
}
}
}
`MLContext.Forecasting.ForecastBySsa` 方法的所有参数:
必需参数
outputColumnName: string
// 预测结果输出列的名称
// 例如: "ForecastedPower"
inputColumnName: string
// 输入数据列的名称,用于预测的源数据列
// 例如: nameof(PowerConsumptionData.Global_active_power)
windowSize: int
// SSA(奇异谱分析)的窗口大小
// - 必须大于0且小于seriesLength
// - 影响模型捕捉的季节性模式
// - 推荐设置为预期周期长度的1/2到1/3
可选但重要的参数
seriesLength: int
// 用于训练的时间序列片段长度
// - 默认值:windowSize * 2
// - 必须大于windowSize
// - 建议值:windowSize的2-3倍
trainSize: int
// 用于训练的数据点数量
// - 默认值:整个数据集大小
// - 确定模型训练使用的历史数据量
horizon: int
// 预测的未来时间点数量
// - 默认值:1
// - 指定要预测多少个未来时间点
置信区间相关参数
confidenceLevel: float
// 预测的置信水平
// - 取值范围:0到1之间
// - 常用值:0.95(95%置信度)
// - 默认值:0.95
confidenceLowerBoundColumn: string
// 置信区间下界的输出列名
// - 可选参数
// - 例如: "LowerBoundPower"
confidenceUpperBoundColumn: string
// 置信区间上界的输出列名
// - 可选参数
// - 例如: "UpperBoundPower"
最佳实践
// 对于每小时数据的典型设置
var pipeline = mlContext.Forecasting.ForecastBySsa(
outputColumnName: "Forecast",
inputColumnName: "Value",
windowSize: 24, // 一天
seriesLength: 72, // 三天
trainSize: 8760, // 一年
horizon: 24, // 预测一天
confidenceLevel: 0.95f
);
// 对于每日数据的典型设置
var pipeline = mlContext.Forecasting.ForecastBySsa(
outputColumnName: "Forecast",
inputColumnName: "Value",
windowSize: 7, // 一周
seriesLength: 21, // 三周
trainSize: 365, // 一年
horizon: 7, // 预测一周
confidenceLevel: 0.95f
);
这些参数的正确设置对模型的预测性能至关重要,建议根据实际数据特征和业务需求进行调整和实验。
模型评估
使用两个主要指标评估模型性能:
- 平均绝对误差 (MAE)
a.MAE 直接反映预测值与实际值的平均偏差
b.数值越小表示预测越准确
c.单位与原始数据相同,便于理解
- 均方根误差 (RMSE)
- RMSE 对大误差更敏感
- 通过平方放大了大误差的影响
- 最终开方使单位与原始数据相同
总结
本文展示了如何使用 ML.NET 构建电力消耗预测模型的完整过程。通过合理配置和训练,可以得到一个可靠的预测模型,帮助优化电力系统运营。这个方法不仅适用于家庭用电预测,还可以扩展到工业用电预测、智能电网管理等领域。