一、写在前面的话
这篇论文主要探索了深度学习在答案选择任务的应用,本质上是做了文本相似的任务。该论文提出了好几个模型,baseline是使用双向LSTM模型对问题和答案的嵌入进行编码,并用余弦相似性度量它们的紧密性。在baseline的基础上,从两个方向进行了改进,一种是将卷积神经网络与基本框架相结合,定义一种更为复合的问题和答案表示,另一种是利用一种简单而有效的注意机制,根据问题的上下文生成答案表示。
二、论文模型
2.1 Basic Model: QA-LSTM
整体结构较为简单,question和answer通过双向LSTM进行编码(共享权值结构),再分别经过mean pooling或max pooling后使用余弦相似性度量相关性,计算hinge loss:
2.2 QA-LSTM/CNN
基于Basic Model加入了CNN对经过双向LSTM编码后的特征进行进一步的编码,最后使用max-k pooling得到向量表征。
2.3 ATTENTION-BASED QA-LSTM
将question经过双向LSTM和pooling之后的向量表征引入answer的双向LSTM的计算中,引入的方式采用attention:
三、实验结果
可以看到引入CNN和attention都对该任务有不错的提高,CNN通过对双向LSTM编码后的特征进行再次地提取,得到更丰富的特征表示,attention则是将question信息引入answer表征过程中,使用answer能得到更合理的表征。这里个人考虑如果将answer信息引入question是否有助于整体任务的提高,另外,引入self-attention是否能比CNN的特征提取取得更好的效果。
四、代码实验
在GitHub上找了一圈,最后选用了https://github.com/Chiang97912/QA_LSTM_ATTENTION的代码学习,比较遗憾的是没有测试代码。实验数据使用的是insuranceQA数据集,代码中已经提供了必要的词向量和代码处理。
# 整体结构
with tf.name_scope("embedding_layer"):
# Map the word index to the word embedding
embeddings = tf.Variable(tf.to_float(self.embeddings), trainable=True, name="W")
q_embed = tf.nn.embedding_lookup(embeddings, self.q)
ap_embed = tf.nn.embedding_lookup(embeddings, self.ap)
an_embed = tf.nn.embedding_lookup(embeddings, self.an)
with tf.variable_scope("bilstm", reuse=tf.AUTO_REUSE):
q_lstm = self.biLSTMCell(q_embed, self.rnn_size)
ap_lstm = self.biLSTMCell(ap_embed, self.rnn_size)
an_lstm = self.biLSTMCell(an_embed, self.rnn_size)
with tf.variable_scope("attention_wrapper", reuse=tf.AUTO_REUSE):
qp_atted, ap_atted = self.attention_wrapper(q_lstm, ap_lstm)
qn_atted, an_atted = self.attention_wrapper(q_lstm, an_lstm)
self.poscosine = self.calc_cosine(qp_atted, ap_atted)
self.negcosine = self.calc_cosine(qn_atted, an_atted)
self.loss, self.acc = self.calc_loss_and_acc(self.poscosine, self.negcosine)
self.train_op = tf.train.AdamOptimizer(self.lr).minimize(self.loss)
# attention部分
def attention_wrapper(self, input_q, input_a):
# h_q = int(input_q.get_shape()[1]) # length of question
w = int(input_q.get_shape()[2]) # length of input for one step
h_a = int(input_a.get_shape()[1]) # length of answer
output_q = self.max_pooling(input_q) # (b,w)
reshape_q = tf.expand_dims(output_q, 1) # (b,1,w) b:batch size
reshape_q = tf.tile(reshape_q, [1, h_a, 1]) # (b,h_a,w)
reshape_q = tf.reshape(reshape_q, [-1, w]) # (b*h_a, w)
reshape_a = tf.reshape(input_a, [-1, w]) # (b*h_a,w)
Wam = tf.get_variable(initializer=tf.truncated_normal([2 * self.rnn_size, self.attention_matrix_size], stddev=0.1), name='Wam')
Wqm = tf.get_variable(initializer=tf.truncated_normal([2 * self.rnn_size, self.attention_matrix_size], stddev=0.1), name='Wqm')
Wms = tf.get_variable(initializer=tf.truncated_normal([self.attention_matrix_size, 1], stddev=0.1), name='Wms')
M = tf.tanh(tf.add(tf.matmul(reshape_q, Wqm), tf.matmul(reshape_a, Wam)))
M = tf.matmul(M, Wms) # (b*h_a,1)
S = tf.reshape(M, [-1, h_a]) # (b,h_a)
S = tf.nn.softmax(S) # (b,h_a)
S_diag = tf.matrix_diag(S) # (b,h_a,h_a)
attention_a = tf.matmul(S_diag, input_a) # (b,h_a,w)
output_a = self.max_pooling(attention_a) # (b,w)
return tf.tanh(output_q), tf.tanh(output_a)
# 损失部分
def calc_loss_and_acc(self, poscosine, negcosine):
# the target function
zero = tf.fill(tf.shape(poscosine), 0.0)
margin = tf.fill(tf.shape(poscosine), self.margin)
with tf.name_scope("loss"):
losses = tf.maximum(zero, tf.subtract(margin, tf.subtract(poscosine, negcosine)))
loss = tf.reduce_sum(losses)