ac自动机 匹配最长前缀_学习NLP的第5天——AC自动机

主要通过《自然语言处理入门》(何晗)的第2章来学习AC自动机。这里主要记录我在学习过程中整理的知识、调试的代码和心得理解,以供其他学习的朋友参考。

AC自动机是用来解决如下问题:

仅通过对文本的一次扫描,就查询出文本内包含的所有出现在词典中的词。

其目的是简化全切分扫描过程的复杂度。


下面我尽可能用通俗的语言来表达。

在全切分(查询出文本内包含的所有出现在词典中的词)长度为n的文本时,需要遍历文本中所有的连续序列。

例如,对于文本“我是文本”,就需要判断以下10个连续序列是否存在于词典中:

  我、我是、我是文、我是文本、是、是文、是文本、文、文本、本

但是,这样需要在字典树中查询10次。那么,一次扫描就查询出所有在字典中出现过的词呢?这就引入了多模式匹配的问题。

给定多个词语(也称模式串),从母文本中匹配它们的问题称为多模式匹配。

AC自动机就是解决多模式匹配的方法。

AC自动机

在使用字典树扫描“我是本文”时候,在以“我”为起点扫描了“我”、“我是”、“我是文”、“我是文本”之后,又得退回到“是”,继续扫描“是”、“是文”、“是文本”……

如果能够在扫描“我是文本”的过程中,想办法知道“是文本”、“文本”、“本”在不在字典树中,就可以省略到这3次查询。这3个字符串的拥有共享递进式的后缀,首尾对调后(“本”、“本文”、“本文是”)恰好可以用另一颗前缀树索引,称它为后缀树

而AC自动机就是在前缀树的基础上,为前缀树上的每个节点建立一棵后缀树,节省了大量查询。

简单来说,就是我们希望实现:在扫描的过程中,无论扫描失败与否,均会在自动机中继续转移,直至将文本扫描完成。

AC自动机具体包括goto表、fail表和output表。

goto表

gotu表其实就是一棵前缀树,用来将每个模式串索引到前缀树上。我们也使用《自然语言处理入门》中的ushers作为母文本,模式串集合为{he,she,his,hers}

09dc1bbb36050da728186baba7315b94.png

图片来自《自然语言处理入门》

值得注意的是,goto表与字典树的区别是,goto表的根节点不光可以按h和s转移,还接受任意其他字符,转移重点都是自己。这样就形成了一个圈,使得一棵树变为一幅有向有环图。

这个圈的目的在于,扫描时若遇到非h且非s字符时,状态机一直保持初始状态。这就使扫描到其他字符时一直维持在初始状态,不会失败。

有向有环图:“图论”相关知识,有回路的有向图。

AC算法是基于自动机的算法,为了区别于树结构,接下来我们按照书中的方法用“状态”来称呼节点。

output表

给定一个状态(节点),我们需要知道该状态是否对应某个或某些模式串,以决定是否输出模式串(是否存在于词典中)以及对应的值,这时我们用到的关联结构就是output表。

在ushers案例中,output表中的状态就是图中的深蓝色节点,对应的output如下:

2→he5→he,she7→his9→hers

虽然称作表,但实际上output表可以看做是状态对象的一个成员变量。如下图所示:

b0f3a213803e085e87277040e26345c9.png

图片来自《自然语言处理入门》

由图中可见,output表中的元素有两种,一种是从初始状态到当前状态的路径本身对应的模式串(例如2号),另一种是路径的后缀所对应的模式串(例如5号中的he)。

于是,output表的构造也分为两步,第一步是记录完整路径对应的模式串,第二步则是找出所有路径后缀及其模式串。其中第二步可以与fail表的构造同步进行。

之所以需要output表,是因为在AC自动机中,可能会通过fail表转移状态;因此在扫描过程中,我们也无法按从初始点的路径获取模式串,因此需将状态对应的模式串存为状态的成员变量。

fail表

在上图的表格中,我们仍然无法实现从除初始状态以外的状态,继续稳定转移状态。由此,我们引入fail表的概念。

fail表保存的是状态间一对一的关系,存储状态转移失败后应当回退的最佳状态。

最佳状态指的是能记住已匹配上的字符串的存在于goto表中的最长后缀的那个状态。

例如,匹配she后来到状态5,再来一个字符,goto失败;此时最长后缀为he,对应路径0-1-2,因此状态2为状态5 fail的最佳选择。fail到状态2之后,自动机记住了he,做好了接受r的准备。

又如,匹配his后来到状态7,再来一个字符,goto失败;此时最长后缀为is,但没有对应路径,次长后缀为s,对应路径0-3,因此状态7应当fail到状态3。

fail表的构建流程

定义S为当前状态;S.goto(C)为转移表,返回S按字符c转移的状态,null表示转移失败;S.fail为fail表,代表转移失败时候从状态S回退的状态。

  1. 初始状态的goto表是满的,永远不会失败,因此没有fail指针。与初始状态直接相连的所有状态,其fail指针都指向初始状态。
  2. 从初始状态开始进行广度优先遍历(BFS),若当前状态S接受字符c直达的状态为T,则沿着S的fail指针回溯,直到找到第一个前驱状态F,使得F.goto(c)!=null。将T的fail指针设为F.goto(c)。简单来说,就是寻找状态S的存在于goto表中的最长后缀的状态F。
  3. (更新Output表)由于F路径是T路径的后缀,因而T的output也应包含F的output,因此将F的output添加到T的output中。

加上完整的fail表后,自动机下图所示:

0924d87abf76a84aa86dfea526d6c1ad.png

图片来自《自然语言处理入门》

基于HanLP的Python实现

from pyhanlp import JClassdef classic_demo():    words = ["hers", "his", "she", "he"]    Trie = JClass('com.hankcs.hanlp.algorithm.ahocorasick.trie.Trie')  # 利用JClass取得HanLP中的AC自动机    trie = Trie()    for w in words:        trie.addKeyword(w)  # 添加模式串    for emit in trie.parseText("ushers"):  # 全切分文本        print("[%d:%d]=%s" % (emit.getStart(), emit.getEnd(), emit.getKeyword()))if __name__ == '__main__':    classic_demo()

运行结果

[2:3]=he[1:3]=she[2:5]=hers

学习使用教材:《自然语言处理入门》(何晗):2.6

本文中代码大部分引自该书中的代码,个人非常推荐这本书,确实是非常好的教材。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值