训练和微调句子转换模型
训练和微调句子转换模型
查看使用Notebook Companion的教程:
训练或微调Sentence Transformers模型高度依赖于可用的数据和目标任务。关键在于:
- 理解如何将数据输入模型并相应地准备数据集。
- 了解不同的损失函数以及它们与数据集的关系。
在本教程中,你将会:
- 通过从头开始创建一个Sentence Transformers模型或者从Hugging Face Hub微调一个模型,来理解Sentence Transformers模型的工作原理。
- 了解数据集可能的不同格式。
- 回顾根据数据集格式选择的不同损失函数。
- 训练或微调模型。
- 将模型分享到Hugging Face Hub。
- 了解何时Sentence Transformers模型可能不是最佳选择。
Sentence Transformers模型的工作原理
在Sentence Transformers模型中,你将一个可变长度的文本(或图像像素)映射为表示该输入意义的固定大小的嵌入。要开始使用嵌入,请查看我们以前的教程。本文重点介绍文本。
Sentence Transformers模型的工作原理如下:
- 第一层 – 输入文本通过一个预训练的Transformer模型,该模型可以直接从Hugging Face Hub获得。本教程将使用“distilroberta-base”模型。Transformer的输出是所有输入标记的上下文化单词嵌入;可以想象一个文本中每个标记的嵌入。
- 第二层 – 嵌入通过池化层得到一个表示整个文本的单个固定大小的嵌入。例如,平均池化会对模型生成的嵌入进行平均。
下图总结了这个过程:
记得使用pip install -U sentence-transformers
命令安装Sentence Transformers库。在代码中,这个两步过程很简单:
from sentence_transformers import SentenceTransformer, models
## 第一步:使用一个已有的语言模型
word_embedding_model = models.Transformer('distilroberta-base')
## 第二步:使用一个池化函数对标记嵌入进行池化
pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())
## 使用modules参数将第一步和第二步连接起来
model = SentenceTransformer(modules=[word_embedding_model, pooling_model])
从上面的代码中,你可以看到Sentence Transformers模型由模块组成,也就是一系列按顺序执行的层。输入文本进入第一个模块,最终输出来自最后一个组件。尽管看起来很简单,但上述模型是Sentence Transformers模型的典型架构。如果需要,还可以添加其他层,例如密集层、词袋模型和卷积层。
为什么不直接使用Transformer模型(如BERT或Roberta)来为整个句子和文本创建嵌入?至少有两个原因:
- 预训练的Transformer在执行语义搜索任务时需要大量计算。例如,对于一组10,000个句子中找到最相似的一对,使用BERT需要进行大约5000万次推理计算(约65小时)。相比之下,使用BERT Sentence Transformers模型的时间缩短到约5秒。
- 一旦训练完成,Transformer模型无法直接创建良好的句子表示。将BERT模型的标记嵌入平均后作为句子嵌入的性能低于2014年开发的GloVe嵌入。
在本节中,我们将从头开始创建一个Sentence Transformers模型。如果你想微调现有的Sentence Transformers模型,可以跳过上述步骤,从Hugging Face Hub导入模型。你可以在“Sentence Similarity”任务中找到大多数Sentence Transformers模型。这里我们加载了“sentence-transformers/all-MiniLM-L6-v2”模型:
from sentence_transformers import SentenceTransformer
model_id = "sentence-transformers/all-MiniLM-L6-v2"
model = SentenceTransformer(model_id)
现在,让我们来看看最关键的部分:数据集的格式。
如何准备用于训练Sentence Transformers模型的数据集
要训练一个句子转换模型,你需要以某种方式告诉它两个句子之间有一定程度的相似性。因此,数据中的每个例子都需要一个标签或结构,让模型能够理解两个句子是相似还是不同。
不幸的是,没有一种单一的方法来准备数据训练句子转换模型。这在很大程度上取决于你的目标和数据的结构。如果你没有明确的标签,这是最有可能的情况,你可以从获得句子的文档设计中推导出来。例如,同一份报告中的两个句子应该更可比,而不同报告中的两个句子可能不太可比。相邻的句子可能比非相邻的句子更可比。
此外,数据的结构将影响你可以使用的损失函数。这将在下一节中讨论。
请记住,本文的笔记本伴侣已经实现了所有的代码。
大多数数据集的配置将采用以下四种形式之一(下面将看到每种情况的示例):
- 情况1:示例是一对句子和一个指示它们相似程度的标签。标签可以是整数或浮点数。这种情况适用于最初为自然语言推理(NLI)准备的数据集,因为它们包含一对句子,并附有一个标签,指示它们是否推理。
- 情况2:示例是一对正面(相似)句子没有标签。例如,一对释义、一对完整文本及其摘要、一对重复的问题、一对(
查询
,响应
)或一对(源语言
,目标语言
)。自然语言推理数据集也可以以这种方式格式化,通过配对包含的句子。如果你的数据以这种格式存在,那么你可以使用MultipleNegativesRankingLoss
,这是句子转换模型中最常用的损失函数之一。 - 情况3:示例是带有整数标签的句子。这种数据格式可以很容易地通过损失函数转换为三个句子(三元组),其中第一个是“锚点”,第二个是与锚点相同类别的“正面”,第三个是与不同类别的“负面”。每个句子都有一个整数标签,指示它所属的类别。
- 情况4:示例是没有类别或标签的三元组(锚点、正面、负面)。
例如,在本教程中,你将使用第四种情况的数据集来训练一个句子转换器。然后,你将使用第二种情况的数据集配置对其进行微调(请参阅本博客的笔记本伴侣)。
请注意,句子转换模型可以使用人工标注(情况1和情况3)或从文本格式中自动推断的标签进行训练(主要是情况2;虽然情况4不需要标签,但要在三元组中找到数据更加困难,除非你将其处理为MegaBatchMarginLoss
函数所做的那样)。
在Hugging Face Hub上有针对上述每种情况的数据集。此外,Hub中的数据集还具有数据集预览功能,可以在下载之前查看数据集的结构。以下是每种情况的示例数据集:
-
情况1:如果你有(或制造)一个指示两个句子之间相似程度的标签,可以使用与自然语言推理相同的设置;例如{0,1,2},其中0表示矛盾,2表示蕴涵。请查看SNLI数据集的结构。
-
情况2:句子压缩数据集包含由正面对组成的示例。如果你的数据集每个示例有多个正面句子,例如COCO Captions或Flickr30k Captions数据集中的五元组,你可以将示例格式化为具有不同组合的正面对。
-
情况3:TREC数据集具有指示每个句子类别的整数标签。Yahoo Answers Topics数据集中的每个示例包含三个句子和一个指示其主题的标签;因此,每个示例可以分为三个。
-
情况4:Quora Triplets数据集是没有标签的三元组(锚点、正面、负面)。
下一步是将数据集转换为句子转换模型可以理解的格式。模型无法接受原始的字符串列表。每个示例必须转换为sentence_transformers.InputExample
类,然后转换为torch.utils.data.DataLoader
类以批处理和洗牌示例。
使用pip install datasets
安装Hugging Face Datasets。然后使用load_dataset
函数导入数据集:
from datasets import load_dataset
dataset_id = "embedding-data/QQP_triplets"
dataset = load_dataset(dataset_id)
本指南使用一个无标签的三元组数据集,即上述第四种情况。
通过datasets
库,您可以探索数据集:
print(f"- {dataset_id}数据集有{dataset['train'].num_rows}个示例。")
print(f"- 每个示例是一个{type(dataset['train'][0])},其值为{type(dataset['train'][0]['set'])}。")
print(f"- 示例如下:{dataset['train'][0]}")
输出:
- embedding-data/QQP_triplets数据集有101762个示例。
- 每个示例是一个,其值为。
- 示例如下:{'set': {'query': '为什么在印度我们没有像美国那样的一对一政治辩论?', 'pos': ['为什么我们不能在印度像美国那样举行一场公开辩论?'], 'neg': ['Quora上的人们能停止印巴辩论吗?我们厌倦了每天大量看到这种辩论。'...]
您可以看到query
(锚点)有一个句子,pos
(正例)是一个句子列表(我们打印的只有一个句子),neg
(负例)是多个句子的列表。
将示例转换为InputExample
对象。为了简单起见,(1)在embedding-data/QQP_triplets数据集中只使用一个正例和一个负例。(2)我们只使用了可用示例的1/2。通过增加示例的数量,您可以获得更好的结果。
from sentence_transformers import InputExample
train_examples = []
train_data = dataset['train']['set']
# 为了灵活性,我们只使用了可用数据的1/2
n_examples = dataset['train'].num_rows // 2
for i in range(n_examples):
example = train_data[i]
train_examples.append(InputExample(texts=[example['query'], example['pos'][0], example['neg'][0]]))
将训练示例转换为Dataloader
对象。
from torch.utils.data import DataLoader
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
下一步是选择适合数据格式的损失函数。
用于训练Sentence Transformers模型的损失函数
还记得数据可能的四种不同格式吗?每种格式都有与之关联的不同损失函数。
情况1:一对句子和一个指示它们相似程度的标签。损失函数优化使得(1)标签最接近的句子在向量空间中靠近,(2)标签最远的句子尽可能远离。损失函数取决于标签的格式。如果是整数,则可以使用ContrastiveLoss
或SoftmaxLoss
;如果是浮点数,则可以使用CosineSimilarityLoss
。
情况2:如果只有两个相似的句子(两个正例),没有标签,则可以使用MultipleNegativesRankingLoss
函数。也可以使用MegaBatchMarginLoss
,它会将示例转换为三元组(anchor_i, positive_i, positive_j)
,其中positive_j
作为负例。
情况3:当样本是形如[anchor, positive, negative]
的三元组,并且每个样本都有一个整数标签时,损失函数优化模型,使得锚点和正例句子在向量空间中更接近,而锚点和负例句子相距较远。可以使用BatchHardTripletLoss
,它要求数据使用整数(例如标签1、2、3)进行标记,假设具有相同标签的样本是相似的。因此,锚点和正例必须具有相同的标签,而负例必须具有不同的标签。另外,您还可以使用BatchAllTripletLoss
、BatchHardSoftMarginTripletLoss
或BatchSemiHardTripletLoss
。它们之间的区别超出了本教程的范围,但可以在Sentence Transformers文档中进行查阅。
案例4:如果每个三元组中的句子没有标签,则应使用TripletLoss
。此损失函数最小化锚点与正例句子之间的距离,同时最大化锚点与负例句子之间的距离。
下图总结了不同类型的数据集格式、Hub中的示例数据集以及它们适用的损失函数。
最困难的部分是在概念上选择一个合适的损失函数。在代码中,只有两行:
from sentence_transformers import losses
train_loss = losses.TripletLoss(model=model)
一旦数据集处于所需的格式并且已经选择了合适的损失函数,拟合和训练一个句子转换模型就很简单。
如何训练或微调句子转换模型
“SentenceTransformers的设计目标是使微调自己的句子/文本嵌入模型变得简单。它提供了大部分构建块,可以组合在一起为特定任务调整嵌入。” – Sentence Transformers文档。
训练或微调的过程如下:
model.fit(train_objectives=[(train_dataloader, train_loss)], epochs=10)
请记住,如果您正在微调现有的句子转换模型(参见笔记本伴侣),则可以直接从中调用fit
方法。如果这是一个新的句子转换模型,您首先必须像在“句子转换模型的工作原理”部分中所做的那样定义它。
就是这样,您有了一个新的或改进的句子转换模型!您是否想将其分享到Hugging Face Hub上?
首先,登录到Hugging Face Hub。您需要在“帐户设置”中创建一个write
令牌。然后有两种登录选项:
-
在终端中键入
huggingface-cli login
,然后输入您的令牌。 -
如果在Python笔记本中,可以使用
notebook_login
。
from huggingface_hub import notebook_login
notebook_login()
然后,您可以通过调用训练模型的save_to_hub
方法来共享您的模型。默认情况下,模型将上传到您的账户。但是,您可以通过在organization
参数中传递它来上传到一个组织中。save_to_hub
会自动生成模型卡片、推理小部件、示例代码片段和更多详细信息。您可以使用train_datasets
参数自动将训练模型所使用的数据集列表添加到Hub的模型卡片中:
model.save_to_hub(
"distilroberta-base-sentence-transformer",
organization= # 添加您的用户名
train_datasets=["embedding-data/QQP_triplets"],
)
在Notebook Companion中,我使用了embedding-data/sentence-compression数据集和MultipleNegativesRankingLoss
损失来微调了相同的模型。
Sentence Transformers的限制是什么?
对于语义搜索,句子转换模型的性能要比简单的Transformer模型好得多。但是,句子转换模型在哪些方面效果不好?如果您的任务是分类,则使用句子嵌入是错误的方法。在这种情况下,🤗 Transformers库将是更好的选择。
额外资源
- 开始使用嵌入。
- 理解语义搜索。
- 开始您的第一个句子转换模型。
- 使用句子转换生成播放列表。
- Hugging Face + Sentence Transformers文档。
感谢阅读!祝您享受嵌入制作的乐趣。