正则表达式需要 5 天才能运行。所以我开发了一个工具,可以在 15 分钟内完成。
开发人员处理文本时,通常需要先进行清理。有时需要替换关键字,例如将“Javascript”替换为“JavaScript”。有时,我们只是想确定文档中是否提到了“JavaScript”。
此类数据清理任务是大多数处理文本的数据科学项目的标准。
数据科学始于数据清理。
我最近有一个非常类似的任务要做。我在Belong.co担任数据科学家,自然语言处理占了我工作的一半。
当我在我们的文档语料库上训练 Word2Vec 模型时,它开始将同义词作为相似的术语。“Javascripting” 就变成了与“JavaScript”相似的术语。
为了解决这个问题,我编写了一个正则表达式 (Regex),将所有已知的同义词替换为标准化名称。该正则表达式将“Javascripting”替换为“JavaScript”,这解决了一个问题,却又产生了另一个问题。
Some people, when confronted with a problem, think
“I know, I’ll use regular expressions.” Now they have two problems.
上面的引言来自这个stack-exchange 问题,它对我来说是真实的。
事实证明,如果要搜索和替换的关键词数量在几百个以内,正则表达式会很快。但我的语料库包含数万个关键词和数百万个文档。
当我对我的正则表达式代码进行基准测试时,我发现一次运行需要5 天时间。我的反应是:
显然我们需要采取一些行动。
[更新]
我开始尝试优化我使用的正则表达式。我发现编译后的正则表达式速度更快,所以我改用了编译后的正则表达式。为了同时替换多个术语,可以使用 group 选项,我调整了它。我仍然无法处理包含特殊字符(例如“C++”、“.Net”等)的关键字。加载
正则表达式时对关键字进行排序也提高了性能。
我最好的学习来自于这个链接。基于 Trie 的正则表达式更快链接当我开始我的项目时我并不知道这一点,但我朝着使用 Trie 的同一方向前进。
我并不是说正则表达式总体上不好,只是理解这么多不同的实现确实有点困难。我之前用的是 Python 版本,而 RUST 有个编译版本,速度更快。另外,还有 C++ 版本,速度更快。
如果你只追求速度,或许可以尝试一下。我需要更强大的控制力和更简单的使用体验,所以我开发了一个工具。它帮助我抽象出细节,确保即使对正则表达式不太了解的人也能轻松上手。
[更新结束]
我在办公室和 Stack Overflow 上询问了一些建议。Vinay、Suresh 和 Stack Overflow 都提到了一种名为Aho -Corasick 算法和Trie字典方法的优秀算法。我查找了一些现有的解决方案,但一无所获。
因此我编写了自己的实现,FlashText就诞生了。
在我们了解 FlashText 是什么以及它如何工作之前,让我们先看看它的性能如何。
与 Regex 相比,FlashText 查找术语所花费的时间。
上图展示了针对单个文档,Regex 编译后与 FlashText 编译后的对比情况。随着关键词数量的增加,Regex 的编译时间几乎呈线性增长。但 FlashText 的编译时间却没有显著变化。
FlashText 将我们的运行时间从 5 天缩短到 15 分钟!!
与 Regex 相比,FlashText 替换术语所花费的时间。
那么什么是 FlashText?
FlashText 是我在GitHub上开源的一个 Python 库,它能够高效地提取关键词,并进行关键词替换。
要使用 FlashText,首先需要传入一个关键字列表。该列表将在内部用于构建 Trie 字典。然后,传入一个字符串,并告知 FlashText 是否执行替换或搜索操作。
它replace
会创建一个替换了关键词的新字符串。search
它会返回在字符串中找到的关键词列表。所有这一切都会在输入字符串的一次传递中完成。
以下是一位满意的用户对该图书馆的评价:
Radim Rehurek 是 Gensim 的创建者。
FlashText 为何这么快?
让我们尝试通过一个例子来理解这部分。假设我们有一个包含 3 个单词的句子I like Python
,以及一个包含 4 个单词的语料库{Python, Java, J2ee, Ruby}
。
如果我们从语料库中取出每个单词,并检查它是否存在于句子中,则需要尝试 4 次。
is 'Python' in sentence?
is 'Java' in sentence?
...
如果语料库中有n
单词,它就会进行n
循环。而且每个搜索步骤is <word> in sentence?
都会花费相应的时间。这有点像正则表达式匹配中的情况。
还有一种方法与第一种方法相反。对于句子中的每个单词,检查它是否存在于语料库中。
is 'I' in corpus?
is 'like' in corpus?
is 'python' in corpus?
如果句子包含m
单词,则需要m
循环。在这种情况下,所需时间仅取决于句子中的单词数量。并且此步骤is <word> in corpus?
可以使用字典查找来加快速度。
FlashText 算法基于第二种方法。它受到 Aho-Corasick 算法和 Trie 数据结构的启发。
它的工作原理是:首先,用语料库创建一个 Trie 字典。它看起来有点像这样
Start
和EOT
(词尾)表示单词边界,例如space
,period
和new_line
。关键字只有两侧都有单词边界时才会匹配。这将阻止在 pineapple 中匹配 apple。
接下来我们将输入一个字符串I like Python
并逐个字符地搜索它。
Step 1: is <start>I<EOT> in dictionary? No
Step 2: is <start>like<EOT> in dictionary? No
Step 3: is <start>Python<EOT> in dictionary? Yes
由于这是逐个字符的匹配,我们可以轻松跳过,<start>like<EOT>
因为<start>l
与l
无关start
。这使得跳过缺失单词的速度非常快。
FlashText 算法只检查了输入字符串“I like Python”中的每个字符。词典中即使包含一百万个关键词,也不会对运行时产生任何影响。这才是 FlashText 算法的真正威力。
您可以通过构建基于 Trie 的 Regex Link来获得类似的速度
那么什么时候应该使用 FlashText?
简单答案:当关键词数量> 500时
复杂答案:Regex 可以根据正则表达式特殊字符搜索术语,例如^,$,*,\d,.
FlashText 不支持所有这些。所有 FlashText 都理解术语的开头和结尾。简而言之,它理解\w,\b
。
因此,如果您想匹配像这样的部分单词,它就不行word\dvec
。但它非常适合提取像这样的完整单词word2vec
。
如何使用 FlashText
查找条款:
# pip install flashtext | |
from flashtext.keyword import KeywordProcessor | |
keyword_processor = KeywordProcessor() | |
keyword_processor.add_keyword('Big Apple', 'New York') | |
keyword_processor.add_keyword('Bay Area') | |
keywords_found = keyword_processor.extract_keywords('I love Big Apple and Bay Area.') | |
keywords_found | |
# ['New York', 'Bay Area'] |
# pip install flashtext | |
from flashtext.keyword import KeywordProcessor | |
keyword_processor = KeywordProcessor() | |
keyword_processor.add_keyword('Big Apple', 'New York') | |
keyword_processor.add_keyword('Bay Area') | |
keywords_found = keyword_processor.extract_keywords('I love Big Apple and Bay Area.') | |
keywords_found | |
# ['New York', 'Bay Area'] |
替换条款:
from flashtext.keyword import KeywordProcessor | |
keyword_processor = KeywordProcessor() | |
keyword_processor.add_keyword('Big Apple', 'New York') | |
keyword_processor.add_keyword('New Delhi', 'NCR region') | |
new_sentence = keyword_processor.replace_keywords('I love Big Apple and new delhi.') | |
new_sentence | |
# 'I love New York and NCR region.' |
from flashtext.keyword import KeywordProcessor | |
keyword_processor = KeywordProcessor() | |
keyword_processor.add_keyword('Big Apple', 'New York') | |
keyword_processor.add_keyword('New Delhi', 'NCR region') | |
new_sentence = keyword_processor.replace_keywords('I love Big Apple and new delhi.') | |
new_sentence | |
# 'I love New York and NCR region.' |
下一步是什么?
如果您认识从事实体识别、NLP、Word2vec、Keras 和 Tensorflow 工作的人,请与他们分享此博客。这个库对我们来说非常有用。我相信它对其他人也会有所帮助。
最初发布于此处
:wq
文章来源:https://dev.to/vi3k6i5/regex-was-take-5-days-to-run-so-i-built-a-tool-that-did-it-in-15-minutes-c98