2025年8月

处理长序列

在长序列上训练一个RNN,必须运行很多时间步长,从而展开的RNN成为一个非常深的网络。就像任何深度神经网络一样,它可能会遇到不稳定的梯度问题:它可能永远在训练,或者训练可能会不稳定。此外,当RNN处理一个长序列时,它会逐渐忘记序列中的第一个输入。

应对不稳定梯度问题

在深度网络中用于应对不稳定梯度问题的许多技巧也可以用于RNN:良好的参数初始化、更快的优化器、dropout,等等。但是,非饱和激活函数(例如ReLU)在这里可能没有太大的帮助。实际上,它可能导致RNN在训练过程中变得更加不稳定。假设梯度下降以一种在第一个时间步长稍微增加输出的方式来更新权重。由于每个时间步长都使用相同的权重,因此第二个时间步长的输出也可能会略有增加,第三个时间步长的输出也会稍有增加,以此类推,直到输出爆炸为止,而非饱和激活函数不能阻止这种情况。可以使用较小的学习率来降低这种风险,但也可以使用饱和激活函数(例如双曲正切)(这解释了为什么将其设定为默认值)。同样的方式,梯度本身也会爆炸。

此外,批量归一化不能像深度前馈网络那样高效地用于RNN。实际上,不能在时间步长之间使用它,而只能在递归层之间使用,更准确地说,从技术上讲,可以在记忆单元中添加一个BN层,以便将其应用于每个时间步长(在该时间步长和前一个时间步长的隐藏状态上)。但是,在每个时间步长都是用相同的BN层,并使用相同的参数,而不管输入的实际比例和偏移以及隐藏状态如何。在实践中,这不能产生良好的效果,BN仅在将BN应用于输入而非隐藏状态时才稍微收益。当应用于循环层之间时,它总比没有好,但不是在递归层中。在Keras中,可以简单地在每个递归层之前添加一个BatchNormalization层来完成此操作

归一化的另一种形式通常与RNN一起使用会更好:层归一化:它和批量归一化非常相似,但是它不是跨批量维度进行归一化,而是在特征维度上进行归一化。它的一个优点是可以在每个时间步长上针对每个实例独立地即时计算所需的统计信息。这也意味着它在训练和测试期间的行为方式相同(与BN相反),并且不需要使用指数移动平均值来估计样本中所有实例的特征统计信息。像BN一样,层归一化学习每个输入的比例和偏移参数。在RNN中,通常在输入和隐藏状态的线性组合后立即使用它

使用tf.keras在一个简单的记忆单元中实现层归一化。为此需要自定义个自定义记忆单元。就像常规层一样,不同处在于其call()方法采用两个参数:当前时间步长的inputs和上一个时间步长的隐藏states。states参数是包含一个或多个张量的列表。对于简单的RNN单元,它包含的单个张量等于上一个时间步长的输出,但是其他单元可能具有多个状态张量(例如,LSTMCell具有长期状态和短期状态)。一个单元必须具有state_size属性和output_size属性。在简单的RNN中,两者都等于单元数量。以下代码实现了一个简单的记忆单元,该单元的行为类似于SimpleRNNCell,不同之处在于它还在每个时间步长应用“层归一化”

import tensorflow as tf
from tensorflow import keras


class LNSimpleRNNCell(keras.layers.Layer):
    def __init__(self, units, activation='tanh', **kwargs):
        super().__init__(**kwargs)
        self.state_size = units
        self.output_size = units
        self.simple_rnn_cell = keras.layers.SimpleRNNCell(units, activation=None)
        self.layer_norm = keras.layers.LayerNormalization()
        self.activation = keras.activation.get(activation)

    def call(self, inputs, states):
        outputs, new_states = self.simple_rnn_cell(inputs, states)
        norm_outputs = self.activation(self.layer_norm(outputs))
        return norm_outputs, [norm_outputs]

LNSimpleRNNCell类继承自keras.layers.Layer类,就像任何自定义层一样。构造函数采用单元数量和所需激活函数,并设置state_size和output_size属性,然后创建一个没有激活函数的SimpleRNNCell(因为要在线性运算之后但在激活函数之前执行“层归一化”),然后,构造函数创建LayerNormalization层,最后获取需要的激活函数。call()函数通过应用于简单的RNN单元开始,该单元计算当前输入和先前输出的隐藏状态的线性组合,并返回两个结果(实际上,在SimpleRNNCell中,输出等于隐藏状态:也就是new_states[0]=outputs,因此可以在其他的call()方法中安全的忽略new_states)。接下来call()方法应用“层归一化”,然后跟随一个激活函数。最后,它返回两个输出(一个作为输出,另一个作为新的隐藏状态)。要使用此自定义单元,需要做的就是创建一个keras.layers.RNN层,并向其传递一个单元实例:

model = keras.models.Sequential([
    keras.layers.RNN(LNSimpleRNNCell(20), return_sequences=True, input_shape=[None, 1]),
    keras.layers.RNN(LNSimpleRNNCell(20), return_sequences=True),
    keras.layers.TimeDistributed(keras.layers.Dense(10))
])

同样可以创建一个自定义单元在每个时间步长之间应用dropout。但是有一种更简单的方法:所有循环层(除kears.layers.RNN之外)和Keras提供的所有单元都有一个超参数dropout和一个超参数recurrent_dropout:前者定义了应用于步长的dropout率(在每个时间步长),后者定义隐藏状态的dropout率(也在每个时间步长)。

在RNN中使用这些技术,可以缓解不稳定的梯度问题,更有效地训练RNN

解决短期记忆问题

由于数据在遍历RNN时会经过转换,因此在每个时间步长都会丢失一些信息。一段时间后,RNN的状态几乎没有任何最初输入的痕迹。这是一个热门问题,想象一下试图翻译一个长句子,当读完句子后,不知道如何开始。为了解决这个问题,引入了具有长期记忆的各种类型的单元。它们被证明非常成功,以至于不再使用基本的单元了

LSTM单元

长短期记忆(LSTM)单元由Sepp Hochriter和Jurgen Schmidhuber于1997年提出,并在随后的几年中由Alex Graves、Hasim Sak和Wojciech Zaremba等几位研究者逐步改进。如果把LSTM单元视作黑盒子,则它可以像基本单元一样使用,组它的性能会更好:训练会收敛得更快,它会检测数据中的长期依赖性。在Keras中,可以简单地使用LSTM层:

