用实例解析fuzz.ratio计算方法
转自:http://blog.sina.com.cn/s/blog_6aaea1760102x5d5.html
这两天做智能问答,也就是需要做语料库的匹配查找,用到了fuzz.ratio,但苦于不知道其中的运作原理,不敢确定如何使用。于是百度、谷歌,发现居然没有人对计算过程进行解析。于是只能通过源码查找相关函数进行逐步解析。为便于记忆,直接上实例说明:
1、首先该函数用法是
from fuzzywuzzy import fuzz
str1 = "我是小杜"
str2 = "你是小翟吗"
fuzz.ratio(str1,str2) # 运行结果为:44
2、运行过程如下:
2.1 首先计算长度
>>>len(str1) #12
>>>len(str2) #15
>>>T = len(str1)+len(str2) #27
2.2 然后提取计算匹配
from difflib import SequenceMatcher
m = SequenceMatcher(None, str1, str2)
print m.get_matching_blocks()
### 输出结果:[Match(a=3, b=3, size=6), Match(a=12, b=15, size=0)]
### 提取方法是一个一个做比较,比较出相同的一段,一段,记录每一段的相同开始、结束位置,和相同的长度。然后最后一段是结束符,长度为0
2.3 计算匹配数量
matches = reduce(lambda sum, triple: sum + triple[-1],m.get_matching_blocks(), 0)
print(matches)
### 输出结果: M = 6 所有size求和
2.4 计算匹配比例
>>>2*6/27 ## Ratio = 2*M/T = 0.4444
print m.ratio()
0.444444444444
2.5 转化成结果
>>>round(0.4444*100) ## fuzz.ratio = 44
即运行结果:fuzz.ratio(str1,str2) # 运行结果为:44
3、再来几个例子:
例2
str1 = "cxd1301 is a boy"
str2 = "zxr1304 is a girl"
## sumoflen = 33
## matches = 10
## 所以结果是 2*10/33*100取整为61
例3
str1 = "cxd1301 is a boy"
str2 = "a girl named zxr1304"
## sumoflen = 36
## matches = 4
## 所以结果是 2*4/36*100取整为22
例4
str1 = "cxd1301 is a boy"
str2 = "a boy is cxd1304"
## sumoflen = 32
## matches = 6
## 所以结果是 2*6/32*100取整为38
4、说明
这种匹配方法,对顺序是严格要求的,见例4.
5、源码的查看分析过程,如下:
1、首先该函数用法是
from fuzzywuzzy import fuzz
str1 = "我是小杜"
str2 = "你是小翟吗"
fuzz.ratio(str1,str2) # 运行结果为:44
计算过程怎么实现?
2、想要知道这个fuzz.ratio是什么机制
进入fuzz.py比如直接ctrl+B,查看源码如下:
def ratio(s1, s2):
s1, s2 = utils.make_type_consistent(s1, s2) # 这个是对输入进行校验或者Unicode处理
m = SequenceMatcher(None, s1, s2) # 这个是匹配计算的核心算法了
return utils.intr(100 * m.ratio()) # 输出是乘以100的结果,同时是转了字符串
3、从2知道,核心算法是SequenceMatcher,因此要进一步了解
从载入信息中找到源头:
from difflib import SequenceMatcher
4、在进入difflib中寻找SequenceMatcher这个类
里面的核心是str1,变成str2需要的步骤:替换、删除、插入、相等
# opcodes
# a list of (tag, i1, i2, j1, j2) tuples, where tag is
# one of
# 'replace' a[i1:i2] should be replaced by b[j1:j2]
# 'delete' a[i1:i2] should be deleted
# 'insert' b[j1:j2] should be inserted
# 'equal' a[i1:i2] == b[j1:j2]
5、然后关键的是我们调用的是m.ratio()
def ratio(self):
# 找到计算方法是匹配值输出结果为2.0*M/T,其中T是两个比较字符串的长度之和,而M是匹配的元素个数。取值范围明细是[0,1],完全一样是1,完全不同是0。
"""Return a measure of the sequences' similarity (float in [0,1]).
Where T is the total number of elements in both sequences, and
M is the number of matches, this is 2.0*M / T.
Note that this is 1 if the sequences are identical, and 0 if
they have nothing in common.
.ratio() is expensive to compute if you haven't already computed
.get_matching_blocks() or .get_opcodes(), in which case you may
want to try .quick_ratio() or .real_quick_ratio() first to get an
upper bound.
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0
"""
#计算过程如下:
matches = reduce(lambda sum, triple: sum + triple[-1],
self.get_matching_blocks(), 0)
return _calculate_ratio(matches, len(self.a) + len(self.b))
其中:_calculate_ratio比较简单,就是2.0*M / T.
def _calculate_ratio(matches, length):
if length:
return 2.0 * matches / length
return 1.0
比较麻烦的是:获取matches,用到get_matching_blocks()
def get_matching_blocks(self):
"""Return list of triples describing matching subsequences.
#寻找返回匹配子串,我们所需要的是这些所匹配的size的总和。
Each triple is of the form (i, j, n), and means that
a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in i and in j
The last triple is a dummy, (len(a), len(b), 0), and is the only triple with n==0.
>>> s = SequenceMatcher(None, "abxcd", "abcd")
>>> s.get_matching_blocks()
[Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]
"""
if self.matching_blocks is not None:
return self.matching_blocks
la, lb = len(self.a), len(self.b)
queue = [(0, la, 0, lb)]
matching_blocks = []
while queue:
alo, ahi, blo, bhi = queue.pop()
i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi)
if k: # if k is 0, there was no matching block
matching_blocks.append(x)
if alo < i and blo < j:
queue.append((alo, i, blo, j))
if i+k < ahi and j+k < bhi:
queue.append((i+k, ahi, j+k, bhi))
matching_blocks.sort()
i1 = j1 = k1 = 0
non_adjacent = []
for i2, j2, k2 in matching_blocks:
if i1 + k1 == i2 and j1 + k1 == j2:
k1 += k2
else:
if k1:
non_adjacent.append((i1, j1, k1))
i1, j1, k1 = i2, j2, k2
if k1:
non_adjacent.append((i1, j1, k1))
non_adjacent.append( (la, lb, 0) )
self.matching_blocks = map(Match._make, non_adjacent)
return self.matching_blocks

浙公网安备 33010602011771号