这个计算余弦相似度的MYSQL函数:

    IF JSON_LENGTH(v1) != JSON_LENGTH(v2) THEN
        RETURN NULL;
    END IF;
    WHILE
        i < len
        DO
            SET sim = sim + (JSON_EXTRACT(v1, CONCAT('$[', i, ']')) * JSON_EXTRACT(v2, CONCAT('$[', i, ']')));
            SET i = i + 1;
        END WHILE;
    RETURN sim;
END

然后通过这个SQL语句搜索:

        $normalizedVector = json_encode($normalizedVector);
        $similarity =  new Expression("COSIM(v.normalized_vector, :json)", [
            ':json' => $normalizedVector
        ]);
        $query2->select(["v.id", "v.merchant_id", "v.product_id", "v.qfa_id", 'similarity' => $similarity]);
        $query2->andWhere(['v.id' => $recordIds]);
        $query2->orderBy(['similarity' => SORT_DESC]);
        $query2->limit($n);
        if ($withQfa) {
            $query2->innerJoinWith('qfa as q');
            $query2->andWhere('q.status=1');
        }

这个COSIM的函数,只是将两个json向量数组对应位置的两个数相乘后累加起来返回,为什么能达到语意搜索的效果?

COSIM 函数实际上只计算了两个 JSON 数组的点积(对应位置元素相乘后累加),而不是严格的余弦相似度(余弦相似度还需要除以两个向量的模长乘积)。但它依然能在你的场景中实现语义搜索,原因如下:


1. 语义搜索的原理:向量嵌入(Embedding)

现代语义搜索通常基于 文本嵌入(如 Word2Vec、BERT 等模型),将一段文本转换成一个高维稠密向量。这些向量的特点是:语义相近的文本,其向量在空间中的方向也相近。因此,我们可以用向量间的夹角余弦来度量相似度,即:

[
\text{cosine\_similarity}(A, B) = \frac{A \cdot B}{|A| |B|}
]

  • 当两个向量方向完全一致时,余弦相似度为 1;
  • 当它们正交时,为 0;
  • 当方向相反时,为 -1(但嵌入向量通常非负,所以范围在 [0,1])。

2. 为什么点积能代替余弦相似度?

在代码中,查询向量使用了 $normalizedVector 这一变量名,暗示查询向量已经做过归一化处理(即 (|B| = 1))。如果存储的向量 v.normalized_vector 同样也是归一化的((|A| = 1)),那么:

[
A \cdot B = \frac{A \cdot B}{1 \times 1} = \text{cosine\_similarity}(A, B)
]

此时点积就等于余弦相似度。即使存储的向量没有归一化,只要查询向量归一化,点积仍然与余弦相似度成正比(相差一个常数因子 (|A|)),排序结果不变。因此,在你的场景下,直接计算点积已经足够得到语义相似度的排序。


3. 函数 COSIM 的作用

COSIM 函数虽然名称是余弦相似度,但实现的是点积。它逐一取出两个 JSON 数组对应位置的值相乘并累加。这个过程正是向量相似度计算的核心运算。

  • 为什么能实现语义搜索?
    因为该函数计算出的点积数值越大,代表两个向量在方向上越接近,从而语义上越相关。配合 ORDER BY similarity DESC,就能将最相关的记录排在前面。

4. 注意事项

  • 如果向量没有归一化,点积会受到向量长度影响,可能导致长度大的向量获得更高的分数,从而影响排序。此时应改用完整的余弦相似度公式:
    [
    \frac{A \cdot B}{\sqrt{\sum A_i^2} \times \sqrt{\sum B_i^2}}
    ]
    其中分母部分可以通过预先存储向量的模长(或计算平方和)来优化。
  • 你的函数未考虑向量长度不等的情况(已返回 NULL),这保证了输入向量的维度一致。
  • 性能方面:该函数使用循环逐元素相乘,当向量维度较高(如 512 维或更高)时,每次查询都会对每条记录执行循环,效率较低。在生产环境中,建议:

    • 使用数据库的向量扩展(如 MySQL 8.0.31 后的 VECTOR 类型 + 向量函数);
    • 或将向量存储到专门支持向量检索的数据库(如 Milvus、Pgvector、Elasticsearch 等);
    • 或利用近似最近邻(ANN)算法加速检索。

总结

COSIM 函数虽然只计算了点积,但因为查询向量已经归一化(且假设存储向量也归一化),它等价于余弦相似度,因此能够正确反映两个文本向量的语义相关性。这正是语义搜索能够“按意思”排序的核心数学基础。

标签: none

添加新评论