model = keras.models.Sequential([
    keras.layers.LSTM(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.LSTM(20, return_sequences=True),
    keras.layers.TimeDistributed(keras.layers.Dense(10))
])

或者,可以使用通用的keras.layers.RNN层,为其提供LSTMCell作为参数:

model = keras.models.Sequential([
    keras.layers.RNN(keras.layers.LSTMCell(20), return_sequences=True, input_shape=[None, 1]),
    keras.layers.RNN(keras.layers.LSTMCell(20), return_sequences=True),
    keras.layers.TimeDistributed(keras.layers.Dense(10))
])

LSTM层在GPU上运行时会使用优化过的实现,因此通常最好使用它

不过不查看框内的内容,则LSTM单元看起来与常规单元完全一样,除了它的状态被分为两个向量:$h_{(t)}$和$c_{(t)}$(‘c’代表“单元cell“。$h_{(t)}$视为短期状态,$c_{(t)}$为长期状态

LSTM单元关键的思想是网络可以学习长期状态下存储的内容、丢弃的内容以及从中读取的内容。当长期状态$c_{(t-1)}$从左到右遍历网络时,可以看到它首先经过了一个遗忘门,丢掉了一些记忆,然后通过加法操作添加了一些新的记忆(由输入门选择的记忆)。结果$c_{(t)}$直接送出来,无需任何进一步的转换。因此,在每个时间步长中,都会丢掉一些记忆,并添加一些记忆。此外,在加法运算之后,长期状态被复制并通过tanh函数传输,然后结果被输出门滤波。这将产生短期状态$h_{(t)}$(等于该时间步长的单元输出$y_{(t)}$)

首先,将当前输入向量$x_{(t)}$和先前的短期状态$h_{(t-1)}$馈入四个不同的全连接层。它们都有不同的目的:

  • 主要层是输出$g_{(t)}$的层。它通常的作用是分析当前输入$x_{(t)}$和先前(短期)状态$h_{(t-1)}$。在基本单元中,除了这一层,没有其他的东西,它的输出直接到$y_{(t)}$和$h_{(t)}$。相比之下,在LSTM单元中,该层的输出并非直接输出,而是将其最重要的部分存储在长期状态中(其余部分则丢弃)
  • 其他三层是门控制器。由于它们使用逻辑激活函数,因此它们的输出范围是0到1。它们的输出被馈送到逐元素乘法运算,因此如果输出0则关闭门,输出1则将门打开。特别地:

    • 遗忘门(由$f_{(t)}$控制)控制长期状态的哪些部分应当被删除
    • 遗忘门(由$i_{(t)}$控制)控制应将$g_{(t)}$的哪些部分添加到长期状态
    • 最后,输出门(由$o_{(t)}$控制)控制应在此时间步长读取长期状态的哪些部分并输出到$h_{(t)}$和$y_{(t)}$
      简而言之,LSTM单元可以学会识别重要的输入(这是输入门的作用),将其存储在长期状态中,只要需要就保留它(即遗忘门的作用),并在需要时将其提取出来。这就解释了为什么这些单元在识别时间序列、长文本、录音等长期模式方面取得了惊人的成功

    下列公式总结了如果计算单个实例在每个时间步长的单元的长期状态、短期状态以及其输出(与整个小批量的方程非常类似):

    $$ \begin{align*}i(t)=&\sigma(W_{xi}^Tx_{(t)}+W_{hi}^Th_{(t-1)}+b_i)\\ f_{(t)}=&\sigma(W_{xf}^Tx_{(t)}+W_{hf}h_{(t-1)}+b_f)\\ o_{(t)}=&\sigma(W_{xo}^T+W_{ho}^Th_{(t-1)}+b_o)\\ g_{(t)}=&tanh(W_{xg}^Tx_{(t)}+W_{hg}^Th_{(t-1)}+b_g)\\ c_{(t)}=&f_{(t)}\otimes c_{(t-1)}+i_{(t)}\otimes g_{(t)}\\ y_{(t)}=&h_{(t)}=o_{(t)}\otimes tanh(c_{(t)}\\ \end{align*} $$

    在此等式中:

    • $W_{xi},W_{xf},W_{xo},W_{xg}$是四层中每层与输入向量$x_{(t)}$连接的权重矩阵
    • $W_{hi},W_{hf},W_{ho},W_{hg}$是四层中的每层与先前的短期状态$h_{(t-1)}$连接的权重矩阵
    • $b_i,b_f,b_o,b_g$是四层中每层的偏置项。TensorFlow将$b_f$初始化为一个全是1不是0的向量。这样可以防止在训练开始时忘记一切

窥视孔连接

在常规LSTM单元中,门控制器只能查看输入$x_{(t)}$和先前的短期状态$h_{(t-1)}$。通过让它们也查看长期状态来给它们更多的功能,这可能是一个好主意。Felix Gers和Jergen Schmidhuber于2020年提出了这个想法。他们提出了一种带有额外连接的LSTM变体,称为窥视孔连接:先前的连接状态$c_{(t-1)}$作为输入添加到输出门的控制器。这通常会提高性能,但并非总是如此,并且没有明确的模式说明哪个任务更好。

在Keras中,LSTM层基于不支持窥视孔的keras.layers.LSTMCell单元。实验性的tf.keras.experimental.PeepholeLSTMCell可以,因此可以创建keras.layers.RNN层,并将PeepholeLSTMCell传递给其构造函数。LSTM单元还有许多其他变体。一种特别流行的变体是GRU单元

GRU单元

门控循环单元(Gated Recurrent Unit,GRU)是由Kyunghyun Cho等人在2014年的论文中提出的,该论文还介绍了Encoder-Decoder网络

GRU单元式LSTM单元的简化版,但它的性能也不错。以下是主要的简化:

  • 两个状态向量合并为一个向量$h_{(t)}$
  • 单个门控制器$z_{(t)}$控制遗忘门和输入门。如果门控制器输出1,则遗忘门打开,输入门关闭。如果输出0,则相反。无论何时记忆需要被存储,其存储位置需要首先被删除。实际上,这本身就是LSTM单元的常见变体
  • 没有输出门,在每个时间步长都输出完整的状态向量。但是,有一个新的门控制器$r_{(t)}$控制先前状态的哪一部分将显示给主要层$g_{(t)}$

GRU计算公式

$$ \begin{align*}z_{(t)}=&\sigma(W_{xz}^Tx_{(t)}+W_{hz}^Th_{(t-1)}+b_z)\\ r_{(t)}=&\sigma(W_{xr}^Tx_{(t)}+W_{hr}^Th_{(t-1)}+b_r)\\ g_{(t)}=&tanh(W_{xg}^Tx_{(t)}+W_{hg}^T(r_{(t)}\otimes h_{(t-1)})+b_g)\\ h_{(t)}=&z_{(t)}\otimes h_{(t-1)}+(1-z_{(t)})\otimes g_{(t)} \end{align*} $$

Keras提供了一个keras.layers.GRU层(基于keras.layers.GRUCell记忆单元),使用它只是用GRU来替换SimpleRNN或LSTM的问题。

LSTM和GRU单元是RNN成功的主要原因之一。尽管它们可以处理比RNN更长的序列,但它们的短期记忆仍然非常有限,而且很难学习100个或更多时间步长时间序列的长期模式,例如音频样本、长时间序列或长句子。解决这个问题的一种方法是缩短输入序列,例如使用一位卷积层。

使用一维卷积层处理序列

2D卷积层的工作原理是在图像上滑动几个相当小的内核(或过滤器),生成多个2D特征图(每个内核一个)。类似地,一维卷积层在一个序列上滑动多个内核,为每个内核生成一维卷积图。每个内核将学习检测单个非常短的顺序模式(不长于内核大小)。如果使用10个内核,则该层的输出将由10个一维的序列(所有长度相同)组成,或者等效地,可以将此输出视为单个10维的序列。这意味着可以构建由循环层和一维卷积层(甚至一维池化层)混合而成的神经网络。如果使用步幅为1且填充为'same'的一维卷积层,则输出序列的长度和输入序列的长度相同。但是,如果使用1填充为’valid‘或步幅大于1,则输出序列比输入序列短,因此需要确保相应地调整目标。

例如,一下模型与前面模型相同,不同之处在于它从一维卷积层开始,将输入序列进行2倍下采样,使用步幅为2。内核大小大于步幅,因此所有的输入将被用来计算层的输出,因此该模型可以学习保留有用的信息,而只删除不重要的细节。通过缩短序列,卷积层可以帮助GRU检测更长的模式。需要注意的是,还必须裁剪掉目标中的前三个时间步长(因为内核大小为4,所有卷积层的第一个输出将基于0至3的输入是时间步长),并对目标进行2倍的下采样

import numpy as np
import tensorflow as tf
from tensorflow import keras


def generate_time_series(batch_size, n_steps):
    freq1, freq2, offsets1, offsets2 = np.random.rand(
        4, batch_size, 1)  # 生成4个,(batch_size,1)形状的矩阵
    time = np.linspace(0, 1, n_steps)  # 时间为将0-1划分为n_steps个时间步长
    series = .5*np.sin((time-offsets1)*(freq1*10+10))  # 创建时间序列数值:sin(时间-相位)*频率)
    series += .2*np.sin((time-offsets2)*(freq2*20+20))
    series += .1*(np.random.rand(batch_size, n_steps)-.5) # 添加噪声
    return series[..., np.newaxis].astype(np.float32)

n_steps = 50
series = generate_time_series(10000, n_steps+10)
X_train, Y_train = series[:7000, :n_steps], series[:7000, -10:,0]
X_valid, Y_valid = series[7000:9000, :n_steps], series[7000:9000, -10:,0]
X_test, Y_test = series[9000:, :n_steps], series[9000:, -10:,0]
Y = np.empty((10000, n_steps, 10))
for step_ahead in range(1, 10+1):
    Y[:, :, step_ahead-1] = series[:, step_ahead:step_ahead +
                                   n_steps, 0]  # 创建时间序列,将预测目标设置为X后n_steps个时间步的数据作为序列中的一个数据,时间步与X相同但是是一个拥有10个数值的序列
Y_train = Y[:7000]
Y_valid = Y[7000:9000]
Y_test = Y[9000:]
print(series.shape)
print(X_train.shape)
print(Y_train.shape)


def last_time_step_mse(Y_true, Y_pred):
    return keras.metrics.mean_squared_error(Y_true[:, -1], Y_pred[:, -1])



model=keras.models.Sequential([
    keras.layers.Conv1D(filters=20,kernel_size=4,strides=2,padding='valid',input_shape=[None,1]),
    keras.layers.GRU(20,return_sequences=True),
    keras.layers.GRU(20,return_sequences=True),
    keras.layers.TimeDistributed(keras.layers.Dense(10))
])
model.compile(loss='mse',optimizer='adam',metrics=[last_time_step_mse])
history=model.fit(X_train,y_train[:,3::2],epochs=20,
                 validtion_data=(X_valid,Y_valid[:,3::2]))
(10000, 60, 1)
(7000, 50, 1)
(7000, 50, 10)
Epoch 1/20
219/219 [==============================] - 4s 11ms/step - loss: 0.0705 - last_time_step_mse: 0.0629 - val_loss: 0.0464 - val_last_time_step_mse: 0.0399
Epoch 2/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0403 - last_time_step_mse: 0.0328 - val_loss: 0.0358 - val_last_time_step_mse: 0.0284
Epoch 3/20
219/219 [==============================] - 2s 9ms/step - loss: 0.0312 - last_time_step_mse: 0.0215 - val_loss: 0.0282 - val_last_time_step_mse: 0.0176
Epoch 4/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0260 - last_time_step_mse: 0.0150 - val_loss: 0.0251 - val_last_time_step_mse: 0.0137
Epoch 5/20
219/219 [==============================] - 2s 9ms/step - loss: 0.0240 - last_time_step_mse: 0.0130 - val_loss: 0.0238 - val_last_time_step_mse: 0.0125
Epoch 6/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0231 - last_time_step_mse: 0.0123 - val_loss: 0.0231 - val_last_time_step_mse: 0.0122
Epoch 7/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0222 - last_time_step_mse: 0.0116 - val_loss: 0.0225 - val_last_time_step_mse: 0.0117
Epoch 8/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0218 - last_time_step_mse: 0.0113 - val_loss: 0.0218 - val_last_time_step_mse: 0.0109
Epoch 9/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0212 - last_time_step_mse: 0.0108 - val_loss: 0.0216 - val_last_time_step_mse: 0.0108
Epoch 10/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0207 - last_time_step_mse: 0.0103 - val_loss: 0.0209 - val_last_time_step_mse: 0.0101
Epoch 11/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0202 - last_time_step_mse: 0.0099 - val_loss: 0.0207 - val_last_time_step_mse: 0.0100
Epoch 12/20
219/219 [==============================] - 2s 7ms/step - loss: 0.0198 - last_time_step_mse: 0.0095 - val_loss: 0.0200 - val_last_time_step_mse: 0.0093
Epoch 13/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0192 - last_time_step_mse: 0.0090 - val_loss: 0.0194 - val_last_time_step_mse: 0.0088
Epoch 14/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0185 - last_time_step_mse: 0.0083 - val_loss: 0.0187 - val_last_time_step_mse: 0.0079
Epoch 15/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0178 - last_time_step_mse: 0.0075 - val_loss: 0.0181 - val_last_time_step_mse: 0.0076
Epoch 16/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0170 - last_time_step_mse: 0.0066 - val_loss: 0.0173 - val_last_time_step_mse: 0.0064
Epoch 17/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0164 - last_time_step_mse: 0.0060 - val_loss: 0.0165 - val_last_time_step_mse: 0.0057
Epoch 18/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0159 - last_time_step_mse: 0.0054 - val_loss: 0.0162 - val_last_time_step_mse: 0.0055
Epoch 19/20
219/219 [==============================] - 2s 9ms/step - loss: 0.0154 - last_time_step_mse: 0.0050 - val_loss: 0.0157 - val_last_time_step_mse: 0.0049
Epoch 20/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0151 - last_time_step_mse: 0.0047 - val_loss: 0.0153 - val_last_time_step_mse: 0.0046
32/32 [==============================] - 0s 3ms/step - loss: 0.0149 - last_time_step_mse: 0.0044





[0.014898738823831081, 0.0043519060127437115]

精度为0.004是目前为止最好的模型,卷积层确实有帮助。

WaveNet

在2016年的一篇论文中,介绍了一种称为WaveNet的架构。他们堆叠了一维卷积层,使每一层的扩散(dilation)率(每个神经元输入的分散程度)加倍:第一个卷积层一次只看到两个时间步长,而下一个卷积层一次看到四个时间步长(其接受视野为四个时间步长),下一次看到8个时间步长,以此类推。这样,较低的层学习短期模式,而较高的曾学习长期模式。由于扩散率的加倍,网络可以非常有效地处理非常大的序列。

在WaveNet论文中,作者实际上堆叠了10个卷积层,其扩散率为1、2、4、8,···,256,512,然后又堆叠了另一组10个相同的层(扩散率也分别为1、2、4、8,···,256,512),然后又是另一组相同的10层。它们通过指出具有这些扩散率的10个卷积层的单个堆栈1来证明该结构的合理性,就像内核大小为1024的超高效卷积层一样工作(除了更快、更强大且使用更少的参数之外),这就是为什么它们堆叠了3个这样的块。它们还对输入序列进行了左填充,这些零与每个层之前的扩散率相等,以保证整个网络中相同的序列长度,以下实现了简化的WaveNet来处理和之前相同的序列:

model=keras.models.Sequential()
model.add(keras.layers.InputLayer(input_shape=[None,1]))
for rate in (1,2,4,8)*2:
    model.add(keras.layers.Conv1D(filters=20,kernel_size=2,padding='causal',activation='relu',dilation_rate=rate))
model.add(keras.layers.Conv1D(fliters=10,kernel_size=1))
model.compile(loss='mse',optimizer='adam',metrics=[last_time_step_mse],validation_data=(X_valid,Y_valid))
history=model.fit(X_train,Y_train,epochs=20,validation_data=(X_valid,Y_valid))
model.evaluate(X_test,Y_test)
Epoch 1/20
219/219 [==============================] - 3s 9ms/step - loss: 0.0700 - last_time_step_mse: 0.0577 - val_loss: 0.0377 - val_last_time_step_mse: 0.0237
Epoch 2/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0318 - last_time_step_mse: 0.0185 - val_loss: 0.0299 - val_last_time_step_mse: 0.0170
Epoch 3/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0274 - last_time_step_mse: 0.0150 - val_loss: 0.0264 - val_last_time_step_mse: 0.0138
Epoch 4/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0258 - last_time_step_mse: 0.0137 - val_loss: 0.0255 - val_last_time_step_mse: 0.0133
Epoch 5/20
219/219 [==============================] - 2s 7ms/step - loss: 0.0247 - last_time_step_mse: 0.0128 - val_loss: 0.0246 - val_last_time_step_mse: 0.0125
Epoch 6/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0239 - last_time_step_mse: 0.0121 - val_loss: 0.0236 - val_last_time_step_mse: 0.0116
Epoch 7/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0232 - last_time_step_mse: 0.0114 - val_loss: 0.0231 - val_last_time_step_mse: 0.0110
Epoch 8/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0227 - last_time_step_mse: 0.0110 - val_loss: 0.0225 - val_last_time_step_mse: 0.0106
Epoch 9/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0221 - last_time_step_mse: 0.0105 - val_loss: 0.0222 - val_last_time_step_mse: 0.0102
Epoch 10/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0216 - last_time_step_mse: 0.0099 - val_loss: 0.0217 - val_last_time_step_mse: 0.0097
Epoch 11/20
219/219 [==============================] - 2s 7ms/step - loss: 0.0212 - last_time_step_mse: 0.0096 - val_loss: 0.0214 - val_last_time_step_mse: 0.0093
Epoch 12/20
219/219 [==============================] - 2s 7ms/step - loss: 0.0207 - last_time_step_mse: 0.0089 - val_loss: 0.0211 - val_last_time_step_mse: 0.0087
Epoch 13/20
219/219 [==============================] - 2s 7ms/step - loss: 0.0203 - last_time_step_mse: 0.0085 - val_loss: 0.0203 - val_last_time_step_mse: 0.0080
Epoch 14/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0198 - last_time_step_mse: 0.0079 - val_loss: 0.0213 - val_last_time_step_mse: 0.0092
Epoch 15/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0196 - last_time_step_mse: 0.0078 - val_loss: 0.0202 - val_last_time_step_mse: 0.0077
Epoch 16/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0193 - last_time_step_mse: 0.0075 - val_loss: 0.0199 - val_last_time_step_mse: 0.0077
Epoch 17/20
219/219 [==============================] - 2s 7ms/step - loss: 0.0190 - last_time_step_mse: 0.0073 - val_loss: 0.0200 - val_last_time_step_mse: 0.0076
Epoch 18/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0188 - last_time_step_mse: 0.0069 - val_loss: 0.0193 - val_last_time_step_mse: 0.0070
Epoch 19/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0187 - last_time_step_mse: 0.0069 - val_loss: 0.0195 - val_last_time_step_mse: 0.0073
Epoch 20/20
219/219 [==============================] - 2s 8ms/step - loss: 0.0183 - last_time_step_mse: 0.0067 - val_loss: 0.0188 - val_last_time_step_mse: 0.0066
32/32 [==============================] - 0s 8ms/step - loss: 0.0183 - last_time_step_mse: 0.0063

[0.018330231308937073, 0.006296257022768259]

MSE到达了0.0063

该Sequential模型从显示输入层开始,然后继续使用填充为“causal”的一维卷积层:这确保卷积层在进行预测时不会窥视未来(它等效于在输入的左侧填充正确数量的零且使用’valid‘填充)。然后添加类似的成对层,使用不断扩大的扩散率:1、2、4、8,还是1、2、4、8.最后添加输出层:一个具有10个大小为1的滤波器且没有任何激活函数的卷积层。多亏了填充层,每个卷积层都输出和输入序列长度相同的序列,因此在训练过程中使用的目标是完整序列,无需将裁减或对它们进行下采样。

到目前为止,最后两个模型在预测时间序列方面提供了最佳性能。在WaveNet论文中,作者在各种音频任务(架构的名称)上获得了最先进的性能,包括从文本到语音的任务,在多种语言中产生了令人难以置信的逼真的声音。他们还是用该模型来生成音乐,一次生成一个音频样本。当意识到一秒钟的音频可以包含数万个时间步长时,这一成就会更加令人印象深刻,甚至LSTM和GRU都无法处理如此长的序列。

循环神经元和层

到目前为止,我们只关注前馈神经网络,其中激活仅在一个方向上流动,从输入层流向输出层。循环神经网络看起来非常像前馈神经网络,除了它还具有指向反向的连接。最简单的RNN由一个神经元接收输入,产生输出并将该输出返送回自身组成。在每个时间步长$t$(也称为帧),该循环神经网络接收输入$x_{(t)}$和前一个时间步长$y_{(t-1)}$的输出。由于在第一个时间步长没有先前的输出,因此通常将其设置为0。

一层递归神经元。在每个时间步长$t$,每个神经元接收输入向量$x_{(t)}$和前一个时间步长的输出向量$y_{(t-1)}$。输入和输出都是向量

每个循环神经元都有两组权重:一组用于输入$x_{(t)}$,另一组用于前一个时间步长$y_{(t-1)}$的输出。称这些权重向量为$w_x$和$w_y$。如果考虑整个循环曾而不仅仅一个神经元,则可以将所有的权重向量放在两个权重矩阵$W_x$和$W_y$中。然后可以如预期的那样计算整个循环层的输出向量,如公式所示:$b$是偏置向量,$\phi(\cdot)$是激活函数(例如ReLU)

单个实例的循环层的输出:
$$y_{(t)}=\phi(W_x^Tx_{(t)}+W_y^Ty_{(t-1)}+b)$$
正如前馈神经网络一样,可以通过将时间步长$t$处的所有输入都放在输入矩阵$X_{(t)}$中,来一次性地计算整个小批量中的递归层的输出

小批量中的所有实例的循环神经元层的输出:
$$Y_{(t)}=\phi(X_{(t)}W_x+Y_{(t-1)}W_y+b)=\phi([X_{(t)}\,Y_{(t-1)}]W+b)$$
其中:
$$W=\begin{bmatrix}W_x \\ W_y\end{bmatrix}$$
在此等式中:
$Y_{(t)}$是一个$m\times n{neurons}$矩阵,包括小批量处理中每个实例在时间步长t时该层的输出($m$是小批量处理中的实例数量,$n_{neurons}$是神经元数量)。

$X_{(t)}$是一个$m\times n_{inputs}$矩阵,包括所有实例的输出($n_{inputs}$是输入特征的数量)

$W_x$是一个$n_{inputs}\times n_{neurons}$矩阵,包含当前时间步长的输入的连接权重

$W_y$是一个$n_{neurons}\times n_{neurons}$矩阵,包含前一个时间步长的输出的连接权重

$b$是大小为$n_{neurons}$的向量,包含每个神经元的偏置项

  • 权重矩阵$W_x$和$W_y$经常垂直合并成形状为$(n_{inputs}+n_{neurons})\times n_{neurons}$的单个权重矩阵$W$
  • 符号$[X_{(t)}\,Y_{(t-1)}]$表示矩阵$X_{(t)}$和$Y_{(t-1)}$的水平合并

$Y_{(t)}$是$X_{(t)}$和$Y_{(t-1)}$的函数,$Y_{(t-1)}$是$X_{(t-1)}$和$Y_{(t-2)}$的函数,以此类推。自时间$t=0$以来,这使$Y_{(t)}$成为所有输入的函数。在第一个时间步长$t=0$时,没有先前的输出,因此通常假定它们均为零

记忆单元

由于在时间步长$t$时递归神经元的输出是先前时间步长中所有输入的函数,因此可以说它具有记忆的形式。在时间步长上保留某些状态的神经网络的一部分称为记忆单元(或简称为单元)。单个循环神经元或一层循环神经元是一个非常基本的单元,它只能学习短模式(通常约为10个步长,但这取决于任务)。

通常,在时间步长$t$的单元状态,表示为$h_{(t)}$(“h”代表“隐藏”),是该时间步长的某些输入和其前一个时间步长状态的函数:$h_{(t)}=f(h_{(t-1)},x_{(t)})$。它在时间步长$t$处的输出表示为$y_{(t)}$,也是先前状态和当前输入的函数。

输入和输出序列

RNN可以同时接收输入序列并产生输出序列。这种类型的序列到序列的网络可用于预测注入股票价格之类的时间序列:输入过去N天的价格作为输入,它必须输出未来偏移一天的价格(即从前N-1天到明天)。

或者,可以向网络提供一个输入序列,并忽略除了最后一个输出外的所有输入。换句话说,这是一个序列到向量的网络。例如,可以向网络提供与电影评论相对应的单词序列,然后网络将输出一个情感的分(例如从-1[恨]到+1[爱])

相反,可以在每个时间步长中一次又一次地向网络提供相同的输入向量,并让其输出一个序列。这是一个向量到序列的网络。例如,输入可以是图像(或CNN地输出),而输出可以是图像的描述。

最后,可能有一个成为编码器的序列到向量的网络,然后是一个成为解码器地向量到序列的网络。例如,这可以用于将句子从一种语言翻译成另一种语言。向网络提供一种语言的句子,编码器会将其转换为单个向量的表征,然后解码器会将此向量解码成另一种语言的句子。这种称为“编码器-解码器(Encoder-Decoder)”的两步模型比使用单个序列到序列的RNN进行即时翻译要好得多:句子的最后一个单词会影响翻译的第一个词,因此在翻译之前需要等待直到看完整个句子。

训练RNN

要训练RNN,就要将其按时间逐步展开,然后简单的使用常规的反向传播。这种策略称为“时间反向传播”(Back Propagation Through Time,BPTT)。

就像在常规反向传播中一样,这里有一个通过展开网络的第一次前向通路。然后使用成本函数$C(Y_{(0)},Y_{(1)},\cdots,Y_{(T)}$(其中$T$是最大时间步长)来评估输出序列。此成本函数可能会忽略某些输出,在序列到向量RNN中,除最后一个输出外,所有的输出都将被忽略。然后,该成本函数的梯度通过展开的网络反向传播。最后,使用在BPTT期间计算的梯度来更新模型参数。梯度反向通过成本函数使用的所有输出,而不是最终输出。而且,由于在每个时间步长上使用相同的参数$W$和$b$,所以反向传播会做正确的事情,在所有时间步长上求和

预测时间序列

假设你正在研究网站上每小时的活跃用户数、城市的每日温度或使用多个指标来评估每个季度公司的财务状况。在所有这些情况下,数据是每个时间步长一个或多个值的序列。这称为时间序列。在前两个示例中,每个时间步长只有一个值,因此它们是单变量时间序列,而在财务示例中,每个时间步长有多个值(例如,公司的收入、债务等),因此它是一个多变量时间序列。典型的任务是预测未来值,这称为预测。另一个常见的任务是填补空白:预测过去的缺失值。这称为插补。

使用由generate_time_series()函数生成的时间序列来研究:

import numpy as np
import tensorflow as tf
from tensorflow import keras


def generate_time_series(batch_size, n_steps):
    freq1, freq2, offsets1, offsets2 = np.random.rand(
        4, batch_size, 1)  # 生成4个,(batch_size,1)形状的矩阵
    time = np.linspace(0, 1, n_steps)  # 时间为将0-1划分为n_steps个时间步长
    series = .5*np.sin((time-offsets1)*(freq1*10+10))  # 创建时间序列数值:sin(时间-相位)*频率)
    series += .2*np.sin((time-offsets2)*(freq2*20+20))
    series += .1*(np.random.rand(batch_size, n_steps)-.5) # 添加噪声
    return series[..., np.newaxis].astype(np.float32)
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\numpy\_distributor_init.py:30: UserWarning: loaded more than 1 DLL from .libs:
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\numpy\.libs\libopenblas.GK7GX5KEQ4F6UYO3P26ULGBQYHGQO7J4.gfortran-win_amd64.dll
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\numpy\.libs\libopenblas.WCDJNK7YVMPZQ2ME2ZZHJJRJ3JIKNDB7.gfortran-win_amd64.dll
  warnings.warn("loaded more than 1 DLL from .libs:"

此函数根据请求(通过batch_size)参数创建任意数量的时间序列,每个序列的长度为n_steps,并且每个序列中每个时间步长只有一个值(即所有序列都是单变量的)。该函数返回一个形状为[批处理大小,时间步长,1]的NumPy数组,其中每个序列是两个固定振幅但频率和相位随机的正弦波的总和,再加上一点噪声。

在处理时间序列(以及其中类型的序列,例如句子)时,输入特征通常表示为形状为[批处理大小,时间步长,维度]的3D数组,其中单变量时间序列的维度为1,多变量时间序列的维度更多。

现在使用此函数创建训练集、验证集和测试集:

n_steps = 50
series = generate_time_series(10000, n_steps+1)  # 生成10000个时间步长为51的单变量时间序列
# 取前7000个时间步长为50作为输入,最后一个时间步长作为输出。通过前50个时间步长来预测后一个时间步长
X_train, y_train = series[:7000, :n_steps], series[:7000, -1]
X_valid, y_valid = series[7000:9000, :n_steps], series[7000:9000, -1]
X_test, y_test = series[9000:, :n_steps], series[9000:, -1]

X_train包含7000个时间序列(即形状为[7000, 50, 1]),而X_valid包含2000个(从第7000个时间序列到8999个),X_test包含1000个(从9000个时间序列到9999个)。由于要为每个序列预测一个值,因此目标是列向量(例如,y_train的形状为[7000, 1])。

基准指标

在开始使用RNN之前,通常最好有一些基准指标,否则可能会认为模型工作得很哈,但是实际上它的表现要比基本模型差。例如,最简单的方法是预测每个序列中的最后一个值。这被称为单纯预测,有时会令人惊讶地表现不好。在这种情况下,它们给了约0.020的均方误差:

y_pred = X_valid[:, -1]
np.mean(keras.losses.mean_squared_error(y_valid, y_pred))
0.020261222


另一种简单的方法是使用全连接的网络。由于它希望每个输入都有一个平的特征列表,因此需要添加一个Flatten层。只是使用一个简单的线性回归模型,是每个预测是时间序列中值的线性组合:

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[50, 1]),
    keras.layers.Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20)
model.evaluate(X_test, y_test)
Epoch 1/20
219/219 [==============================] - 0s 1ms/step - loss: 0.2783
Epoch 2/20
219/219 [==============================] - 0s 977us/step - loss: 0.0724
Epoch 3/20
219/219 [==============================] - 0s 863us/step - loss: 0.0486
Epoch 4/20
219/219 [==============================] - 0s 1ms/step - loss: 0.0354
Epoch 5/20
219/219 [==============================] - 0s 867us/step - loss: 0.0275
Epoch 6/20
219/219 [==============================] - 0s 872us/step - loss: 0.0225
Epoch 7/20
219/219 [==============================] - 0s 849us/step - loss: 0.0192
Epoch 8/20
219/219 [==============================] - 0s 863us/step - loss: 0.0168
Epoch 9/20
219/219 [==============================] - 0s 918us/step - loss: 0.0147
Epoch 10/20
219/219 [==============================] - 0s 881us/step - loss: 0.0130
Epoch 11/20
219/219 [==============================] - 0s 890us/step - loss: 0.0114
Epoch 12/20
219/219 [==============================] - 0s 950us/step - loss: 0.0101
Epoch 13/20
219/219 [==============================] - 0s 872us/step - loss: 0.0090
Epoch 14/20
219/219 [==============================] - 0s 838us/step - loss: 0.0080
Epoch 15/20
219/219 [==============================] - 0s 844us/step - loss: 0.0072
Epoch 16/20
219/219 [==============================] - 0s 991us/step - loss: 0.0066
Epoch 17/20
219/219 [==============================] - 0s 867us/step - loss: 0.0060
Epoch 18/20
219/219 [==============================] - 0s 844us/step - loss: 0.0056
Epoch 19/20
219/219 [==============================] - 0s 876us/step - loss: 0.0053
Epoch 20/20
219/219 [==============================] - 0s 867us/step - loss: 0.0050
32/32 [==============================] - 0s 581us/step - loss: 0.0047





0.00474193412810564


如果使用MSE损失和默认的Adam优化器来编译此模型,然后在训练集上训练20个轮次并在验证集上评估,得出的MSE约为0.005,这比单纯预测的方法好得多

实现一个简单的RNN

model = keras.models.Sequential([
    keras.layers.SimpleRNN(1, input_shape=[None, 1])
])

它仅包含有一个有单个神经元的单层。不需要指定输入序列的长度(与之前的模型不同),因为循环神经网络可以处理任意数量的时间步长(这就是将第一个输入的维度设置为None的原因)。默认情况下,SimpleRNN层使用双曲正切激活函数。它的工作原理:初始状态$h_{(init)}$设置为0,并将其与第一个时间步长的值$x_{(0)}$一起传递给单个循环神经元。神经元计算这些值的加权总和,并将双曲正切激活函数应用于其结果,这得出了第一个输出$y_0$。在简单的RNN中,此输出也是新状态$h_0$。这个新的状态与西安一个输入值$x_{(1)}$一起传递给相同的循环神经网络,并重复该过程直到最后一个时间步长。然后,该层仅仅输出最后一个值$y_{49}$。对于每个时间序列,所有这些都是同时执行的。

默认情况下,Keras中的循环层仅返回最终输出。要使它们每个时间步长返回一个输出,必须设置return_sequences=True。

model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20)
model.evaluate(X_test, y_test)
Epoch 1/20
219/219 [==============================] - 7s 29ms/step - loss: 0.0126
Epoch 2/20
219/219 [==============================] - 6s 30ms/step - loss: 0.0112
Epoch 3/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0111
Epoch 4/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 5/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0110
Epoch 6/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0110
Epoch 7/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 8/20
219/219 [==============================] - 7s 30ms/step - loss: 0.0110
Epoch 9/20
219/219 [==============================] - 6s 28ms/step - loss: 0.0110
Epoch 10/20
219/219 [==============================] - 6s 29ms/step - loss: 0.0110
Epoch 11/20
219/219 [==============================] - 7s 30ms/step - loss: 0.0110
Epoch 12/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0109
Epoch 13/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0110
Epoch 14/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 15/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0110
Epoch 16/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 17/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 18/20
219/219 [==============================] - 6s 29ms/step - loss: 0.0110
Epoch 19/20
219/219 [==============================] - 6s 29ms/step - loss: 0.0110
Epoch 20/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0109
32/32 [==============================] - 0s 6ms/step - loss: 0.0109





0.010947210714221


MSE只有0.011,因此它比单纯方法好,但它还不能击败简单的线性模型。对于每个神经元,线性模型的每个输入和每个时间步长都有一个参数,加上一个偏置项。相反,对于简单RNN中的每个循环神经元,每个输入和每个隐藏状态维度只有一个参数(在简单RNN中,这只是该层中循环神经元的数量),加上一个偏置项。在这个简单的RNN中,总共只有三个参数。

趋势和季节性

还有许多其他模型可以预测时间序列,例如加权移动平均模型或自回归集成移动平均(ARIMA)模型。其中有些要求你首先删除趋势和季节性。例如,如果正在研究网站上的活动用户数,并且该数字每月以10%的速度增长,则需要从时间序列中消除这种趋势。一旦训练完模型并开始进行预测,你不得不将趋势添加回去来得到最终的预测。同样,如果试图预测每个月出售的防晒乳液的数量,可能会观察到强烈的季节性:因为它在夏季会销售得非常好,而且每年都会重复类似的模式。你必须从时间序列中删除此季节性因素,例如,通过计算每个时间步长的值与一年前的值的差(这种技术称为差分)。同样,在对模型进行训练并做出预测之后,将不得不重新添加季节性模式来得到最终的预测。

使用RNN时,通常不需要执行所有这些操作,但是在某些情况下可能会提高性能,因为该模型不需要学习趋势或季节性。

深度RNN

把多层单元堆叠起来是非常普遍的。这就可以实现深度RNN。

使用tf.keras实现深度RNN非常简单:只需要堆叠循环层。在此示例中,使用了三个SimpleRNN层(也可以添加任何其他类型的循环层,例如LSTM层或GRU层):

model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20, return_sequences=True),
    keras.layers.SimpleRNN(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20)
model.evaluate(X_test, y_test)
Epoch 1/20
219/219 [==============================] - 23s 100ms/step - loss: 0.0234
Epoch 2/20
219/219 [==============================] - 23s 105ms/step - loss: 0.0041
Epoch 3/20
219/219 [==============================] - 24s 108ms/step - loss: 0.0036
Epoch 4/20
219/219 [==============================] - 23s 104ms/step - loss: 0.0034
Epoch 5/20
219/219 [==============================] - 24s 108ms/step - loss: 0.0033
Epoch 6/20
219/219 [==============================] - 24s 110ms/step - loss: 0.0033
Epoch 7/20
219/219 [==============================] - 23s 105ms/step - loss: 0.0032
Epoch 8/20
219/219 [==============================] - 22s 100ms/step - loss: 0.0031
Epoch 9/20
219/219 [==============================] - 24s 112ms/step - loss: 0.0031
Epoch 10/20
219/219 [==============================] - 24s 108ms/step - loss: 0.0030
Epoch 11/20
219/219 [==============================] - 23s 103ms/step - loss: 0.0030
Epoch 12/20
219/219 [==============================] - 24s 107ms/step - loss: 0.0030
Epoch 13/20
219/219 [==============================] - 24s 109ms/step - loss: 0.0030
Epoch 14/20
219/219 [==============================] - 23s 103ms/step - loss: 0.0030
Epoch 15/20
219/219 [==============================] - 23s 103ms/step - loss: 0.0029
Epoch 16/20
219/219 [==============================] - 25s 113ms/step - loss: 0.0029
Epoch 17/20
219/219 [==============================] - 23s 103ms/step - loss: 0.0028
Epoch 18/20
219/219 [==============================] - 23s 105ms/step - loss: 0.0028
Epoch 19/20
219/219 [==============================] - 24s 112ms/step - loss: 0.0029
Epoch 20/20
219/219 [==============================] - 23s 104ms/step - loss: 0.0029
32/32 [==============================] - 1s 20ms/step - loss: 0.0028





0.0028372975066304207


MSE为0.0028,终于设法击败了线性模型

最后一层不是理想的:它必须有一个单元,因此要预测一个单变量时间序列,这意味着每个时间步长必须有一个输出值。但是,只有一个单元意味着隐藏状态只是一个数字。

RNN主要使用其他循环层的隐藏状态来传递其所需的所有信息,而不会使用最终层的隐藏状态。此外,在默认情况下SimpleRNN层使用tanh激活函数,因此预测值必须在-1到1的范围内。但是,如果要使用其他激活函数,最好把输出层替换为Dense层:运行速度稍快,精度大致相同,并且可以选择所需的任何输出激活函数。如果做了更改,在最后一个循环层中删除return_sequences=True:

model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20),
    keras.layers.Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20)
