1. Attention
H是一个矩阵,它是由LSTM层产生的多个向量[h1,h2,…,hT]组成的。其中T是句子的长度。句子的表示r是输出向量的权重之和。
M=tan(H) α=softmax(WTM) r=HαT
output_h = tf.add(output_forward, output_backward) # total_num, time_steps,gru_size
output_h的维度为[total_num, time_steps,gru_size],其中total_num指的是batch_size,time_steps指的是length of sentence。
attention_w = tf.get_variable(‘attention_omega’, [gru_size, 1]) #attention_w本质上是每个GRU单元不同的权重,也就是对每个单词赋予不同的权重。
- t1 = tf.tanh(output_h) #[total_num, time_steps,gru_size]
- t2 = t1.reshape(t1, [total_numtime_steps, gru_size]) #total_numtime_steps, gru_size
- t3 = tf.matmul(t2, attention_w)#total_num*time_steps,1
- t4 = tf.reshape(t3, [total_num, time_steps]) #total_num * time_steps
- t5 = tf.nn.softmax(t4) # softmax是针对每个词来说的, total_num * time_steps
- t6 = tf.reshape(t5, [total_num, 1, time_steps])
- t7 = tf.matmul(t6, output_h) # total_num * 1 * gru_size,其中三维张量的乘法,第一维不变(第一维比较相同),后两维进行二维矩阵的乘法。
- r = tf.reshape(t7, [total_num, gru_size])
- result = tf.tanh( r )
另外的实现代码如下所示:
def attention(inputs):
# Trainable parameters
# B*T*H B*T*1
hidden_size = inputs.shape[2].value
u_omega = tf.get_variable("u_omega", [hidden_size], initializer=tf.keras.initializers.glorot_normal())
with tf.name_scope('v'):
v = tf.tanh(inputs)
# For each of the timestamps its vector of size A from `v` is reduced with `u` vector
vu = tf.tensordot(v, u_omega, axes=1, name='vu') # (B,T) shape
alphas = tf.nn.softmax(vu, name='alphas') # (B,T) shape
# Output of (Bi-)RNN is reduced with attention vector; the result has (B,D) shape
output = tf.reduce_sum(inputs * tf.expand_dims(alphas, -1), 1)
# Final output with tanh
output = tf.tanh(output)
return output, alphas
2. BLSTM
class AttLSTM:
def __init__(self, sequence_length, num_classes, vocab_size, embedding_size,
hidden_size, l2_reg_lambda=0.0):
# Placeholders for input, output and dropout
self.input_text = tf.placeholder(tf.int32, shape=[None, sequence_length], name='input_text')
self.input_y = tf.placeholder(tf.float32, shape=[None, num_classes], name='input_y')
self.emb_dropout_keep_prob = tf.placeholder(tf.float32, name='emb_dropout_keep_prob')
self.rnn_dropout_keep_prob = tf.placeholder(tf.float32, name='rnn_dropout_keep_prob')
self.dropout_keep_prob = tf.placeholder(tf.float32, name='dropout_keep_prob')
initializer = tf.keras.initializers.glorot_normal
# Word Embedding Layer
with tf.device('/cpu:0'), tf.variable_scope("word-embeddings"):
self.W_text = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -0.25, 0.25), name="W_text")
self.embedded_chars = tf.nn.embedding_lookup(self.W_text, self.input_text)
# Dropout for Word Embedding
with tf.variable_scope('dropout-embeddings'):
self.embedded_chars = tf.nn.dropout(self.embedded_chars, self.emb_dropout_keep_prob)
# Bidirectional LSTM
with tf.variable_scope("bi-lstm"):
_fw_cell = tf.nn.rnn_cell.LSTMCell(hidden_size, initializer=initializer())
fw_cell = tf.nn.rnn_cell.DropoutWrapper(_fw_cell, self.rnn_dropout_keep_prob)
_bw_cell = tf.nn.rnn_cell.LSTMCell(hidden_size, initializer=initializer())
bw_cell = tf.nn.rnn_cell.DropoutWrapper(_bw_cell, self.rnn_dropout_keep_prob)
self.rnn_outputs, _ = tf.nn.bidirectional_dynamic_rnn(cell_fw=fw_cell,
cell_bw=bw_cell,
inputs=self.embedded_chars,
sequence_length=self._length(self.input_text),
dtype=tf.float32)
self.rnn_outputs = tf.add(self.rnn_outputs[0], self.rnn_outputs[1])
# Attention
with tf.variable_scope('attention'):
self.attn, self.alphas = attention(self.rnn_outputs)
# Dropout
with tf.variable_scope('dropout'):
self.h_drop = tf.nn.dropout(self.attn, self.dropout_keep_prob)
# Fully connected layer
with tf.variable_scope('output'):
self.logits = tf.layers.dense(self.h_drop, num_classes, kernel_initializer=initializer())
self.predictions = tf.argmax(self.logits, 1, name="predictions")
# Calculate mean cross-entropy loss
with tf.variable_scope("loss"):
losses = tf.nn.softmax_cross_entropy_with_logits_v2(logits=self.logits, labels=self.input_y)
self.l2 = tf.add_n([tf.nn.l2_loss(v) for v in tf.trainable_variables()])
self.loss = tf.reduce_mean(losses) + l2_reg_lambda * self.l2
# Accuracy
with tf.variable_scope("accuracy"):
correct_predictions = tf.equal(self.predictions, tf.argmax(self.input_y, 1))
self.accuracy = tf.reduce_mean(tf.cast(correct_predictions, tf.float32), name="accuracy")
# Length of the sequence data
@staticmethod
def _length(seq):
relevant = tf.sign(tf.abs(seq))
length = tf.reduce_sum(relevant, reduction_indices=1)
length = tf.cast(length, tf.int32)
return length
3. 训练模型
def train():
with tf.device('/cpu:0'):
x_text, y = data_helpers.load_data_and_labels(FLAGS.train_path)
# Build vocabulary
# Example: x_text[3] = "A misty <e1>ridge</e1> uprises from the <e2>surge</e2>."
# ['a misty ridge uprises from the surge <UNK> <UNK> ... <UNK>']
# =>
# [27 39 40 41 42 1 43 0 0 ... 0]
# dimension = FLAGS.max_sentence_length
vocab_processor = tf.contrib.learn.preprocessing.VocabularyProcessor(FLAGS.max_sentence_length)
x = np.array(list(vocab_processor.fit_transform(x_text)))
print("Text Vocabulary Size: {:d}".format(len(vocab_processor.vocabulary_)))
print("x = {0}".format(x.shape))
print("y = {0}".format(y.shape))
print("")
# Randomly shuffle data to split into train and test(dev)
np.random.seed(10)
shuffle_indices = np.random.permutation(np.arange(len(y)))
x_shuffled = x[shuffle_indices]
y_shuffled = y[shuffle_indices]
# Split train/test set
# TODO: This is very crude, should use cross-validation
dev_sample_index = -1 * int(FLAGS.dev_sample_percentage * float(len(y)))
x_train, x_dev = x_shuffled[:dev_sample_index], x_shuffled[dev_sample_index:]
y_train, y_dev = y_shuffled[:dev_sample_index], y_shuffled[dev_sample_index:]
print("Train/Dev split: {:d}/{:d}\n".format(len(y_train), len(y_dev)))
with tf.Graph().as_default():
session_conf = tf.ConfigProto(
allow_soft_placement=FLAGS.allow_soft_placement,
log_device_placement=FLAGS.log_device_placement)
session_conf.gpu_options.allow_growth = FLAGS.gpu_allow_growth
sess = tf.Session(config=session_conf)
with sess.as_default():
model = AttLSTM(
sequence_length=x_train.shape[1],
num_classes=y_train.shape[1],
vocab_size=len(vocab_processor.vocabulary_),
embedding_size=FLAGS.embedding_dim,
hidden_size=FLAGS.hidden_size,
l2_reg_lambda=FLAGS.l2_reg_lambda)
# Define Training procedure
global_step = tf.Variable(0, name="global_step", trainable=False)
optimizer = tf.train.AdadeltaOptimizer(FLAGS.learning_rate, FLAGS.decay_rate, 1e-6)
gvs = optimizer.compute_gradients(model.loss)
capped_gvs = [(tf.clip_by_value(grad, -1.0, 1.0), var) for grad, var in gvs]
train_op = optimizer.apply_gradients(capped_gvs, global_step=global_step)
# Output directory for models and summaries
timestamp = str(int(time.time()))
out_dir = os.path.abspath(os.path.join(os.path.curdir, "runs", timestamp))
print("Writing to {}\n".format(out_dir))
# Summaries for loss and accuracy
loss_summary = tf.summary.scalar("loss", model.loss)
acc_summary = tf.summary.scalar("accuracy", model.accuracy)
# Train Summaries
train_summary_op = tf.summary.merge([loss_summary, acc_summary])
train_summary_dir = os.path.join(out_dir, "summaries", "train")
train_summary_writer = tf.summary.FileWriter(train_summary_dir, sess.graph)
# Dev summaries
dev_summary_op = tf.summary.merge([loss_summary, acc_summary])
dev_summary_dir = os.path.join(out_dir, "summaries", "dev")
dev_summary_writer = tf.summary.FileWriter(dev_summary_dir, sess.graph)
# Checkpoint directory. Tensorflow assumes this directory already exists so we need to create it
checkpoint_dir = os.path.abspath(os.path.join(out_dir, "checkpoints"))
checkpoint_prefix = os.path.join(checkpoint_dir, "model")
if not os.path.exists(checkpoint_dir):
os.makedirs(checkpoint_dir)
saver = tf.train.Saver(tf.global_variables(), max_to_keep=FLAGS.num_checkpoints)
# Write vocabulary
vocab_processor.save(os.path.join(out_dir, "vocab"))
# Initialize all variables
sess.run(tf.global_variables_initializer())
# Pre-trained word2vec
if FLAGS.embedding_path:
pretrain_W = utils.load_glove(FLAGS.embedding_path, FLAGS.embedding_dim, vocab_processor)
sess.run(model.W_text.assign(pretrain_W))
print("Success to load pre-trained word2vec model!\n")
# Generate batches
batches = data_helpers.batch_iter(list(zip(x_train, y_train)), FLAGS.batch_size, FLAGS.num_epochs)
# Training loop. For each batch...
best_f1 = 0.0 # For save checkpoint(model)
for batch in batches:
x_batch, y_batch = zip(*batch)
# Train
feed_dict = {
model.input_text: x_batch,
model.input_y: y_batch,
model.emb_dropout_keep_prob: FLAGS.emb_dropout_keep_prob,
model.rnn_dropout_keep_prob: FLAGS.rnn_dropout_keep_prob,
model.dropout_keep_prob: FLAGS.dropout_keep_prob
}
_, step, summaries, loss, accuracy = sess.run(
[train_op, global_step, train_summary_op, model.loss, model.accuracy], feed_dict)
train_summary_writer.add_summary(summaries, step)
# Training log display
if step % FLAGS.display_every == 0:
time_str = datetime.datetime.now().isoformat()
print("{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))
# Evaluation
if step % FLAGS.evaluate_every == 0:
print("\nEvaluation:")
feed_dict = {
model.input_text: x_dev,
model.input_y: y_dev,
model.emb_dropout_keep_prob: 1.0,
model.rnn_dropout_keep_prob: 1.0,
model.dropout_keep_prob: 1.0
}
summaries, loss, accuracy, predictions = sess.run(
[dev_summary_op, model.loss, model.accuracy, model.predictions], feed_dict)
dev_summary_writer.add_summary(summaries, step)
time_str = datetime.datetime.now().isoformat()
f1 = f1_score(np.argmax(y_dev, axis=1), predictions, labels=np.array(range(1, 19)), average="macro")
print("{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))
print("[UNOFFICIAL] (2*9+1)-Way Macro-Average F1 Score (excluding Other): {:g}\n".format(f1))
# Model checkpoint
if best_f1 < f1:
best_f1 = f1
path = saver.save(sess, checkpoint_prefix + "-{:.3g}".format(best_f1), global_step=step)
print("Saved model checkpoint to {}\n".format(path))
4.验证模型
def test():
with tf.device('/cpu:0'):
x_text, y = data_helpers.load_data_and_labels(FLAGS.test_path)
# Map data into vocabulary
text_path = os.path.join(FLAGS.checkpoint_dir, "..", "vocab")
text_vocab_processor = tf.contrib.learn.preprocessing.VocabularyProcessor.restore(text_path)
x = np.array(list(text_vocab_processor.transform(x_text)))
checkpoint_file = tf.train.latest_checkpoint(FLAGS.checkpoint_dir)
graph = tf.Graph()
with graph.as_default():
session_conf = tf.ConfigProto(
allow_soft_placement=FLAGS.allow_soft_placement,
log_device_placement=FLAGS.log_device_placement)
session_conf.gpu_options.allow_growth = FLAGS.gpu_allow_growth
sess = tf.Session(config=session_conf)
with sess.as_default():
# Load the saved meta graph and restore variables
saver = tf.train.import_meta_graph("{}.meta".format(checkpoint_file))
saver.restore(sess, checkpoint_file)
# Get the placeholders from the graph by name
input_text = graph.get_operation_by_name("input_text").outputs[0]
# input_y = graph.get_operation_by_name("input_y").outputs[0]
emb_dropout_keep_prob = graph.get_operation_by_name("emb_dropout_keep_prob").outputs[0]
rnn_dropout_keep_prob = graph.get_operation_by_name("rnn_dropout_keep_prob").outputs[0]
dropout_keep_prob = graph.get_operation_by_name("dropout_keep_prob").outputs[0]
# Tensors we want to evaluate
predictions = graph.get_operation_by_name("output/predictions").outputs[0]
# Generate batches for one epoch
batches = data_helpers.batch_iter(list(x), FLAGS.batch_size, 1, shuffle=False)
# Collect the predictions here
preds = []
for x_batch in batches:
pred = sess.run(predictions, {input_text: x_batch,
emb_dropout_keep_prob: 1.0,
rnn_dropout_keep_prob: 1.0,
dropout_keep_prob: 1.0})
preds.append(pred)
preds = np.concatenate(preds)
truths = np.argmax(y, axis=1)
prediction_path = os.path.join(FLAGS.checkpoint_dir, "..", "predictions.txt")
truth_path = os.path.join(FLAGS.checkpoint_dir, "..", "ground_truths.txt")
prediction_file = open(prediction_path, 'w')
truth_file = open(truth_path, 'w')
for i in range(len(preds)):
prediction_file.write("{}\t{}\n".format(i, utils.label2class[preds[i]]))
truth_file.write("{}\t{}\n".format(i, utils.label2class[truths[i]]))
prediction_file.close()
truth_file.close()
perl_path = os.path.join(os.path.curdir,
"SemEval2010_task8_all_data",
"SemEval2010_task8_scorer-v1.2",
"semeval2010_task8_scorer-v1.2.pl")
process = subprocess.Popen(["perl", perl_path, prediction_path, truth_path], stdout=subprocess.PIPE)
for line in str(process.communicate()[0].decode("utf-8")).split("\\n"):
print(line)