tf-dev-cert-gd-merge-3

60 阅读1小时+

TensorFlow 开发者认证指南(四)

原文:annas-archive.org/md5/a13e63613764fcdebedea75ef780532d

译者:飞龙

协议:CC BY-NC-SA 4.0

第十二章:时间序列、序列与预测概述

时间序列横跨各行各业,涉及我们生活的方方面面。金融、医疗、社会科学、物理学——你说得出,时间序列数据就存在。它出现在监测我们环境的传感器中,社交媒体平台追踪我们的数字足迹,在线交易记录我们的财务行为,等等。这种按时间顺序排列的数据代表着随时间变化的动态过程,随着我们逐步数字化地球,这类数据的量和其重要性将呈指数级增长。

时间序列遵循时间顺序,捕捉事件的发生。时间序列的这种时间特性赋予了它与横截面数据的独特区别。当我们聚焦于时间序列数据时,可以观察到如趋势、季节性、噪声、周期性和自相关等特征。这些独特的特性使得时间序列数据蕴含丰富的信息,但也为我们带来了一系列独特的挑战,我们必须克服这些挑战,以充分利用这种数据类型的潜力。借助TensorFlow等框架,我们可以利用过去的模式做出关于未来的明智决策。

本章将涵盖以下内容:

  • 时间序列分析——特征、应用与预测技术

  • 时间序列预测的统计学技术

  • 使用神经网络为预测准备数据

  • 使用神经网络进行销售预测

本章结束时,你将获得理论上的见解和实际操作经验,学习如何使用统计学和深度学习技术构建、训练和评估时间序列预测模型。

时间序列分析——特征、应用与预测技术

我们知道,时间序列数据是由数据点在时间序列中的顺序定义的。想象我们正在预测伦敦的能耗模式。多年来,由于城市化,能源消耗逐年增加——这表示一个正向上升的趋势。每年冬天,我们预期能源消耗会增加,因为更多的人需要为保持温暖而加热家中和办公室的空间。季节性的天气变化也导致了能源利用的季节性波动。此外,我们还可能因为一次重大体育赛事,看到能源消耗的异常激增,这可能是因为赛事期间大量客人的涌入。这种情况会在数据中产生噪声,因为这些事件通常是一次性的或不规律发生的。

在接下来的章节中,让我们一起探索时间序列的特征、类型、应用以及建模时间序列数据的技术。

时间序列的特征

为了有效地构建高效的预测模型,我们需要清楚地理解时间序列数据的基本性质。我们可能会遇到具有正向上升趋势、月度季节性、噪声和自相关性的时间序列数据,而下一个我们处理的时间序列可能具有年度季节性和噪声,但数据中没有明显的自相关性或趋势。

了解时间序列数据的这些数据特性,使我们能够具备必要的细节来做出明智的预处理决策。例如,如果我们处理的数据集波动性较大,我们对这一点的了解可能会促使我们在预处理步骤中应用平滑技术。在本章后面,我们将构建用于预测时间序列的统计学和深度学习模型,我们将看到对时间序列数据特性的理解如何指导我们在新特征工程、选择最优超参数值以及模型选择决策方面的判断。让我们来审视时间序列的特点:

  • 趋势:趋势是指时间序列在长期内所呈现的总体方向。我们可以将趋势视为时间序列数据的整体大局。趋势可以是线性的,如图 12**.1所示,或者是非线性的(如二次型和指数型);它们也可以是正向(上升)或负向(下降)的:

图 12.1 – 显示股票价格随时间变化的正向趋势的图表

图 12.1 – 显示股票价格随时间变化的正向趋势的图表

趋势分析使数据专业人员、企业和决策者能够做出关于未来的明智决策。

  • 季节性:季节性是指在特定周期内(如日、周、月或年)定期发生的重复性周期。这些变化通常是季节性波动的副产品;例如,位于居民区的零售商店可能在周末的销售量比工作日更高(每周季节性)。同一家商店在 12 月的假期季节可能会见证销售激增,并在节庆过后不久出现销售下降(年度季节性),如图 12**.2所示:

图 12.2 – 显示零售商店的年度季节性的图表

图 12.2 – 显示零售商店的年度季节性的图表

  • 周期性:周期性指的是在较长时间内,时间序列中发生的不规则周期性波动。与季节性不同,这些周期具有长期性质,其持续时间和幅度不规律,因此相比季节性,它们更难预测。经济周期是周期性的一个典型例子。这些周期受到通货膨胀、利率和政府政策等多种因素的影响。由于周期性的不规则性,预测周期的时机、持续时间和幅度是相当具有挑战性的。通常需要先进的统计学和机器学习模型来准确建模和预测周期模式。

  • 自相关:自相关是一个统计概念,指的是时间序列与其滞后版本之间的相关性,如图 12.3所示。

图 12.3 – 显示自相关的图表

图 12.3 – 显示自相关的图表

它通常被称为序列相关,衡量数据点与其过去值之间的关联程度。自相关可以是正相关或负相关,值的范围从 -1 到 1。

  • 噪声:噪声是任何现实世界数据中的固有部分。它指的是数据中无法通过模型解释的随机波动,也无法通过任何已知的潜在因素、模式或结构性影响解释。这些波动可能来自各种来源,如测量误差或意外事件。例如,在金融市场中,政治公告等未预测到的事件可能会产生噪声,导致股价偏离其潜在趋势。噪声呈现出随机性和无法解释的变化,预测时间序列数据时可能需要平滑这些波动。

我们已经讨论了一些可能单独或共同出现在时间序列数据中的重要特征。接下来,让我们看看时间序列数据的类型。

时间序列数据的类型

时间序列可以分为以下几类:

  • 平稳与非平稳

  • 单变量和多变量

平稳与非平稳时间序列

平稳时间序列是指其统计特性(均值、方差和自相关)随时间保持恒定的时间序列。这种序列展示出周期性的模式和行为,未来很可能会重复出现。非平稳时间序列则相反。它不是平稳的,我们通常在许多现实场景中发现这类时间序列,其中序列可能会展示趋势或季节性。例如,我们可以预期滑雪度假村的月度销售在冬季达到峰值,而在淡季下滑。这个季节性成分会影响序列的统计特性。

单变量和多变量时间序列

单变量时间序列是一种只跟踪单一指标随时间变化的时间序列。例如,我们可以使用智能手表来跟踪我们每天步数的变化。另一方面,当我们跟踪多个指标随时间变化时,就会有多变量时间序列,如图 12.4所示。在这张图表中,我们看到通货膨胀和工资增长之间的相互关系。随着时间的推移,我们可以看到通货膨胀持续超过工资增长,使得普通人的实际收入和购买力下降:

图 12.4 – 多变量时间序列展示通货膨胀与工资增长的关系

图 12.4 – 多变量时间序列展示通货膨胀与工资增长的关系

多变量时间序列分析可以帮助我们考虑多个变量之间随时间变化的依赖关系和相互作用。接下来,让我们深入了解时间序列数据的重要性及其各种应用。

时间序列的应用

我们已经讨论了时间序列数据的类型和属性。通过应用机器学习技术,我们可以利用这些数据类型中蕴含的大量信息。接下来,让我们来看一些机器学习在时间序列中的重要应用:

  • 预测:我们可以应用机器学习模型来预测时间序列;例如,我们可能希望预测零售商店未来的销售情况,从而指导库存决策。如果我们分析商店的销售记录,可能会发现一些规律,比如在假期季节销售增加,或者在特定月份销售下降。我们可以利用这些规律来训练模型,从而做出关于商店未来销售的预测,帮助相关利益方有效规划预期需求。

  • 插补数据:缺失值在分析或预测时间序列数据时可能会带来重大挑战。一个有效的解决方案是应用插补方法,允许我们使用替代值填补缺失的数据点。例如,在*图 12.5 (a)中,我们看到了一张展示一年温度值的图。我们很快注意到一些温度记录缺失了。在插补方法的帮助下,我们可以利用相邻天的数据来估算这些缺失值,如图 12.5 (b)*所示:

图 12.5 – 展示随时间变化的温度图(a)有缺失值(b)没有缺失值

图 12.5 – 展示随时间变化的温度图(a)有缺失值(b)没有缺失值

通过填补缺失值,我们现在拥有了一个完整的数据集,可以更好地用于分析和预测。

  • 异常检测:异常是指显著偏离常规的数据点。我们可以应用时间序列分析来检测异常并潜在地识别出重要问题。例如,在信用卡交易中,如图 12.6所示,一笔异常大的交易可能表示欺诈活动:

图 12.6 – 交易金额激增的图示

图 12.6 – 交易金额激增的图示

通过使用时间序列分析来识别这些异常,银行可以迅速采取措施,减少潜在损失。

  • 趋势分析:了解趋势可以为潜在现象提供宝贵的洞察。例如,国际能源机构显示,2022 年所有新售汽车中有 14%是电动汽车。这个趋势可能表明人们正在转向更加可持续的交通方式(见www.iea.org/reports/global-ev-outlook-2023/executive-summary)。

  • 季节性分析:时间序列的另一个有用应用是季节性分析。这对于指导能源消耗规划和基础设施扩展需求可能非常有用。

我们现在已经了解了一些时间序列数据的重要应用。接下来,让我们看看一些重要的时间序列预测技术。

时间序列预测技术

在本书中,我们将探讨两种主要的时间序列预测技术:统计方法和机器学习方法。统计方法通过使用数学模型来捕捉时间序列数据的趋势、季节性和其他组成部分,常见的模型包括自回归综合滑动平均ARIMA)和季节性与趋势分解使用 LOESSSTL)。然而,这些方法超出了本书的范围。在这里,我们将使用更简单的统计方法,如朴素预测移动平均来建立我们的基准,然后再应用不同的机器学习方法。在本章中,我们将重点使用深度神经网络DNNs),而在下一章中,我们将应用递归神经网络RNNs)和长短期记忆网络LSTMs)。

每种方法都有其优缺点,最佳的时间序列预测方法在很大程度上取决于数据的具体特征和当前的问题。值得强调的是,时间序列预测是一个广泛的领域,仍有其他方法超出了本书的范围,您可以在以后的阶段进行探索。在我们开始建模时间序列问题之前,让我们先看看如何评估这种数据。

评估时间序列预测技术

为了有效评估时间序列预测模型,我们必须使用适当的指标来衡量其表现。在第三章《使用 TensorFlow 的线性回归》中,我们探讨了几种回归指标,如 MAE、MSE、RMSE 和 MAPE。我们可以使用这些指标来评估时间序列预测模型。然而,在本章中,我们将重点应用 MAE 和 MSE,以符合考试要求。我们使用 MAE 计算预测值与真实值之间绝对差异的平均值。通过这种方式,我们可以了解预测结果有多偏差。较小的 MAE 表示模型拟合较好。想象你是一名股票市场分析师,试图预测某只特定股票的未来价格。使用 MAE 作为评估指标,你可以清楚地了解你的预测与实际股票价格之间的平均差异。这些信息可以帮助你调整模型,做出更准确的预测,降低潜在的财务风险。

