关于语法的一点说明:从 TF ~1.0 开始,您需要在一个循环中定义多个层,而不是使用 [cell] * num_layers 语法,例如:
lstm_cells = []
for _ in range(num_layers):
cell = tf.contrib.rnn.BasicLSTMCell(n_hidden)
lstm_cells.append(cell)
stacked_lstm_cell = tf.contrib.rnn.MultiRNNCell(lstm_cells)
您的主要问题:
- 您的代码为您提供了一个具有 3 层 (
num_layers) 的网络,其中每一层都包含一个隐藏状态长度为 2 (n_hidden) 的 LSTM。稍后再详细介绍。
- 三个 LSTM 层之间没有权重:每个 LSTM 将其输出馈送到下一个 LSTM 的输入。
- 除非您告诉 TF 不要训练某些东西,否则您网络中的所有权重和偏差都将被视为可训练变量并通过反向传播进行训练。
- LSTM 中的忘记和更新等操作在网络输入和网络先前隐藏状态的线性组合上执行一些函数。其中的“线性组合”部分涉及由您的网络训练的权重和偏差。
看一看 LSTM
我们来看看LSTM网络架构。 This is a pretty great overview that I recommend reading。基本上,单个 LSTM 单元保持一个隐藏状态,该状态代表其迄今为止所见内容的“记忆”,并且在每个更新步骤中,它决定将多少新信息与此隐藏状态中的现有信息混合使用“门”。它还使用一个门来确定它将输出什么。看一下单个单元格的更新过程:
-
我们首先确定要忘记多少旧信息(我们的忘记门):f_k = sigmoid(W_f * [h_k-1, x_k] + b_f)
在这里,我们在网络之前的历史记录h_k-1 与当前观察结果x_k 连接。您的历史向量h 的大小由n_hidden 定义。权重W_f 和偏差b_f 将通过训练过程学习。
-
我们确定要合并多少新信息(我们的输入门,i_k),并创建一些新的候选细胞状态(c'_k):
i_k = sigmoid(W_i * [h_k-1, x_k] + b_i)
c`_k = tanh(W_c * [h_k-1, x_k] + b_c)
再次,我们在旧的内部状态 h_k-1 和新的观察结果 x_k 上运行,以找出下一步该做什么。单元状态c 和候选单元状态c' 的大小也由n_hidden 确定。 W_* 和 b_* 是我们将要学习的更多参数。
-
将旧信息与新候选状态相结合,得出新的细胞状态:c_k = f_k * c_k-1 + i_k * c'_k
这里我们做的是逐元素乘法,而不是点积或其他任何东西。基本上,我们选择保留多少旧信息 (f_k * c_k-1),以及合并多少新信息 (i_k * c'_k)。
-
最后,我们通过输出门确定我们想要输出多少细胞状态:
o_k = sigmoid(W_o * [h_k-1, x_k] + b_o)
h_k = o_k * tanh(c_k)
所以基本上我们将新旧信息混合到内部“单元状态”c_k,然后在h_k 中输出一些信息。我还建议查看 gated recurrent unit (GRU) network,它的性能与 LSTM 类似,但结构更易于理解。
现在介绍多层网络的堆叠方式。基本上,你有这样的东西:
x_k ---> (network 0) --h0_k--> (network_1) --h1_k--> (network_2) --h2_k-->
因此,您的观察结果进入第一个网络,然后该网络的输出作为输入馈送到下一个网络,该网络将其与自己的内部状态混合以产生输出,然后成为第三个网络的输入,依此类推,直到结束。这应该有助于学习数据中的时间结构。我没有很好的引用。
通常,如果您正在进行分类(例如),您会在最后一个网络的输出上放置一个最终的全连接层,以获得一定程度的置信度,即您观察到的过程位于您要分类的每个类别中。
可训练变量
您可以使用以下方式打印出您的网络将要学习的所有可训练变量:
for var in tf.trainable_variables():
print('{}\nShape: {}'.format(var.name, var.get_shape()))
Tensorflow 通过组合不同的操作做了一些奇特的事情,所以你可能会看到一些奇怪的形状,并且显然缺少权重矩阵和偏差,但它就在那里。基本上,您正在学习每个门中使用的权重和偏差。在上面,那将是:
- 权重:
W_f、W_i、W_c 和 W_o每一层
- 偏差:
b_f、b_i、b_c 和 b_o对于每一层
- 以及您在最后一个 LSTM 层之上添加的其他输出层权重/偏差
我更熟悉 TF 如何处理 GRU 架构,它基本上将所有门组合成一个大矩阵运算,因此所有门都有一个组合权重矩阵和一个组合偏置向量。然后它将结果拆分到每个单独的门中,以将它们应用到正确的位置。仅供参考,以防您看起来每个单元格的每个单独步骤都没有权重和偏差。