基于DGCNN和概率图的轻量级信息抽取模型
By 苏剑林 | 2019-06-03 | 402333位读者 |背景:前几个月,百度举办了“2019语言与智能技术竞赛”,其中有三个赛道,而我对其中的“信息抽取”赛道颇感兴趣,于是报名参加。经过两个多月的煎熬,比赛终于结束,并且最终结果已经公布。笔者从最初的对信息抽取的一无所知,经过这次比赛的学习和研究,最终探索出在监督学习下做信息抽取的一些经验,遂在此与大家分享。
笔者在最终的测试集上排名第七,指标F1为0.8807(Precision是0.8939,Recall是0.8679),跟第一名相差0.01左右。从比赛角度这个成绩不算突出,但自认为模型有若干创新之处,比如自行设计的抽取结构、CNN+Attention(所以足够快速)、没有用Bert等预训练模型,私以为这对于信息抽取的学术研究和工程应用都有一定的参考价值。
基本分析 #
信息抽取(Information Extraction, IE)是从自然语言文本中抽取实体、属性、关系及事件等事实类信息的文本处理技术,是信息检索、智能问答、智能对话等人工智能应用的重要基础,一直受到业界的广泛关注。... 本次竞赛将提供业界规模最大的基于schema的中文信息抽取数据集(Schema based Knowledge Extraction, SKE),旨在为研究者提供学术交流平台,进一步提升中文信息抽取技术的研究水平,推动相关人工智能应用的发展。------ 比赛官方网站介绍
任务介绍 #
本次的信息抽取任务,更精确地说是“三元组”抽取任务,示例数据如下:
{
"text": "九玄珠是在纵横中文网连载的一部小说,作者是龙马",
"spo_list": [
["九玄珠", "连载网站", "纵横中文网"],
["九玄珠", "作者", "龙马"]
]
}
就是输入一个句子,然后输出该句子包含的所有三元组。其中三元组是(s, p, o)的形式,它的s是subject,即主实体,为query中的一个片段;而o是object,即客实体,也是query中的一个片段;而p是predicate,即两个实体之间的关系,比赛事先给出了所有的候选predicate列表(schema,一共50个候选predicate)。总的来说,(s, p, o)可以理解的“s的p是o”。
比赛给出了将近20万的标注数据,标注质量也颇高,感谢百度。(请不要问我要数据啊,我不负责分享数据集。据说数据集迟点会在http://ai.baidu.com/broad/download公开发布,到时就可以下载了。)
样本特点 #
很显然,这是一个“一对多”的抽取+分类任务,通过对人工观察样本情况,发现其特点如下:
1、s和o未必是分词工具分出来的词,因此要对query做标注才能抽取出正确的s、o,而考虑到分词可能切错边界,因此应该使用基于字的输入来标注;
2、样本中大多数的抽取结果是“一个s、多个(p, o)”的形式,比如“《战狼》的主演包括吴京和余男”,那么要抽出“(战狼, 主演, 吴京)”、“(战狼, 主演, 余男)”;
3、抽取结果是“多个s、一个(p, o)”甚至是“多个s、多个(p, o)”的样本也占有一定比例,比如“《战狼》、《战狼2》的主演都是吴京”,那么要抽出“(战狼, 主演, 吴京)”、“(战狼2, 主演, 吴京)”;
4、同一对(s, o)也可能对应多个p,比如“《战狼》的主演和导演都是吴京”,那么要抽出“(战狼, 主演, 吴京)”、“(战狼, 导演, 吴京)”;
5、极端情况下,s、o之间是可能重叠的,比如“《鲁迅自传》由江苏文艺出版社出版”,严格上来讲,除了要抽出“(鲁迅自传, 出版社, 江苏文艺出版社)”外,还应该抽取出“(鲁迅自传, 作者, 鲁迅)”。
模型设计 #
在“样本特点”一节我们列举了5点基本的观察结果,其中除了第5点略显极端外,其余4点都是信息抽取任务的常见特点。在正式动手之前,我简单调研了目前主要的信息抽取模型,发现竟然没有一个模型能很好地覆盖这5个特点。所以我放弃了已有的抽取思路,自行设计了一个基于概率图思想的抽取方案,然后从效率出发,利用CNN+Attention完成了这个模型
概率图思想 #
比如,一种比较基准的思路是先进行实体识别,然后对识别出的实体进行关系分类,但这种思路无法很好地处理同一组(s, o)对应多个p的情况,同时会存在采样效率地的问题;另一种思路是作为一个整体的序列标注来搞,参考论文《Joint Extraction of Entities and Relations Based on a Novel Tagging Scheme》,但这种设计不能很好地处理同时有多个s、多个o的情况,需要非常丑陋的“就近原则”;还有“杀鸡用牛刀”地动用强化学习的方法...而无一例外地,这些方法都不能解决s、o有重叠的情况。
信息抽取研究了这么多年,居然连上述几个基本问题都没有解决,在我看来是十分不可思议的。而我自己的原则是:不优雅的设计必须抛弃,所以我决定放弃我了解到的所有抽取思路,自行设计一个抽取方案。为此,我考虑到了类似seq2seq的概率图思路。
做过seq2seq的朋友都知道,解码器实际上在建模
\begin{equation}P(y_1,y_2,\dots,y_n|x)=P(y_1|x)P(y_2|x,y_1)\dots P(y_n|x,y_1,y_2,\dots,y_{n-1})\end{equation}
实际预测的时候,是先通过$x$来预测第一个单词,然后假设第一个单词已知来预测第二个单词,依此递推,直到出现结束标记。那抽取三元组为什么不参考这个思路呢?我们考虑
\begin{equation}P(s, p, o) = P(s) P(o|s)P(p|s,o)\end{equation}
也就是说,我们可以先预测s,然后传入s来预测该s对应的o,然后传入s、o来预测所传入的s、o的关系p,实际应用中,我们还可以把o、p的预测合并为一步,所以总的步骤只需要两步:先预测s,然后传入s来预测该s所对应的o及p。
理论上,上述模型只能抽取单一一个三元组,而为了处理可能由多个s、多个o甚至多个p的情况,我们全部使用“半指针-半标注”结构(说白了,将softmx换成sigmoid,在《基于CNN的阅读理解式问答模型:DGCNN》一文中也介绍过),并且在关系分类的时候也使用sigmoid而不是softmax激活。
经过这样的设计后,最终的模型可以非常简单高效地解码,并且完全覆盖了“样本特点”列举的5个特点。
注1:为什么不先预测o然后再预测s及对应的p?
那是因为引入在第二步预测的时候要采样传入第一步的结果(而且只采样一个),而前面已经分析了,多数样本的o的数目比s的数目要多,所以我们先预测s,然后传入s再预测o、p的时候,对s的采样就很容易充分了(因为s少),反过来如果要对o进行采样就不那么容易充分(因为o可能很多)。
带着这个问题继续读下去,读者会更清楚地认识到这一点。
注2:刷到最近的arxiv论文,发现在思想上,本文的这种抽取设计与文章《Entity-Relation Extraction as Multi-Turn Question Answering》类似。
整体结构 #
至此,我们已经用相当长的篇幅说明了模型的抽取思想,即先识别s,然后传入s来同时识别p和o。现在来介绍本文模型整体结构。
为了保证效率,模型使用了CNN+Attention的结构(外加了一个短序列的LSTM,由于序列很短,所以即使是LSTM也不影响效率),没有用以慢著称的Bert之类的预训练模型。其中CNN沿用了之前介绍过的DGCNN,Attention是用了Google力推的Self Attention,整体结构示意图如下图。
具体来说,模型的处理流程为:
1、输入字id序列,然后通过字词混合Embedding(具体的混合方式后面再介绍)得到对应的字向量序列,然后加上Position Embedding;
2、将得到“字-词-位置 Embedding”输入到12层DGCNN中进行编码,得到编码后的序列(记为$\boldsymbol{H}$);
3、将$\boldsymbol{H}$传入一层Self Attention后,将输出结果与先验特征进行拼接(先验特征可加可不加,构建方式后面再详细介绍);
4、将拼接后的结果传入CNN、Dense,用“半指针-半标注”结构预测s的首、尾位置;
5、训练时随机采样一个标注的s(预测时逐一遍历所有的s),然后将$\boldsymbol{H}$对应此s的子序列传入到一个双向LSTM中,得到s的编码向量,然后加上相对位置的Position Embedding,得到一个与输入序列等长的向量序列;
6、将$\boldsymbol{H}$传入另一层Self Attention后,将输出结果与第5步输出的向量序列、先验特征进行拼接(先验特征可加可不加,构建方式后面再详细介绍);
7、将拼接后的结果传入CNN、Dense,对于每一种p,都构建一个“半指针-半标注”结构来预测对应的o的首、尾位置,这样就同时把o、p都预测出来了。
该模型与我早期开源的一个baseline模型(https://github.com/bojone/kg-2019-baseline)已有较大区别,望读者知悉。
另外,关于读者的可能有的两个疑惑,在此先回答好了。第一是“为什么第5步只采样一个s?”,这个问题的答案很简单,一是因为采样一个就够了(采样多个等效增大batch size),二是因为采样一个比较好操作,我建议不明白的读者好好想明白这一步再来读下去;第二是“为什么要不用Bert?”,这个问题其实很无聊,为什么要问这个问题呢,我不爱用不行吗...具体的原因是我一直以来就对Bert没什么好感,所以我也没怎么琢磨Bert的fine tune,直到前不久我才上手了Bert的fine tune,所以比赛中也就没用了。而且基于Bert的fine tune实在是没有什么意思,效率又低,又体现不了个人的价值,如无必要,实在是不想使用。(关于Bert的做法,在比赛截止前几天也尝试了一下,后面另写文章分享吧。)
模型细节 #
前面我们已经介绍了模型的设计思想与整体结构,现在我们来看模型的实现细节。
字词混合Embedding #
开头部分我们已经说了,为了最大程度上避免边界切分出错,我们应当选择字标注的方式,即以字为基本单位进行输入。不过,单纯的字Embedding难以储存有效的语义信息,换句话说,单个字基本上是没有语义的,更为有效地融入语义信息的方案应该是“字词混合Embedding”。
在本次比赛的模型中,笔者使用了一种自行设计的字词混合方式。首先,我们输入以字为单位的文本序列,经过一个字Embedding层后得到字向量序列;然后将文本分词,通过一个预训练好的Word2Vec模型来提取对应的词向量,为了得到跟字向量对齐的词向量序列,我们可以将每个词的词向量重复“词的字数”那么多次;得到对齐的词向量序列后,我们将词向量序列经过一个矩阵变换到跟字向量一样的维度,并将两者相加。整个过程如下图:
实现上,笔者使用pyhanlp作为分词工具,用1000万条百度百科词条训练了一个Word2Vec模型(Skip Gram + 负采样),而字向量则使用随机初始化的字Embedding层,在模型训练过程中,固定Word2Vec词向量不变,只优化变换矩阵和字向量,从另一个角度看也可以认为是我们是通过字向量和变换矩阵对Word2Vec的词向量进行微调。这样一来,我们既融合了预训练词向量模型所带来的先验语义信息,又保留了字向量的灵活性。
根据笔者自己的目测,相比单纯使用字向量,这种字词混合的方式能提升最终的效果约1%~2%,提升是可观的,并且我在其他任务上也实验过这个方案,均有差不多幅度的提升,证明了这种混合方式的有效性。不同的预训练词向量模型对效果会有一定的影响,但不会特别大(千分之五以下),我也试过直接使用腾讯AI LAB所提供的词向量(但只用了前100万个词),结果差不多。
Position Embedding #
由于主要使用CNN+Attention进行编码,所以编码出的向量序列“位置感”不够强,但就本次比赛的数据而言,位置信息是有一定的价值的,比如s通常出现在句子开头部分,又比如o通常出现在s附近。加入位置信息的一个有效信息是Position Embedding,而不同于之前所介绍的由公式直接计算而来的Position Embedding,本次模型使用了可优化的Position Embedding。
具体做法是设定一个最大长度为512(印象中所有样本的句子长度不超过300),然后全零初始化一个新的Embedding层(维度跟字向量维度一样),传入位置ID后输出对应的Position Embedding,并把这个Position Embedding加到前面的字词混合Embedding中,作为完整的Embedding结果,传入到下述DGCNN编码中。
模型另一处用到了Position Embedding是在编码s的时候,采样得到的s经过BiLSTM进行编码后,得到一个固定大小的向量,然后我们将它复制拼接到原来的编码序列中,作为预测o、p的条件之一。不过考虑到o更可能是s附近的词,所以笔者并非直接统一复制,而是复制同时还加上了当前位置相对于s所谓位置的“相对位置向量”(如果对此描述还感觉模糊,请直接阅读源码),它跟开头的输入共用同一个Embedding层。
DGCNN #
DGCNN在之前的《基于CNN的阅读理解式问答模型:DGCNN》一文已经介绍过,是笔者之前做阅读理解模型时所提出的设计,它其实就是“膨胀门卷积”,其中门卷积的概念来自《Convolutional Sequence to Sequence Learning》,在那论文中被称为GLU(Gated Linear Units),然后笔者通过把普通卷积换成了膨胀卷积来增加感受野。类似的做法出现在论文《Fast Reading Comprehension with ConvNets》。
当输入输出维度一样时,DGCNN可以加上残差,而之前笔者就证明了加上残差之后的DGCNN在数学上等价于Highway形式的膨胀卷积:
\begin{equation}\begin{aligned}\boldsymbol{Y}=&\boldsymbol{X}\otimes \Big(1-\boldsymbol{\sigma}\Big) + \text{Conv1D}_1(\boldsymbol{X}) \otimes \boldsymbol{\sigma}\\
\boldsymbol{\sigma} =& \sigma\Big(\text{Conv1D}_2(\boldsymbol{X})\Big)
\end{aligned}\end{equation}
本次模型都是使用这种形式的DGCNN,它体现了信息的选择性多通道传输。
最后的模型共使用了12层DGCNN,膨胀率依次为$[1, 2, 5, 1, 2, 5, 1, 2, 5, 1, 1, 1]$,即$[1, 2, 5]$重复三次(颗粒度从细到粗反复学习),然后$[1, 1, 1]$(细颗粒度精调)。
远程监督的先验特征 #
本次比赛不允许使用额外的三元组知识库,但是我们可以将训练集里边所有的三元组整合成一个知识库,然后面对一个新句子时,直接从这个知识库中进行远程监督式的搜索,得到这个句子的一些候选三元组。所谓远程监督,就是指如果一个句子的某两个实体刚好是知识库的某个三元组的s和o,那么就把这个三元组抽取出来作为候选三元组。这样一来,只要有一个知识库,那么我们可以用纯粹检索的方法来抽出任意一个句子的候选三元组。不过要注意的是,这仅仅是候选的三元组,而且有可能抽取出来的三元组全是错的。
对于远程监督的结果,笔者的使用方法是:将远程监督的结果作为特征传入到模型中。首先,将所有远程监督得到的s构成一个跟标注结构类似的0/1向量,然后拼接到编码向量序列,然后再进行s的预测;然后将所有远程监督得到的o及对应的p也构成一个跟标注结构类似的0/1向量,拼接到编码向量序列后再进行o、p的预测。具体实现方法请参考开源代码。要提醒的是,在训练的时候,构建远程监督特征时要先排除当前训练样本自身的三元组,即只能借助其他样本的三元组来生成当前样本的远程监督结果,这样才能模拟测试集的使用情况。
效果上,加入远程监督的先验特征后,模型在线下验证集的提升非常可观,达到了2%以上!并且已经反复确认,代码中不存在“使用未来信息”的漏洞,也就是说,这个线下结果是合理的。但是非常遗憾,线上测试集的结果却跟没有加先验特征差不多,也就是几乎没有提升。但是通过观测发现加不加先验特征给出来的结果差异是比较大的,所以最后将两者的结果进行融合了。
其他补充内容 #
在我的代码实现中,还包括了一些额外的辅助模块,包括代码中的变量pn1、pn2、pc、po,这些模块从理论上提供了一些“全局信息”,pn1、pn2可以认为是全局的实体识别模块,而pc可以认为是全局的关系检测模块,po可以认为是全局的关系存在性判断,这些模块都不单独训练,而是直接乘到s、o的预测结果上。
这些模型基本上不会影响最终的效果,但能起到加速训练的作用,而且个人直觉上感觉加上这些模块可能会更合理一些。
此外,还有前面提到随机采样s后,将s对应的向量序列传入到BiLSTM中编码,实现的时候有所差别,是通过s的首尾id均匀插值后取出s的固定数目个(模型选了6个)向量来构成一个定长向量序列传入到BiLSTM中编码,这样做主要是为了避免处理s不定长的问题。
实验炼丹 #
最后,介绍一些模型训练过程中的细节问题。
模型代码:https://github.com/bojone/kg-2019
代码测试环境是Python 2.7 + Keras 2.2.4 + Tensorflow 1.8。
如果本文的模型对你的后续工作有帮助,烦请注明一下(其实也不是太奢望):
@misc{
jianlin2019bdkgf,
title={A Hierarchical Relation Extraction Model with Pointer-Tagging Hybrid Structure},
author={Jianlin Su},
year={2019},
publisher={GitHub},
howpublished={\url{https://github.com/bojone/kg-2019}},
}
基本的训练过程 #
首先是损失函数的选择,由于“半指针-半标注”实际上就是两个二分类,所以损失函数依然用二分类交叉熵。注意s的预测只有两个2分类,而预测o的同时还预测了p,所以o的预测实际上有$100=50\times 2$个2分类,但它们的损失函数依然按照1:1相加。换句话说,按照loss的绝对值来看,o的loss是s的loss的50倍。咋看之下有点反直觉,因为先预测s然后再预测o,似乎s的权重应该更大(因为如果s错了那么预测结果肯定错),但实验结果表明s、o同样重要。此外,诸如focal loss之类的交叉熵变种对最终结果的提升并没有帮助,
模型使用Adam优化器进行训练,先用$10^{-3}$的学习率训练不超过50个epoch,然后加载训练的最优结果,再用$10^{-4}$的学习率继续训练到最优。第一个epoch用来WarmUp,如果不进行WarmUp可能不收敛。
为了保证训练结果稳定提升,模型用到了EMA(Exponential Moving Average),衰减率为0.9999。
解码过程调优 #
模型的解码过程有比较大的调优空间。“半指针-半编码结构”用两个sigmoid激活的标注方式来分别标注实体的首和尾,而为了解码就需要一个阈值,即超过多少阈值就认为此处出现了实体。一般来说二分类的阈值为0.5,但在本次模型中,发现将“首”的阈值设为0.5,将“尾”的阈值设为0.4,解码结果的F1最佳。
此外,这次比较的测试集有一个特点,就是测试集的质量非常高,即抽取结果错漏很少而且很规范。相比之下,训练集的错漏相对多一些,而且规范性可能也没那么严格。所以,我们需要通过后期规则调整预测结果来满足官方给出的一些规范性。在比赛截止日期的前几天,各个队伍的提升都非常明显,我估计大家都是在不断地找规则来修正预测结果,从而提升的效果,笔者个人也是通过一些规则获得了将近1%的提升!不过这些纯粹是比赛的trick,不在学术范围内,不做过多讨论。
模型平均与融合 #
模型的ensemble是提升最终效果的有力方法,针对本次任务,笔者使用了分层ensemble的方案。
前面已经说过,加不加先验特征的线上效果都差不多,但是结果文件差异性比较大,为此,可以将两个模型的结果取并集,而在取并集之前,则通过模型平均的方法提高单一模型的准确性。具体来说,先不加先验特征,然后将所有数据随机打乱并且分成8份,做8折的交叉验证,从而得到8个不加先验特征的模型;然后加上先验特征重做一次,得到另外8个加上先验特征的模型。
得到这16个模型后,将不加先验特征的8个模型进行平均融合(如下图,即将输出的概率进行平均,然后再解码出三元组),再将加了先验特征的8个模型进行平均融合,这样一共得到两份结果文件,由于进行了平均融合,可以认为这两份结果文件的精度都有了保证,最后,将这两份结果文件取并集。
知识蒸馏 #
前面已经说过,测试集可以认为是非常完美的,而训练集却是有一定缺漏和不规范的(当然大部分是好的),因此,我们使用了一种类似知识蒸馏的方式来重新整理训练集,改善训练集质量。
首先,我们使用原始训练集加交叉验证的方式,得到了8个模型(不加远程监督特征),然后用这8个模型对训练集进行预测,得到关于训练集的8份预测结果。如果某个样本的某个三元组同时出现在8份预测结果中但没有出现在训练集的标注中,那么就将这个三元组补充到该样本的标注结果中;如果某个样本的某个三元组在8份预测结果中都没有出现但却被训练集标注了,那么将这个三元组从该样本的标注结果中去掉。
这样一增一减之后,训练集就会完善很多,用这个修正后的训练集重新训练和融合模型,能在线上的测试集上获得将近1%的提升。当然不排除删减这一步会错误地去掉一些困难样本的标注结果,但是也无妨了,知识蒸馏的意思就是集中火力攻克主要矛盾。
提升效率的策略 #
“高效”是本模型的一个显著特点,哪怕是前述的共16个模型进行ensemble,完成最后测试集(共约10万个样本)的预测只花了4个小时(如果服务器的CPU更好些,可以缩短到2小时左右),而在比赛群里讨论知道,最后的测试集预测很多队伍都花了十几个小时以上,有些队伍甚至花了几天几夜...
当然,效率的追求是无止境的,除了硬件上的升级,在工程上很多时候我们还可以牺牲一点效果来换取更高的速度。对于本模型,可以化简某些模块,来换取速度的大幅度提升。比如,可以将12层DGCNN改为膨胀率依次为$[1, 2, 5, 1, 2, 5]$的六层。还有,前面介绍编码s用了BiLSTM,其实也可以直接将s的首位和末位对应的编码向量拼接起来作为最终的编码结果,省去了BiLSTM的计算量。此外,还可以考虑减少Word2Vec模型的词汇量,这也能提高生成词向量序列的速度,如果愿意放弃更多的精度,那么也可以考虑去掉字词混合Embedding,直接单纯使用字Embedding。
经过这样处理,模型的速度可以提升5倍以上,而性能下降大概是2%~4%左右,具体幅度取决于你怎么取舍了。
文章总结 #
文章用比较长的篇幅记录了笔者参加这次比赛的模型和炼丹经历。
在参加比赛之前,笔者对信息抽取乃至知识图谱领域基本上是处于一无所知状态,所以参加这个比赛的时候,完全是凭着自己的直觉来设计模型且调参的(当然也有一部分原因是经过调研后发现主流的信息抽取模型都不能让我满意)。所以,最终交出的这份答卷和获得的成绩,是我比较满意的结果了。
当然,本文的模型有不少“闭门造车”之处,如果读者觉得有什么不妥的地方,欢迎大力吐槽。即便是在这个比赛之后,在信息抽取、知识图谱这一块,笔者依然还算是一个新手,望相关前辈们多多指教。
转载到请包括本文地址:https://kexue.fm/archives/6671
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Jun. 03, 2019). 《基于DGCNN和概率图的轻量级信息抽取模型 》[Blog post]. Retrieved from https://kexue.fm/archives/6671
@online{kexuefm-6671,
title={基于DGCNN和概率图的轻量级信息抽取模型},
author={苏剑林},
year={2019},
month={Jun},
url={\url{https://kexue.fm/archives/6671}},
}
September 16th, 2020
苏神,请问relation是怎么体现的呢?在模型介绍里只说是对每一种p进行object的提取。是进行embedding处理还是什么?感谢
我觉得本文已经说清楚了,你再多看两次试试?
其他内容补充中写了:而pc可以认为是全局的关系检测模块,po可以认为是全局的关系存在性判断。
具体我再看代码了解一下,谢谢~
September 28th, 2020
苏神您好,请问为什么选择了start/end(sigmoid)的方式,而不是bio那种形式的标注,是否是因为start/end方式有更优的性能?
该说的文章已经说了。
October 3rd, 2020
貌似有1.5问题:
1. predicate 是先验知道50个左右,然后遍历,模型通用性有问题,当然这是比赛;
2. 条件概率的乘没有体现出来,觉s和o的解码概率要乘下,作为最后的解码概率;
1. 是的,基本上目前所有的关系抽取模型都存在这个问题;
2. 你可以理解当前的策略是逐步贪心搜素。
NLP方面实践很少,“基本上目前所有的关系模型都存在这个问题“,令人惊讶,这个模型像预测s那样预测p不行吗?
$p$不一定出现在文本中,一般来说,$p$代表着标准化描述,比如“广州塔又叫小蛮腰”,抽出来可能是(广州塔, 别名, 小蛮腰)。正是由于这种标准化的存在,才赋予知识图谱具有更深层次的推理能力的可能性。否则如果只是将一个句子的主谓宾抽取来,不对谓语做标准化,那么很难将各种关系联系起来。
嗯嗯,”p不一定出现在文本中“,想了下,貌似可以先预测s和O,再用类似《万能的seq2seq:基于seq2seq的阅读理解问答》方法,把P(p|s,o)当作语义理解问题处理,解决通用性
问题在于,你用有限个$p$去训练seq2seq模型时,训练好的模型预测出来的$p$一般来说也只局限在这个有限范围内,不会自己去创造一些新标准出来。
说到底,这种标准化只是人为的设计,比如关系为“别名”,其实也可以叫做“别称”、“又名”等等,为什么非得叫做“别名”,这没有任何理由可讲,可能是随机选的,也有可能是个人喜好。而机器无法去推理这种不确定因素,因此是没有办法做的。
嗯嗯,我想的时候默认是有人的主观限定这个约束的,就是数据标注者怎么标注那就怎么预测,暂时不考虑类似”别名“还有”又名“等这些随机,相当于p其实也是有限的,只是有限的范围比较大,而且人为标注数据控制;
这就有点像分类问题中的label embedding了,那是另外一个研究方向了。
October 27th, 2020
苏神,我想问下,我试了下本文的膨胀门卷积,但是验证集从第一个epoch开始就是一个相同的值,这是什么原因呢?
不知道呀~可能需要炼丹。
December 22nd, 2020
苏神您好 我想问您一个问题 就是这个模型是专属于关系集合的吗 因为我看到您的模型中依赖于关系的数目 如果换一组关系就需要换一组训练数据重新训练模型吗 那我感觉这种模型的用处会不会有些窄呢 如果我有根据一组关系重新标注训练数据的时间 为什么不直接去人工提取其中的三元组关系呢
我是个刚进入联合抽取领域的菜鸡 如果问题中有什么常识性的错误 希望您能谅解
我感觉这个问题本身就是对三元组抽取领域的常识性错误。
你认为它应用窄,那也无所谓,反正当前大多数有监督的信息抽取工作,都是这种应用窄的研究。至于你有没有重新标注训练数据的时间我不知道,但是我想不明白“为什么不直接去人工提取其中的三元组关系”是个什么操作,你是想不用模型、全上人工识别?
就是我原本以为您用这个数据集训练出来的模型也可以应用到其他数据集上 就比如我通过您这个关系集合的训练 下次也可以不换模型只换关系集合来得到我想要的数据集中的三元关系 我说的“为什么不直接去人工提取其中的三元组关系”的意思是 比如说我现在有一个新的数据集想要提取其中的三元组关系 但是我发现如果我想要应用您的这种模型 就需要我先标注出一部分数据来作为训练数据得到模型 再去进行提取 我不知道我理解的对不对
很感谢您能回答我这个菜鸡的问题^_^
理解没错,就是需要重新标注数据,不然一个模型解决所有,那要人来干嘛?
好的 谢谢您
January 18th, 2021
苏神您好,最近也在搞有重叠问题的信息抽取,看到了您的这篇博客,还有ACL上的那篇论文,麻烦请教您2个问题。
按您的思路,相当于每个relation会有一个专门的分类器去识别,这样每个分类器得到的训练正/负样本的比例差异比较大。
问题(1):就标记的类别来看,0和1的数量级差异应该很大,为什么还训练的那么好呀(因为我按您这样的思路在我的数据上尝试,发现只有给正样本加很大的weight才能训练出来结果,且结果不怎么好)?
问题(2):按我对您博客的理解,采用“半指针-半标注”的0/1标注方式,主要是要解决关系重叠的问题。不知道这种方式,对样本的极度不平衡有什么潜在的作用么? 或者说这种“半指针-半标注”方式会不会有样本极度不平衡的问题呢?
感谢您呀~~
你这两个问题其实都是针对不平衡问题问的,我这里就统一回复了。其实我在训练百度比赛数据的时候,并没有发现不平衡问题导致的不收敛、效果差等情况,所以就没有深究。本文实际上也提供了一个改善不平衡问题的策略,但实际上不加那个策略,只是收敛慢一点,效果略差一点,整体差不多。
不平衡问题源于多个二分类的负样本角度,跟多标签分类所面临的情况是一致的。如果是极度不平衡的问题,你可以尝试一下 https://kexue.fm/archives/7359 里边所介绍的loss,那个loss很多读者都实验过,据说体验还行。
January 18th, 2021
感谢感谢~~
January 24th, 2021
苏神您好 有一处问题想请苏神指点一下
文章中说 "将得到“字-词-位置 Embedding”输入到12层DGCNN中进行编码,得到编码后的序列(记为H)" 后面重用编码向量序列用的也是这个H;
我个人认为应该把经过第一次self-attention后的编码记为H , 在后面也直接重用这个H ; 否则在预测p,o时H还要重新过一遍self-attention(增加计算量 , 且意义不大 , 直接重用上面经过self-attention后的向量就好了). 且我的这种方案应该对抽取s的编码有更好的效果(因为多经过了一层self-attention做信息整合 , 再做Bi-lstm) , 这样就可以把原模型中第二次self-attention省去了
不知道我说的这种方式是否合理 , 或者是苏神还有其他考量?
这种细微的改动,你有兴趣的话去尝试即可,我也不知道有没有提升。我当初的目的,可能是就是想拉开预测s和预测p、o两部分模型的差别,因为这严格来说就是两个模型,共享过多未必有好处。
好的 谢谢苏神
February 24th, 2021
苏神,你好~~想复现下您这个模型。。弄到了百度的数据集和您的源码。我发现缺少您提到的‘用1000万条百度百科词条训练的Word2Vec模型‘,不知道这个如何获取?
我看代码中,您是事先训练了模型,并在代码中直接获取word2vec = Word2Vec.load('../word2vec_baike/word2vec_baike')这个词向量。
恳请指点下。
https://github.com/bojone/dgcnn_for_reading_comprehension
感谢感谢!!
楼主可以分享一下数据集吗
http://ai.baidu.com/broad/download里面就有,我是下载的Knowledge Extraction的数据集,应该就是苏神提到的
March 4th, 2021
苏神,我这几天在研究你的这个源码,对数据集的处理部分,还有几点疑问,恳请指点下迷津:
1. spo_searcher应该就是你提到的远程监督的先验特征吧?对于你文中的说法,用纯粹检索的方法来抽出任意一个句子的候选三元组,而且有可能抽取出来的三元组全是错的。我觉得说得很对。但是我很不明白:
1)先排除当前训练样本自身的三元组,其目的是什么?--这个远程监督的原理有没相关的文献可以学习下。。我对这块操作感觉很是不理解。。靠AC自动机搜索出来的三元组,感觉很可能就是不合理的,如何能作为标签呢?
2. data_generator中(其实不仅仅此处),涉及到了代码
text_words = tokenize(text)
text = ''.join(text_words) #这里很奇怪,tokenize是对text进行分词,这里又将分完词的结果合并到一起,其实还是原始的text,是否这行有点多余?
另外,text = d['text'][:maxlen]是将文本进行截断处理,很可能存在文本中涉及到主语和宾语的部分也被截掉了,这样不会影响训练么?
3. 代码中的random_order_vote.json保存下来的目的是什么?感觉是将数据集打乱后切分出训练集和验证集,这个不是可以使用随机种子设置,来确保每次分割的结果可以复现么?
4. repair的方法,其目的是什么? 我看了半天,是用来修复标签错误的情况? 而且只针对谓语为['歌手', '作词', '作曲']的情况,进行针对性的标签删除?为什么要删?
5. Evaluate类中的on_epoch_end方法,用到(self.stage == 0 and epoch > 10 and (f1 < 0.5 or np.argmax(self.F1) < len(self.F1) - 8))来判断是否进行移动平均处理,这行代码目的是什么,特别是np.argmax(self.F1) < len(self.F1) - 8完全看不明白。。
小白一枚,恳请大神指点~~
1. 肯定要排除自身的三元组啊,不然要抽取的三元组肯定在远程监督的结果中?这肯定不可能吧。这是防止信息泄漏的一个基本步骤,是很自然的吧。我抽取出来,供模型选用,至于模型用不用,是模型的事,我作为辅助信息提供给它,这不是什么问题吧?它要是觉得没用可以不用的,一切都是“机器”在学习;
2. 主要是对于某些分词工具来说,''.join(tokenize(text))未必等于text,而我这里需要保证它等;
3. 我不喜欢设置随机种子;
4. repair是按照测试出的规则对结果进行标准化,只有当时参加比赛有用,对于事后研究没啥用。
5. 为了满足某些特定的条件时,就停止当前训练,而加载保存的最优权重,使用十分之一的学习率继续训练。
很感谢您的指点~~我再去研究学习下~~
今天又继续研究了您的代码,仍旧有几点新疑问:
1. 我看代码中,您将dev_data.json文件转换后,并没有拿来使用?而是在train_data.json文件中,分割出部分作为验证集。为什么呢?
2. 在Evaulate类中的on_batch_begin函数提到,第一个epoch用来warmup,否则有不收敛的可能,为什么?是试验过程中发现的?同时,我也看到,训练过程中lr貌似除了第一个epoch调整了下,其他epoch下都是固定值1e-4?这样的话,我试了下,貌似一直都到不了0.88+的f1?
3. 对于前面提到的on_epoch_end方法中条件判断的语句,感觉那个np.argmax(self.F1) < len(self.F1) -8,是和交叉验证有关?数值8刚好是你文中提到的8折交叉验证?只是在代码中没有看到相关的8折验证操作。。
4. 两份训练py文件,without_ds就是没有远程监督的,with_ds就是带了远程监督的吧。。。只是我还是不太了解post_repair文件用来做什么?是否您文中提到的将模型预测结果,进行知识蒸馏处理呢?