SimCSE简介以及核心代码详解——无监督文本向量抽取

今年NLP 领域出现了一个比较火的模型叫SimCSE,文章的全称是Simple Contrastive Learning of Sentence Embeddings。直译过来就是:通过一种简单的对比学习去做句子嵌入。而且在可以不要监督数据的情况下,生成质量较好的句子向量。以往无论是word2vec词向量做平均,还是预训练的bert生成的句子向量等无监督的方式生成的句子向量都是太好。SimSCE可以算在无监督句向量的抽取领域又往前迈了一步。

对比学习

在介绍SimCSE之前,我们先来了解一个叫对比学习的东西。对比学习顾名思义就是需要通过对比来学习,这里笔者拿图像领域的特征抽取来介绍一下对比学习的整个过程,如下图所示:
(1)将一只猫的图X,数据增强的方式生成另一张猫的图片作为正例X+,构建正例样本对,选择一只狗作为负例X-。
(2)将这个正负例样本组(X,X+,X-)同时输入到一个模型中进行特征抽取。
(3)优化对比损失,将X和X+的在特征空间里面的距离拉近,同时将X和X-在特征空间中的距离拉远。

这样整个对比学习的过程就介绍完了,通过对比学习我们就可以得到高质量的特征表示。至于如何构建正负例样本组(X,X+,X-),如何两个样本在衡量样本空间距离等,都是值得探索和思考的问题。

对比学习

通过对比学习方式,可以达到下面两个目的:
(1)相似图片早特征空间靠的很近,这就实现了相似图片,它们的特征也比较相近
(2)而不相似的图片在特征空间中距离会非常大,导致这些具有差异性得图片会比较分散的分布在特征空间中,
从另一个角度上来思考,就是特征空间的信息会尽可能的多。


特征空间

从对比学习的整个流程来看,我们可以发现在没有标注数据的时候,只需要做一下数据增强构建正例样本对,就可以很方便的使用对比学习抽取出比较有用的特征向量。

SimSCE的设计思路

而SimSCE 就是一个采用对比学习框架进行句子嵌入的方法,SimCES提出了有监督和无监督这两种对比学习方式来进行句子嵌入。只不过SimSCE 构建正例样本的方法非常的简单,但是却能够产生奇效。那我们就来看看SimCSE 到底是如何构建正样本对的。

SimCSE
无监督的SimSCE:采用dropout做数据增强,来构建正例

如上图所示,过程如下:
(1) 将同一个句子输入到模型两次,得到两个不同的特征向量。(这里就有人会疑问了,为什么一个句子输入到模型两次会得到不同的向量呢,这是由于模型中存在dropout 层,神经元随机失活会导致同一个句子在训练阶段输入到模型中得到的输出都会不一样).
(2) 在一个batch中,将同一个句子在模型中的两次输出当作正例,将其他句子的输出全部当作负例。
(3) 优化对比损失,增加正例之间的相似度,减小负例之间的相似度。

有监督的SimSCE:数据集中存在相似句子对(X,X+)这样的正例样本对

如上图所示,过程如下:
(1) 由于数据集中存在标注好的相似数据,这里只需要做一个简单的负采样,构造(X,X+,X-)三元组。
(2) 然后将这个(X,X+,X-)同时输入到文本向量抽取的模型中进行特征抽取。
(3) 优化对比损失,增加正例之间的相似度,减小负例之间的相似度。

SimCSE 对比Loss的代码详解

这里笔者解读了一下https://github.com/zhengyanzhao1997/NLP-model/tree/main/model/model/Torch_model/SimCSE-Chinese 这位大神实现的对比损失,将每一行的输出以及含义在comment 里进行了仔细的描述,希望能够帮助到初识SimCSE的同学。

无监督的SimCSE的 Loss 实现与详解