另一方面,MSE 计算的是预测值与实际值之间差异的平方平均值。通过对误差进行平方处理,MSE 比 MAE 对较大误差更加敏感,因此在误差较大的情况不利时,MSE 更为有用。例如,在处理电网时,精确的负荷预测至关重要,因此 MSE 尤为重要。考虑到这一点,现在让我们转向一个销售的使用案例,并应用我们的学习来预测未来的销售额。

零售店预测

想象一下,你作为一名机器学习工程师,刚刚接到一个新项目。佛罗里达州的一家快速增长的大型超市请求你的帮助。他们希望预测未来的评论,这将为他们扩展商店并满足预期需求提供指导。你负责根据 Tensor 超市提供的历史数据构建一个预测模型。让我们一起看看你如何解决这个问题,因为公司正指望你。我们开始吧!

  1. 我们首先导入项目所需的库:

    import pandas as pd
    
    import numpy as np
    
    import matplotlib.pyplot as plt
    

    在这里,我们导入numpymatplotlib用于数值分析和可视化,pandas用于数据转换。

  2. 接下来,我们加载时间序列数据:

    df = pd.read_csv('/content/sales_data.csv')
    
    df.head()
    

    在这里,我们加载数据并使用head函数查看数据的前五行。当我们运行代码时,看到给定数据中的销售第一天是2013-01-01。接下来,让我们查看一些统计信息,以便了解我们手头的数据。

  3. 使用以下代码检查数据类型和汇总统计信息:

    # Check data types
    
    print(df.dtypes)
    
    # Summary statistics
    
    print(df.describe())
    

    当我们运行代码时,它返回的数据类型为float64,并显示我们销售数据的关键汇总统计信息。使用describe函数,我们得到了 3,653 个数据点的计数。这指示了一个 10 年的每日数据周期。我们还看到每日的平均销售额大约为 75 美元,这给了我们一个关于数据集中趋势的直观感受。每日销售额有着相当的波动,标准差为20.2。最小值和最大值显示销售额的范围从 22 美元到 128 美元,表明存在显著的波动。第 25 和第 75 百分位数分别为60.2789.18,显示低销售量的日子销售额大约为 60 美元,而高销售量的日子销售额则大约为 90 美元。接下来,我们通过绘图进一步探索数据。

  4. 让我们可视化数据:

    #Sales data plot
    
    df.set_index('Date').plot()
    
    plt.ylabel('Sales')
    
    plt.title('Sales Over Time')
    
    plt.xticks(rotation=90)
    
    plt.show()
    

    代码返回了如图 12.7所示的图表,表示公司在 10 年期间的销售情况:

图 12.7 – 显示销售随时间变化的图表

图 12.7 – 显示销售随时间变化的图表

图 12.7中的图表中,我们可以观察到一个整体的积极上升趋势,这可能表明经济增长或成功的商业策略,如新产品发布和有效的市场营销。一个明显的年度季节性波动也浮现出来;这可能表明公司销售的是季节性商品,且有着每年波动的需求。同时,我们也观察到存在一些噪声。这可能是由于天气变化、随机事件或竞争对手的进入所导致。数据中的上升趋势显示了销售额随着时间的推移表现出良好的增长。然而,季节性效应和噪声因素揭示了总体趋势下更为复杂的动态。接下来,让我们探索如何对数据进行分区。

数据分区

在时间序列预测中,我们通常将数据集分成不同的部分:用于训练机器学习模型的训练期、用于模型调优和评估的验证期,以及用于评估未见数据性能的测试期。这个过程被称为固定分区。另一种方法是滚动前进分区,我们将在接下来的部分讨论这一方法。

我们的销售数据展示了季节性波动,因此,我们必须将数据按方式划分,以确保每个分区都包含完整的季节周期。我们这样做是为了确保不会遗漏一个或多个分区中的重要季节性模式。虽然这种方法与典型的数据分区方法有所不同,但我们看到在处理其他机器学习问题时,通常是通过随机抽样来形成训练集、验证集和测试集,基本目标保持一致。我们在训练数据上训练模型,使用验证数据进行调优,并在测试数据上评估模型。然后,我们可以将验证数据合并到训练数据中,以便利用最新的信息来预测未来的数据。

通常,确保训练集足够大以捕捉数据中所有相关模式是一个好主意,包括数据的季节性行为。在设置验证集的大小时,必须找到平衡点。虽然更大的验证集能够提供更可靠的模型性能估计,但它也会减少训练集的大小。你还应该记得在做出最终预测之前,使用整个数据集(将训练集和验证集合并)重新训练最终模型。这种策略最大化了模型从数据中学习的量,可能提高其对未来未见数据的预测性能。同样,避免在分割数据之前打乱数据,因为这会破坏时间顺序,导致误导性结果。在固定划分中,我们通常使用按时间顺序划分的方法,训练数据应该来自最早的时间戳,接着是验证集,最后是包含最新时间戳的测试集。让我们将销售数据分为训练集和验证集:

# Split data into training and validation sets
split_time = int(len(df) * 0.8)
train_df = df.iloc[:split_time]
valid_df = df.iloc[split_time:]

在这里,我们将数据分为训练集和验证集。我们取 80%的数据(len(df) * 0.8),在这个例子中是 8 年的数据用于训练,最后 2 年的数据用于验证。我们使用int函数确保分割时间是整数,以便进行索引操作。我们设置了训练集和验证集,使用分割时间之前的所有数据作为训练集,分割时间之后的所有数据作为验证集。

接下来,让我们绘制训练集和验证集的数据:

plt.figure(figsize=(12, 9))
# Plotting the training data in green
plt.plot(train_df['Date'], train_df['Sales'], 'green',
    label = 'Training Data')
plt.plot(valid_df['Date'], valid_df['Sales'], 'blue',
    label = 'Validation Data')
plt.title('Fixed Partitioning')
plt.xlabel('Date')
plt.ylabel('Sales')
all_dates = np.concatenate([train_df['Date'],
    valid_df['Date']])
plt.xticks(all_dates[::180], rotation=90)
plt.legend()
plt.tight_layout()
plt.show()

这段代码展示了销售数据的划分,将训练集和验证集分别用绿色和蓝色标记,x轴表示日期,y轴表示销售额。为了提高可读性,我们将x轴的刻度设置为每 180 天一次:

图 12.8 – 显示固定划分的图表

图 12.8 – 显示固定划分的图表

图 12.8中,我们将销售数据划分为 8 年的训练数据和 2 年的验证数据。在这种情况下,我们的测试集将是来自未来的数据。这是为了确保模型在时间序列的最早部分进行训练,在最近的过去进行验证,并在未来进行测试。另一种划分时间序列数据的方法被称为滚动前移划分或“前行”验证。在这种方法中,我们从一个较短的训练期开始,并逐渐增加训练期的长度。对于每个训练期,接下来的时间段作为验证集。这种方法模拟了现实生活中的情况,我们会随着新数据的到来不断重新训练模型,并利用它来预测下一时期。让我们讨论一下我们的第一种预测方法,叫做天真预测。

天真预测

朴素预测是时间序列分析中最简单的方法之一。朴素预测的原理是将所有预测值设为最后一个观察点的值。这就是为什么它被称为“朴素”的原因。它是一种假设未来值可能与当前值相同的方法。尽管其简单,但朴素预测通常可以作为时间序列预测的一个良好基准;然而,它的表现可能会根据时间序列的特征有所不同。

让我们看看如何在代码中实现这个:

  1. 让我们实现朴素预测:

    # Apply naive forecast
    
    df['Naive_Forecast'] = df['Sales'].shift(1)
    
    df.head()
    

    为了实现朴素预测方法,每个预测值仅设置为前一个时间步的实际观察值,通过将Sales列向前移动一个单位来实现。我们使用df.head()来显示 DataFrame 的前五行,以快速概览销售数据和朴素预测:

图 12.9 – 显示销售和朴素预测的 DataFrame 快照

图 12.9 – 显示销售和朴素预测的 DataFrame 快照

图 12.9中的表格来看,我们看到第一个预测值是不可用的。这是因为该方法仅仅取从验证数据前一步开始,直到序列倒数第二个值的所有值。这实际上将时间序列向未来移动了一个时间步长。

  1. 我们创建一个用于绘图的函数:

    def plot_forecast(validation_df, forecast_df,
    
        start_date=None, end_date=None,
    
        plot_title='Naive Forecasting',
    
        forecast_label='Naive Forecast'):
    
            if start_date:
    
                validation_df = validation_df[
    
                    validation_df['Date'] >= start_date]
    
                forecast_df = forecast_df[forecast_df[
    
                    'Date'] >= start_date]
    
            if end_date:
    
                validation_df = validation_df[
    
                    validation_df['Date'] <= end_date]
    
                forecast_df = forecast_df[forecast_df[
    
                    'Date'] <= end_date]
    
        # Extract the dates in the selected range
    
        all_dates = validation_df['Date']
    
        plt.figure(figsize=(12, 9))
    
        plt.plot(validation_df['Date'],
    
            validation_df['Sales'],
    
            label='Validation Data')
    
        plt.plot(forecast_df['Date'],
    
            forecast_df['Naive_Forecast'],
    
            label=forecast_label)
    
        plt.legend(loc='best')
    
        plt.title(plot_title)
    
        plt.xlabel('Date')
    
        plt.ylabel('Sales')
    
        # Set x-ticks to every 90th date in the selected range
    
        plt.xticks(all_dates[::90], rotation=90)
    
        plt.legend()
    
        plt.tight_layout()
    
        plt.show()
    

    我们构建一个实用的图表来生成真实和预测验证值的图形。该函数接受我们的验证数据的预测值和真实值,并包括开始日期、结束日期、图表标题和标签。该函数为我们提供了灵活性,可以深入探讨图表中的不同兴趣区域。

  2. 接下来,让我们绘制朴素预测:

    plot_forecast(valid_df, valid_df,
    
        plot_title='Naive Forecasting',
    
        forecast_label='Naive Forecast')
    

    我们传入所需的参数并运行代码,生成以下图表:

图 12.10 – 使用朴素方法的时间序列预测

图 12.10 – 使用朴素方法的时间序列预测

图 12.10中的图表显示了预测值和验证数据的真实值。由于值接近,图表看起来有点密集,因此让我们放大,以帮助我们在视觉上检查预测的效果。

  1. 让我们看一下特定的时间范围:

    plot_forecast(valid_df, valid_df,
    
        start_date='2022-01-01', end_date='2022-06-30')
    

    我们将开始日期和结束日期参数设置为2022-01-012022-06-30,得到的图表如下:

