特定领域知识图谱(Domain-specific KnowledgeGraph:DKG)融合方案:技术知识前置【一】-文本匹配算法、知识融合学术界方案、知识融合业界落地方案、算法测评KG生产质量保障
0.前言
本项目主要围绕着特定领域知识图谱(Domain-specific KnowledgeGraph:DKG)融合方案:技术知识前置【一】-文本匹配算法、知识融合学术界方案、知识融合业界落地方案、算法测评KG生产质量保障讲解了文本匹配算法的综述,从经典的传统模型到孪生神经网络“双塔模型”再到预训练模型以及有监督无监督联合模型,期间也涉及了近几年前沿的对比学习模型,之后提出了文本匹配技巧提升方案,最终给出了DKG的落地方案。这边主要以原理讲解和技术方案阐述为主,之后会慢慢把项目开源出来,一起共建KG,从知识抽取到知识融合、知识推理、质量评估等争取走通完整的流程。
1.文本匹配算法综述(短文本匹配)
文本匹配任务在自然语言处理中是非常重要的基础任务之一,一般研究两段文本之间的关系。有很多应用场景;如信息检索、问答系统、智能对话、文本鉴别、智能推荐、文本数据去重、文本相似度计算、自然语言推理、问答系统、信息检索等,但文本匹配或者说自然语言处理仍然存在很多难点。这些自然语言处理任务在很大程度上都可以抽象成文本匹配问题,比如信息检索可以归结为搜索词和文档资源的匹配,问答系统可以归结为问题和候选答案的匹配,复述问题可以归结为两个同义句的匹配。
- 如语言不规范,同一句话可以有多种表达方式;如“股市跳水、股市大跌、股市一片绿”
- 歧义,同一个词语或句子在不同语境可能表达不同意思;如“割韭菜”,“领盒饭”,“苹果”“小米”等在不同语境下语义完全不同
- 不规范或错误的输入;如 “ yyds”,“绝绝子”“夺笋”“耗子尾汁”
- 需要知识依赖;如奥运冠军张怡宁绰号“乒乓大魔王”等。
短文本匹配即计算两个短文本的相似度,通常分成无监督方式、有监督方式、有监督+无监督方式
常见的文本匹配算法如下表(简单罗列),按传统模型和深度模型简单的分为两类:
算法 | 类型 |
---|---|
Jaccord | 传统模型 |
BM25 | 传统模型 |
VSM | 传统模型 |
SimHash | 传统模型 |
Levenshtein | 传统模型 |
cdssm | 深度模型 |
arc-ii | 深度模型 |
match_pyramid | 深度模型 |
mvlstm | 深度模型 |
bimpm | 深度模型 |
drcn | 深度模型 |
esim | 深度模型 |
textmatching | 深度模型 |
bert-base | 深度模型 |
albert-base | 深度模型 |
albert-large | 深度模型 |
raberta | 深度模型 |
sbert | 深度模型 |
DiffCSE | 深度模型 |
ERNIE-Gram | 深度模型 |
1.1 文本匹配传统模型(无监督方式)
文本表征:词袋模型(one-hot 、TF)、词向量预训练(word2vector、fasttext、glove) 相似度计算:余弦相似度、曼哈顿距离、欧氏距离、jaccard距离等
1.1.1 Jaccord 杰卡德相似系数
jaccard相似度是一种非常直观的相似度计算方式,即两句子分词后词语的交集中词语数与并集中词语数之比。
$\text { jaccard }=\frac{A \bigcap B}{A \bigcup B}$
def jaccard(list1, list2):
"""
jaccard相似系数
:param list1:第一句话的词列表
:param list2: 第二句话的词列表
:return:相似度,float值
"""
list1, list2 = set(list1), set(list2) #去重
intersection = list1.intersection(list2) # 交集
union = list1.union(list2) # 并集
Similarity = 1.0 * len(intersection) / len(union) #交集比并集
return Similarity
a.分词
内蒙古 锡林郭勒盟 多伦县 县医院 / 多伦县 医院
绵阳市 四 零 四 医院 / 四川 绵阳 404 医院
邓州市 人民 医院 / 南召县 人民 医院
b.去重求交集--并集
多伦县(交集) -- 内蒙古、锡林郭勒盟、多伦县、县医院、医院(并集)
医院(交集) -- 绵阳市、四、零、医院、四川、绵阳、404(并集)
人民、医院(交集) -- 邓州市、人民、医院、南召县(并集)
c.相似度
文本对 | 相似度 | 真实标签 |
---|---|---|
内蒙古 锡林郭勒盟 多伦县 县医院 / 多伦县 医院 | 1/5=0.2 | 1 |
绵阳市 四零四医院/四川 绵阳 404 医院 | 1/7 = 0.14 | 1 |
邓州市 人民 医院 / 南召县 人民 医院 | 2/4 = 0.5 |
1.1.2.Levenshtein编辑距离
一个句子转换为另一个句子需要的编辑次数,编辑包括删除、替换、添加,然后使用最长句子的长度归一化得相似度。
def Levenshtein(text1, text2):
"""
Levenshtein编辑距离
:param text1:字符串1
:param text2:字符串2
:return:相似度,float值
"""
import Levenshtein
distance = Levenshtein.distance(text1, text2)
Similarity = 1 - distance * 1.0 / max(len(text1), len(text2))
return Similarity
a.分词-计字数
内 蒙 古 锡 林 郭 勒 盟 多 伦 县 县 医 院 (14) / 多 伦 县 医 院(5)
绵 阳 市 四 零 四 医 院(8) / 四 川 绵 阳 4 0 4 医 院(9)
邓 州 市 人 民 医 院 (7) / 南 召 县 人 民 医 院(7)
b.计算编辑距离
内 蒙 古 锡 林 郭 勒 盟 多 伦 县 县 医 院 ------->删除内、蒙、古、锡、林、郭、勒、盟、县
绵 阳 市 四 零 四 医 院 ------->加 四、川
------->删除 市
------->替换 四(4)、零(0)、四(4)
邓 州 市 人 民 医 院 ------->替换邓(南)、 州(召)、 市(县)
文本对 | 距离 | 真实标签 |
---|---|---|
内蒙古 锡林郭勒盟 多伦县 县医院 / 多伦县 医院 | 0.357 | 1 |
绵阳市 四零四医院/四川 绵阳 404 医院 | 0.333 | 1 |
邓州市 人民 医院 / 南召县 人民 医院 | 0.571 |
1.1.3 simhash相似度
先计算两句子的simhash二进制编码,然后使用海明距离计算,最后使用两句的最大simhash值归一化得相似度。
def simhash_(text1, text2):
"""
simhash相似度
:param s1: 字符串1
:param s2: 字符串2
:return: 相似度,float值
"""
from simhash import Simhash
text1_simhash = Simhash(text1, f=64) #计算simhash向量
text2_simhash = Simhash(text2, f=64) #计算simhash向量
distance = text1_simhash.distance(text2_simhash) #汉明距离
Similarity = 1 - distance / max(len(bin(text1_simhash.value)), len(bin(text2_simhash.value))) #相似度
return Similarity
a.分词
内蒙古 锡林郭勒盟 多伦县 县医院 / 多伦县 医院
绵阳市 四 零 四 医院 / 四川 绵阳 404 医院
邓州市 人民 医院 / 南召县 人民 医院
b.计算词权重(此处用tfidf计算,其他方法也可以)
| Text | Text | Text |
内蒙古5 | 锡林郭勒盟5 | 多伦县2 | 县医院5 | 多伦县7 | 医院1 | |||
---|---|---|---|---|---|---|---|---|
绵阳市3 | 四6 | 零3 | 四6 | 医院1 | 四川5 | 绵阳5 | 4045 | 医院1 |
邓州市7 | 人民4 | 医院1 | 南召县7 | 人民4 | 医院1 |
c.hash函数映射词向量
- 先将词映射到二进制编码,
- 然后用b步骤中的权重值替换1,
- b步骤中权重值的负数替换0
d.合并(将一段文本内的词向量进行累加)
e海明距离判断相似度
海明距离可以理解为:两个二进制串之间相同位置不同的个数。举个例子,[1,1,1,0,0,0]和[1,1,1,1,1,1]的海明距离就是3。
1.1.4 Bm25相似度
一句话概况其主要思想:对Query(待查询语句)进行语素解析,生成语素qi;然后,对于每个搜索结果D,计算每个语素qi与D的相关性得分,最后,将qi相对于D的相关性得分进行加权求和,从而得到Query与D的相关性得分。公式如下:
$\operatorname{Score}(Q, d)=\sum_i^n W_i \cdot R\left(q_i, d\right)$
Q表示Query,qi即Q分词后的每一个解析语素(对中文而言,我们可以把对Query的分词作为语素分析,每个词看成语素qi)。d表示一个搜索结果文档,Wi表示语素qi的权重,R(qi,d)表示语素qi与文档d的相关性得分。 判断一个词与一个文档的相关性的权重定义Wi方法有多种,较常用的是IDF。公式如下:
$\operatorname{IDF}\left(q_i\right)=\log \frac{N-n\left(q_i\right)+0.5}{n\left(q_i\right)+0.5}$
- N为索引中的全部文档数,
- n(qi)为包含了qi的文档数。
根据IDF的定义可以看出当很多文档都包含了qi时,qi的区分度就不高,因此使用qi来判断相关性时的重要度就较低。
$\operatorname{Score}(Q, d)=\sum_i^n I D F\left(q_i\right) \cdot \frac{f_i \cdot\left(k_1+1\right)}{f_i+k_1 \cdot\left(1-b+b \cdot \frac{d l}{a v g d l}\right)}$
求Score(qi,d)具体的公式可以参考文本相似度-BM25算法 其中
- f(qi, D)为单词在当前候选文档中的词频
- k1、b为调节因子,通常设为k1=2,b=0.75
- |D|为当前候选文本数(与目标文本匹配的总条数)
- avgdl为语料库中所有文档的平均长度。
在做文本匹配的时候(如重复问题检测)可以尝试BM25的方法,但在搜索领域中,有时候搜索query和候选文档的长度是不一样甚至差距很大,所以BM25在计算相似性的时候需要对文档长度做一定的处理。
#Bm25
import math
import jieba
class BM25(object):
def __init__(self, docs):#docs是一个包含所有文本的列表,每个元素是一个文本
self.D = len(docs) #总文本数
self.avgdl = sum([len(doc)+0.0 for doc in docs]) / self.D #平均文本长度
self.docs = docs #文本库列表
self.f = [] # 列表的每一个元素是一个dict,dict存储着一个文档中每个词的出现次数
self.df = {} # 存储每个词及出现了该词的文档数量
self.idf = {} # 存储每个词的idf值
self.k1 = 1.5
self.b = 0.75
self.init()
def init(self):
for doc in self.docs: #对每个文本
tmp = {} #定义一个字典存储词出现频次
for word in doc:
tmp[word] = tmp.get(word, 0) + 1 # 存储每个文档中每个词的出现次数
self.f.append(tmp)
for k in tmp.keys():
self.df[k] = self.df.get(k, 0) + 1
for k, v in self.df.items():
self.idf[k] = math.log(self.D-v+0.5)-math.log(v+0.5) #计算idf
def sim(self, doc, index):
score = 0
for word in doc:
if word not in self.f[index]:
continue
d = len(self.docs[index])
score += (self.idf[word]*self.f[index][word]*(self.k1+1)
/ (self.f[index][word]+self.k1*(1-self.b+self.b*d
/ self.avgdl)))
return score
def simall(self, doc):
scores = []
for index in range(self.D):
score = self.sim(doc, index)
scores.append(score)
return scores
if __name__ == '__main__':
sents1 = ["多伦县医院", #数据库
"四川绵阳404医院",
"南召县人民医院"]
sents2 = ["内蒙古锡林郭勒盟多伦县县医院","绵阳市四零四医院","邓州市人民医院"]#待匹配文本
doc = []
for sent in sents1:
words = list(jieba.cut(sent))
doc.append(words)
print(doc)
s = BM25(doc)
print(s.f)
print(s.idf)
for k in sents2:
print(s.simall(jieba.lcut(k))) #打印相似度匹配结果
1.1.5 VSM(向量空间模型)算法
VSM算法的思路主要分为两步:
(1) 用向量表示句子,用向量表示句子的方法很多,简单的有onehot,词频法,基于语义的有word2vec/fastText/glove/bert/elmo等,本例中使用基于简单的词频的向量化方式。
(2)计算两向量的余弦距离(曼哈顿距离、欧几里得距离、明式距离、切比雪夫距离)得相似度。
#tfidf_余弦
def sim_vecidf(self, s1, s2):
"""词向量通过idf加权平均后计算余弦距离"""
v1, v2 = [], []
# 1. 词向量idf加权平均
for s in jieba.cut(s1):
idf_v = idf.get(s, 1)
if s in voc:
v1.append(1.0 * idf_v * voc[s])
v1 = np.array(v1).mean(axis=0)
for s in jieba.lcut(s2):
idf_v = idf.get(s, 1)
if s in voc:
v2.append(1.0 * idf_v * voc[s])
v2 = np.array(v2).mean(axis=0)
# 2. 计算cosine
sim = self.cosine(v1, v2)
return sim
a.句子向量化
a1.取句子对的唯一词元组
set(内蒙古 锡林郭勒盟 多伦县 县医院 / 多伦县 医院) = (内蒙古 锡林郭勒盟 多伦县 县医院 医院)
set(绵阳市 四 零 四 医院 / 四川 绵阳 404 医院) = (绵阳市 四 零 医院 四川 绵阳 404 )
set(邓州市 人民 医院 / 南召县 人民 医院) = (邓州市 人民 医院 南召县)
a2.根据每个句子在元组中的词频建立向量表示
b.计算余弦距离
$\operatorname{Cos}(x 1, x 2)=\frac{x 1 \cdot x 2}{|x 1||x 2|}$
句子 | 距离 |
---|---|
内蒙古 锡林郭勒盟 多伦县 县医院 / 多伦县 医院 | 0.3535 |
绵阳市 四零四医院/四川 绵阳 404 医院 | 0.1889 |
邓州市 人民 医院 / 南召县 人民 医院 | 0.6666 |
1.1.6 word2vector + 相似度计算(BERT模型+余弦相似度为例)
常用做法是通过word2vec等预训练模型得到词向量,然后对文本做分词,通过embedding_lookup得到每个token对应的词向量,然后得到短文本的句向量。对两个文本的句子向量采用相似度计算方法如余弦相似度、曼哈顿距离、欧氏距离等。无监督方式取得的结果取决于预训练词向量的效果。
BERT是谷歌在2018年推出的深度语言表示模型,是关于语言理解的深度双向transformers的预训练模型,开启了预训练模型的新篇章。它可以学习文本的语义信息,通过向量形式的输出可以用于下游任务。也就说,它自己已经在大规模预料上训练好的参数,我们在用的时候只需要在这个基础上训练更新参数。bert模型可以解决多种自然语言处理问题,如单文本分类、语句对分类、序列标注等。在解决文本匹配任务时,有两种思路,第一种直接把文本匹配任务作为语句对分类任务,模型输入语句对,输出是否匹配的标签;第二种利用bert模型预训练文本对的上下文嵌入向量,再通过余弦相似度等相似度计算方法验证文本对是否匹配,在此基础上还有很多基于bert模型的变种,篇幅有限不做一一讲述。接下来简单介绍一下bert预训练文本嵌入+余弦相似度的算法框架。
a.首先使用大量公域文本数据对BERT模型进行预训练(或直接用谷歌预训练好的模型)
b.将文本直接输入模型
c.对模型输出的语义向量C,或中间隐层向量,计算余弦相似度,得到匹配结果。
基于深度学习的匹配算法种类繁多,如基于CNN网络、RNN网络、LSTM网络等及各种变种层出不穷,在此不一一列举实现。
传统的文本匹配方法主要关注文本间字与字,词与词的匹配关系,无法准确识别不同表达方式下不同文本的同一指向关系,即语义关系。因此在这一背景下,要对多源异构的海量地址数据进行匹配,传统的文本匹配方法已不再适用,深度学习方法大行其道。但深度学习方法也有自身的局限性,比如对海量文本和算力的高要求等,都使得深度学习方法的普适性大打折扣,因此没有最好的文本匹配算法,只有当前条件下最适合的文本匹配算法。
2.有监督方式
2.1 孪生神经网络(Siamese Network)
原文:《Learning a Similarity Metric Discriminatively, with Application to Face Verification》
-
要解决什么问题?
- 用于解决类别很多(或者说不确定),然而训练样本的类别数较少的分类任务(比如人脸识别、人脸认证)
- 通常的分类任务中,类别数目固定,且每类下的样本数也较多(比如ImageNet)
-
用了什么方法解决?
提出了一种思路:将输入映射为一个特征向量,使用两个向量之间的“距离”(L1 Norm)来表示输入之间的差异(图像语义上的差距)。 基于上述思路设计了Siamese Network。每次需要输入两个样本作为一个样本对计算损失函数。
-
[x] 常用的softmax只需要输入一个样本。
-
[x] FaceNet中的Triplet Loss需要输入三个样本。
-
[x] 提出了Contrastive Loss用于训练。
- 效果如何?
文中进行了一个衡量两张人脸的相似度的实验,使用了多个数据库,较复杂。siamese network现在依然有很多地方使用,可以取得state-of-the-art的效果。
- 还存在什么问题?
- contrastive loss的训练样本的选择需要注意,论文中都是尽量保证了50%的正样本对和50%的负样本对。
分类问题:
-
第一类,分类数量较少,每一类的数据量较多,比如ImageNet、VOC等。这种分类问题可以使用神经网络或者SVM解决,只要事先知道了所有的类。
-
第二类,分类数量较多(或者说无法确认具体数量),每一类的数据量较少,比如人脸识别、人脸验证任务。
文中提出的解决方案:
learn a similar metric from data。核心思想是,寻找一个映射函数,能够将输入图像转换到一个特征空间,每幅图像对应一个特征向量,通过一些简单的“距离度量”(比如欧式距离)来表示向量之间的差异,最后通过这个距离来拟合输入图像的相似度差异(语义差异)。
2.1.1 简介
- Siamese Network 是一种神经网络的框架,而不是具体的某种网络,就像seq2seq一样,具体实现上可以使用RNN也可以使用CNN。
- Siamese network就是“连体的神经网络”,神经网络的“连体”是通过共享权值来实现的。(共享权值即左右两个神经网络的权重一模一样)
- siamese network的作用是衡量两个输入的相似程度。孪生神经网络有两个输入(Input1 and Input2),将两个输入feed进入两个神经网络(Network1 and Network2),这两个神经网络分别将输入映射到新的空间,形成输入在新的空间中的表示。通过Loss的计算,评价两个输入的相似度。
Siamese Network有两个结构相同,且共享权值的子网络。分别接收两个输入X1与X2,将其转换为向量Gw(X1)与Gw(X2),再通过某种距离度量的方式计算两个输出向量的距离Ew。
训练Siamese Network采用的训练样本是一个tuple (X1,X2,y)(X1,X2,y),标签y=0表示X1与X2属于不同类型(不相似、不重复、根据应用场景而定)。y=1则表示X2与X2属于相同类型(相似)。
LOSS函数的设计应该是
- 当两个输入样本不相似(y=0)时,距离Ew越大,损失越小,即关于Ew的单调递减函数。
- 当两个输入样本相似(y=1)时,距离Ew越大,损失越大,即关于Ew的单调递增函数。
用L+(X1,X2)表示y=1时的LOSS,L−(X1,X2)表示y=0时的LOSS,则LOSS函数可以写成如下形式:
Lw(X1,X2)=(1−y)L−(X1,X2)+yL+(X1,X2)
简单来说:孪生体现在使用相同的编码器(sentence encoder),将文本转为高维向量。具体步骤为,有文本A和文本B分别输入 sentence encoder 进行特征提取和编码,将输入映射到新的空间得到特征向量u和v;最终通过u、v的拼接组合,经过下游网络来计算文本A和B的相似性
- 在训练和测试中,模型的编码器是权重共享的,编码器可以选择CNN、RNN、LSTM等
- 提取到特征u、v后,可以使用余弦距离、欧氏距离等得到两个文本的相似度。
- 缺点是两个文本在编码时候没有交互,缺乏交互的结构没有充分利用到两个文本相互影响的信息
2.2 匹配聚合网络(ESIM,BIMPM)
在上述孪生网络的基础上,得到特征u、v但是不直接计算向量相似度,而是通过注意力机制将两个文本进行信息交互,最后通过全连接层得到相似度。
代表的模型有ESIM,BIMPM等
以ESIM为例
-
首先是对两个文本的初期编码,就是对两个文本做分词、文本表征,即对句子进行信息的提取。如果使用LSTM作为encoder,可以得到每个时刻(每个单词)的输出,通常维度为[batch_size, seq_len, embedding_dim]。举例子为,句子A长度10,句子B长度也为10,那么进过编码以后句子A的维度[1,10,300],句子B[1,10,300],这里就得到了上述所提到的u、v
-
接下来是交互操作,为了操作简单忽略batchsize维度,交互即矩阵相乘得到[10,10],矩阵需要对句子A做横向概率归一,对句子B做纵向概率归一。上面这句话其实就是ESIM的核心要点。它是一个两个item之间互相做attention,简单称之为both attention。
-
对attention后得到的向量做拼接后输出编码器,输出再接到全连接层、softmax就可以得到结果,即两个文本相似(label 1)或不相似(label 0)
3.预训练语言模型
-
第一阶段,使用通用的预料库训练语言模型,
-
第二阶段预训练的语言模型(BERT相关衍生的模型)做相似度任务,得到信息交互后的向量,
-
连接全连接层,输出概率。即将两个短文本拼接(CLS A1 A2 … A 10 SEP B1 B2 … B10 SEP),然后CLS向量连接全连接层,判断相似与否。
这种模型参数多,并且使用了通用的语料库,能够获取到短文本之间隐藏的交互信息,效果较好。
简单来说用拼接的方法类似“单塔”,孪生网络的方法类似“双塔”,不完全准确后续会详细说明,预训练模型就不展开讲了,大家去官网或者多看几篇学术论文吧,BERT ERNIE。
4.有监督方式 + 无监督方式
无监督:直接相加得到句向量,不能很好的表达语义信息,并且词的位置信息没有得到体现,也不包含上下文的语义信息。
有监督学习:时间复杂度太高。可以将标准库中的句向量计算完成并存储。新的文本来临时,只需要解决用户问题即可,然后与存储在库中的标准问句进行距离度量。
可以使用BERT代替孪生网络的CNN或LSTM结构,获取更多语义信息的句向量,还可以通过蒸馏降低BERT模型的参数,节约时间成本。
4.1 Sentence-BERT
文章链接:https://arxiv.org/pdf/1908.10084.pdf
论文代码:https://github.com/UKPLab/
为了让BERT更好地利用文本信息,作者们在论文中提出了如下的SBERT模型。SBERT沿用了孪生网络的结构,文本Encoder部分用同一个BERT来处理。之后,作者分别实验了CLS-token和2种池化策略(Avg-Pooling、Mean-Pooling),对Bert输出的字向量进一步特征提取、压缩,得到u、v。
关于u、v整合,作者提供了3种策略:
-
将u、v拼接,接入全连接网络,经过softmax输出,损失函数用交叉熵损失
-
直接计算两个文本的余弦相似度,损失函数用均方根误差
- 如果输入的是三元组
SBERT直接用BERT的原始权重初始化,在具体数据集微调,训练过程和传统Siamese Network类似。但是这种训练方式能让Bert更好的捕捉句子之间的关系,生成更优质的句向量。在测试阶段,SBERT直接使用余弦相似度来衡量两个句向量之间的相似度,极大提升了推理速度。
使用NLI和STS为代表的匹配数据集,在分类目标函数训练时,作者测试了不同的整合策略,结果显示“(u, v, |u-v|)”的组合效果最好。最重要的部分是元素差:(|u - v|)。句向量之间的差异度量了两个句子嵌入维度间的距离,确保相似的pair更近,不同的pair更远。
4.2 对比学习
深度学习的本质是做两件事情:①表示学习 ②归纳偏好学习。对比学习(ContrastiveLearning)则是属于表示学习的范畴,它并不需要关注样本的每一个细节,但是学到的特征要使其能够和其他样本区分开。对比学习作为一种无监督表示学习方法,最开始也是在CV领域掀起浪潮,之后NLP跟进,在文本相似度匹配等任务上超过SOTASOTA。该任务主要是对文本进行表征,使相近的文本距离更近,差别大的文本距离更远。
NLP的对比学习算法下文将不详细讲述简单展示更多内容参考链接。
4.2.1 BERT-Flow 2020.11
很多研究发现BERT表示存在问题:未经微调的BERT模型在文本相似度匹配任务上表现不好,甚至不如Glove?作者通过分析BERT的性质,如图:
在理论上BERT确实提取到了足够的语义信息,只是这些信息无法通过简单的consine直接利用。主要是因为:
- ①BERT的词向量在空间中不是均匀分布,而是呈锥形。高频词都靠近原点,而低频词远离原点,相当于这两种词处于了空间中不同的区域,那高频词和低频词之间的相似度就不再适用;
- ②低频词的分布很稀疏。低频词表示得到的训练不充分,分布稀疏,导致该区域存在语义定义不完整的地方(poorly defined),这样算出来的相似度存在问题。
针对以上问题,提出了BERT-Flow,基于流式生成模型,将BERT的输出空间由一个锥形可逆地映射为标准的高斯分布空间。
4.2.2 BERT-Whitening 2021.03
BERT-Whitening首先分析了余弦相似度为什么可以衡量向量的相似度:向量A 与B 的乘积等于A AA在B BB所在直线上投影的长度。将两个向量扩展到d维
$\cos (A, B)=\frac{\sum_{i=1}^d a_i b_i}{\sqrt{\sum_{i=1}^d a_i^2} \sqrt{\sum_{i=1}^d b_i^2}}$
$\text { 模的计算公式: }|A|=\sqrt{a_1^2+a_2^2+\ldots+a_n^2}$
上述等式的成立,都是在标准正交基(忘了的同学可以自行复习一下)的条件下,也就是说向量依赖我们选择的坐标基,基底不同,内积对应的坐标公式就不一样,从而余弦值的坐标公式也不一样。 所以,BERT的句向量虽然包含了足够的语义,但有可能是因为此时句向量所属的坐标系并非标准正交基,而导致用基于标准正交基的余弦相似度公式计算时效果不好。那么怎么知道具体用了何种基底呢?可以依据统计学去判断,在给向量集合选择基底时,尽量平均地用好每一个基向量,这就体现为每个分量的使用都是独立的、均匀的,如果这组基是标准正交基,那么对应的向量集应该表现出“各向同性”来,如果不是,可以想办法让它变得更加各向同性一写,然后再用余弦公式计算,BERT-Flow正是想到了“flow模型”的办法,而作者则找到了一种更简单的线性变换的方法。
标准化协方差矩阵
BERT-Whitening还支持降维操作,能达到提速和提效的效果。
★PCA和SVD差异分析:PCA可以将方阵分解为特征值和特征向量,SVD则可以分解任意形状的矩阵。
4.2.3 ConSERT 2021.05
https://arxiv.org/pdf/2105.11741.pdf
美团技术团队提出了基于对比学习的句子表示迁移方法——ConSERT,主要证实了以下两点:
①BERT对所有的句子都倾向于编码到一个较小的空间区域内,这使得大多数的句子对都具有较高的相似度分数,即使是那些语义上完全无关的句子对。我们将此称为BERT句子表示的“坍缩(Collapse)”现象。
②BERT句向量表示的坍缩和句子中的高频词有关。当通过平均词向量的方式计算句向量时,高频词的词向量将会主导句向量,使之难以体现其原本的语义。当计算句向量时去除若干高频词时,坍缩现象可以在一定程度上得到缓解。
为了解决BERT存在的坍缩问题,作者提出了句子表示迁移框架:
对BERT encoder做了改进,主要包括三个部分:
*①数据增强模块,作用于embedding层,为同一文本生成不同的编码。
- shuffle:更换position id的顺序
- token cutoff:在某个token维度把embedding置为0
- feature cutoff:在embedding矩阵中,有768个维度,把某个维度的feature置为0
- dropout:embedding中每个元素都有一定概率为0,没有行或列的约束
数据增强效果:Token Shuffle > Token Cutoff >> Feature Cutoff ≈ Dropout >> None
-
②共享的Bert encoder,生成句向量。
-
③一个对比损失层,在一个Batch内计算损失,拉近同一样本不同句向量的相似度,使不同样本之间相互远离。损失函数:
$L_{i, j}=-\log \frac{\exp \left(\operatorname{sim}\left(r_i, r_j\right) / \tau\right)}{\sum_{k=1}^{2 N} 1_{[k \neq i]} \exp \left(\operatorname{sim}\left(r_i, r_k\right) / \tau\right)}$
N:Batchsize,2N表示2种数据增强方式,sim():余弦相似度函数,r:句向量,τ:实验0.08−0.12最优
除了无监督训练之外,作者还提出了三种进一步融合监督信号的策略:
-
①联合训练(joint):有监督的损失和无监督的损失通过加权联合训练模型。
-
②先有监督再无监督(sup-unsup):先使用有监督损失训练模型,再使用无监督的方法进行表示迁移。
-
③联合训练再无监督(joint-unsup):先使用联合损失训练模型,再使用无监督的方法进行表示迁移。
参考链接:https://blog.csdn.net/PX2012007/article/details/127614565
4.2.4 SimCSE:2021.04
前几节讲述了对比学习的原理和几种基于 Bert 的方式获取句子向量,例如 BERT-flow和 BERT-whitening 等,对预训练 Bert 的输出进行变换从而得到更好的句子向量。后面将通过 ①构造目标函数 ②构建正负例 的对比学习方法训练模型,取得SOTA的效果。
SimCSE是有大神陈丹琦发表的《Simple Contrastive Learning of Sentence Embeddings》,简单高效
SimCSE包含无监督(图左部分)和有监督(图右部分)两种方法。实线箭头代表正例,虚线代表负例。
- Unsupervised
创新点在于使用Dropout对文本增加噪音。
1.正例构造:利用Bert的随机Dropout,同一文本经过两次Bert enconder得到不同的句向量构成相似文本。
2.负例构造:同一个Batch中的其他样本作为负例被随机采样。
- Supervised
1.正例:标注数据 2.负例:同Batch内的其他样本
4.2.5 R-Drop(Supervised):2021.06
https://arxiv.org/abs/2106.14448
Dropout虽然可以防止模型训练中的过拟合并增强鲁棒性,但是其操作在一定程度上会使训练后的模型成为一种多个子模型的组合约束。SimCSE就是希望Dropout对模型结果不会有太大影响,也就是模型输出对Dropout是鲁棒的。所以,“Dropout两次”这种思想是可以推广到一般任务的,这就是R-Drop(Regularized Dropout),由微软亚洲研究院和苏州大学提出的更加简单有效的正则方法。
-
R-Drop与传统作用于神经元或模型参数的约束方法不同,而是作用于输出层,弥补了Dropout在训练和测试时的不一致性。在每个mini-batch中,每个数据样本过两次带有Dropout的同一个模型,R-Drop再使用KL-divergence(KL散度)约束两次的输出一致。所以,R-Drop约束了由于Dropout带来的两个随机子模型的输出一致性。
-
R-Drop只是简单增加了一个KL-散度损失函数项,并没有其他任何改动。虽然该方法看起来很简单,但在NLP和CV的任务中,都取得了非常不错的SOTA结果。
同样的输入,同样的模型,经过两个 Dropout 得到的将是两个不同的分布,近似将这两个路径网络看作两个不同的模型网络。基于此,这两个不同的模型产生的不同分布而这篇文章的主要贡献就是在训练过程中不断拉低这两个分布之间的KL 散度。由于KL 散度本身具有不对称性,作者通过交换这两种分布的位置以间接使用整体对称的KL 散度,称之为双向KL 散度。
4.2.6 ESimCSE(Unsupervised):2021.09
https://arxiv.org/abs/2109.04380
SimCSE构建正负例时存在两个两个缺点:
-
①构造正例长度相等,导致模型预测时存在偏差,长度相等的文本会倾向预测相似度高。
-
②对比学习理论上负例越多,对之间模型学习的越好,但增大Batch会受到性能的限制。
ESimCSE针对以上问题做了相应的改进:
-
正例构造:通过引入噪声较小的“单词重复”方式改变正例的长度,设置重复率dup_rate,确定dup_len后利用均匀分布随机选取dup_len子词进行重复。
-
负例构造:为了更有效的扩展负对,同时不降低性能,通过维护一个队列,重用前面紧接的mini-batch的编码嵌入来扩展负对:
- ①将当前mini-batch的句嵌入放入队列,同时将“最老的”句子踢出队列。由于排队句子嵌入来自前面的mini-batch,通过取其参数的移动平均来保持动量更新模型,并利用动量模型生成排队句子嵌入。
- 在使用动量编码器时,关闭了dropout,这可以缩小训练和预测之间的差距。
4.2.7 PromptBERT(Unsupervised):2022.01
https://arxiv.org/pdf/2201.04337v1.pdf
Prompt Learning比较火热,号称NLP的第四范式,
-
作者发现BERT在语义相似度方面表现不好,主要由:static token embeddings biases和ineffective layers,而不是high cosine similarity of the sentence embedding。static token embedding是在bert结构中,输入进block前,通过embedding layer产生的结果,这里强调是静态的embedding,就是embedding metrics中每个token都唯一对应的embedding,是不随句子环境而变化的。至于ineffective layers就很好理解了,就是bert中堆叠的block结构,比如bert-base中的12层。作者认为这些结构,对语义相似度的表征这个方面是无效的。
-
Anisotropy(各向异性):上篇我们已经提到,词向量是有维度的,每个维度上基向量单位向量长度不一样,就是各向异性的。这会造成计算向量相似度的时候产生偏差。如何度量Anisotropy:
作者分析了造成embedding bias的原因,除了token frequency是造成bias的原因,作者又提出了:subwords,case sentitive
图中不同词频token的分布情况,颜色越深代表词频越高,我们可以看出词频高的token,分布比较紧凑,词频低的token,分布较分散。作者输出这个图像还有一个目的是他提出各向异性(anisotropy)和偏差(bias)是不相关的,各向异性不是导致偏差的原因。 Embedding bias意思是映射分布被一些不相关的信息所干扰,是可以用降维的方式去可视化的。
更多细节参考原论文,Prompt效果就不赘述了,百度开发的UIE模型在NLP就很强大!
4.2.8 SNCSE(Unsupervised):2022.01
https://arxiv.org/abs/2201.05979
SNCSE同样是由微软团队提出,主要是针对以上方法存在的问题:当前对比学习的数据增强方式,获取的正样本都极为相似,导致模型存在特征抑制,即模型不能区分文本相似度和语义相似度,并更偏向具有相似文本,而不考虑它们之间的实际语义差异。
为了减轻特征抑制,该论文提出了通过软负样本结合双向边际损失的无监督句子嵌入对比学习方法。其中,软负样本,即具有高度相似,但与原始样本在语义上存在明显的差异的样本。双向边际损失,即通过扩大原始样本与正例和原始样本与软负例之间的距离,使模型更好地学习到句子之间的语义差别。 软负样本构造:为原文本添加显示的否定词。
- 在获取句子表征时,受PromptBERT启发,通过三种模板表示原样本、正样本和软负样本:
4.2.9 DiffCSE(Unsupervised):2022.04
https://arxiv.org/pdf/2204.10298.pdf
结合句子间差异的无监督句子嵌入对比学习方法——DiffCSE主要还是在SimCSE上进行优化(可见SimCSE的重要性),通过ELECTRA模型的生成伪造样本和RTD(Replaced Token Detection)任务,来学习原始句子与伪造句子之间的差异,以提高句向量表征模型的效果。
其思想同样来自于CV领域(采用不变对比学习和可变对比学习相结合的方法可以提高图像表征的效果)。作者提出使用基于dropout masks机制的增强作为不敏感转换学习对比学习损失和基于MLM语言模型进行词语替换的方法作为敏感转换学习「原始句子与编辑句子」之间的差异,共同优化句向量表征。
在SimCSE模型中,采用pooler层(一个带有tanh激活函数的全连接层)作为句子向量输出。该论文发现,采用带有BN的两层pooler效果更为突出,BN在SimCSE模型上依然有效。
- ①对于掩码概率,经实验发现,在掩码概率为30%时,模型效果最优。
- ②针对两个损失之间的权重值,经实验发现,对比学习损失为RTD损失200倍时,模型效果最优。
参考链接:https://blog.csdn.net/PX2012007/article/details/127696477
4.2.10 小结
SimCSE以来几种比较重要的文本增强式的对比学习算法,按时间顺序,理论上应该是距离越近的算法效果越好,但使用时,还是要结合具体的业务场景,算法没有好坏,还是用看怎么用。对于有些内容,可能叙述的不是很细致或是需要一定的知识铺垫,感兴趣的同学可以针对性的研读论文和辅助其他资料。当然,算法层出不穷,更新很快,后续出现比较重要的对比学习算法。
5.文本匹配常见思路(技巧提升)
- TextCNN/TEXTRNN
- Siamese-RNN
- 采用多种BERT类预训练模型
- 对单模型进行调参
- 多模型融合
- BERT后接上RCNN/RNN/CNN/LSTM/Siamese等等
5.1方案一
特征工程
-
数据清洗:大赛给的数据比较规整,数据清洗部分工作不多,简单做了特殊字符处理等操作。
-
数据增强
- 传递闭包扩充(标签传递)
根据IF A=B and A=C THEN B=C的规则,对正样本做了扩充增强。
根据IF A=B and A!=C THEN B!=C的规则,对负样本做了扩充增强。
在对负样本进行扩充后, 正负样本比例从原始的1.4:1, 变成2.9:1。 所以又对负样本进行了下采样, 是的正负样本比例1:1。
- 同义词替换
使用开源包synormise的效果不太好, 后面可以尝试使用公开医学预料训练word2vec模型来做同义词替换(时间问题, 没有尝试)。
随机删除,随机替换, 随机交换 句式比较短, 随机删除更短。 很多query仅仅相差一个单词, 随机替换改变语义。 多数属于问句, 随机交换改变了语义。
- 模型选择
在预训练模型的基础上通过对抗训练增强模型的泛化性能。
-
BRET Bert的一个下游基础任务语句对分类(Sentence Pair Classification Task), [CLS] Bert的输出有一个维度的向量表示
-
BERT+CNN(LSTM) 将BERT的输出特征作为文本的表示向量, 然后后面再接上LSTM或者CNN(效果下降)
-
BERT+siamese 将大赛提供的category信息利用上, 借用孪生网络的思想与两个Query进行拼接(效果下降)。
- 结果分析
- 单模型线上效果
目前所训练的模型中:
小模型中BERT-wwm-ext表现是最好的,
大模型中RoBERTa-large-pair表现最好。
在现有的资源和模型上, 对单模型的参数寻优到达一个天花板,线上最高的分数为0.9603。后面开始探索多模型融合。
- 多模型融合线上效果
将不同类型的预训练模型作为基模型进行多模型融合。基模型的挑选准则基于单模型的线上提交效果,从不同类型的单模型中挑选线上表现最好的参数, 重新训练融合。
基模型:
BERT-wwm-ext + FGM
RoBERTa-large-pair + FGM
Ernie(BaiDu)+ FGM
模型融合策略使用的是averaging, 为了降低差别比较小的模型对结果的影响,采用sigmoid反函数的方式进行ensemble。
关于对抗训练在NLP中的作用,引用大佬的一句话叫缘,妙不可言~
5.2方案二
- 探索分析
文本长度:训练集和验证集分布类似,大都集中在10-20个字
标签分布
总体思路
- 数据划分
采用kfold交叉验证(右边的划分方式)
•利用全部数据,获得更多信息
•降低方差,提高模型性能
- 模型设计
二分类交叉熵损失函数:
- 模型融合
小模型同时加入CHIP2019数据训练
模型 | 特点 | 权重 | 加入外部句对数据 |
---|---|---|---|
BERT-wwm-ext | 全词Mask | 1 | YES |
Ernie-1.0 | 对词、实体及实体关系建模 | 1 | YES |
RoBERTa-large-pair | 面向相似性或句子对任务优化 | 1 | NO |
- 数据预处理
对称扩充、传递扩充(注意要保持原来的分布,否则会过拟合)
- 训练
- 三种结构:(实际使用差别不大,第一种又好又简单)
- 对抗训练
#代码来自苏剑林bert4keras
def adversarial_training(model, embedding_name, epsilon=1):
"""给模型添加对抗训练
其中model是需要添加对抗训练的keras模型,embedding_name
则是model里边Embedding层的名字。要在模型compile之后使用。
"""
if model.train_function is None: # 如果还没有训练函数
model._make_train_function() # 手动make
old_train_function = model.train_function # 备份旧的训练函数
# 查找Embedding层
for output in model.outputs:
embedding_layer = search_layer(output, embedding_name)
if embedding_layer is not None:
break
if embedding_layer is None:
raise Exception('Embedding layer not found')
# 求Embedding梯度
embeddings = embedding_layer.embeddings # Embedding矩阵
gradients = K.gradients(model.total_loss, [embeddings]) # Embedding梯度
gradients = K.zeros_like(embeddings) + gradients[0] # 转为dense tensor
# 封装为函数
inputs = (model._feed_inputs +
model._feed_targets +
model._feed_sample_weights) # 所有输入层
embedding_gradients = K.function(
inputs=inputs,
outputs=[gradients],
name='embedding_gradients',
) # 封装为函数
def train_function(inputs): # 重新定义训练函数
grads = embedding_gradients(inputs)[0] # Embedding梯度
delta = epsilon * grads / (np.sqrt((grads**2).sum()) + 1e-8) # 计算扰动
K.set_value(embeddings, K.eval(embeddings) + delta) # 注入扰动
outputs = old_train_function(inputs) # 梯度下降
K.set_value(embeddings, K.eval(embeddings) - delta) # 删除扰动
return outputs
model.train_function = train_function # 覆盖原训练函数
写好函数后,启用对抗训练只需要一行代码
adversarial_training(model, 'Embedding-Token', 0.5)
- 预测
- 算数平均→几何平均→sigmoid平均(用反函数取出sigmoid/softmax归一化之前的状态做平均,信息量更大,提升明显)
-
分类阈值微调(0.47)
-
伪标签
5.3 更多方案
更多方案就不一一展开了,参考下方链接:
https://tianchi.aliyun.com/notebook/101626
https://tianchi.aliyun.com/notebook/101648
https://tianchi.aliyun.com/notebook/101648
参考链接:
https://tianchi.aliyun.com/competition/entrance/231776/forum
https://tianchi.aliyun.com/notebook/102057
6.特定领域知识图谱(Domain-specific KnowledgeGraph:DKG)融合方案(重点!)
在前面技术知识下可以看看后续的实际业务落地方案和学术方案
关于图神经网络的知识融合技术学习参考下面链接:PGL图学习项目合集&数据集分享&技术归纳业务落地技巧[系列十]
7.总结
本项目主要围绕着特定领域知识图谱(Domain-specific KnowledgeGraph:DKG)融合方案:技术知识前置【一】-文本匹配算法、知识融合学术界方案、知识融合业界落地方案、算法测评KG生产质量保障讲解了文本匹配算法的综述,从经典的传统模型到孪生神经网络“双塔模型”再到预训练模型以及有监督无监督联合模型,期间也涉及了近几年前沿的对比学习模型,之后提出了文本匹配技巧提升方案,最终给出了DKG的落地方案。这边主要以原理讲解和技术方案阐述为主,之后会慢慢把项目开源出来,一起共建KG,从知识抽取到知识融合、知识推理、质量评估等争取走通完整的流程。