正文
甚至可以将这种方法与多 Multi-Vector Retrieval 结合起来,使用 Multi-embedding 方式。例如,对于图像,如果需要原始图像,可以直接对其进行嵌入;如果需要总结,则使用其总结进行嵌入。方法多种多样,能够灵活应对不同类型的检索需求。
Semantic Search 看似行将就木,原因在于其本身缺乏明确的数据结构。它本质上是一堆向量的集合,这些向量简单地堆在一起,而没有更深层次的组织形式。
在传统的搜索方法中,通常会借助树状结构或其他形式的索引关系来优化搜索效率,但 Semantic Search 中并不存在这样的结构。原生的 RAG 系统本身也没有任何系统化的结构。
在 ANN(Approximate Nearest Neighbors,近似最近邻)中所使用的结构,虽然在某种程度上可以用于检索小型数据库,但它并不是为了构建系统结构而设计的。
然而,正如老子所说“无为而无不为”,他什么都不是,那就什么都可以是。正是因为 Semantic Search 没有内在的数据结构,反而为其提供了极大的灵活性。这种灵活性使得我们可以为其赋予任何合适的数据结构。
RAG 系统设计的关键之一,就是为 Semantic Search 提供一个数据结构,使其更贴近数据的天然结构
。例如,如果数据的天然结构是一棵树,那么就可以将系统设计成树状结构;如果数据的天然结构是一根草,那么就可以设计成相应的草状结构。
系统设计是整个工作的重中之重。在深入探讨之前,需要先强调一个重要的观点:
工程是取舍的艺术(Engineering is the Art of Trade-off)
。这是我仅有的 3 年工程师生涯中领悟的最重要的道理,我觉得,这也是每一位工程师都必须深刻理解的核心理念。
工程师需要明确自己能够做出什么样的取舍权衡(trade-off),能够接受什么,不能接受什么,以及可以在哪些方面做出牺牲,以换取其他方面的利益。这些是必须仔细考虑的问题。接下来所有的内容都会是这个原则的拓展延伸
在整个系统设计环节中,权衡是核心原则。以 Contrastive Loss(对比损失函数)为例。对比损失函数的公式,我们很明显看得出他是一个 switch loss. 什么意思呢?当标签 y=0 时,1−y=1,因此公式中的一项被触发,而另一项则不被触发(switch)。
此时,损失值为 ,即距离的平方。当 y=1 时,1−y=0,只有另一项被触发。公式中的 是一个缩放因子(scaling factor),通常不会对结果产生显著影响。
因此,损失值为 。如果 大于 m,即距离大于阈值 m,那么 是一个负数,小于 0,因此损失值为 0。相反,如果 DW 小于 m,则损失值为正。
换句话说,对比损失函数的关键在于距离 。只要有距离存在,就会产生损失。这种损失会将相似的点(尽可能地拉到同一个点上,即把相似的样本紧密地聚集在一起。
然而,往外推的机制仅在距离小于阈值 m 时起作用。如果距离超过了阈值 m,则对比损失函数不再对其产生影响。所以,一个 batch 的 loss 是 0 的时候,可能你不需要太慌张。由此可知,Contrastive loss 形成的是多个相距 m 距离的紧密聚类,适用于结构紧密,方差较小的数据。
接下来的例子是 Triplet Loss(三元组损失)。其中,d 表示距离,a 表示 anchor(锚点),p 表示 positive(正样本), n 表示 negative。Triplet Loss 的计算方式是:Anchor 到 Positive 的距离减去 Anchor 到 Negative 的距离,再加上一个阈值 m。
如果 Anchor 到 Positive 的距离减去 Anchor 到 Negative 的距离的绝对值小于阈值 m,那么损失值就不是 0;反之,如果这个绝对值大于或等于 m,那么损失值就是 0。
也就是说,Triplet Loss 并不关心 Negative 和 Positive 之间的距离有多远,它只关注这两个点与 Anchor 之间距离的差值是否小于阈值 m。如果差值小于 m,它会将样本向外推,使其满足条件;如果差值已经大于或等于 m,那么它就不再进行优化,损失值为 0。
在训练使用 Triplet Loss 和 contrastive loss 的模型时,都可能会出现整个批次的损失值为 0 的情况。这时不要急于下结论,这并不一定意味着模型过拟合。可能只是巧合,该批次的数据恰好满足了损失函数的条件。因为 Triplet Loss 只关注 positive 和 negative 与 anchor 之间的相对距离,所以它很容易满足条件,导致模型更新较少,收敛速度相对较慢。
此外,只有当相对距离小于阈值 m 时,才会产生损失值。与对比 Contrastive Loss 不同,Triplet Loss 不会将所有相似的样本强行聚到一个点上,因此同类内方差(Intra-Clas Variance)较大。
它形成的聚类通常是距离大致等于 m 的较大范围的集合,而不是紧密聚集在一起。这意味着一个类别中可以包含更多元的样本。
Triplet Loss 特别适用于类内方差较大的数据。例如,人脸数据就是一个很好的例子。很多人对人脸数据存在误解,认为同一个人的不同人脸图像之间的差异很小,但实际上,同一个人在不同光照、角度、表情等条件下的脸可能有非常大的差距。
这就是为什么 triplet loss 是人脸识别的默认 loss。而那些没有看过数据整天想当然的工程师可能就会选用应对小方差的 Contrastive loss。
选择损失函数的前提是必须充分了解数据
,只有真正理解数据的特性,才能明确应该采用什么样的损失函数来指导模型训练。 对于其他各种损失函数,也可以用类似的方法进行分析。
在选择 Distant Function 时,需要考虑其本质是度量嵌入。从这个角度出发,几乎所有的距离函数可以分为两类:一是满足度量空间(Metric Space)定义的距离函数,例如欧几里得距离(Euclidean distance),也叫 L2 距离;二是不满足度量空间定义的余弦距离(cosine distance)。
余弦距离不是度量距离的原因在于它不满足度量空间的两个基本条件:非负性(positivity)和三角不等式(triangle inequality)。大致证明如下:假设从原点出发有两个不同的点,它们在同一条直线上,但方向相反。
这两个点的余弦相似度是相同的,这意味着它们的“距离”为 0,这显然不符合非负性。此外,考虑三个点 x = [1, 0]; y = [0, 1]; z = [1, 1],,余弦距离不满足三角不等式,即两边之和小于第三边。
尽管余弦距离不符合度量空间的定义,但它具有计算简单的优势,因为它只考虑方向,而与 Magnitude 无关。这使得余弦距离特别适合那些只关注方向的场景,例如在推荐系统中,如 Netflix 和 Spotify。
如果用户喜欢摇滚音乐,那么所有与摇滚相关的方向都是相关的;如果用户喜欢恐怖电影,那么所有与恐怖电影相关的方向也是相关的。在这种场景中,不需要考虑具体的数值,只需要确定方向即可。余弦距离的值域在 0 到 1 之间,也不会出现数值溢出的情况。
欧几里得距离(Euclidean distance)与余弦距离不同,其计算相对复杂,需要考虑两个点在空间中的实际距离。这有点类似于 Word2Vec 模型中通过训练得到的向量空间关系,例如“king - man = queen - woman”这种语义关系。