图 12.11 – 使用朴素方法的放大时间序列预测

图 12.11 – 使用朴素方法的放大时间序列预测

图 12.11中的图表可以看出,预测开始得晚了一步,因为朴素预测向未来移动了一个时间步。

  1. 接下来,让我们评估朴素方法的表现:

    # Compute and print mean squared error
    
    mse = tf.keras.metrics.mean_squared_error(
    
        valid_df['Sales'], valid_df
    
            ['Naive_Forecast']).numpy()
    
    print('Mean Squared Error:', mse)
    
    # Compute and print mean absolute error
    
    mae = tf.keras.metrics.mean_absolute_error(
    
        valid_df['Sales'],
    
        valid_df['Naive_Forecast']).numpy()
    
    print('Mean Absolute Error:', mae)
    

    我们使用.numpy()提供的metrics函数,将 TensorFlow 张量的结果转换为 NumPy 数组。当我们运行代码时,得到朴素预测的均方误差(MSE)为45.22,朴素预测的平均绝对误差(MAE)为5.43。回想一下,对于 MSE 和 MAE 值,较低的值总是更好。

在我们探索其他预测技术时,记住这一点。朴素预测可以作为基准,用于比较更复杂模型的表现。接下来,我们将研究另一种统计方法,称为移动平均。

移动平均

移动平均是一种通过将每个数据点替换为邻近数据点的平均值来平滑时间序列数据的技术。在此过程中,我们生成一个新的序列,其中的数据点是原始数据序列中的数据点的平均值。此方法中的关键参数是窗口宽度,它决定了计算平均值时包括的连续原始数据点的数量。

“移动”一词指的是沿时间序列滑动窗口来计算平均值,从而生成一个新的序列。

移动平均的类型

时间序列分析中常用的两种主要移动平均类型是中心移动平均和滞后移动平均:

  • 中心移动平均:中心移动平均围绕中心点(t)计算平均值。它使用兴趣时间点前后的数据进行可视化,如图 12.12所示。中心移动平均能够提供数据趋势的均衡视图,但由于它需要未来数据,因此不适用于预测,因为在进行预测时我们无法获得未来的值。中心移动平均适用于可视化和时间序列分析。

图 12.12 – 显示中心移动平均的图形

图 12.12 – 显示中心移动平均的图形

中心移动平均能够提供数据趋势的均衡视图,但由于它需要未来数据,因此不适用于预测,因为在进行预测时我们无法获得未来的值。中心移动平均适用于可视化和时间序列分析。

  • 滞后移动平均:滞后移动平均,也称为滚动平均或运行平均,通过使用最近的n个数据点来计算平均值。此方法仅需要过去的数据点,如图 12.13所示,非常适合用于预测。

图 12.13 – 显示滞后移动平均的图形

图 12.13 – 显示滞后移动平均的图形

计算滞后移动平均的第一步是选择窗口宽度 (W)。这个选择可以依赖于各种因素,例如序列的模式和你希望实现的平滑程度。较小的窗口宽度能够更紧密地跟踪快速变化,但这也可能会带来更多噪音。另一方面,较大的窗口宽度能够提供更平滑的曲线,但可能会错过一些短期波动。

让我们看看如何实现移动平均:

# Calculate moving average over a 30-day window
window =30
df['Moving_Average_Forecast'] = df['Sales'].rolling(
    window=window).mean().shift(1)

代码使用 pandas 的rolling函数来计算 Sales 数据的 30 天窗口移动平均,然后将结果向前移动一步,以模拟下一时间步的预测,并将结果存储在新的 Moving_Average_Forecast 列中。你可以将其视为使用 30 天的销售数据来预测第 31 天的销售。

我们调用 plot_forecast 函数来绘制验证数据和移动平均预测数据。我们可以在图 12.14中看到结果图:

图 12.14 – 使用移动平均法进行时间序列预测

图 12.14 – 使用移动平均法进行时间序列预测

图 12.14中,移动平均预测是使用过去 30 天的销售数据计算出来的。较小的窗口大小,例如 7 天的窗口,将比 30 天的移动平均更加贴近实际销售,但也可能会捕捉到数据中的更多噪音。

下一步逻辑就是再次使用metric函数评估我们的模型。这次,我们将移动平均预测与真实的验证值进行对比:

# Compute and print mean squared error
mse = tf.keras.metrics.mean_squared_error(
    valid_df['Sales'],
    df.loc[valid_df.index,
    'Moving_Average_Forecast']).numpy()
print('Mean Squared Error:', mse)
# Compute and print mean absolute error
mae = tf.keras.metrics.mean_absolute_error(
    valid_df['Sales'],
    df.loc[valid_df.index,
    'Moving_Average_Forecast']).numpy()
print('Mean Absolute Error:', mae)
print('Mean Squared Error for moving average forecast:',
    mse)

当我们运行代码时,移动平均预测的 MSE 为 55.55,MAE 为 6.05。这两个值比我们的基准结果要差得多。如果你将窗口大小改为 7 天,我们最终得到的 MSE 和 MAE 分别为 48.575.61,这是一个更好的结果,但比我们天真的方法稍差。你可以尝试更小的窗口大小,看看结果是否能够超过基准值。

然而,我们需要注意的是,使用移动平均法的前提假设是平稳性。我们知道我们的时间序列同时包含趋势和季节性,那么我们该如何在这些数据上实现平稳性呢?这是否能帮助我们实现更低的 MAE?为了实现平稳性,我们使用一个叫做差分的概念。接下来让我们讨论差分,并看看如何应用它,以及它是否能帮助我们实现更低的 MAE 和 MSE。

差分

差分是用于实现时间序列平稳性的一种方法。它通过计算连续观测值之间的差异来工作。其逻辑是,尽管原始序列可能存在趋势并且非平稳,但序列值之间的差异可能是平稳的。通过对数据进行差分,我们可以去除趋势和季节性,从而使序列平稳,并使其适合用于移动平均预测模型。这可以显著提高模型的准确性,从而提高预测的可靠性。让我们在以下代码中查看这一点:

# Perform seasonal differencing
df['Differenced_Sales'] = df['Sales'].diff(365)
# Plotting the differenced sales data
plt.figure(figsize=(12, 9))
# Plotting the differenced data
plt.plot(df['Date'], df['Differenced_Sales'],
    label = 'Differenced Sales')
plt.title('Seasonally Differenced Sales Data')
plt.xlabel('Date')
plt.ylabel('Differenced Sales')
# Select dates to be displayed on x-axis
all_dates = df['Date']
plt.xticks(all_dates[::90], rotation=90)
plt.legend()
plt.tight_layout()
plt.show()

上述代码块对我们的时间序列数据应用了差分。我们首先生成一个新序列,其中每个值与 365 天前的值之间存在差异。我们这样做是因为我们知道数据具有年度季节性。接下来,我们绘制我们的数据:

图 12.15 – 显示差分后销售时间序列的图表

图 12.15 – 显示差分后销售时间序列的图表

我们可以看到,图 12.15 中的图表没有趋势或季节性。因此,我们已经实现了使用移动平均法时所需的平稳性假设。

现在让我们恢复趋势和季节性:

window=7
# Restore trend and seasonality
df['Restored_Sales'] = df['Sales'].shift(
    365) + df['Differenced_Sales']
# Compute moving average on the restored data
df['Restored_Moving_Average_Forecast'] = df[
    'Restored_Sales'].rolling(
    window=window).mean().shift(1)
# Split into training and validation again
train_df = df.iloc[:split_time]
valid_df = df.iloc[split_time:]
# Get forecast and true values on validation set
forecast = valid_df['Restored_Moving_Average_Forecast']
true_values = valid_df['Sales']

在这里,我们将季节性重新融入到经过差分的时间序列数据中,然后对这个恢复的数据应用移动平均预测。我们使用一个 7 天窗口进行移动平均计算。通过将平移后的销售数据添加到差分后的销售数据中恢复趋势和季节性后,我们计算这些恢复的销售数据在所选窗口大小上的移动平均值,然后将结果序列向前移动一步进行预测。然后,我们再次将数据拆分为训练集和验证集。使用相同的拆分时间,以确保与之前的拆分一致。这对于确保我们正确评估模型至关重要。最后,我们通过从验证集中提取Restored_Moving_Average_ForecastSales值,准备进行预测值和真实销售值的评估:

图 12.16 – 恢复季节性和趋势后的销售预测

图 12.16 – 恢复季节性和趋势后的销售预测

图 12.16中的图表中,橙色线表示我们将过去的值重新加入以恢复趋势和季节性后的预测结果。回想一下,我们使用了一个大小为7的窗口,这使得我们得到了更低的 MAE 和 MSE 值。现在,我们本质上是利用差分序列的预测来预测从一年到下一年的变化,然后将这些变化加到一年前的值上,从而得到最终的预测值。

当我们计算恢复季节性和趋势的预测的 MSE 时,结果是 48.57,而恢复季节性和趋势的 MAE 为 5.61,这两个值明显低于未使用差分时的结果。我们还可以尝试平滑数据中的噪声,以提高 MAE 和 MSE 的得分。接下来,让我们看看如何使用机器学习,特别是使用 TensorFlow 的神经网络进行预测。

使用机器学习进行时间序列预测

