微信扫码
与创始人交个朋友
我要投稿
导读
通过阅读本文你将了解:
什么是套娃词向量模型
特点及解决什么问题?
获取对应论文, 技术细节, 模型权重
介绍
Embedding(文本嵌入)是单个词或整个句子的高维向量表示。
这些数字数组捕获了关于底层文本的丰富信息,可用于许多下游任务,如语义理解、分类、聚类、信息检索(RAG)、重新排序等。
通常,嵌入向量的维度 d 是固定的。嵌入维度通常是2的幂,范围从64到4096。
使用套娃嵌入( Matryoshka embeddings),可以根据应用程序更改嵌入的维度。这可以减少存储空间,节省成本,并提高检索速度。
什么是文本嵌入
我们首先定义一个词汇表,将所有可能的输入字符映射为整数值。词汇表不仅包括字母,还包括特殊字符、短词和子词:
{"a": 1,"b": 2,"c": 3,..."z": 26,"the": 27," ": 28}
经过标记化后,我们可以将标记列表输入我们的编码器模型。编码器已经从大量的训练数据中学习,将每个标记转换为高维数值向量嵌入。
例如,OpenAI的text-embedding-3-large模型的嵌入输出维度为3072(一个很奇怪的数字)
要获得单个句子嵌入,我们需要压缩来自多个标记嵌入的信息。一种方法是简单地对所有标记嵌入求平均。
Matryoshka Embeddings
Matryoshka embeddings是由华盛顿大学、谷歌研究和哈佛大学的研究人员在2022年的论文“Matryoshka Representation Learning”中引入
Matryoshka embeddings被训练为在一个单一的嵌入向量中编码不同粒度的信息。
例如,我们不是简单地训练一个大小为d = 1024的完整嵌入向量,而是使用一个维度列表matryoshka_dims = [1024,512,256,128,64]来优化我们想要同时优化的损失函数[2]。
这导致了一个嵌入向量,其中包含了嵌套信息,最粗糙的信息存储在前几个维度中,而越来越多的细节存储在后面的维度中。
这实际上意味着我们可以在任何我们想要的地方截断我们的嵌入向量,而不会牺牲太多性能。
为什么这很重要?
假设我们想在向量数据库中存储 n 个文本嵌入向量。每个嵌入向量有 d 个维度。而且每个数字通常是一个32位浮点数。因此,我们需要 n * d * 4 字节 的存储空间。
如果我们想计算诸如点积或余弦相似度(这只是一个归一化的点积)之类的相似度度量,维度 d 越高,我们需要做的数学计算就越多。
使用 MRL,如果我们关心内存占用小、处理速度快,因此节省成本,我们可能只使用前64个维度。如果我们想要最佳的下游性能,我们就使用所有维度。或者我们可能在两者之间做出选择。
因此,MRL使LLM用户能够在嵌入大小(成本)和下游性能的小幅降低之间进行权衡。
使用 MRL 与 Nomic AI
Nomic 的 Matryoshka 文本嵌入模型 nomic-embed-text-v1.5 是使用 matryoshka_dims = [768,512,256,128,64] 进行训练的。该模型可以在 Hugging Face 上获取。
该编码器模型的另一个好处是它支持不同的前缀。该模型支持前缀 [search_query, search_document, classification, clustering],以获得每个特定下游任务的更好嵌入。
以下是 nomic-embed-text-v1.5 在 Massive Text Embedding Benchmark (MTEB) 上的表现:
使用 Python 和 Sentence Transformers 库来实现该模型:
!pip install torch sentence_transformers einops
import torch
from sentence_transformers import SentenceTransformer
device = "cuda" if torch.cuda.is_available() else"cpu"
model = SentenceTransformer(
"nomic-ai/nomic-embed-text-v1.5",
device=device,
trust_remote_code=True,
prompts={
"search_query": "search_query: ",
"search_document": "search_document: ",
"classification": "classification: ",
"clustering": "clustering: ",
},
)
def embed_sentences(
model: SentenceTransformer,
sentences: list[str],
prompt_name: str,
matryoshka_dim: int,
device: str,
):
assert matryoshka_dim <= 768, "maximum dimension for nomic-embed-text-v1.5 is 768"
embeddings = model.encode(
sentences, prompt_name=prompt_name, device=device, convert_to_tensor=True
)
embeddings = torch.nn.functional.layer_norm(
embeddings, normalized_shape=(embeddings.shape[1],)
)
embeddings = embeddings[:, :matryoshka_dim]
embeddings = torch.nn.functional.normalize(embeddings, p=2, dim=1)
return embeddings.cpu()
使用 matryoshka_dim 参数,我们截断了我们的 768 维嵌入向量,然后对新的嵌入向量进行了归一化。
现在,我们可以设置我们期望的维度,并对一些维基百科文本以及我们的检索辅助生成(RAG)用例的问题进行编码:
matryoshka_dim = 64
wikipedia_texts = [
"The dog (Canis familiaris or Canis lupus familiaris) is a domesticated descendant of the wolf.",
"Albert Einstein was born in Ulm in the Kingdom of Württemberg in the German Empire, on 14 March 1879.",
"Einstein excelled at physics and mathematics from an early age, and soon acquired the mathematical expertise normally only found in a child several years his senior.",
"Werner Karl Heisenberg was a German theoretical physicist, one of the main pioneers of the theory of quantum mechanics, and a principal scientist in the Nazi nuclear weapons program during World War II.",
"Steven Paul Jobs (February 24, 1955 - October 5, 2011) was an American businessman, inventor, and investor best known for co-founding the technology giant Apple Inc.",
"The cat (Felis catus), commonly referred to as the domestic cat or house cat, is the only domesticated species in the family Felidae.",
]
question = ["Where was Albert Einstein born?"]
question_embedding = embed_sentences(
model,
sentences=question,
prompt_name="search_query",
matryoshka_dim=matryoshka_dim,
device=device,
)
document_embeddings = embed_sentences(
model,
sentences=wikipedia_texts,
prompt_name="search_document",
matryoshka_dim=matryoshka_dim,
device=device,
)
print(f"document_embeddings.shape: {document_embeddings.shape}")
print(f"question_embedding.shape:{question_embedding.shape}")
>> document_embeddings.shape: torch.Size([6, 64])
>> question_embedding.shape: torch.Size([1, 64])
我们可以使用散点图可视化我们的 Matryoshka 文本嵌入的前两个维度。然而,该嵌入模型并没有明确针对 2 维的 Matryoshka 维度进行优化。
接下来,我们可以将我们的文档嵌入存储在一个向量数据库中。我使用 Faiss。Faiss 是 Meta Research 的一个开源库,用于高效的密集向量相似性搜索和聚类
!pip install faiss-cpu
import faiss
index = faiss.IndexFlatIP(matryoshka_dim)
index.add(document_embeddings)
这将使用 "内积的精确搜索" 和 IndexFlatIP 创建一个向量数据库,其中使用点积相似度度量。由于我们使用了归一化嵌入,点积和余弦相似度是相同的。
index 现在是一个包含六个文本嵌入的向量数据库:
print(index.ntotal)>> 6
让我们搜索与我们的问题最相似的嵌入,并检索前 k 个结果:
distances, indices = index.search(question_embedding, k=6)print(indices)print(distances)>> [[1 2 3 4 0 5]]>> [[0.96335280.729192 0.63353264 0.62068397 0.512541 0.43155164]]
我们最相似的文本在我们的数据库中的索引为 1,相似度得分为 0.96(最大值为 1.0)。
Output:
# d=64 的结果print(question)print(wikipedia_texts[1])>> ['阿尔伯特·爱因斯坦出生在哪里?']>> '阿尔伯特·爱因斯坦出生在德意志帝国的维尔滕贝格王国乌尔姆,于1879年3月14日。'
我还使用 matryoshka_dim=768 重新运行了代码,并获得了类似的结果。然而,更高的维度需要更多的内存和计算。
# 结果与 d=768print(indices)print(distances)>> [[1 2 4 3 0 5]]>> [[0.92466116 0.645744 0.54405797 0.54004824 0.39331824 0.37972206]]
MRL & 量化
如果我们想要进一步压缩我们的嵌入,我们可以使用 MRL 与二进制向量量化。二进制量化将嵌入向量中大于零的所有数字转换为一,其他所有数字转换为零
使用二进制量化,维度为 d 的嵌入向量只需要 d / 8 字节的内存,与 float32 的 d * 4 字节相比,这是尺寸减小了 32 倍 [4]。然而,这种减小是以性能为代价的。
结论
在训练过程中使用Matryoshka损失的嵌入模型同时针对多个嵌入维度进行了优化。
通过Matryoshka表示学习,LLM用户可以在文本嵌入大小和性能之间进行权衡。
较小的嵌入需要更少的内存和计算资源,从长远来看可以节省大量资金。它们计算速度更快,因此在RAG应用程序中具有更高的检索速度。
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-05-28
2024-04-26
2024-04-11
2024-08-21
2024-07-09
2024-08-13
2024-07-18
2024-10-25
2024-07-01
2024-06-17