Gox语言中用超大数计算向量的余弦相似度-GX30

我们利用Gox语言中引入Go语言的math标准包,就可以计算两个向量的余弦相似度,例子代码如下:

// work out the cosine similarity of two vectors
// the result is in [0..1], which 1 indicates almost the same, 0 indicates not same at all

calCosSim = func(f1, f2) {
    l1 = len(f1)
    l2 = len(f2)

    if l1 != l2 {
        printfln("two vectors' length are not same,length of f1: %v,length of f2: %v", l1, l2)
        return -1
    }

    rr = 0.0
    f1r = 0.0
    f2r = 0.0

    for i = 0; i < l1; i++ {
        rr += f1[i] * f2[i]
        f1r += f1[i] * f1[i]
        f2r += f2[i] * f2[i]
    }

    rs = rr / (math.Sqrt(f1r) * math.Sqrt(f2r))

    return rs
}

v1 = [1, 2, 3, 5]
v2 = [2.0, 2, 3, 4]

println(calCosSim(v1, v2))

v3 = [9, 9, 8, 8]
println(calCosSim(v1, v3))



运行结果是:

λ gox scripts\calCosineSimilarity.gox
0.9756156783416059
0.8556774556139578

可以看出,经过计算,向量v1与v2更加相似,v1与v3则更不相似一些,这也是正确地结果。注意,两个向量的长度必须一致才能计算余弦相似度。

上面这段代码中计算余弦相似度的函数,在一般应用中已经比较完美,但当所计算的向量存在非常大的数值时会有一定的风险。这是因为各种数据类型所能表达的数值范围都是有限的,即使是64个二进制位表达的float64类型的变量,也有一定的可表达数值的范围。虽然我们输入该函数的参数一般不会突破这个范围,但仔细观察该函数内的计算过程可以发现,其中涉及有平方操作并且求总和,理论上这就存在一定在计算过程中出现计算值过大数值溢出而导致计算结果错误的危险性。对于这种情况,Go语言在math包下专门提供了一个big子包来处理这种超大数或超高精度数值的计算,Gox语言也内置了这个包。我们可以试着改写一下这个函数。

// work out the cosine similarity of two vectors
// the result is in [0..1], which 1 indicates almost the same, 0 indicates not same at all

// set the short name for math/big package
big = math_big

calCosineSimilarityBig = func(f1, f2) {

    l1 = len(f1)
    l2 = len(f2)

    if l1 != l2 {
        printfln("two vectors' length are not same,length of f1: %v,length of f2: %v", l1, l2)
        return -1
    }

    rr = big.NewFloat(0.0)

    f1r = big.NewFloat(0.0)

    f2r = big.NewFloat(0.0)

    for i = 0; i < l1; i++ {
        f1Sub = big.NewFloat(f1[i])
        f2Sub = big.NewFloat(f2[i])

        rr.Add(rr, big.NewFloat(0).Mul(f1Sub, f2Sub))
        f1r.Add(f1r, big.NewFloat(0).Mul(f1Sub, f1Sub))
        f2r.Add(f2r, big.NewFloat(0).Mul(f2Sub, f2Sub))
    }

    tmp1 = big.NewFloat(0).Mul(big.NewFloat(0).Sqrt(f1r), big.NewFloat(0).Sqrt(f2r))

    tmp2 = big.NewFloat(0).Quo(rr, tmp1)

    tmp3, _ = tmp2.Float64()

    return tmp3
}


v1 = [1, 2, 3, 5]
v2 = [2.0, 2, 3, 4]

println(calCosineSimilarityBig(v1, v2))

v3 = [9, 9, 8, 8]
println(calCosineSimilarityBig(v1, v3))

println("------------")

v1 = [111111, 2222222, 3333333, 55555555555555]
v2 = [2.0, 2, 3, 4]

println(calCosineSimilarityBig(v1, v2))

v3 = [7877358736587356853, 9666666, 8.01, 999999999999]
println(calCosineSimilarityBig(v1, v3))


运行结果如下:

λ gox scripts\calCosineSimilarityBig.gox
0.9756156783416059
0.8556774556139578
------------
0.6963106697792856
1.2894610181964116e-07

可以看出,对于第二组超大数的向量对比,计算出的结果精度还是不错的。

推荐阅读更多精彩内容