面向经济和金融的 TensorFlow2 机器学习(三)
原文:Machine Learning for Economics and Finance in TensorFlow 2
七、时间序列
经济学中的实证工作通常与因果推理和假设检验有关,而机器学习则以预测为中心。然而,当涉及到经济和金融预测时,目标之间有一个明显的交集。因此,人们对使用机器学习方法来产生和评估经济预测越来越感兴趣。
在第二章中,我们讨论了 Coulombe 等人(2019),他们评估了机器学习对于时间序列计量经济学的有用性。他们认为非线性模型、正则化、交叉验证和替代损失函数是潜在的有价值的工具,可以引入用于时间序列计量经济学。
在这一章中,我们将讨论机器学习对于时间序列预测的价值。由于我们将专注于 TensorFlow 实现,我们的重点将从 Coulombe 等人(2019 年)转移,而是专注于深度学习模型。特别是,我们将利用具有专门层的神经网络模型来处理顺序数据。
在整个章节中,我们将建立一个预测练习(Nakamura 2005),这是神经网络在时间序列计量经济学中的第一个应用。Nakamura (2005)使用密集神经网络证明了单变量自回归模型在预测通货膨胀方面的优势。
机器学习的序列模型
到目前为止,我们已经讨论了神经网络的几个专门层,但是还没有解释如何处理顺序数据。正如我们将看到的,在神经网络中有处理这种数据的健壮框架,这主要是为了自然语言处理(NLP)的目的而开发的,但在时间序列上下文中同样有用。在本章的最后,我们还将简要地回到它们在 NLP 环境中的使用。
密集神经网络
我们已经在 5 和 6 章节中使用了密集神经网络;然而,我们还没有解释它们如何适用于顺序数据。到目前为止,我们对神经网络的所有使用都涉及缺乏或没有利用时间维度的练习。
我们将通过研究如何利用序列数据在类似于 Nakamura (2005)的设置中预测季度通货膨胀来开始这一部分。为了进行这个练习,我们将使用美国在 1947 年:Q2 和 2020 年:Q2, 1 之间的季度通货膨胀,绘制在图 7-1 中。此外,根据 Nakamura (2005),我们将考虑单变量模型,其中我们不包括任何超出通货膨胀滞后的额外解释变量。
在前面的章节中,当我们处理文本和图像数据时,我们经常需要执行预处理任务,将原始输入转换为适合在神经网络中使用的内容。对于序列数据,我们还需要将时间序列转换成固定长度的序列。
图 7-1
1947 年 Q2 和 2020 年 Q2 之间的 CPI 通货膨胀。资料来源:美国劳工统计局
我们将从决定序列长度开始,序列长度是我们将用作神经网络输入的滞后数。例如,如果我们选择长度为 3 的序列,那么网络将使用周期 t 、 t-1 和 t-3 中的实现来预测周期 t+h 中的通货膨胀。图 7-2 说明了预处理步骤,其中我们将单个时间序列分割成三个连续观测值的重叠序列。图表的左侧显示了原始输入序列。右侧显示了两个序列示例。如果我们使用单个季度作为预测范围( h =1),虚线矩形将序列与它们预测的值连接起来。
图 7-2
将时间序列划分为三个连续观测值的重叠序列
我们假设数据已经被下载并作为inflation.csv保存在位于data_path的目录中。我们首先用清单 7-1 中的pandas加载它,然后将其转换成一个numpy array。接下来,我们将使用tensorflow.keras.preprocessing.sequence子模块中的TimeseriesGenerator()来定义一个生成器对象。作为输入,它将接受网络的特征和目标、序列的长度以及批量大小。在这种情况下,我们将执行单变量回归,其中特征和目标都是inflation。我们将使用长度为 4 的序列,我们可以使用length参数来设置它。最后,我们将使用 12 的batch_size,这意味着我们的生成器将在每次迭代中产生 12 个序列和 12 个目标值。
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import TimeseriesGenerator
# Set data path.
data_path = '../data/chapter7/'
# Load data.
inflation = pd.read_csv(data_path+'inflation.csv')
# Convert to numpy array.
inflation = np.array(inflation['Inflation'])
# Instantiate time series generator.
generator = TimeseriesGenerator(inflation, inflation,
length = 4, batch_size = 12)
Listing 7-1Instantiate a sequence generator for inflation
我们现在有了一个生成器对象,可以用来创建批量数据。Keras 模型可以使用生成器而不是数据作为输入。在清单 7-2 中,我们将定义一个模型,然后使用生成器训练它。注意,我们使用了一个Sequential()模型。这使我们能够通过按顺序堆叠层来构建模型,并且与顺序数据的使用没有任何关系。
我们首先使用顺序 API 实例化模型。然后,我们设置输入节点的数量以匹配序列长度,定义一个具有两个节点的隐藏层,并定义一个使用linear激活函数的输出层,因为我们有一个连续的目标。最后,我们将使用均方误差损失和一个adam优化器来编译模型。
当我们以前训练模型时,我们使用np.array()或tf.constant()对象作为输入数据。在清单 7-2 中,我们使用了一个生成器,这将要求我们使用fit_generator()方法,而不是之前的fit()。
# Define sequential model.
model = tf.keras.models.Sequential()
# Add input layer.
model.add(tf.keras.Input(shape=(4,)))
# Define dense layer.
model.add(tf.keras.layers.Dense(2, activation="relu"))
# Define output layer.
model.add(tf.keras.layers.Dense(1, activation="linear"))
# Compile the model.
model.compile(loss="mse", optimizer="adam")
# Train the model.
model.fit_generator(generator, epochs=100)
Train for 25 steps
Epoch 1/100
25/25 [==============================] - loss: 4.3247
...
Epoch 100/100
25/25 [==============================] - loss: 0.3816
Listing 7-2Train a neural network using generated sequences
在时段 1 和 100 之间,该模型在减少均方误差方面取得了相当大的进展,将其从 4.32 降低到 0.38。重要的是,我们没有使用正则化,如放弃,也没有创建测试样本分割,因此有可能存在大量的过拟合。在清单 7-3 中,我们使用model的summary()方法来检查它的架构。我们可以看到,它只有 13 个可训练的参数,与我们以前使用的模型相比,这是很小的。
# Print model architecture.
print(model.summary())
_____________________________________________________
Layer (type) Output Shape Param #
=====================================================
dense_1 (Dense) (None, 2) 10
_____________________________________________________
dense_1 (Dense) (None, 1) 3
=====================================================
Total params: 13
Trainable params: 13
Non-trainable params: 0
_____________________________________________________
Listing 7-3Summarize model architecture
我们现在可以使用model.predict_generator(generator)来生成一系列通货膨胀的预测值。图 7-3 描绘了通货膨胀的真实值与我们的模型预测值的对比。虽然模型性能看起来令人信服,但我们还没有采取适当的预防措施来确保我们不会过拟合。
在图 7-4 中,我们通过使用 2000 年后的时期作为测试样本来检验过拟合是否是一个问题。为此,我们需要构造一个单独的生成器,它只使用 2000 年以前的值进行训练。然后,我们使用原始生成器对整个样本进行预测,包括 2000 年以后的值。
图 7-3
密集网络提前一个季度预测通货膨胀
我们可以看到图 7-4 与 2000 年后的图 7-3 看起来没有实质性的不同。特别是,2000 年后似乎没有出现性能下降,如果模型过拟合 2000 年前的数据,这正是我们所预期的。这并不奇怪,因为模型的参数相对较少,很难过拟合。
在本节的剩余部分,我们将利用相同的预处理步骤,但是将向我们的模型添加专门的层,用于处理输入序列。这些图层将利用 lag 结构中编码的时态信息,而不是像我们当前对密集模型所做的那样,对所有要素一视同仁。
图 7-4
基于 1947 年至 2000 年数据训练的模型中的密集网络提前一个季度预测通货膨胀
循环神经网络
循环神经网络接受一系列输入,并使用密集层和专门递归层的组合对其进行处理(Rumelhart 等人,1986 年)。 2 这个输入序列可以是单词向量、单词嵌入、音符,或者,正如我们将在本章中考虑的,在不同时间点的膨胀测量。
我们将遵循 Goodfellow 等人(2017)中给出的循环神经网络(RNNs)的处理方法。作者将递归层描述为由单元组成,每个单元接受输入值 x ( t )和状态h(t-1),并产生输出值 o ( t )。等式 7-1、7-2 和 7-3 给出了为循环单元产生输出值的过程。
在等式 7-1 中,我们取数列的状态,h(t—1),并乘以权重, W 。然后,我们获取输入值, x ( t ),并将其乘以一组单独的权重, U 。最后,我们将这两项以及一个偏差项 b 相加。
方程式 7-1。对 RNN 单元执行 乘法步骤 。
接下来,我们将乘法步骤的输出传递给双曲正切激活函数,如方程 7-2 所示。这一步的输出是系统的更新状态, h ( t )。
方程式 7-2。在 RNN 单元中应用激活函数。
在等式 7-3 给出的最后一步中,我们将更新后的状态乘以一组单独的权重 V ,并添加一个偏置项。
方程式 7-3。从 RNN 单元产生 输出值 。
在本章的例子中,通货膨胀是唯一的特征。这意味着 x ( t )是标量, W 、 U 和 V 也是标量。此外,请注意,这些权重在所有时间段都是共享的,相对于密集网络所需的大小,这减少了模型的大小。在我们的例子中,对于具有一个 RNN 单元的层,我们只需要五个参数。
图 7-5 提供了 RNN 的完整图示。粉红色的节点表示输入值,在我们的例子中是通货膨胀的滞后。橙色节点表示目标变量,即下一季度的通货膨胀。蓝色的节点是单独的 RNN 细胞,形成了 RNN 层。我们图示的网络有四个输入和两个 RNN 单元。
图 7-5
RNN(上)和展开的 RNN 电池(下)的图示
图 7-5 的底部面板显示了一个“展开的”RNN 单元格,其中单元格的迭代结构已被分解为一个序列。在每个单独的步骤中,该状态与一个输入相结合以产生下一个状态。最后一步产生一个输出, o ( t ),这个输出是最后一个密集层的输入——连同其他单元的输出——产生一个季度前的通货膨胀预测。
我们现在已经看到,RNN 通过保留状态来利用顺序数据,并在序列的每一步更新状态。它还通过使用权重共享来减少参数的数量。此外,由于没有必要应用特定于时间的权重,所以也有可能使用具有任意和可变长度的序列的 RNN 单元。
我们已经讨论了 RNN 与密集网络的不同之处。让我们为通胀预测示例构建一个简单的 RNN。我们将从加载清单 7-4 中的数据开始。这里,我们重复了清单 7-1 中的步骤,但是有两个重要的区别。首先,我们使用np.expand_dims()向inflation数组添加一个维度。这将使我们的时间序列数据符合 Keras 中 RNN 像元的输入形状要求。第二,我们定义了一个训练生成器,它通过对inflation数组进行切片,只保留前 211 个观察值,专门使用 2000 年以前的数据。
一旦我们加载并准备好了数据,下一步就是定义模型,我们在清单 7-5 中就是这么做的。正如我们所看到的,这个模型并不比我们用来预测通货膨胀的密集网络需要更多的代码行。我们所做的就是定义一个序列模型,添加一个 RNN 层,并用一个linear激活函数定义一个密集输出层。
注意我们使用的SimpleRNN层需要两个参数:RNN 细胞的数量和输入层的形状。对于第一个参数,我们选择了两个单元来保持网络的简单,但存在数据拟合不足的潜在风险。我们需要提供第二个参数,因为我们将 RNN 层定义为网络中的第一层。我们将input_shape设置为(4,1 ),因为序列长度为 4,特征数量为 1。
# Define sequential model.
model = tf.keras.models.Sequential()
# Define recurrent layer.
model.add(tf.keras.layers.SimpleRNN(2, input_shape=(4, 1)))
# Define output layer.
model.add(tf.keras.layers.Dense(1, activation="linear"))
Listing 7-5Define an RNN model in Keras.
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import TimeseriesGenerator
# Load data.
inflation = pd.read_csv(data_path+'inflation.csv')
# Convert to numpy array.
inflation = np.array(inflation['Inflation'])
# Add dimension.
inflation = np.expand_dims(inflation, 1)
# Instantiate time series generator.
train_generator = TimeseriesGenerator(
inflation[:211], inflation[:211],
length = 4, batch_size = 12)
Listing 7-4Instantiate a sequence generator for inflation
最后一步是编译模型,并使用fit_generator()方法训练它,以及我们之前构建的train_generator。正如我们在清单 7-6 中看到的,该模型实现了比我们在密集网络中通过 100 个时期的训练所能实现的更低的均方误差(0.2594)。此外,如图 7-6 所示,测试样本性能(2000 年后)似乎没有任何明显的下降。
# Compile the model.
model.compile(loss="mse", optimizer="adam")
# Fit model to data using generator.
model.fit_generator(train_generator, epochs=100)
Epoch 1/100
18/18 [==============================] - 1s 31ms/step - loss: 0.9206
...
Epoch 100/100
18/18 [==============================] - 0s 2ms/step - loss: 0.2594
Listing 7-6Compile and train an RNN model in Keras
我们还提到,RNN 模型比密集网络需要更少的参数值。当我们逐步完成在 RNN 像元中执行的操作时,我们看到只有一个 RNN 像元的图层只需要五个参数。在清单 7-7 中,我们将使用model的summary()方法来探索模型的架构。我们可以看到,它在 RNN 层中有八个参数,在密集输出层中有三个参数。它总共有 11 个参数,比我们之前使用的密集网络要少。当然,这里的差别不是特别大,因为两个网络都很小。
图 7-6
基于 1947-2000 年数据训练的 RNN 模型对未来一个季度通货膨胀的预测
# Print model summary.
print(model.summary())
_____________________________________________________
Layer (type) Output Shape Param #
=====================================================
simple_rnn_1 (SimpleRNN) (None, 2) 8
_____________________________________________________
dense_1 (Dense) (None, 1) 3
=====================================================
Total params: 11
Trainable params: 11
Non-trainable params: 0
_____________________________________________________
Listing 7-7Summarize RNN architecture in a Keras model
在实践中,我们通常不会使用未经修改的 RNN 模型。至少,我们要考虑调整两件事。第一个问题与一个技术问题有关,即“消失梯度问题”,这使得训练深度网络具有挑战性。这也是原始 RNN 模型和长序列数据的一个问题。原始 RNN 模型的另一个问题是,它没有考虑到时间上或序列中相距较远的物体比距离较近的物体关系更密切的可能性。在下面的两个小节中,我们将对 RNN 模型做一些小的调整,这将使我们能够处理这两个问题。
长短期记忆(LSTM)
rnn 的第一个问题是,当长数据序列用作输入时,它们遭受消失梯度问题。解决这个问题最有效的方法是利用门控 RNN 室。通常使用两种这样的细胞:(1)长短期记忆(LSTM)和(2)门控循环单位(GRUs)。在这一小节中,我们将集中讨论前者。
Hochreiter 和 Schmidhuber (1997)引入了 LSTM 模型,并通过使用限制长序列中的信息跟随的操作符来发挥作用。我们将再次跟随 Goodfellow 等人(2017)描述在 LSTM 中执行的操作。
等式 7-4、7-5 和 7-6 定义了“忽略门”、“外部输入门”和“输出门”,所有这些门在控制信息通过 LSTM 单元时都起作用。
方程式 7-4。可训练重量的定义称为忘记门。
方程式 7-5。称为外部输入门的可训练权重的定义。
方程式 7-6。称为输出门的可训练权重的定义。
请注意,每个门具有相同的函数形式,并使用 sigmoid 激活函数,但有自己单独的权重和偏差。这允许学习门控过程,而不是根据固定的规则来应用。
内部状态使用等式 7-7 中的表达式更新,其中遗忘门、外部输入门、输入序列和状态均适用。
方程式 7-7。用于更新内部状态的表达式。
最后,我们利用内部状态和输出门更新隐藏状态,如等式 7-8 所示。
方程式 7-8。用于更新隐藏状态的表达式。
虽然门的使用增加了模型中的参数数量,但在许多实际应用中,它也在处理长序列方面产生了实质性的改进。出于这个原因,我们通常会使用 LSTM 模型作为时间序列分析的基线,而不是原始的 RNN 模型。
在清单 7-8 中,我们使用 100 个时期来定义和训练 LSTM 模型。唯一的区别是我们使用了tf.keras.layers.LSTM(),而不是tf.keras.layers.SimpleRNN()。我们可以看到,在 100 个历元之后,LSTM 的均方误差比 RNN 高。这是因为模型必须训练更多的重量,这将需要额外的训练时期。此外,LSTM 可能在序列较长的设置中最有用。
# Define sequential model.
model = tf.keras.models.Sequential()
# Define recurrent layer.
model.add(tf.keras.layers.LSTM(2, input_shape=(4, 1)))
# Define output layer.
model.add(tf.keras.layers.Dense(1, activation="linear"))
# Compile the model.
model.compile(loss="mse", optimizer="adam")
# Train the model.
model.fit_generator(train_generator, epochs=100)
Epoch 1/100
18/18 [==============================] - 1s 62ms/step - loss: 3.1697
...
Epoch 100/100
18/18 [==============================] - 0s 3ms/step - loss: 0.5873
Listing 7-8Train an LSTM model in Keras
最后,在清单 7-9 中,我们总结了模型的架构。当我们讨论 LSTM 单元所需的额外操作时,我们提到它引入了一个遗忘门、一个外部输入门和一个输出门。所有这些都需要它们自己的一组参数。从清单 7-9 中我们可以看到,LSTM 层使用了 32 个参数,是 RNN 的四倍。
# Print model architecture.
print(model.summary())
_____________________________________________________
Layer (type) Output Shape Param #
=====================================================
lstm_1 (LSTM) (None, 2) 32
_____________________________________________________
dense_1 (Dense) (None, 1) 3
=====================================================
Total params: 35
Trainable params: 35
Non-trainable params: 0
_____________________________________________________
Listing 7-9Summarize LSTM architecture in a Keras model
中间隐藏状态
按照惯例,LSTM 模型只利用隐藏状态的最终值。例如,在图 7-5 中,模型使用了 h ( t ),而没有使用h(t-1)、h(t-2)和h(t-3),尽管我们已经计算过它们。然而,最近的工作表明,使用中间隐藏状态可以在建模长期依赖关系方面带来相当大的改进,特别是在自然语言处理问题方面(周等,2016)。这通常是在注意力模型的环境中完成的。
我们不会在这里讨论注意力模型,但会解释如何利用 LSTM 模型中的隐藏状态。让我们从天真地设置清单 7-8 模型中的 LSTM 单元格开始,通过将return_sequences设置为True来返回隐藏状态。我们将在清单 7-10 中这样做,然后使用summary()方法检查模型的架构。
# Define sequential model.
model = tf.keras.models.Sequential()
# Define recurrent layer to return hidden states.
model.add(tf.keras.layers.LSTM(2, return_sequences=True,
input_shape=(4, 1)))
# Define output layer.
model.add(tf.keras.layers.Dense(1, activation="linear"))
# Summarize model architecture.
model.summary()
_____________________________________________________
Layer (type) Output Shape Param #
=====================================================
lstm_1 (LSTM) (None, 4, 2) 32
_____________________________________________________
dense_1 (Dense) (None, 4, 1) 3
=====================================================
Total params: 35
Trainable params: 35
Non-trainable params: 0
_____________________________________________________
Listing 7-10Incorrect use of LSTM hidden states
正如我们所看到的,该模型的架构有些不寻常:它不是为批处理中的每个观察值输出一个标量预测,而是输出一个 4x1 向量。这似乎是 LSTM 层的结果,它现在从其两个 LSTM 单元中的每一个输出 4x1 向量,而不是标量。
有几种方法可以利用 LSTM 输出。其中一种方法称为叠加 LSTM (Graves 等人,2013 年)。其工作原理是将全序列隐藏状态传递到第二个 LSTM 层,从而在网络中创建深度,以允许不止一个级别的表示。
在清单 7-11 中,我们定义了这样一个模型。在第一个 LSTM 层中,我们使用了一个具有三个 LSTM 单元和输入形状(4,1)的层。我们将return_sequences设置为True,这意味着每个单元格将返回一个 4x1 的隐藏状态序列,而不是一个标量。然后,我们将把这个 3-张量(4x1x3)传递给具有两个单元的第二个 LSTM 层,它只返回最终的隐藏状态,而不返回中间状态值。
# Define sequential model.
model = tf.keras.models.Sequential()
# Define recurrent layer to return hidden states.
model.add(tf.keras.layers.LSTM(3, return_sequences=True,
input_shape=(4, 1)))
# Define second recurrent layer.
model.add(tf.keras.layers.LSTM(2))
# Define output layer.
model.add(tf.keras.layers.Dense(1, activation="linear"))
Listing 7-11Define a stacked LSTM model
清单 7-12 总结了该模型的架构。我们可以看到,它现在输出一个标量预测,这就是我们想要的通货膨胀预测。我们将省略对模型性能的分析,但将指出这种模型在时间序列预测中的应用仍未得到充分探索。在建模长期依赖性很重要的情况下,使用堆叠 LSTM 模型、注意力模型或变压器模型可能会导致时间序列预测的改进。
# Summarize model architecture.
model.summary()
_____________________________________________________
Layer (type) Output Shape Param #
=====================================================
lstm_1 (LSTM) (None, 4, 3) 60
_____________________________________________________
lstm_2 (LSTM) (None, 2) 48
_____________________________________________________
dense_1 (Dense) (None, 1) 3
=====================================================
Total params: 111
Trainable params: 111
Non-trainable params: 0
_____________________________________________________
Listing 7-12Summarize stacked LSTM architecture
多元预测
到目前为止,我们已经关注了不同方法的机制,并围绕 Nakamura (2005)的单变量通胀预测练习构建了所有示例。我们讨论过的方法都适用于多变量环境。为了完整起见,我们将提供一个简单的多变量预测示例,使用 LSTM 模型和梯度提升树,我们在第四章中讨论过。我们将再次尝试预测通货膨胀,但将以每月一次的频率进行,并使用五个特征,而不是一个。
我们将从加载和预览清单 7-13 中的数据开始。然后,我们将讨论如何使用 LSTM 和梯度增强树实现多元预测模型。我们添加的四个特征是失业、制造业工作时间、制造业每小时收入和货币供应量(M1)。失业率用第一个差异来衡量,而所有水平变量都用前一时期的百分比变化来转换。
import pandas as pd
# Load data.
macroData = pd.read_csv(data_path+'macrodata.csv',
index_col = 'Date')
# Preview data.
print(macroData.round(1).tail())
Inflation Unemployment Hours Earnings M1
Date
12/1/19 -0.1 0.1 0.5 0.2 0.7
1/1/20 0.4 0.6 -1.7 -0.1 0.0
2/1/20 0.3 -0.2 0.0 0.4 0.8
3/1/20 -0.2 0.8 -0.2 0.4 6.4
4/1/20 -0.7 9.8 -6.8 0.5 12.9
Listing 7-13Load and preview inflation forecast data
-什么
正如我们在本章前面看到的,我们可以通过实例化一个生成器来准备在 LSTM 模型中使用的数据。我们首先将目标和特征转换成np.array()对象。然后我们将为训练数据创建一个生成器,为测试数据创建另一个生成器。在前面的例子中,我们使用季度数据和四个季度的序列长度。在这种情况下,我们将在清单 7-14 中使用月度数据和 12 个月的序列长度。
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import TimeseriesGenerator
# Define target and features.
target = np.array(macroData['Inflation'])
features = np.array(macroData)
# Define train generator.
train_generator = TimeseriesGenerator(features[:393],
target[:393], length = 12, batch_size = 6)
# Define test generator.
test_generator = TimeseriesGenerator(features[393:],
target[393:], length = 12, batch_size = 6)
Listing 7-14Prepare data for use in LSTM model
定义了生成器之后,我们现在可以训练清单 7-15 中的模型。我们将使用两个 LSTM 电池。此外,我们需要改变输入形状,因为现在每个序列中有 12 个元素和 5 个特征。经过 20 个时期的训练,该模型将均方误差从 0.3065 降低到 0.0663。如果你已经使用计量经济学模型进行了宏观经济预测,你可能会担心模型参数的数量,因为我们使用了更长的序列和更多的变量;然而,由于我们前面讨论的原因,更长的序列长度不会增加参数的数量。事实上,该模型只有 67 个参数。
# Define sequential model.
model = tf.keras.models.Sequential()
# Define LSTM model with two cells.
model.add(tf.keras.layers.LSTM(2, input_shape=(12, 5)))
# Define output layer.
model.add(tf.keras.layers.Dense(1, activation="linear"))
# Compile the model.
model.compile(loss="mse", optimizer="adam")
# Train the model.
model.fit_generator(train_generator, epochs=100)
Epoch 1/20
64/64 [==============================] - 2s 26ms/step - loss: 0.3065
...
...
Epoch 20/20
64/64 [==============================] - 0s 6ms/step - loss: 0.0663
Listing 7-15Define and train LSTM model with multiple features
最后,在清单 7-16 中,我们将通过比较训练样本结果和测试样本结果来评估模型。我们可以看到,训练集性能似乎比测试集性能更好,这是常见的;然而,如果差异变得足够大,我们应该考虑使用正则化或在更少的时期后终止训练过程。
# Evaluate training set using MSE.
model.evaluate_generator(train_generator)
0.06527029448989197
# Evaluate test set using MSE.
model.evaluate_generator(test_generator)
0.15478561431742632
Listing 7-16Use MSE to evaluate train and test sets
梯度增强树
作为最后一个例子,我们将考虑执行相同的预测练习,但是使用梯度增强树,我们在第四章中讨论过。在 TensorFlow 提供的一系列工具中,梯度提升树和深度学习最适合时间序列预测任务。
正如 LSTM 模型要求我们通过将数据分割成序列来准备数据一样,使用树进行梯度推进将要求我们以 Estimator API 中可用的格式来准备数据。这将涉及为五个特性中的每一个定义特性列,正如我们在清单 7-17 中所做的。
下一步是定义生成数据的函数。我们将为训练和测试函数分别做这件事,这样我们就可以评估过拟合,就像我们对 LSTM 的例子所做的那样。清单 7-18 定义了这两个函数。同样,我们使用相同的样本分割:训练集将覆盖 2000 年之前的年份,测试集将覆盖之后的年份。
# Define lagged inflation feature column.
inflation = tf.feature_column.numeric_column(
"inflation")
# Define unemployment feature column.
unemployment = tf.feature_column.numeric_column(
"unemployment")
# Define hours feature column.
hours = tf.feature_column.numeric_column(
"hours")
# Define earnings feature column.
earnings = tf.feature_column.numeric_column(
"earnings")
# Define M1 feature column.
m1 = tf.feature_column.numeric_column("m1")
# Define feature list.
feature_list = [inflation, unemployment, hours,
earnings, m1]
Listing 7-17Define feature columns
在清单 7-19 中,我们使用 100 个历元和train_data来训练一个BoostedTreeRegressor。然后,我们对训练集和测试集进行评估,并打印结果。
# Define input function for training data.
def train_data():
train = macroData.iloc[:392]
features = {"inflation": train["Inflation"],
"unemployment": train["Unemployment"],
"hours": train["Hours"],
"earnings": train["Earnings"],
"m1": train["M1"]}
labels = macroData["Inflation"].iloc[1:393]
return features, labels
# Define input function for test data.
def test_data():
test = macroData.iloc[393:-1]
features = {"inflation": test["Inflation"],
"unemployment": test["Unemployment"],
"hours": test["Hours"],
"earnings": test["Earnings"],
"m1": test["M1"]}
labels = macroData["Inflation"].iloc[394:]
return features, labels
Listing 7-18Define the data generation functions
结果表明模型可能过拟合。平均训练损失为 0.01,平均测试损失为 0.14。这表明我们应该尝试使用更少的历元再次训练模型,然后看看两者之间的差距是否缩小。如果我们没有看到两者之间的收敛,那么我们将希望执行额外的模型调整以减少过拟合。查看我们可以调整的参数,参见第四章。
# Instantiate boosted trees regressor.
model = tf.estimator.BoostedTreesRegressor(feature_columns =
feature_list, n_batches_per_layer = 1)
# Train model.
model.train(train_data, steps=100)
# Evaluate train and test set.
train_eval = model.evaluate(train_data, steps = 1)
test_eval = model.evaluate(test_data, steps = 1)
# Print results.
print(pd.Series(train_eval))
print(pd.Series(test_eval))
average_loss 0.010534
label/mean 0.416240
loss 0.010534
prediction/mean 0.416263
global_step 100.000000
dtype: float64
average_loss 0.145123
label/mean 0.172864
loss 0.145123
prediction/mean 0.286285
global_step 100.000000
dtype: float64
Listing 7-19Train and evaluate model. Print results
摘要
将机器学习应用于经济学和金融学的挑战之一是,机器学习与预测有关,而经济学和金融学的大部分研究与因果推断和假设检验有关。然而,机器学习与经济学在几个领域有相当大的重叠,预测就是两者完全重合的例子。
在这一章中,我们研究了如何利用机器学习中的时间序列预测工具,主要关注深度学习模型,但也涵盖了 TensorFlow 中也可用的梯度提升树。我们围绕神经网络在经济学中的最早用途之一构建了示例,用于时间序列预测(Nakamura 2005)。然后,我们介绍了现代模型,包括 RNNs、lstm 和堆叠 lstm,这些模型主要是为其他顺序数据处理任务(如 NLP)开发的。
有兴趣了解更多利用深度学习模型进行宏观经济时间序列预测的读者,不妨读一读 Cook and Hall (2017)。关于最近在股票回报和债券溢价预测方面的金融工作,请参见等人(2016 年)、Messmer (2017 年)、Rossi (2018 年)和 Chen 等人(2019 年)。有关高维时间序列回归和稀疏群套索模型的近期工作,请参见 Babii、Ghysels 和 Striaukas (2019、2020)。
文献学
Babii,a .,E. Ghysels 和 J. Striaukas。2019."对具有异方差和自相关的高维回归的推断."arXiv 预印本。
Babii,a .,E. Ghysels 和 J. Striaukas。2020."机器学习时间序列回归及其在临近预报中的应用."arXiv 预印本。
bianchi d . m . büchner 和 A. Tamoni。2020."机器学习的债券风险溢价." WBS 金融集团研究论文第 252 号。
Chen,l .,M. Pelger 和 J. Zhu。2019.“资产定价的深度学习。”(arXiv)。
库克,T.R .和 A.S .霍尔。2017."用深度神经网络进行宏观经济指标预测."堪萨斯城美联储银行,研究工作论文 17-11。
库隆贝,P.G .,m .勒鲁,d .斯蒂凡诺维奇和 s .苏普雷南特 2019."机器学习对宏观经济预测有什么用?" CIRANO 工作底稿。
古德费勒,我,y .本吉奥,和 a .库维尔。2017.深度学习。麻省剑桥:麻省理工学院出版社。
格雷夫斯,a . a .-r .穆罕默德和 g .辛顿。2013."深度循环神经网络的语音识别." arXiv。
F .关昊、何振梁和 N.G .波尔森。2018.“预测资产回报的深度学习。” arXiv。
希顿,J.B .,N.G .波尔森和 J.H .维特。2016.《金融深度学习:深度投资组合》商业和工业中应用的随机模型33(1):3–12。
Hochreiter,s .和 J. Schmidhuber。1997."长短期记忆"神经计算9(8):1735–1780。
梅斯梅尔,M. 2017。"深度学习和预期收益的横截面." SSRN 工作文件。
中村,Emi。2005."使用神经网络进行通货膨胀预测."经济学快报85:373–378。
罗西,A.G. 2018。“用机器学习预测股市回报。”工作文件。
鲁梅尔哈特,d . g .辛顿和 r .威廉姆斯。1986."通过反向传播误差学习表征."性质533–536。
周平,史文伟,田军,齐,李,郝海东,徐。2016."用于关系分类的基于注意的双向长短期记忆网络."计算语言学协会第 54 届年会会议录。柏林:计算语言学协会。
Footnotes 1消费者价格指数(CPI)和由此得出的通货膨胀指标由劳工统计局计算: www.bls.gov 。我们在这个练习中使用的系列可以在 BLS 的网站上以 ID 号 CUSR0000SA0 获得。
2
在自然语言处理环境中,RNNs 通常还包含一个嵌入层。
八、降维
机器学习中的很多问题类本来就是高维的。例如,自然语言处理问题通常涉及从单词中提取含义,这些单词可能出现在大量难以处理的书面潜在序列中。即使我们只限于分析文本中最常见的 1000 个单词,一个 50 个单词的短段落也将有 10 150 种可能的排列,这比可观测宇宙的原子数量还要多。在这种情况下,如果不重新思考问题或减少问题的维度,我们就不可能取得进展。
经济学和金融学的研究经常使用降维技术,如主成分分析(PCA)和因子分析(FA)。这通常是在协变量(特征)的数量足够大以至于存在过拟合的风险或明显违反计量经济学模型假设的情况下进行的。当有兴趣将数据减少到少数感兴趣的因素时,有时也使用 PCA 和 FA。
在本章中,我们将简要讨论机器学习和经济学中使用的两种方法:PCA 和偏最小二乘法(PLS)。然后,我们将介绍用于机器学习的自编码器的概念。自编码器执行“上采样”和“下采样”或“压缩”和“解压缩”的组合这个过程的副产品是一种潜在状态,它对恢复原始输入状态所需的信息进行编码。我们可以认为自编码器提供了一种灵活的、基于深度学习的降维方法。
经济学中的降维
在本节中,我们将遵循 Gentzkow 等人(2019)的符号,该符号讨论了文本分析环境中的维度缩减。此外,我们将使用sklearn和tensorflow的组合来执行经济学中常用的降维任务。虽然在tensorflow什么都可以做,但是它缺少sklearn提供的 PCA 和 PLS 的许多便利方法。
在本章的大部分内容中,我们还将使用一个共同的数据集:1961 年至 2020 年期间 25 个国家的 GDP 增长:Q2 和 Q1,该数据集由经合组织编制。数据曲线如图 8-1 所示。我们省略了一个图例,因为各个国家系列无法区分。
图 8-1
25 个国家从 1961 年的 GDP 增长:Q2 到 2020 年:Q1
在大多数练习中,我们将尝试提取样本中所有国家的共同增长成分。像主成分分析这样的技术将使我们能够确定增长的方差中有多少份额是由少数几个共同成分解释的。我们还将看到这些组成部分如何与单个国家系列相关,从而了解哪些国家可能负责推动国际增长。
主成分分析
经济学和金融学中最常用的降维方法是主成分分析。PCA 将一组特征映射到 k 主成分,其中 k 由计量经济学家设定。这些成分按它们在数据中解释的方差份额排序。例如,第一个主成分解释了数据中最大的方差份额。此外,它们被构造成正交的。
在许多情况下,我们将执行 PCA,目的是降低数据集的维数,以便我们可以在回归中使用少量的主成分。我们之前描述的属性使它在这方面特别有吸引力。
使用 Gentzkow 等人(2019 年)的符号,我们可以将 PCA 记为等式 8-1 中给出的最小化问题的解决方案。
方程式 8-1。主成分分析 最小化问题 。
在清单 8-1 和 8-2 中,我们概述了如何在tensorflow中解决这样的优化问题;然而,出于我们的目的,使用sklearn中的实现会更方便,我们将在本章的剩余部分中这样做。
import tensorflow as tf
import pandas as pd
import numpy as np
# Define data path.
data_path = '../data/chapter8/'
# Load data.
C = pd.read_csv(data_path+'gdp_growth.csv',
index_col = 'Date')
# Convert data to constant object.
C = tf.constant(np.array(C), tf.float32)
# Set number of principal components.
k = 5
# Get shape of feature matrix.
n, p = C.shape
# Define variable for gamma matrix.
G = tf.Variable(tf.random.normal((n, k)), tf.float32)
# Define variable for beta matrix.
B = tf.Variable(tf.random.normal((p, k)), tf.float32)
Listing 8-1Define variables for PCA in TensorFlow
清单 8-1 加载数据作为特征矩阵, C 。然后它将矩阵转换为一个tf.constant()对象,将主成分的数量设置为 5,然后构造 G 和 B 矩阵。请注意, G 是一个 n x k 矩阵,而 B 是一个 p x k 矩阵,其中 n 是时间段的数量, p 是国家的数量。
在我们的例子中, G 矩阵捕捉了每个时期因素的影响大小。此外, B 衡量每个因素与每个国家相关的程度。
在清单 8-2 中,我们定义了一个损失函数pcaLoss,它将 C 、 G 和 B 作为输入,并返回一个损失值,根据等式 8-1 构建。然后,我们实例化一个优化器,并在 1000 个时期内训练该模型。回想一下,只有 G 和 B 是可训练的,应该提供给var_list。
# Define PCA loss.
def pcaLoss(C, G, B):
D = C - tf.matmul(G, tf.transpose(B))
DT = tf.transpose(D)
DDT = tf.matmul(D, DT)
return tf.linalg.trace(DDT)
# Instantiate optimizer.
opt = tf.optimizers.Adam()
# Perform train model.
for i in range(1000):
opt.minimize(lambda: pcaLoss(C, G, B), var_list = [G, B])
Listing 8-2Perform PCA in TensorFlow
既然我们已经看到了如何使用tensorflow来构建 PCA 的求解方法,那么让我们看看如何使用sklearn来完成相同的任务。清单 8-3 从sklearn.decomposition导入PCA方法并加载和准备数据。我们将使用np.array()格式的数据。
在清单 8-4 中,我们设置了主要组件的数量,实例化了一个PCA模型,并应用了fit()方法。我们现在可以恢复相当于我们在tensorflow训练的矩阵。特别是,我们可以使用components_方法恢复 B ,使用pca.transform(C)恢复 G 。除此之外,我们可以恢复每个主成分所解释的方差的份额, S 。
# Set number of components.
k = 25
# Instantiate PCA model with k components.
pca = PCA(n_components=k)
# Fit model.
pca.fit(C)
# Return B matrix.
B = pca.components_.T
# Return G matrix.
G = pca.transform(C)
# Return variance shares.
S = pca.explained_variance_ratio_
Listing 8-4Perform PCA with sklearn
from sklearn.decomposition import PCA
# Load data.
C = pd.read_csv(data_path+'gdp_growth.csv',
index_col = 'Date')
# Transform feature matrix into numpy array.
C = np.array(C)
Listing 8-3Import the PCA library from sklearn and prepare the data
请注意,我们已经计算了 25 个主成分,这是我们最初拥有的 GDP 增长序列的数量。因为我们的目标是降维,所以我们希望降低这个数字。一种常见的选择主成分的直观方法叫做“肘法”这需要绘制方差的解释份额, S ,以确定斜率大小的急剧下降——一个“肘形”——这表明下一个主成分在重要性上远不如前一个主成分。这可以在图 8-2 中看到。
图 8-2
主成分解释方差份额图
根据图 8-2 ,最明显的“肘”出现在第五个主成分上。随后的主要成分似乎解释了相当小的 GDP 增长份额。因此,我们可能希望在随后的练习中只使用前五个主要部分。
除此之外,我们可能还希望看到主要成分和原始国家系列之间的关联强度。这些值在 B 矩阵中给出。图 8-3 绘出了第一主成分,由 B 的第一列给出。这似乎是希腊和冰岛等小型开放经济体增长的一个组成部分。
图 8-3
国家系列和第一主成分之间的关联强度
一般来说,当我们执行 PCA 或另一种形式的降维时,我们将在更广泛的问题的背景下这样做。一个常见的应用是主成分回归(PCR),这是一个两步程序,包括使用主成分分析,然后在回归中包含选定的主成分。例如,在 Bernanke 等人(2005)中使用了这种方法的一种变体来执行因子增强向量自回归(FAVAR),他们用这种方法来确定货币传导机制。 1
我们将考虑方程 8-2 形式的一个简单问题,其中我们想使用其他国家的增长数据来预测加拿大的 GDP 增长。我们可能希望这样做,以便在国家缺少某个值的时期估算 GDP 增长的值。或者,我们可能对恢复系数估计本身感兴趣,因此我们可以看到一个国家的 GDP 增长如何受到不同的全球增长成分的影响。
方程式 8-2。 主成分回归 。
在清单 8-5 中,我们使用pandas加载数据,从DataFrame中提取加拿大的列,创建DataFrame的副本,从该副本中删除卢森堡的列,然后将两者都转换为np.array()对象。
import tensorflow as tf
import numpy as np
import pandas as pd
# Load data.
gdp = pd.read_csv(data_path+'gdp_growth.csv',
index_col = 'Date')
# Copy Canada from C.
Y = gdp['CAN'].copy()
# Copy gdp to C and drop LUX.
C = gdp.copy()
del C['CAN']
# Convert data to numpy arrays.
Y = np.array(Y)
C = np.array(C)
Listing 8-5Prepare data for use in a principal component regression
在清单 8-6 中,我们对 C 执行 PCA 并恢复主成分 G ,我们将其用作tensorflow中 G 上 Y 的 PCR 回归的输入。
# Set number of components.
k = 5
# Instantiate PCA model with k components.
pca = PCA(n_components=k)
# Fit model and return principal components.
pca.fit(C)
G = tf.cast(pca.transform(C), tf.float32)
# Initialize model parameters.
beta = tf.Variable(tf.random.normal([k,1]),
tf.float32)
alpha = tf.Variable(tf.random.normal([1,1]),
tf.float32)
# Define prediction function.
def PCR(G, beta, alpha):
predictions = alpha + tf.reshape(
tf.matmul(G, beta), (236,))
return predictions
# Define loss function.
def mseLoss(Y, G, beta, alpha):
return tf.losses.mse(Y, PCR(G, beta, alpha))
# Instantiate an optimizer and minimize loss.
opt = tf.optimizers.Adam(0.1)
for j in range(100):
opt.minimize(lambda: mseLoss(Y, G, beta, alpha),
var_list = [beta, alpha])
Listing 8-6Perform PCA and PCR
现在我们已经训练了一个模型,我们可以用它来预测加拿大的 GDP 增长序列。我们对照图 8-4 中的真实系列绘制该系列。在始于 20 世纪 80 年代中期的大缓和时期之前,我们可以看到 GDP 增长更加不稳定,模型拟合度更差。然而,1980 年以后,加拿大国内生产总值的大部分增长似乎是由其他 24 个国家的国内生产总值增长系列中的五个因素所解释的。
图 8-4
加拿大实际和 PCR 预测的 GDP 增长
我们的发现表明,有一些共同的全球性因素与增长有关。如果我们想进一步研究这个问题,我们可以通过使用 B 矩阵检查这些因素与不同国家的关系来确定这些因素是什么。例如,北美的增长可能对加拿大的增长尤为重要。主成分分析将帮助我们减少问题的维度,但也会给我们一些工具,让我们尝试讲述减少后剩下的似乎可信的故事。
偏最小二乘法
PCR 仅使用五个主成分就成功地解释了加拿大 GDP 增长的季度变化。虽然我们描述的两步过程便于实现,并且对于各种各样的任务来说执行起来都很方便,但是它没有考虑第一阶段中 C 和 Y 之间的关系,如果我们的目标最终是执行预测,我们可能会认为这是次优的。
事实上,PCA 是专门使用 C 来执行的。然后,我们从 C 中提取主要成分,并在回归中使用它们,将 Y 作为因变量。然而,我们选择的组成部分可能解释了许多国家 GDP 增长差异的很大一部分,但不是加拿大。
然而,PCR 的替代方法可以解释 Y 和 C 的特征列之间的同步强度。在这个简短的小节中,我们将考虑其中的一个——偏最小二乘法(PLS)。我们的描述遵循 Gentzkow 等人(2019)的方法,由以下步骤组成:
-
计算
,其中 C * j 是第 j 个特征列,而 ψ j 是 Y 和 C j * 之间的单变量协方差。
-
相对于
正交 Y 和 C 。
-
重复步骤 1。
-
重复步骤 2 和 1,生成所需数量的组件。
与 PLR 相反,PLS 利用 Y 和 C 之间的协方差来生成最适合预测 Y 的分量。原则上,这将引导我们选择比我们在 C 上使用 PCA 然后在第二步中执行线性回归产生的预测值更大的成分。
在清单 8-7 中,我们使用sklearn实现了一个 PLS 回归。我们将假设 C 和 Y 已经被定义为它们在清单 8-5 中的样子。为了与 PLR 结果相比较,我们将再次使用五个组件。然后,我们将实例化并训练一个 PLS 模型,然后使用predict()方法生成加拿大的预测时间序列。
from sklearn.cross_decomposition import PLSRegression
# Set number of components.
k = 5
# Instantiate PLS model with k components.
pls = PLSRegression(n_components = k)
# Train PLS model.
pls.fit(C, Y)
# Generate predictions.
pls.predict(C)
Listing 8-7Perform PLS
在图 8-5 中,我们比较了样本期间加拿大实际和 PLS 预测的 GDP 增长。正如预期的那样,PLS 比我们能够用两步 PCA 程序所做的有了轻微的改进。这是因为它允许我们利用我们的目标变量和特征矩阵之间的关系。
图 8-5
加拿大实际和 PLS 预测的 GDP 增长
请注意,PCR 和 PLS 都有多种形式。当我们在第二步中使用 OLS 进行 PCR 时,我们原则上可以使用任何模型来捕捉从特征矩阵中提取的主成分和加拿大 GDP 增长之间的关系。这是在tensorflow而不是sklearn中执行第二步的好处之一。
关于 PLS 的计量经济学理论的更深入的处理,参见 Kelly 和 Pruitt (2013,2015)。此外,对于用 PCA 进行预测的严格处理,参见 Stock 和 Watson (2002)。关于该方法在新冠肺炎疫情期间每周 GDP 增长预测中的应用,请参见 Lewis 等人(2020)。
自编码器模型
自编码器是一种被训练来预测其输入值的神经网络。这种模型可以用来生成音乐、对图像去噪,并执行主成分分析的广义和非线性版本,这是我们在本章中将重点讨论的内容。
自编码器模型是由 LeCun (1987)、Bourlard 和 Kamp (1988)以及 Hinton 和 Zemel (1993)开发的。Goodfellow 等人(2017)将自编码器描述为由两个功能组成。第一个是编码器函数, f (x),在等式 8-3 中给出,它接受输入, x ,并产生一个潜在状态, h 。第二个是解码器功能,如等式 8-4 所示,它采用潜在状态 h ,并产生输入的重构 r 。
方程式 8-3。 编码器功能 。
方程式 8-4。 解码器功能 。
实际上,我们可以通过最小化方程 8-5 中给出的损失函数来训练自编码器。注意, g ( f ( x ))是重构, r ,我们从编码器和解码器功能以及输入集生成。 r 与 x 之间的距离越小,损失越小。
方程式 8-5。 Autoencoder 损失功能 。
网络的编码器部分具有类似于标准密集神经网络的架构。它接受输入,然后将它们通过一系列节点数量逐渐减少的密集层。编码器执行“下采样”或“压缩”相反,解码器具有反向神经网络的架构。它将一个潜在状态作为输入,然后执行“上采样”或“解压缩”以产生更大的输出。
图 8-6 给出了示例自编码器的架构。这里,我们有五个输入节点,在下面的神经网络层中减少到三个。然后,我们从编码器网络输出两个节点。这些被用作解码器网络的输入,该网络向上采样至三个节点,然后是五个节点,最终为我们提供与输入相当的东西。图像顶部的粉色节点是模型输入,而底部的粉色节点表示输入的重构尝试。
图 8-6
自编码器的示例架构
虽然我们将重点关注使用自编码器来执行降维,但它们在机器学习中还有两个更常见的用途,也可以应用于经济和金融问题:
-
降噪:音频和图像往往都含有噪声。自编码器允许我们通过仅记忆图像或音频信号的大的、重要的特征来过滤噪声。通过选择潜在状态节点相对较少的架构,我们可以迫使网络将图像或音频信号中包含的所有信息压缩成几个数字。当我们试图使用解码器重建图像或音频信号时,将不可能恢复特殊噪声,因为这将需要比潜在状态中包含的更多的信息。这意味着我们将只恢复一个降噪版本。
-
生成式机器学习:除了对不同类型的对象进行分类,机器学习算法还可以用来生成一个类的新实例。自编码器模型的解码器被训练成从潜在状态的信息中重建图像。这意味着我们可以通过随机产生一个潜在状态,然后通过解码器来产生全新的图像。此外,我们可以使用编码器从图像中提取潜在状态,并修改它输出的潜在状态,以便在将图像传递给解码器时处理图像。
基于它们作为去噪器和在生成机器学习任务中的使用,有两件事应该是清楚的。首先,我们通常不希望训练自编码器来精确地恢复输入。相反,我们希望它学习数据中的重要关系,以便它能够归纳,而不是记忆。这就是我们使用正则化并保持网络足够小的原因。第二,编码器的输出层,即潜在状态,是一个必须在一组输入中总结特征的瓶颈。这正是它作为一种降维形式有用的原因。
在本章的最后一个练习中,我们将演示如何在相同的 GDP 增长数据上训练自编码器。在清单 8-8 中,我们将假设 Y 和 C 已经被加载,并按照它们在本章中的定义进行定义。然后,我们将定义编码器和解码器模型,它们将共享权重,但也将能够独立地接受输入并产生输出。我们将把潜在状态中的节点数量latentNodes设置为 5,这将在我们在下面的步骤中执行回归时给我们一个五因素 PCR 模型的等价物。
# Set number of countries.
nCountries = 24
# Set number of nodes in latent state.
latentNodes = 5
# Define input layer for encoder.
encoderInput = tf.keras.layers.Input(shape = (nCountries))
# Define latent state.
latent = tf.keras.layers.Input(shape = (latentNodes))
# Define dense output layer for encoder.
encoded = tf.keras.layers.Dense(latentNodes, activation = 'tanh')(encoderInput)
# Define dense output layer for decoder.
decoded = tf.keras.layers.Dense(nCountries, activation = 'linear')(latent)
# Define separate models for encoder and decoder.
encoder = tf.keras.Model(encoderInput, encoded)
decoder = tf.keras.Model(latent, decoded)
# Define functional model for autoencoder.
autoencoder = tf.keras.Model(encoderInput, decoder(encoded))
# Compile model
autoencoder.compile(loss = 'mse', optimizer="adam")
# Train model
autoencoder.fit(C, C, epochs = 200)
Listing 8-8Train
an autoencoder using the Keras API
相对于我们迄今为止用神经网络所做的,这个模型是相当不寻常的。当我们训练模型时,我们可以看到特征和目标是相同的。此外,我们有一个编码器和一个解码器模型,它们本身是功能性的,但也是更大的自编码器模型的一部分,这是我们实际训练的模型。我们还可以看到,我们选择了最简单的架构,假设我们有一个包含五个节点的潜在状态。清单 8-9 对此进行了总结。
# Print summary of model architecture.
print(autoencoder.summary())
_____________________________________________________
Layer (type) Output Shape Param #
=====================================================
input_11 (InputLayer) [(None, 24)] 0
_____________________________________________________
dense_8 (Dense) (None, 5) 125
_____________________________________________________
model_10 (Model) (None, 24) 144
=====================================================
Total params: 269
Trainable params: 269
Non-trainable params: 0
_____________________________________________________
Listing 8-9Autoencoder model architecture summary
总的来说,该模型只有 269 个参数,但经过训练,可以恢复 24 个 GDP 增长序列,每个序列都由 236 个季度的观测数据组成。在图 8-7 中,我们通过绘制美国的实际和预测系列来评估系列构建的质量,这可以使用autoencoder的predict()方法来完成。
自编码器似乎以合理的准确度再现了美国的系列。正如我们之前讨论的,自编码器将被迫丢弃一些噪声,因为瓶颈层(潜在状态)将限制有多少信息可以传递给解码器。因此,我们可以看到我们生成的序列比原始序列具有更低的方差。
图 8-7
使用自编码器重建的美国 GDP 增长序列
下一步是恢复所有周期中的潜在状态,这将由来自编码器的五个输出值组成。我们可以使用编码器函数的 predict 方法来实现这一点,如清单 8-10 所示。
# Generate latent state time series.
latentState = encoder.predict(C)
# Print shape of latent state series.
print(latentState.shape)
(236, 5)
Listing 8-10Generate latent state time series
我们现在可以在回归中使用这些潜在状态时间序列来预测加拿大的 GDP 增长。正如我们从清单 8-11 中所看到的,我们在 PCR 中所做的没有任何实质性的改变。一旦从编码器模型中提取出潜在状态,问题就简化为线性回归。
# Initialize model parameters.
beta = tf.Variable(tf.random.normal([latentNodes,1]))
alpha = tf.Variable(tf.random.normal([1,1]))
# Define prediction function.
def LSR(latentState, beta, alpha):
predictions = alpha + tf.reshape(
tf.matmul(latentState, beta), (236,))
return predictions
# Define loss function.
def mseLoss(Y, latentState, beta, alpha):
return tf.losses.mse(Y, LSR(latentState,
beta, alpha))
# Instantiate an optimizer and minimize loss.
opt = tf.optimizers.Adam(0.1)
for j in range(100):
opt.minimize(lambda: mseLoss(Y, latentState, beta,
alpha), var_list = [beta, alpha])
Listing 8-11Perform dimensionality reduction in a regression setting with an autoencoder latent state
在图 8-8 中,我们使用基于自编码器的潜在状态构建的回归模型,绘制了加拿大 GDP 增长的实际和预测时间序列。我们可以看到,性能与我们能够使用 PLS 实现的性能相似。
图 8-8
使用自编码器对特征集进行降维的加拿大实际和 OLS 预测的 GDP 增长
最后,请注意,对于我们解决这个问题的方法,我们至少可以改变两件事。首先,我们可以修改自编码器的架构。例如,如果我们认为模型不合适,不能跨系列推广,我们可以添加隐藏层或层内的额外节点。第二,我们可以在第二步中使用完全不同的模型,例如神经网络。此外,使用 TensorFlow,我们可以将该模型直接连接到自编码器,共同训练它们用一组五个潜在特征来预测 Y 。这将给我们提供更能预测 Y 的潜在状态,产生该方法的 PLS 类型概括。
摘要
降维是经济学和机器学习中常见的一种经验策略。在许多情况下,当问题的第二步——可能是监督学习任务——使用可用的特征集不可行时,我们将使用降维。使用主成分分析或来自自编码器的潜在状态,我们可以将高维特征集压缩成少量因子。
在本章中,我们演示了如何在tensorflow和sklearn中执行降维任务。专注于 GDP 增长预测,我们看到主成分回归表现良好,但最终使用的因素不是根据它们与因变量的关系选择的。当我们使用偏最小二乘法时,它确实利用了特征和因变量之间的同步性,我们发现预测的质量略有提高。
最后,我们探索了使用自编码器进行降维的可能性。自编码器模型由编码器和解码器网络组成,并被训练以输出其输入的重构。网络的编码器部分输出潜在状态,该潜在状态可以被视为关于输入特征的压缩信息。我们表明,使用来自自编码器的潜在状态进行降维的回归表现与 PLS 相当,并且可以扩展,允许与预测模型进行联合训练。
文献学
伯南克、B.S .、j .博伊文和 p .埃利阿斯。2005."衡量货币政策的效果:一种因子增强向量自回归(FAVAR)方法."经济学季刊 120(1):387–422。
h .布尔拉德和 y .坎普。1988."通过多层感知器和奇异值分解的自动关联."生物控制论 59:291–294。
根茨科,m . b .凯利和 m .塔迪。2019."文本作为数据。"经济文献杂志57(3):535–574。
古德费勒,我,y .本吉奥,和 a .库维尔。2017.深度学习。麻省剑桥:麻省理工学院出版社。
辛顿和 R.S .泽梅尔。1993."自编码器、最小描述长度和亥姆霍兹自由能."1993 年的 NIPS。
b .凯利和 s .普鲁特。2013."现值横截面中的市场预期."金融杂志68(5):1721–1756。
b .凯利和 s .普鲁特。2015."三重回归过滤器:使用多种预测因子进行预测的新方法."计量经济学杂志186(2):294–316。
LeCun,Y. 1987 年。"学习的连接模式"巴黎第六大学博士论文。
刘易斯博士、k .默滕斯和 j .斯托克。2020.“SARS-Cov-2 爆发前几周的美国经济活动。”纽约美联储银行职员报告 920。
斯托克、J.H .和 M.W .沃森。2002."使用大量预测因子的主成分进行预测."美国统计协会杂志97(460):1167–1179。
Footnotes 1使用 FAVAR 可以让 Bernanke 等人(2005)极大地扩展 VAR 中包含的变量集,以便他们可以正确地解释中央银行和私人行为者可以访问的信息集。
九、生成模型
机器学习模型可以分为两类:判别型和生成型。辨别模型被训练来执行分类或回归。也就是说,我们输入一组特征,并期望接收类标签的概率或预测值作为输出。相反,生成模型被训练来学习数据的底层分布。一旦我们训练了一个生成模型,我们就可以用它来产生一个类的新例子。图 9-1 说明了两类模型之间的区别。
图 9-1
鉴别器和发生器模型的比较
到目前为止,我们在这本书里已经关注了判别模型;然而,有一个例外:潜在的狄利克雷分配(Blei et al. 2003),我们在第六章中介绍了它。LDA 模型将文本语料库作为输入,并返回一组主题,其中每个主题被定义为词汇的分布。
最近,生成机器学习文献取得了相当大的进展,其中大部分集中在两种类型的模型的开发上:变分自编码器(VAEs)和生成对抗网络(GANs)。关于图像、文本和音乐生成,这两类模型已经实现了相当大的突破。
在很大程度上,这种进步还没有达到经济学和金融学学科;然而,经济学中的一些工作已经开始使用 GANs。在本章的最后一节,我们将简要讨论甘斯在经济学中的两个最新应用。艾尔。2019 年和 Kaji 等人 2018 年),并推测未来的潜在用途。
可变自编码器
在第八章中,我们介绍了自编码器的概念,它由两个共享权重的网络组成:一个编码器和一个解码器。编码器将模型输入转换为潜在状态。解码器将潜在状态作为输入,并产生输入到编码器的特征的重构。我们通过计算重建损失来训练模型,重建损失是输入和它们的预测值之间的差异的转换。
我们使用自编码器来执行降维,但是讨论了自编码器的其他用途,主要涉及生成任务,例如创建新的图像、音乐和文本。我们没有提到的是,自编码器受到两个问题的困扰,这两个问题阻碍了它们在这些任务上的性能。我们下面讨论的这两个问题都与它们产生潜在状态的方式有关:
-
潜在状态的位置和分布:具有 N 个节点的自编码器的潜在状态是 ℝ N 中的点。对于很多问题,这些点会倾向于聚集在同一个区域;然而,自编码器不允许我们明确地确定这样的点在 ℝ N 中如何以及在哪里聚集。这可能看起来不重要,但它将最终决定哪些潜在状态可以输入到模型中。例如,如果我们试图生成一幅图像,那么知道什么构成了有效的潜在状态,从而知道什么可以被输入到模型中,这将是非常有用的。否则,我们将使用远离模型所观察到的任何东西的状态,这将产生一个新颖的、但也许不可信的图像。
-
训练中不存在的潜在状态的性能:自编码器被训练来为一组例子重建输入。对于与一组特征相关联的潜在状态,解码器应该产生类似于输入特征的输出。然而,如果我们稍微扰动潜在向量,就不能保证解码器有能力从一个从未访问过的点生成一个令人信服的例子。
变分自编码器(VAEs)被开发来克服这些限制。VAEs 不具有潜在状态层,而是具有均值层、对数方差层和采样层。采样图层采用由前面图层中的平均值和对数方差参数定义的正态分布。然后,采样层的输出作为训练过程中的潜在状态传递给解码器。将相同的特征传递给编码器两次,每次都会产生不同的潜在状态。
除了架构上的差异,VAEs 还修改了损失函数,以包括采样层中每个正态分布的 Kullback-Leibler (KL)散度。KL 散度惩罚了每个正态分布与均值和对数方差均为零的正态分布之间的距离。
这些特性的组合完成了三件事。首先,它消除了潜在状态的决定论。每组特征现在将与潜在状态的分布相关联,而不是与单个潜在状态相关联。这将通过强制模型将每个单独的潜在状态特征视为连续变量来提高生成性能。其次,它消除了采样问题。我们现在可以通过使用采样层来随机绘制有效的状态。第三,它修正了潜在空间分布的问题。损失的 KL 散度分量将使分布均值接近于零,并迫使它们具有相似的方差。
本节的剩余部分将重点介绍在 TensorFlow 中实现 VAEs。有关 VAE 模型发展的扩展概述及其理论属性的详细探索,请参见金玛和韦林(2019)。
我们将在本章中使用的例子利用了我们在第八章中介绍的 GDP 增长数据。作为更新,它包括 25 个不同经合组织国家的季度时间序列,时间跨度从 1961 年的 Q2 到 2020 年的 Q1。在第八章中,我们使用降维技术在每个时间点从 25 个系列中提取少量的公共成分。
在本章中,我们将使用 GDP 增长数据来训练一个能够生成类似序列的 VAE。我们将从清单 9-1 开始,导入我们将在本练习中使用的库,然后加载并准备数据。注意,我们转置了 GDP 数据,因此列对应于特定的季度,行对应于国家。然后,我们将数据转换成一个np.array(),并为批量大小和潜在空间中输出节点的数量设置参数。
import tensorflow as tf
import pandas as pd
import numpy as np
# Define data path.
data_path = '../data/chapter9/'
# Load and transpose data.
GDP = pd.read_csv(data_path+'gdp_growth.csv',
index_col = 'Date').T
# Print data preview.
print(GDP.head())
Time 4/1/61 7/1/61 10/1/61 1/1/62
AUS -1.097616 -0.715607 1.139175 2.806800 ...
AUT -0.349959 1.256452 0.227988 1.463310 ...
BEL 1.167163 1.275744 1.381074 1.346942 ...
CAN 2.529317 2.409293 1.396820 2.650176 ...
CHE 1.355571 1.242126 1.958044 0.575396 ...
# Convert data to numpy array.
GDP = np.array(GDP)
# Set number of countries and quarters.
nCountries, nQuarters = GDP.shape
# Set number of latent nodes and batch size.
latentNodes = 2
batchSize = 1
Listing 9-1Prepare GDP growth data for use in a VAE
下一步是定义 VAE 模型架构,它将由一个编码器和一个解码器组成,类似于第八章的自编码器模型。然而,与自编码器相反,在训练过程中,潜在状态将从一组独立的正态分布中采样。我们将从定义一个执行清单 9-2 中的采样任务的函数开始。
# Define function for sampling layer.
def sampling(params, batchSize = batchSize, latentNodes = latentNodes):
mean, lvar = params
epsilon = tf.random.normal(shape=(
batchSize, latentNodes))
return mean + tf.exp(lvar / 2.0) * epsilon
Listing 9-2Define function to perform sampling task in VAE
注意sampling层不包含任何自己的参数。相反,它将一对参数作为输入,从潜在状态中的每个输出节点的标准正态分布中提取epsilon,然后使用与该状态中的节点相对应的mean和lvar参数来转换每个提取。
一旦我们定义了一个采样层,我们还可以定义一个编码器模型,它将非常类似于我们为 autoencoder 模型构建的模型。我们将在清单 9-3 中这样做。唯一的初始区别是,我们将一个国家的完整时间序列作为输入,而不是某个时间点上各国的横截面值。
另一个差异出现在mean和lvar层,这在自编码器中不存在。这些层具有与潜在状态相同数量的节点。这是因为它们由与潜在状态中的每个节点相关联的正态分布的均值和对数方差参数值组成。
我们接下来定义一个Lambda层,它接受我们之前定义的sampling函数,并传递给它mean和lvar参数。我们可以看到,采样层为潜在状态中的每个特征(节点)生成一个输出。最后,我们定义了一个函数模型encoder,它采用输入特征(季度 GDP 增长观察值)并返回一个均值层、一个对数方差层以及使用均值和对数方差参数化正态分布的抽样输出。
# Define input layer for encoder.
encoderInput = tf.keras.layers.Input(shape = (nQuarters))
# Define latent state.
latent = tf.keras.layers.Input(shape = (latentNodes))
# Define mean layer.
mean = tf.keras.layers.Dense(latentNodes)(encoderInput)
# Define log variance layer.
lvar = tf.keras.layers.Dense(latentNodes)(encoderInput)
# Define sampling layer.
encoded = tf.keras.layers.Lambda(sampling, output_shape=(latentNodes,))([mean, lvar])
# Define model for encoder.
encoder = tf.keras.Model(encoderInput, [mean, lvar, encoded])
Listing 9-3Define encoder model for VAE
在清单 9-4 中,我们将为解码器模型和整个可变自编码器定义功能模型。类似于自编码器的解码器组件,它接受潜在状态作为来自编码器的输入,然后生成输入的重构作为输出。全 VAE 模型也与自编码器相似,将时间序列作为输入,并将其转换为同一时间序列的重构。
最后一步是定义损失函数,它由两个部分组成——重建损失和 KL 散度——并将其附加到模型中,我们在清单 9-5 中就是这么做的。重建损失与我们用于自编码器的损失没有什么不同。KL 散度测量每个采样层分布离标准正态分布有多远。它们离得越远,惩罚就越高。
# Compute the reconstruction component of the loss.
reconstruction = tf.keras.losses.binary_crossentropy(
vae.inputs[0], vae.outputs[0])
# Compute the KL loss component.
kl = -0.5 * tf.reduce_mean(1 + lvar - tf.square(mean) - tf.exp(lvar), axis = -1)
# Combine the losses and add them to the model.
combinedLoss = reconstruction + kl
vae.add_loss(combinedLoss)
Listing 9-5Define VAE loss
# Define output for decoder.
decoded = tf.keras.layers.Dense(nQuarters, activation = 'linear')(latent)
# Define the decoder model.
decoder = tf.keras.Model(latent, decoded)
# Define functional model for autoencoder.
vae = tf.keras.Model(encoderInput, decoder(encoded))
Listing 9-4Define decoder model for VAE
最后,在清单 9-6 中,我们编译并训练模型。在清单 9-7 中,我们现在有了一个经过训练的变量自编码器,我们可以用它来执行各种不同的生成任务。例如,我们可以使用vae的predict()方法为给定的时间序列输入生成重建。我们还可以生成给定输入的潜在状态的实现,例如美国的 GDP 增长。我们还可以通过添加随机噪声来扰乱这些潜在状态,然后使用解码器的predict()方法,根据修改后的潜在状态生成一个全新的时间序列。
# Generate series reconstruction.
prediction = vae.predict(GDP[0,:].reshape(1,236))
# Generate (random) latent state from inputs.
latentState = encoder.predict(GDP[0,:].reshape(1,236))
# Perturb latent state.
latentState[0] = latentState[0] + np.random.normal(1)
# Pass perturbed latent state to decoder.
decoder.predict(latentState)
Listing 9-7Generate latent states and time series with trained VAE.
# Compile the model.
vae.compile(optimizer='adam')
# Fit model.
vae.fit(GDP, batch_size = batchSize, epochs = 100)
Listing 9-6Compile and fit VAE
最后,在图 9-2 中,我们展示了 25 个生成的时间序列,它们基于美国 GDP 增长序列的潜在状态实现。然后,我们在 5×5 网格上扰动该原始状态,其中行将[–1,1]间隔上的等间距值添加到第一潜在状态,列将[–1,1]间隔上的等间距值添加到第二潜在状态。网格中心的系列,显示为红色,加上[0,0],因此,是原始的潜在状态。
图 9-2
VAE 生成的美国 GDP 增长时间序列
虽然这个例子很简单,并且为了演示的目的,潜在状态只包含两个节点,但是 VAE 体系结构可以应用于各种各样的问题。例如,我们可以在编码器和解码器中添加卷积层,并改变输入和输出形状。这将给我们一个产生图像的 VAE。或者,我们可以将 LSTM 细胞添加到编码器和编码器中,这将为我们提供一个可以生成文本或音乐的 VAE。 1 此外,基于 LSTM 的架构可以在时间序列生成方面比我们在本例中采用的密集网络方法有所改进。
生成对抗网络
两个模型家族主导了生成机器学习文献:变分自编码器和生成对抗网络。正如我们所见,VAEs 通过操纵潜在状态和它们编码的特征,提供了对实例生成的粒度控制。相比之下,GANs 在制作极具说服力的课程范例方面更为成功。例如,一些最有说服力的生成图像是使用 GANs 生成的。
正如我们在上一节中讨论的,vae 是两个模型的组合:编码器和解码器,由采样层连接。类似地,GANs 也由两个模型组成:生成器和鉴别器。生成器取一个随机的输入向量,我们可能会认为它是一个潜在状态,生成一个类的例子,比如一个真实的 GDP 增长时间序列(或者一个图像,一句话,或者一个乐谱)。
一旦 GAN 的生成器组件生成了一个类的几个示例,它们就被传递给鉴别器,同时还有相同数量的真实示例。在我们的例子中,这将是真实的和生成的真实 GDP 增长序列的组合。然后训练鉴别器来区分真实和虚假的例子。
在鉴别器完成分类任务后,我们可以使用一个对抗性网络来训练发生器,该网络结合了发生器和鉴别器模型。正如 VAE 的编码器和解码器组件的情况一样,敌对网络将与两个网络共享权重。敌对网络将训练发生器使鉴别器网络的损耗最大化。
正如 Goodfellow 等人(2017 年)所讨论的,我们可以将这两个网络视为试图在零和游戏中最大化它们各自的收益,其中鉴别器接收 v ( g , d ),生成器接收 v ( g , d )。发生器选择样本 g 来欺骗鉴别器;鉴别器为每个样本选择概率 d 。等式 9-1 给出了由一组生成图像g∫表征的平衡。
方程式 9-1。GAN 中图像生成 的平衡条件。
因此,当我们训练网络的敌对部分时,我们必须冻结鉴别器权重。这将约束网络改进生成过程,而不是削弱鉴别器。在训练过程中重复这些步骤将最终产生方程 9-1 中描述的进化平衡。
图 9-3 显示了 GAN 的发生器和鉴别器网络。总的来说,生成器产生了新的例子,这些例子不是从数据中提取的。鉴别器将这些示例与真实示例相结合,然后执行分类。敌对网络通过将发电机连接到一个鉴别器上来训练发电机,但权重是固定的。网络上的训练迭代发生。
按照 VAEs 一节中的例子,我们将再次使用 GDP 增长数据,我们在清单 9-8 中加载并准备了这些数据。我们的目的是训练一个 GAN 从随机抽取的向量输入中生成可信的 GDP 增长时间序列。我们将遵循克罗恩等人(2020)描述的 GAN 构建方法。
图 9-3
来自 GAN 的发生器和鉴别器的描述
import tensorflow as tf
import pandas as pd
import numpy as np
# Load and transpose data.
GDP = pd.read_csv(data_path+'gdp_growth.csv',
index_col = 'Date').T
# Convert pandas DataFrame to numpy array.
GDP = np.array(GDP)
Listing 9-8Prepare GDP growth data for use in a GAN
在清单 9-9 中,我们定义了生成模型。我们再次遵循简单的 VAE 模型,画一个有两个元素的向量作为生成器的输入。由于发生器的输入可以看作是 VAE 中潜在向量的类比,我们应该把发生器看作是一个解码器。这意味着我们将从一个狭窄的瓶颈型层开始,并将向上采样到产出,这将是一个生成的 GDP 增长时间序列。
生成器的最简单版本由接受潜在向量的输入层和对输入层进行上采样的输出层组成。由于我们的输出层由 GDP 增长值组成,我们将使用一个linear激活函数。我们还将包含一个带有relu激活的隐藏层,因为否则模型将无法捕捉非线性。
# Set dimension of latent state vector.
nLatent = 2
# Set number of countries and quarters.
nCountries, nQuarters = GDP.shape
# Define input layer.
generatorInput = tf.keras.layers.Input(shape = (nLatent,))
# Define hidden layer.
generatorHidden = tf.keras.layers.Dense(16, activation="relu")(generatorInput)
# Define generator output layer.
generatorOutput = tf.keras.layers.Dense(236, activation="linear")(generatorHidden)
# Define generator model.
generator = tf.keras.Model(inputs = generatorInput, outputs = generatorOutput)
Listing 9-9Define the generative model of a GAN
接下来我们将在清单 9-10 中定义鉴别器。它将实际和生成的 GDP 增长序列作为输入,每个序列的长度为nQuarters。然后,它将为每个投入序列产生一个成为实际国内生产总值增长序列的概率。注意我们没有编译generator,但是编译了discriminator。这是因为我们将使用对抗网络来训练generator。
# Define input layer.
discriminatorInput = tf.keras.layers.Input(shape = (nQuarters,))
# Define hidden layer.
discriminatorHidden = tf.keras.layers.Dense(16, activation="relu")(discriminatorInput)
# Define discriminator output layer.
discriminatorOutput = tf.keras.layers.Dense(1, activation="sigmoid")(discriminatorHidden)
# Define discriminator model.
discriminator = tf.keras.Model(inputs = discriminatorInput, outputs = discriminatorOutput)
# Compile discriminator.
discriminator.compile(loss='binary_crossentropy', optimizer=tf.optimizers.Adam(0.0001))
Listing 9-10Define and compile the discriminator model of a GAN
我们现在已经定义了一个生成器模型和一个鉴别器模型。我们还编写了鉴别器。下一步是定义和编译一个对抗模型,用于训练生成器。对抗模型将与生成器共享权重,并将为鉴别器使用冻结版本的权重——也就是说,当我们训练对抗网络时,权重不会更新,但当我们训练鉴别器时,权重会更新。
清单 9-11 定义了敌对网络。敌对网络的输入是一个潜在向量,因此它将与generator的输入具有相同的大小。我们接下来将发电机模型的输出定义为timeSeries,这将是一个伪 GDP 增长时间序列。然后我们可以将discriminator的可训练性设置为False,这样它就不会在我们训练敌对网络时更新。最后,我们将网络的输出设置为鉴别器的输出,并定义和编译一个功能模型adversarial。在清单 9-12 中,我们将训练discriminator和adversarial。
# Set batch size.
batch, halfBatch = 12, 6
for j in range(1000):
# Draw real training data.
idx = np.random.randint(nCountries,
size = halfBatch)
real_gdp_series = GDP[idx, :]
# Generate fake training data.
latentState = np.random.normal(size=[halfBatch, nLatent])
fake_gdp_series = generator.predict(latentState)
# Combine input data.
features = np.concatenate((real_gdp_series,
fake_gdp_series))
# Create labels.
labels = np.ones([batch,1])
labels[halfBatch:, :] = 0
# Train discriminator.
discriminator.train_on_batch(features, labels)
# Generate latent state for adversarial net.
latentState = np.random.normal(size=[batch, nLatent])
# Generate labels for adversarial network.
labels = np.ones([batch, 1])
# Train adversarial network.
adversarial.train_on_batch(latentState, labels)
Listing 9-12Train the discriminator and the adversarial network
# Define input layer for adversarial network.
adversarialInput = tf.keras.layers.Input(shape=(nLatent))
# Define generator output as generated time series.
timeSeries = generator(adversarialInput)
# Set discriminator to be untrainable.
discriminator.trainable = False
# Compute predictions from discriminator.
adversarialOutput = discriminator(timeSeries)
# Define adversarial model.
adversarial = tf.keras.Model(adversarialInput, adversarialOutput)
# Compile adversarial network.
adversarial.compile(loss='binary_crossentropy', optimizer=tf.optimizers.Adam(0.0001))
Listing 9-11Define and compile the adversarial model of a GAN
我们从定义批量开始。然后,我们进入由几个步骤组成的训练循环。首先,我们绘制随机整数并使用它们来选择GDP矩阵中的行,每个行都由一个 GDP 增长时间序列组成。这将是鉴别器训练集中的真实样本。接下来,我们通过绘制潜在向量来生成假数据,然后将它们传递给generator。然后,我们将这两种类型的系列组合起来,并给它们分配相应的标签(例如,1 =真实,0 =虚假)。我们现在可以将这些数据传递给鉴别器来执行一批训练。
接下来,我们对敌对网络进行迭代训练。在这里,我们将生成一批潜在状态,将它们输入到generator中,然后进行训练,目的是欺骗discriminator将它们归类为真实状态。请注意,我们正在迭代两个模型的训练,并且不会在训练过程中使用正常的停止标准。相反,我们将寻找一种稳定的进化均衡,在这种均衡中,任何一种模式似乎都无法获得优势。
在图 9-4 中,我们绘制了随时间变化的模型损耗。我们可以看到,在大约 500 次训练迭代之后,两个模型都没有出现实质性的改进,这表明我们已经达到了稳定的进化平衡。
图 9-4
通过训练迭代的鉴别器和对抗模型损失
最后,我们在图 9-5 中绘制了 GAN 产生的 GDP 增长序列之一。除了白噪声向量输入和关于鉴别器性能的信息,敌对网络在 1000 次训练迭代后,设法训练生成器产生一个相当可信的假 GDP 增长序列。当然,如果允许更多的潜在特性和更高级的模型架构,比如 LSTM,我们本可以大大提高性能。
图 9-5
虚假 GDP 增长序列示例
经济学和金融学的应用
在这一章中,我们集中讨论了一个看似晦涩的例子:通过使用生成式机器学习模型生成模拟 GDP 增长序列;然而,这种练习在蒙特卡罗模拟研究中很常见,蒙特卡罗模拟研究用于检验计量经济学中估计量的小样本性质。如果不生成真实的序列并充分捕捉序列之间的相互依赖性,就很难准确评估估计量的属性。
事实上,GANs 在经济学文献中的最早应用之一就是为了实现这一目标。Athey 等人(2019 年)考虑了使用 Wasserstein GANs 来模拟数据的可能性,这些数据看起来与现有数据集中的观察值相似,但现有数据集不够大,无法用于蒙特卡罗模拟。这样做的价值在于,它允许计量经济学家避免这种方法的两种常见替代方案:(1)从小数据集本身随机抽取,这将导致相同观察值的多次重复,以及(2)生成模拟序列,这些模拟序列通常无法准确捕捉数据集中序列之间的相关性。Athey 等人(2019 年)通过使用 WGAN 生成的人工数据评估估值器,证明了他们的方法(以及更一般的 GAN)的价值。
除了 Athey 等人(2019 年),经济学文献中最近的工作(Kaji 等人,2018 年)检查了 WGANs 是否可用于执行间接推断,间接推断通常用于估计经济学和金融学中的结构模型。在 Kaji 等人(2018 年)的研究中,他们试图估计一个模型,其中不同类型的工人从工资和地点菜单中进行选择。他们要恢复的参数是结构性的,无法从数据中直接估计出来,这就需要他们使用间接推断的方法。他们使用的方法是将模型模拟与鉴别器结合起来,训练模型,直到模拟数据与真实数据无法区分。
除了目前集中于模型估计的现有应用,GANs 和 VAEs 也可以用于图像和文本生成的现成应用。尽管图像数据在经济学中的应用仍然有限——即使是在判别模型中——但 GANs 和 VAEs 提供了用经济数据进行可视化反事实模拟的可能性。例如,在城市经济学中,我们可以根据公共政策和其他因素推断公共基础设施的布局会如何变化。
类似地,经济和金融领域日益增长的自然语言处理文献可以利用文本生成来研究,例如,当经济或行业的基本状态发生变化时,公司新闻稿会如何变化。
摘要
在这一章之前,这本书主要讨论了判别机器学习模型。这种模型执行分类或回归。也就是说,它们从训练集中提取特征,并尝试区分不同的类别或对目标进行连续预测。生成式机器学习不同于鉴别式机器学习,因为它生成新的示例,而不是在示例之间进行鉴别。
在经济和金融学科之外,生成式机器学习已经被用于创建引人注目的图像、音乐和文本。它还被用于改进蒙特卡罗模拟(Athey 等人,2019 年)和对经济学中的结构模型进行间接推断(Kaji 等人,2018 年)。
在这一章中,我们主要讨论了两种生成模型:变分自编码器(VAE)和生成对抗网络(GAN)。VAE 模型通过包括均值、方差和采样层来扩展自编码器。这通过对其潜在空间施加限制来改进自编码器,迫使状态围绕原点聚集,并且具有 0 的对数方差。
类似于自编码器和 VAEs,GANs 也由多个组件模型组成:一个生成器模型、一个鉴别器模型和一个对抗模型。发电机模型创造了新的例子。鉴别器模型试图对它们进行分类。对抗模型训练生成器创造引人注目的例子来欺骗鉴别者。GANs 的训练过程包括寻找一个稳定的进化平衡。
最后,我们展示了 VAEs 和 GANs 如何用于生成人工 GDP 增长数据。我们还讨论了它们目前是如何在经济学中应用的,以及如果它们得到更广泛的采用,它们在未来可能如何应用。
文献学
Athey,s .,G.W. Imbens,J. Metzger 和 E. Munro。2019."使用 Wasserstein 生成对抗网络设计蒙特卡罗模拟."第 3824 号工作文件。
布莱,D.M .,A.Y. Ng 和 M.I .乔丹。2003."潜在的狄利克雷分配."机器学习研究杂志3(993–1022)。
古德费勒,我,y .本吉奥,和 a .库维尔。2017.深度学习。马萨诸塞州剑桥:麻省理工学院出版社。
古德费勒、国际法院、普热-阿巴迪、米尔扎、徐、沃德-法利、奥泽尔、库维尔和本吉奥。“生成性对抗网络”NIPS’2014。 2014 年。
Kaji、E. Manresa 和 G. Pouliot。2018."深度推理:用于结构估计的人工智能."工作文件。
金玛,D.P .和 m .韦林。2019."变分自编码器的介绍."机器学习的基础和趋势12(4):307–392。
j .克罗恩、g .贝维尔德和 a .巴森斯。2020.深度学习图解:人工智能的可视化互动指南。艾迪森-卫斯理。
Footnotes 1参见 www.datacamp.com/community/tutorials/using-tensorflow-to-compose-music 获取音乐生成的生成模型的扩展教程。
十、理论模型
相对于其他机器学习包,TensorFlow 需要大量的时间投入才能掌握。这是因为它为用户提供了定义和求解任何基于图形的模型的能力,而不是为他们提供一组简单且可解释的预定义模型。TensorFlow 的这一功能旨在促进深度学习模型的发展;但是,对于想要解决理论模型的经济学家来说,它也有次要的价值。
在本章中,我们将简要概述 TensorFlow 在这一领域的能力。我们将从演示如何在 TensorFlow 中定义和求解任意数学模型开始。然后,我们将应用这些工具来解决完全折旧的新古典商业周期模型。这个模型有一个解析解,这将允许我们评估 TensorFlow 的表现如何。然而,我们也将讨论在我们没有分析解决方案的情况下如何评估性能。
在我们演示了如何在 TensorFlow 中求解基本的数学模型之后,我们将通过检查深度强化学习来结束这一章,这是一个结合了强化学习和深度学习的领域。近年来,它已经积累了几项令人印象深刻的成就,包括开发机器人和网络,以超人的性能水平玩视频游戏。我们将会看到这如何应用于解决经济学中难以处理的理论模型。
求解理论模型
到目前为止,我们已经定义了一个模型,方法是选择一个特定的架构,然后使用数据训练模型的参数。然而,在经济学和金融学中,我们经常遇到一系列不同的问题,这些问题本质上是理论性的,而不是经验性的。这些问题需要我们求解一个泛函方程或者微分方程组。这类问题来源于一个理论模型,该模型描述了家庭、企业或社会规划者的最优化问题。
在这种情况下,模型的深层参数——通常描述技术、约束和偏好——在模型之外进行校准或估计,因此在实施解决方法之前就已经知道了。TensorFlow 在这种情况下的作用是实现微分方程系统的求解。
吃蛋糕的问题
吃蛋糕问题通常被用作动态编程的“hello world”式介绍。 1 在这个问题中,一个人被赋予了蛋糕,必须决定在每个时期吃多少。尽管高度程式化,但它与经济学中的标准消费储蓄问题有着很强的相似性。在标准消费储蓄问题中,个人必须决定是现在消费更多,还是通过增加储蓄来推迟消费。
正如我们之前讨论的,这种模型的深层参数通常是在求解程序之外校准或估计的。在这种情况下,消费蛋糕的个人有一个效用函数和一个折扣因子。效用函数衡量个人从消费一块一定大小的蛋糕中获得的快乐。贴现因子告诉我们,相对于未来,一个人将如何评价今天的一块蛋糕。我们将使用效用函数和贴现因子中参数的常用值。
形式上,吃蛋糕问题可以写成一个动态的、受约束的优化问题。等式 10-1 定义了个人在时间 t 吃一块蛋糕所获得的瞬时效用。特别地,我们假设接收到的瞬时效用对于代理接收到它的周期是不变的:也就是说,我们在 c 上放置一个时间下标,而不是 u ()。我们还假设效用是蛋糕消耗量的自然对数。这将确保更多的蛋糕产生更多的效用,但是更多蛋糕的增量收益——边际效用——在 c 中减少。这为吃蛋糕的人提供了一种自然的欲望,即随着时间的推移,将消费间隔开,而不是今天就吃掉整个蛋糕。
方程式 10-1。蛋糕消费的瞬时效用。
消费的边际效用可以表示为u(ct)相对于 c t 的导数,如等式 10-2 所示。注意,方程 10-1 和方程 10-2 都不含参数。这是采用对数效用来解决此类问题的好处之一:它为效用和边际效用生成简单的、无参数的表达式,并满足我们通常在经济学和金融学中对效用函数的要求。
方程式 10-2。消费的边际效用。
除此之外,二阶导数总是负的,如方程式 10-3 所示。
方程式 10-3。消费的边际效用。
为了简化问题,我们将蛋糕的大小归一化为 1,这意味着所有的消费选择将介于 0 和 1 之间。在图 10-1 中,我们绘制了在此区间内效用水平及其对 c 值的一阶和二阶导数。
图 10-1
消费效用,及其在(0,1)区间上的一阶和二阶导数
我们首先考虑一个有限范围的问题,代理人必须在 T 个时间段内分配消费。这可能是因为这种蛋糕只在 T 时期可以食用,或者是因为这种蛋糕只在 T 时期可以食用。在这个程式化的例子中,推理并不特别重要,但对于消费储蓄问题来说,推理当然更重要。
在时间 t = 0 时,代理人最大化等式 10-4 中给出的目标函数,服从等式 10-5 中的预算约束和等式 10-6 中对st+1的积极性约束。也就是说,代理人必须做出一系列的消费选择, c 0 ,…,cT—1,每个消费选择都受到蛋糕剩余量、 s t 的约束,并要求携带一个正数量的蛋糕, s t 此外,所有未来期间的消耗都按β ≤ 1 折现。
在方程 10-4 中,我们还应用了最优化原理(Bellman 1954)来重申用 s 0 蛋糕进入周期 0 的值。它将等于沿着最优消费路径的效用的贴现总和,我们将它表示为未知函数, V ()。
方程式 10-4。t = 0 时代理的目标函数。
方程式 10-5。预算限制。
方程式 10-6。积极约束。
贝尔曼(1954)证明了我们可以用后来称为“贝尔曼方程”的公式(10-7)来重新表达任意周期内的目标函数。我们还将预算约束代入等式。
方程式 10-7。吃蛋糕问题的贝尔曼方程。
我们没有为 T-t+1 期间选择消费序列,而是为当前期间选择 c t 或 s t + 1 。解决这个问题就简化为求解一个函数方程来恢复 V ()。这样做之后,选择一个st+1将确定瞬时效用和未来时期的效用贴现流,使这成为一系列单时期优化问题。
对于有限视界的问题,比如我们设置的这个,我们可以牵制V(sT; T )为所有 s T 。由于决策问题在周期T1 结束,所以 s T 的所有选择都会产生V(sT; T ) = 0。因此,我们将从解方程 10-8 开始,其中消耗sT—1总是最优的。我们现在可以在时间上递归后退,在每个周期中求解 V (),直到我们到达 t = 0。
方程式 10-8。吃蛋糕问题的贝尔曼方程。
有几种方法可以执行递归优化步骤。常见的一种是使用离散网格来表示价值函数。为了利用 TensorFlow 的优势并保持本章剩余部分的连续性,我们将重点放在参数方法上。更具体地说,我们将参数化策略函数,该函数将时间 t 时的状态(即我们在周期开始时的蛋糕量)映射到时间 t+1 时的状态(即我们带入下一周期的蛋糕量)。
为了简单起见,我们将使用一个与状态成比例的线性函数作为决策规则,如等式 10-9 所示。
方程式 10-9。吃蛋糕政策规则的功能形式。
我们现在将在 TensorFlow 中针对简单的情况实现这种方法,其中 T = 2。也就是说,我们从一块大小为 1 的蛋糕开始,必须决定将多少结转到期间T1。
在清单 10-1 中,我们定义了求解模型所需的常数和参数。这包括政策函数theta的斜率,它告诉我们我们结转到下一期的蛋糕份额;贴现因子beta,它告诉我们相对于 t+1 ,代理在 t 期间对蛋糕估价多少;以及零时段剩余的蛋糕份额,s0。注意theta是一个可训练变量;beta设置为 1.0,表示我们不贴现 t+1 期间的蛋糕消费;我们最初有一个完整的蛋糕(s0 = 1)。
import tensorflow as tf
# Define policy rule parameter.
theta = tf.Variable(0.1, tf.float32)
# Define discount factor.
beta = tf.constant(1.0, tf.float32)
# Define state at t = 0.
s0 = tf.constant(1.0, tf.float32)
Listing 10-1Define the constants and variables for the cake-eating problem
接下来,我们为清单 10-2 中的策略规则定义一个函数,它接受参数值并产生s1。注意,我们将s1定义为theta*s0。我们使用tf.clip_by_value()将s1限制在[0.01,0.99]区间内,这强加了积极性约束。
接下来,在清单 10-3 中,我们定义了损失函数,它将参数值作为输入并产生损失。请注意,v1被选择的s1固定,因为 1 是终止周期。确定了v1之后,我们就可以计算v0,条件是选择theta。我们将选择theta,因此s1,以最大化v0。然而,由于我们将在实践中执行最小化,我们将使用-v0作为损耗的度量。
# Define the loss function.
def loss(theta, s0 = s0, beta = beta):
s1 = policyRule(theta)
v1 = tf.math.log(s1)
v0 = tf.math.log(s0-s1) + beta*v1
return -v0
Listing 10-3Define the loss function
# Define policy rule.
def policyRule(theta, s0 = s0, beta = beta):
s1 = tf.clip_by_value(theta*s0,
clip_value_min = 0.01, clip_value_max = 0.99)
return s1
Listing 10-2Define a function for the policy rule
接下来,我们实例化一个优化器,并在清单 10-4 中的 500 次迭代过程中执行最小化。
# Instantiate an optimizer.
opt = tf.optimizers.Adam(0.1)
# Perform minimization.
for j in range(500):
opt.minimize(lambda: loss(theta),
var_list = [theta])
Listing 10-4Perform optimization
经过 100 次迭代训练,theta收敛到 0.5,如图 10-2 所示。theta = 0.5 的解释是,代理人应该在 0 期吃一半蛋糕,在 1 期吃一半蛋糕,这正是我们在代理人不贴现未来的情况下会预期到的。
图 10-2
策略函数参数在训练迭代中的演化
当然,我们通常会假设beta小于 1。图 10-3 针对不同的beta值绘制了theta的最佳值。在每种情况下,我们都要重新求解模型。正如所料,我们看到两者之间的关系是向上倾斜的。也就是说,当我们更看重未来的消费时,我们也选择把更多的蛋糕带到未来去消费。
这个问题是高度程式化的,关注两个时期的情况使它变得更加琐碎。然而,它确实展示了在 TensorFlow 中构建和求解理论模型的基本模板。在下面的小节中,我们将考虑一个更现实的问题,但是将集中在我们有一个封闭形式的解决方案的情况。这将使评估我们方法的性能变得相对容易。
图 10-3
折扣系数和策略规则参数之间的关系
新古典商业周期模型
我们将通过解决布洛克和米尔曼(1972)提出的新古典商业周期模型的一种特殊形式来结束这一节。在该模型中,一个社会规划者最大化一个代表性家庭的消费效用贴现流。在每个期间 t ,计划员选择下一个期间的资金kt+1,在下一个期间 y t + 1 。在对数效用和完全折旧的假设下,该模型有一个易于处理的封闭解。
等式 10-10 是初期的计划者问题,受等式 10-11 中的预算约束。目标类似于吃蛋糕的问题,但家庭是无限活的,所以我们现在有一个消费的贴现效用流的无限和。预算约束表明社会计划者在每个时期将产出分为消费和资本。等式 10-12 规定了生产函数。
方程式 10-10。社会规划者的问题。
方程式 10-11。整体经济的预算约束。
方程式 10-12。生产函数。
我们还假设 β < 1, α ∈ (0,1),资本在每一期都充分折旧。这意味着我们用前期结转的资本来恢复产出,但我们没有恢复任何资本本身。
我们可以解决这个问题的一种方式是通过识别满足欧拉方程的策略函数。方程 10-13 中给出的欧拉方程要求在 t 期间消费的边际效用等于在 t+1 期间贴现的总资本回报率乘以在 t+1 期间消费的边际效用。
方程式 10-13。欧拉方程。
欧拉方程有一个直观的解释:如果计划者不能通过从时段 t 到时段 t+1 重新分配少量消费来使家庭变得更好,或者反之亦然,则解决方案是最优的。通过定义资本和消费的政策函数,我们将找到与方程 10-11、10-12 和 10-13 一致的解。不过,我们会看到,消费的政策功能是多余的。
我们首先假设解决方案可以表示为与产出成比例的政策函数。也就是说,计划者将选择一部分产出分配给资本和消费。等式 10-14 提供了资本的政策函数,等式 10-15 提供了消费的函数。
方程式 10-14。 政策功能 为首都。
方程式 10-15。消费的政策功能。
方程 10-16 和 10-17 给出了政策函数的封闭表达式。我们将使用这些来评估我们在 TensorFlow 中的结果的准确性。
方程式 10-16。资本的政策规则。
方程式 10-17。消费的政策规则。
我们现在已经定义了问题,可以在 TensorFlow 中实现一个解决方案。我们将从定义清单 10-5 中的参数和资本网格开始。我们将使用alpha和beta的标准值、生产函数参数和贴现因子。接下来我们将定义thetaK,在接下来的时间里分配给资本的产出份额。最后,我们将定义期初资本网格,k0。这是一个家庭在周期 t 开始时可以持有的资本价值的向量。
import tensorflow as tf
# Define production function parameter.
alpha = tf.constant(0.33, tf.float32)
# Define discount factor.
beta = tf.constant(0.95, tf.float32)
# Define params for decision rules.
thetaK = tf.Variable(0.1, tf.float32)
# Define state grid.
k0 = tf.linspace(0.001, 1.00, 10000)
Listing 10-5Define model parameters
在清单 10-6 中,我们定义了损失函数。我们首先计算下一期资本的政策规则,然后将政策规则代入欧拉方程。然后,我们从左侧减去右侧,得到error,它有时被称为欧拉方程残差。然后,我们对残差求平方,并计算平均值。
# Define the loss function.
def loss(thetaK, k0 = k0, beta = beta):
# Define period t+1 capital.
k1 = thetaK*k0**alpha
# Define Euler equation residual.
error = k1**alpha-
beta*alpha*k0**alpha*k1**(alpha-1)
return tf.reduce_mean(tf.multiply(error,error))
Listing 10-6Define the loss function
最后一步是定义一个优化器并执行最小化,这是我们在清单 10-7 中做的。执行优化后,我们打印出thetaK和封闭解中的参数表达式beta*alpha。在这两种情况下,我们都得到0.3135002,这表明我们的 TensorFlow 实现确定了模型的真实解。
# Instantiate an optimizer.
opt = tf.optimizers.Adam(0.1)
# Perform minimization.
for j in range(1000):
opt.minimize(lambda: loss(thetaK),
var_list = [thetaK])
# Print thetaK.
print(thetaK)
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.31350002>
# Compare analytical solution and thetaK.
print(alpha*beta)
tf.Tensor(0.31350002, shape=(), dtype=float32)
Listing 10-7Perform optimization and evaluate results
既然我们已经解决了策略规则,我们可以使用它们来做诸如计算转换路径之类的事情。清单 10-8 展示了如何使用政策规则并从 0.05 的股本值开始计算消费、资本和产出的转换。我们在图 10-4 中绘制了过渡路径。
# Set initial value of capital.
k0 = 0.05
# Define empty lists.
y, k, c = [], [], []
# Perform transition.
for j in range(10):
# Update variables.
k1 = thetaK*k0**alpha
c0 = (1-thetaK)*k0**alpha
# Update lists.
y.append(k0**alpha)
k.append(k1)
c.append(c0)
# Update state.
k0 = k1
Listing 10-8Compute transition path
最后,值得指出的是,我们使用了一个有意忽略的例子,在这个例子中,可以解析地计算出解。在实践中,我们通常会遇到并非如此的问题。在这种情况下,我们将经常使用欧拉方程残差来评估求解方法的准确性。
清单 10-9 展示了我们如何修改损失函数来计算欧拉方程残差。我们将首先定义一个网格来计算它们。在某些情况下,我们可能希望扩展我们用来求解模型的范围,以证明我们的模型在远离稳态时也表现良好。在这种情况下,我们将使用与求解模型时相同的网格。
图 10-4
产出、资本和消费的转变路径
也许并不奇怪——因为我们的策略规则与解析解匹配——最大欧拉方程残差小得可以忽略不计。虽然对这个问题并不特别重要,但每当我们想确定我们的结果受近似误差影响的程度时,欧拉方程残差将是有用的。
# Define state grid.
k0 = tf.linspace(0.001, 1.00, 10000)
# Define function to return Euler equation residuals.
def eer(k0, thetaK = thetaK, beta = beta):
# Define period t+1 capital.
k1 = thetaK*k0**alpha
# Define Euler equation residual.
residuals = k1**alpha-
beta*alpha*k0**alpha*k1**(alpha-1)
return residuals
# Generate residuals.
resids = eer(k0)
# Print largest residual.
print(resids.numpy().max())
5.9604645e-08
Listing 10-9Compute the Euler equation residuals
深度强化学习
经济学和金融学的标准理论模型假设代理人是理性的优化者。这意味着代理人对未来形成无偏见的期望,并通过执行优化来实现他们的目标。一个理性的代理人可能会错误地预测每个时期的资本回报,但它不会系统地过度预测或不预测它。类似地,优化器不会总是在事后获得最佳结果,但在事前,它会根据给定的信息集做出最佳决策。更明确地说,优化器将根据效用函数和约束条件选择精确的最优值,而不是使用试探法或经验法则。
正如 Palmer (2015)所描述的,我们可能希望偏离 rational optimizer 框架有几个原因。一个是,我们可能希望专注于代理人形成政策规则的过程,而不是假设他们已经采用了理性和优化所隐含的规则。另一个原因是,打破合理性或优化要求将大大提高许多模型的计算处理能力。
如果我们真的想脱离标准模型,另一种方法是强化学习,在萨顿和巴尔托(1998)描述。Athey 和 Imbens (2019 年)和 Palmer (2015 年)讨论了它在经济学中的价值。此外,它在 Hull (2015)中被用作解决棘手的动态规划问题的一种方法。
类似于经济学中的标准 rational optimizer 框架,强化学习问题中的代理执行优化,但是它们是在关于系统状态的信息有限的环境中执行优化的。这导致了“探索”和“利用”之间的权衡——也就是说,学习更多关于系统的知识或者优化你所理解的系统的一部分。
在这一节中,我们将重点关注最近推出的一种强化学习的变体,称为“深度 Q 学习”,它结合了深度学习和强化学习。我们的目标将是放松那些阻止我们解决高维状态空间问题的 rational optimizer 版本的计算约束,而不是研究学习过程本身。也就是说,我们仍然会为 rational 优化器的问题寻找一个解决方案,但是我们将会使用深度 Q 学习来这样做,而不是使用计算经济学中更传统的方法。
类似于动态编程,Q-learning 通常使用“查找表”方法来完成。在动态编程中,这需要构建一个表来表示处于每个状态的值。然后,我们迭代地更新该表,直到我们达到收敛。表本身就是价值函数的解。相反,在 Q-learning 中,我们构建一个状态-动作表。在我们的新古典商业周期模型的例子中,我们将回到这里,国家是资本存量,行动是消费水平。
等式 10-18 展示了在我们使用时间差分学习的情况下,Q 表将如何被更新。也就是说,我们在迭代 i+1 中更新与状态-动作对( s t , a t )相关联的值,这是通过取 i 中的值并将其添加到学习率,乘以通过选择最佳动作而引起的值的预期变化来实现的。
方程式 10-18。更新 Q 表。
深度 Q 学习用称为“深度 Q 网络”的深度神经网络代替了查找表 Mnih 等人(2015)介绍了这种方法,最初用于训练 Q-networks 以超人的性能水平玩视频游戏。
我们将简要概述深度 Q 学习如何用于解决经济模型,回到新古典商业周期模型的例子。在 TensorFlow 中有几种方法可以做到这一点。两个常见的选项是tf-agents,它是一个本地 TensorFlow 实现,以及keras-rl2,它利用了 TensorFlow 中的高级 Keras API。因为我们的报道将是简短的和介绍性的,所以我们将集中在keras-rl2上,这将允许用更熟悉的语法实现更简单的实现。
在清单 10-10 中,我们安装keras-rl2模块并导入tensorflow和numpy。然后,我们从新安装的rl模块中导入三个子模块:DQNAgent,我们将使用它们来定义深度 Q 学习代理;EpsGreedyQPolicy,我们将使用它来设置在培训路径上生成政策决策的流程;以及SequentialMemory,用于保留决策路径和结果,然后用作训练深度 Q 网络的输入。最后,我们导入 gym,我们将使用它来定义模型环境。
# Install keras-rl2.
!pip install keras-rl2
# Import numpy and tensorflow.
import numpy as np
import tensorflow as tf
# Import reinforcement learning modules from keras.
from rl.agents.dqn import DQNAgent
from rl.policy import EpsGreedyQPolicy
from rl.memory import SequentialMemory
# Import module for comparing RL algorithms.
import gym
Listing 10-10Install and import modules to perform deep Q-learning
在清单 10-11 中,我们将设置资本节点的数量并定义一个环境planner,它是gym.Env的子类。这将详细说明社会规划者的强化学习问题。
我们的类planner被构造为在初始化时执行以下操作:定义一个离散的资本网格,定义动作和观察空间,将决策数初始化为零,设置最大决策数,设置资本初始值的节点索引(1000 中的 500),设置生产函数参数(alpha)。出于我们的目的,动作和观察空间都是具有 1000 个节点的离散对象,使用gym.spaces来定义。在我们的例子中,观察空间是整个状态空间:即所有的首都节点。动作空间也一样。
# Define number of capital nodes.
n_capital = 1000
# Define environment.
class planner(gym.Env):
def __init__(self):
self.k = np.linspace(0.01, 1.0, n_capital)
self.action_space = \
gym.spaces.Discrete(n_capital)
self.observation_space = \
gym.spaces.Discrete(n_capital)
self.decision_count = 0
self.decision_max = 100
self.observation = 500
self.alpha = 0.33
def step(self, action):
assert self.action_space.contains(action)
self.decision_count += 1
done = False
if(self.observation**self.alpha – action) > 0:
reward = \
np.log(self.k[self.observation]**self.alpha –
self.k[action])
else:
reward = -1000
self.observation = action
if (self.decision_count >= self.decision_max)\
or reward == -1000:
done = True
return self.observation, reward, done,\
{"decisions": self.decision_count}
def reset(self):
self.decision_count = 0
self.observation = 500
return self.observation
Listing 10-11Define custom reinforcement learning environment
我们接下来定义该类的一个step方法,该方法需要返回四个输出:observation(状态)、reward(瞬时工具)、一个指示训练会话是否应该被重置的指示器(done)以及一个包含相关调试信息的字典对象。调用这个方法增加了decision_count属性,它记录了一个代理在一个训练会话中做出的决定的数量。它还最初将done设置为False。然后,我们评估代理人是否做出了有效的决策,即选择了正的消费值。如果一个代理做出了超过decision_max个决策或者选择了一个非正的消耗值,那么reset()方法将被调用,它将重新初始化状态和决策计数。
在清单 10-12 中,我们实例化了一个planner环境,然后在 TensorFlow 中定义了一个神经网络。我们使用具有一个致密层和一个relu激活函数的Sequential模型。注意,模型应该有一个包含n_capital节点的输出层;然而,除此之外,我们可以选择最适合我们问题的架构。
# Instantiate planner environment.
env = planner()
# Define model in TensorFlow.
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Flatten(input_shape=(1,) + env.observation_space.shape))
model.add(tf.keras.layers.Dense(32, activation="relu"))
model.add(tf.keras.layers.Dense(n_capital, activation="linear"))
Listing 10-12Instantiate environment and define model in TensorFlow
既然已经定义了我们的环境和网络,我们需要指定超参数并训练模型,我们在清单 10-13 中就是这么做的。我们首先使用SequentialMemory来保留 50,000 个决策路径的“重放缓冲区”,它将用于训练模型。然后,我们将模型设置为使用ε= 0.30 的ε贪婪策略。在训练期间,这意味着该模型将在 70%的时间内最大化效用,并在剩余的 30%时间内用随机决策进行探索。最后,我们设置DQNAgent模型的超参数,编译它,并执行训练。
# Specify replay buffer.
memory = SequentialMemory(limit=10000, window_length=1)
# Define policy used to make training-time decisions.
policy = EpsGreedyQPolicy(0.30)
# Define deep Q-learning network (DQN).
dqn = DQNAgent(model=model, nb_actions=n_capital, memory=memory,
nb_steps_warmup=100, gamma=0.95,
target_model_update=1e-2, policy=policy)
# Compile and train model.
dqn.compile(tf.keras.optimizers.Adam(0.005), metrics=['mse'])
dqn.fit(env, nb_steps=10000)
Listing 10-13Set model hyperparameters and train
监控训练过程产生了两个观察结果。首先,每次会话的决策数量随着迭代而增加,这表明代理人学会了通过不像贪婪的政策所暗示的那样急剧地提取资本来避免未来期间的负值。第二,损失下降,平均回报开始上升,表明代理人正在接近最优。
如果我们想对我们的解的质量进行更彻底的分析,我们可以检查欧拉方程残差,正如我们在上一节中所讨论的。这将告诉我们 DQM 模型是否产生了近似最优的结果。
摘要
TensorFlow 不仅为我们提供了一种训练深度学习模型的方法,还提供了一套可用于解决任意数学模型的工具。这包括经济学和金融学中常用的模型。在这一章中,我们使用一个玩具模型(吃蛋糕模型)和计算文学中的一个通用基准:新古典商业周期模型来检验如何做到这一点。使用经济学中的常规方法求解这两个模型都是微不足道的,但提供了一种简单的方法来演示如何使用 TensorFlow 来求解与经济学家相关的理论模型。
我们还展示了深度强化学习如何被用作计算经济学中标准方法的替代方法。特别是,在 TensorFlow 中使用深度 Q 学习网络(DQN)可以使经济学家在非线性设置中解决高维模型,而不改变模型假设或引入大量数值误差。
文献学
阿西和 G.W .因本斯。2019."经济学家应该知道的机器学习方法."年度经济学评论11:685–725。
贝尔曼河,1954。"动态规划理论."美国数学学会公报 60:503–515。
布洛克,w .和 l .米尔曼。1972."最优经济增长和不确定性:贴现案例."经济理论杂志4(3):479–513。
赫尔岛,2015 年。"具有后决策状态的近似动态规划作为动态经济模型的求解方法."经济动态与控制杂志55:57–70。
Mnih,v .等人,2015 年。"通过深度强化学习进行人类水平的控制."性质518:529–533。
新墨西哥州帕尔默,2015。个人和社会学习:从第一原则实现有限理性。计算社会科学博士论文,弗吉尼亚州费尔法克斯:乔治梅森大学。
萨顿和巴尔托。1998.强化学习:简介。剑桥:麻省理工学院出版社。
Footnotes 1动态规划是一种将多步优化问题转化为一系列单步问题的方法。在经济学和金融学中,动态规划通常用于多期动态优化问题。动态规划将这类问题简化为一系列单周期问题。