Watch talks from the 2019 TensorFlow Dev Summit Watch now

递归神经网络

简介

如需关于递归神经网络和 LSTM 的简介,请参阅了解 LSTM 网络

语言建模

在此教程中,我们将展示如何训练递归神经网络来执行一项颇有挑战性的语言建模任务。该问题的目标是拟合概率模型,该模型会向句子分配概率。为实现此目标,它会根据文本中之前字词的历史记录预测后续字词。为此,我们将使用宾州树库 (PTB) 数据集,该数据集是衡量这些模型质量的热门基准,它占用空间小且训练速度相对较快。

语言建模是解决很多有趣问题(例如语音识别、机器翻译或图像说明)的关键。它本身也很有趣,您可以点击此处了解详情。

在此教程中,我们将重现 Zaremba 等人在 2014 年发布的技巧 (pdf) 中的结果。对于 PTB 数据集,该技巧可实现很好的效果。

教程文件

此教程参考了以下文件(位于 TensorFlow 模型代码库中的 models/tutorials/rnn/ptb 内):

文件 用途
ptb_word_lm.py 用 PTB 数据集训练语言模型的代码。
reader.py 读取数据集的代码。

下载并准备数据

此教程所需的数据位于 Tomas Mikolov 网页中 PTB 数据集data/ 目录下。

该数据集已经过预处理,总共包含 10000 个不同的字词,包括句子结束标记和表示罕见字词的特殊符号 (<unk>)。在 reader.py 中,我们将每个字词转换为一个唯一整数标识符,方便神经网络处理数据。

模型

LSTM

该模型的核心由一个 LSTM 单元组成,该单元一次处理一个字词,并计算句子中下一个字词的可能值的概率。该网络的内存状态初始化为零矢量,并在读取每个字词后进行更新。出于计算原因,我们将以大小为 batch_size 的小批次处理数据。请务必注意,本示例中的 current_batch_of_words 不对应于字词“句子”。批次中的每个字词都应对应于时间 t。TensorFlow 将自动对每个批次的梯度求和。

例如:

 t=0  t=1    t=2  t=3     t=4
[The, brown, fox, is,     quick]
[The, red,   fox, jumped, high]

words_in_dataset[0] = [The, The]
words_in_dataset[1] = [brown, red]
words_in_dataset[2] = [fox, fox]
words_in_dataset[3] = [is, jumped]
words_in_dataset[4] = [quick, high]
batch_size = 2, time_steps = 5

基本的伪代码如下:

words_in_dataset = tf.placeholder(tf.float32, [time_steps, batch_size, num_features])
lstm = tf.contrib.rnn.BasicLSTMCell(lstm_size)
# Initial state of the LSTM memory.
state = lstm.zero_state(batch_size, dtype=tf.float32)
probabilities = []
loss = 0.0
for current_batch_of_words in words_in_dataset:
    # The value of state is updated after processing each batch of words.
    output, state = lstm(current_batch_of_words, state)

    # The LSTM output can be used to make next word predictions
    logits = tf.matmul(output, softmax_w) + softmax_b
    probabilities.append(tf.nn.softmax(logits))
    loss += loss_function(probabilities, target_words)

截断的反向传播算法

按照设计,递归神经网络 (RNN) 的输出取决于任意距离的输入。很遗憾,这会导致反向传播算法的计算变得困难。为了使学习过程易于处理,常见做法是创建网络的“展开”版本,其中包含固定数量 (num_steps) 的 LSTM 输入和输出。然后,该模型基于 RNN 的这种有限逼近进行训练。具体实现方法如下:一次馈送长度为 num_steps 的输入,并在每个此类输入块之后执行反向传播。

下面是一个经过简化的代码块,用于创建执行截断的反向传播算法的图:

# Placeholder for the inputs in a given iteration.
words = tf.placeholder(tf.int32, [batch_size, num_steps])

lstm = tf.contrib.rnn.BasicLSTMCell(lstm_size)
# Initial state of the LSTM memory.
initial_state = state = lstm.zero_state(batch_size, dtype=tf.float32)

for i in range(num_steps):
    # The value of state is updated after processing each batch of words.
    output, state = lstm(words[:, i], state)

    # The rest of the code.
    # ...

final_state = state

对整个数据集迭代一次的方法如下:

# A numpy array holding the state of LSTM after each batch of words.
numpy_state = initial_state.eval()
total_loss = 0.0
for current_batch_of_words in words_in_dataset:
    numpy_state, current_loss = session.run([final_state, loss],
        # Initialize the LSTM state from the previous iteration.
        feed_dict={initial_state: numpy_state, words: current_batch_of_words})
    total_loss += current_loss

输入

字词 ID 会先嵌入到密集表示法中(请参阅矢量表示法教程),然后再馈送至 LSTM。这样一来,模型便可高效地表示关于特定字词的信息。而且代码编写起来很轻松:

# embedding_matrix is a tensor of shape [vocabulary_size, embedding size]
word_embeddings = tf.nn.embedding_lookup(embedding_matrix, word_ids)

嵌入矩阵将随机初始化,模型会学习如何通过查看数据来区分字词的含义。

损失函数

我们希望尽可能减小目标字词的平均负对数概率:

$$ \text{loss} = -\frac{1}{N}\sum_{i=1}^{N} \ln p_{\text{target}_i} $$

实现此目标并不是很困难,但是已经有函数 sequence_loss_by_example,因此我们可以直接在这里使用它。

很多论文中报告的典型衡量指标为平均每个字词的困惑度(通常简称为困惑度),它等于

$$e^{-\frac{1}{N}\sum_{i=1}^{N} \ln p_{\text{target}_i}} = e^{\text{loss}} $$

我们将在整个训练过程中监控它的值。

堆叠多个 LSTM

为使模型更具表现力,我们可以添加多层 LSTM 来处理数据。第一层的输出将成为第二层的输入,以此类推。

有一个名为 MultiRNNCell 的类可达成无缝实现:

def lstm_cell():
  return tf.contrib.rnn.BasicLSTMCell(lstm_size)
stacked_lstm = tf.contrib.rnn.MultiRNNCell(
    [lstm_cell() for _ in range(number_of_layers)])

initial_state = state = stacked_lstm.zero_state(batch_size, tf.float32)
for i in range(num_steps):
    # The value of state is updated after processing each batch of words.
    output, state = stacked_lstm(words[:, i], state)

    # The rest of the code.
    # ...

final_state = state

运行代码

在运行代码之前,请下载 PTB 数据集,如此教程开头所述。然后,解压缩主目录下的 PTB 数据集,如下所示:

tar xvfz simple-examples.tgz -C $HOME

(注意:在 Windows 上,您可能需要使用其他工具。)

现在,从 GitHub 克隆 TensorFlow 模型代码库。运行以下命令:

cd models/tutorials/rnn/ptb
python ptb_word_lm.py --data_path=$HOME/simple-examples/data/ --model=small

教程代码提供 3 种支持的模型配置:“小”、“中”和“大”。这些模型配置之间的区别在于 LSTM 的大小以及用于训练的超参数集。

模型越大,效果就越好。small 模型应该能够在测试集上实现低于 120 的困惑度,而 large 模型则低于 80(虽然它可能需要花费几个小时来训练)。

后续学习计划

还有一些我们没有提及的模型改善技巧,包括:

  • 降低学习速率计划,
  • 在 LSTM 层之间添加丢弃层。

请研究并修改代码,以进一步改进模型。