正则表达式需要 5 天才能运行。所以我开发了一个工具,可以在 15 分钟内完成。

2025-06-07

正则表达式需要 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.
Enter fullscreen mode Exit fullscreen mode

上面的引言来自这个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 编译后的对比情况。随着关键词数量的增加,Regex 的编译时间几乎呈线性增长。但 FlashText 的编译时间却没有显著变化。

FlashText 将我们的运行时间从 5 天缩短到 15 分钟!!

我们现在很好:)

与 Regex 相比,FlashText 替换术语所花费的时间。
与 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?
...
Enter fullscreen mode Exit fullscreen mode

如果语料库中有n单词,它就会进行n循环。而且每个搜索步骤is <word> in sentence?都会花费相应的时间。这有点像正则表达式匹配中的情况。

还有一种方法与第一种方法相反。对于句子中的每个单词,检查它是否存在于语料库中。

is 'I' in corpus?
is 'like' in corpus?
is 'python' in corpus?
Enter fullscreen mode Exit fullscreen mode

如果句子包含m单词,则需要m循环。在这种情况下,所需时间仅取决于句子中的单词数量。并且此步骤is <word> in corpus?可以使用字典查找来加快速度。

FlashText 算法基于第二种方法。它受到 Aho-Corasick 算法和 Trie 数据结构的启发。

它的工作原理是:首先,用语料库创建一个 Trie 字典。它看起来有点像这样

语料库的 Trie 词典。

StartEOT(词尾)表示单词边界,例如space,periodnew_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
Enter fullscreen mode Exit fullscreen mode

<Start> Python <EOT> 存在于字典中。

由于这是逐个字符的匹配,我们可以轻松跳过,<start>like<EOT>因为<start>ll无关start。这使得跳过缺失单词的速度非常快。

FlashText 算法只检查了输入字符串“I like Python”中的每个字符。词典中即使包含一百万个关键词,也不会对运行时产生任何影响。这才是 FlashText 算法的真正威力。

您可以通过构建基于 Trie 的 Regex Link来获得类似的速度

那么什么时候应该使用 FlashText?

简单答案:当关键词数量> 500时

当关键字数量 > 500 时,FlashText 的查找性能优于正则表达式

复杂答案: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
PREV
🎭 一个基于 React Hooks + Express 的全栈表情包生成器 🪐
NEXT
将连接到 MongoDb 的 Node.js 应用程序 Docker 化