专栏名称: 微软中国MSDN
微软中国MSDN开发社区官方微信。
目录
相关文章推荐
中国半导体论坛  ·  半导体精品公众号推荐! ·  11 小时前  
中国半导体论坛  ·  半导体精品公众号推荐! ·  11 小时前  
半导体行业联盟  ·  中国芯50强(新锐) ·  昨天  
半导体行业联盟  ·  DeepSeek:2025半导体封测十大预测! ·  3 天前  
半导体行业联盟  ·  突发,意法裁员3000人! ·  5 天前  
半导体行业联盟  ·  软银 + OpenAI,1800亿! ·  5 天前  
51好读  ›  专栏  ›  微软中国MSDN

使用 C# 神经网络的时序回归

微软中国MSDN  · 公众号  ·  · 2017-12-04 19:00

正文

时序回归问题旨在根据历史时间数据进行预测。例如,如果有一年或两年内的月度销售数据,建议预测下个月的销售情况。时序回归通常很难运行,可以使用多种不同的技术。


在本文中,我将展示如何结合使用滚动窗口数据和神经网络,从而执行时序回归分析。最好通过举例介绍这个想法。请查看图 1 中的演示程序。演示程序分析了从 1949 年 1 月到 1960 年 12 月的每月航空旅客数。


图 1:滚动窗口时序回归演示


演示数据来自 Internet 上许多地方都有的知名基准数据集,可以从本文随附的下载内容中获取。原始数据如下:


"1949-01";112"1949-02";118"1949-03";132"1949-04";129"1949-05";121"1949-06";135"1949-07";148"1949-08";148
..."1960-11";390"1960-12";432


共有 144 个原始数据项。第一个字段是年份和月份。第二个字段是每月国际航空旅客总人数,以千人为单位。演示程序创建定型数据的方法为,使用大小为 4 的滚动窗口生成 140 个定型项。定型数据的规范化方式为,用每个旅客计数除以 100:

[  0]   1.12   1.18   1.32   1.29   1.21
[  1]   1.18   1.32   1.29   1.21   1.35
[  2]   1.32   1.29   1.21   1.35   1.48
[  3]   1.29   1.21   1.35   1.48   1.48
...
[139]   6.06   5.08   4.61   3.90   4.32


请注意,数据中删除了明确的时间值。第一个窗口由前四个用作预测指标值的旅客计数(1.12、1.18、1.32、1.29)组成,后跟第五个计数 (1.21),即要预测的值。

下一个窗口由第二个到第五个计数(即下一组预测指标值 1.18、1.32、1.29、1.21)组成,后跟第六个计数 (1.35),即要预测的值。简单来说,就是使用每组连续四个旅客计数来预测下一个计数。

演示程序使用的神经网络包含 4 个输入节点、12 个隐藏的处理节点和 1 个输出节点。输入节点数量对应于滚动窗口中的预测指标数量。

必须通过反复试验来确定窗口大小,这是此项技术的最大缺点。神经网络的隐藏节点数量也必须通过反复试验进行确定,

神经网络一向如此。只有一个输出节点,因为时序回归预测只提前一个时间单位。

神经网络有 (4 * 12) + (12 * 1) = 60 个节点间权重,以及 (12 + 1) = 13 个偏差,这其实就定义了神经网络模型。借助滚动窗口数据,演示程序可以使用基本的随机反向传播算法对网络进行定型,其中学习率设置为 0.01,固定迭代次数设置为 10,000。

在定型过程中,演示程序每 2,000 次迭代就会显示一次预测输出值与正确输出值的均方误差。定型误差很难解释,之所以监视此误差主要是为了确定是否有非常奇怪的情况发生(这种情况相当常见)。在此示例中,定型误差似乎在大约 4,000 次迭代后开始趋于稳定。

完成定型后,演示代码显示 73 个权重和偏差值。再强调一遍,大部分情况下,这被用作健全性检查。对于时序回归问题,通常必须使用自定义准确度指标。在这种情况下,正确的预测结果为,未规范化的预测旅客计数与实际计数的差值不超过 30。根据这一定义,演示程序的准确度为 91.43%。也就是说,在 140 个预测旅客计数中,128 个正确,12 个不正确。

演示程序最后使用定型的神经网络,预测 1961 年 1 月(定型数据范围过后的第一时间段)的旅客计数。这就是所谓的“外推法”。预测的旅客计数为 433。此值可用作 1961 年 2 月旅客计数的预测指标变量,以此类推。

为了能够更好地理解本文,需要拥有中等或更高水平编程技能,并掌握神经网络方面的基本知识,但不必对时序回归了解得面面俱到。虽然演示程序是使用 C# 进行编码,但也可以将代码重构为其他语言(如 Java 或 Python),应该不会遇到太多麻烦。演示程序因太长而无法在本文中全部展示,但可以在本文随附的文件下载中获取整个源代码。


