用开源的人工标注数据来增强RoFormer-Sim
By 苏剑林 | 2021-07-19 | 139671位读者 |大家知道,从SimBERT到SimBERTv2(RoFormer-Sim),我们算是为中文文本相似度任务建立了一个还算不错的基准模型。然而,SimBERT和RoFormer-Sim本质上都只是“弱监督”模型,跟“无监督”类似,我们不能指望纯弱监督的模型能达到完美符合人的认知效果。所以,为了进一步提升RoFormer-Sim的效果,我们尝试了使用开源的一些标注数据来辅助训练。本文就来介绍我们的探索过程。
有的读者可能想:有监督有啥好讲的?不就是直接训练么?说是这么说,但其实并没有那么“显然易得”,还是有些“雷区”的,所以本文也算是一份简单的“扫雷指南”吧。
前情回顾 #
笔者发现,自从SimBERT发布后,读者问得最多的问题大概是:
为什么“我喜欢北京”跟“我不喜欢北京”相似度这么高?它们不是意思相反吗?
尤其是RoFormer-Sim发布之后,类似的问题几乎一两周就出现一次。此外,不止笔者自己的科学空间交流群,别的NLP相关的群也时不时冒出差不多的问题,说明类似的疑惑是普遍存在的。
那么,怎么理解这件事呢?
首先,“意思相反”这个认知是不对的,从相似的角度来看,只有“相似”、“不相似”的说法,并没有“相反”的说法,原则上来看,没有两个绝对毫无关联的句子,所以理论上没有哪两个句子的相似度为0,更不用说没有明确定义的“相反”了。而恰恰相反,我们通常认为的“反义词”,从客观上来看,它们都算是比较相似的词,比如“喜欢”和“讨厌”,它们的共性多了去了:都是动词,都是描述情感倾向,用法也差不多,所以我们怎么能说这两个词“毫不相似”甚至“相反”?我们说它是反义词,是指它在某一个极小的维度下是对立关系,要注意,只是某个维度,不是全部,所以意味着我们这种认知本身是非客观的(这么多维度相似,只有一个维度不相似,我们就说它们是“反义词”,这还不是不客观?)。
同理,按照笔者的理解,从客观角度来看,“我喜欢北京”跟“我不喜欢北京”就是很相似的,所以模型给出的相似度高是很合理的,给出相似度低才是不合理的。当然,我不是说“我喜欢北京”跟“我不喜欢北京”在任何场景下都相似,它们确实是存在对立的维度,但问题是无监督、弱监督学习出来的都是比较客观的结果,而如果我们认为“我喜欢北京”跟“我不喜欢北京”不相似,那么就说明我们主观地挑出了我们要进行比较的维度,而不是客观的全部的维度。而既然是人的主观行为,我们不应该指望无监督、弱监督的方法能学出来,最好的办法就是标注数据来有监督学习。
所以,说白了就是:
模型没错,错的是人。如果人坚持自己没错,那就请通过标注数据有监督学习的方式来告诉模型它错了。
分门别类 #
通过上述讨论,我们应该就能理解标数据有监督的必要性了。不是所有问题都可以通过无监督、弱监督的方式解决,如果非要想无监督、弱监督的方案,其成本可能远远大于标几条数据。
至于相似度相关的中文人工标注数据,目前收集到的有三种类型:
1、是非类型:这种是比较常见的类型,主要格式是“(句子1, 句子2, 是否相似)”,这里收集到的ATEC、BQ、LCQMC、PAWSX都是这种类型;
2、NLI类型:NLI的全称是Natrual Language Inference(自然语言推理),样本格式是“(句子1, 句子2, 蕴涵/中立/矛盾)”,可以视为更为精细一点的相似度数据集,当前可以找到的中文NLI数据集是英文版翻译过来的,链接位于CNSD;
3、打分类型:这算是最精细的相似度语料,格式为“(句子1, 句子2, 相似程度)”,这个相似程度一般是比0/1更细颗粒度的等级,目前可以找到的中文数据集是STS-B,也是由对应的英文数据集翻译过来的。
由于量比较大的是前2种,所以为了处理上的方便,我们直接设置一个阈值,将第3种的STS-B数据转化为第1种情形,所以可利用的就两种数据格式:1、句子对的2分类;2、句子对的3分类。
出乎意料 #
文章开头写到,虽然是监督训练,但也不是那么“显然易得”,这主要是因为训练方式的选择有点出乎意料。简单起见,我们先以2分类的训练样本为例进行说明。
假设两个句子通过编码器后得到的句向量分别为$u,v$,由于在检索阶段我们通常使用它们的余弦值$\cos(u,v)=\frac{\langle u,v\rangle}{\Vert u\Vert \Vert v\Vert}$为相似度进行排序,所以很自然的想法就是基于$\cos(u,v)$设计损失函数,一些比较容易想到的有:
\begin{equation}\begin{aligned}
&t\cdot (\cos(u,v) - 1)^2 + (1 - t)\cdot \cos^2(u,v) \\
&t\cdot (\cos(u,v) - 1)^2 + (1 - t)\cdot (\cos(u,v) + 1)^2 \\
&t\cdot \max(0.9 - \cos(u,v), 0) + (1-t)\cdot \max(\cos(u,v) - 0.1, 0)
\end{aligned}\end{equation}
其中$t\in\{0,1\}$是该句子对的标签。上述几个loss的大致意思是让正样本对的$\cos(u,v)$尽量大,让负样本对的$\cos(u,v)$尽量小。
然而,在笔者的实验中,这样的训练和预测都一致训练方案,结果居然都不如出自InferSent、并且被Sentence-BERT沿用的一种看起来是训练和预测不一致的方案。具体来说,Sentence-BERT是将$u,v,|u-v|$(其中$|u-v|$是指$u-v$的每个元素都取绝对值后构成的向量)拼接起来做为特征,后面接一个全连接层做2分类(如果是NLI数据集则是3分类)。
当然,这只是训练方案,使用的时候,还是把句向量拿出来,用余弦相似度做检索。这样看来,InferSent、Sentence-BERT使用的这种方案,事实上属于训练和预测不一致的方案,训练的时候并没有直接涉及到$\cos(u,v)$,预测的时候却可以用$\cos(u,v)$来检索,并且表现还相当不错,所以不能不说出人意料。
闭门造车 #
对此,笔者也是百思不得其解。笔者留意到,Sentence-BERT的论文里,还比较了不同的特征拼合方式的最终效果,显示出$u,v,|u-v|$拼接的效果是最佳的,如果只保留它们的一部分,效果都将会明显下降,如下表。
受到这个表格的启发,笔者“闭门造车”地构思了一种解释。首先,我们知道,人是非常“挑剔”的,尤其是对于相似度任务,我们通常是认为比较严格的相似才算是相似,但是我们的训练数据通常没那么精准。一方面,标注本身可能存在噪声;另一方面,对于某些样本对,标注人员可能由于它们主题(而不是语义)相同就标记为正样本对。也就是说,标注数据通常没有我们要求的那么严格,如果直接用标注结果来学习我们的排序度量,那么反而会带来意外的偏差。
回看$u,v,|u-v|$拼接然后接个全连接的做法,它的打分函数相当于
\begin{equation}s = \langle u, w_1\rangle + \langle v, w_2\rangle + \langle |u-v|, w_3\rangle\end{equation}
这里的$w_1,w_2,w_3$是对应的参数向量。其中前两项打分为$\langle u, w_1\rangle + \langle v, w_2\rangle$,如果它很大,并不能说明$u,v$很接近,同理如果它很小,也不能说明$u,v$差得很远,它的作用更像是一个“主题分类”模型,用于识别$u,v$的主题是否一致;而对于第三项,我们知道$|u-v|=0\Leftrightarrow u=v$,所以第三项是有能力判断两个向量的近似程度的,它也许代表了真正的“语义相似”。
综合起来,我们就可以认为,$u,v,|u-v|$拼接然后接个全连接的做法,它既包含了判断两个句子主题是否一致的打分,也包含了两个句子语义相似的打分,它将“主题”和“语义”分离开来,增强了模型对数据的容错性,从而使得最终学习出来的向量更能体现出较为纯粹、精准的“语义”。
鱼与熊掌 #
通过Sentence-BERT的方案,利用开源的相似度数据集,我们可以学习到一个效果还不错的句向量模型,也即检索模型,利用它抽取特征并且用余弦相似度作为度量可以得到不错的结果。但问题是,SimBERT、RoFormer-Sim从来就不是单纯的检索模型,它希望“鱼与熊掌兼得”——既具备好的检索效果,又具备生成相似句的能力。
为此,我们通过上述方式训练好一个Sentence-BERT后,通过《SimBERTv2来了!融合检索和生成的RoFormer-Sim模型》介绍的方案,把Sentence-BERT的检索效果蒸馏到RoFormer-Sim上去,从而在保留相似句生成的基础上提高检索模型的效果。此外,同尺寸模型之间的蒸馏往往还能提升一点效果,所以我们蒸馏后的RoFormer-Sim的检索效果,其实还比直接训练得到的Sentence-BERT效果要好些。
效果演示 #
我们把用标注数据训练好的RoFormer-Sim开源如下(文件名带-ft的权重):
下面是《无监督语义相似度哪家强?我们做了个比较全面的评测》中的几个任务的测试结果(测试集):
\begin{array}{c|ccccc}
\hline
& \text{ATEC} & \text{BQ} & \text{LCQMC} & \text{PAWSX} & \text{STS-B} \\
\hline
\text{RoFormer-Sim} & 39.27 & 48.31 & 72.30 & 6.70 & 71.75 \\
\text{RoFormer-Sim-FT} & 51.71 & 73.48 & 79.56 & 62.84 & 78.28 \\
\hline
\text{RoFormer-Sim-small} & 37.08 & 46.83 & 71.27 & 5.8 & 71.29 \\
\text{RoFormer-Sim-FT-small} & 51.21 & 73.09 & 78.88 & 56.41 & 76.33 \\
\hline
\end{array}
可以看到效果有明显提升,并且small版本也有相当不俗的表现。当然,经过了监督训练,有提升是必然的,这个表格的对比意义不算大。但对于用户来说,有现成的模型可用就行了,管它是怎么来的对吧。读者可能更关心的是,这个新模型有没有解决之前检索模型的“痛点”,比如能不能拉开“我喜欢北京”跟“我不喜欢北京”的差距?下面就来看一些例子(base版,另外small版的结果相差无几):
>>> similarity(u'今天天气不错', u'今天天气很好')
0.9769838
>>> similarity(u'今天天气不错', u'今天天气不好')
0.62359834
>>> similarity(u'我喜欢北京', u'我很喜欢北京')
0.9921096
>>> similarity(u'我喜欢北京', u'我不喜欢北京')
0.5291042
>>> similarity(u'电影不错', u'电影很好')
0.96764225
>>> similarity(u'电影不错', u'电影不好')
0.6312722
>>> similarity(u'红色的苹果', u'绿色的苹果')
0.6974633
>>> similarity(u'给我推荐一款红色的车', u'给我推荐一款黑色的车')
0.7191832
>>> similarity(u'给我推荐一款红色的车', u'推荐一辆红车')
0.9866457
>>> similarity(u'给我推荐一款红色的车', u'麻烦来一辆红车')
0.9460306
从例子可以看出,经过有监督训练后,模型确实能体现出更符合普通常规认知的相似度打分,比如加了“不”字后相似度明显降低,经过对比我们发现这部分效果主要由NLI数据集带来的;还有,对“红”、“黑”等颜色也会更加敏感,尤其是最后三个例子,体现出它的检索排序结果更符合我们常规的意图识别场景。
本文小结 #
本文介绍了我们利用标注数据增强RoFormer-Sim的过程,并开源了相应的训练好的模型,使得中文相似度模型有了一个效果更好的开源可用开源的baseline。
转载到请包括本文地址:https://www.kexue.fm/archives/8541
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Jul. 19, 2021). 《用开源的人工标注数据来增强RoFormer-Sim 》[Blog post]. Retrieved from https://www.kexue.fm/archives/8541
@online{kexuefm-8541,
title={用开源的人工标注数据来增强RoFormer-Sim},
author={苏剑林},
year={2021},
month={Jul},
url={\url{https://www.kexue.fm/archives/8541}},
}
July 19th, 2021
说白了,压根没法用
July 19th, 2021
效果上还是有提升的,最后引入了负向配对进行了提升(作者做了蒸馏)。之前看到过一个 neg bert 通过 NER 的方式识别 not 等负向条件来微调的。因此看起来针对 negation 的处理,还是得对“语义”进行人工的监督学习一下。
就是希望通过监督学习来结束这个坑了~
July 19th, 2021
请问苏大佬,在进行分词的时候segment_ids有什么作用呢?
token_ids, segment_ids = tokenizer.encode(text, maxlen=maxlen)
我读源码,发现 segment_ids = [0] * len(token_ids)
segment_ids 是全0,对这个部分的想法,没有理解。
感谢回答
建议读一下BERT的论文,里面有介绍来源。
好的,谢谢,
July 20th, 2021
苏神,分门别类话题下面的弱监督有错字
谢谢,已经修正。
July 20th, 2021
为什么训练预测一致的方案在这里行不通呢?体现在cosine中,语义相近,cosine值越小,也没毛病.
你说SimCSE的方案?靠Dropout构造出来的只能算是非常弱的正样本对,自然是比不上人工标注的。
文中还是没有说到bert-cls输出搭配cosine,相比bert-cls搭配u-v,前者是不是一个不太合适的搭配?这个搭配是不是有问题?bert很强,cosine很弱。
什么直觉让你觉得“bert-cls输出搭配cosine”有问题?cosine弱在哪?|u-v|又强在哪?
July 26th, 2021
如果最后检索是需要看cosine相似度的话,训练和预测阶段都用cosine做损失函数效果会不会更好一些?(即训练时让相似句子对的cosine similarity靠近1,不相似靠近0这样)
请问苏神怎么看?(因为在具体实践中发现用cosine做损失比用u,v,u-v稍微好,当然这跟数据源也有关系。)
请认真看本文,本文已经给出了自己的答案:不会。
当然,一切皆有可能,只是我的实验结果显示不会。
July 26th, 2021
关于Sentence-BERT使用的训练和预测不一致的方案,我有一个猜想,纯属瞎猜的哈哈哈:
首先,观察 bert 句向量 cosine 相似度值的分布,可以发现其主要分布在 (0.5, 1)之间;又因为cosine 函数的取值范围是[-1,1],可以推测:bert 句向量在向量空间中的分布不是各向异性的,而是集中在某个方向的临域中。
其次,设计训练目标时,让负样本对的cos(u,v)尽量小(尽量接近-1)会带来问题。
设有一负样本对 u、v; 句子 u 有相似句 u',句子 v 有相似句 v'。负样本对 u、v 被纳入训练集中,u'、v' 不在训练集。
这个训练目标,相当于将句子 u 的句向量 和句子 v 的句向量拉扯开,使彼此之间成反方向。此时,原本值接近1的 cos(u,u'), cos(v,v'),值将不再接近1。 由于训练集的大小是有限的,当训练结束后,bert 句向量在向量空间可能并不会形成均匀分布,而是乱糟糟的
最后,感觉让正样本对的cos(u,v)尽量接近1是正确的,但让负样本对的cos(u,v)尽量接近-1有些不合理
1、按道理,让负样本对的cos值为0是最合理的,因为高维空间中两个随机向量几乎都是垂直的,参考:https://kexue.fm/archives/7076
2、事实上,实验结果跟你的猜测相反,让负样本对的cos值为-1效果是最好的(在直接学习cos值的方案中),当然,我们loss让它的目标为-1,它并不能学到-1,只能学到尽可能小。
interesting,我看到的cosine值最低的也是在-0.2左右。
July 26th, 2021
你好,公式2是一个很有意思解释,谢谢,我根据目前所做以及一些其他论文,延伸了一下,想听听你的看法。除了concat(u,v,|u-v|),u*v也能考虑,但是在他们(SBERT)实验中性能下降了,他们也没有出特定的解释,如果按照你的公式2,是不是可以解释成u*v影响了本身u,v部分”主题“与|u-v|部分”语义“的权重的平衡?其次,在ConSERT(美团)的论文中,在一些Loss上,比如SimCLR,他们用了一个叫做max_sqaure的向量torch.max(rep_a, rep_b).pow(2),也拼接到了这个大向量上,在我的实验里也是有提升的,但是我不知道为什么会这样,哈哈,如果你能回答,感谢!最后一个小问题是,相比于这种向量拼接再softmax,为什么不直接考虑triplet loss这类的,可以理解成”hard negative"的句子对进行训练, e.g, 今天天气不错,今天天气不好,是不是也能达到类似的效果?
1、这里的思想是分为“主题识别”和“语义识别”两部分,主题识别不要涉及到向量的比较,而语义识别则需要比较向量。我们有$|u-v|=0\Leftrightarrow u=v$,所以$|u-v|$有能力完成这一点,但是$u*v$不大好做到这一点(即无法认为$u*v$等于某个值时就有$u=v$);
2、没有用triplet loss是因为根本没有triplet形式的数据啊,我们只有正样本对和负样本对,如果基于这些正负样本对去构建triplet数据,又感觉太局限(依赖于全匹配和传递性),如果随机采样负样本的方式,又感觉负样本太没针对性;
3、事后来看,FlatNCE这个改进(https://kexue.fm/archives/8586),可能可以直接学习cos并且保持效果,不过现在还没试。
谢谢回复。我提到triplet loss其实是想问一下这个Loss是不是更直观(相比于concat的loss)用于语义比较,因为这种hard negatives可以直接用来对比。嗯,关于你说的第2点,我一般用来一个很强的预训练语义模型做一个筛选得到hard negative,其次就是对于其他的负样本直接抽样,最终合成的negative和postive达到一定的比例。我一般是同一个数据,进行变化后,做成多任务(concat(u,v, |u-v|),trinary-classfiication,加上变种的triplet loss, 最终testing还是用cosine, 也是有效果提升的。我跟ConSERT那边确认了,他们在所有实验中(包括论文最终结果)都是用了这个torch.max(rep_a, rep_b).pow(2)trick,也许这个和u*v某种程度都帮助了”主题识别“部分。
1、需要精心构建hard negative就没啥意思了,累啊~
2、sentence-bert自己的论文显示,加入u*v或单独使用u*v都有下降,这些特征都很玄学,我感觉有个大致的解释也就是了,懒得深究了。
你好,【一般用来一个很强的预训练语义模型做一个筛选得到hard negative】,这个是怎么做得,能具体介绍一下吗?
July 27th, 2021
请问苏神loss中1.5代表什么呢,我理解是区分正反例的阈值,不知道是否正确
这是多任务的一种合并写法而已。
本文已经说了,训练数据有两种:2分类的和3分类的,这严格来讲属于两个任务,所以我合并成一个5分类来做,并且识别是哪一种类型(标签小于1.5,即0、1为2分类数据;标签大于1.5,即2、3、4为3分类数据),做对应的mask(如果不做mask,那就是一个互斥的5分类问题,不符合事实;做了mask之后,本质上等价于一个2分类和一个3分类的多任务问题。)。
明白了,感谢苏神回复~
代码实现的真的太妙了
July 27th, 2021
一个相似度模型养活了我们一个算法部门......
哈哈哈,RoFormer-Sim表示非常荣幸~