这里假设每个batch 输入给模型的样本 格式如下 :
(1) [0,1,2,3,4,5] 代表一个batch中含有六个样本
(2)其中0,1表示同一个句子,其中2,3是同一个句子,其中4,5是同一个句子
那么 模型的输出 y_pred 就是[X0,X1,X2,X3,X4,X5] 这六个句子的向量表示。
对比Loss 的详解 如下。

def compute_loss(y_pred, lamda=0.05):
    idxs = torch.arange(0, y_pred.shape[0])  # [0,1,2,3,4,5] 
   #这里[(0,1),(2,3),(4,5)]代表三组样本,
   #其中0,1是同一个句子,输入模型两次
   #其中2,3是同一个句子,输入模型两次
   #其中4,5是同一个句子,输入模型两次
    y_true = idxs + 1 - idxs % 2 * 2 #  生成真实的label  = [1,0,3,2,5,4]  
    # 计算各句子之间的相似度,形成下方similarities 矩阵,其中xij 表示第i句子和第j个句子的相似度
   #[[ x00,x01,x02,x03,x04 ,x05  ]
   # [ x10,x11,x12,x13,x14 ,x15  ]
   # [ x20,x21,x22,x23,x24 ,x25  ]
   # [ x30,x31,x32,x33,x34 ,x35  ]
   # [ x40,x41,x42,x43,x44 ,x45  ]
   # [ x50,x51,x52,x53,x54 ,x55  ]]
    similarities = F.cosine_similarity(y_pred.unsqueeze(1), y_pred.unsqueeze(0), dim=2)
    # similarities屏蔽对角矩阵即自身相等的loss
   #[[ -nan,x01,x02,x03,x04 ,x05   ]   
   # [ x10, -nan,x12,x13,x14 ,x15 ]
   # [ x20,x21, -nan,x23,x24 ,x25 ]
   # [ x30,x31,x32, -nan,x34 ,x35 ]
   # [ x40,x41,x42,x43, -nan,x45  ]
   # [ x50,x51,x52,x53,x54 , -nan ]]
    similarities = similarities - torch.eye(y_pred.shape[0]) * 1e12
    # 论文中除以 temperature 超参
    similarities = similarities / lamda
    #下面这一行计算的是相似矩阵每一行和y_true = [1,0,3,2,5,4] 的交叉熵损失
   #[[ -nan,x01,x02,x03,x04 ,x05  ]   label = 1 含义:第0个句子应该和第1个句子的相似度最高,即x01越接近1越好
   # [ x10, -nan,x12,x13,x14,x15 ]   label = 0  含义:第1个句子应该和第0个句子的相似度最高,即x10越接近1越好
   # [ x20,x21, -nan,x23,x24,x25 ]   label = 3  含义:第2个句子应该和第3个句子的相似度最高,即x23越接近1越好
   # [ x30,x31,x32, -nan,x34,x35 ]   label = 2  含义:第3个句子应该和第2个句子的相似度最高,即x32越接近1越好
   # [ x40,x41,x42,x43, -nan,x45 ]   label = 5  含义:第4个句子应该和第5个句子的相似度最高,即x45越接近1越好
   # [ x50,x51,x52,x53,x54 , -nan ]]  label = 4 含义:第5个句子应该和第4个句子的相似度最高,即x54越接近1越好
   #这行代码就是simsce的核心部分,就是一个句子被dropout 两次得到的向量相似度应该越大 
   #越好,且和其他句子向量的相似度越小越好
    loss = F.cross_entropy(similarities, y_true) 
    return torch.mean(loss)

有监督的SimCSE的Loss 详解

这里假设每个batch 输入给模型的样本 格式如下 :
(1) [0,1,2,3,4,5] 代表一个batch中含有六个样本
(2)[(0,1,2),(3,4,5)]代表二组样本,其中0,1是相似句子代表正例,0,2是不相似的句子代表负例;其中3,4是相似句子代表正例,3,5是不相似的句子代表负例

同意模型的输出 y_pred 就是[X0,X1,X2,X3,X4,X5] 这六个句子的向量表示。
对比Loss 的详解 如下。

