本系列旨在揭开嵌入embedding模型的神秘面纱,并向您展示如何在项目中使用它们。
将介绍三种令人兴奋的应用:
- 查找最相似的 Quora 或 StackOverflow 问题
- 给定一个庞大的数据集,找出最相似的项目
- 直接在用户浏览器中运行搜索嵌入模型(无需服务器)
什么是嵌入?
您一直在阅读关于 "嵌入这个 "和 "嵌入那个 "的文章,但您可能仍然不知道它们到底是什么。你并不孤单!即使您对嵌入有一个模糊的概念,您也可能通过黑盒子 API 使用它们,而不真正了解它们在引擎盖下发生了什么。这是一个问题,因为目前的开源嵌入模型非常强大--它们非常容易部署、体积小(因此托管费用低),而且性能优于许多闭源模型。
嵌入将信息表示为数字向量(把它想象成一个列表!)。例如,我们可以获得一个单词、一个句子、一份文档、一张图片、一个音频文件等的嵌入。给出句子 "今天是个晴朗的日子",我们就可以得到它的嵌入,它是一个特定大小的向量,比如 384 个数字(这种向量可以是 [0.32, 0.42, 0.15, ......,0.72])。
有趣的是,嵌入可以捕捉信息的语义。
例如,嵌入句子 "今天是个晴朗的日子Today is a sunny day "与嵌入句子 "今天天气不错The weather is nice today” "非常相似。即使用词不同,但意思相似,嵌入也会反映出这一点。
因此,这个向量捕捉到了信息的语义,使其更容易相互比较。例如,我们可以使用嵌入技术在 Quora 或 StackOverflow 中查找类似的问题、搜索代码、查找类似的图片等。让我们来看一些代码!
我们将使用 Sentence Transformers(句子转换器),这是一个开源库,可以让我们轻松使用预训练的嵌入模型。特别是,ST 可以让我们快速将句子转化为嵌入模型。让我们运行一个示例,然后讨论它在引擎盖下是如何工作的。
嵌入的机制
首先,让我们安装该库:
!pip install sentence_transformers |
第二步是加载现有模型。我们将首先使用all-MiniLM-L6-v2。这并不是最好的开源嵌入模型,但它相当流行,而且非常小(2,300 万个参数),这意味着我们可以很快上手。
from sentence_transformers import SentenceTransformer |
现在我们加载了一个模型,让我们用它来编码一些句子。我们可以使用编码方法获取句子列表的嵌入。让我们试试看!
from sentence_transformers import util |
输出:
(3, 384)
all-MiniLM-L6-v2 创建了 384 个嵌入值。我们可以得到三个嵌入值,每个句子一个。
将嵌入视为嵌入式的 "数据库":给定一个新句子,我们如何找到最相似的句子?
我们可以使用 util.pytorch_cos_sim 方法计算新句子嵌入和数据库中所有嵌入之间的余弦相似度(我们很快会详细讨论)。
余弦相似度是一个介于 0 和 1 之间的数字,表示两个嵌入的相似程度:
- 数值为 1 表示嵌入完全相同,
- 而 0 表示嵌入完全不同。
让我们试试看!
first_embedding = model.encode("Today is a sunny day") |
输出:
tensor([[0.7344]]) The weather today is beautiful
tensor([[0.4180]]) It's raining!
tensor([[0.1060]]) Dogs are awesome
我们能从中解读出什么呢?
- 虽然 "今天是个晴朗的日子today is a sunny day "和 "今天天气很好The weather today is beautiful "的词语并不相同,但嵌入可以捕捉到一些语义,因此余弦相似度相对较高。
- 另一方面,"狗真棒Dogs are awesome "虽然是事实,但与天气或今天无关,因此余弦相似度很低。
如何在产品中使用嵌入
为了扩展类似嵌入的概念,让我们来看看如何在产品中使用它们。
想象一下,美国社会保障机构希望允许用户在输入框中输入与医疗保险相关的问题。这个问题非常敏感,我们很可能不想让模型产生与此无关的幻觉!相反,我们可以利用问题数据库(在这种情况下,有一个现有的医疗保险常见问题解答)。过程与上述类似"。
- 我们有一个问题和答案的语料库(集合)。
- 我们计算所有问题的嵌入。
- 给定一个新问题,计算其嵌入。
- 我们计算新问题嵌入与数据库中所有嵌入之间的余弦相似度。
- 我们将返回最相似的问题(与最相似的嵌入相关联)。
第 1 步和第 2 步可以离线完成(也就是说,我们只计算一次嵌入并将其存储起来)。其余步骤可以在搜索时完成(每次用户提问时)。让我们看看代码是怎样的。
# Data from https://faq.ssa.gov/en-US/topic/?id=CAT-01092 |
我们再次使用编码方法来获取所有问题的嵌入。
corpus_embeddings = model.encode(list(faq.values())) |
输出:(5, 384)
一旦用户提出问题,我们就会获得其嵌入。我们通常将这种嵌入称为查询嵌入。
user_question = ""加薪后我需要支付更多吗?" |
输出:(384,)
现在我们可以计算语料库嵌入和查询嵌入之间的相似度。
我们可以像之前那样使用 util.pytorch.cos_sim 进行循环计算,但句子转换器提供了一种更友好的方法,名为 semantic_search,它可以为我们完成所有工作。它会返回前 k 个最相似的嵌入及其相似度得分。让我们试试看!
similarities = util.semantic_search(query_embedding, corpus_embeddings, top_k=3) |
输出:
[[{'corpus_id': 3, 'score': 0.35796287655830383}, |
现在让我们来看看这与哪些问题和答案相对应:
for i, result in enumerate(similarities[0]): |
Top 1 question (p=0.35796287655830383): 我的医疗保险保费是否会因为我的收入较高而增加?? |
很好,在给出 "加薪后我需要支付更多的费用吗?"这个问题后,我们知道最相似的问题是 "我的医疗保险费会因为我的收入提高而增加吗?",因此我们可以返回所提供的答案。在实践中,你可能会有成千上万个嵌入问题,但这只是一个简单而强大的例子,说明了嵌入是如何用于查找相似问题的。
既然我们已经更好地理解了什么是嵌入以及如何使用嵌入,那么就让我们对嵌入进行更深入的研究吧!