在NPU的接入过程中,经常会遇到最终结果精度不足的情况,这个时候我们就需要dump每层数据和原始框架float结果对比,可能很多人直接beyond compare,有时候肉眼可以看出来,不过最好的方法还是看它的余弦相似度。基本上所有NPU的发布包里或多或少都提供了类似的功能,比如rknn、nnie等。 NPU跑的是量化网络,dump下来一般已经做了反量化,所以得到的也是float结果,所以可以直接比较。
通常相似度的比较可以用余弦距离和空间欧式距离表示,一般用的多的可能是余弦距离,根据线性空间理论,当两个向量相似时,他们的内积较大,我们可以认为内积大小表示两个向量的相似性,cos的结果表示余弦相似度。
怎么解释呢?
假设要比2个tensor的相似度
tensor1:1,1,2,1,1,1,0,0,0
tensor2:1,1,1,0,1,1,1,1,1
那么根据上面的公式
得到相似度0.81,还是比较像的
公式是这样,用c/c++实现可以,不过通常使用python更加方便,不用造轮子了。
python cos_distance.py --tensor1 file1.txt --tensor2 file2.txt
# cos_distance.py
import os
import argparse
import numpy as np
from scipy.spatial import distance
def cmp_tensor(tensor_file1, tensor_file2):
file1_ext = os.path.splitext(tensor_file1)[1]
file2_ext = os.path.splitext(tensor_file2)[1]
if file1_ext == '.npy':
tensor1 = np.load(tensor_file1).astype(np.float32).reshape(-1)
else:
tensor1 = np.loadtxt(tensor_file1, dtype=np.float32)
if file2_ext == '.npy':
tensor2 = np.load(tensor_file2).astype(np.float32).reshape(-1)
else:
tensor2 = np.loadtxt(tensor_file2, dtype=np.float32)
eculidean_distance = distance.euclidean(tensor1, tensor2)
cosine_distance = 1.0 - distance.cosine(tensor1, tensor2)
return eculidean_distance, cosine_distance
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--tensor1',
type=str,
help="Tensor1 file path.")
parser.add_argument('--tensor2',
type=str,
help="Tensor2 file path.")
args = parser.parse_args()
ecu_dis, cos_dis = cmp_tensor(args.tensor1, args.tensor2)
print('eculidean distance: {}'.format(ecu_dis))
print('cosine distance: {}'.format(cos_dis))
列出余弦距离的c语言版本,其实就是上面的公式翻译一下而已,你看看,是不是很简单。
//假设要计算的tensor1 tensor2都以二进制的方式保存在data1、data2内存里,tensor_len是有多少元素
float cos_distance(int tensor_len, float *data1, float *data2)
{
int i;
float fenzi = 0.0f;
float fenmu = 0.0f;
float sqrt_vec0 = 0.0f;
float sqrt_vec1 = 0.0f;
for(i = 0; i < tensor_len; i ++)
{
fenzi += data1[i]*data2[i];
sqrt_vec0 += data1[i]*data1[i];
sqrt_vec1 += data2[i]*data2[i];
}
sqrt_vec0 = sqrt(sqrt_vec0); //记得-lm
sqrt_vec1 = sqrt(sqrt_vec1);
fenmu = sqrt_vec0 * sqrt_vec1;
return fenzi/fenmu;
}