winnowing 算法 -- 提取文档指纹特征

关于 winnowing 算法的简单的解释就是:基于 k-gram 的哈希码,以最小规则提取部分gram 的哈希码作为文档特征指纹,并记录gram的位置。源码和步骤如下:

(1)对文档进行字符标记,比如 "asdf",变成 [(0,"a"),(1,"s"),(2,"d"),(3,"f")]

def sanitize(text):
    """Removes irrelevant features such as spaces and commas.
    :param text: A string of (index, character) tuples.
    """

    import re

    # NOTE: \p{L} or \p{Letter}: any kind of letter from any language.
    # http://www.regular-expressions.info/unicode.html
    p = re.compile(r'\w', re.UNICODE)

    def f(c):
        return p.match(c[1]) is not None

    return filter(f, map(lambda x: (x[0], x[1].lower()), text))

text = u"你是谁啊?" # 原始文档内容
n = len(list(text)) # 得到文档长度
text = zip(range(n), text) # 得到[(0,"你"),(1,"是"),(2,"谁"),(3,"啊"),(4,"?")]
text = sanitize(text) # 对文档进行预处理

(2)对文档根据所选择的 k 值进行k-gram 切分,比如当 k = 2 时,"asd" 且分为 [(0,"as"),(1,"sd")], 然后对每一个gram 进行哈希运算,将得到的哈希码后4位作为 16 进制整数返回。代码如下:

# kgram 切分函数
def kgrams(text, k=5):
    """Derives k-grams from text."""

    text = list(text)
    n = len(text)

    if n < k:
        yield text
    else:
        for i in range(n - k + 1):
            yield text[i:i+k]

# 返回哈希计算值和 gram 位置
def winnowing_hash(kgram):
    """
    :param kgram: e.g., [(0, 'a'), (2, 'd'), (3, 'o'), (5, 'r'), (6, 'u')]
    """
    kgram = zip(*kgram)
    kgram = list(kgram)

    # FIXME: What should we do when kgram is shorter than k?
    text = ''.join(kgram[1]) if len(kgram) > 1 else ''

    hs = hash_function(text)

    # FIXME: What should we do when kgram is shorter than k?
    return (kgram[0][0] if len(kgram) > 1 else -1, hs)

# 哈希计算默认用 sha1 方法
def default_hash(text):
    import hashlib

    hs = hashlib.sha1(text.encode('utf-8'))
    hs = hs.hexdigest()[-4:]
    hs = int(hs, 16)

    return hs

''' 这里的 text 变量接上一处理结果,后面的也类似,相同的变量是上一步的处理结果'''
# 得到 k=3 的 gram 片段的哈希值
hashes =  map(lambda x: winnowing_hash(x), kgrams(text, k=3))

(3) 此时我们得到了文档的 gram 的哈希值,并记录了gram 的位置。比如[(0,123),(1,234),(2,234),(3,435),(4,456)] 文档的 5 个gram 片段都有一个哈希值并记录了它们的位置。然后我们再以 4 为gram,取哈希值最小的那个片段作为文档指纹。比如这里就是[(0,123),(1,234),(2,234),(3,435)] 和 [(1,234),(2,234),(3,435),(4,456)] ,取哈希最小分别就是 (0,123) 和 (1,234)。 于是文档的指纹就是 set([(0,123),(1,234)])  代码如下:

def select_min(window):
    """In each window select the minimum hash value. If there is more than one
    hash with the minimum value, select the rightmost occurrence. Now save all
    selected hashes as the fingerprints of the document.
    :param window: A list of (index, hash) tuples.
    """

    #print window, min(window, key=lambda x: x[1])

    return min(window, key=lambda x: x[1])

'''再对 gram 哈希值进行 gram 切分,4 此时是固定的,不可调 '''  
windows = kgrams(hashes, 4)
# 选择最小的哈希值的 gram 作为文档指纹
return set(map(select_min, windows))

以上就是 winnowing 算法的代码解析,在使用时我们只要这样:

pip install winnowing

然后这样使用即可

from winnowing import winnow
text = u"你是谁啊?"

# 设定 k 值并获取文档指纹
print winnow(text,k= 3) k default 5

''' 也可以不使用默认的哈希算法,使用自定义方法 '''
def hash_md5(text):
    import hashlib

    hs = hashlib.md5(text)
    hs = hs.hexdigest()
    hs = int(hs, 16)

    return hs

winnow.hash_function = hash_md5

 

你可能感兴趣的:(NLP)