使用ModernBERT进行上下文嵌入及嵌入微调的实用指南

2025年02月05日 由 alex 发表 4842 0

在这篇文章中,我们将深入探讨ModernBERT ,这是对传统BERT模型的重大升级。虽然我们之前探索了针对特定领域的微调嵌入的概念,重点是Matryoshka 表示学习方法,但这次,我们将换个角度。


我们重点介绍ModernBERT及其如何增强上下文嵌入的使用。我们还将介绍如何生成用于微调的数据集,并展示如何微调ModernBERT以在 NLP 任务中提供更强大的结果。


嵌入在机器学习和 NLP 中的重要性


6


嵌入是机器学习和 NLP 中的一个关键概念,其中复杂数据(如单词、短语或产品)被转换为数字向量表示。这些向量捕获实体之间的语义和句法关系,将相似的实体更紧密地映射到密集的低维空间中。与将实体视为独立实体的独热编码不同,嵌入允许模型理解大型数据集内的模式和共现。


嵌入对于检索、推荐和分类等任务至关重要。在检索中,它们使系统能够通过比较查询和文档的向量表示来查找语义相关的项目。在推荐系统中,嵌入将具有相似交互的用户和产品更紧密地映射到一起,从而改善个性化建议。在分类中,嵌入帮助模型理解更高级的概念,从而基于含义而不仅仅是关键字做出更准确的预测。总体而言,嵌入弥合了非结构化数据和机器学习模型之间的差距,提高了各种任务的性能。


上下文嵌入


7


上下文嵌入是单词或标记的动态表示,不同于无论上下文如何都会分配固定向量的静态嵌入。上下文嵌入根据同一单词周围的文本为其生成不同的向量,从而捕捉句子或文档中细微的含义和用法。这是通过考虑整个输入序列来实现的。


捕捉上下文很重要,因为它可以解决语言中的歧义和细微差别。单词具有多重含义(例如,“苹果”、“鼠标”),理解正确含义依赖于周围的上下文。上下文嵌入使模型能够理解这些复杂的关系,与仅捕捉字面含义的静态模型相比,可以更准确地表示文本。


在需要更深入的语义理解的任务中,上下文嵌入始终优于静态嵌入。示例包括命名实体识别 (NER),它可以区分“Apple”是公司还是水果;问答,这需要在正确的上下文中理解问题的细微差别;情绪分析,甚至机器翻译。上下文嵌入提供的上下文敏感表示使模型能够实现类似人类的理解,与静态模型相比具有显著优势。


上下文嵌入与普通嵌入

普通(传统)嵌入是静态的,这意味着每个单词或实体都被分配了一个固定的向量表示,而不管其上下文如何。常见示例包括Word2Vec、GloVe和FastText。这些模型根据来自大量文本的共现模式为词汇表中的每个单词生成一个向量。虽然普通嵌入可以捕获一些语义关系(例如,“king”比“apple”更接近“queen”),但它们无法解释单词使用上下文的不同含义。例如,“bat”这个词无论是用来指飞行哺乳动物还是运动器材,都具有相同的向量表示。


另一方面,上下文嵌入会根据每个单词或实体周围的上下文动态地为其生成唯一的向量表示。这些嵌入是使用深度学习模型(例如BERT、ELMo和RoBERTa)生成的,这些模型在句子或文档的上下文中处理单词。在这些模型中,同一个单词会根据其周围的单词而具有不同的表示。例如,“bank”在短语“river bank”和“financial bank”中使用时会具有不同的嵌入。这种捕捉上下文的能力使上下文嵌入更加通用和准确,尤其是在问答、机器翻译和情感分析等复杂的 NLP 任务中。


主要差异一览


8


虽然普通嵌入提供了基于词频和共现的通用表示,但上下文嵌入提供了更细致入微、更动态的表示,这些表示对单词出现的特定上下文很敏感,从而能够在广泛的 NLP 应用中获得更好的性能。


现代BERT

ModernBERT 有什么新功能?


9


ModernBERT引入了多项高级功能,这些功能有效增强了其利用上下文嵌入的能力。其中一项关键创新是用旋转位置嵌入(RoPE)取代了传统的位置编码。RoPE使模型能够更好地理解序列中单词的相对位置,这对于处理更长的上下文至关重要。通过融入RoPE,ModernBERT能够扩展到最多8,192个标记的序列长度,极大地提高了其处理长篇文档或代码的能力。这一功能解决了早期模型(如原始BERT)的一个局限性,即上下文窗口较短。


