基于SBERT的语义搜索,语义相似度计算,SimCSE、GenQ等无监督训练

  • 0. 由SBERT引发的一些思考
  • 1. SBERT介绍
  • 2. 基本应用
    • 2.1 语义相似度计算
    • 2.2 语义搜索
    • 2.3 聚类和主题模型
    • 2.4 图片检索
  • 3. 无监督方法的训练
    • 3.1 SimCSE
    • 3.2 TSDAE
    • 3.3 GenQ
    • 3.4 CT

0. 由SBERT引发的一些思考

从去年sentence-transformer的概念提出,到今年上半年对比学习在深度学习领域广受关注,尤其是SimCSE等方法,在无监督和小样本的场景下取得了很大的成功。无论是Sentence Bert还是SimCSE其原理都非常朴素,实现起来也很容易。

当时由于sentence-transformer模块的环境配置问题(不想把transformers库的版本升到太高),就自己用bert4keras分别写了单塔和双塔结构的sentence bert,在STS数据集,以及STS的中文翻译版本上进行了训练,因为参考论文的描述和代码是一个编码器,但是论文的图上好像是两个编码器,就对单塔和双塔结构分别做了实验,结果是差别并不明显。所以个人认为在语义空间预训练模型的微调这类任务上没有必要耗费近乎多一倍的参数量去搭建双塔结构。

但是前不久在看SBERT的官网的时候发现,sentence-transformer已经更新到了2.0版本,并且在huggingface上上传了很多预训练模型,新增了一些具体应用场景的样例,所以在此决定对其中的内容进行介绍,主要是搬运翻译SBERT的官方说明文档。不得不承认,开源社区的力量实在是太强大,短短几个月的时间,已经有了很大的发展。之前我个人主要在用keras,记得苏神说过,torch能做到的事情,keras同样能够做到。我对这句话一直深信不疑,但问题是预训练模型的发展速度和huggingface社区的活跃远超出我的预期,如果继续用keras就不得不维护两套代码,并且在两种风格上不停地迁移,耗费很多时间。所以还是决定放弃做bert4keras的信徒,换用transformers作为主要的工具。

1. SBERT介绍

这篇博客主要是对SBERT的官方文档的搬运和介绍。
SBERT官方帮助文档:www.sbert.net
sentence-transformer GitHub:https://github.com/UKPLab/sentence-transformers
Huggingface上预训练模型地址:https://huggingface.co/sentence-transformers

官网的介绍已经比较详细,更多具体的应用实例可以参考git上example,对于一些常用的应用,在这篇博客中也进行了整理。

sentence-transformer是基于huggingface transformers模块的,如果环境上没有sentence-transformer模块的话,只使用transformers模块同样可以使用它的预训练模型。在环境配置方面,目前的2.0版本,最好将transformers,tokenizers等相关模块都升级到最新,尤其是tokenizers,如果不升级的话在创建Tokenizer的时候会报错。

目前sentence-transformers一共公开了98个预训练模型。

在预训练模型的选择上,如果是要求准确度的话,就选择mpnet-base-v2(这个模型好像还在flickr30k等数据集上进行了训练,可以用于对图像的编码,我还没有尝试使用)。如果应用场景是中文的话,可以选择multilingual相关的模型,这个模型目前支持50多种语言,以语义相似度计算为例,多语种的模型可以比较中文和英文之间的语义相似度,但是目前还没有专门的中文预训练模型。如果对模型效率有要求,那就选择distil相关的模型。

如果是对称语义搜索问题(query和answer的长度相近,例如两句话比较语义相似度)则采用https://www.sbert.net/docs/pretrained_models.html#sentence-embedding-models中给出的预训练模型;
而如果是非对称语义搜索问题(query很短,但是需要检索出的answer是一篇比较长的文档),则采用https://www.sbert.net/docs/pretrained-models/msmarco-v3.html中给出的模型。

2. 基本应用

2.1 语义相似度计算

预训练模型在作为编码器使用时非常简单。

from sentence_transformers import SentenceTransformer, util
# 【创建模型】
# 这里的编码器可以换成mpnet-base-v2等
# 模型自动下载,并在/root/.cache下创建缓存
# 如果是想加载本地的预训练模型,则类似于huggingface的from_pretrained方法,把输入参数换成本地模型的路径
model = SentenceTransformer('paraphrase-MiniLM-L12-v2')
# model= SentenceTransformer('path-to-your-pretrained-model/paraphrase-MiniLM-L12-v2/')# 计算编码
sentence1 = 'xxxxxx'
sentence2 = 'xxxxxx'
embedding1 = model.encode(sentence1, convert_to_tensor=True)
embedding2 = model.encode(sentence2, convert_to_tensor=True)# 计算语义相似度
cosine_score = util.pytorch_cos_sim(embedding1, embedding2)

