SimCSE:NLP中的对比学习

Posted LolitaAnn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SimCSE:NLP中的对比学习相关的知识,希望对你有一定的参考价值。

论文简介

论文链接:SimCSE: Simple Contrastive Learning of Sentence Embeddings

如果大家了解对比学习的话就好办了,这篇文章就是将对比学习应用到了自然语言处理领域。起初对学习先是用在图像领域的。如果你了解的话就可以继续往下看,如果你不了解的话我建议是先了解一下对比学习。
另外推荐几篇我写的对比学习的文章。

  1. 诸神黄昏时代的对比学习
  2. “军备竞赛”时期的对比学习好。

无监督获取句子向量:

有监督的方式主要是:


对于对比学习来说,最重要是如何构造正负样本。

在图像中有多种构造对比学习的样本,比SimCLR中提到的:反转、局部裁剪、局部显出、裁剪翻转、调整饱和度、调整颜色、使用各种滤波器比如最大值滤波器,最小值滤波器、锐化滤波器。

在自然语言处理中也有很多的数据增广方式,但是他们对句子的影响都特别大。会严重降低对比学习的效果。为了解决这个问题SimCSE模型提出了一种通过随机采样dropout mask的操作来构造正样本的方法。模型使用的是BERT,每次出来的Dropout是不同的。随机dropout masks机制存在于模型的fully-connected layers和attention probabilities上,因此相同的输入,经过模型后会得到不同的结果。所以只需要将同一个句子两次喂给模型就可以得到两个不同的表示。使用这种方法产生出来的相似样本对语义完全一致,只是生成的embedding不同而已,可以认为是数据增强的最小形式。比其他的数据增强方法都要好很多。

什么是dropout

首先我们要了解什么是dropout。dropout在深度学习中通常被用来防止模型过拟合。dropout最初由Hinton组于2014年提出。可以看一下我之前的文章:模型泛化 | 正则化 | 权重衰退 | dropout

模型为什么会过拟合。因为我们的数据集相较于我们的模型来说太小了。而我们的模型相较于我们的数据集来说太复杂。因此为了防止模型过拟合,我们可以使用dropout,让模型在训练的时候忽略一些节点。就像上图中那样。这样模型在训练数据时每次都在训练不同的网络,模型不会太依赖某些局部的特征,所以模型的泛化性更强,降低了过拟合发生的概率。

dropout和其他数据增强方法进行比较

通过dropout masks机制进行数据增强构造正例的方法。

作者在STS-B数据集上进行试验。比较dropout与其他数据增强方法的差异。

裁剪,删除和替换等数据增强方法,效果均不如dropout masks机制,即使删除一个词也会损害性能,详细如下表所示:

dropout正例与原始样本之间采用完全相同的句子,只有在向量表征过程中的dropout mask有所不同。可以视为一种最小形式的数据扩充。

不同的dropout rate

为了验证模型dropout rate对无监督SimCSE的影响,作者在STS-B数据集上进行了消融实验。从上面表格中我们可以看出当dropout rate设置为0.1的时候,模型在STS-B测试集的效果最好。

上图中$Fixed 0.1$表示对于同一个样本使用相同的dropout mask,也就是说编码两次得到的向量是一样的,可以看到这种情况下效果是最差的。

我个人感觉$Fixed 0.1$的时候能达到40%以上已经挺好的了。毕竟在我眼里可能会造成模型坍塌。

我还看了一下别人复现这篇论文的文章,复现的人说尝试了0.1 0.2 0.3,效果都差不多,最后还是选择了论文中的0.1.不。

对比学习评价指标

alignment 和 uniformity 是对比学习中比较重要的两种属性,可用于衡量对比学习的效果。

  • alignment 计算所有正样本对之间的距离,如果 alignment 越小,则正样本的向量越接近,对比学习效果越好,计算公式如下:
    $$
    \\ell\\text align \\triangleq \\underset\\left(x, x^+\\right) \\sim p\\text pos \\mathbbE\\left|f(x)-f\\left(x^+\\right)\\right|^2
    $$
  • uniformity 表示所有句子向量分布的均匀程度,越小表示向量分布越均匀,对比学习效果越好,计算公式如下:
    $$
    \\ell\\text uniform \\triangleq \\log \\quad \\mathbbEx, y \\stackreli . i . d .\\sim p_\\text data e^-2|f(x)-f(y)|^2
    $$

其中$p_data$表示数据分布。这两个指标与对比学习的目标是一致的:正例之间学到的特征应该是相近的,而任意向量的语义特征应该尽可能地分散在超球体上。

至于这个“超球体”我认为是像InstDisc中右侧这个图一样,将每个样本的特征表示映射到空间中。(个人观点,如果理解有错请各位指教。)

无监督