model.evaluate(X_test, y_test)
Epoch 1/20
219/219 [==============================] - 14s 63ms/step - loss: 0.0340
Epoch 2/20
219/219 [==============================] - 15s 66ms/step - loss: 0.0041
Epoch 3/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0033
Epoch 4/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0032
Epoch 5/20
219/219 [==============================] - 14s 65ms/step - loss: 0.0031
Epoch 6/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0031
Epoch 7/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0031
Epoch 8/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0031
Epoch 9/20
219/219 [==============================] - 15s 66ms/step - loss: 0.0030
Epoch 10/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0030
Epoch 11/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0030
Epoch 12/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0029
Epoch 13/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0030
Epoch 14/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0030
Epoch 15/20
219/219 [==============================] - 14s 65ms/step - loss: 0.0029
Epoch 16/20
219/219 [==============================] - 14s 66ms/step - loss: 0.0029
Epoch 17/20
219/219 [==============================] - 14s 65ms/step - loss: 0.0028
Epoch 18/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0028
Epoch 19/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0029
Epoch 20/20
219/219 [==============================] - 15s 67ms/step - loss: 0.0028
32/32 [==============================] - 1s 14ms/step - loss: 0.0028





0.002804815536364913


输出层换成Dense后,收敛得更快并且性能也一样好。另外,可以根据需要更改输出激活函数。