到目前为止,我们已经使用统计方法取得了相当不错的结果。接下来,我们将使用深度学习技术对时间序列数据进行建模。我们将从掌握如何设置窗口数据集开始。我们还将讨论一些概念,如洗牌和批处理,并看看如何构建和训练一个神经网络来解决我们的销售预测问题。让我们首先掌握如何使用 TensorFlow 工具准备时间序列数据,以便使用窗口数据集方法进行建模:

  1. 我们从导入所需的库开始:

    import tensorflow as tf
    
    import numpy as np
    

    在这里,我们将使用 NumPy 和 TensorFlow 准备并处理我们的数据,使其符合建模所需的结构。

  2. 让我们创建一个简单的数据集。这里我们假设数据由两周的温度值组成:

    # Create an array of temperatures for 2 weeks
    
    temperature = np.arange(1, 15)
    
    print(temperature)
    

    当我们打印温度数据时,得到以下内容:

    [ 1  2  3  4  5  6  7  8  9 10 11 12 13 14]
    

    我们得到一个从 1 到 14 的数值数组,其中我们假设温度从第一天的 1 上升到第 14 天的 14。虽然有些奇怪,但我们就假设是这样。

  3. 让我们创建窗口化数据。现在我们有了数据,接下来需要创建一个数据点的“窗口”:

    window_size = 3
    
    batch_size = 2
    
    shuffle_buffer = 10
    

    window_size 参数指的是正在考虑的数据窗口。如果我们将窗口大小设置为 3,这意味着我们将使用连续 3 天的温度值来预测下一天的温度。批量大小决定了在每次训练迭代中处理的样本数量,而 shuffle_buffer 指定了在洗牌数据时,TensorFlow 从多少个元素中随机采样。我们洗牌是为了避免顺序偏差;我们将在下一章进一步讨论这一点。

  4. 创建数据集的过程如下:

    dataset = tf.data.Dataset.from_tensor_slices(temperature)
    
    for element in dataset:
    
        print(element.numpy())
    

    这行代码用于从我们的温度数据创建一个 TensorFlow Dataset 对象。这个 Dataset API 是一个高层次的 TensorFlow API,用于读取数据并将其转换为机器学习模型可以使用的形式。接下来,我们遍历数据集并打印每个元素。我们使用 numpy() 将 TensorFlow 对象转换为 NumPy 数组。当我们运行代码时,我们得到数字 1-14;然而,现在它们已经准备好进行窗口化处理了。

  5. 接下来,让我们将温度数据转换成一个“窗口化”的数据集:

    dataset = dataset.window(window_size + 1, shift=1, 
    
        drop_remainder=True)
    
    for window in dataset:
    
        window_data = ' '.join([str(
    
            element.numpy()) for element in window])
    
        print(window_data)
    

    我们使用window方法来创建一个窗口数据集,其中每个窗口本身也是一个数据集。window_size + 1参数意味着我们将window_size个元素作为输入,接下来的一个元素作为标签。shift=1参数表示窗口每次移动一步。drop_remainder=True参数表示如果最后几个元素无法形成完整窗口,我们将丢弃它们。

    当我们打印出我们的window_data时,得到如下输出:

    After window:
    
    1 2 3 4
    
    2 3 4 5
    
    3 4 5 6
    
    4 5 6 7
    
    5 6 7 8
    
    6 7 8 9
    
    7 8 9 10
    
    8 9 10 11
    
    9 10 11 12
    
    10 11 12 13
    
    11 12 13 14
    

    我们看到现在窗口大小被设置为 3,+1部分将作为标签。由于我们将shift值设置为1,下一个窗口将从序列中的第二个值开始,在这个例子中是2。接下来,窗口将继续移动一步,直到我们处理完所有窗口数据。

  6. 展平数据的过程如下:

    dataset = dataset.flat_map(
    
        lambda window: window.batch(window_size + 1))
    
    for element in dataset:
    
        print(element.numpy())
    

    很容易将上一阶段创建的每个窗口视为一个单独的数据集。通过这段代码,我们将数据展平,使得每个窗口的数据作为主数据集中的一个单独批次打包。我们使用flat_map将其展平回张量数据集,并使用window.batch(window_size + 1)将每个窗口数据集转换为批次张量。当我们运行代码时,得到如下结果:

    After flat_map:
    
    [1 2 3 4]
    
    [2 3 4 5]
    
    [3 4 5 6]
    
    [4 5 6 7]
    
    [5 6 7 8]
    
    [6 7 8 9]
    
    [ 7  8  9 10]
    
    [ 8  9 10 11]
    
    [ 9 10 11 12]
    
    [10 11 12 13]
    
    [11 12 13 14]
    

    我们可以看到,窗口数据现在已被放入批次张量中。

  7. 打乱数据的过程如下:

    dataset = dataset.shuffle(shuffle_buffer)
    
    print("\nAfter shuffle:")
    
    for element in dataset:
    
        print(element.numpy())
    

    在这段代码中,我们对数据进行了打乱。这是一个重要步骤,因为打乱用于确保模型在训练过程中不会错误地学习到数据呈现顺序中的模式。当我们运行代码时,得到如下结果:

    After shuffle:
    
    [5 6 7 8]
    
    [4 5 6 7]
    
    [1 2 3 4]
    
    [11 12 13 14]
    
    [ 7  8  9 10]
    
    [ 8  9 10 11]
    
    [10 11 12 13]
    
    [ 9 10 11 12]
    
    [6 7 8 9]
    
    [2 3 4 5]
    
    [3 4 5 6]
    

    我们可以看到主数据集中的小型数据集已被打乱。但是,请注意,打乱后的数据集中,特征(窗口)和标签保持不变。

  8. 特征和标签映射的过程如下:

    dataset = dataset.map(lambda window: (window[:-1],
    
        window[-1]))
    
    print("\nAfter map:")
    
    for x,y in dataset:
    
        print("x =", x.numpy(), "y =", y.numpy())
    

    map方法对数据集的每个元素应用一个函数。在这里,我们将每个窗口分割成特征和标签。特征是窗口中的所有元素(除了最后一个元素,window[:-1]),标签是窗口中的最后一个元素,window[-1]。当我们运行代码时,看到如下结果:

    After map:
    
    features = [3 4 5] label = 6
    
    features = [1 2 3] label = 4
    
    features = [10 11 12] label = 13
    
    features = [ 8  9 10] label = 11
    
    features = [4 5 6] label = 7
    
    features = [7 8 9] label = 10
    
    features = [11 12 13] label = 14
    
    features = [5 6 7] label = 8
    
    features = [2 3 4] label = 5
    
    features = [6 7 8] label = 9
    
    features = [ 9 10 11] label = 12
    

    从打印结果来看,我们可以看到特征由三次观察值组成,并且下一个值是我们的标签。

  9. 批次处理和预取数据的过程如下:

    dataset = dataset.batch(batch_size).prefetch(1)
    
    print("\nAfter batch and prefetch:")
    
    for batch in dataset:
    
        print(batch)
    

    batch()函数将数据集分成大小为batch_size的批次。在这里,我们创建了大小为2的批次。prefetch(1)性能优化函数确保 TensorFlow 在处理当前批次时,总是有一个批次已准备好。在进行这些转换后,数据集准备好用于训练机器学习模型。让我们打印出批次数据:

    After batch and prefetch:
    
    (<tf.Tensor: shape=(2, 3), dtype=int64,
    
        numpy=array([[10, 11, 12],
    
            [ 3,  4,  5]])>, <tf.Tensor: shape=(2,),
    
            dtype=int64, numpy=array([13,  6])>)
    
    (<tf.Tensor: shape=(2, 3), dtype=int64,
    
        numpy=array([[ 9, 10, 11],
    
            [11, 12, 13]])>, <tf.Tensor: shape=(2,),
    
            dtype=int64, numpy=array([12, 14])>)
    
    (<tf.Tensor: shape=(2, 3), dtype=int64,
    
        numpy=array([[6, 7, 8],
    
            [7, 8, 9]])>, <tf.Tensor: shape=(2,),
    
            dtype=int64, numpy=array([ 9, 10])>)
    
    (<tf.Tensor: shape=(2, 3), dtype=int64,
    
        numpy=array([[1, 2, 3],
    
            [4, 5, 6]])>, <tf.Tensor: shape=(2,),
    
            dtype=int64, numpy=array([4, 7])>)
    
    (<tf.Tensor: shape=(2, 3), dtype=int64,
    
        numpy=array([[2, 3, 4],
    
            [5, 6, 7]])>, <tf.Tensor: shape=(2,),
    
            dtype=int64, numpy=array([5, 8])>)
    
    (<tf.Tensor: shape=(1, 3), dtype=int64, numpy=array(
    
        [[ 8,  9, 10]])>, <tf.Tensor: shape=(1,),
    
        dtype=int64, numpy=array([11])>)
    

我们看到数据集中的每个元素是一个特征和标签对的批次,其中特征是原始序列中window_size个值的数组,而标签是我们想要预测的下一个值。我们已经看到如何准备我们的时间序列数据进行建模;我们已经将其切片、窗口化、批处理、打乱,并将其拆分成特征和标签以供使用。

接下来,我们将利用在合成销售数据上学到的内容,来预测未来的销售值。

使用神经网络进行销售预测

让我们回到我们创建的销售数据,用于通过天真方法和移动平均方法预测销售。现在我们使用一个神经网络;在这里,我们将使用一个 DNN:

  1. 提取数据的过程如下:

    time = pd.to_datetime(df['Date'])
    
    sales = df['Sales'].values
    

    在这段代码中,我们从Sales数据框中提取DateSales数据。Date被转换为日期时间格式,Sales被转换为 NumPy 数组。

  2. 数据拆分的过程如下:

    split_time = int(len(df) * 0.8)
    
    time_train = time[:split_time]
    
    x_train = sales[:split_time]
    
    time_valid = time[split_time:]
    
    x_valid = sales[split_time:]
    

    为了统一性,我们使用 80%的split_time将数据分为训练集和验证集,采用相同的 80:20 分割方式。

  3. 创建窗口化数据集的过程如下:

    def windowed_dataset(
    
        series, window_size, batch_size, shuffle_buffer):
    
            dataset = tf.data.Dataset.from_tensor_slices(
    
                series)
    
            dataset = dataset.window(window_size + 1,
    
                shift=1, drop_remainder=True)
    
            dataset = dataset.flat_map(lambda window:
    
                window.batch(window_size + 1))
    
            dataset = dataset.shuffle(shuffle_buffer).map(
    
                lambda window: (window[:-1], window[-1]))
    
            dataset = dataset.batch(
    
                batch_size).prefetch(1)
    
        return dataset
    
    dataset = windowed_dataset(x_train, window_size,
    
        batch_size, shuffle_buffer_size)
    

    我们创建了windowed_dataset函数;该函数接收一个序列、一个窗口大小、一个批量大小和一个随机缓冲区。它为训练创建数据窗口,每个窗口包含window_size + 1个数据点。这些窗口随后被打乱并映射到特征和标签,其中特征是窗口中的所有数据点(除了最后一个),标签是最后一个数据点。然后,这些窗口被批处理并预取,以提高数据加载效率。

  4. 构建模型的过程如下:

    model = tf.keras.models.Sequential([
    
        tf.keras.layers.Dense(10,
    
            input_shape=[window_size], activation="relu"),
    
        tf.keras.layers.Dense(10, activation="relu"),
    
        tf.keras.layers.Dense(1)
    
        ])
    
    model.compile(loss="mse",
    
        optimizer=tf.keras.optimizers.SGD(
    
            learning_rate=1e-6, momentum=0.9))
    

    接下来,我们使用一个简单的前馈神经网络FFN)进行建模。该模型包含两个具有 ReLU 激活函数的全连接层,随后是一个具有单个神经元的全连接输出层。模型使用 MSE 损失函数,并以随机梯度下降SGD)作为优化器进行编译。

  5. 训练模型的过程如下:

    model.fit(dataset, epochs=100, verbose=0)
    

    模型在窗口化的数据集上训练 100 个周期。

  6. 生成预测的过程如下:

    input_batches = [sales[time:time + window_size][
    
        np.newaxis] for time in range(len(
    
            sales) - window_size)]
    
    inputs = np.concatenate(input_batches, axis=0)
    
    forecast = model.predict(inputs)
    
    results = forecast[split_time-window_size:, 0]
    

    在这里,我们批量生成预测结果以提高计算效率。只保留验证期的预测结果。

  7. 评估模型的过程如下:

    print(tf.keras.metrics.mean_squared_error(x_valid,
    
        results).numpy())
    
    print(tf.keras.metrics.mean_absolute_error(x_valid,
    
        results).numpy())
    

    计算真实验证数据和预测数据之间的 MSE 和 MAE。在这里,我们得到了 MSE 为34.51,MAE 为4.72,这超越了我们所有的简单统计方法。

  8. 可视化结果的过程如下。将真实验证数据和预测数据随时间绘制,以可视化模型的表现:

