文章起源 | 恒源云社区(恒源云,专一 AI 行业的共享算力平台)
原文地址 | Dropout
原文作者 | Mathor
要说2021年上半年NLP最火的论文,想必非《SimCSE: Simple Contrastive Learning of Sentence Embeddings》莫属。SimCSE的全称是Simple Contrastive Sentence Embedding
Sentence Embedding
Sentence Embedding始终是NLP畛域的一个热门问题,次要是因为其利用范畴比拟宽泛,而且作为很多工作的基石。获取句向量的办法有很多,常见的有间接将[CLS]地位的输入当做句向量,或者是对所有单词的输入求和、求均匀等。但以上办法均被证实存在各向异性(Anisotropy)问题。艰深来讲就是模型训练过程中会产生Word Embedding各维度表征不统一的问题,从而使得取得的句向量也无奈间接比拟
目前比拟风行解决这一问题的办法有:
- 线性变换:BERT-flow、BERT-Whitening。这两者更像是后处理,通过对BERT提取的句向量进行某些变换,从而缓解各向异性问题
- 比照学习:SimCSE。 比照学习的思维是拉近类似的样本,推开不类似的样本,从而晋升模型的句子示意能力
Unsupervised SimCSE
SimCSE利用自监督学习来晋升句子的示意能力。因为SimCSE没有标签数据(无监督),所以把每个句子自身视为类似句子。说白了,实质上就是\( (本人,本人) \)作为正例、\( (本人,他人)\)作为负例来训练比照学习模型。当然,其实远没有这么简略,如果仅仅只是完全相同的两个样本作正例,那么泛化能力会大打折扣。一般来说,咱们会应用一些数据扩增伎俩,让正例的两个样本有所差别,然而在NLP中如何做数据扩增自身也是一个问题,SimCSE提出了一个极为简略优雅的计划:间接把Dropout当做数据扩增!
具体来说,\( N\)个句子通过带Dropout的Encoder失去向量\( \boldsymbol{h}_1^{(0)},\boldsymbol{h}_2^{(0)},…,\boldsymbol{h}_N^{(0)} \),而后让这批句子再从新过一遍Encoder(这时候是另一个随机Dropout)失去向量\( \boldsymbol{h}_1^{(1)},\boldsymbol{h}_2^{(1)},…,\boldsymbol{h}_N^{(1)} \) ,咱们能够\( (\boldsymbol{h}_i^{(0)},\boldsymbol{h}_i^{(1)}) \)视为一对(略有不同的)正例,那么训练指标为
其中,\( \text{sim}(\boldsymbol{h}_1, \boldsymbol{h}_2)=\frac{\boldsymbol{h}_1^T\boldsymbol{h}_2}{\Vert \boldsymbol{h}_1\Vert \cdot \Vert \boldsymbol{h}_2\Vert} \)。实际上式(1)如果不看\( -\log \)和\( \tau \)的局部,剩下的局部十分像是\( \text{Softmax} \)。论文中设定\( \tau = 0.05 \),至于这个\( \tau \)有什么作用,我在网上看到一些解释:
- 如果间接应用余弦类似度作为logits输出到\( \text{Softmax} \),因为余弦类似度的值域是\( [-1,1] \),范畴太小导致\( \text{Softmax} \)无奈对正负样本给出足够大的差距,最终后果就是模型训练不充沛,因而须要进行修改,除以一个足够小的参数\( \tau \)将值进行放大
- 超参数\( \tau \)会将模型更新的重点,聚焦到有难度的负例,并对它们做相应的惩办,难度越大,也即是与\( {h}_i^{(0)} \)间隔越近,则调配到的惩办越多。其实这也比拟好了解,咱们将\( \text{sim}(\boldsymbol{h}_i^{(0)},\boldsymbol{h}_j^{(1)}) \)除以\( \tau \)相当于同比放大了负样本的logits值,如果\( \tau \)足够小,那么那些\( \text{sim}(\boldsymbol{h}_i^{(0)},\boldsymbol{h}_j^{(1)}) \)越凑近1的负样本,通过\( \tau \)的放大后会占主导
集体感觉没有严格的数学证实,单从理性的角度去思考一个式子或者一个符号的意义是不够的,因而在查阅了一些材料后我将\( \tau \)这个超参数的作用整顿成了另一篇文章:Contrastive Loss中参数\( \tau \)的了解
总结一下SimCSE的办法,个人感觉切实是太奇妙了,因为给两个句子让人类来判断是否类似,这其实十分主观,例如:“我喜爱北京”跟“我不喜爱北京”,请问这两句话到底相不类似?模型就像是一个刚出生的孩子,你教它这两个句子类似,那它就认为类似,你教它不类似,于是它当前见到相似的句子就认为不类似。此时,模型的性能或者准确度与训练过程、模型构造等都没有太大关系,真正影响模型预测后果的是人,或者说是人标注的数据
然而如果你问任何一个人“我喜爱北京”跟“我喜爱北京”这两句话相不类似,我想正常人没有说不类似的。SimCSE通过Dropout生成正样本的办法能够看作是数据扩增的最小模式,因为原句子和生成的句子语义是完全一致的,只是生成的Embedding不同而已。这样做防止了人为标注数据,或者说此时的样本十分主观
Alignment and Uniformity
比照学习的指标是从数据中学习到一个优质的语义示意空间,那么如何评估这个示意空间的品质呢?Wang and Isola(2020)提出了掂量比照学习品质的两个指标:alignment和uniformity,其中alignment计算\( x_i \)和\( x_i^+ \)的均匀间隔:
而uniformity计算向量整体散布的平均水平:
咱们心愿这两个指标都尽可能低,也就是一方面心愿正样本要挨得足够近,另一方面语义向量要尽可能地均匀分布在超球面上,因为均匀分布的信息熵最高,散布越平均则信息保留的越多。作者从维基百科中随机抽取十万条句子来微调BERT,并在STS-B dev上进行测试,试验后果如下表所示:
其中None是作者提出的随机Dropout办法,其余办法均是在None的根底上对\( x_{i}^+ \)进行扭转,能够看到,追加显式数据扩增办法均会不同水平升高模型性能,成果最靠近Dropout的是删除一个单词,然而删除一个单词并不能对uniformity带来很大的晋升,作者也专门做了个试验来证实,如下图所示:
Connection to Anisotropy
近几年不少钻研都提到了语言模型生成的语义向量散布存在各向异性的问题,在探讨为什么Contrastive Learning能够解决词向量各向异性问题前,咱们先来理解一下,什么叫各向异性。具体来说,假如咱们的词向量设置为2维,如果各个维度上的基向量单位长度不一样,就是各向异性(Anisotropy)
例如下图中,基向量是非正交的,且各向异性(单位长度不相等),计算\( x_1x \)与\( x_2 \)的cos类似度为0,\( x_1 \)与\( x_3 \)的余弦类似度也为0。然而咱们从几何角度上看,\( x_1 \)与\( x_3 \)其实是更类似的,可是从计算结果上看,\( x_1 \)与\( x_2 \)和\( x_3 \)的类似度是雷同的,这种不失常的起因即是各向异性造成的
SimCSE的作者证实了当负样本数量趋于无穷大时,比照学习的训练指标能够渐近示意为:
略微解释一下这个式子,为了不便起见,接下来将\( \frac{1}{\tau}\mathop{\mathbb{E}}\limits_{(x,x^+)\sim p_{\text{pos}}}\left[f(x)^Tf(x^+)\right] \)称为第一项,\( \mathop{\mathbb{E}}\limits_{x\sim p_{\text{data}}}\left[\log \mathop{\mathbb{E}}\limits_{x^-\sim p_{\text{data}}}\left[e^{f(x)^Tf(x^-)/\tau}\right]\right] \)称为第二项。
咱们的最终目标是心愿式(4)越小越好,具体来说,如果第一项越大、第二项越小,那么整体后果就十分小了。第一项大,则阐明正样本对之间的类似度大;第二项小,则阐明负样本对之间的类似度小,这也是咱们最终心愿看到的模型体现
接着咱们尝试着从式(1)变换到式(4),留神实际上\( f(x)=\boldsymbol{h}_{i}^{(0)},f(x^+)=\boldsymbol{h}_{i}^{(1)},f(x^-)=\boldsymbol{h}_{j}^{(1)} \)
从上面开始,就不存在严格的等于了,而是一些等价或者反比关系。例如本来\( \text{sim}(\boldsymbol{h}_1, \boldsymbol{h}_2)=\frac{\boldsymbol{h}_1^T\boldsymbol{h}_2}{\Vert \boldsymbol{h}_1\Vert \cdot \Vert \boldsymbol{h}_2\Vert} \),这里咱们把分母省略掉,改成冀望,同时将求和也改为冀望,则
咱们能够借助Jensen不等式进一步推导第二项的下界:
首先等号的局部很容易了解,就是把冀望改成了概率求和的模式,并且把\( f(x) \)和\( f(x^-) \)又改回\( \boldsymbol{h}_i,\boldsymbol{h}_j \)的模式。可能有同学不太理解Jensen不等式,这里我简略科普一下。对于一个凸函数\( f(x) \),若\( \lambda_i \ge 0 \)且\( \sum_i \lambda_i=1 \),则有
回到式(5)的证实,因为\( \log \)是凸函数,同时咱们将\( \frac{1}{m} \)看作是\( \lambda_i \),\( e^{\boldsymbol{h}_i^T \boldsymbol{h}_j/\tau} \)看作是\( x_i \),利用Jensen不等式可得
算了半天,回顾一下咱们的终极目标是要优化式(4),或者说最小化式(4)的第二项。设\( \mathbf{W} \)为\( {x_i}_{i=1}^m \)对应的Sentence Embedding矩阵,即\( \mathbf{W} \)的第\( i \)行是\( \boldsymbol{h}_i \)。那么此时优化第二项等价于最小化\( \mathbf{W}\mathbf{W}^T \)的上界。为什么?因为\( \text{Sum}(\mathbf{W}\mathbf{W}^T)=\sum_{i=1}^m \sum_{j=1}^m\boldsymbol{h}_i^T \boldsymbol{h}_j! \)假如咱们曾经标准化了\( \boldsymbol{h}_i \),此时\( \mathbf{W}\mathbf{W}^T \)的对角线元素全为1,\( \text{tr}(\mathbf{W}\mathbf{W}^T) \)为特征值之和,是一个常数。依据Merikoski (1984)的论断,如果\( \mathbf{W}\mathbf{W}^T \)的所有元素均为正值,则\( \text{Sum}(\mathbf{W}\mathbf{W}^T) \)是\( \mathbf{W}\mathbf{W}^T \)最大特征值的上界,因而,当咱们最小化第二项时,其实是在间接最小化\( \mathbf{W}\mathbf{W}^T \)的最大特征值,也就是隐式地压平了嵌入空间的奇怪谱,或者说使得嵌入空间的散布更平均
到此为止,集体感觉曾经将SimCSE核心内容讲的够分明了,至于说原论文当中的监督学习局部,这里就不多赘言,因为实质上就是批改正样本对和负样本对的定义罢了
Results
原论文的试验十分丰盛,读者能够仔细阅读原文,这里简略贴出一个试验比照图
总体后果没有什么好剖析的,通过上图咱们能够晓得SimCSE在多个数据集上都达到了SOTA,并且作者发现,在原有的训练指标的根底上退出MLM预训练指标,将两个指标的loss按比例相加\( \ell + \lambda \ell^{mlm} \) 一起训练,可能避免SimCSE遗记token级别的常识,从而晋升模型成果。这倒是让我感觉有点讶异的,做了那么多的操作,好不容易使得模型可能比拟好的提取句子级别的特色了,后果token级别的常识又遗记了,真是拆了东墙补西墙
Code
尽管实践上咱们说SimCSE是将同一个批次内的句子别离送入两个Encoder(这两个Encoder仅仅只是Dropout不同),但实现的时候咱们其实是将一个batch内的所有样本复制一遍,而后通过一次Encoder即可。假如初始输出为\( [A, B] \)两条句子,首先复制一遍\( [A, A, B, B] \),那么通过Encoder失去的句向量为\( [\boldsymbol{h}_A^{(0)}, \boldsymbol{h}_A^{(1)}, \boldsymbol{h}_B^{(0)}, \boldsymbol{h}_B^{(1)}] \),当初的问题在于,咱们的label是什么?
很显著咱们晓得如果给定\( \boldsymbol{h}_A^{(0)},\boldsymbol{h}_A^{(1)} \),他们的label是1;如果给定\( \boldsymbol{h}_B^{(0)},\boldsymbol{h}_B^{(1)} \),他们的label是1,其它状况均是0,所以给咱们能够给出上面这样一张表格(标签为1的中央是雷同句子不同Embedding的地位)
下面的表格能够转换为label:\( [1, 0, 3, 2] \)。假如原始batch内有4条句子,复制后就共有8条句子,依照上述表格的排列形式,咱们能够失去label:\( [1, 0, 3, 2, 5, 4, 7, 6] \)。依照这个法则生成label就能够了,而且这个法则其实挺显著的,就不解释了
import torchimport torch.nn.functional as Fdevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')def SimCSE_loss(pred, tau=0.05): ids = torch.arange(0, pred.shape[0], device=device) y_true = ids + 1 - ids % 2 * 2 similarities = F.cosine_similarity(pred.unsqueeze(1), pred.unsqueeze(0), dim=2) # 屏蔽对角矩阵,即本身相等的loss similarities = similarities - torch.eye(pred.shape[0], device=device) * 1e12 similarities = similarities / tau return torch.mean(F.cross_entropy(similarities, y_true))pred = torch.tensor([[0.3, 0.2, 2.1, 3.1], [0.3, 0.2, 2.1, 3.1], [-1.79, -3, 2.11, 0.89], [-1.79, -3, 2.11, 0.89]])SimCSE_loss(pred)
References
- SimCSE: Simple Contrastive Learning of Sentence Embeddings
- SimCSE: Simple Contrastive Learning of Sentence Embeddings(知乎)
- 中文工作还是SOTA吗?咱们给SimCSE补充了一些试验
- SimCSE论文解读
- SimCSE比照学习: 文本增广是什么牛马,我只须要简略Dropout两下
- 张俊林:比照学习研究进展精要
- SimCSE论文超强解析
- 超细节的比照学习和SimCSE知识点
- Bert中的词向量各向异性具体什么意思啊?