Github下载完整代码
https://github.com/rockingdingo/deepnlp/tree/master/deepnlp/pos
这篇文章中我们将基于Tensorflow的LSTM模型来实现序列化标注的任务,以NLP中的POS词性标注为例实现一个深度学习的POS Tagger。文中具体介绍如何基于Tensorflow的LSTM cell单元来构建多层LSTM、双向Bi-LSTM模型,以及模型的训练和预测过程。对LSTM模型的基本结构和算法不熟悉的可以参考拓展阅读里的一些资料。 完整版代码可以在Github上找到:https://github.com/rockingdingo/deepnlp/tree/master/deepnlp/pos
我们使用的词性标注POS的训练集来源是url [人民日报1998年的新闻语料],格式为”充满/v 希望/n 的/u 新/a 世纪/n ——/w 一九九八年/t”。具体的预处理过程包含以下步骤:
我们首先定义一个POSTagger类,通过初始化函数init,根据超参数构建tensorflow的一个graph模型。 所有LSTM模型的超参数保存在config这个类中,传入init函数。
class LargeConfigChinese(object):
"""Large config."""
init_scale = 0.04
learning_rate = 0.5
max_grad_norm = 10
num_layers = 2
num_steps = 30
hidden_size = 128
max_epoch = 5
max_max_epoch = 55
keep_prob = 1.0
lr_decay = 1 / 1.15
batch_size = 1 # single sample batch
vocab_size = 50000
target_num = 44 # POS tagging for Chinese
# 定义类和初始化函数init
class POSTagger(object):
"""The pos tagger model"""
def __init__(self, is_training, config):
self.batch_size = batch_size = config.batch_size
self.num_steps = num_steps = config.num_steps
size = config.hidden_size
vocab_size = config.vocab_size
target_num = config.target_num # target output number
# define model
# To Do
self._input_data = tf.placeholder(tf.int32, [batch_size, num_steps])
self._targets = tf.placeholder(tf.int32, [batch_size, num_steps])
现在我们将输入到占位符_input_data和_targets的ID数据,转化为对应的词向量。 在Tensorflow定义了简单方法:首先随机生成一个embedding矩阵,形状为[vocab_size, size],即词典大小vocab_size 乘以定义的词向量的维度 size。 然后利用tf.nn.embedding_lookup() 方法来查找每个ID对应的向量。这个过程就是将长度为vocab_size的One-Hot输入向量Xi转化为一个固定长度size的词向量。 在后向传播过程,词向量也同时得到训练。
代码3embedding = tf.get_variable("embedding", [vocab_size, size], dtype=data_type())
inputs = tf.nn.embedding_lookup(embedding, self._input_data)
基本LSTM单元:通过tf.nn.rnn_cell.BasicLSTMCell() 函数构建,size为LSTM的每层节点个数,forget_bias为偏移量,state_is_tuple=True为内部实现的一种结构,在tensorflow 0.10.0 后的版本为了提升计算速度已经建议均设置为TRUE,FALSE版本会被去除掉。
Dropout Wrapper层: 通过tf.nn.rnn_cell.DropoutWrapper() 函数可以在LSTM层加上Dropout避免训练过程的过拟合,通过设置 output_keep_prob 的概率来调节;
多层LSTM的cell单元:通过函数tf.nn.rnn_cell.MultiRNNCell() 构建,层数的参数是config.num_layers,第一层LSTM的输出会作为下一层LSTM的输入,将多层LSTM叠加在一起获得更好的模型Capacity;
代码4-1lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(size, forget_bias=0.0, state_is_tuple=True)
if is_training and config.keep_prob < 1:
lstm_cell = tf.nn.rnn_cell.DropoutWrapper(
lstm_cell, output_keep_prob=config.keep_prob)
cell = tf.nn.rnn_cell.MultiRNNCell([lstm_cell] * config.num_layers, state_is_tuple=True)
lstm_fw_cell = tf.nn.rnn_cell.BasicLSTMCell(size, forget_bias=0.0, state_is_tuple=True)
lstm_bw_cell = tf.nn.rnn_cell.BasicLSTMCell(size, forget_bias=0.0, state_is_tuple=True)
cell_fw = tf.nn.rnn_cell.MultiRNNCell([lstm_fw_cell] * num_layers, state_is_tuple=True)
cell_bw = tf.nn.rnn_cell.MultiRNNCell([lstm_bw_cell] * num_layers, state_is_tuple=True)
initial_state_fw = cell_fw.zero_state(batch_size, data_type())
initial_state_bw = cell_bw.zero_state(batch_size, data_type())
# Split to get a list of 'n_steps' tensors of shape (batch_size, n_input)
inputs_list = [tf.squeeze(s, squeeze_dims=[1]) for s in tf.split(1, num_steps, inputs)]
with tf.variable_scope("pos_bilstm"):
outputs, state_fw, state_bw = tf.nn.bidirectional_rnn(
cell_fw, cell_bw, inputs_list, initial_state_fw = initial_state_fw,
initial_state_bw = initial_state_bw)
LSTM模型每次读取当前步t的输入Xt 和上一步的隐含层的向量h(t-1),通过LSTM内部结构的一系列计算得到相应的输出。定义前向过程,通过for循环,每次输入一个步t对应的词向量 inputs[:, time_step, :],是一个3D的Tensor [batch_size, time_step, size] 。其中size为词向量的维度。之后会将每一步的结果添加到outputs这个list中。
最后的全连接层:将output这个向量乘以softmax_w再加上偏移softmax_b,得到输出部分的logits,最后利用tf.nn.sparse_softmax_cross_entropy_with_logits 比较真实值的向量_targets和预测值的向量 logits,计算交叉熵cross-entropy的损失函数loss;
代码5state = self._initial_state
with tf.variable_scope("pos_lstm"):
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)
outputs.append(cell_output)
output = tf.reshape(tf.concat(1, outputs), [-1, size])
softmax_w = tf.get_variable(
"softmax_w", [size, target_num], dtype=data_type())
softmax_b = tf.get_variable("softmax_b", [target_num], dtype=data_type())
logits = tf.matmul(output, softmax_w) + softmax_b
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits, tf.reshape(targets, [-1]))
Tensorflow中定义损失函数有:tf.nn.sparse_softmax_cross_entropy_with_logits() 和 tf.nn.softmax_cross_entropy_with_logits()。 另外还有一个函数tf.nn.seq2seq.sequence_loss_by_example()接收参数和sparse_softmax_cross_entropy_with_logits类似。 二者输出结果一致,区别在于接收的输入不同:
"sparse_softmax"这个函数输入参数labels表示待拟合的标签,形状为 [batch_size] ,每个值为一个整形数值,int32或者int64, 代表了待预测标签的ID,即每个样本的标签有且仅有一个。
"softmax" 这个函数输入参数labels形状为[batch_size, num_classes],每个元素类型为float32或者 float64。每个样本对应的标签向量可以是One-Hot表示,即每个样本只属于一个类别;同时也可以是对应多个标签soft softmax,即每个样本的label不仅限于一个,而是给出符合每个类别的概率分布。这个函数支持一个样本在多个类别下都有分布情况。
session.run() 函数每次将feed_dict的数据输入Graph模型,计算后返回fetches列表中定义的几个变量[cost, state, _ ]。_ 代表了评估的operator。
代码6def run_epoch(session, model, word_data, tag_data, eval_op, verbose=False):
"""Runs the model on the given data."""
epoch_size = ((len(word_data) // model.batch_size) - 1) // model.num_steps
start_time = time.time()
costs = 0.0
iters = 0
state = session.run(model.initial_state)
for step, (x, y) in enumerate(reader.iterator(word_data, tag_data, model.batch_size,
model.num_steps)):
fetches = [model.cost, model.final_state, eval_op]
feed_dict = {}
feed_dict[model.input_data] = x
feed_dict[model.targets] = y
for i, (c, h) in enumerate(model.initial_state):
feed_dict[c] = state[i].c
feed_dict[h] = state[i].h
cost, state, _ = session.run(fetches, feed_dict)
costs += cost
iters += model.num_steps
if verbose and step % (epoch_size // 10) == 10:
print("%.3f perplexity: %.3f speed: %.0f wps" %
(step * 1.0 / epoch_size, np.exp(costs / iters),
iters * model.batch_size / (time.time() - start_time)))
# Save Model to CheckPoint when is_training is True
if model.is_training:
if step % (epoch_size // 10) == 10:
checkpoint_path = os.path.join(FLAGS.pos_train_dir, "pos.ckpt")
model.saver.save(session, checkpoint_path)
print("Model Saved... at time step " + str(step))
return np.exp(costs / iters)