def compute_loss(y_pred,lamda=0.05):
    row = torch.arange(0,y_pred.shape[0],3,device='cuda') # [0,3]
    col = torch.arange(y_pred.shape[0], device='cuda') # [0,1,2,3,4,5]
   #这里[(0,1,2),(3,4,5)]代表二组样本,
   #其中0,1是相似句子,0,2是不相似的句子
   #其中3,4是相似句子,3,5是不相似的句子
    col = torch.where(col % 3 != 0)[0].cuda() # [1,2,4,5]
    y_true = torch.arange(0,len(col),2,device='cuda') # 生成真实的label  = [0,2]
   #计算各句子之间的相似度,形成下方similarities 矩阵,其中xij 表示第i句子和第j个句子的相似度
   #[[ x00,x01,x02,x03,x04 ,x05  ]
   # [ x10,x11,x12,x13,x14 ,x15  ]
   # [ x20,x21,x22,x23,x24 ,x25  ]
   # [ x30,x31,x32,x33,x34 ,x35  ]
   # [ x40,x41,x42,x43,x44 ,x45  ]
   # [ x50,x51,x52,x53,x54 ,x55  ]]
    similarities = F.cosine_similarity(y_pred.unsqueeze(1), y_pred.unsqueeze(0), dim=2)
    #这里将similarities 做切片处理,形成下方矩阵
    #[[ x01,x02,x04 ,x05 ]  
    # [x31,x32,x34 ,x35 ]]
    similarities = torch.index_select(similarities,0,row)
    similarities = torch.index_select(similarities,1,col)
    #论文中除以 temperature 超参 
    similarities = similarities / lamda
   #下面这一行计算的是相似矩阵每一行和y_true = [0, 2] 的交叉熵损失
   #[[ x01,x02,x04 ,x05 ]   label = 0 含义:第0个句子应该和第1个句子的相似度最高,  即x01越接近1越好
   # [x31,x32,x34 ,x35 ]]  label = 2 含义:第3个句子应该和第4个句子的相似度最高   即x34越接近1越好
   #这行代码就是simsce的核心部分,和正例句子向量相似度应该越大 
   #越好,和负例句子之间向量的相似度越小越好
    loss = F.cross_entropy(similarities,y_true)
    return torch.mean(loss)

结语

其实除了通过dropout 两次来构造对比学习的正例样本,其实还有很多的方式构造正例,比如可以采用文本处理里面经常用到的同义词替换,回译等方式去进行正例构造,但SimCSE的作者想到用dropout这么简单的方式来构建对比学习的正例样本对,并且在很多数据集上表现不俗,不得不感叹一句——大道至简。

参考:
https://blog.csdn.net/weixin_45839693/article/details/116302914
https://kexue.fm/archives/8348
https://link.springer.com/chapter/10.1007/978-3-030-95398-0_15
https://github.com/zhengyanzhao1997/NLP-model/tree/main/model/model/Torch_model/SimCSE-Chinese

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 【概述】 SVM训练分类器的方法是寻找到超平面,使正负样本在超平面的两侧(分类正确性即“分得开”),且样本到超平面...
    sealaes阅读 10,980评论 0 7
  • 作者:林夕 本文长度为10000字,建议阅读10+分钟 对于信任和管理我们的人工智能“合作伙伴”,可解释AI则至关...
    李苏溪阅读 2,207评论 0 1
  • 1 为什么要对特征做归一化 特征归一化是将所有特征都统一到一个大致相同的数值区间内,通常为[0,1]。常用的特征归...
    顾子豪阅读 6,298评论 2 22
  • 1 为什么要对特征做归一化 特征归一化是将所有特征都统一到一个大致相同的数值区间内,通常为[0,1]。常用的特征归...
    顾子豪阅读 1,305评论 0 1
  • 首页 资讯 文章 资源 小组 相亲 登录 注册 首页 最新文章 IT 职场 前端 后端 移动端 数据库 运维 其他...
    Helen_Cat阅读 3,837评论 1 10