除了像上面这样计算两句话的语义相似度,还可以比较两个list

sentences1 = ['The cat sits outside','A man is playing guitar','The new movie is awesome']sentences2 = ['The dog plays in the garden','A woman watches TV','The new movie is so great']#Compute embedding for both lists
embeddings1 = model.encode(sentences1, convert_to_tensor=True)
embeddings2 = model.encode(sentences2, convert_to_tensor=True)#Compute cosine-similarits
cosine_scores = util.pytorch_cos_sim(embeddings1, embeddings2)

如果是希望在一堆输入的文字中找出相近的话:

# Single list of sentences
sentences = ['The cat sits outside','A man is playing guitar','I love pasta','The new movie is awesome','The cat plays in the garden','A woman watches TV','The new movie is so great','Do you like pizza?']# Compute embeddings
embeddings = model.encode(sentences, convert_to_tensor=True)# 两两计算相似度
cosine_scores = util.pytorch_cos_sim(embeddings, embeddings)# 找到相似度最高的句对
pairs = []
for i in range(len(cosine_scores)-1):for j in range(i+1, len(cosine_scores)):pairs.append({'index': [i, j], 'score': cosine_scores[i][j]})# 根据相似度大小降序排列
pairs = sorted(pairs, key=lambda x: x['score'], reverse=True)for pair in pairs[0:10]:i, j = pair['index']print("{} \t\t {} \t\t Score: {:.4f}".format(sentences[i], sentences[j], pair['score']))

2.2 语义搜索

语义搜索与相似度计算其实是一样的,只是把sentences1看做是query,把sentences2看做是corpus,然后还是利用util.pytorch_cos_sim()去计算相似度,再返回相似度最高的topk即可。
写了一个简单的方法:

def semantic_search(query, corpus_or_emb, topk=1, model=model):""":param query: 查询语句:param corpus_or_emb: 候选答案或候选答案的编码:param topk: 返回前多少个答案:param model: 用于编码的模型:return [(most_similar, score)]: 结果和分数---------------ver: 2021-08-23by: changhongyu"""topk = min(topk, len(corpus))q_emb = model.encode(query, convert_to_tensor=True)if type(corpus_or_emb) == list:c_emb = model.encode(corpus, convert_to_tensor=True)elif type(corpus_or_emb) == torch.Tensor:c_emb = corpus_or_embelse:raise TypeError("Attribute 'corpus_or_emb' must be list or tensor.")cosine_scores = util.pytorch_cos_sim(q_emb, c_emb)[0]top_res = torch.topk(cosine_scores, k=topk)return [(corpus[int(index.cpu())], float(score.cpu().numpy()) for score, index in zip(top_res[0], top_res[1])]

2.3 聚类和主题模型

利用sentence-transformer系列的模型获取的embedding除了用于相似度计算之外,也可以作为特征直接应用于聚类。提供了K-means,Agglomerative cluster以及fast cluster三种方法。
K-means聚类也是利用了skearn的k-means,取SBERT的编码特征作为k-means的输入特征:

from sentence_transformers import SentenceTransformer
from sklearn.cluster import KMeansembedder = SentenceTransformer('paraphrase-MiniLM-L6-v2')# Corpus with example sentences
corpus = ['A man is eating food.','A man is eating a piece of bread.','A man is eating pasta.','The girl is carrying a baby.','The baby is carried by the woman','A man is riding a horse.','A man is riding a white horse on an enclosed ground.','A monkey is playing drums.','Someone in a gorilla costume is playing a set of drums.','A cheetah is running behind its prey.','A cheetah chases prey on across a field.']
corpus_embeddings = embedder.encode(corpus)# Perform kmean clustering
num_clusters = 5
clustering_model = KMeans(n_clusters=num_clusters)
clustering_model.fit(corpus_embeddings)
cluster_assignment = clustering_model.labels_clustered_sentences = [[] for i in range(num_clusters)]
for sentence_id, cluster_id in enumerate(cluster_assignment):clustered_sentences[cluster_id].append(corpus[sentence_id])for i, cluster in enumerate(clustered_sentences):print("Cluster ", i+1)print(cluster)print("")

以sentence transformer作为编码器的主题模型,包括Top2Vec,BERTopic等。

2.4 图片检索

sentence-transformer同样提供了基于vit的预训练模型,因而可以计算图像和文字的相似度。用法与文本相类似。

from sentence_transformers import SentenceTransformer, util
from PIL import Image#Load CLIP model
model = SentenceTransformer('clip-ViT-B-32')#Encode an image:
img_emb = model.encode(Image.open('two_dogs_in_snow.jpg'))#Encode text descriptions
text_emb = model.encode(['Two dogs in the snow', 'A cat on a table', 'A picture of London at night'])#Compute cosine similarities
cos_scores = util.cos_sim(img_emb, text_emb)
print(cos_scores)

除此之外,利用sentence-transformer还可以支持一些其他的应用,例如召回+重排列等。具体内容可以参考官方文档。

3. 无监督方法的训练

3.1 SimCSE

SimCSE采用一个编码器,利用dropout机制中的随机原理,构建训练的正样本,拉进空间中正例之间的距离。

帮助文档中给出的简单的训练样例:

from sentence_transformers import SentenceTransformer, InputExample
from sentence_transformers import models, losses
from torch.utils.data import DataLoader# Define your sentence transformer model using CLS pooling
model_name = 'distilroberta-base'
word_embedding_model = models.Transformer(model_name, max_seq_length=32)
pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())
model = SentenceTransformer(modules=[word_embedding_model, pooling_model])# Define a list with sentences (1k - 100k sentences)
train_sentences = ["Your set of sentences","Model will automatically add the noise","And re-construct it","You should provide at least 1k sentences"]# Convert train sentences to sentence pairs
train_data = [InputExample(texts=[s, s]) for s in train_sentences]# DataLoader to batch your data
train_dataloader = DataLoader(train_data, batch_size=128, shuffle=True)# Use the denoising auto-encoder loss
train_loss = losses.MultipleNegativesRankingLoss(model)# Call the fit method
model.fit(train_objectives=[(train_dataloader, train_loss)],epochs=1,show_progress_bar=True
)model.save('output/simcse-model')