图 12.17 – 使用简单 FFN 的时间序列预测

图 12.17 – 使用简单 FFN 的时间序列预测

图 12.17中的图表来看,我们可以看到我们的预测值与验证集上的真实值高度吻合,只有少量噪声波动。我们的 FFN 在这次实验中表现出了显著的成就。与传统的统计方法相比,我们的模型在性能上有了显著的提升。你可以调整超参数并实现回调来提高性能。但我们的工作在这里已经完成。

我们在最后一章见面,那时我们将预测应用股票价格。到时候见。

总结

在本章中,我们探讨了时间序列的概念,研究了时间序列的核心特征和类型,并了解了时间序列在机器学习中的一些著名应用。我们还介绍了如滞后窗口和居中窗口等概念,并研究了如何借助 TensorFlow 的工具准备时间序列数据,以便用于神经网络建模。在我们的案例研究中,我们结合统计学方法和深度学习技术,为一家虚拟公司构建了一个销售预测模型。

在下一章中,我们将使用更复杂的架构,如 RNN、CNN 和 CNN-LSTM 架构,来扩展我们的建模,进行时间序列数据预测。同时,我们还将探索学习率调度器和 Lambda 层等概念。为了结束本书的最后一章,我们将构建一个苹果公司收盘股票价格的预测模型。

问题

  1. 使用提供的练习笔记本,将天真预测原理应用于“航空乘客”数据集。

  2. 在同一数据集上实现移动平均技术,并通过计算 MAE 和 MSE 值来评估其性能。

  3. 接下来,将差分方法引入到你的移动平均模型中。再次,通过确定 MAE 和 MSE 值来评估你的预测准确性。

  4. 手头有样本温度数据集,演示如何从这些数据中创建有意义的特征和标签。

  5. 最后,在数据集上尝试简单的 FFN 模型,并观察其性能。

第十三章:使用 TensorFlow 进行时间序列、序列和预测

欢迎来到我们与 TensorFlow 旅程的最后一章。在上一章中,我们通过应用神经网络(如 DNN)有效地预测时间序列数据,达到了一个高潮。本章中,我们将探索一系列高级概念,例如将学习率调度器集成到我们的工作流中,以动态调整学习率,加速模型训练过程。在前几章中,我们强调了找到最佳学习率的必要性和重要性。在构建带有学习率调度器的模型时,我们可以使用 TensorFlow 中内置的学习率调度器,或者通过制作自定义学习率调度器,以动态方式实现这一目标。

接下来,我们将讨论 Lambda 层,以及如何将这些任意层应用到我们的模型架构中,以增强快速实验的能力,使我们能够无缝地将自定义函数嵌入到模型架构中,尤其是在处理 LSTM 和 RNN 时。我们将从使用 DNN 构建时间序列模型转向更复杂的架构,如 CNN、RNN、LSTM 和 CNN-LSTM 网络。我们将把这些网络应用到我们的销售数据集案例研究中。最后,我们将从 Yahoo Finance 提取苹果股票的收盘价数据,并应用这些模型构建预测模型,预测未来的股价。

本章我们将涵盖以下主题:

  • 理解并应用学习率调度器

  • 在 TensorFlow 中使用 Lambda 层

  • 使用 RNN、LSTM 和 CNN 进行时间序列预测

  • 使用神经网络进行苹果股价预测

到本章结束时,你将对使用 TensorFlow 进行时间序列预测有更深入的理解,并拥有将不同技术应用于构建时间序列预测模型的实践经验,适用于真实世界的项目。让我们开始吧。

理解并应用学习率调度器

第十二章,《时间序列、序列和预测简介》中,我们构建了一个 DNN,成功使用了 TensorFlow 中的 LearningRateScheduler 回调函数,我们可以通过一些内置技术在训练过程中动态调整学习率。让我们来看看一些内置的学习率调度器:

  • ExponentialDecay:从指定的学习率开始,在经过一定步数后以指数方式下降。

  • PiecewiseConstantDecay:提供分段常数学习率,你可以指定边界和学习率,将训练过程划分为多个阶段,每个阶段使用不同的学习率。

  • PolynomialDecay:该学习率是调度中迭代次数的函数。它从初始学习率开始,根据指定的多项式函数,将学习率逐渐降低至最终学习率。

让我们为第十二章《时间序列、序列和预测导论》中使用的前馈神经网络添加一个学习率调度器。我们使用相同的销售数据,但这次我们将应用不同的学习率调度器来提高模型的性能。让我们开始吧:

  1. 我们从导入该项目所需的库开始:

    import numpy as np
    
    import matplotlib.pyplot as plt
    
    import tensorflow as tf
    
    from tensorflow import keras
    
  2. 接下来,让我们加载我们的数据集:

    #CSV sales data
    
    url = 'https://raw.githubusercontent.com/oluwole-packt/datasets/main/sales_data.csv'
    
    # Load the CSV data into a pandas DataFrame
    
    df = pd.read_csv(url)
    

    我们从本书的 GitHub 仓库加载销售数据,并将 CSV 数据放入 DataFrame 中。

  3. 现在,我们将Date列转换为日期时间格式并将其设置为索引:

    df['Date'] = pd.to_datetime(df['Date'])
    
    df.set_index('Date', inplace=True)
    

    第一行代码将日期列转换为日期时间格式。我们这样做是为了方便进行时间序列操作。接下来,我们将日期列设置为 DataFrame 的索引,这使得使用日期切片和处理数据变得更容易。

  4. 让我们从 DataFrame 中提取销售值:

    data = df['Sales'].values
    

    在这里,我们从销售 DataFrame 中提取销售值并将其转换为 NumPy 数组。我们将使用这个 NumPy 数组来创建我们的滑动窗口数据。

  5. 接下来,我们将创建一个滑动窗口:

    window_size = 20
    
    X, y = [], []
    
    for i in range(window_size, len(data)):
    
        X.append(data[i-window_size:i])
    
        y.append(data[i])
    

    正如我们在第十二章《时间序列、序列和预测导论》中所做的那样,我们使用滑动窗口技术将时间序列转换为包含特征和标签的监督学习问题。在这里,大小为 20 的窗口作为我们的X特征,包含 20 个连续的销售值,而我们的y是这 20 个销售值之后的下一个即时值。这里,我们使用前 20 个值来预测下一个值。

  6. 现在,让我们将数据拆分为训练集和验证集:

    X = np.array(X)
    
    y = np.array(y)
    
    train_size = int(len(X) * 0.8)
    
    X_train, X_val = X[:train_size], X[train_size:]
    
    y_train, y_val = y[:train_size], y[train_size:]
    

    我们将数据转换为 NumPy 数组,并将数据拆分为训练集和验证集。我们使用 80%的数据用于训练,20%的数据用于验证集,我们将用它来评估我们的模型。

  7. 我们的下一个目标是构建一个 TensorFlow 数据集,这是训练 TensorFlow 模型时更高效的格式:

    batch_size = 128
    
    buffer_size = 10000
    
    train_data = tf.data.Dataset.from_tensor_slices(
    
        (X_train, y_train))
    
    train_data = train_data.cache().shuffle(
    
        buffer_size).batch(batch_size).prefetch(
    
        tf.data.experimental.AUTOTUNE)
    

    我们应用from_tensor_slices()方法从 NumPy 数组创建数据集。之后,我们使用cache()方法通过将数据集缓存到内存中来加速训练。接着,我们使用shuffle(buffer_size)方法随机打乱训练数据,以防止诸如顺序偏差等问题。然后,我们使用batch(batch_size)方法将数据拆分成指定大小的批次;在这个例子中,训练过程中每次将 128 个样本输入到模型中。接下来,我们使用prefetch方法确保 GPU/CPU 始终有数据准备好进行处理,从而减少一个批次处理完与下一个批次之间的等待时间。我们传入tf.data.experimental.AUTOTUNE参数,告诉 TensorFlow 自动确定预取的最佳批次数量。这使得我们的训练过程更加顺畅和快速。

我们的数据现在已经准备好进行建模。接下来,我们将使用 TensorFlow 内置的学习率调度器探索这个数据,然后我们将探讨如何使用自定义学习率调度器找到最佳学习率。

内置学习率调度器

我们将使用与 第十二章* 《时间序列、序列与预测入门》 中相同的模型。让我们定义模型并探索内置的学习率调度器:

  1. 我们将从模型定义开始:

    # Model
    
    model = Sequential()
    
    model.add(Dense(10, activation='relu',
    
        input_shape=(window_size,)))
    
    model.add(Dense(10, activation='relu'))
    
    model.add(Dense(1))
    

    在这里,我们使用了三个密集层。

  2. 接下来,我们将使用指数衰减学习率调度器:

    # ExponentialDecay
    
    lr_exp = tf.keras.optimizers.schedules.ExponentialDecay(
    
        initial_learning_rate=0.1,
    
        decay_steps=100, decay_rate=0.96)
    
    optimizer = tf.keras.optimizers.Adam(
    
        learning_rate=lr_exp)
    
    model.compile(optimizer=optimizer, loss='mse')
    
    history_exp = model.fit(X_train, y_train, epochs=100)
    

    指数衰减学习率调度器设置了一个学习率,该学习率随着时间的推移按指数方式衰减。在此实验中,初始学习率设置为0.1。这个学习率将在每 100 步时按 0.96 的衰减速率进行指数衰减,这是由decay_steps参数定义的。接下来,我们将我们的指数学习率分配给优化器并编译模型。之后,我们将模型拟合 100 个 epochs。

  3. 接下来,我们将使用 MAE 和 均方误差 (MSE) 评估模型的性能,并将验证预测与真实值进行比较:

    # Evaluation
    
    forecast_exp = model.predict(X_val)
    
    mae_exp = mean_absolute_error(y_val, forecast_exp)
    
    mse_exp = mean_squared_error(y_val, forecast_exp)
    
    # Plot
    
    plt.plot(forecast_exp,
    
        label='Exponential Decay Predicted')
    
    plt.plot(y_val, label='Actual')
    
    plt.title('Exponential Decay LR')
    
    plt.legend()
    
    plt.show()
    

    这将生成以下输出:

图 13.1 – 使用指数衰减的真实预测与验证预测(放大版)

图 13.1 – 使用指数衰减的真实预测与验证预测(放大版)

当我们运行代码块时,得到的 MAE 约为 5.31,MSE 为 43.18,并且从图 13.1 中的放大图可以看到,我们的模型紧密跟踪实际的销售验证数据。然而,结果并没有比我们在 第十二章* 《时间序列、序列与预测入门》中取得的更好。接下来,我们将尝试使用 PiecewiseConstantDecay

  1. 让我们使用 PiecewiseConstantDecay 学习率调度器:

    # PiecewiseConstantDecay
    
    lr_piecewise = tf.keras.optimizers.schedules.PiecewiseConstantDecay(
    
        [30, 60], [0.1, 0.01, 0.001])
    
    optimizer = tf.keras.optimizers.Adam(
    
        learning_rate=lr_piecewise)
    
    model.compile(optimizer=optimizer, loss='mse')
    
    history_piecewise = model.fit(X_train, y_train,
    
        epochs=100)
    

    PiecewiseConstantDecay 学习率调度器允许我们在训练过程中为不同的时期定义特定的学习率。在我们的例子中,我们将 30 和 60 步设为边界;这意味着在前 30 步中,我们应用学习率 0.1,从第 30 步到第 60 步,我们应用学习率 0.01,从第 61 步到训练结束,我们应用学习率 0.001。对于 PiecewiseConstantDecay,学习率的数量应该比应用的边界数多一个。例如,在我们的例子中,我们有两个边界([30, 60])和三个学习率([0.1, 0.01, 0.001])。设置好调度器后,我们使用相同的优化器,并像使用指数衰减学习率调度器一样编译和拟合模型。然后,我们评估模型的性能并生成以下验证图:

图 13.2 – 使用指数衰减的真实预测与验证预测

图 13.2 – 使用指数衰减的真实预测与验证预测

在这次实验中,我们实现了 4.87 的 MAE 和 36.97 的 MSE。这是一次改进的表现。再次,图 13.2中的预测很好地跟随了真实值。为了清晰起见,我们放大了一些。

图 13.3 – 我们前两个实验的放大图

图 13.3 – 我们前两个实验的放大图

图 13.3中可以看到,我们在应用多项式衰减时,将视图放大到前 200 天,相较于使用指数衰减学习率调度器时,预测图更好地与真实值匹配。

  1. 现在让我们应用PolynomialDecay

    # PolynomialDecay
    
    lr_poly = tf.keras.optimizers.schedules.PolynomialDecay(
    
    initial_learning_rate=0.1,
    
        decay_steps=100,
    
        end_learning_rate=0.01,
    
        power=1.0)
    

    在这个实验中,我们将initial_learning_rate设置为0.1,它作为我们的初始学习率。我们将decay_steps参数设置为100,表示学习率将在这 100 步中衰减。接下来,我们将end_learning_rate设置为0.01,这意味着在decay_steps结束时,学习率将降到此值。power参数控制步长衰减的指数。在本实验中,我们将power值设置为1.0,实现线性衰减。

    当我们评估模型的表现时,我们发现目前为止我们取得了最佳结果,MAE 为 4.72,MSE 为 34.49。从图 13.4中可以看到,预测结果与数据非常接近,比我们使用PiecewiseConstantDecay时更为准确。

图 13.4 – 使用 PolynomialDecay 的真实预测与验证预测(放大)

图 13.4 – 使用 PolynomialDecay 的真实预测与验证预测(放大)

现在,你已经大致了解了如何应用这些学习率调度器,接下来可以调整值,看看是否能实现更低的 MAE 和 MSE。当你完成后,我们来看看一个自定义学习率调度器。

通过简单地调整学习率,我们可以看到我们的PiecewiseConstantDecay学习率调度器在这场竞争中获胜了,不仅超越了其他学习率,还优于我们在第十二章《时间序列、序列和预测导论》中使用的相同架构的简单 DNN 模型。你可以通过文档www.tensorflow.org/api_docs/python/tf/keras/callbacks/LearningRateScheduler或 Moklesur Rahman 在 Medium 上的这篇优质文章rmoklesur.medium.com/learning-rate-scheduler-in-keras-cc83d2f022a6了解更多关于学习率调度器的内容。

自定义学习率调度器

除了使用内置的学习率调度器外,TensorFlow 还提供了一种简便的方法来构建自定义学习率调度器,帮助我们找到最佳学习率。接下来我们来实现这一点:

  1. 让我们从定义自定义学习率调度器开始:

    # Define learning rate schedule
    
    lr_schedule = tf.keras.callbacks.LearningRateScheduler(
    
        lambda epoch: 1e-7 * 10**(epoch / 10))
    

    在这里,我们从一个较小的学习率(1×10−7)开始,并随着每个 epoch 的增加,学习率呈指数增长。我们使用 10**(epoch / 10) 来确定学习率增长的速度。

  2. 我们使用初始学习率定义优化器:

    # Define optimizer with initial learning rate
    
    optimizer = tf.keras.optimizers.SGD(
    
        learning_rate=1e-7, momentum=0.9)
    

    这里,我们使用了一个学习率为 1×10−7 的 SGD 优化器,并设置动量为 0.9。动量帮助加速优化器朝正确的方向前进,同时也减少震荡。

  3. 接下来,我们用定义好的优化器编译模型,并将损失设置为 MSE:

    model.compile(optimizer=optimizer, loss='mse')
    
  4. 现在,我们开始训练模型:

    history = model.fit(train_data, epochs=200,
    
        callbacks=[lr_schedule], verbose=0)
    

    我们训练模型 200 个 epoch,然后将学习率调度器作为回调传入。这样,学习率将根据定义的自定义学习率调度器进行调整。我们还设置了 verbose=0,以避免打印训练过程。

  5. 计算每个 epoch 的学习率:

    lrs = 1e-7 * (10 ** (np.arange(200) / 10))
    

    我们使用这段代码来计算每个 epoch 的学习率,并且它会给我们一个学习率数组。

  6. 我们绘制模型损失与学习率的关系图:

    plt.semilogx(lrs, history.history["loss"])
    
    plt.axis([1e-7, 1e-3, 0, 300])
    
    plt.xlabel('Learning Rate')
    
    plt.ylabel('Loss')
    
    plt.title('Learning Rate vs Loss')
    
    plt.show()
    

    这个图是选择最佳学习率的有效方法。

图 13.5 – 学习率损失曲线

图 13.5 – 学习率损失曲线

为了找到最佳的学习率,我们需要找出损失最迅速下降的地方,在其开始再次上升之前。从图中可以看到,学习率下降并在大约 3x10-5 附近稳定,然后开始再次上升。因此,我们将选择这个值作为本次实验的理想学习率。接下来,我们将使用这个新的学习率作为固定学习率来重新训练模型。当我们运行代码时,我们得到 MAE 为 5.96,MSE 为 55.08。

我们现在已经看到如何使用内置的学习率调度器和自定义调度器。接下来,让我们将注意力转向使用 CNN 进行时间序列预测。

用于时间序列预测的 CNN

CNN 在图像分类任务中取得了显著的成功,因为它们能够检测网格状数据结构中的局部模式。这一思想同样可以应用于时间序列预测。通过将时间序列视为一系列时间间隔,CNN 可以提取和识别有助于预测未来趋势的模式。CNN 的另一个重要优势是它们的平移不变性。这意味着,一旦它们在一个片段中学习到某个模式,网络就能在序列中出现该模式的任何地方进行识别。这对于在时间步长之间检测重复模式非常有用。

CNN 的设置还通过池化层的帮助,自动减少输入数据的维度。因此,CNN 中的卷积和池化操作将输入序列转化为一种简化的形式,捕捉核心特征,同时确保计算效率。与图像不同,在这里我们使用 1D 卷积滤波器,因为时间序列数据的特性(单一维度)。这个滤波器沿着时间维度滑动,观察作为输入的局部值窗口。它通过在每个元素上的乘法和加和操作,检测这些区间中的有用模式。

多个滤波器被用来提取多样的预测信号——趋势、季节性波动、周期等。类似于图像中的模式,卷积神经网络(CNN)能够识别这些时间序列中的变换版本。当我们应用连续的卷积层和池化层时,网络将这些低级特征组合成更高级的表示,逐步将序列浓缩成其最显著的组成部分。最终,完全连接层利用这些学习到的特征进行预测。

让我们回到笔记本,并在我们的销售数据建模中应用 1D CNN。我们已经有了训练数据和测试数据。现在,为了使用 CNN 建模我们的数据,我们需要进行一个额外的步骤,即调整数据形状以满足 CNN 所期望的输入形状。在第七章**, 使用卷积神经网络进行图像分类中,我们看到 CNN 需要 3D 数据,而我们使用 DNN 建模时则使用 2D 数据;这里也是同样的情况。

CNN 需要一个批量大小、一个窗口大小和特征数量。批量大小是输入形状的第一维,它表示我们输入 CNN 的序列数量。我们将窗口大小设置为20,特征数量指的是每个时间步的独立特征数。对于单变量时间序列,这个值是1;对于多变量时间序列,这个值将是2或更多。