预测未来几个时间步长

到目前为止,仅仅预测了下一个时间步长的值,但是可以很容易地通过适当地更改目标来预测未来几步的值(例如,要预测下十步,只需将目标更改为未来10步,而不是未来1步)。但是,如何预测接下来的10个值

第一种选择是使用已经训练好的模型,使其预测下一个值,然后将该值加到输入中(就像这个预测值实际上已经有了),然后再次使用该模型预测后面的值,以此类推,如以下代码所示:

series = generate_time_series(1, n_steps+10)
X_new, Y_new = series[:, :n_steps], series[:, n_steps:]
X = X_new
for step_ahead in range(10):
    y_pred_one = model.predict(X[:, step_ahead:])[:, np.newaxis, :]
    X = np.concatenate([X, y_pred_one], axis=1)
Y_pred = X[:, n_steps:]
np.mean(keras.losses.mean_squared_error(Y_new, Y_pred))
0.055745773


由于误差可能会累积,因此对下一个步长的预测通产会被对未来几个时间步长的预测更为准确。

第二种选择是训练RNN一次预测所有10个值,仍然可以使用一个序列到向量的模型,但是它输出10个值而不是1个值。但是,首先需要将目标更改为包含接下来10个值的向量:

series = generate_time_series(10000, n_steps+10)
X_train, Y_train = series[:7000, :n_steps], series[:7000, -10:,0]
X_valid, Y_valid = series[7000:9000, :n_steps], series[7000:9000, -10:,0]
X_test, Y_test = series[9000:, :n_steps], series[9000:, -10:,0]
model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20),
    keras.layers.Dense(10)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, Y_train, epochs=20, validation_data=(X_valid, Y_valid))
