项目已上传至 GitHub —— nl-modeling
1. 下载 PTB 数据集
官方下载地址的速度可能比较慢,这里提供了其他两种下载途径:
需要的 PTB 数据集就在解压之后的 data/ 文件夹下,data/ 文件夹下有 7 个文件,要用的只有 3 个:
ptb.test.txt
ptb.train.txt
ptb.valid.txt
要读取 PTB 数据集需要用到 reader.py,在 GitHub 上面有源码,可以下载或者直接复制下来。reader.py 提供了两个函数用于读取和处理 PTB 数据集:
- ptb_raw_data(DATA_PATH):读取原始数据
- ptb_producer(raw_data,batch_size,num_steps):用于将数据组织成大小为 batch_size,长度为 num_steps 的数据组
原书中的代码没有将这两个函数的操作对象视为 tensor,而根据 reader.py 中的源码说明,它是对 tensor 进行操作的。并且 ptb_producer() 函数中使用了 tf.train.range_input_producer() 函数,所以需要开启多线程。
以下代码示范了如何使用这两个函数:
import reader
import tensorflow as tf
# 数据路径
DATA_PATH = 'simple-examples/data/'
# 读取原始数据
train_data, valid_data, test_data, _ = reader.ptb_raw_data(DATA_PATH)
# 将数据组织成batch大小为4,截断长度为5的数据组,要放在开启多线程之前
batch = reader.ptb_producer(train_data, 4, 5)
with tf.Session() as sess:
tf.global_variables_initializer().run()
# 开启多线程
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(coord=coord)
# 读取前两个batch,其中包括每个时刻的输入和对应的答案,ptb_producer()会自动迭代
for i in range(2):
x, y = sess.run(batch)
print('x:', x)
print('y:', y)
# 关闭多线程
coord.request_stop()
coord.join(threads)
运行结果如下:
x: [[9970 9971 9972 9974 9975]
[ 332 7147 328 1452 8595]
[1969 0 98 89 2254]
[ 3 3 2 14 24]]
y: [[9971 9972 9974 9975 9976]
[7147 328 1452 8595 59]
[ 0 98 89 2254 0]
[ 3 2 14 24 198]]
x: [[9976 9980 9981 9982 9983]
[ 59 1569 105 2231 1]
[ 0 312 1641 4 1063]
[ 198 150 2262 10 0]]
y: [[9980 9981 9982 9983 9984]
[1569 105 2231 1 895]
[ 312 1641 4 1063 8]
[ 150 2262 10 0 507]]
2. 完整代码
整个代码的结构如下:
- PTBModel 类用于创建 LSTM 网络结构及维护其状态
- run_epoch() 函数使用给定的 model 在数据集上运行 train_op 并返回 perplexity 值
- main() 函数定义图的运行
由于原书中的代码是基于 1.0,而我用的是 1.5,所以出现了很多错误,我将所遇到的错误的解决方法都记录在了文末。源码如下:
import reader
import numpy as np
import tensorflow as tf
# 数据参数
DATA_PATH = 'simple-examples/data/' # 数据存放路径
VOCAB_SIZE = 10000 # 单词数量
# 神经网络参数
HIDDEN_SIZE = 200 # LSTM隐藏层规模
NUM_LAYERS = 2 # LSTM结构层数
LEARNING_RATE = 1.0 # 学习速率
KEEP_PROB = 0.5 # 节点不被dropout的概率
MAX_GRAD_NORM = 5 # 用于控制梯度膨胀的参数
# 训练参数
TRAIN_BATCH_SIZE = 20 # 训练数据batch大小
TRAIN_NUM_STEP = 35 # 训练数据截断长度
# 测试参数
EVAL_BATCH_SIZE = 1 # 测试数据batch大小
EVAL_NUM_STEP = 1 # 测试数据截断
NUM_EPOCH = 2 # 使用训练数据的轮数
# 通过PTBModel描述模型,方便维护循环神经网络中的状态
class PTBModel():
def __init__(self, is_training, batch_size, num_steps):
# 记录batch和截断长度
self.batch_size = batch_size
self.num_steps = num_steps
# 定义输入层
self.input_data = tf.placeholder(tf.int32, [batch_size, num_steps])
# 定义预期输出
self.targets = tf.placeholder(tf.int32, [batch_size, num_steps])
# 定义LSTM为使用dropout的两层网络
lstm_cell = tf.contrib.rnn.BasicLSTMCell(HIDDEN_SIZE)
if is_training:
lstm_cell = tf.contrib.rnn.DropoutWrapper(
lstm_cell, output_keep_prob=KEEP_PROB)
cell = tf.contrib.rnn.MultiRNNCell([lstm_cell] * NUM_LAYERS)
# 初始化state
self.initial_state = cell.zero_state(batch_size, tf.float32)
# 将单词ID转为单词向量。每个单词都是HIDDEN_SIZE维
embedding = tf.get_variable('embedding', [VOCAB_SIZE, HIDDEN_SIZE])
# 将原本batch_size*num_steps的输入层转化为batch_size*num_steps*HIDDEN_SIZE
inputs = tf.nn.embedding_lookup(embedding, self.input_data)
# 只在训练时使用dropout
if is_training:
inputs = tf.nn.dropout(inputs, KEEP_PROB)
# 定义输出列表
outputs = []
state = self.initial_state
with tf.variable_scope('RNN'):
for time_step in range(num_steps):
if time_step > 0:
tf.get_variable_scope().reuse_variables()
cell_output, state = cell(inputs[:, time_step, :],
state) # 将当前时刻的数据和状态传入LSTM
outputs.append(cell_output) # 将当前输出加入输出列表
# 将输出列表展开成[batch,hidden_size*num_steps]
# 再reshape成[batch*num_steps,hidden_size]
output = tf.reshape(tf.concat(outputs, 1), [-1, HIDDEN_SIZE])
# 将输出传入全连接层,每个时刻的输出都是长度为VOCAB_SIZE的数组
weight = tf.get_variable('weight', [HIDDEN_SIZE, VOCAB_SIZE])
bias = tf.get_variable('bias', [VOCAB_SIZE]