由于我们在案例研究中处理的是单变量时间序列,因此我们的输入形状需要类似于(128, 20, 1):

  1. 让我们准备数据以适应 CNN 模型的正确形状:

    # Create sequences
    
    window_size = 20
    
    X = []
    
    y = []
    
    for i in range(window_size, len(data)):
    
        X.append(data[i-window_size:i])
    
        y.append(data[i])
    
    X = np.array(X)
    
    y = np.array(y)
    
    # Train/val split
    
    split = int(0.8 * len(X))
    
    X_train, X_val = X[:split], X[split:]
    
    y_train, y_val = y[:split], y[split:]
    
    # Reshape data
    
    X_train = X_train.reshape(-1, window_size, 1)
    
    X_val = X_val.reshape(-1, window_size, 1)
    
    # Set batch size and shuffle buffer
    
    batch_size = 128
    
    shuffle_buffer = 1000
    
    train_data = tf.data.Dataset.from_tensor_slices(
    
        (X_train, y_train))
    
    train_data = train_data.shuffle(
    
        shuffle_buffer).batch(batch_size)
    

    这段代码中的大部分内容是相同的。这里的关键步骤是reshape步骤,我们用它来实现 CNN 建模所需的输入形状。

  2. 让我们构建我们的模型:

    # Build model
    
    model = Sequential()
    
    model.add(Conv1D(filters=64, kernel_size=3,
    
        strides=1,
    
        padding='causal',
    
        activation='relu',
    
        input_shape=(window_size, 1)))
    
    model.add(MaxPooling1D(pool_size=2))
    
    model.add(Conv1D(filters=32, kernel_size=3,
    
        strides=1,
    
        padding='causal',
    
        activation='relu'))
    
    model.add(MaxPooling1D(pool_size=2))
    
    model.add(Flatten())
    
    model.add(Dense(16, activation='relu'))
    
    model.add(Dense(1))
    

    在我们的模型中,由于时间序列数据是单维的,我们采用了 1D 卷积层,而不是像用于图像分类的 2D 卷积神经网络(CNN),因为图像具有二维结构。我们模型由两个 1D 卷积层组成,每个卷积层后跟一个最大池化层。在我们的第一个卷积层中,我们使用了 64 个滤波器来学习各种数据模式,滤波器大小为3,这使得它能够识别跨越三个时间步的模式。我们使用了步幅1,这意味着我们的滤波器每次遍历数据时只跨一步,为了确保非线性,我们使用了 ReLU 作为激活函数。请注意,我们使用了一种新的填充方式,称为因果填充。这种选择是有战略意义的,因为因果填充确保模型在某个时间步的输出仅受该时间步及其前置时间步的影响,永远不会受到未来数据的影响。通过在序列的开头添加填充,因果填充尊重了我们数据的自然时间序列。这对于防止模型“不经意地提前预测”至关重要,确保预测仅依赖于过去和当前的信息。

    我们之前提到,我们需要 3D 形状的输入数据来馈送到由批量大小、窗口大小和特征数量组成的 CNN 模型中。在这里,我们使用了input_shape=(window_size, 1)。我们没有在输入形状定义中说明批量大小。这意味着模型可以接受不同大小的批量,因为我们没有硬编码任何批量大小。另外,由于我们处理的是单变量时间序列,所以我们只有一个特征,这就是为什么在输入形状中指定了1以及窗口大小的原因。最大池化层减少了我们数据的维度。接下来,我们进入第二个卷积层,这次我们使用了 32 个滤波器,核大小为3,依然使用因果填充和 ReLU 作为激活函数。然后,最大池化层再次对数据进行采样。之后,数据被展平并输入到全连接层中,根据我们从销售数据中学到的模式进行预测。

  3. 让我们编译并训练模型 100 个周期:

    model.compile(loss='mse', optimizer='adam')
    
    # Train model
    
    model.fit(train_data, epochs=100)
    
  4. 最后,让我们评估一下我们模型的性能:

    # Make predictions
    
    preds = model.predict(X_val)
    
    # Calculate metrics
    
    mae = mean_absolute_error(y_val, preds)
    
    mse = mean_squared_error(y_val, preds)
    
    # Print metrics
    
    print('MAE: ', mae)
    
    print('MSE: ', mse)
    

    我们通过生成 MAE 和 MSE 在验证集上评估模型。当我们运行代码时,获得了 5.37 的 MAE 和 44.28 的 MSE。在这里,你有机会通过调整滤波器数量、滤波器的大小等来看看是否能够获得更低的 MAE。

接下来,让我们看看如何使用 RNN 系列模型来进行时间序列数据预测。

时间序列预测中的 RNN

时间序列预测在机器学习领域提出了一个独特的挑战,它涉及基于先前观察到的顺序数据预测未来的值。一种直观的思考方式是考虑一系列过去的数据点。问题就变成了,给定这个序列,我们如何预测下一个数据点或数据点序列?这正是 RNN 展示其有效性的地方。RNN 是一种专门为处理序列数据而开发的神经网络。它们保持一个内部状态或“记忆”,存储迄今为止观察到的序列元素的信息。这个内部状态会在序列的每个步骤中更新,将新的输入信息与之前的状态合并。例如,在预测销售时,RNN 可能会保留有关前几个月销售趋势、过去一年的总体趋势以及季节性效应等数据。

然而,标准的 RNN 存在一个显著的局限性:“梯度消失”问题。这个问题导致在序列中很难保持和利用来自早期步骤的信息,特别是当序列长度增加时。为了解决这个问题,深度学习社区引入了先进的架构。LSTM 和 GRU 是专门设计用来解决梯度消失问题的 RNN 变种。由于它们内置的门控机制,这些类型的 RNN 能够学习长期依赖关系,控制信息在记忆状态中进出的流动。

因此,RNN、LSTM 和 GRU 可以是强大的时间序列预测工具,因为它们本身就包含了问题的时间动态。例如,在预测销售时,这些模型可以通过保持对先前销售时期的记忆来考虑季节性模式、假期、周末等因素,从而提供更准确的预测。

让我们在这里运行一个简单的 RNN,看看它在我们的数据集上的表现如何:

  1. 让我们从准备数据开始:

    # Create sequences
    
    seq_len = 20
    
    X = []
    
    y = []
    
    for i in range(seq_len, len(data)):
    
        X.append(data[i-seq_len:i])
    
        y.append(data[i])
    
    X = np.array(X)
    
    y = np.array(y)
    
    # Train/val split
    
    split = int(0.8*len(X))
    
    X_train, X_val = X[:split], X[split:]
    
    y_train, y_val = y[:split], y[split:]
    
    # Create dataset
    
    batch_size = 128
    
    dataset = tf.data.Dataset.from_tensor_slices(
    
        (X_train, y_train))
    
    dataset = dataset.shuffle(buffer_size=1024).batch(batch_size)
    

    在这里,你会看到我们准备数据的方式与使用 DNN 时相同,并且我们没有像在 CNN 模型中那样重新塑形数据。接下来会有一个简单的技巧,帮助你在我们的模型架构中实现这一点。

  2. 让我们定义我们的模型架构:

    model = tf.keras.models.Sequential([
    
        tf.keras.layers.Lambda(lambda x: tf.expand_dims(
    
            x, axis=-1),
    
            input_shape=[None]),
    
        tf.keras.layers.SimpleRNN(40,
    
            return_sequences=True),
    
        tf.keras.layers.SimpleRNN(40),
    
        tf.keras.layers.Dense(1),
    
        tf.keras.layers.Lambda(lambda x: x * 100.0)
    
    ])
    

    在使用 RNN 建模时,就像我们在使用 CNN 时所看到的那样,我们需要将数据重新塑形,因为我们的模型也需要 3D 形状的输入数据。然而,在你希望保持原始输入形状不变,以便进行不同模型的各种实验时,我们可以采用一个简单而有效的解决方案——Lambda 层。这个层是我们工具箱中的一个强大工具,允许我们对输入数据执行简单的任意函数,使其成为快速实验的绝佳工具。

    使用 Lambda 层,我们可以执行逐元素的数学操作,例如归一化、线性缩放和简单的算术运算。例如,在我们的案例中,我们使用 Lambda 层将 2D 输入数据的维度扩展,以符合 RNN 的 3D 输入要求(batch_sizetime_stepsfeatures)。在 TensorFlow 中,您可以利用 Keras API 的 tf.keras.layers.Lambda 来创建 Lambda 层。Lambda 层作为适配器,允许我们对数据进行微小调整,确保其格式适合我们的模型,同时保持原始数据不变,供其他用途。接下来,我们遇到两个每个有 40 个单元的简单 RNN 层。需要注意的是,在第一个 RNN 中,我们设置了 return_sequence=True。我们在 RNN 和 LSTM 中使用此设置,当一个 RNN 或 LSTM 层的输出需要输入到另一个 RNN 或 LSTM 层时。我们设置此参数,以确保第一个 RNN 层会为序列中的每个输入返回一个输出。然后将输出传递到第二个 RNN 层,该层仅返回最终步骤的输出,然后将其传递到密集层,密集层输出每个序列的预测值。接下来,我们遇到了另一个 Lambda 层,它将输出乘以 100。我们使用这个操作来扩展输出值。

  3. 让我们编译并拟合我们的模型:

    model.compile(optimizer=tf.keras.optimizers.Adam(
    
        learning_rate=8e-4), loss='mse')
    
    # Train model
    
    model.fit(dataset, epochs=100)momentum=0.9))
    

在这里,我们在验证集上获得了 4.84 的 MAE 和 35.65 的 MSE。这个结果稍微比我们使用 PolynomialDecay 学习率调度器时的结果要差一些。也许在这里,您有机会尝试不同的学习率调度器,以实现更低的 MAE。

接下来,让我们探索 LSTM。

时间序列预测中的 LSTM

在 NLP 部分,我们讨论了 LSTM 的能力及其相对于 RNN 的改进,解决了如梯度消失问题等问题,使得模型能够学习更长的序列。在时间序列预测的背景下,LSTM 网络可以非常强大。让我们看看如何将 LSTM 应用于我们的销售数据集:

  1. 让我们首先准备我们的数据:

    # Create sequences
    
    seq_len = 20
    
    X = []
    
    y = []
    
    for i in range(seq_len, len(data)):
    
        X.append(data[i-seq_len:i])
    
        y.append(data[i])
    
    X = np.array(X)
    
    X = X.reshape(X.shape[0], X.shape[1], 1)
    
    y = np.array(y)
    
    # Train/val split
    
    split = int(0.8*len(X))
    
    X_train, X_val = X[:split], X[split:]
    
    y_train, y_val = y[:split], y[split:]
    
    # Set batch size and buffer size
    
    batch_size = 64
    
    buffer_size = 1000
    
    # Create dataset
    
    dataset = tf.data.Dataset.from_tensor_slices(
    
        (X_train, y_train))
    
    dataset = dataset.shuffle(
    
        buffer_size).batch(batch_size)
    

    注意,由于我们不使用 Lambda 层,因此我们在这里使用 reshape 步骤,以避免重复此代码块。请注意,我们将在此次实验和下一次使用 CNN-LSTM 架构的实验中使用它。

  2. 接下来,让我们定义我们的模型:

    model_lstm = tf.keras.models.Sequential([
    
        tf.keras.layers.LSTM(50, return_sequences=True,
    
            input_shape=[None, 1]),
    
        tf.keras.layers.LSTM(50),
    
        tf.keras.layers.Dense(1)
    
    ])
    

    我们在这里使用的架构是一个使用 LSTM 单元的 RNN 结构——第一层是一个具有 50 个神经元的 LSTM 层,它的 return_sequence 参数设置为 True,以确保返回的输出是完整的序列,这些输出会传递到最终的 LSTM 层。在这里,我们还使用了 [None, 1] 的输入形状。下一层也有 50 个神经元,并且输出一个单一的值,因为我们没有在此处设置 return_sequence 参数为 True,该输出将传递到密集层进行预测。

  3. 下一步是编译模型。我们像以前一样编译并拟合我们的模型,然后对其进行评估。在这里,我们获得了 4.56 的 MAE 和 32.42 的 MSE,这是迄今为止最好的结果。

