🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
🖍foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟👋
文章目录
有许多数据集是按时间间隔的顺序自然生成的,例如每隔几分钟到达岸边的海浪或每隔几微秒发生一次的股票市场交易。通过分析以前发生的历史,预测下一波何时会到来或下一次股票交易的价格可能是多少的模型是一种称为时间序列模型的数据科学算法。虽然传统的时间序列方法长期以来一直用于预测,但使用深度学习,我们可以使用高级方法来获得更好的结果。在本章中,我们将重点介绍如何构建常用的基于深度学习的时间序列模型,例如循环神经网络( RNN ) 和长短期记忆( LSTM ),使用 PyTorch Lightning 执行时间序列预测。
在本章中,我们将从简要介绍时间序列问题开始,然后查看 PyTorch Lightning 中的一个用例。有没有想过您手机上的天气应用程序如何向您显示第二天的温暖或寒冷?它是在使用天气数据历史的时间序列预测模型的帮助下完成的。
我们将看到时间序列预测如何帮助企业预测即将到来的交通量,他们可以使用它来估计下一小时到达目的地所需的时间或全天的变化情况。
在构建、训练、加载和预测阶段将涵盖不同的 PyTorch Lightning 方法和功能。为了您的利益,我们还将使用 PyTorch Lightning 中可用的不同功能,例如以自动方式识别学习率,以更深入地了解框架。
本章将帮助您为使用 PyTorch Lightning 的高级类型的时间序列模型做好准备,同时让您熟悉隐藏的功能和特性。
在本章中,我们将介绍以下主题:
- 时间序列简介
- 时间序列模型入门
- 使用 LSTM 时间序列模型的交通量预测
技术要求
本章的代码已经在 macOS 上使用 Anaconda 或在 Google Colab 中使用 Python 3.6 开发和测试。如果您使用的是其他环境,请对您的环境变量进行适当的更改。
在本章中,我们将主要使用以下 Python 模块,并在其版本中提及:
- PyTorch Lightning (version 1.5.2)
- Seaborn (version 0.11.2)
- NumPy (version 1.21.5)
- Torch (version 1.10.0)
- pandas (version 1.3.5)
为了确保这些模块一起工作并且不会不同步,我们使用了特定版本的torch、torchvision、torchtext、torchaudio 和PyTorch Lightning 1.5.2。您还可以使用相互兼容的最新版 PyTorch Lightning 和torch compatible。
!pip install torch==1.10.0 torchvision==0.11.1 torchtext==0.11.0 torchaudio==0.10.0 --quiet
!pip install pytorch-lightning==1.5.2 --quiet
源数据集可在 Metro Interstate Traffic Volume 数据集中找到:https ://archive.ics.uci.edu/ml/datasets/Metro+Interstate+Traffic+Volume 。
这是从 UCI 机器学习存储库获得的捐赠数据集。该数据集由 Dua, D. 和 Graff, C. (2019), UCI Machine Learning Repository ( http://archive.ics.uci.edu/ml ), Irvine, CA: University of California, School of Information 提供和计算机科学。
时间序列简介
在典型的机器学习用例中,数据集是特征 ( x ) 和目标变量 ( y ) 的集合。这模型使用特征来学习和预测目标变量。
举个例子。为了预测房价,特征可以是卧室数量、浴室数量和平方英尺,目标变量是房价。在这里,目标可以是使用所有特征 ( x ) 来训练模型并预测房子的价格 ( y )。我们在这样的用例中观察到的一件事是,在预测目标变量时,数据集中的所有记录都被平等对待,在我们的例子中是房子的价格,数据的顺序并不重要。结果 ( y ) 仅取决于x的值。
另一方面,在时间序列预测中,数据的顺序在捕捉一些特征方面起着重要作用,例如趋势和季节。时间序列数据集是通常是涉及时间测量的数据集——例如,每小时记录温度的气候数据集。时间序列中的预测并不总是在特征 ( x ) 和变量 ( y ) 之间很好地划分,但我们根据 x 的先前值预测 x 的下一个值(例如根据今天的温度预测明天的温度)。
时间序列数据集可以具有一个或多个特征。如果只有一个特征,那就是称为单变量时间序列,如果有多个特征,则称为多变量时间序列。这个可以是任何量子的时间,无论是微秒、小时、天,甚至是年。
虽然使用传统机器学习方法(如 ARIMA)解决时间序列问题有多种方法,但深度学习方法提供了更简单、更好的方法。我们将了解如何使用深度学习方法处理时间序列问题,以及 PyTorch Lightning 如何借助示例帮助其实现。
使用深度学习进行时间序列预测
技术上时间序列预测是一种利用历史时间序列数据构建回归模型来预测预期结果的形式。简单来说,时间序列预测使用历史数据来训练模型并预测未来值。
虽然时间序列的传统方法很有用,但深度学习在时间序列预测克服了传统机器学习的缺点,例如:
- 识别复杂的模式、趋势和季节
- 长期预测
- 处理缺失值
- 循环神经网络
- 长短期记忆体
- 门控循环单元
- 编码器-解码器模型
在下一节中,我们将看到 LSTM 使用用例的实际应用。
时间序列模型入门
在本章的下一部分中,我们将通过时间序列预测的真实示例进行详细解释。每个时间序列模型通常遵循以下结构体:
让我们从下一节开始。
使用 LSTM 时间序列模型的交通量预测
时间序列模型还有其他各种业务应用,例如例如预测股票价格、预测产品需求或预测机场每小时的乘客数量。
这种模型最常用的应用之一(您必须在驾驶时不知不觉地使用它)是交通预测。在本节中,我们将尝试预测 94 号州际公路的交通量,Uber、Lyft 和/或谷歌地图等拼车公司可以使用它来预测两个司机的交通量和到达目的地所需的时间和拼车客户。交通量因小时而异(取决于一天中的时间、办公时间、通勤时间等),时间序列模型有助于进行此类预测。
在这个用例中,我们将使用 Metro Interstate Traffic Volume 数据集来构建多层堆叠 LSTM 模型并预测交通量。我们还将关注一些用于处理模型内部验证数据集的 PyTorch Lightning 技术,以及使用 PyTorch Lightning 自动化技术之一来识别模型学习率值。
在本节中,我们将介绍以下主要主题:
- 数据集分析
- 特征工程
- 创建自定义数据集
- 使用 PyTorch Lightning 配置 LSTM 模型
- 训练模型
- 测量训练损失
- 加载模型
- 对测试数据集的预测
数据集分析
我们将在本章中使用的数据集称为Metro Interstate Traffic Volume数据集,其中包含交通量的历史数据在 UCI 机器学习存储库上提供。从 2012 年 10 月 2 日到 2018 年 9 月 30 日,它每小时记录明尼阿波利斯和明尼苏达州圣保罗之间的 I-94 西行的交通量。该数据集可在 UCI 机器学习存储库中获得,并可从以下 URL 下载:Index of /ml/machine-learning-databases/00492。
ZIP 文件夹包含一个 CSV 文件,其中包含 2012 年 10 月 2 日至 2018 年 9 月 30 日的 8 列和 48,204 行数据。
这是我们训练数据集前五行的屏幕截图:
![](https://i-blog.csdnimg.cn/blog_migrate/452b7ba0fbf1cf3b330c817e889573c0.jpeg)
图 5.1 – 显示前五行数据
列如下:
- holiday:美国国定假日,加上地区性假期和明尼苏达州博览会
- temp:数字开尔文的平均温度
- rain_1h:一小时内发生的降雨量,以毫米为单位
- snow_1h:一小时内发生的以毫米为单位的积雪量
- cloud_all : 云量百分比
- weather_main:当前天气的分类简短文本描述
- weather_description:当前天气的分类较长文本描述
- date_time:在本地 CST 时间收集的数据的DateTime小时
- traffic_volume:数字每小时 I-94 ATR 301 报告的西行交通量
我们将首先将 CSV 文件加载到 pandas DataFrame 中,以便在将其输入到我们的自定义数据模块之前执行探索性数据分析( EDA ) 和数据处理。
加载数据集
安装后并导入所需的库,第一步是在 Google Colab 工作区中加载数据集。首先,我们将使用以下 URL 从 UCI 机器学习存储库下载数据集:https ://archive.ics.uci.edu/ml/machine-learning-databases/00492/ 。
然后,我们将提取压缩文件以在提取的文件夹中找到Metro_Interstate_Traffic_Volume.csv文件。我们已经看到了几种将数据集从 Kaggle、Google Drive 等直接下载到 Colab notebook 中的方法。在本节中,我们将使用手动方法将数据直接上传到 Colab 会话存储中。单击左侧的文件图标,如下所示:
图 5.2 – 在 Google Colab 上打开文件菜单
后打开“文件”菜单,通过单击图标选择“上传到会话存储”选项,如下所示:
图 5.3 – 将本地数据上传到 Google Colab 环境
作为如前所示,最后选择您之前从 UCI 机器学习存储库下载的Metro_Interstate_Traffic_Volume.csv文件。
现在,是时候将我们的数据集加载到 pandas DataFrame 中了。由于我们知道我们的数据集有一个名为date_time的列,我们将使用该列来解析日期并将其设置为索引列,如下面的代码片段所示:
df_traffic = pd.read_csv('Metro_Interstate_Traffic_Volume.csv', parse_dates=['date_time'], index_col="date_time")
将 CSV 文件加载到 pandas DataFrame 后,如前所示,我们可以使用 DataFrame 上的head方法显示前五行,如下所示:
df_traffic.head()
显示以下输出:
图 5.4 – 索引时间戳
我们现在将使用df_traffic DataFrame 上的shape方法检查数据集中的总行数和列数,如下所示:
print("Total number of row in dataset:", df_traffic.shape[0])
print("Total number of columns in dataset:", df_traffic.shape[1])
显示以下输出:
![](https://i-blog.csdnimg.cn/blog_migrate/0b3959d54ae45e692f382b3c47947b65.jpeg)
图 5.5 – 数据集大小
探索性数据分析
现在,是时候执行探索性数据分析,以更好地了解数据并检查数据质量问题。我们将首先分析分类列。让我们从weather_main列的频率分布开始,如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/30b33c012ca9e39f80e03fc0c8098869.jpeg)
图 5.6 – 天气数据文件的行数
在里面上图中,我们在df_traffic DataFrame 的weather_main列上使用value_counts方法来获取唯一值的计数。可以看出有 11 种不同类型的天气,其中云是最常见的一种。
我们现在将得到假期列的频率分布,如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/bf03aae4168a5b8ff287b3698be5d871.jpeg)
图 5.7 – 假期文件的基本数据
有数据集中记录了 12 种不同类型的假期以及 None 值,这意味着只有 11 个假期,并且大多数假期的频率非常小。
检查时间序列中的重复时间戳总是很重要的;我们将使用 pandas 的duplicated()方法来识别重复的时间戳,如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/fda8f7bd2a9fe02787bd8d2dd5af1a82.jpeg)
图 5.8 – 检查数据集的重复项
我们可以从上一个屏幕截图中可以看出,有 7,629 行具有重复的时间戳。我们需要删除所有重复的时间戳,这可以使用重复的方法结合 pandas 切片轻松完成,如下所示:
df_traffic = df_traffic[~df_traffic.index.duplicated(keep='last')]
在前面的代码中,我们使用 duplicated 方法来保留最后一条记录,并从df_traffic DataFrame 中删除所有其他重复记录。去重后,我们可以使用shape方法检查 DataFrame 中剩余的行数:
图 5.9 – 行数的输出
我们可以看到,删除重复的时间戳后,DataFrame 现在包含 40,575 行,而不是 48,204 行。
现在,让我们使用 Matplotlib 和 Seaborn 库绘制流量的时间序列,如下所示:
plt.xticks(
rotation=90,
horizontalalignment='right',
fontweight='light',
fontsize='x-small'
)
sns.lineplot(df_traffic.index, df_traffic["traffic_volume"]).set_title('Traffic volume time series')
在里面在前面的代码中,我们使用时间戳(已经转换为df_traffic DataFrame 的索引)作为x轴,并在y轴上绘制traffic_volume的值,得到如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/480686ccd427fd51d422f8cbe66a059a.jpeg)
图 5.10 – 交通量时间序列图
从前面的时间序列可以看出,整个数据集中都存在缺失值,但是从 2014 年底到 2015 年底几乎所有的值都缺失了。如果缺失值是间歇性的,则大多数数据插补技术都有效,例如 2013 年至 2014 年或 2016 年至 2018 年的情况,但对于 2014 年至 2015 年的流量,它们将失败。因此,我们将仅考虑最近 3 年数据,因为它似乎足够一致,因为很少存在缺失值,可以使用简单的插值进行估算。
将时间序列可视化以获取缺失值的概览总是一个好主意,但为了获得缺失值的确切数量,我们将创建虚拟数据并将数据集中的时间戳与其进行比较,如此处所示:
date_range = pd.date_range('2012-10-02 09:00:00', '2018-09-30 23:00:00', freq='1H')
df_dummy = pd.DataFrame(np.random.randint(1, 20, (date_range.shape[0], 1)))
df_dummy.index = date_range # 设置索引
df_missing = df_traffic
# 根据参考索引检查缺少的日期时间索引值(所有值)
missing_hours = df_dummy.index[~df_dummy.index.isin(df_missing.index)]
print(missing_hours)
在里面在前面的代码中,我们正在创建一个名为date_range的日期范围变量,其开始时间和结束时间等于数据集中记录的开始时间和结束时间。我们将名为df_dummy的虚拟数据的索引设置为date_range,其频率为 1 小时。然后,我们比较这两个索引以找出所有时间戳,这些时间戳本应存在于我们的数据集中但丢失了。前面代码片段的输出如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/7b36b8be26d9eb5b72a067d431792141.jpeg)
图 5.11 – 缺失数据分析的输出
从前面的输出可以看出,我们的数据集中有 11,976 个缺失的时间戳。
现在,我们将使用 pandas 的describe方法探索区间变量温度,如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/2bc34afc13f21a4ecded57fb2c42d206.jpeg)
图 5.12 – 探索温度变量
我们可以在温度统计中观察到,我们数据集中的最低温度为 0 开尔文,这是不可能的,因此我们需要处理这些异常值。由于这是时间序列数据,因此我们需要替换它们以保持连续性,而不是删除异常值,如下所示:
df_traffic['temp']=df_traffic['temp'].replace(0,df_traffic['temp'].median())
在里面在前面的代码片段中,我们将 0 开尔文温度值替换为数据集中记录的温度中值。(或者,您也可以使用均值进行替换。)
在下一步中,我们将选择最近 3 年的数据,如前所述,因为这与一些间歇性缺失值更一致:
df_traffic = df_traffic[df_traffic.index.year.isin([2016,2017,2018])].copy()
# 使用回填和插值方法填充缺失值
df_traffic = pd.concat([df_traffic.select_dtypes(include=['object']).fillna(method='backfill'),
df_traffic.select_dtypes(include=['float']).interpolate()],axis=1)
df_traffic.shape
在前面的代码中,我们首先选择 2016 年、2017 年和 2018 年的数据。然后,我们使用回填和插值方法填充缺失值。最后,我们有一个数据集,其中包含过去 3 年的 24,096 行,没有缺失值,可以检查,如下所示:
图 5.13 – 再次检查数据集的缺失值
现在,是时候将我们的分类变量转换为虚拟/指标变量了。为此,我们将使用 pandas 的get_dummies()方法,如下所示:
df_traffic = pd.get_dummies(df_traffic, columns = ['holiday', 'weather_main'], drop_first=True)
df_traffic.drop('weather_description', axis=1, inplace=True)
在前面的代码中,我们首先为holiday和weather_main分类变量创建虚拟变量,然后从数据集中删除原始分类变量。我们还删除了weather_description列,它是 weather_main 列的扩展,它不包含任何对我们的预测模型有用的额外信息:
![](https://i-blog.csdnimg.cn/blog_migrate/6d7b137341b7d76c3bc37ec122cddf43.jpeg)
图 5.14 – 数据集的示例视图
将数据集拆分为训练、测试和验证集
数据集将是分为训练、测试和验证,用于训练模型、评估模型和估计训练阶段的模型性能。
由于这是一个时间序列数据集,我们不会使用标准的 70–30/80–20 拆分;相反,我们将根据时间段拆分数据集。为此,我们需要检查下采样数据的开始和结束时间,这可以使用以下代码片段来实现:
图 5.15 – 下采样数据的结果
训练数据集
我们将考虑 2 年的数据,从 2016 年 1 月 1 日到 2017 年 12 月 31 日,作为训练数据集。以下代码片段在我们要求的日期范围内划分数据集。该训练数据集将用于训练我们的模型:
df_traffic_train = df_traffic.loc[:datetime.datetime(year=2017,month=12,day=31,hour=23)]
print("Total number of row in train dataset:", df_traffic_train.shape[0])
print("Train dataset start date :",df_traffic_train.index.min())
print("Train dataset end date:",df_traffic_train.index.max())
显示以下输出:
图 5.16 – 训练数据集
显示训练数据集的大小及其开始和结束日期。
验证数据集
现在我们将考虑未来 6 个月的数据以进行验证。以下代码片段在我们要求的日期范围内划分数据集:
df_traffic_val = df_traffic.loc[datetime.datetime(year=2018,month=1,day=1,hour=0):datetime.datetime(year=2018,month=6,day=30,hour=23)]
print("Total number of row in validate dataset:", df_traffic_val.shape[0])
print("Validate dataset start date :",df_traffic_val.index.min())
print("Validate dataset end date:",df_traffic_val.index.max())
图 5.17 – 验证数据集
验证数据集现已准备就绪。
测试数据集
最后,对于测试数据集,我们将考虑从 2018 年 7 月到 2018 年 9 月的剩余时间段,也就是数据集中的结束时间段。以下代码片段在我们要求的日期范围内划分数据集。该测试数据集用于在模型训练后预测我们的结果:
df_traffic_test = df_traffic.loc[datetime.datetime(year=2018,month=7,day=1,hour=0):]
print("Total number of row in test dataset:", df_traffic_test.shape[0])
print("Validate dataset start date :",df_traffic_test.index.min())
print("Validate dataset end date:",df_traffic_test.index.max())
到目前为止,我们已经读取了数据,进行了 EDA 和数据预处理,并将其拆分为三个不同的 pandas DataFrames 用于训练、测试和验证:
- df_traffic_train:这是训练数据集,用于训练我们的模型。
- df_traffic_val:这是验证数据集,用于在每个 epoch 之后验证我们的模型。
- df_traffic_test:这是测试当我们的模型被训练并准备好时的数据集。我们将使用这些数据进行预测和比较结果。
特征工程
中的一个关键的特征工程步骤是将分类变量转换为虚拟/指示变量,这是在将数据拆分为训练、测试和验证之前完成的。
下一个也是最重要的特征工程步骤是对区间变量进行归一化的过程。始终建议执行归一化以获得更好的结果并更快地训练模型。有许多可用的标准化技术,我们将使用sklearn 预处理模块中的 min-max 缩放器。我们将缩放器应用于训练、验证和测试数据集中的所有区间变量 - temp、rain_1h、snow_1h、clouds_all和traffic_volume:
#创建缩放器
temp_scaler = MinMaxScaler()
rain_scaler = MinMaxScaler()
snow_scaler = MinMaxScaler(
cloud_scaler = MinMaxScaler()
volume_scaler = MinMaxScaler()
#创建转换器
temp_scaler_transformer = temp_scaler.fit(df_traffic_train[['temp']])
rain_scaler_transformer = rain_scaler.fit(df_traffic_train[['rain_1h']])
snow_scaler_transformer = snow_scaler.fit(df_traffic_train[['snow_1h']])
cloud_scaler_transformer = cloud_scaler.fit(df_traffic_train[['clouds_all']])
volume_scaler_transformer = volume_scaler.fit(df_traffic_train[['traffic_volume']])
在前面的代码块中,我们主要执行以下步骤:
- 创建缩放器:我们为每个间隔列创建五个不同的最小-最大缩放器,因为我们有五个间隔列(temp、rain_1h、snow_1h、clouds_all和traffic_volume)。
- 创建转换器:下一步是使用前面步骤中定义的缩放器创建转换器。这可以通过在训练数据集上为五个单独的缩放器调用fit方法来完成。在这里,我们为 Metro Interstate Traffic Volume 数据集的每个间隔列(temp、rain_1h、snow_1h、clouds_all和traffic_volume )创建了五个不同的转换器。
- 现在,我们将将先前定义和拟合的缩放应用于训练数据集,如下所示:
df_traffic_train["temp"] = temp_scaler_transformer.transform(df_traffic_train[['temp']]) df_traffic_train["rain_1h"] = rain_scaler_transformer.transform(df_traffic_train[['rain_1h']]) df_traffic_train["snow_1h"] = snow_scaler_transformer.transform(df_traffic_train[['snow_1h']]) df_traffic_train["clouds_all"] = cloud_scaler_transformer.transform(df_traffic_train[['clouds_all']]) df_traffic_train["traffic_volume"] = volume_scaler_transformer.transform(df_traffic_train[['traffic_volume']])
如前面的代码所示,使用定义的缩放器上的变换方法将缩放器应用于训练数据集。
- 我们将使用与训练数据集相同的缩放器来对测试和验证数据集应用缩放,如下所示:
df_traffic_val["temp"] = temp_scaler_transformer.transform(df_traffic_val[['temp']]) df_traffic_val["rain_1h"] = rain_scaler_transformer.transform(df_traffic_val[['rain_1h']]) df_traffic_val["snow_1h"] = snow_scaler_transformer.transform(df_traffic_val[['snow_1h']]) df_traffic_val["clouds_all"] = cloud_scaler_transformer.transform(df_traffic_val[['clouds_all']]) df_traffic_val["traffic_volume"] = volume_scaler_transformer.transform(df_traffic_val[['traffic_volume']]) df_traffic_test["temp"] = temp_scaler_transformer.transform(df_traffic_test[['temp']]) df_traffic_test["rain_1h"] = rain_scaler_transformer.transform(df_traffic_test[['rain_1h']]) df_traffic_test["snow_1h"] = snow_scaler_transformer.transform(df_traffic_test[['snow_1h']]) df_traffic_test["clouds_all"] = cloud_scaler_transformer.transform(df_traffic_test[['clouds_all']]) df_traffic_test["traffic_volume"] = volume_scaler_transformer.transform(df_traffic_test[['traffic_volume']])
在前面代码中,我们再次在训练数据集上安装的缩放器上使用变换方法来缩放测试和验证数据集中的间隔列。
创建自定义数据集
在本节中,我们将创建一个实用程序类从数据集中提取特征和目标。因此,我们将加载我们之前拆分的训练、验证和测试 DataFrame,并从这些 DataFrame 创建要在模型中使用的特征。此自定义数据集类的输出将用于在本节的后面部分创建数据加载器。
加载数据中
让我们从加载数据帧。在特征工程部分的最后,我们准备好了三个不同的 pandas DataFrames 用于训练、验证和测试:
#STEP1:加载数据
self.df_traffic_train = df_traffic_train
self.df_traffic_val = df_traffic_val
self.df_traffic_test = df_traffic_test
在前面的代码中,我们复制了三个训练、验证和测试 DataFrame。
创建特征
#STEP2: 创建功能
if train: #process train dataset
features = self.df_traffic_train
target = self.df_traffic_train.traffic_volume
elif validate: #process 验证数据集
features = self.df_traffic_val
target = self.df_traffic_val.traffic_volume
else: #process 测试数据集
features = self.df_traffic_test
target = self.df_traffic_test.traffic_volume
在这个代码块中,我们创建了两个变量——一个是由DataFrame和我们的features列组成的特征,它是包含temp、rain_1h、snow_1h、clouds_all、traffic_volume以及从分类变量创建的所有列的 DataFrame。第二个变量是目标,它是一个 pandas 系列的traffic_volume。
这一步也定义了选择DataFrame的条件。如果训练参数为True,则为我们的训练数据集设置特征和目标变量——即使用df_traffic_train数据集;如果验证参数为True,则为验证数据集设置特征和目标变量——即使用df_traffic_val数据帧;同样,如果测试参数为True,则为我们的测试数据集设置特征和目标变量——即使用df_traffic_test DataFrame。
创建窗口/排序
在我们执行窗口化步骤之前,让我们简要讨论一下它是什么以及我们为什么需要这样做。
在时间序列预测中,重要的是给出建模在给定时间可能最完整的信息,时间序列中的历史数据在做出未来预测中起着重要作用。
因此,它期望在具有滚动输出的周期的特定间隔内生成的数据。如果数据集不是这种格式,我们需要先重塑数据集,然后才能将其传递给时间序列模型算法。这样做最常用的过程称为“加窗”或“排序”。固定窗口为模型提供了使用滚动目标的跨度(如上一节所述)。
让我们考虑一个有九行的单变量时间序列数据集:
图 5.18 – 包含 Date 和 Target 列的数据集
上表具有以<mm/dd/YY>格式表示的Date列和名为Target的列。让我们对前面的数据集应用窗口,窗口大小为3。下图显示了窗口大小为3的窗口化结果:
图 5.19 – 窗口大小为 3 的窗口
应用窗口后窗口大小为3,我们可以看到生成了六行数据;Window 1的特征将是1/1/20、1/2/20和1/3/20的所有特征的集合,目标变量将是1/4/20的目标值。
例如,如果我们使用记录的湿度来预测一天的温度,那么对于Window 1,特征将是记录1/1/20、1/2/20和1天的湿度的集合/3/20,目标变量将是1/4/20的温度值。
加窗/排序是其中之一时间序列预测中最常用的技术,它帮助我们提供为我们的模型提供完整的信息,以便更好地进行预测。
现在,到目前为止,我们已经学会了如何为时间序列预测工作准备数据。在下一节中,我们将使用 LSTM 模型使用 PyTorch Lightning 执行预测。
现在,我们将执行窗口化的数据准备步骤:
#STEP3: Create windows/sequencing
self.x, self.y = [], []
for i in range(len(features) - window_size):
v = features.iloc[i:(i + window_size)].values
self.x.append(v)
self.y.append(target.iloc[i + window_size])
在这一步中,我们将数据集转换为默认大小为480的窗口,即 20 天的数据。我们将使用 20 天的历史数据来训练我们的 LSTM 模型,以预测第二天的情况。
在前面的代码中,我们有两个 Python 类变量;x将具有过去 20 天的特征序列(temp、rain_1h、snow_1h、clouds_all、traffic_volume以及从分类变量创建的所有列),y类变量将具有下一个目标变量(traffic_volume)连续一天。
计算数据集的长度
在这个自定义数据类中,我们也在计算我们的数据在执行窗口化后的总记录数,稍后将用于 __len__(self) 方法:
#STEP4:计算数据集的长度
self.num_sample = len(self.x)
总结一下我们的__init__()方法,它需要三个布尔参数作为输入,根据设置的标志,它选择训练、验证或测试数据集。然后,从数据集中提取特征和目标,并对特征和目标数据执行窗口化,默认窗口大小为480。开窗后,此序列数据存储在名为x和y的类变量中,它们表示特征和目标变量。
返回行数
我们还将定义一个效用函数返回数据集中的行数,该行数存储为num_sample类变量。这是在__init__()中计算和初始化的:
def __len__(self):
#返回数据集的总记录数
return self.num_sample
最后,我们定义__getitem__方法来获取索引处的x和y的值,如下所示:
def __getitem__(self, index):
x = self.x[index].astype(np.float32)
y = self.y[index].astype(np.float32)
return x, y
在前面的代码中,__getitem__方法将index作为参数,并返回我们的特征和目标变量的记录——也就是说,它从两个类变量x和y中返回index处的值。这是在__init__方法中对我们的数据集执行窗口后完成的。
现在,我们已准备好测试TrafficVolumeDataset,然后继续构建 LSTM 模型的下一部分。测试它的一种快速方法是使用for循环,并在单次迭代后通过调用break语句停止循环:
traffic_volume = TrafficVolumeDataset(test=True)
#let's loop it over single iteration and print the shape and also data
for i, (features,targets) in enumerate(traffic_volume):
print("Size of the features",features.shape)
print("Printing features:\n", features)
print("Printing targets:\n", targets)
break
在前面的代码中,我们为测试数据创建了TrafficVolumeDataset ,将它循环到一个单次迭代,并打印特征的大小、特征的内容和目标。
显示以下输出:
![](https://i-blog.csdnimg.cn/blog_migrate/d62b0855e8a61c0de1b918ae2fd029ec.jpeg)
图 5.20 – 我们数据集的特征形状
我们的特征尺寸是具有480行和26列的多维数据——也就是说,我们有480行历史数据,因为我们的默认窗口大小是480,以及26列,因为我们有 5 个区间列和从 2 个分类变量创建的 21 个指标变量。最后,有一个单一的未来目标值。
重要的提示
如果您用新值覆盖window_size参数,那么您可能不会产生相同的输出——也就是说,特征的大小为 (480, 26)。
使用 PyTorch Lightning 配置 LSTM 模型
我们有将数据集加载到 pandas 中,进行探索性数据分析,执行特征工程,并创建自定义数据集以访问数据。现在,我们已准备好将处理后的数据和带有窗口的工程特征输入到时间序列预测模型中。
让我们开始构建基于深度学习的预测模型。我们将构建一个多层双层 LSTM 模型来预测 Metro Interstate 交通量。
要使用 PyTorch Lightning 构建我们的 LSTM 模型,让我们将过程分为以下步骤:
- 定义模型。
- 设置优化器。
- 设置数据。
- 配置训练循环。
- 配置验证循环。
定义模型
- 使用带有输入大小、所需的总隐藏维度和层数的torch nn模块初始化和构建 LSTM 模型。此外,我们将batch_first参数设置为True,因为 LSTM 层是我们这里的第一层。
- 使用torch nn模块创建一个线性层,其中输入大小是隐藏维度的总数乘以窗口大小,第二个参数是输出的大小。在我们的例子中,我们预测的是traffic_volume列,所以我们的输出大小是1。
初始化损失函数
我们正在使用用于计算损失的 MSE 损失函数。MSE 损失函数创建了一个标准,用于测量 xx 输入中的每个元素与yy目标之间的均方误差。
此外,我们将学习率设置为0.0001。但是,在本节的后面部分,我们还将尝试使用 PyTorch Lightning 的学习率查找器来确定学习率,并在选择一个好的学习率时减少猜测。
让我们创建一个名为TrafficVolumePrediction的类,它继承自 PyTorch Lightning 的LightningModule类:
def __init__(self, input_size=26, output_size=1, hidden_dim=10, n_layers=2, window_size=480):
def __init__(self, input_size=26, output_size=1, hidden_dim=10, n_layers=2, window_size=480):
"""
input_size:输入中的特征数
hidden_dim:隐藏层数
n_layers:要相互堆叠的 RNN 数量
output_size:要输出的项目数
"""
super(TrafficVolumePrediction, self).__init__()
self.hidden_dim = hidden_dim
self.n_layers = n_layers
self.lstm = nn.LSTM(input_size, hidden_dim, n_layers, bidirectional=False, batch_first=True)
self.fc = nn.Linear(hidden_dim * window_size, output_size)
self.loss = nn.MSELoss()
self.learning_rate = 0.0001
- input_size:这表示x输入中预期特征的数量。在我们的例子中,Metro Interstate Traffic Volume 数据集中的要素总数为 26,因此该参数的默认值为26。
- hidden_dim:此参数表示隐藏 LSTM 的总数——即 LSTM 的副本——需要。默认值设置为 10——也就是说,我们有 10 个隐藏层。
- n_layers:此参数表示堆叠在一起的 LSTM 层数。在本章中,我们将使用两层 LSTM 神经网络,因此此处将默认值设置为 2。
- output_size:模型预期的输出数量。由于我们正在预测traffic_volume,这是一个回归问题,因此输出大小为1。
- window_size:数据要被划分的窗口的大小。默认大小为480——也就是说,我们正在考虑 20 天的数据。
重要的提示
在识别学习率的自动化过程中,默认情况下,PyTorch Lightning在我们的 init 方法中查找具有learning_rate名称或lr的变量。将变量名称设置为learning_rate或lr始终是一个好习惯。
定义隐藏层
def get_hidden(self, batch_size):
# hidden = torch.zeros(self.n_layers, batch_size, self.hidden_dim)
hidden_state = torch.zeros(self.n_layers, batch_size, self.hidden_dim)
cell_state = torch.zeros(self.n_layers, batch_size, self.hidden_dim)
hidden = (hidden_state, cell_state)
return hidden
get_hidden方法采用批量大小和输入,并返回一个元组,其中包含两个用于隐藏状态的张量和一个用于 LSTM 隐藏层的单元状态。这两个张量用零初始化。
定义前向传球
到目前为止,我们是熟悉forward方法,它就像一个映射器,其中数据通过层传递以生成模型的输出。我们现在将使用forward方法通过配置输入和输出来连接所有层,如下所示:
def forward(self, x):
batch_size = x.size(0)
hidden = self.get_hidden(batch_size)
out, hidden = self.lstm(x, hidden)
out =out.reshape(out.shape[0], -1)
out = self.fc(out)
return out
在forward方法中,我们执行以下操作:
- 首先,我们使用size方法从x中提取批量大小。
- 然后,我们调用前面定义的get_hidden效用函数来初始化隐藏层,这将返回用零填充的多维张量。
- 在下一步中,我们将x输入与隐藏层一起传递给我们的 LSTM 模型,该模型返回输出和隐藏层。
- 输出是然后在发送到我们的全连接层之前展平为一个维度。最后,forward方法返回全连接层的输出。
设置优化器
正如我们在前面学到的章节,PyTorch Lightning 中的优化器可以在生活中配置称为configure_optimizers的循环方法。configure_optimizers生命周期方法的代码如下:
def configure_optimizers(self):
params = self.parameters()
optimizer = optim.Adam(params=params, lr = self.learning_rate)
return optimizer
在configure_optimizer方法中,我们主要做了两个操作:
- 获取模型参数:由于该方法是在我们的模型类中编写的,因此可以通过调用self.parameters()方法来访问模型的参数。
- 然后,我们使用已在__init__方法中初始化的学习率的Adam优化器(从torch.optim模块访问)。
一旦设置了参数和学习率,configure_optimizer最终会返回 Adam 优化器。
重要的提示
对于这个用例,我们使用 Adam 作为优化器,学习率为0.0001。这始终可以使用不同的优化器进行尝试,我们可以使用不同的学习率进行尝试以提高模型性能。该模型还使用RMSprop优化器进行了尝试,这是 LSTM 类型模型的另一个不错的选择。
设置数据
在上一个部分,我们创建了一个训练、验证和测试数据集。我们现在将使用训练数据来训练模型,并在训练期间使用验证数据来估计它。由于 PyTorch Lightning 需要数据加载器来访问数据,我们现在将在以下代码块中定义train_dataloader和val_dataloader生命周期方法。让我们覆盖 pl.LightningModule 的train_dataloader方法如下:
def train_dataloader(self):
traffic_volume_train = TrafficVolumeDataset(train=True)
train_dataloader = torch.utils.data.DataLoader(traffic_volume_train, batch_size=50)
return train_dataloader
在train_dataloader方法中,我们首先使用训练数据初始化我们之前定义的类TrafficVolumeDataset的实例。然后,我们将存储为traffic_volume_train的TrafficVolumeDataset的输出传递给批大小为 50的torch.utils.data.DataLoader模块,以创建训练数据加载器。最后,此方法返回批大小为 50的训练数据加载器。
与上一步类似,为了在验证数据集上训练 LSTM 模型,让我们通过覆盖pl.LightningModule的名为val_dataloader的方法来设置验证数据加载器:
def val_dataloader(self):
traffic_volume_val = TrafficVolumeDataset(validate=True)
val_dataloader = torch.utils.data.DataLoader(traffic_volume_val, batch_size=50)
return val_dataloader
在前面的代码中,我们调用了我们在上一节中定义的TrafficVolumeDataset实例来创建一个名为traffic_volume_val的验证数据集。然后,我们将traffic_volume_val传递给torch.utils.data.DataLoader类来创建我们的验证数据加载器。此方法返回批大小为 50 的验证数据加载器。
重要的提示
从我们的TrafficVolumeDataset类创建数据集时,对于train_dataloader方法,我们将train标志设置为true,对于val_dataloader方法,我们将val标志设置为true。
在前面的代码中,我们将批量大小设置为50。根据硬件要求和性能,可以增加或减少此批量大小。
配置训练循环
现在,我们可以定义training_step生命周期方法,在计算损失的同时训练模型,如下所示:
def training_step(self, train_batch, batch_idx):
features, targets = train_batch
output = self(features)
output = output.view(-1)
loss = self.loss(output, targets)
self.log('train_loss', loss, prog_bar=True)
return {"loss": loss}
在前面的代码块中,training_step方法接受两个输入:
- batch_idx:批次的索引。
- train_batch:这是从train_dataloader方法返回的数据加载器中的数据批次。
以下是training_step方法的分步说明:
- 第一步是从train_batch参数中提取特征和目标,这是一个元组类型。元组的第一个元素是特征,元组的第二个元素是目标。
- 在下一步中,我们使用self方法将x的特征传递给我们的 LSTM 模型。self方法将特征作为输入,将其传递给我们的 LSTM 模型,然后返回输出。
- 然后,借助 PyTorch视图方法,将 LSTM 模型的输出转换为一维数组。转换输出后,使用__init__方法中定义的损失对象计算损失。
- 正如我们在前几章中看到的,在返回语句之前记录训练损失是一个好主意。稍后将在 TensorBoard 上绘制。记录训练损失是通过调用log方法实现的,第一个输入作为日志的名称。在这里,我们称之为train_log。第二个输入是日志本身,我们将prog_bar标志设置为True。通过将prog_bar设置为True,train_loss值将在 Trainer 过程中显示在进度条上。
- 最后是training_step方法返回损失,它作为损失存储在字典中。
重要的提示
正在记录的任何数据都将记录在 PyTorch Lightning 日志目录中,也可以显示在 TensorBoard 中。
配置验证循环
现在,我们将定义我们的validation_step生命周期方法来估计训练期间的验证损失,如下所示:
def validation_step(self, val_batch, batch_idx):
features, targets = val_batch
output = self(features)
output = output.view(-1)
loss = self.loss(output, targets)
self.log('val_loss', loss, prog_bar=True)
在前面的代码块中,training_step方法接受两个输入:
- batch_idx:批次的索引。
- val_batch:这是从val_dataloader方法返回的数据加载器的数据批处理。
此方法的工作方式与之前描述的training_step完全相同。因此,它将训练模型并计算验证数据的损失,稍后可以使用 TensorBoard 将其绘制到评估模型的性能。
重要的提示
正在记录的任何数据都将记录在 PyTorch Lightning 日志目录中,也可以显示在 TensorBoard 中。
训练模型
seed_everything(10)
model = TrafficVolumePrediction()
trainer = pl.Trainer( max_epochs=40, progress_bar_refresh_rate=25)
# 运行学习率查找器
lr_finder = trainer.tuner.lr_find(model, min_lr=1e-04, max_lr=1, num_training=30)
# 根据情节选择点,或获得建议
new_lr = lr_finder.suggestion()
print("Suggested Learning Rate is :", new_lr)
# 更新模型的 hparams
model.hparams.lr = new_lr
以下是训练模型的分步说明:
- 我们首先将种子设置为10,以使实验结果具有可重复性。然后,我们调用上一节中创建的TrafficVolumePrediction实例。
- 接下来,我们创建一个最大 epoch 数为40的Trainer对象,每 25 个单位刷新一次进度条。我们可以尝试训练更多的 epoch 并比较准确度。
- 我们还在Trainer对象中调用lr_find方法。如上一节所述,这是帮助我们确定最佳学习率的方法。lr_find方法接受模型的输入、最小学习率(在本例中为0.0001 )、最大学习率(为1)和训练总数。
- 然后,我们调用建议方法来获得lr_method建议的最佳学习率。这有效地覆盖了早期的学习率。
- 在最后一步,我们用新的学习率覆盖我们的学习率超参数。
print("model learning rate:",model.hparams)
输出如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9ad8bb65c3370333c5059dc7550a8df7.jpeg)
图 5.21 – 模型学习率
现在,由于我们已经确定了最佳学习率,是时候训练我们的模型了:
trainer.fit(model)
我们正在传递模型,它是TrafficVolumePrediction的一个实例,使用训练器上的fit方法来训练模型。
显示以下输出:
![](https://i-blog.csdnimg.cn/blog_migrate/6b7afb72b9b96c63708321903e455532.jpeg)
图 5.22 – 训练结果
重要的提示
当我们将auto_lr_find输入作为True传递时,PyTorch Lightning在我们的LSTM类中搜索具有learning_rate名称或lr的变量。确保您已在 LSTM 类中创建了一个名为learning_rate名称或lr的变量。
学习速率是最重要的超参数之一,要确定最佳学习率并不容易。PyTorch Lightning 帮助我们确定最佳学习率,但它可能不是最佳学习率。但是,这是开始确定最佳学习率的好方法。我们还将在没有 PyTorch Lightning 建议的学习率的情况下在这里训练模型并比较结果。
测量训练损失
在前面的步骤中,在训练步骤,我们记录了train_loss。现在,让我们使用 TensorBoard 在每个 epoch 中绘制和监控我们的训练损失。让我们通过运行以下代码来启动 TensorBoard:
%load_ext tensorboard
%tensorboard --logdir Lightning_logs/
输出如下:
图 5.23 – TensorBoard 仪表板的屏幕截图
我们在 LSTM 模型中记录的两个值train_loss和val_loss现在可以从 TensorBoard 访问。这些图表帮助我们监控模型的性能。
训练损失如下:
![](https://i-blog.csdnimg.cn/blog_migrate/427a5f91697522afc9591819ab8007f1.jpeg)
图 5.24 – TensorBoard 上的训练损失图
正如我们所看到的,训练损失正在减少。
![](https://i-blog.csdnimg.cn/blog_migrate/4fbfd1b797d0e0077a8bc0a8af74bf51.jpeg)
图 5.25 – TensorBoard 上的验证损失图
如您所见,验证损失也在减少,这意味着模型正在收敛并且稳定。
加载模型
在我们对测试数据进行预测并比较结果之前,我们将在本章的下一节中进行,我们需要首先加载模型,它可以通过以下步骤完成。
列出 PyTorch Lightning 默认路径 ( lightning_logs ) 中的文件。一旦我们知道模型的文件名,我们就可以使用以下代码,它使用load_from_checkpoint方法加载模型并将模式更改为eval。现在,模型正在从文件中加载并准备好执行预测:
PATH = 'lightning_logs/version_0/checkpoints/epoch=39-step=13679.ckpt'
trained_traffic_volume_TrafficVolumePrediction = model.load_from_checkpoint(PATH)
trained_traffic_volume_TrafficVolumePrediction.eval()
显示以下输出:
![](https://i-blog.csdnimg.cn/blog_migrate/831c67cd433b7d2ee76a606c939f55a4.jpeg)
图 5.26 – 加载的模型
对测试数据集的预测
这是本节的最后一步,我们使用模型对我们创建的测试数据集进行预测并绘制实际值与预测值的图表。以下代码中涉及对测试数据集进行预测的步骤如下:
- 首先,通过将测试参数传递为True创建TrafficVolumeDataset对象,并使用数据集创建批量大小为20的加载器。
- 遍历数据加载器并收集特征;这些特征用于对训练好的模型进行预测。
- 所有预测和实际目标值分别存储在名为predict_result和actual_result的局部变量中:
#初始化数据集 traffic_volume_test_dataset = TrafficVolumeDataset(test=True) traffic_volume_test_dataloader = torch.utils.data.DataLoader(traffic_volume_test_dataset, batch_size=20) predicted_result, actual_result = [], [] #让我们在单次迭代中循环它并打印形状和数据 for i, (features,targets) in enumerate(traffic_volume_test_dataloader): result = trained_traffic_volume_TrafficVolumePrediction(features) predicted_result.extend(result.view(-1).tolist()) actual_result.extend(targets.view(-1).tolist())
- 在我们绘制之前图表,让我们使用在特征工程阶段创建的volume_scaler_transformer对数据集执行逆变换:
actual_predicted_df = pd.DataFrame(data={"actual":actual_result, "predicted": predicted_result}) inverse_transformed_values = volume_scaler_transformer.inverse_transform(actual_predicted_df) actual_predicted_df["actual"] = inverse_transformed_values[:,[0]] actual_predicted_df["predicted"] = inverse_transformed_values[:,[1]] actual_predicted_df
显示以下输出:
![](https://i-blog.csdnimg.cn/blog_migrate/7cb38bba7087b35c599dd07b195e50c9.jpeg)
图 5.27 – 实际结果与预测结果
- 现在,让我们绘制实际值与预测值的折线图:
plt.plot(actual_predicted_df["actual"],'b') plt.plot(actual_predicted_df["predicted"],'r') plt.show()
在这里,我们绘制了交通量预测实际值与预测值的值。蓝线代表实际流量,红线代表我们的 LSTM 模型的预测值:
图 5.28 – 绘制实际交通量与预测交通量的图表
正如我们所看到的,该模型的预测非常好,并且符合预期结果。LSTM 是最强的算法之一对于时间序列,由于其良好的预测能力而被广泛使用。
额外的培训和测试结果
你也可以训练模型通过禁用自动学习率选项。我们以 0.001 的学习率和具有 140 个 epoch 的 Adam 优化器(您可以在前面的代码中注释掉auto_lr部分来实现这一点):
![](https://i-blog.csdnimg.cn/blog_migrate/be74ea2d8eb496af938ac00eb06a77e8.jpeg)
图 5.29 – 140 个 epoch 后的训练结果
与之前的结果相比,您可以看到损失要低得多,准确度要好得多。我们将模型改进了 10 倍。这可以是不依赖推荐学习率以及更多训练时期的组合。
![](https://i-blog.csdnimg.cn/blog_migrate/5865fa30bf5781756d8466bf9a15750c.jpeg)
图 5.30 – 修正后的实际与预测图
接下来的步骤
- 尝试使用具有预设和自动学习率选项的 RMSProp 优化器训练模型。
- 更改源数据集并重新运行代码。一些推荐的选项是伦敦自行车共享数据集或天气预报数据集。
- 我们在本章中使用了 LSTM 模型进行时间序列预测。还有其他可用的 RNN 架构,例如作为门控循环单元( GRU )。尝试另一种网络架构来提高您的预测技能。
概括
时间序列建模是机器学习在工业界和学术界最古老、最普遍的应用之一。在本章中,我们看到了 PyTorch Lightning 如何使用深度学习算法(如 RNN 和 LSTM)促进时间序列建模。这还为数据科学家提供了各种工具来定义 RNN 和 LSTM 配置,并具有轻松监控学习和丢失率的功能。我们了解了如何使用 PyTorch Lightning 快速构建用于时间序列预测的复杂深度学习模型。
我们将在下一章继续我们的 PyTorch Lightning 之旅,其中包含机器学习中最迷人和最新的算法之一——生成对抗网络( GAN )。它可以生成不存在的人和物体的真实面孔,它们看起来如此真实,以至于没有人可以肯定地说它们是人造实体。使用 PyTorch Lightning,您会发现执行生成建模要容易得多。