时序回归

时序回归问题通常用折线图表示,如图 2 所示。蓝线表示从 1949 年 1 月一直到 1960 年 12 月 144 个未规范化的实际旅客计数(以千人为单位)。

浅红色的线表示神经网络时序模型生成的预测旅客计数。请注意,因为此模型使用的滚动窗口包含四个预测指标值,所以第一个预测旅客计数直到 5 月才出现。此外,我还在定型数据范围外,多预测了九个月的旅客计数。

这些预测旅客计数用红色虚线表示。

图 2:时序回归折线图

除了能够在定型数据范围外进行预测外,时序回归分析还可用于发现异常的数据点。演示程序的旅客计数数据不存在这种问题。可以看到,预测计数与实际计数非常相近。

例如,月份 t = 67 的实际旅客计数为 302(图 2 中心附近的蓝点),预测计数为 272。不过,如果假设月份 t = 67 的实际旅客计数为 400,则可以明显看出,月份 67 对应的实际旅客计数为离群值。

也可以使用编程方法,通过时序回归发现异常数据。例如,可以标记任何时间值,其中实际数据值与预测值的差值超过某固定阈值,如预测数据值与实际数据值的标准偏差的四倍。

演示程序

为了对演示程序进行编码,我启动了 Visual Studio,并新建了名为 Neural-TimeSeries 的 C# 控制台应用程序。虽然我使用的是 Visual Studio 2015,但演示程序并不非常依赖 .NET Framework,因此任何新发布的版本都可以正常运行。

在编辑器窗口中加载模板代码后,我右键单击了“解决方案资源管理器”窗口中的 Program.cs 文件,并将此文件重命名为“NeuralTimeSeriesProgram.cs”,然后就是允许 Visual Studio 为我自动重命名类 Program。

在模板生成的代码顶部,我删除了所有不必要的 using 语句,仅留下引用顶级 System 命名空间的语句。

图 3 展示了整体程序结构(为节省空间,进行了少量小幅改动)。

