一、写在前面的话
这篇论文发表于ACL2016,在SemEval 2010 Task 8 数据集上达到了88%的F1-Score,为目前最高,不过貌似还未有人可以复现。论文使用两层的Attention尽可能应用关系分类任务所特有的信息,损失函数在原有的基础上提出了新的margin function,另外在输入中加入了trigram信息。不过,引入两层Attention导致训练似乎非常困难,加上没有人复现,所以后面文章的对比基本上忽略了这篇文章。
二、论文笔记
1. 论文整体架构
该模型结构主要包含了输入的Attention、卷积层、池化的Attention以及最后的margin function。
2. 输入部分
输入主要分为3部分:
- word embedding
- position embedding
- trigram信息
前两个没什么好说的,就是前两篇笔记里提到的操作,trigram信息是指设置一个滑动窗口k,以每个word 为中心,左右k/2个词作为上下文,将它们拼接起来(这里拼接的不止是词向量,也包括位置向量)。简单来说,其实就是在输入加入了lexical level features,只是一般lexical level features是等深度学习这边操作完成输出句子特征之后拼接这些特征,就像《Relation Classification via Convolutional Deep Neural Network》中的操作一样。
简单来说,现在原来一个词的向量维度变成了:一个词上下文的数量k * (词向量维度 + 位置向量维度*2)
3. 输入的Attention
输入的Attention主要是衡量我们的词与两个实体直接的关系,论文中是引入了两个对角矩阵来分别记录每个词和两个实体之间的attention(不知道这个对角矩阵的意义是什么,其实直接用向量计算似乎就可以了,后面有待研究),计算公式如下,其中,是实体向量,是词向量:
每个词都有对两个entity的权重,论文中提出了如下三种方式融合:
4. 卷积层
与其他文章的卷积大致相同:
5. 池化的Attention
这一层Attention其实就是求卷积的输出和relation labels的关系,首先这需要将relation labels也做一个嵌入 ,然后使用如下公式获得Attention:
在这个基础上我们再去做pooling:
6. 损失函数
这个损失函数其实建立在relation labels嵌入的基础上,可以看到,这个损失使用了网络最后的输出和relation labels嵌入做distance,最后加上了一个margin,下图中设置margin为1:
7. 实验结果
基本上目前是最优秀的结果,哪怕当前加入了bert之后的最新论文也就达到87%多,但基本没人复现是坑。另外,文章中也详细地做了消去实验,实验也算完备。
三、总结
尽管这篇论文存在不能完整复现的问题,但文章中多层Attention的想法其实是很值得考虑得,从几个维度考虑Attention或者在不同阶段加入Attention都是不错的一个技巧,另外关于损失函数,该论文使用了embedding的逼近来作为距离函数,也有较好地提升,能否在其他任务里加入这样的技巧,也是值得思考的。整体来说,虽然坑很多,但思路很好,值得阅读和尝试复现。
四、代码
运行了https://github.com/FrankWork/acnn,在运行代码上没什么坑,但是效果并不怎么样,下次会重新改到自己的代码上:
输入的Attention实现:
def _input_attention(self, x, e1, e2, initializer=None):
bz = self.config.batch_size
n = self.config.max_len
with tf.name_scope('input_attention'):
A1 = tf.matmul(x, tf.expand_dims(e1, -1))# bz, n, 1
A2 = tf.matmul(x, tf.expand_dims(e2, -1))
A1 = tf.reshape(A1, [bz, n])
A2 = tf.reshape(A2, [bz, n])
alpha1 = tf.nn.softmax(A1)# bz, n
alpha2 = tf.nn.softmax(A2)# bz, n
alpha = (alpha1 + alpha2)/2
return alpha
配置如下:
import tensorflow as tf
# Basics
# tf.app.flags.DEFINE_boolean("debug", True,
# "run in the debug mode.")
tf.app.flags.DEFINE_boolean("test_only", False,
"no need to run training process.")
# Data files
tf.app.flags.DEFINE_string("data_path", "data/", "Data directory")
tf.app.flags.DEFINE_string("embedding_file", "embedding/senna/embeddings.txt",
"embedding file")
tf.app.flags.DEFINE_string("embedding_vocab", "embedding/senna/words.lst",
"embedding vocab file")
tf.app.flags.DEFINE_string("train_file", "train.txt", "training file")
tf.app.flags.DEFINE_string("test_file", "test.txt", "Test file")
# tf.app.flags.DEFINE_string("log_file", 'run/log.txt', "Log file")
# tf.app.flags.DEFINE_string("save_path", 'run/', "save model here")
tf.app.flags.DEFINE_string("log_file", None, "Log file")
tf.app.flags.DEFINE_string("save_path", None, "save model here")
tf.app.flags.DEFINE_string("embedding_size",'50' , "pad word")
tf.app.flags.DEFINE_string("max_len",'30' , "pad word")
# Model details
tf.app.flags.DEFINE_integer("pos_embed_num", 123, "position embedding number")
tf.app.flags.DEFINE_integer("pos_embed_size", 5, "position embedding size")
tf.app.flags.DEFINE_integer("slide_window", 3, "Slide window size")
tf.app.flags.DEFINE_integer("num_filters", 100,
"How many features a convolution op have to output")
tf.app.flags.DEFINE_integer("classnum", 19, "Number of relations")
# Optimization details
tf.app.flags.DEFINE_integer("batch_size", 100, "Batch size")
tf.app.flags.DEFINE_integer("num_epoches", 100, "Number of epoches")
tf.app.flags.DEFINE_float("keep_prob", 1.0, "Dropout keep prob.")
tf.app.flags.DEFINE_float("learning_rate", 1e-3, "Learning rate.")
tf.app.flags.DEFINE_float("l2_reg_lambda", 1, "regularization parameter")
# tf.app.flags.DEFINE_float("learning_rate2", 1e-3, "learning_rate for regularization")
tf.app.flags.DEFINE_float("margin", 1, "margin based loss function")
tf.app.flags.DEFINE_float("grad_clipping", 10., "Gradient clipping.")
FLAGS = tf.app.flags.FLAGS