返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >浅谈分词器Tokenizer
  • 910
分享到

浅谈分词器Tokenizer

2024-04-02 19:04:59 910人浏览 泡泡鱼
摘要

目录一、概述二、AC自动机(Aho-Corasick automaton)2.1、字典树(trie树)2.2、失败指针三、最终的分词结果一、概述 分词器的作用是将一串字符串改为“词”

一、概述

分词器的作用是将一串字符串改为“词”的列表,下面以“大学生活”这个输入为例进行讲解:

对“大学生活”这句话做分词,通常来说,一个分词器会分三步来实现:

(1)找到“大学生活”这句话中的全部词做为一个集合,即:[大、大学、大学生、学、学生、生、生活、活]

(2)在第一步中得到的集合中找到所有能组合成“大学生活”这句话的子集,即:

[大、学、生、活]

[大、学、生活]

[大、学生、活]

[大学、生、活]

[大学、生活]

[大学生、活]

(3)在第二步中产生的所有子集中挑选一个最有可能的作为最终的分词结果。

为了得到第1步需要的集合,通常我们需要一个词典。大部分的分词器都是基于词典去做分词的(也就是说也可以不基于词典来做分词,在此暂时不做讨论)。那么现在假设我们有一个小词典:[大学、大学生、学习、学习机、学生、生气、生活、活着]。首先要在“大学生活”这句话里面匹配到这个词典里面的全部词,有些同学脑中可能会出现这种过程:


public class Demo1{
    //加载词典中的所有词汇
    static Set<String> dic  = new HashSet(){{
        add("大学");
        add("大学生");
        add("学习");
        add("学习机");
        add("学生");
        add("生气");
        add("生活");
        add("活着");
    }};
    //匹配句子中词典中存在的所有词汇
    static List<String> getAllWordsMatched(String sentence){
        List<String> wordList = new ArrayList<>();
        for(int index = 0;index < sentence.length();index++){
            for(int offset = index+1; offset <= sentence.length();offset++){
                String sub = sentence.substring(index,offset);
                if(dic.contains(sub)){
                    wordList.add(sub);
                }
            }
        }
        return wordList;
    }
    public static void main(String[] args){
        String sentence = "大学生活";
        getAllWordsMatched(sentence).forEach(System.out::println);
    }
}

执行这段代码会输出:

大学

大学生

学生

生活

似乎到这里,我们已经完美地完成了在词典中找到词的任务。然而真实的分词器的词典往往有几十万甚至几百万的词汇量,使用上面这种算法性能太低了。高效地实现这种匹配的算法有很多,下面简单介绍一种:

二、AC自动机(Aho-Corasick automaton)

AC自动机是一种常用的多模式匹配算法,基于字典树(trie树)的数据结构和KMP算法的失败指针的思想来实现,有不错的性能并且实现起来非常简单。

2.1、字典树(trie树)

引用一下百度百科对于trie树的描述:Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

下面一个存放了[大学、大学生、学习、学习机、学生、生气、生活、活着]这个词典的trie树:

它可以看作是用每个词第n个字做第n到第n+1层节点间路径哈希值的哈希树,每个节点是实际要存放的词。

现在用这个树来进行“大学生活”的匹配。依然从“大”字开始匹配,如下图所示:从根节点开始,沿最左边的路径匹配到了大字,沿着“大”节点可以匹配到“大学”,继续匹配则可以匹配到“大学生”,之后字典中再没有以“大”字开头的词,至此已经匹配到了[大学、大学生]第一轮匹配结束。

继续匹配“学”字开头的词,方法同上步,可匹配出[学生]

继续匹配“生”和“活”字开头的词,这样“大学生活”在词典中的词全部被查出来。

可以看到,以匹配“大”字开头的词为例,第一种匹配方式需要在词典中查询是否包含“大”、“大学”、“大学”、“大学生活”,共4次查询,而使用trie树查询时当找到“大学生”这个词之后就停止了该轮匹配,减少了匹配的次数,当要匹配的句子越长,这种性能优势就越明显。

2.2、失败指针

再来看一下上面的匹配过程,在匹配“大学生”这个词之后,由于词典中不存在其它以“大”字开头的词,本轮结束,将继续匹配以“学”字开头的词,这时,需要再回到根节点继续匹配,如果这个时候“大学生”节点有个指针可以直指向“学生”节点,就可以减少一次查询,类似地,当匹配完“学生”之后如果“学生”节点有个指针可以指向“生活”节点,就又可以减少一次查询。这种当下一层节点无法匹配需要进行跳转的指针就是失败指针,创建好失败指针的树看起来如下图:

图上红色的线就是失败指针,指向的是当下层节点无法匹配时应该跳转到哪个节点继续进行匹配。

失败指针的创建过程通常为:

1.创建好trie树。

2.BFS每一个节点(不能使用DFS,因为每一层节点的失败指针在创建时要确保上一层节点的失败指针全部创建完成)。

3.根节点的子节点的失败指针指向根节点。

