用 Go 做语义检索、推荐或 RAG 时,总要算向量相似度。做法大致三种:自己写几行、用数值库、或者交给向量数据库。下面按「方案」捋一捋,方便你按场景选。


一、为何要多种方案?

向量相似度用在语义检索、推荐、去重聚类等场景很常见,但数据量差别很大:有时就几百几千个向量在内存里算,有时是百万级要做近似最近邻(ANN)检索。所以没有一种写法能通吃,有的场景适合手写,有的适合用库,有的直接上向量库。下面分别说。


二、方案一:手写实现

适合你:向量不多(几百几千)、不想加依赖、或者要自己写打分逻辑。拿标准库在内存里算就行。

常用的就三种度量,用 []float64 都能手写。

余弦相似度:看两个向量的方向是否一致,和长度无关,适合高维 embedding。

// CosineSimilarity 计算两向量的余弦相似度,返回值在 [-1, 1]
func CosineSimilarity(a, b []float64) float64 {
    if len(a) != len(b) || len(a) == 0 {
        return 0
    }
    var sumAB, sumA2, sumB2 float64
    for i := range a {
        sumAB += a[i] * b[i]
        sumA2 += a[i] * a[i]
        sumB2 += b[i] * b[i]
    }
    if sumA2 == 0 || sumB2 == 0 {
        return 0
    }
    return sumAB / (math.Sqrt(sumA2) * math.Sqrt(sumB2))
}

点积:向量做过归一化的话,点积和余弦是一回事,写起来更简单,很多召回排序直接用点积。

// DotProduct 计算两向量的点积
func DotProduct(a, b []float64) float64 {
    if len(a) != len(b) {
        return 0
    }
    var sum float64
    for i := range a {
        sum += a[i] * b[i]
    }
    return sum
}

欧氏距离:就是两点之间的直线距离,越小越像。想要「相似度」的话,可以用 1/(1+distance) 之类的式子转一下。

// EuclideanDistance 计算欧氏距离
func EuclideanDistance(a, b []float64) float64 {
    if len(a) != len(b) {
        return math.MaxFloat64
    }
    var sum float64
    for i := range a {
        diff := a[i] - b[i]
        sum += diff * diff
    }
    return math.Sqrt(sum)
}

手写时注意三点:两个向量维度要一样;注意空向量、全零别除零;外部来的向量先看有没有归一化,再决定用点积还是余弦。这是最轻量的一种做法,不加任何依赖。


三、方案二:数值库

适合你:一个查询向量要对很多候选算相似度,或者想用矩阵运算把性能拉上去。

Go 里常用 gonumgonum.org/v1/gonum)做向量、矩阵运算,点积、范数都有,拿来算余弦、欧氏距离都行,还能批量算。不用自己写循环,库里有优化。说白了,就是「还在自己进程里算,但交给库来算」——介于手写和向量库之间。

数据量上来、又要高性能、还不想上向量库时,可以优先用这种。


四、方案三:向量数据库

适合你:向量特别多(十万、百万级)、要建索引做近似最近邻(ANN)检索,或者你们已经在用向量库了。

Milvus、Pinecone、Qdrant、Weaviate 等都提供 Go SDK。向量写进去之后,相似度怎么算、怎么检索,都交给数据库,余弦、点积、欧氏都支持,你只要传查询向量、拿回 Top-K 就行。相似度在库内算,Go 这边只管调 SDK 和写业务逻辑,不用再手写相似度。

如果要对检索结果做二次排序、过滤或自定义打分,可以在 Go 里对返回的少量结果再算一遍相似度或加权,这时用方案一配合一下就好。


五、如何选方案?

  • 数据量小、不想加依赖、或者打分逻辑要自己控 → 方案一手写。
  • 量上来了、还要在进程内算、想提速 → 方案二数值库(如 gonum)。
  • 规模很大、要索引和 ANN → 方案三向量数据库 + Go SDK。

三种也可以搭着用:比如先用向量库粗排,再在 Go 里对 Top-K 做一次自定义相似度或业务加权。


六、小结

Go 算向量相似度,常见就这三条路:手写(余弦、点积、欧氏)适合小规模、零依赖;数值库(如 gonum)适合批量、要性能;向量数据库 + Go SDK 适合大规模检索。按数据量和要不要做 ANN 来选,需要的话再把检索结果在代码里算一遍相似度或打分,灵活和性能都能兼顾。