图 13.6 – 使用 LSTM 的真实预测与验证预测(放大显示)

图 13.6 – 使用 LSTM 的真实预测与验证预测(放大显示)

从图中我们可以看到,预测值和真实值比我们迄今为止进行的任何其他实验都更加一致。让我们看看是否可以使用 CNN-LSTM 架构来改进我们的结果,进行下一个实验。

用于时间序列预测的 CNN-LSTM 架构

深度学习为时间序列预测提供了引人注目的解决方案,而这一领域中的一个显著架构是 CNN-LSTM 模型。该模型利用了 CNN 和 LSTM 网络的优势,为处理时间序列数据的独特特性提供了有效的框架。CNN 因其在图像处理任务中的表现而闻名,主要得益于其在图像中学习空间模式的能力,而在顺序数据中,它们能够学习局部模式。网络中的卷积层通过一系列滤波器对数据进行处理,学习并提取显著的局部和全局时间模式及趋势。这些特征作为原始数据的压缩表示,保留了关键信息,同时减少了维度。维度的减少导致更高效的表示,能够捕捉到相关模式。

一旦通过卷积层提取了显著特征,这些特征将作为网络 LSTM 层的输入。CNN-LSTM 模型在端到端学习能力上具有优势。在这种架构中,CNN 和 LSTM 各自扮演着互补的角色。CNN 层捕捉局部模式,而 LSTM 层从这些模式中学习时间关系。与独立架构相比,这种联合优化是 CNN-LSTM 模型性能提升的关键。让我们看看如何将这种架构应用到我们的销售数据中。由于我们之前已经多次查看过数据准备步骤,因此这里直接进入模型架构:

  1. 让我们使用卷积层来构建我们的模型:

       # Build the Model
    
    model = tf.keras.models.Sequential([
    
        tf.keras.layers.Conv1D(filters=64, kernel_size=3,
    
        strides=1,
    
        activation="relu",
    
        padding='causal',
    
        input_shape=[window_size, 1]),
    

    我们使用 1D 卷积层从值序列中检测模式,就像我们在 CNN 预测实验中做的那样。记得将padding设置为causal,以确保输出大小与输入保持一致。我们将输入形状设置为[window_size, 1]。这里,window_size代表每个输入样本中的时间步长数量。1表示我们正在处理单变量时间序列。例如,如果我们将window_size设置为7,这意味着我们使用一周的数据进行预测。

  2. 接下来,我们的数据进入 LSTM 层,由 2 个 LSTM 层组成,每个 LSTM 层包含 64 个神经元:

        tf.keras.layers.LSTM(64, return_sequences=True),
    
        tf.keras.layers.LSTM(64),
    
  3. 然后,我们有密集层:

        tf.keras.layers.Dense(30, activation="relu"),
    
        tf.keras.layers.Dense(10, activation="relu"),
    
        tf.keras.layers.Dense(1),
    

    LSTM 层为 CNN 层提取的特征添加了时间上下文,而全连接层则生成最终的预测值。在这里,我们使用了三层全连接层,并输出最终的预测结果。通过这种架构,我们实现了 4.98 的 MAE 和 40.71 的 MSE。效果不错,但不如单独使用 LSTM 模型的结果。

调整超参数以优化模型性能在这里是个好主意。通过调整学习率、批处理大小或优化器等参数,我们可能能够提升模型的能力。我们不会在这里详细讨论,因为你已经具备了处理这些的能力。接下来让我们继续探讨苹果股票价格数据,并运用我们所学的知识,创建一系列实验,预测苹果股票的未来价格,看看哪种架构最终会脱颖而出。

预测苹果股票价格数据

现在我们已经涵盖了 TensorFlow 开发者证书考试中关于时间序列的所有内容。让我们用一个实际的时间序列用例来结束这一章和本书。在这个练习中,我们将使用一个真实世界的数据集(苹果股票的收盘日价格)。接下来让我们看看如何进行操作。这个练习的 Jupyter notebook 可以在这里找到:github.com/PacktPublishing/TensorFlow-Developer-Certificate-Guide。让我们开始:

  1. 我们首先导入所需的库:

    import numpy as np
    
    import matplotlib.pyplot as plt
    
    import tensorflow as tf
    
    from tensorflow import keras
    
    import yfinance as yf
    

    这里我们使用了一个新的库 yfinance。它让我们能够访问苹果股票数据,用于我们的案例分析。

注意

如果导入失败,你可能需要运行pip install yfinance来让其正常工作。

  1. 创建一个 DataFrame:

    df_apple = yf.Ticker(tickerSymbol)
    
    df_apple = df_apple.history(period='1d',
    
        start='2013-01-01', end='2023-01-01')
    

    我们通过使用 AAPL 股票代码来创建一个 DataFrame,AAPL代表苹果公司(Apple)在股票市场中的代号。为此,我们使用来自 yfinance 库的 yf.Ticker 函数来访问苹果的历史数据,该数据来自 Yahoo Finance。我们将 history 方法应用到我们的 Ticker 对象上,以访问苹果的历史市场数据。在这里,我们将 period 设置为 1d,意味着获取的是每日数据。同时,我们也设置了 startend 参数,定义了我们想要访问的日期范围;在此案例中,我们收集了从 2013 年 1 月 1 日到 2023 年 1 月 31 日的 10 年数据。

  2. 接下来,我们使用 df.head() 来查看我们 DataFrame 的快照。可以看到数据集由七列组成(OpenHighLowCloseVolumeDividends,和 Stock Splits),如下图所示。

图 13.7 – 苹果股票数据快照

图 13.7 – 苹果股票数据快照

让我们来理解这些列的含义:

  • Open表示交易日的开盘价格。

  • High表示交易日内股票交易的最高价格。

  • Low表示交易日内股票交易的最低价格。

  • Close表示交易日的收盘价格。

  • 交易量表示在交易日内成交的股票数量。这可以作为市场强度的一个指标。

  • 股息表示公司如何将其收益分配给股东。

  • 股票分割可以看作是公司通过拆分每一股股票,从而增加公司流通股本的一种行为。

  1. 让我们绘制每日收盘价:

    plt.figure(figsize=(14,7))
    
    plt.plot(df_apple.index, df_apple['Close'],
    
        label='Close price')
    
    plt.title('Historical prices for AAPL')
    
    plt.xlabel('Date')
    
    plt.ylabel('Price')
    
    plt.grid(True)
    
    plt.legend()
    
    plt.show()
    

    当我们运行代码时,我们得到以下图表:

图 13.8 – 显示 2013 年 1 月到 2023 年 1 月期间苹果股票收盘价的图表

图 13.8 – 显示 2013 年 1 月到 2023 年 1 月期间苹果股票收盘价的图表

图 13.8的图表中,我们可以看到股票呈现出正向上升的趋势,偶尔会有下跌。

  1. 将数据转换为 NumPy 数组:

    Series = df_apple['Close'].values
    

    在这个练习中,我们将预测每日的股票收盘价。因此,我们取收盘价列并将其转换为 NumPy 数组。这样,我们为实验创建了一个单变量时间序列。

  2. 准备窗口数据集:

    # Sliding window
    
    window_size = 20
    
    X, y = [], []
    
    for i in range(window_size, len(data)):
    
        X.append(data[i-window_size:i])
    
        y.append(data[i])
    
    X = np.array(X)
    
    y = np.array(y)
    
    # Train/val split
    
    train_size = int(len(X) * 0.8)
    
    X_train, X_val = X[:train_size], X[train_size:]
    
    y_train, y_val = y[:train_size], y[train_size:]
    
    # Dataset using tf.data
    
    batch_size = 128
    
    buffer_size = 10000
    
    train_data = tf.data.Dataset.from_tensor_slices(
    
        (X_train, y_train))
    
    train_data = train_data.cache().shuffle(
    
        buffer_size).batch(batch_size).prefetch(
    
        tf.data.experimental.AUTOTUNE)
    

    我们现在已经熟悉了这个代码块,它用于准备数据以进行建模。接下来,我们将使用相同的架构进行苹果股票数据集的实验。结果如下:

模型MAE
DNN4.56
RNN2.24
LSTM3.02
CNN-LSTM18.75

图 13.9 – 显示各种模型的 MAE 表格

从我们的结果来看,我们使用 RNN 架构取得了最佳表现的模型,MAE 为 2.24。现在你可以保存你最好的模型,它可以用来预测未来的股票价值,或应用于其他预测问题。你也可以进一步调整超参数,看看是否能达到更低的 MAE。

注释

该模型展示了利用神经网络进行预测的可能性。然而,我们必须意识到它的局限性。请不要使用此模型做出金融决策,因为现实世界的股市预测涉及复杂的关系,如经济指标、市场情绪以及其他相互依赖性,而我们的基础模型并未考虑这些因素。

这样,我们就来到了本章和整本书的结尾。

总结

在本章的最后,我们探讨了一些使用 TensorFlow 进行时间序列预测的高级概念。我们了解了如何使用内建的学习率调度器,也学习了如何设计定制的调度器来满足我们的需求。接着,我们使用了更专业的模型,如 RNN、LSTM 网络,以及 CNN 和 LSTM 的组合。我们还学习了如何应用 Lambda 层来实现自定义操作并为我们的网络架构增加灵活性。

本章总结时,我们进行了苹果股票收盘价的预测。到本章结束时,你应该已经充分理解如何应用学习率调度器、Lambda 层等概念,并且能够使用不同架构有效地构建时间序列预测模型,为你的考试做好准备。祝你好运!

作者的话

看到你从机器学习的基础知识到使用 TensorFlow 构建各种项目,我感到非常高兴。你现在已经探索了如何使用不同的神经网络架构来构建模型。你已经拥有了坚实的基础,可以并且应该在此基础上,作为一名认证的 TensorFlow 开发者,构建一个令人惊叹的职业生涯。你只有通过构建解决方案,才能成为顶尖开发者。

本书中涵盖的所有内容将帮助你在最终准备 TensorFlow 开发者证书考试以及更远的未来中取得成功。我想祝贺你没有放弃,成功完成了本书中的所有概念、项目和练习。我鼓励你继续学习、实验,并保持对机器学习领域最新发展的关注。祝你考试顺利,并在未来的职业生涯中取得成功。

问题

  1. 从 Yahoo Finance 加载 2015-01-01 到 2020-01-01 的 Google 股票数据。

  2. 创建训练、预测和绘图函数。

  3. 准备数据以进行训练。

  4. 构建 DNN、CNN、LSTM 和 CNN-LSTM 模型来建模数据。

  5. 使用 MAE 和 MSE 评估模型。

参考文献