Sentence-Transformer

879 阅读6分钟

Sentence-Transformer 

1. 各种嵌入(Embedding):

词嵌入(Word Embedding)和句子嵌入(Sentence Embedding)是自然语言处理(NLP)中常用的两种嵌入方法

概念不同

  • 词嵌入将词语映射到高维向量空间的过程
  • 句子嵌入是将整个句子映射到一个向量表示的过程

维度不同

  • 词嵌入通常具有相对较低的维度,例如50、100或300维
  • 句子嵌入通常具有较高的维度,因为需要捕捉更多的上下文信息,可能是几百到几千维

语义表示范围不同

  • 词嵌入每个词都被表示为一个实数向量,这个向量通常包含了该词在语义上的一些信息,每个词都有独立的嵌入,词与词之间的关系在嵌入空间中有时不太直观
  • 句子嵌入捕捉了整个句子的语义信息。不同于词嵌入,句子嵌入考虑了整个上下文,通过考虑整个句子的语境,能够更好地捕捉上下文信息,适用于更复杂的NLP任务

训练方式不同

  • 词嵌入可以通过训练得到(如Word2Vec、Fasttext)或者使用预训练的模型(如BERT预训练模型)
  • 句子嵌入通常需要使用特定的模型进行训练,如Siamese网络、LSTM、Transformer等

应用场景不同

  • 词嵌入主要用于单个词语的表示,常用于词语级别的NLP任务,如文本分类、命名实体识别等
  • 句子嵌入适用于处理整个句子的语义,常用于句子级别的NLP任务,如文本相似度、情感分析、文本生成等

2. 简介:

  • Sentence Transformers 是一种基于Transformers的神经网络架构,用于生成文本的嵌入表示。这些嵌入表示可以用于各种下游任务:
  • 语句相似性
  • 聚类
  • 信息检索

3. 结构:

  • 为了生成在语义上有意义的嵌入,Sentence Transformers通常使用Siamese(孪生)和Triplet网络结构进行训练,这样可以确保在向量空间中,语义相似的句子是接近的,而不相似的句子则是远离的。

Siamese网络结构

  • Siamese网络由两个相同的子网络组成,这两个子网络共享相同的参数。在文本应用中,这两个子网络通常是两个相同的Transformer模型,例如BERT或RoBERTa。Siamese网络的目标是学习输入对(比如两个句子)之间的相似性。在训练过程中,输入对共同被输入到这两个子网络,然后分别计算每个句子的嵌入表示。接着,通过计算这两个嵌入之间的距离(比如余弦相似度或Euclidean距离),然后利用对比损失函数(contrastive loss function),比如余弦相似度损失或三重损失来优化网络。

Triplet网络结构 

  • Triplet网络结构类似于孪生网络,但它处理三个输入而不是两个:一个锚点(anchor)、一个正样本(positive sample,与锚点在语义上接近的样本)和一个负样本(negative sample,与锚点在语义上不接近的样本)。三元组网络的目标是使得锚点的嵌入表示与正样本的嵌入表示距离尽可能小,与负样本的嵌入表示距离尽可能大。这是通过最小化三元组损失函数(triplet loss function)来实现的,该函数考虑了锚点与正负样本之间的相对距离。
Siamese孪生网络Siamese孪生网络训练Sentence Transformer
  • 输出结果上增加了一个pooling操作,从而生成一个固定大小的句子embedding向量
  • 微调过程即为在预训练模型的基础上,输入领域数据,优化Loss,从而不断优化整体模型的嵌入向量,使之能够更好的区分领域数据

4. 训练数据

  • 训练Sentence Transformers通常需要大规模的标注数据集。数据集可以是特定于任务的,也可以是通用的,这取决于模型的应用目的。

5. 训练(微调)过程:


model_name = '预训练模型'

word_embedding_model = models.Transformer(model_name)

pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension(),

                              pooling_mode_mean_tokens=True,

                              pooling_mode_cls_token=False,

                              pooling_mode_max_tokens=False)

model = SentenceTransformer(modules=[word_embedding_model, pooling_model],device='cuda')

train_loss = losses.CosineSimilarityLoss(model=model)

evaluator = EmbeddingSimilarityEvaluator.from_input_examples(dev_samples, name='sts-dev')

model.fit(train_objectives=[(train_dataloader, train_loss)],

          evaluator=evaluator,

          epochs=num_epochs,

          evaluation_steps=1000,

          warmup_steps=warmup_steps,

          output_path=model_save_path)

6. 为什么Sentence Transformer 比只用 Bert[1]在计算相似度方面效果好速度快?

(1)速度快
图1 Bert模型做语义相似度图2 Sentence Bert 模型做语义相似度

bert模型做语义相似度

  • 这种方式的特点在于 sent1 和 sent2 两个句子 每计算一次 相似度时,都需要进行拼接(拼接方式:[CLS] sent1 [SEP] sent2 ),然后输入到 Bert 模型中计算相似度,也就是说所有句子 都要进行两两拼接;
  • 计算复杂度:由于 所有句子 都要进行两两拼接,假设对 1W 个句子做相似度计算,那么 需要计算 1w*(1w-1) 次;
  • 从10000个句子中找出最相似的句子对,大概需要5000万(C100002=49,995,000)个推理计算,在V100GPU上耗时约65个小时

Sentence-BERT 做 语义相似度任务

  • sent1 和 sent2 是分开输入到 模型
  • 计算复杂度:需要计算 1w*(1w-1) 次

分析

  • Bert 每一次都需要 将 sent1和 sent2 两个句子进行拼接,然后输入到 Bert 模型中;
  • Sentence-BERT 由于 所有句子是分开输入到模型,所有首先可以提前对所有句子计算一次embedding,然后进行存储,在计算相似的时候,只需要查询计算cos_sim就可以

(2)质量高

如下:

现象:

  • 结果显示直接采用BERT的输出结果,效果很差

原因:

  • BERT的直接输出结果(平均embedding)不适合用来计算余弦相似度、曼哈顿距离和欧式距离。
  • BERT使用自注意力机制来处理输入序列,通过训练能够理解上下文中每个词的语义,将线性输入变成了非线性输出,这导致BERT的输出不能够保留原始输入之间的线性结构(输入序列中的顺序关系),而这些相似性度量(如余弦相似度、曼哈顿距离和欧式距离)通常假设了线性关系,因此,如果想在BERT输出上使用这些线性相似性度量,可能需要对输出进行一些额外的线性或非线性变换,这有助于保留有用的语义信息。

结论:

  • Sentence-Bert(Sentence Transformer变种)模型效果明显要比直接使用Bert好很多。在查找最相似的句子时候,从上述的65小时降低到5秒(计算余弦相似度大概0.01s)

附:

[1] cloud.tencent.com/developer/a… 

[2] CLS位置的输出: BERT模型的输出中,通常将[CLS]标记对应位置的隐藏状态作为整个序列的表示。这个位置的输出被用于下游任务的分类等工作。