ModernBERT通过引入GeGLU层,对标准的GeLU激活函数进行了改进。这些层增强了模型的表达能力和效率,使其能够学习数据中更复杂的关系。该架构还得益于简化的设计,去除了不必要的偏置项,从而更充分地利用了模型的参数预算。此外,在嵌入之后还增加了一个额外的归一化层,进一步稳定了训练过程,确保模型能够更可靠地收敛。


ModernBERT的突出特点之一是其使用了交替注意力机制。该模型没有完全依赖全局注意力(随着输入序列长度的增加,全局注意力的计算成本会变得很高),而是在剩余层中交替使用全局注意力(每三层一次)和局部注意力(128个标记的滑动窗口)。这使得模型能够更快地处理长输入,同时不牺牲嵌入的质量。从概念上讲,这种方法模仿了人类处理文本的方式——在必要时理解更广泛的上下文,但大部分时间都专注于紧邻的文本。这显著降低了计算复杂性,使ModernBERT能够比传统模型更高效地处理更长的序列。


10


此外,ModernBERT融入了Unpadding和Sequence Packing两项技术,以提高其计算效率。填充(Padding)通常用于对齐不同长度的序列,但会浪费宝贵的计算资源。ModernBERT通过在处理过程中完全去除填充标记来解决这一问题,从而提高了速度,尤其是在与Flash Attention结合使用时效果更显著。序列打包(Sequence Packing)技术的使用进一步优化了训练过程,它通过将序列分组为连接在一起的批次,最大限度地利用GPU的并行处理能力,加快了预训练速度。


11


与传统模型和其他高级嵌入相比,ModernBERT的上下文嵌入得益于这些创新,不仅速度更快,而且更能捕捉数据中长上下文和复杂关系的微妙之处。这些功能共同提升了ModernBERT生成更准确、更具上下文感知能力的嵌入的能力,从而在一系列广泛的自然语言处理(NLP)任务中提高了性能。


ModernBERT对检索系统的影响

与其他编码器模型相比,ModernBERT通过提供更高的速度和准确性,显著增强了检索系统。其处理多达8,192个标记的能力使其能够处理长文档或查询而不丢失上下文,这对于需要理解大段文本的检索任务至关重要。这种长上下文处理能力与每三层交替一次的局部(128个标记)和全局注意力机制相结合,提高了计算效率和可扩展性。


在基准性能测试方面,ModernBERT在GLUE、CSN、SQA等基准测试中优于DeBERTaV3、NomicBERT等模型,展示了其卓越的语言理解能力。这直接增强了其在检索任务中检索上下文相关文档的能力。此外,在检索增强生成(RAG)管道中,ModernBERT通过为文档检索生成高质量的嵌入,表现出色,有助于生成更准确、更连贯的输出。


12


为微调生成数据

为了微调modernbert-embed-base模型,拥有高质量的训练数据至关重要。如果你已经使用Langfuse或Langsmith等可观测性工具从基于大型语言模型(LLM)的应用程序中收集了真实的交互数据,那将是非常理想的。然而,如果此类数据不可用,你可以像我们一样生成合成数据。


我们的方法涉及使用大型语言模型(LLM)从文档中提取问题-上下文对。这个结构化数据集遵循以下格式:


Dataset({
   features: ['query', 'answer'],
   num_rows: 600
})


每个条目都构成一个正样本对,这意味着查询和答案是语义相关的。这可以包括(查询,响应)对、同义复述、摘要、重复问题,甚至自然语言推理(NLI)对。无论是使用真实数据还是合成数据,这一步都能确保我们的模型为检索任务学习到有意义的上下文表示。


代码


环境设置

我们安装用于嵌入生成、基于大型语言模型(LLM)的应用程序和数据集处理的基本库。


! pip install sentence-transformers llama-index llama-index-llms-gemini datasets transformers==4.48.0


用于API访问和报告的环境变量也已配置好。


import os
GOOGLE_API_KEY = "xxxxxxxxxxxx"
os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY

os.environ["WANDB_DISABLED"] = "true"


导入必要的库

我们引入了llama-index、datasets和pandas等库来处理数据处理、数据集创建和操作。我们还导入了Gemini模型,用于从文档中生成问题和上下文。


from llama_index.core.evaluation import generate_question_context_pairs
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter
from llama_index.llms.gemini import Gemini
from llama_index.core import Settings
from datasets import Dataset
import pandas as pd


设置大型语言模型(LLM)