using System;namespace NeuralTimeSeries
{  class NeuralTimeSeriesProgram
  {    static void Main(string[] args)
    {
      Console.WriteLine("Begin times series demo");
      Console.WriteLine("Predict airline passengers ");
      Console.WriteLine("January 1949 to December 1960 ");      double[][] trainData = GetAirlineData();
      trainData = Normalize(trainData);
      Console.WriteLine("Normalized training data:");
      ShowMatrix(trainData, 5, 2, true);  // first 5 rows

      int numInput = 4; // Number predictors
      int numHidden = 12;      int numOutput = 1; // Regression

      Console.WriteLine("Creating a " + numInput + "-" + numHidden +        "-" + numOutput + " neural network");
      NeuralNetwork nn = new NeuralNetwork(numInput, numHidden,
        numOutput);      int maxEpochs = 10000;      double learnRate = 0.01;      double[] weights = nn.Train(trainData, maxEpochs, learnRate);
      
      Console.WriteLine("Model weights and biases: ");
      ShowVector(weights, 2, 10, true);      double trainAcc = nn.Accuracy(trainData, 0.30); 
      Console.WriteLine("\nModel accuracy (+/- 30) on training " +        "data = " + trainAcc.ToString("F4"));      double[] future = new double[] { 5.08, 4.61, 3.90, 4.32 };      double[] predicted = nn.ComputeOutputs(future); 
      Console.WriteLine("January 1961 (t=145): ");
      Console.WriteLine((predicted[0] * 100).ToString("F0"));

      Console.WriteLine("End time series demo ");
      Console.ReadLine();
    } // Main

    static double[][] Normalize(double[][] data) { . . }    static double[][] GetAirlineData() {. . }    static void ShowMatrix(double[][] matrix, int numRows,      int decimals, bool indices) { . . }    static void ShowVector(double[] vector, int decimals,      int lineLen, bool newLine) { . . }  public class NeuralNetwork { . . }
} // ns

图 3:NeuralTimeSeries 程序结构

演示程序使用从头开始实现的单一隐藏层简单神经网络。此外,也可以结合使用本文介绍的技术和神经网络库(如 Microsoft 认知工具包 (CNTK))。

演示程序从设置定型数据入手,如图 4 所示。

double[][] trainData = GetAirlineData();
trainData = Normalize(trainData);
Console.WriteLine("Normalized training data:");
ShowMatrix(trainData, 5, 2, true);

Method GetAirlineData is defined as:static double[][] GetAirlineData()
{  double[][] airData = new




    
 double[140][];
  airData[0] = new double[] { 112, 118, 132, 129, 121 };
  airData[1] = new double[] { 118, 132, 129, 121, 135 };
...
  airData[139] = new double[] { 606, 508, 461, 390, 432 };  return airData;
}

图 4:设置定型数据

随后,对滚动窗口数据进行硬编码,窗口大小为 4。在编写时序程序之前,我编写了一个简短的实用工具程序,以根据原始数据生成滚动窗口数据。

在大多数的非演示情况下,请从文本文件中读取原始数据,再以编程方式生成滚动窗口数据,其中窗口大小已经过参数化,可以试验不同的大小。

Normalize 方法直接用所有数据值除以常数 100。我这样做纯粹是出于实际原因。第一次尝试时,我使用的是非规范化数据,结果非常糟糕,但在执行规范化后,结果得到了很大的改善。从理论上讲,使用神经网络时,数据无需进行规范化,但在实践中,规范化往往会产生巨大影响。

下面创建了神经网络:

int numInput = 4; 
int numHidden = 12;int numOutput = 1; 
NeuralNetwork nn =  new NeuralNetwork(numInput, numHidden, numOutput);


输入节点数量被设置为 4 个,因为每个滚动窗口都有四个预测指标值。输出节点数量被设置为 1 个,因为每组窗口值都用于预测下个月的情况。隐藏节点数量被设置为 12 个,这是通过反复试验确定的。

神经网络通过以下语句进行定型和评估:

int maxEpochs = 10000;double learnRate = 0.01;double[] weights = nn.Train(trainData, maxEpochs, learnRate);
ShowVector(weights, 2, 10, true);


Train 方法使用基本的反向传播算法。变体有许多,包括使用动量或自适应学习率提高定型速度,以及使用 L1/L2 正则化或 Dropout 防止模型过度拟合。帮助程序方法 ShowVector 显示矢量,其中实际值的格式为 2 位小数,每行 10 个值。

创建神经网络时序模型后,评估它的预测准确度:

double trainAcc = nn.Accuracy(trainData, 0.30);
Console.WriteLine("\nModel accuracy (+/- 30) on " + 
  " training data = " + trainAcc.ToString("F4"));


对于时序回归,确定预测值的正确与否取决于所要调查的问题。对于航空旅客数据,如果未规范化的预测计数与实际原始计数的差值不超过 30,Accuracy 方法就会将预测旅客计数标记为正确。在演示数据中,t = 5 至 t = 9 的前五个预测计数都正确,但 t = 10 的预测计数不正确:

t  actual  predicted
= = = = = = = = = = =
 5   121     129
 6   135     128
 7   148     137
 8   148     153
 9   136     140
10   119     141


演示程序最后使用末尾四个旅客计数(t = 141 至 t = 144),预测定型数据范围外首个时间段(t = 145,即 1961 年 1 月)的旅客计数:

double[] predictors = new double[] { 5.08, 4.61, 3.90, 4.32 };double[] forecast = nn.ComputeOutputs(predictors); 
Console.WriteLine("Predicted for January 1961 (t=145): ");
Console.WriteLine((forecast[0] * 100).ToString("F0"));
Console.WriteLine("End time series demo");


请注意,由于时序模型是使用规范化数据(除以 100)进行定型,因此预测时也进行了规范化,所以演示程序在显示时用预测值乘以 100。

用于时序分析的神经网络

定义神经网络时,必须指定隐藏层节点和输出层节点使用的激活函数。简要地说,我建议使用双曲正切 (tanh) 函数激活隐藏层,并使用恒等函数激活输出层。

使用神经网络库或系统(如 Microsoft CNTK 或 Azure 机器学习)时,必须明确指定激活函数。演示程序会对这些激活函数进行硬编码。关键代码出现在 ComputeOutputs 方法中。下面计算了隐藏节点值:

for (int j = 0; j < numHidden; ++j) 
  for (int i = 0; i < numInput; ++i)
    hSums[j] += this.iNodes[i] * this.ihWeights[i][j];for (int i = 0; i < numHidden; ++i)  // Add biases
  hSums[i] += this.hBiases[i];for (int i = 0; i < numHidden; ++i)   // Apply activation
  this.hNodes[i] = HyperTan(hSums[i]); // Hardcoded







请到「今天看啥」查看全文

推荐文章
中国半导体论坛  ·  半导体精品公众号推荐!
11 小时前
中国半导体论坛  ·  半导体精品公众号推荐!
11 小时前
半导体行业联盟  ·  中国芯50强(新锐)
昨天
半导体行业联盟  ·  DeepSeek:2025半导体封测十大预测!
3 天前
半导体行业联盟  ·  突发,意法裁员3000人!
5 天前
半导体行业联盟  ·  软银 + OpenAI,1800亿!
5 天前
正和岛  ·  吴晓波:量布裁衣,量米做饭
7 年前