当前位置:网站首页>SimCLR:NLP中的对比学习

SimCLR:NLP中的对比学习

2022-07-06 08:53:00 InfoQ

论文简介

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

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

  • 诸神黄昏时代的对比学习 
  • “军备竞赛”时期的对比学习

无监督获取句子向量:

  • 使用预训练好的 Bert 直接获得句子向量,可以是 CLS 位的向量,也可以是不同 token 向量的平均值。
  • Bert-flow^[
    On the Sentence Embeddings from Pre-trained Language Models
    ],主要是利用流模型校正 Bert 的向量。
  • Bert-whitening^[
    Whitening Sentence Representations for Better Semantics and Faster Retrieval
    ],用预训练 Bert 获得所有句子的向量,得到句子向量矩阵,然后通过一个线性变换把句子向量矩阵变为一个均值 0,协方差矩阵为单位阵的矩阵。

有监督的方式主要是:

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



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

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

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

什么是dropout

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

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

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

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

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

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

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

不同的dropout rate

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

上图中

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

我个人感觉

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

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

对比学习评价指标

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

  • alignment 计算所有正样本对之间的距离,如果 alignment 越小,则正样本的向量越接近,对比学习效果越好,计算公式如下:$

    $
  • 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}}$$

其中

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

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

null

无监督

null
无监督的目标函数是这样的。看一下上边,他图中示例是把三个句子作为输入传给编码器,然后编码器会得到对应句子的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}}$$

有监督

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

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


结果

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

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

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


代码实践

既然它的效果这么好,如何快捷的在电脑上使用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维度的向量。

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

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.')

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

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

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.....

null
原网站

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/431d80efa30681bf38d18f559