前段时间做了一个表情搜索的评测,用到了相似度评测,在实践过程中遇到了一个问题,在这里和大家分享下。
表情搜索做了一次重构,在做结果质量评测时,会对比新的服务器和现有线上服务器的返回结果数,返回结果重合率,返回结果相似度这三个维度。进而评估新服务器的质量。
以上3个维度是递进的关系,结果重合率是对比前N个结果经过相同排序后,重合的比例,但是这种情况下,即使相同,也不能说完全一样,比如:ABCDE和AEBDC。所以鉴于这种情况,就增加了距离相似度评测。
相似度算法介绍:
对比不同的距离算法,最后通过结果对比,选择莱文斯坦(Levenshtein)距离算法。git地址为:https://github.com/miohtama/python-Levenshtein。python可以直接通过pip安装,是业界成熟的相似度距离算法,调用方法如下:
#! /usr/bin/python # -*- coding: utf8 -*- from Levenshtein import * apply_edit() #根据第一个参数editops()给出的操作权重,对第一个字符串基于第二个字符串进行相对于权重的操作 distance() #计算2个字符串之间需要操作的绝对距离 editops() #找到将一个字符串转换成另外一个字符串的所有编辑操作序列 hamming() #计算2个字符串不同字符的个数,这2个字符串长度必须相同 inverse() #用于反转所有的编辑操作序列 jaro() #计算2个字符串的相识度,这个给与相同的字符更高的权重指数 jaro_winkler() #计算2个字符串的相识度,相对于jaro 他给相识的字符串添加了更高的权重指数,所以得出的结果会相对jaro更大(%百分比比更大) matching_blocks() #找到他们不同的块和相同的块,从第六个开始相同,那么返回截止5-5不相同的1,第8个后面也开始相同所以返回8-8-1,相同后面进行对比不同,最后2个对比相同返回0 median() #找到一个列表中所有字符串中相同的元素,并且将这些元素整合,找到最接近这些元素的值,可以不是字符串中的值。 median_improve() #通过扰动来改进近似的广义中值字符串。 opcodes() #给出所有第一个字符串转换成第二个字符串需要权重的操作和操作详情会给出一个列表,列表的值为元组,每个元组中有5个值 #[('delete', 0, 1, 0, 0), ('equal', 1, 3, 0, 2), ('insert', 3, 3, 2, 3), ('replace', 3, 4, 3, 4)] #第一个值是需要修改的权重,例如第一个元组是要删除的操作,2和3是第一个字符串需要改变的切片起始位和结束位,例如第一个元组是删除第一字符串的0-1这个下标的元素 #4和5是第二个字符串需要改变的切片起始位和结束位,例如第一个元组是删除第一字符串的0-0这个下标的元素,所以第二个不需要删除 quickmedian() #最快的速度找到最相近元素出现最多从新匹配出的一个新的字符串 ratio() #计算2个字符串的相似度,它是基于最小编辑距离 seqratio() #计算两个字符串序列的相似率。 setmedian() #找到一个字符串集的中位数(作为序列传递)。取最接近的一个字符串进行传递,这个字符串必须是最接近所有字符串,并且返回的字符串始终是序列中的字符串之一。 setratio() #计算两个字符串集的相似率(作为序列传递)。 subtract_edit() #从序列中减去一个编辑子序列。看例子这个比较主要的还是可以将第一个源字符串进行改变,并且是基于第二个字符串的改变,最终目的是改变成和第二个字符串更相似甚至一样
本次使用的是setratio(),在使用的过程中,遇到了一个问题:由于表情搜索返回的唯一标识为md5.这样对测试和线上的结果会形成两个list形如:[‘abc’,’dae’]和[‘cbf’,’efc’]。
print (Levenshtein.seqratio(['abc','dae'],['cbf','efc'])) 0.33 print (Levenshtein.seqratio(['a','b'],['c','d'])) print (Levenshtein.seqratio(['a','b'],['A','B'])) 0
调用后,发现有相似度分值,而实际场景两个list是完全不一样的,这是不符合预期的,但是对比[‘a’,’b’]和[‘c’,’d’]两个list的结果是0。这说明对比算法还是按照单个字符逐个对比,且对大小写敏感。
以上说明,直接比两个md5的list的相似度,肯定是不准确的。需要将两个md5list的每个元素变为单个字符,具体做法如下:
1、由0~9,a~z,A~Z组成62个单字符集(由于测试和线上服务器最多需要对比前30张图,所以最多需要60个单字符)。
2、对线上服务器的前30个结果,分别替换成单字符list:[‘0’,’1’…’s’,’t’]。
3、对测试服务器的前30个结果,逐个查找是否存在于线上服务器列表中。
4、如果存在,则替换成已分配的字符。
5、如果不存在,则从剩余未分配的单字符集中获取。
这样操作后,将原有的md5的list换成了单字符list。再调用seqratio()方法,就没问题了。