model.evaluate(X_test, Y_test)
Epoch 1/20
219/219 [==============================] - 16s 69ms/step - loss: 0.0700 - val_loss: 0.0371
Epoch 2/20
219/219 [==============================] - 17s 76ms/step - loss: 0.0272 - val_loss: 0.0201
Epoch 3/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0168 - val_loss: 0.0138
Epoch 4/20
219/219 [==============================] - 15s 67ms/step - loss: 0.0133 - val_loss: 0.0114
Epoch 5/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0118 - val_loss: 0.0103
Epoch 6/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0109 - val_loss: 0.0115
Epoch 7/20
219/219 [==============================] - 16s 75ms/step - loss: 0.0105 - val_loss: 0.0110
Epoch 8/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0101 - val_loss: 0.0099
Epoch 9/20
219/219 [==============================] - 14s 65ms/step - loss: 0.0101 - val_loss: 0.0094
Epoch 10/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0096 - val_loss: 0.0092
Epoch 11/20
219/219 [==============================] - 16s 73ms/step - loss: 0.0095 - val_loss: 0.0086
Epoch 12/20
219/219 [==============================] - 17s 76ms/step - loss: 0.0094 - val_loss: 0.0089
Epoch 13/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0095 - val_loss: 0.0091
Epoch 14/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0090 - val_loss: 0.0092
Epoch 15/20
219/219 [==============================] - 16s 73ms/step - loss: 0.0090 - val_loss: 0.0085
Epoch 16/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0089 - val_loss: 0.0097
Epoch 17/20
219/219 [==============================] - 16s 73ms/step - loss: 0.0091 - val_loss: 0.0099
Epoch 18/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0090 - val_loss: 0.0111
Epoch 19/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0088 - val_loss: 0.0089
Epoch 20/20
219/219 [==============================] - 16s 73ms/step - loss: 0.0089 - val_loss: 0.0081
32/32 [==============================] - 0s 15ms/step - loss: 0.0082