3.2 TSDAE

Transformers and Sequential Denoising Auto-Encoder (TSDAE) 是基于transformer的一个encoder-decoder结构,用于将输入的包含噪声的文本去噪。

训练代码:

from sentence_transformers import SentenceTransformer
from sentence_transformers import models, util, datasets, evaluation, losses
from torch.utils.data import DataLoader# Define your sentence transformer model using CLS pooling
model_name = 'bert-base-uncased'
word_embedding_model = models.Transformer(model_name)
pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension(), 'cls')
model = SentenceTransformer(modules=[word_embedding_model, pooling_model])# Define a list with sentences (1k - 100k sentences)
train_sentences = ["Your set of sentences","Model will automatically add the noise", "And re-construct it","You should provide at least 1k sentences"]# Create the special denoising dataset that adds noise on-the-fly
train_dataset = datasets.DenoisingAutoEncoderDataset(train_sentences)# DataLoader to batch your data
train_dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True)# Use the denoising auto-encoder loss
train_loss = losses.DenoisingAutoEncoderLoss(model, decoder_name_or_path=model_name, tie_encoder_decoder=True)# Call the fit method
model.fit(train_objectives=[(train_dataloader, train_loss)],epochs=1,weight_decay=0,scheduler='constantlr',optimizer_params={'lr': 3e-5},show_progress_bar=True
)model.save('output/tsdae-model')

这篇文章我还没有看,在这里记录一下论文地址:
https://arxiv.org/abs/2104.06979

3.3 GenQ

GenQ的应用场景是非对称语义搜索。在没有labeled查询-结果句对的情况下,首先利用T5模型生成查询,构建出’silver dataset’,再用构建的数据集去精调双塔结构的SBERT模型。

首先给入一段话,生成问句。注意这里的预训练模型,是query-gen-msmarco-t5-large-v1,而不是google发布的T5模型,如果是T5模型,生成的结果就不是对应的问句,而是与para类似的一个片段。

另外,BeIR只发布了三个模型,都是在英文预料上进行预训练的,所以如果要应用在中文的场景,只能通过翻译的方法先把文档翻译成英文,生成英文问句之后再回译。

from transformers import T5Tokenizer, T5ForConditionalGeneration
import torchtokenizer = T5Tokenizer.from_pretrained('BeIR/query-gen-msmarco-t5-large-v1')
model = T5ForConditionalGeneration.from_pretrained('BeIR/query-gen-msmarco-t5-large-v1')
model.eval()para = "Python is an interpreted, high-level and general-purpose programming language. Python's design philosophy emphasizes code readability with its notable use of significant whitespace. Its language constructs and object-oriented approach aim to help programmers write clear, logical code for small and large-scale projects."input_ids = tokenizer.encode(para, return_tensors='pt')
with torch.no_grad():outputs = model.generate(input_ids=input_ids,max_length=64,do_sample=True,top_p=0.95,num_return_sequences=3)print("Paragraph:")
print(para)print("\nGenerated Queries:")
for i in range(len(outputs)):query = tokenizer.decode(outputs[i], skip_special_tokens=True)print(f'{i + 1}:{query}')

