最近尝试将 winnowing 算法应用于抄袭检测中,这是一种字符串指纹算法,详细的内容见下面这篇论文。
Winnowing Local Algorithms for Document Fingerprinting.pdf
k-grams 是最常见的指纹算法了,它将一个大字符串分为很多小字符串,而每个小字符串仅仅差一个字符,是一种错位的排列。比如 adorunrunrunadorunrun
,进行 5-grams 之后就是
adoru dorun orunr runru
unrun nrunr runru unrun
nruna runad unado nador
adoru dorun orunr runru
unrun
k 就是每个小字符串的长度。
然后对这个小字符串组计算 hash,并抽样得到大字符串的特征值。抽样的算法有很多,不同的算法可能会影响最后的精确度,比如 hash 结果都是数字,就取出 hash % p = 0的,但是缺点就是特征中可能遗漏大段的内容,造成结果不准确。其余的算法也是有自己的缺点,比如取最小值。
winnowing 就是要结果对 hash 取样的时候遇到的问题,它将 hash 类似 k-grams 一样进行分组,比如 hash 值是
77 72 42 17 98 50 17 98 8 88 67 39 77 72 42 17 98
假设我们希望任意t个字符之内(不包含t)的重复都能被查出来,那么我们就设置一个窗口 w = t - k + 1。举个例子,比如在上面 5-grams 的例子中我们希望 t = 8 ,那么我们就设置 w = 8 - 5 + 1 = 4,于是,得到了以下14个hash值的窗口:
(77, 74, 42, 17) (74, 42, 17, 98) (42, 17, 98, 50)
(17, 98, 50, 17) (98, 50, 17, 98) (50, 17, 98, 8)
(17, 98, 8, 88) (98, 8, 88, 67) ( 8, 88, 67, 39)
(88, 67, 39, 77) (67, 39, 77, 74) (39, 77, 74, 42)
(77, 74, 42, 17) (74, 42, 17, 98)
我们选出每个窗口中的最小值,因为相连几个窗口的最小值可能指的是文档中同一个 hash,这个时候我们只需要在这个 hash 第一次出现的那个窗口选择它就可以了,其它的窗口就不再需要选择最小值了。比如在这个例子中,前三个窗口的最小值都是17,而且指向的是同一个 hash ,所以我们只需要选择第一个就可以了,另外两个窗口都不需要选择,直到第四个窗口又出现了另外一个17。通过这个算法我们就可以选出以下5个 hash 值:
17 17 8 39 17
这个就是这整个文档的fingerprint。如果需要字符串出现的位置不影响结果的话,将 hash 值去重就好了。
下面是我使用 Python 简单的实现
# coding=utf-8
import hashlib
class Winnowing(object):
def __init__(self, text, k=5, w=10):
"""
:param text:
:param k: 字符串分组长度
:param w: 窗口长度
:return:
"""
self.text = text
self.k = k
self.w = w
def _kgrams(self, l, size):
"""
将 l 分组,每组大小是size,每组错位一个元素
:param l:
:param size: 分组大小
:return:
"""
n = len(l)
if n < size:
yield l
else:
for i in xrange(n - size + 1):
yield l[i:i+size]
def _hash(self, string):
return int(hashlib.md5(string).hexdigest()[:8], 16)
def fingerprint(self):
cur = 0
result = []
for item in self._kgrams(map(lambda x: self._hash(x), self._kgrams(self.text, self.k)), self.w):
# 对于hash分组,每组都是找到最右边的最小值
min_num = item[0]
for i in range(len(item)):
if item[i] <= min_num:
min_num = item[i]
result.append(min_num)
cur += 1
return set(result)
print Winnowing("abcdefghijklmnopqrstuvwxyz").fingerprint()
参考 http://ytliu.info/blog/2013/12/16/fingerprintsuan-fa-cha-chao-xi/