无监督的目标函数是这样的。看一下上边,他图中示例是把三个句子作为输入传给编码器,然后编码器会得到对应句子的embedding。输入两次会得到两次不同的embedding。一个句子和它对应增强的句子是正样本,其余的句子作为负样本。最终使用的损失函数如下:
$$
\\elli=-\\log \\frace^\\operatornamesim\\left(\\mathbfhi^zi, \\mathbfhi^zi^\\prime\\right) / \\tau\\sumj=1^N e^\\operatornamesim\\left(\\mathbfhi^zi, \\mathbfhj^zj^\\prime\\right) / \\tau
$$

有监督

使用有监督学习的一个难点,就是要找到适合构造正负样本的数据集。最终作者的选择如下:

那它的正负利是如何构造的呢。
以其中的NLI数据集为例,在这个数据集中打进一个前提。就是注释者需要手动编写一个绝对正确的句子及蕴句子。一个可能正确的句子,中立句子。和一个绝对错误的句子矛盾句子。然后这篇论文就将这个数据集进行扩展,将原来的(句子,蕴含句子)改变为(句子,蕴含句子,矛盾句子)。在这个数据集中正样本是这个句子及其包含蕴含关系的句子。负样本有两种,是这个句子包含矛盾关系的句子以及其他的句子。
损失函数如下:
$$
Li=-\\log \\frace^\\operatornamesim\\left(hi, hi^+\\right) / \\tau\\sumj=1^N\\left(e^\\operatornamesim\\left(hi, hj^+\\right) / \\tau+e^\\operatornamesim\\left(hi, hj^-\\right) / \\tau\\right)
$$

结果

对7个语义文本相似度(STS)任务进行了实验,将无监督和有监督的SimCSE与STS任务中的最先进的句子嵌入方法进行了比较,可以发现,无监督和有监督的SimCSE均取得了sota的效果,具体如下表所示:

因为SimCSE做的是一个句子表征的任务,即获得更好的句子的embedding,实验效果如上图。作者使用基于BERT和基于RoBERTa的SimCSE分别与Baseline进行比较,均取得较好的效果。

下边是SimCSE使用不同版本的BERT及其变体做出的模型,对应的模型可以直接从hugging face上获取.

Model Avg. STS
princeton-nlp/unsup-simcse-bert-base-uncased 76.25
princeton-nlp/unsup-simcse-bert-large-uncased 78.41
princeton-nlp/unsup-simcse-roberta-base 76.57
princeton-nlp/unsup-simcse-roberta-large 78.90
princeton-nlp/sup-simcse-bert-base-uncased 81.57
princeton-nlp/sup-simcse-bert-large-uncased 82.21
princeton-nlp/sup-simcse-roberta-base 82.52
princeton-nlp/sup-simcse-roberta-large 83.76

代码实践

既然它的效果这么好,如何快捷的在电脑上使用SimCSE呢?

先安装一下:

pip install simcse

用这两行代码加载模型。我上面那个表格里写了不同的版本, SimCSE("在这里填写不同版本")

from simcse import SimCSE
model = SimCSE("princeton-nlp/sup-simcse-bert-base-uncased")

既然是用来做sentence embedding的,那先看一下他怎么给句子编码:

embeddings = model.encode("A woman is reading.")

它反应比较慢,你需要等一下它才会出结果。不出意外的话,它应该会给出一个特别长的embedding编码。

我输出一下看一下,他应该是把一个句子编码成768维度的向量。

计算两组句子之间的‎‎余弦相似性‎:

sentences_a = [A woman is reading., A man is playing a guitar.]
sentences_b = [He plays guitar., A woman is making a photo.]
similarities = model.similarity(sentences_a, sentences_b)

similarities1 = model.similarity(A woman is reading., A man is playing a guitar.)
similarities2 = model.similarity(He plays guitar., A man is playing a guitar.)

除了计算句子组之间,我还放了计算两个句子的一些相似性。最后效果显示如下:

他还可以为那一组句子构建index,构建之后你再输入一个句子,从其中进行查找。他会找到和哪个句子更为相似。并且输出相似度是多少。

sentences = [A woman is reading., A man is playing a guitar.]
model.build_index(sentences)
results = model.search("He plays guitar.")

在上一段代码中我已经计算过这两个句子之间的相似度,我们可以看到跟上一段代码中的结果是一样的。

以上是关于SimCSE:NLP中的对比学习的主要内容,如果未能解决你的问题,请参考以下文章

恒源云_[SimCSE]:对比学习,只需要 Dropout?

恒源云_[SimCSE]:对比学习,只需要 Dropout?

simCSE:论文解读

预训练句子表征——EMNLP 2021SimCSE

自然语言处理向量表示PairSupCon:用于句子表示的成对监督对比学习

NLP中文酒店评论语料文本数据分析