利用生成的数据集训练一个SBERT模型

from sentence_transformers import SentenceTransformer, InputExample, losses, models, datasets
import ostrain_examples = []
for para in paras:# 所有的未标注的篇章数据for query in para['queries']:# 根据自己存储的数据格式调整train_examples.append(InputExample(texts=[query, para]))# For the MultipleNegativesRankingLoss, it is important
# that the batch does not contain duplicate entries, i.e.
# no two equal queries and no two equal paragraphs.
# To ensure this, we use a special data loader
train_dataloader = datasets.NoDuplicatesDataLoader(train_examples, batch_size=64)# Now we create a SentenceTransformer model from scratch
word_emb = models.Transformer('distilbert-base-uncased')
pooling = models.Pooling(word_emb.get_word_embedding_dimension())
model = SentenceTransformer(modules=[word_emb, pooling])# MultipleNegativesRankingLoss requires input pairs (query, relevant_passage)
# and trains the model so that is is suitable for semantic search
train_loss = losses.MultipleNegativesRankingLoss(model)#Tune the model
num_epochs = 3
warmup_steps = int(len(train_dataloader) * num_epochs * 0.1)
model.fit(train_objectives=[(train_dataloader, train_loss)], epochs=num_epochs, warmup_steps=warmup_steps, show_progress_bar=True)os.makedirs('output', exist_ok=True)
model.save('output/programming-model')

接下来就可以利用上文所述的语义搜索的方法对一个新的query进行查询。

3.4 CT

CT是另一种无监督的训练方法,它的思路是利用两个编码器对两组输入分别进行编码,如果是同一个句子,编码器1和编码器2对其的编码应该是类似的,而不同的句子,两个编码器给出的编码则不一样。于是训练的目标是令前者两个编码向量的点积尽可能大,而后者点积尽可能小。


训练代码:

