SimCSE,丹琦女神把对比学习用到了NLP中了!

语言: CN / TW / HK

theme: Chinese-red

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情


本文共计2944字,阅读大概需要花费6分钟


论文简介

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

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

无监督获取句子向量: - 使用预训练好的 Bert 直接获得句子向量,可以是 CLS 位的向量,也可以是不同 token 向量的平均值。

有监督的方式主要是: - Sentence-Bert (SBERT):Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks,通过 Bert 的孪生网络获得两个句子的向量,进行有监督学习,SBERT 的结构如下图所示。


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

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

image.png

在自然语言处理中也有很多的数据增广方式,但是他们对句子的影响都特别大。会严重降低对比学习的效果。为了解决这个问题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机制,即使删除一个词也会损害性能,详细如下表所示:

image.png

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

不同的dropout rate

image.png

为了验证模型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 }}}{\mathbb{E}}\left\|f(x)-f\left(x^{+}\right)\right\|^{2} $$
  • uniformity 表示所有句子向量分布的均匀程度,越小表示向量分布越均匀,对比学习效果越好,计算公式如下: $$ \ell_{\text {uniform }} \triangleq \log \quad \mathbb{E}{x, y \stackrel{i . i . d .}{\sim} p{\text {data }}} e^{-2\|f(x)-f(y)\|^{2}} $$

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

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

无监督

image.png

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

有监督

image.png

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

image.png

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

结果

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

image.png

因为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呢?

先安装一下:

py pip install simcse

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

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

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

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

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

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

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

```python 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.') ``` 除了计算句子组之间,我还放了计算两个句子的一些相似性。最后效果显示如下: image.png

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

python sentences = ['A woman is reading.', 'A man is playing a guitar.'] model.build_index(sentences) results = model.search("He plays guitar.") 在上一段代码中我已经计算过这两个句子之间的相似度,我们可以看到跟上一段代码中的结果是一样的。

similarities2 = model.similarity('He plays guitar.', 'A man is playing a guitar.')的输出结果是0.8934233..... 现在你查询He plays guitar.它输出最相似的句子为A man is playing a guitar.相似度为0.8934233.....

image.png