陌路茶色/

Sentence Embedding 调研与实践

按照 https://github.com/Separius/awesome-sentence-embedding 来,从 word embeddings,contextualized word embeddings,encoders三个部分来梳理对应的论文,并给出一些实践的经验。
后续我会详细的梳理bert的变种,并完成一次mlm和nsp的pretrain bert任务以及自定义的bert变种 pretrain任务。
【本次任务的实验数据是按sts标准来测评的,数据选择的是领域内点击共现数据】

word embeddings

这部分暂且只讲一下word2vec,其他类似glove,fasttext的方法同理,不去叙述。
word2vec 类似如下的方法就可以train一版结果:

git clone https://github.com/tmikolov/word2vec.git
cd word2vec
make

./word2vec -train "input.txt" -output "output.model" -cbow 0 -size 128 -window 20 -negative 50 -hs 0 -sample 1e-4 -threads 16 -binary 0 -iter 10

我尝试用领域的数据train了一版word2vec,因为是title的数据所以我按title计算了一下词对应的idf值作为sentence中词的权重,同时直接拿bert-base-chinese 的cls和avg的方法进行了对比,在领域内选择的若干相似title下的结果:

模型Spearman
word2vec0.6301
idf weight word2vec0.6166
bert-base-chinese CLS0.273
bert-base-chinese AVG0.6312

contextualized word embeddings

这一侧的方法我也只是简单地尝试一下,拿领域的数据(即word2vec训练的数据)pretrain mlm任务,avg的方法就已经秒了word2vec了。因为测评数据是title相似pair,因此用title数据pretrain的方法相对article的方法会更好一点:

模型Spearman
bert-base-chinese AVG0.6312
bert-wwm AVG0.6706
bert-wwm+article pretrain AVG0.7247
bert-wwm+title pretrain AVG0.7373

encoders

这一侧是我想叙述的重点,会着重叙述若干篇论文。

Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks

先贴两个图:

Augmented SBERT: Data Augmentation Method for Improving Bi-Encoders for Pairwise Sentence Scoring Tasks

贴两个图:

On the Sentence Embeddings from Pre-trained Language Models

本论文问答两个问题:
(1)为什么使用bert得到的sentence embedding在语义相似性任务上表现这么差,是因为只能提取到这一点语义信息还是说没有完全被开采出来?
(2)如果是因为BERT本身就含有足够的信息,仅仅是因为很难被直接利用,那除了监督的方法应该如何提取呢?

我们认为bert的sentence embedding应该有能力揭示sentence之间的语义相似度,但是与实验观察矛盾,语言模型限制了word embedding学习anisotropic,导致word embedding会出现一个norrow cone,即推断出sentence embedding也会遭受anisotropy,通过实验观测,我们发现bert sentence embedding空间是语义non-smoothing的,且在一些空间上很难定义,因此很难直接通过向cosine similarity这样的简单相似指标来评价。解决这个问题,提出了通过normalizing flow将bert sentence embedding分布转换到smooth,isotropic Gaussian分布,即我们学习 flow-based generative model来最大化 从 Gaussian latent variable到generating bert sentence embeddings的似然估计,以无监督的形式。
训练阶段,只有flow网络被优化,而bert参数不被改变,训练完成后,被用来transform bert sentence embedding到Gaussian空间,这种方法被叫为BERT-flow。
更多侧细节可以参考BERT-flow: Sentence-BERT + Normalizing flows,讲解的非常清楚了,唯一一点没有展开讲的就是Flow-based Generative Model,可以参考李宏毅的Flow-based Generative Model,pdfflow_note
通俗一点理解就是使用flow将bert的输出映射到了高斯分布,也就是在指定数据上做了一个归一化,这一点在你可能不需要BERT-flow:一个线性变换媲美BERT-flow中提到过,对此还专门写了一篇论文,可以参考。

SimCSE: Simple Contrastive Learning of Sentence Embeddings

