本次要分享的论文是
好了,老规矩,带着代码看论文。
整体网络结构
任务描述
本篇论文的应用场景是在完形填空的任务上:
其中
任务很简单,其实个人感觉也可以视为一个
模型描述
contextual Embedding
将
然后,将经过
这一步代码如何实现呢?
embedding = tf.get_variable('embedding',
┆ ┆ ┆ [FLAGS.vocab_size, FLAGS.embedding_size],
┆ ┆ ┆ initializer=tf.random_uniform_initializer(minval=-0.05, maxval=0.05))
regularizer = tf.nn.l2_loss(embedding)
doc_emb = tf.nn.dropout(tf.nn.embedding_lookup(embedding, documents), FLAGS.dropout_keep_prob)
doc_emb.set_shape([None, None, FLAGS.embedding_size])
query_emb = tf.nn.dropout(tf.nn.embedding_lookup(embedding, query), FLAGS.dropout_keep_prob)
query_emb.set_shape([None, None, FLAGS.embedding_size])
with tf.variable_scope('document', initializer=orthogonal_initializer()):
fwd_cell = tf.contrib.rnn.GRUCell(FLAGS.hidden_size)
back_cell = tf.contrib.rnn.GRUCell(FLAGS.hidden_size)
doc_len = tf.reduce_sum(doc_mask, reduction_indices=1)
h, _ = tf.nn.bidirectional_dynamic_rnn(
┆ fwd_cell, back_cell, doc_emb, sequence_length=tf.to_int64(doc_len), dtype=tf.float32)
#h_doc = tf.nn.dropout(tf.concat(2, h), FLAGS.dropout_keep_prob)
h_doc = tf.concat(h, 2)
with tf.variable_scope('query', initializer=orthogonal_initializer()):
fwd_cell = tf.contrib.rnn.GRUCell(FLAGS.hidden_size)
back_cell = tf.contrib.rnn.GRUCell(FLAGS.hidden_size)
query_len = tf.reduce_sum(query_mask, reduction_indices=1)
h, _ = tf.nn.bidirectional_dynamic_rnn(
┆ fwd_cell, back_cell, query_emb, sequence_length=tf.to_int64(query_len), dtype=tf.float32)
#h_query = tf.nn.dropout(tf.concat(2, h), FLAGS.dropout_keep_prob)
h_query = tf.concat(h, 2)
pair-wise Matching Score
论文中提到,我们可以根据上面生成的
得到的
实现代码:
M = tf.matmul(h_doc, h_query, adjoint_b=True)
M_mask = tf.to_float(tf.matmul(tf.expand_dims(doc_mask, -1), tf.expand_dims(query_mask, 1)))
Individual Attentions
在上一步中,我们得到一个
Attention-over-Attention
上面我们做了
实现代码:
# Softmax over axis
def softmax(target, axis, mask, epsilon=1e-12, name=None):
with tf.op_scope([target], name, 'softmax'):
max_axis = tf.reduce_max(target, axis, keep_dims=True)
target_exp = tf.exp(target-max_axis) * mask
normalize = tf.reduce_sum(target_exp, axis, keep_dims=True)
softmax = target_exp / (normalize + epsilon)
return softmax
alpha = softmax(M, 1, M_mask)##mask矩阵,非零位置为1,反正为0,axis=0为batch
beta = softmax(M, 2, M_mask)
需要注意的是,我看过一些基于
论文里还对
然后做了矩阵乘积操作:
如何解释这个矩阵操作呢?直观上看,就像把每个
实现代码:
query_importance = tf.expand_dims(tf.reduce_mean(beta, 1) / tf.to_float(tf.expand_dims(doc_len, -1)), -1)
s = tf.squeeze(tf.matmul(alpha, query_importance), [2])
预测部分
上面我们可以得到一个
这部分代码:
unpacked_s = zip(tf.unstack(s, FLAGS.batch_size), tf.unstack(documents, FLAGS.batch_size))
y_hat = tf.stack([tf.unsorted_segment_sum(attentions, sentence_ids, FLAGS.vocab_size) for (attentions, sentence_ids) in unpacked_s])##注意这里面y_hat也就是上面所讲的s向量,但是其经过unsorted_segment_sum操作后,其长度变为vocab_size.
那在
实现代码:
下面代码中的一波操作不太好理解,其在
index = tf.range(0, FLAGS.batch_size) * FLAGS.vocab_size + tf.to_int32(answer)##这里面为啥乘以vocab_size,看下面解释
flat = tf.reshape(y_hat, [-1])## 注意每个样本的y_hat长度为vocab_size,直接将batch_size个flat reshape成一维。
relevant = tf.gather(flat, index)##以index为准,找到flat中对应的值,也就是answer中的词在s向量中的概率值。
loss = -tf.reduce_mean(tf.log(relevant))
accuracy = tf.reduce_mean(tf.to_float(tf.equal(tf.argmax(y_hat, 1), answer)))
个人感想
好了,这篇论文所介绍的网络结构已经介绍完毕了,来谈谈我个人读完这篇论文和代码后的感想。
- 我看过一些
QA、QG 等方面的论文,感觉大部分都做了类似论文所说的document−level attention 操作,也就是结合query 去attention document , 这篇创新的也做了query−level attention 操作。 - 感觉这篇论文实际上做了两层
attention ,在第一层中不仅做了document−level attention ,也做了query−level attention ,第二层中,把结合query−level attention 的信息对document−level attention 又做了attention 操作。