0.00820199865847826


该模型运行良好:接下里10个时间步长的MSE约为0.0082。这比线性模型好得多。但是仍然可以做得更好:实际上,与其训练模型在最后一个时间步长预测下一个10个值,不如训练模型在每个时间步长来预测下一个10个值。可以将这个序列到向量的RNN转换为序列到序列的RNN。这种技术的优势在于,损失将包含每个时间步长的RNN输出项,而不仅仅是最后一个时间步长的输出项。这意味着将有更多的误差梯度流过模型,它们不需要随时间而“流淌”。它们从每个时间步长的输出中流出。这会稳定和加速训练。

为了清楚期间,模型会在时间步长0时输出一个向量,其中包含时间步长1到10的预测,然后在时间步长1时模型会预测时间步长2到11,以此类推。因此,每个目标必须是与输入序列长度相同的序列,在每个步长都必须包含10维向量:

Y = np.empty((10000, n_steps, 10))
for step_ahead in range(1, 10+1):
    Y[:, :, step_ahead-1] = series[:, step_ahead:step_ahead +
                                   n_steps, 0]  # 创建时间序列,将预测目标设置为X后n_steps个时间步的数据作为序列中的一个数据,时间步与X相同但是是一个拥有10个数值的序列
Y_train = Y[:7000]
Y_valid = Y[7000:9000]
Y_test = Y[9000:]
print(series.shape)
print(X_train.shape)
print(Y_train.shape)
(10000, 60, 1)
(7000, 50, 1)
(7000, 50, 10)