这篇论文是在https://paperswithcode.com/ 网站上sts任务刷榜了,因为正好在搞CLIP,contrastive learning效果非常好,就直接拿搜索侧这边点击的数据预处理一下作为正样本pair,单GPU的batch_size设置为512,batch内其他样本作为负样本,正样本pair有6千万,用的是nn.DataParallel 8GPU跑了10个小时吧,2个epoch后eval loss就没啥变化了,自评效果非常好,比SBERT要好不少,当然也是因为SBERT我训练的时候样本太少了,才几十万吧,线上开ab实验,在random query下增加一路SimCSE召回,总的搜索有点比提升了1.2%,其他指标也提升非常可观。(之前是接入的中台召回,他们那路召回把语义召回+精排全做了,但是在通用语料上训练的模型,我这个虽然只做了语义召回,但是理论上垂域的数据训练的模型应该是要秒杀通用场景的模型,有点比大幅度提升也没毛病)

SimCSE这个我没有引入hard negative sample,看了一些论文,有提到用BM25,tf/idf,聚类等方法来筛选hard negative sample,感觉需要花点时间,就没来得及尝试,之前SBERT筛选hard sample的方法非常的粗暴,有点击的样本中title和query之间字重合度小于等于1的都被作为hard负样本。
虽然只用了几十万的数据训练SBERT模型,但是这个模型在其他场景都是有收益的,在综合搜索场景,因为我这路召回是刷的redis,没有搞实时检索(后面SimCSE模型就是走的线上实时推理,query embedding推理大概在12ms左右,hmsw检索大概在10ms以下,总的时延avg在30ms左右),导致线上有30%+的query是没有我这路召回的(这些都是random query),而且top query上我这路是比不过中台召回的(top query中台做的效果比较好,应该是加了term based recall以及后面的精排加了很多doc侧信息),因此开实验的时候基本上没啥置信收益。

部分代码如下

class TextMatchModel(nn.Module):
    def __init__(self,args):
        super(TextMatchModel,self).__init__()
        self.text_model = TextBERTModel(args)
        self.logit_scale = nn.Parameter(torch.ones([]) * np.log(1 / 0.07))
    
    def forward(self,query_ids,query_mask,title_ids,title_mask,text_inference):
        if text_inference[0][0] == torch.tensor(1):
            text_features = self.text_model(query_ids,query_mask)
            text_features = F.normalize(text_features,dim=-1)
            return text_features, torch.tensor([[0]]*text_features.size()[0])

        # print(f'image size: {images.size()}')
        query_features = self.text_model(query_ids,query_mask)
        title_features = self.text_model(title_ids,title_mask)

        query_features = F.normalize(query_features,dim=-1)
        title_features = F.normalize(title_features,dim=-1)

        logit_scale = self.logit_scale.exp()
        logits_per_title = logit_scale * title_features @ query_features.t()
        logits_per_query = logit_scale * query_features @ title_features.t()

        return logits_per_title, logits_per_query

Refs

Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks
SimCSE: Simple Contrastive Learning of Sentence Embeddings
CLINE: Contrastive Learning with Semantic Negative Examples for Natural Language Understanding
ESimCSE: Enhanced Sample Building Method for Contrastive Learning of Unsupervised Sentence Embedding
Pre-trained Language Model based Ranking in Baidu Search

留下一条评论

共有 3 条评论

  1. 阿雷安:

    大佬你好!我想问一下contextualized word embeddings这部分您用了domain的pretrain,请问数据量级大概多少呢?这种预训练的话,训练时间需要很长吗?或者说怎么知道训的差不多了?

    December 30th, 2021 at 02:14 pm 回复
    1. moluchase:

      domain 语料 我用了大概6G的文章数据,训练MLM任务,单机8卡,大概不到4天吧,transformers和bert那个github上都有example,我训练也没怎么详细看指标,一般训练1~2个epoch,可以看一下困惑度吧;可以看针对什么任务哈,比如像query/title这种,只需要找一些类似的短文本效果会更好点,比如千万条数据可能就不错了;不过如果能找到很好的pair训练simcse这种,其实也不需要pretrain。

      January 1st, 2022 at 10:15 pm 回复
      1. 阿雷安:

        不好意思才看到,多谢大佬耐心回复!(FYI:写的超级好,收获很多!)

        January 6th, 2022 at 09:02 pm 回复