4.其它节点查找其父节点的失败指针指向的节点的子节点是否有和该节点字相同的节点,如果有则失败指针指向该节点,如果没有则重复刚才的过程直至找到字相同的节点或根节点。

查询过程如下:

执行这段代码会输出:

大学

大学生

学生

生活

在匹配到了词典中所有出现在句子中的词之后,继续第二步:在得到的集合中找到所有能组合成“大学生活”这个句子的子集。但是在这个地方遇到了一个小问题,上面查到的4个词中仅有“大学”和“生活”这两个词可以组成“大学生活”这个句子,而“大学生”和“生活”则无法在匹配到的词中找到能够与其连接的词汇。现实情况中,词典很难囊括所有词汇,所以这种情况时有发生。在这里,可以额外将单个字放到匹配到的词的集合中,这得到了一个新集合:

[大学、大学生、学生、生活]U[大、学、生、活] = [大学、大学生、学生、生活、大、学、生、活]

可以用一个有向图来表示这个集合的分词组合,从开始节点到结束节点的全部路径就是所有分词方式。

三、最终的分词结果

那么在这些可能的分词组合中,应该选取哪一种作为最终的分词结果呢?大部分分词器的主要差异也体现在这里,有些分词器可能有很多不同的分词策略供使用者选择。例如最少词策略,就是在有向图中选择能够达到结束节点的全部路径中最短(经过最少节点)的一条。对于上面这张有向图,最短路径有两条,分别是“大学,生活”与“大学生,活”最终的分词结束就在这两条路径中选择一条。这种选择方法最为简单,性能也很高,但是准确性较差。其实仔细考虑一下不难发现,无论使用哪种分词策略,其目的都是想要挑选出一条最可能正确的,也就是概率最大的一种。“大学生活”分词为[大、学、活]的概率为P(大)P(学|大)P(生|大,学)P(活|大,学,生),这就是说,想要计算其的概率,需要知道“大”的出现概率,“大”出现时“学”出现的概率,“大”、“学”同时出现时“生”的概率,“大”,“学”,“生”同时出现时出现“活”的概率。这些出现概率可以在一份由大量文章组成的文本库中统计得出,但是问题是,如果词典要记录任意N个词出现时出现词W的概率,一个存放M个词汇的词典需要存放M^N量级的关系数据,这个词典会太大,所以通常会限制N的大小,一般来说,N为2或者为3,计算条件概率时只考虑到它前面2到3个词,这是基于马尔可夫链做的简化。当N为2时称为二元模型,N为3时称为三元模型。一个有50万词的词典的二元模型需要50万*50万条关系,这也是相当大的一个量级,可以对其进行压缩或转化为其它近似形式,这部分相对比较复杂,在此不作讲解,这里使用更简单一些的形式,假设每个词的出现都是独立事件,令P(大,学,生,活)=P(大)P(学)P(生)P(活)。要计算这个概率,只需要知道每个词的出现概率,一个词的出现概率=词出现的次数/文本库中词总量。那么将之前使用的词典更新为[大学5、大学生4、学习6、学习机3、学生5、生气8、生活7、活着2] 后面的数字是这些词在文本库中出现的次数,文本库中词的问题就是这些词出现次数之和=5+4+6+3+5+8+7+2=40

那么P(大学,生活)=P(大学)P(生活)=5/40*7/40

P(大学生、活)=P(大学生)P(活)=4/40*0/40 在这个地方出现了问题,对于词典里不存在的词,它的概率是0,这将会导致整个乘积是0,这是不合理的,对于这种情况可以做平滑处理,简单地来说,可以设词典中不存在的词的出现次数为1,于是P(大学生、活)=P(大学生)P(活)=4/40*1/40

最终可以挑选出一条最有可能的分词组合。至此第三步结束。

以上就是浅谈分词器Tokenizer的详细内容,更多关于分词器Tokenizer的资料请关注编程网其它相关文章!

--结束END--

本文标题: 浅谈分词器Tokenizer