目标中包含了在输入中出现的值X_train和Y_train之间有很多重叠。那不是作弊吗?幸运的是,一点也不是:在每个时间步长上,该模型仅知道过去的时间步长,因此不能向前看,这是一种因果模型。

要将模型转换为序列到序列的模型,必须在所有循环层(甚至最后一层)中设置return_sequences=True,必须在每个时间步长都应用输出Dense层。Keras为此提供了一个TimeDistributed层:它包装了任何层(例如Dense层),在输入序列的每个时间步长应用这个层。它通过重构输入的形状来有效地做到这一点,以便每个时间步长都被视为一个单独的实例(将输入的形状从[批量大小,时间不长,输入维度]调整为[批量大小X时间步长,输出维度]。在此示例中,输入维度为20,因为前一个SimpleRNN有20个单元),然后运行Dense层,最后将输出重构为序列(将输出从[批量大小X时间步长。输出维度]重构为[批量大小,时间步长,输出维度]。在此示例中,由于Dense层有10个单元,因此输出维度为10)。这是更新的模型:

model=keras.models.Sequential([
    keras.layers.SimpleRNN(20,return_sequences=True,input_shape=[None,1]),
    keras.layers.SimpleRNN(20,return_sequences=True),
    keras.layers.TimeDistributed(keras.layers.Dense(10))
])

Dense层实际上支持序列作为输入(甚至是更高维的输入):它像TimeDistributed(Dense(...))一样处理它们,这意味着它仅仅应用于最后一个输出维度(独立于所有的时间步长)。因此,可以使用Dense(10)代替最后一层。但是为了清楚起见,继续使用TimeDistributed(Dense(10)),因为它清楚地表明在每个时间步长都独立应用了Dense层,并且将模型输出一个序列,而不仅仅是一个向量。

训练期间需要所有的输出,但是只有最后一个时间步长的输出才对预测和评估有用。因此,尽管要依赖所有输出的MSE进行计算,但是需要使用自定义指标进行评估,以便在最后一个时间步长来计算输出的MSE:

def last_time_step_mse(Y_true, Y_pred):
    return keras.metrics.mean_squared_error(Y_true[:, -1], Y_pred[:, -1])