import math
from sentence_transformers import models, losses, SentenceTransformer
import tqdm## Training parameters
model_name = 'distilbert-base-uncased'  # 如果是本地模型则修改为路径
batch_size = 16
pos_neg_ratio = 8   # batch_size must be devisible by pos_neg_ratio
num_epochs = 1
max_seq_length = 75
output_name = ''  # 模型保存路径model_output_path = 'output/train_ct{}-{}'.format(output_name, datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))# 建立编码器,可以采用SBERT的预训练模型
word_embedding_model = models.Transformer(model_name, max_seq_length=max_seq_length)# Apply mean pooling to get one fixed sized sentence vector
pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())
model = SentenceTransformer(modules=[word_embedding_model, pooling_model])################# Read the train corpus  #################
train_sentences = ["Your set of sentences","Model will automatically add the noise","And re-construct it","You should provide at least 1k sentences"]# For ContrastiveTension we need a special data loader to construct batches with the desired properties
train_dataloader =  losses.ContrastiveTensionDataLoader(train_sentences, batch_size=batch_size, pos_neg_ratio=pos_neg_ratio)# As loss, we losses.ContrastiveTensionLoss
train_loss = losses.ContrastiveTensionLoss(model)warmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1)  # 10% of train data for warm-up# Train the model
model.fit(train_objectives=[(train_dataloader, train_loss)],epochs=num_epochs,warmup_steps=warmup_steps,optimizer_params={'lr': 5e-5},checkpoint_path=model_output_path,show_progress_bar=True,use_amp=False  # Set to True, if your GPU supports FP16 cores)

总而言之,sentence-transformer是一个非常好用的编码器,并且上述所用功能的原理都非常容易理解,在此基础上也可以根据自己的实际应用场景去开发一些具有创造性的功能。

NLP实践——基于SBERT的语义搜索,语义相似度计算,SimCSE、GenQ等无监督训练相关推荐

  1. 语义检索系统【二】:基于无监督训练SimCSE+In-batch Negatives策略有监督训练的语义索引召回

    搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排).系统架构.常见问题.算法项目实战总结.技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排 ...

  2. NLP实践——基于SIFRank的英文关键短语抽取

    NLP实践--基于SIFRank的英文关键短语抽取 1. 回顾 2. 英文关键词抽取 2.1 预训练词汇权重 2.2 分词/词性标注模型 2.3 候选短语抽取模型 2.4 编码模型 1. 回顾 之前的 ...

  3. 搜索,然后学习:两阶段的无监督文本生成

    论文标题: Unsupervised Text Generation by Learning from Search 论文作者: Jingjing Li, Zichao Li, Lili Mou, X ...

  4. 基于信息内容的词林词语相似度计算 - 论文及代码讲解

    文章目录 论文 同义词林简介 特点 代码 获取词的编码 求IC值 求相似度 选取相似度最大值 论文:<基于信息内容的词林词语相似度计算 >-2018-彭琦,朱新华等 查看 代码:https ...

  5. WMD:基于词向量的文档相似度计算

    EMD算法简介 该部分引用自[1] Earth Mover's Distance (EMD),和欧氏距离一样,他们都是一种距离度量的定义,可以用来测量某分布之间的距离.EMD主要应用在图像处理和语音信 ...

  6. 语义表征的无监督对比学习:一个新理论框架

    点击上方↑↑↑蓝字关注我们~ 「2019 Python开发者日」7折优惠最后3天,请扫码咨询 ↑↑↑ 译者 | Linstancy 责编 | 琥珀 出品 | AI科技大本营(ID:rgznai100) ...

  7. NLP热门词汇解读:预训练、Transformer、无监督机器翻译

    Transformer Transformer在2017年由Google在题为<Attention Is All You Need>的论文中提出.Transformer是一个完全基于注意力 ...

  8. Mockingjay: 基于双向Transformer编码的无监督语音表征学习

    本次分享台湾大学李宏毅老师团队在ICASSP 2020会议发表的论文<MOCKINGJAY: UNSUPERVISED SPEECH REPRESENTATION LEARNING WITH D ...

  9. 基于深度学习的短文本相似度学习与行业测评

    文本相似度计算作为NLP的热点研究方向之一,在搜索推荐.智能客服.闲聊等领域得到的广泛的应用.在不同的应用领域,也存在着一定的差异,例如在搜索领域大多是计算query与document的相似度:而在智 ...

  10. 字节约翰斯·霍普金斯上交提出iBOT框架,基于MIM进行自监督训练,在ImageNet-1K上达到86.3%的微调精度!...

    关注公众号,发现CV技术之美 ▊ 写在前面 语言Transformer的成功主要归功于masked language modeling(MLM) 的预训练任务,其中文本首先被标记为语义上有意义的片段. ...

最新文章

  1. java手机游戏模拟器下载_Java手机游戏模拟器
  2. 解压文件出错解决方法(invalid compressed data--format violated)
  3. Node 即学即用 笔记 思维导图
  4. PID控制器改进笔记之四:改进PID控制器之设定值响应
  5. vue 查看变量类型_Vue学习 开始走向VUE开发2---插值使用详解
  6. python画图xlable显示中文_xlabel和ylabel超出绘图区域,无法在figu中完全显示
  7. javascript 常用方法 解析URL,补充前导字符, 省市联动, 循环替换模板
  8. oracle 建表 varchar,一个完整的Oracle建表的例子
  9. Insyde uefi 隐藏设置_固件级安全,微软安全工具新增UEFI扫描功能
  10. 成为会带团队的技术人 业务理解:深入业务是做好架构的前提
  11. which的用法总结c语言,which的用法总结
  12. 段地址x16+偏移地址=物理地址的本质含义
  13. 解决 No converter found capable of converting from type [java.lang.String] to type ... 的问题
  14. hashmap底层源码详解
  15. 手机密码大全及国产贴牌与OEM型号对照表
  16. THIS和supper用法
  17. UsbDeviceManager.java
  18. Altium designer除了GND以外的Nets自动布线
  19. 怎么把avi转成mp4格式?
  20. 为什么websocket没有被广泛使用,他解决了前后端数据没有实时刷新的问题,原因来了。

热门文章

  1. C# word类库 光标移动
  2. GraphQL基金会宣布与联合开发基金会合作推动开源和开放标准
  3. git 怎么把以前的账号注销_QQ号被注销了,我的游戏账号怎么办?腾讯只用一句话让玩家没话说...
  4. 绵阳python培训_绵怎么组词
  5. zbox的测试例——selectAll+selectInverse
  6. 微信小程序+vant组件 侧边导航栏切换显示
  7. Rect、RectF方法解析
  8. vs2019,C#,MySQL创建图书管理系统3(管理员相关页面的布局和设计实现,图书显示,图书添加)
  9. BUGS 小胡的学习日志
  10. 【IoT】成功十大因素,命、运、风水 、、贵人、养生,哪个最重要?