本文链接: https://lsjlt.com/news/129199.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 浅谈分词器Tokenizer
    目录一、概述二、AC自动机(Aho-Corasick automaton)2.1、字典树(trie树)2.2、失败指针三、最终的分词结果一、概述 分词器的作用是将一串字符串改为“词”...
    99+
    2024-04-02
  • 基于DF的Tokenizer分词是怎么样的
    这篇文章给大家介绍基于DF的Tokenizer分词是怎么样的,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Tokenizer分词进行文本分析前,对文本中句子进行分词我们处理的第一步。大家都是Spark的机器学习库分为基...
    99+
    2023-06-19
  • 浅谈python jieba分词模块的基本用法
    jieba(结巴)是一个强大的分词库,完美支持中文分词,本文对其基本用法做一个简要总结。 特点 支持三种分词模式: 精确模式,试图将句子最精确地切开,适合文本分析; 全模式,把句...
    99+
    2022-06-04
    分词 浅谈 模块
  • 浅谈谈Android 图片选择器
    ImageSelector 简介 Android自定义相册,实现了拍照、图片选择(单选/多选)、ImageLoader无绑定 任由开发者选择 https://github.c...
    99+
    2022-06-06
    选择器 选择 图片 Android
  • 浅谈python迭代器
    1、yield,将函数变为 generator (生成器) 例如:斐波那契数列 def fib(num): a, b, c = 1, 0, 1     while a <= num: ...
    99+
    2022-06-04
    浅谈 迭代 python
  • 浅谈C#索引器
    目录一、概要二、应用场景一、概要 索引器使你可从语法上方便地创建类、结构或接口,以便客户端应用程序可以像访问数组一样访问它们。编译器将生成一个 Item 属性(或者如果存在 Inde...
    99+
    2024-04-02
  • 浅谈MySQL索引优化分析
    为什么你写的sql查询慢?为什么你建的索引常失效?通过本章内容,你将学会MySQL性能下降的原因,索引的简介,索引创建的原则,explain命令的使用,以及explain输出字段的意义。助你了解索引,分析索...
    99+
    2024-04-02
  • 浅谈Python]程序的分支结构
    单分支结构:if 语句 Python 中 if 语句的语法格式如下: if <条件>:          <语句块...
    99+
    2023-05-15
    Python分支 Python分支结构
  • 浅谈Node.js中的定时器
    Node.js中定时器的实现 上一篇博文提到,在Node中timer并不是通过新开线程来实现的,而是直接在event loop中完成。下面通过几个JavaScript的定时器示例以及Node相关源码来分析在...
    99+
    2022-06-04
    定时器 浅谈 Node
  • 浅谈Java解释器模式
    ​ **请注意!请注意!!!**今天讲给大家讲解非常“有用”的设计模式,解释器模式!!! ​ 设计模式有三大种类,一种是创建型模式,一种是结构型模式,最后一种...
    99+
    2024-04-02
  • 浅谈MySQL中分区表基本类型
    小编这次要给大家分享的是浅谈MySQL中分区表基本类型,文章内容丰富,感兴趣的小伙伴可以来了解一下,希望大家阅读完这篇文章之后能够有所收获。MySQL分区表概述随着MySQL越来越流行,Mysql里面的保存...
    99+
    2024-04-02
  • 浅谈MySQL分页Limit的性能问题
    MySQL的分页查询通常通过limit来实现。limit接收1或2个整数型参数,如果是2个参数,第一个是指定第一个返回记录行的偏移量,第二个是返回记录行的最大数目。初始记录行的偏移量是0。为了与Postgr...
    99+
    2024-04-02
  • 浅谈PostgreSQL表分区的三种方式
    目录一、简介二、三种方式2.1、Range范围分区2.2、List列表分区2.3、Hash哈希分区三、总结一、简介 表分区是解决一些因单表过大引用的性能问题的方式,比如某张表过大就会...
    99+
    2024-04-02
  • 浅谈java分页三个类 PageBean ResponseUtil StringUtil
    如下所示:package ssmy.page;public class PageBean {private int page;//第几页private int pageSize;//每页显示的记录数private int start ;//...
    99+
    2023-05-31
    java 分页类 bea
  • 浅谈redis采用不同内存分配器tcmalloc和jemalloc
    我们知道Redis并没有自己实现内存池,没有在标准的系统内存分配器上再加上自己的东西。所以系统内存分配器的性能及碎片率会对Redis造成一些性能上的影响。 在Redis的 zmalloc.c 源码中,我们可...
    99+
    2022-06-04
    分配器 浅谈 内存
  • 浅谈C++空间配置器allocator
    目录概述1. Allocator 的标准接口2. SGI STL 内存分配失败的异常处理3. SGI STL 内置轻量级内存池的实现4. SGI STL 内存池在多线程下的互斥访问概...
    99+
    2024-04-02
  • 浅谈java监听器的作用
    监听器是JAVA Web开发中很重要的内容,其中涉及到的知识,可以参考下面导图:Web监听器1 什么是web监听器?web监听器是一种Servlet中的特殊的类,它们能帮助开发者监听web中的特定事件,比如ServletContext,Ht...
    99+
    2023-05-31
    java 监听器
  • Python中如何浅谈装饰器
    本篇文章给大家分享的是有关Python中如何浅谈装饰器,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。一 装饰器是什么   装饰器是一个用于封装函数或者类的代...
    99+
    2023-06-04
  • 浅谈MySQL 亿级数据分页的优化
    目录背景分析数据模拟1、创建两个表:员工表和部门表2、创建两个函数:生成随机字符串和随机编号3、编写存储过程,模拟500W的员工数据4、编写存储过程,模拟120的部门数据5、建立关键字段的索引,这边是跑完数据之后再建...
    99+
    2022-05-27
    MySQL 亿级数据分页 MySQL 分页优化
  • 浅谈Tensorflow2对GPU内存的分配策略
    目录一、问题源起二、开发环境三、Tensorflow针对GPU内存的分配策略四、问题分析验证五、GPU分配策略分析六、扩展一、问题源起 从以下的异常堆栈可以看到是BLAS程序集初始化...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作