Flashtext:字符搜索和替换

1,117 阅读5分钟

什么是flashtext算法

Flashtext算法是一个高效的字符搜索和替换算法。
该算法的时间复杂度不依赖于搜索或替换的字符的数量。
比如,对于一个文档有 N 个字符,和一个有 M 个词的关键词库,那么时间复杂度就是O(N),与M没有关系。
这个算法比正则匹配法快很多,因为正则匹配的时间复杂度是 O(M * N)。

Flashtext算法被设计为只匹配完整的单词,而不匹配单词内的子字符串。
这个算法也被设计为首先匹配最长字符串。

比如,我们输入一个单词Apple,那么flashtext算法就不会去匹配“I like Pineapple”中的apple。
再举个例子,比如我们有这样一个数据集 {Machine, Learning,Machine Learning},
一个文档“I like Machine Learning”,那么我们的算法只会去匹配 “Machine Learning”,因为这是最长匹配。

1/介绍

在信息检索领域,字符搜索和替代都是很常见的问题。
我们经常想要在一个特定的文本中搜索特定的关键词,或者在文本中替代一个特定的关键词。
1.例如:
 <1>字符搜索的应用场景
    假设我们有一份软件工程师的简历,
    我们拥有一个20k的编程技巧词库:
          corpus = {Java, python, javascript, machien learning, …}。
    我们想知道词库corpus中的哪些词出现在了简历中
     
 <2>字符替换的应用场景
    当我们有一个同义词的语料库(不同的拼写表示的是同一个单词),
    比如 corpus = {javascript: [‘javascript’,‘javascripting’,‘java script’], …} ,
    在简历中我们需要使用标准化的词,所有我们需要用一个标准化的词来替换不同简历中的同义词。

2.为了去解决上述这些问题,正则表达式是最常用的一个技术。
  虽然正则表达式可以很好的解决这个问题,但是当我们的数据量增大时,这个速度就会非常慢了。
  如果我们的文档达到百万级别时,那么这个运行时间将达到几天的量级。

3.随着我们需要处理的字符越来越多,正则表达式的处理速度几乎都是线性增加的。
  然而,Flashtext几乎是一个常量。
  

2/Flashtext

Flashtext是一种基于 Trie 字典数据结构和 Aho Corasick 的算法。
它的工作方式是,首先它将所有相关的关键字作为输入。使用这些关键字建立一个 trie 字典,如下图所示:

start和eot是两个特殊的字符,用来定义词的边界,这和我们上面提到的正则表达式是一样的。
这个 trie 字典就是我们后面要用来搜索和替换的数据结构。

<1>利用Flashtext进行搜索

  对于输入字符串(文档),我们对字符进行逐个遍历。
  当我们在文档中的字符序列 <\b>word<\b> 匹配到字典中的word时(start和eot分别是字符序列的开始标签和结束标签),
  我们认为这是一个完整匹配了。
  我们将匹配到的字符序列所对应的标准关键字进行输出,具体如下:

<2>利用Flashtext进行替换

  对于输入字符串(文档),我们对字符串进行逐个遍历。
  我们先创建一个空的字符串,当我们字符序列中的 <\b>word<\b> 无法在Trie字典中找到匹配时,
  那么我们就简单的原始字符复制到返回字符串中。
  但是,当我们可以从Trie字典中找到匹配时,那么我们将将匹配到的字符的标准字符复制到返回字符串中。
  因此,返回字符串是输入字符串的一个副本,唯一的不同是替换了匹配到的字符序列,
  具体如下:

如何使用flashtext

<1>提取关键字

    from flashtext import KeywordProcessor
    
    keyword_processor = KeywordProcessor()
    # keyword_processor.add_keyword(<unclean name>, <standardised name>)  
    # 第一个词是模糊不清的,第二个是标准的
    
    keyword_processor.add_keyword('Big Apple', 'New York')# 这是我们自己的预料库,越丰富越好
    keyword_processor.add_keyword('Bay Area')
    
    str = 'I love Big Apple and Bay Area.'
    keywords_found = keyword_processor.extract_keywords(str)
    print( keywords_found )
    # ['New York', 'Bay Area']

<2>关键字替换

    keyword_processor.add_keyword('New Delhi', 'NCR region')
    str = 'I love Big Apple and new delhi.' # 没有区分大小写字母
    new_str = keyword_processor.replace_keywords(str)
    print( new_str )
    # 'I love New York and NCR region.'

<3>区分大小写字母

   from flashtext import KeywordProcessor
   
   keyword_processor = KeywordProcessor(case_sensitive=True)  # 区分大小写字母
   keyword_processor.add_keyword('Big Apple', 'New York')
   keyword_processor.add_keyword('Bay Area')
   
   str = 'I love big Apple and Bay Area.'
   keywords_found = keyword_processor.extract_keywords(str)
   print( keywords_found )
   # ['Bay Area']

<4>关键字不清晰

    from flashtext import KeywordProcessor
    
    keyword_processor = KeywordProcessor()
    
    keyword_processor.add_keyword('Big Apple')
    keyword_processor.add_keyword('Bay Area')
    
    str = 'I love big Apple and Bay Area.'
    keywords_found = keyword_processor.extract_keywords(str)
    
    print( keywords_found )
    # ['Big Apple', 'Bay Area']

<5>同时添加多个关键词

    from flashtext import KeywordProcessor
    keyword_processor = KeywordProcessor()
    keyword_dict = {
         "java": ["java_2e", "java programing"],
         "product management": ["PM", "product manager"]
    }
    # {'clean_name': ['list of unclean names']}
    keyword_processor.add_keywords_from_dict(keyword_dict)
    # Or add keywords from a list:
    keyword_processor.add_keywords_from_list(["java", "python"])
    keyword_processor.extract_keywords('I am a product manager for a java_2e platform')
    # output ['product management', 'java']

<6>删除关键字

    from flashtext import KeywordProcessor
    
    keyword_processor = KeywordProcessor()
    
    keyword_dict = {
         "java": ["java_2e", "java programing"],
         "product management": ["PM", "product manager"]
    }
    keyword_processor.add_keywords_from_dict(keyword_dict)
    print(keyword_processor.extract_keywords('I am a product manager for a java_2e platform'))
    # output ['product management', 'java']
    keyword_processor.remove_keyword('java_2e')
    # you can also remove keywords from a list/ dictionary
    keyword_processor.remove_keywords_from_dict({"product management": ["PM"]})
    keyword_processor.remove_keywords_from_list(["java programing"])
    keyword_processor.extract_keywords('I am a product manager for a java_2e platform')
    # output ['product management']      

<7>有时候我们会将一些特殊符号作为字符边界,比如 空格,\ 等等。

  为了重新设定字边界,我们需要添加一些符号告诉算法,这是单词字符的一部分。
    from flashtext import KeywordProcessor
    
    keyword_processor = KeywordProcessor()
    
    keyword_processor.add_keyword('Big Apple')
    print(keyword_processor.extract_keywords('I love Big Apple/Bay Area.'))
    # ['Big Apple']
    keyword_processor.add_non_word_boundary('/')
    print(keyword_processor.extract_keywords('I love Big Apple/Bay Area.'))
    # []