optimizer = keras.optimizers.Adam(lr=.01)
model.compile(loss='mse', optimizer=optimizer, metrics=[last_time_step_mse])
model.fit(X_train, Y_train, epochs=20, validation_data=(X_valid, Y_valid))
model.evaluate(X_test, Y_test)
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\keras\optimizer_v2\optimizer_v2.py:355: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
  warnings.warn(


Epoch 1/20
219/219 [==============================] - 17s 75ms/step - loss: 0.0469 - last_time_step_mse: 0.0365 - val_loss: 0.0343 - val_last_time_step_mse: 0.0202
Epoch 2/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0331 - last_time_step_mse: 0.0200 - val_loss: 0.0311 - val_last_time_step_mse: 0.0194
Epoch 3/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0288 - last_time_step_mse: 0.0156 - val_loss: 0.0264 - val_last_time_step_mse: 0.0120
Epoch 4/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0260 - last_time_step_mse: 0.0129 - val_loss: 0.0247 - val_last_time_step_mse: 0.0107
Epoch 5/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0239 - last_time_step_mse: 0.0113 - val_loss: 0.0237 - val_last_time_step_mse: 0.0127
Epoch 6/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0221 - last_time_step_mse: 0.0097 - val_loss: 0.0209 - val_last_time_step_mse: 0.0084
Epoch 7/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0210 - last_time_step_mse: 0.0087 - val_loss: 0.0216 - val_last_time_step_mse: 0.0079
Epoch 8/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0205 - last_time_step_mse: 0.0085 - val_loss: 0.0196 - val_last_time_step_mse: 0.0078
Epoch 9/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0200 - last_time_step_mse: 0.0079 - val_loss: 0.0214 - val_last_time_step_mse: 0.0103
Epoch 10/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0201 - last_time_step_mse: 0.0083 - val_loss: 0.0209 - val_last_time_step_mse: 0.0088
Epoch 11/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0195 - last_time_step_mse: 0.0076 - val_loss: 0.0191 - val_last_time_step_mse: 0.0069
Epoch 12/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0193 - last_time_step_mse: 0.0074 - val_loss: 0.0205 - val_last_time_step_mse: 0.0080
Epoch 13/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0191 - last_time_step_mse: 0.0074 - val_loss: 0.0206 - val_last_time_step_mse: 0.0087
Epoch 14/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0203 - last_time_step_mse: 0.0090 - val_loss: 0.0199 - val_last_time_step_mse: 0.0095
Epoch 15/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0190 - last_time_step_mse: 0.0073 - val_loss: 0.0190 - val_last_time_step_mse: 0.0068
Epoch 16/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0192 - last_time_step_mse: 0.0076 - val_loss: 0.0191 - val_last_time_step_mse: 0.0070
Epoch 17/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0294 - last_time_step_mse: 0.0209 - val_loss: 0.0383 - val_last_time_step_mse: 0.0335
Epoch 18/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0347 - last_time_step_mse: 0.0283 - val_loss: 0.0269 - val_last_time_step_mse: 0.0177
Epoch 19/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0258 - last_time_step_mse: 0.0158 - val_loss: 0.0232 - val_last_time_step_mse: 0.0127
Epoch 20/20
219/219 [==============================] - 16s 75ms/step - loss: 0.0225 - last_time_step_mse: 0.0118 - val_loss: 0.0210 - val_last_time_step_mse: 0.0102
32/32 [==============================] - 0s 15ms/step - loss: 0.0208 - last_time_step_mse: 0.0100





[0.020847154781222343, 0.010038050822913647]


得到的验证MSE约为,可以将此方法与第一个方法结合使用:使用这个RNN来预测接下来的10个值,然后将这些值合并到输入时间序列,然后再次使用该模型预测接下来的10个值。对于长期预测而言,它可能不太准确,但是如果目标是生成原始音乐或文本,则可能会很好。

预测时间序列是,将一些误差与预测一起使用通常很有用。为此,在每个记忆单元内添加一个MC Dropout层,删除部分输入和隐藏状态。训练后,为了预测新的时间序列,多次使用模型,并在每个时间步长计算预测的均值和标准差

简单的RNN可以很好的预测时间序列或处理其他类型的序列,但是在长序列上表现不佳。

使用RNN和CNN处理序列

击球手将球击出。外野手立即开始奔跑,预测球的轨迹。他跟踪它,调整自己的运动,最后抓住球。不管你是在听完朋友的话还是在早餐时期待咖啡的味道,预测都是你一直在做的事情。循环神经网络(RNN),这是一类可以预测未来的网络(在一定程度上)。它们可以分析时间序列数据(例如股票价格),并告诉你何时进行买卖。在自动驾驶系统中,它们可以预测汽车的行驶轨迹并帮助避免发生事故。更笼统地说,它们可以处理任意长度的序列,而不是像到目前为止的所有网络一样,用于固定大小的输入。例如,它们可以将句子、文档或音频样本作为输入,使其对于自然语言处理应用非常有用,例如自动翻译或语音转文本。

RNN面临的两个主要困难:

  • 不稳定的梯度,可以使用多种技术解决,包括递归dropout和递归层归一化
  • (非常)有限的短期记忆,可以使用LSTM和GRU单元进行拓展

RNN并不是唯一能够处理顺序数据的神经网络类型:对于较小的序列,常规的密集网络可以解决问题。对于很长的序列,例如音频样本或文本,卷积神经网络实际上也可以很好地工作。

语义分割

在语义分割中,每个像素根据其所属物体的类别(例如,道路、汽车、行人、建筑物等)进行分类。例如分割图像右侧的所有自行车都变成了一大块像素。此任务的主要困难在于,当图像通过常规CNN时,它们会逐渐失去其空间分辨率(由于步幅大于1的层),因此常规的CNN可能最终会知道在图像的左下角有一个人,但不会比这更精确了。

就像物体检测一样,有许多种方法可以解决此问题,有些方法非常复杂。但是,Jonathan Long等人在2015年的论文中提出了一个相当简单的解决方法。作者首先采用经过预训练的CNN,然后将其转换为FCN。CNN对输入图像应用的总步幅为32(如果所有步幅的总和都大于1),则意味着最后一层输出的特征图是输入图像的1/32。这显然太粗糙了,因此他们添加了一个单独的上采样层把分辨率乘以32.

有几种解决方法可用于上采样(增加图像的大小),例如双线性插值,但仅在X4或X8时才有效。取而代之的是,他们使用转置的卷积层:等效于首先插入空的行和列(充满零)来拉伸图像,然后执行常规的卷积。可以对转置的卷积层进行初始化以执行接近于线性插值的操作,但是由于它是可训练的层,因此在训练过程中会学习的更好。在tf.keras中,可以使用Conv2DTranspose层。

在转置的卷积层中,步幅定义了输入将被拉伸的程度,而不是滤波步长的大小,因此步幅越大,输出就越大。

这个解决方法可以,但仍然不够精确。为了做得更好,作者添加了来自较低层的跳过连接:例如,他们将输出图像上采样2倍(而不是32倍),并添加了具有两倍分辨率的较低层的输出。然后,他们对结果进行16倍的上采样,从而得到32倍的总上采样。这样可以恢复一些早期池化层丢失的空间分辨率。在他们的最佳架构中,他们使用了第二个类似的跳过连接来从更底层恢复甚至更精细的细节。简而言之,原始CNN的输出经过以下额外步骤:放大X2,添加较低层(适当比例)的输出,放大X2,添加甚至更底层的输出,最后方法X8。甚至有可能扩大到超出原始图像的大小:这可用于提高图像的分辨率,这是一种称为超分辨率的技术。

TensorFlow卷积运算

  • keras.layers.Conv1D

为一维输入(例如时间序列或文本(字母或单词的序列))创建卷积层。

  • keras.layers.Conv3D

为3D输入(例如3D PET扫描)创建卷积层

  • dilaton_rate

将任何卷积层的dilation_rate超参数设置为2或更大的值会创建“a-trous卷积层”。这等效于使用常规卷积层,并通过插入为零的行和列像孔一样来扩大滤波器。例如,可以使用4来扩展[[1,2,3]]的1X3滤波器,从而得到[[1,0,0,0,2,0,0,0,3]]来扩展滤波器。这使得卷积层无需任何计算费用,也无需使用任何额外参数就具有更大的接受视野

  • tf.nn.depthwise_conv2d()

可用于创建深度卷积层(但需要自己创建变量)。它将每个滤波器独立地应用于每个单独的输入通道。因此,如果有$f_n$个滤波器和$f_{n'}$个输入通道,则将输出$f_n\times f_{n'}$特征图

YOLO

YOLO是Joseph Redmon等人在2015年的论文中提出的一种极其快速、准确的物体检测架构随后在2016年(YOLOv2)和2018(YOLOv3)中进行了改进。

YOLOv3的架构和全卷积网络的架构非常相似,但有一些重要的区别:

  • 它为每个网格单元输出5个边界框(而不是一个),并且每个边界框都带有一个客观分数。由于它在包含20个类的PASCAL VOC数据集上进行了训练,因此每个网格单元还输出20个类别概率。每个网格单元共有45个数字:5个边界框,每个边界框具有4个坐标,再加上5个客观分数和20个类概率。
  • YOLOv3不预测边界框中心的绝对坐标,而是预测相对于网格单元坐标的偏移,其中(0,0)表示在该单元格左上角,而(1,1)表示右下角。对于每个网格单元,YOLOv3被训练只能预测其中心位于该单元内的边界框(但是边界框本身通常会远远超出网格单元)。YOLOv3将逻辑激活函数应用于边界框坐标,以确保它们在0到1的范围内。
  • 在训练神经网络之前,YOLOv3会找到5个具有代表性的边界框,称为锚框(或先验边界框)。它通过K-Means算法应用于训练集边界框的高度和宽度来实现。例如,如果训练图像包含许多行人,则锚框之一将可能具有典型的行人尺寸。然后,当神经网络预测每个网格单元的5个边界框时,它实际上预测了重新缩放每个锚定框的幅度。例如,假设一个锚框的高度为100像素,宽度为50像素,并且网络预测(对于一个网格单元)垂直缩放因子为1.5,水平缩放因子为0.9。那预测的边界框大小为150X45。更准确地说,对于每个网格单元和每个锚框,网络都将预测垂直和水平缩放比例的对数。拥有这些先验知识使网络更有可能预测出适当尺寸的边界框,并且由于它能更快地学习到合理边界框的样子,因此也加快了训练速度。
  • 该网络使用不同比例的图像进行训练:在训练过程中每隔几批,网络会随机选择一个训的图像尺寸。这使得网络能够学习检测不同比例的物体。还可以在不同的比例下使用YOLOv3:较小比例的准确率较低,但速度比较大比例的要快,因此可以根据实际情况选择合适的折衷方案。