在这里,我们配置来自llama-index的Gemini模型,该模型将用于生成问题-上下文对。模型被加载并分配给Settings.llm,以确保其在后续的数据生成过程中可以随时使用。


llm = Gemini(
    model="models/gemini-1.5-flash",
)
Settings.llm = llm


生成问题-上下文对

create_qa_dataset函数处理来自指定目录的文档,将它们拆分成更小的块,并生成问题-上下文对。它首先加载文档,然后使用SentenceSplitter将它们拆分成块。该函数利用generate_question_context_pairs方法从这些块中创建问题及其相关的上下文。


def create_qa_dataset(input_dir ,  llm , num_questions_per_chunk):
    documents = SimpleDirectoryReader(input_dir=input_dir).load_data()
    node_parser = SentenceSplitter(chunk_size=256)
    nodes = node_parser.get_nodes_from_documents(documents)
    qa_dataset = generate_question_context_pairs(
          nodes,
          llm=llm,
          num_questions_per_chunk=num_questions_per_chunk
      )
    queries = qa_dataset.queries.values()
    contexts = [doc.get_content() for doc in nodes]
    return list(queries) , contexts


在这里,调用create_qa_dataset函数来生成问题-上下文对。


扁平化数据并创建 DataFrame


queries , contexts = create_qa_dataset("/content/train_data", llm , 2 )


然后,将数据展平为一个简单的查询-上下文结构,其中每个查询都与其对应的上下文配对。


# Flatten data into query-context pairs
data = {
    "query": [],
    "context": []
}
for i, context in enumerate(contexts):
    # Each context corresponds to two consecutive queries
    for query in queries[i * 2: i * 2 + 2]:
        data["query"].append(query)
        data["context"].append(context)
# Create a DataFrame
df = pd.DataFrame(data)
df.to_csv("train_data.csv" , index=False)


结果被组织成一个数据框,并保存为CSV文件以供进一步处理……


将数据拆分为训练集和测试集

在这里,将数据加载到Hugging Face数据集中,拆分为训练集和测试集(80-20),并打印出大小以进行验证。


hf_dataset = Dataset.from_pandas(df).train_test_split(test_size=0.2)
train_dataset = hf_dataset["train"]
test_dataset = hf_dataset["test"]
print(f"Train dataset size: {len(train_dataset)}")
print(f"Test dataset size: {len(test_dataset)}")


加载模型和损失函数

在这里,使用SentenceTransformer库加载ModernBERT模型。设备(GPU或CPU)会自动选择,并且将MultipleNegativesRankingLoss设置为微调时的损失函数,该函数适用于语义相似度等任务。


import torch
from sentence_transformers.losses import MultipleNegativesRankingLoss
from sentence_transformers import SentenceTransformer
model_name = "nomic-ai/modernbert-embed-base"
device = "cuda" if torch.cuda.is_available() else "cpu"
model = SentenceTransformer(model_name, device=device)
loss = MultipleNegativesRankingLoss(model)


设置训练参数

此代码块定义了微调模型的训练参数,包括轮数、批量大小、学习率和评估策略。


from sentence_transformers import SentenceTransformerTrainingArguments , SentenceTransformerTrainer
args = SentenceTransformerTrainingArguments(
    output_dir="output",
    num_train_epochs=1,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    learning_rate=2e-5,
    warmup_ratio=0.1,
    fp16=False, # i want to use fp32
    eval_strategy="steps",
    eval_steps=0.125,
    save_strategy="steps",
    save_steps=0,
    save_total_limit=2,
    logging_steps=1,
    report_to=None
)


fp16=False设置确保训练使用32位浮点数精度(fp32)进行。output_dir指定了在训练过程中模型输出将保存的位置。


初始化和运行训练器

在这里,我们创建一个SentenceTransformerTrainer实例,传入模型、训练参数、数据集、分词器和损失函数。训练器将处理训练循环。


trainer = SentenceTransformerTrainer(
    model=model,
    args=args,
    train_dataset=train_dataset,
    eval_dataset=test_dataset,
    tokenizer=model,
    loss=loss,
)


trainer.train()命令启动微调过程,利用之前定义的参数和数据集。


trainer.train()


在这篇文章中,我们了解了ModernBERT的上下文嵌入如何增强自然语言处理任务,如信息检索。通过生成合成数据并微调模型,你可以为特定用例解锁更好的性能。

文章来源:https://blog.gopenai.com/contextual-embeddings-with-modernbert-a-hands-on-guide-to-fine-tuning-modernbert-embed-3cca3d93ad4d
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消