Jieba分词的原理及应用(三)

news2025/4/15 5:12:24

前言

“结巴”中文分词:做最好的 Python 中文分词组件

上一篇文章讲了使用TF-IDF+分类器范式进行企业级文本分类的案例。其中提到了中文场景不比英文场景,在喂给模型之前需要进行分词操作。

分词的手段有很多,其中最常用的手段还是Jieba库进行分词。(大模型除外,大模型的因为分词对输入的token很重要,所以需要单独训练自己的Tokenizer)

下面这篇文章就来讲一讲Jieba分词的原理和使用吧~~

还是老规矩,从原理出发,并使用代码帮忙辅助理解。
在这里插入图片描述
(结巴说话就像在分词一样,不知道作者当初是不是这么想的起了个这个名字)

1.原理

官网上对Jieba分词的原理描述如下:

  • 基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图(DAG)
  • 采用动态规划查找最大概率路径,找出基于词频的最大切分组合
  • 对于未登录词,采用了基于汉字成词能力的HMM模型,使用了Viterbi算法

是不是很抽象,完全不懂他在说啥。

So,I’m coming,我的作用就到了,就让我来给大家详细讲一下它到底是怎么回事吧

1.前缀词典(前缀树或Trie)

其中每个节点代表一个字符串的前缀。每个节点的字节点代表的字服饰该前缀的延伸。这样就可以高效的实现字符串集合的存储和查询操作,特别是前缀匹配。

  • 结构
    • 根节点:不含字符,通常代表一个空字符串
    • 边:从一个节点到另一个节点的变表示一个字符
    • 节点:每个节点代表从根节点到当前节点的字符序列
    • 叶子节点:完整的字符串
  • 基本操作
    • 插入(Insert):将一个字符串插入到 Trie 中。
    • 搜索(Search):检查一个字符串是否存在于 Trie 中。
    • 前缀匹配(Prefix Search):查找以某个前缀开头的所有字符串。
  • 前缀树代码

字典树(Trie)是一种专门用于处理字符串集合的数据结构,尤其适用于前缀匹配操作

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end_of_word = True

    def search(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                return False
            node = node.children[char]
        return node.is_end_of_word

    def starts_with(self, prefix):
        node = self.root
        for char in prefix:
            if char not in node.children:
                return False
            node = node.children[char]
        return True

# 使用示例
trie = Trie()
trie.insert("apple")
print(trie.search("apple"))    # 输出: True
print(trie.search("app"))      # 输出: False
print(trie.starts_with("app")) # 输出: True
trie.insert("app")
print(trie.search("app"))      # 输出: True
  • 有向图环图代码

是更通用的数据结构

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end_of_word = True

    def search(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                return False
            node = node.children[char]
        return node.is_end_of_word

def build_dag(sentence, trie):
    n = len(sentence)
    dag = {i: [] for i in range(n)}

    for i in range(n):
        node = trie.root
        j = i
        while j < n and sentence[j] in node.children:
            node = node.children[sentence[j]]
            if node.is_end_of_word:
                dag[i].append(j + 1)
            j += 1

    return dag

# 示例词典和句子
words = ["有", "资格", "讨论", "黑客", "精神", "技术", "工作者", "拒绝", "铜臭", "然后", "分享", "转", "强"]
trie = Trie()
for word in words:
    trie.insert(word)

sentence = "有资格讨论黑客精神的技术工作者首先是要拒绝铜臭然后是要分享技术。"
dag = build_dag(sentence, trie)

# 打印 DAG
for key in dag:
    print(f'{key}: {dag[key]}')
  • 有向无环图和前缀树的区别
    • 用途不同
      • Trie 主要用于存储和快速查找字符串集合,适合前缀匹配。
      • DAG 用于表示从起点到终点的所有可能路径,适合处理句子的所有分词路径
    • 结构不同
      • Trie 的结构是树形的,每个插入的字符串有一条唯一路径。
      • DAG 的结构是图形的,允许多个路径表示不同的词组合。(允许一个节点有多条边指向多个节点)

2.动态规划

  • 定义概率模型:对于每个词w,定义其概率P(w)。通常,这个概率是基于词频统计得出的: P(w) = 词w的频率 / 语料库中总词数
  • 定义动态规划状态:定义dp[i]表示从句子开始到第i个字的最大概率路径的概率
  • 动态规划递推:从句子末尾向开头遍历,对于每个位置idp[i] = max(P(w) * dp[j]), 其中w是从 i 到 j 的词, j > i
  • 记录最优路径: 在计算dp[i]的同时,记录导致最大概率的切分点j。这通常用一个route数组来存储。
  • 回溯构建最优分词结果:从句首开始,根据route数组回溯,即可得到最优分词序列

具体实现步骤:

  1. 初始化:dp[n] = 1, n为句子长度 route[n] = n
  2. 从后向前遍历句子: for i in range(n-1, -1, -1): dp[i] = 0 for j in DAG[i]: # j是从i开始可能的词的结束位置 prob = P(sentence[i:j+1]) * dp[j+1] if prob > dp[i]: dp[i] = prob route[i] = j
for i in range(n-1, -1, -1): 
	dp[i] = 0 
	for j in DAG[i]: # j是从i开始可能的词的结束位置 
		prob = P(sentence[i:j+1]) * dp[j+1] 
		if prob > dp[i]: 
			dp[i] = prob 
			route[i] = j
  1. 分词结果回溯:
i = 0 
result = [] 
while i < n: 
	j = route[i] 
	result.append(sentence[i:j+1]) 
	i = j + 1

这种方法的优点是:

  1. 考虑了词的概率(频率),有利于选择更常见的词组合。
  2. 使用动态规划,避免了重复计算,提高了效率。
  3. 能够全局最优化,找到整个句子的最佳分词方案。

3.隐马尔可夫模型与维特比算法

这里比较难,鉴于HMM在NLP领域乃至整个人工智能领域的重要性,我会之后专门出一篇文章来进行详细说明。

这一步主要用于处理未登录词(Out-Of-Vocabulary, OOV),即在词典中没有出现的词。

1.HMM模型概述:

隐马尔可夫模型(Hidden Markov Model, HMM)在这里用于模拟汉字的成词能力。我们将词的边界视为隐藏状态,观察到的是汉字序列。

2.HMM的要素:

  • 状态集合: S = {B, M, E, S}
    B: 词的开头, M: 词的中间, E: 词的结尾, S: 单字成词
  • 观察集合: O = {所有可能的汉字}
  • 初始状态概率: π
  • 状态转移概率: A
  • 发射概率(观察概率): B

3.Viterbi算法:

用于找出最可能的隐藏状态序列,即最可能的分词方案。

4.具体实现步骤:

  1. 模型训练:
    使用已分词的语料库估计HMM的参数(π, A, B)。(Baum-Welch算法

  2. Viterbi算法实现:
    a. 初始化:
    对于每个字c_i和每个可能的状态s:
    V [ 0 ] [ s ] = π [ s ] ∗ B [ s ] [ c 0 ] V[0][s] = π[s] * B[s][c_0] V[0][s]=π[s]B[s][c0]
    p a t h [ 0 ] [ s ] = [ s ] path[0][s] = [s] path[0][s]=[s]

    b. 递推:
    对于t从1到T-1 (T是句子长度):
    对于每个当前状态s:
    V [ t ] [ s ] = m a x ( V [ t − 1 ] [ s ′ ] ∗ A [ s ′ ] [ s ] ∗ B [ s ] [ c t ] ) V[t][s] = max(V[t-1][s'] * A[s'][s] * B[s][c_t]) V[t][s]=max(V[t1][s]A[s][s]B[s][ct])
    p a t h [ t ] [ s ] = p a t h [ t − 1 ] [ a r g m a x ( s ′ ) ] + [ s ] path[t][s] = path[t-1][argmax(s')] + [s] path[t][s]=path[t1][argmax(s)]+[s]

    c. 终止:
    最优路径的概率 = m a x ( V [ T − 1 ] [ s ] ) max(V[T-1][s]) max(V[T1][s])
    最优路径 = p a t h [ T − 1 ] [ a r g m a x ( s ) ] path[T-1][argmax(s)] path[T1][argmax(s)]

  3. 根据最优路径进行分词:
    将连续的BM*E序列或单独的S标记为一个词。

5.优点:

  1. 能够处理未登录词,提高分词系统的鲁棒性。
  2. 考虑了汉字的上下文信息,有助于提高分词准确性。
  3. Viterbi算法保证了在HMM模型假设下的全局最优解。

6.限制:

  1. HMM模型假设状态之间是马尔可夫的,这在实际语言中可能不总是成立。
  2. 需要大量已分词的语料库来训练模型参数。
  3. 对于非常罕见的字符组合,可能表现不佳。

4.综合示例:用"我要去北京"串联全流程

假设词典包含以下词及词频:

{"我": 1000, "要": 800, "去": 1500, "北京": 2000, "北": 500, "京": 300}

步骤1:构建DAG

原始句子:0 1 2 3 4
          我 要 去 北 京

DAG构建过程:

位置0:"我"是词 → [1]
位置1:"要"是词 → [2]
位置2:"去"是词 → [3]
位置3:"北"是词 → [4]
         "北京"是词 → [5]
位置4:"京"是词 → [5]

最终DAG:

0: [1]
1: [2]
2: [3]
3: [4,5]  # 既可以单独切分"北",也可以合并为"北京"
4: [5]

步骤2:动态规划计算

计算最大概率路径(假设总词频为10000):

dp[5] = 1
dp[4] = 300/10000 * 1 = 0.03
dp[3] = max(
    500/10000 * dp[4] = 0.0015,
    2000/10000 * dp[5] = 0.2
) → 0.2
dp[2] = 1500/10000 * dp[3] = 0.03
dp[1] = 800/10000 * dp[2] = 0.0024
dp[0] = 1000/10000 * dp[1] = 0.00024

最优路径回溯:

0→1→2→3→5 → 分词为 ["我", "要", "去", "北京"]

步骤3:HMM处理未登录词

假设现在有个句子:“我要去清华”("清华"不在词典中)

HMM模型通过以下状态序列判断:

汉 字: 清  华
HMM状态: B  E  # 组成双字词
概率计算:
P(清|B) * P(华|E) * P(B→E) > P(清|S) * P(华|S)
最终切分为 ["我", "要", "去", "清华"]

5.关键点总结

  • 词典优先:先用前缀词典找到所有可能的词组合

  • 概率最优:通过动态规划选择全局最优的常见词组合

  • 容错机制:HMM处理词典未覆盖的新词,保证对未知词汇的处理能力

  • 效率平衡:Trie提升查找效率,动态规划避免重复计算,HMM仅在必要时触发

2.实践应用

1.精确模式分词

import jieba

text = "结巴分词是一个很好用的中文分词工具"
words = jieba.cut(text, cut_all=False)
print("精确模式:" + "/".join(words))
# 输出: 
# 精确模式:结巴/分词/是/一个/很/好/用/的/中文/分词/工具

2.全模式分词

words = jieba.cut(text, cut_all=True)
print("全模式:" + "/".join(words))
# 输出: 
# 全模式:结巴/分词/是/一个/很/好/用/的/中文/分词/工具

3.搜索引擎模式

words = jieba.cut_for_search(text)
print("搜索引擎模式:" + "/".join(words))
# 输出: 
# 搜索引擎模式:结巴/分词/是/一个/很/好/用/的/中文/分词/工具

4.添加自定义词典

import os

with open("user_dict.txt", "w", encoding="utf-8") as f:
    f.write("结巴分词 100\n")
    f.write("自然语言处理 100")

jieba.load_userdict("user_dict.txt")
# user_dict.txt 文件内容示例:
# 结巴分词 n
# 自然语言处理 n

text = "结巴分词在自然语言处理中非常有用"
words = jieba.cut(text)
print("使用自定义词典:" + "/".join(words))
# 输出: 使用自定义词典:结巴分词/在/自然语言处理/中/非常/有用

5.词性标注

import jieba.posseg as pseg

words = pseg.cut(text)
for word, flag in words:
    print(f"{word} {flag}")
# 输出:
# 结巴分词 x
# 在 p
# 自然语言处理 x
# 中 f
# 非常 d
# 有用 v

6.TF-IDF分析(Term Frequency-Inverse Document Frequency)

from jieba import analyse

text = "结巴分词是一个很好用的中文分词工具,对中文自然语言处理很有帮助"
tfidf = analyse.extract_tags(text, topK=5, withWeight=True)
for word, weight in tfidf:
    print(f"{word} {weight}")
# 输出:
# 中文 2.0370262532875
# 结巴分词 1.4943459378625
# 自然语言处理 1.4943459378625
# 分词 1.462931634325
# 工具 0.764681704455

7.获取词语位置信息

result = jieba.tokenize(text)
for tk in result:
    print("word %s\t\t start: %d \t\t end:%d" % (tk[0], tk[1], tk[2]))

# word 结巴分词		 start: 0 		 end:4
# word 是		 start: 4 		 end:5
# word 一个		 start: 5 		 end:7
# word 很		 start: 7 		 end:8
# word 好		 start: 8 		 end:9
# word 用		 start: 9 		 end:10
# word 的		 start: 10 		 end:11
# word 中文		 start: 11 		 end:13
# word 分词		 start: 13 		 end:15
# word 工具		 start: 15 		 end:17
# word ,		 start: 17 		 end:18
# word 对		 start: 18 		 end:19
# word 中文		 start: 19 		 end:21
# word 自然语言处理		 start: 21 		 end:27
# word 很		 start: 27 		 end:28
# word 有		 start: 28 		 end:29
# word 帮助		 start: 29 		 end:31

3.总结和展望

在这篇文章中,我们十分深入的理解了什么是结巴分词,从最底层的原理出发,利用代码进行辅助,并用一个例子将整个内容串起来。最后还有对Jieba库的一个简单说明。

相信大家在度过这篇文章之后,应该对Jieba分词不再陌生了吧。

思考题🤔

  1. 结巴分词的时间复杂度是多少呢?为什么企业中还是这么热衷于结巴分词呢?

下期预告🚀
1.最早的预训练语言模型W2C及其变体

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2334019.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Openlayers:flat样式介绍

在前段时间我在使用WebGL矢量图层时接触到了flat样式&#xff0c;我对其十分的感兴趣&#xff0c;于是我花了几天的时间对其进行了了解&#xff0c;在这篇文章中我将简单的介绍一下flat样式的使用方式以及我对其的一些理解。 一、了解flat样式 1.什么是flat样式&#xff1f; …

149页研读——华为基于IPD全过程研发质量管理【附全文阅读】

本文介绍了IPD(集成产品开发)的全过程研发质量管理,强调了以客户需求为导向,通过跨部门协同、资源整合、快速响应等方式提高研发效率和成功率。文章详细阐述了IPD研发管理体系的精要,包括其核心思想、优势、框架以及核心理念。 其中,跨领域平台与技术研发、端到端流程与项…

Oracle 23ai Vector Search 系列之5 向量索引(Vector Indexes)

文章目录 Oracle 23ai Vector Search 系列之5 向量索引Oracle 23ai支持的向量索引类型内存中的邻居图向量索引 (In-Memory Neighbor Graph Vector Index)磁盘上的邻居分区矢量索引 (Neighbor Partition Vector Index) 创建向量索引HNSW索引IVF索引 向量索引示例参考 Windows 环…

vue模拟扑克效果

vue模拟扑克效果 效果图&#xff1a; step1:C:\Users\wangrusheng\PycharmProjects\untitled18\src\views\Home.vue <template><div class"poker-container"><!-- 使用复合数据对象实现双行显示 --><divv-for"(card, index) in POKER_…

Android12源码编译之预置Android Studio项目Android.mk文件编写

1、在AndroidManifest.xml文件中添加package"com.sprd.silentinstalldemo"属性&#xff0c;因为新版本的Android Studio默认生成的AndroidManifest.xml是没有这个属性值的 <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:an…

Spring Boot 测试详解,包含maven引入依赖、测试业务层类、REST风格测试和Mock测试

Spring Boot 测试详解 1. 测试依赖引入 Spring Boot 默认通过以下 Maven 依赖引入测试工具&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</s…

leetcode刷题日记——螺旋矩阵

[ 题目描述 ]&#xff1a; [ 思路 ]&#xff1a; 题目要求按顺时针顺序给出m行n列的矩阵的数组按照题目所给的顺序挨个插入答案数组中运行如下 int* spiralOrder(int** matrix, int matrixSize, int* matrixColSize, int* returnSize) {*returnSize matrixSize * matrixCol…

模板引擎语法-标签

模板引擎语法-标签 文章目录 模板引擎语法-标签[toc]一、用于进行判断的{% if-elif-else-endif %}标签二、关于循环对象的{% for-endfor %}标签三、关于自动转义的{% autoescape-endautoescape %}标签四、关于循环对象的{% cycle %}标签五、关于检查值是否变化的{% ifchange %}…

深度学习学习笔记

目录 摘要 Abstracts 简介 Hourglass Module&#xff08;Hourglass 模块&#xff09; 网络结构 Intermediate Supervision&#xff08;中间监督&#xff09; 训练过程细节 评测结果 摘要 本周阅读了《Stacked Hourglass Networks for Human Pose Estimation》&#xf…

当Browser Use遇见A2A:浏览器自动化与智能体协作的“冰与火之歌“

——一场正在改写数字文明的技术奇遇 第一章 浏览器革命&#xff1a;从"手动挡"到"自动驾驶" 1.1 传统自动化工具的"中年危机" 还记得2023年那个抓狂的凌晨吗&#xff1f;你蹲守演唱会门票时&#xff0c;Selenium脚本因为验证码识别失败第108次…

(已解决)如何安装python离线包及其依赖包 2025最新

字数 305&#xff0c;阅读大约需 2 分钟 没有网络的Linux服务器上&#xff0c;如何安装完整的、离线的python包 1. 写入待安装的包 新建requirement.txt, 写入待安装的包 和 包的版本 如 flwr1.13.0 2.使用命令行直接下载 pip download -d flwr_packages -r requirements.tx…

豪越赋能消防安全管控,解锁一体化内管“安全密码”

在消防安全保障体系中&#xff0c;内部管理的高效运作是迅速、有效应对火灾及各类灾害事故的重要基础。豪越科技凭借在消防领域的深耕细作与持续创新&#xff0c;深入剖析消防体系内部管理的痛点&#xff0c;以自主研发的消防一体化安全管控平台&#xff0c;为行业发展提供了创…

拓扑排序 —— 2. 力扣刷题207. 课程表

题目链接&#xff1a;https://leetcode.cn/problems/course-schedule/description/ 题目难度&#xff1a;中等 相关标签&#xff1a;拓扑排序 / 广度优先搜搜 BFS / 深度优先搜索 DFS 2.1 问题与分析 2.1.1 原题截图 2.1.2 题目分析 首先&#xff0c;理解题目后必须马上意识到…

【STM32】ST7789屏幕驱动

目录 CubeMX配置 配置SPI 开DMA 时钟树 堆栈大小 Keil工程配置 添加两个group 添加文件包含路径 驱动编写 写单字节函数 写字函数 写多字节函数 初始化函数 设置窗口函数 情况一&#xff1a;正常的0度旋转 情况二&#xff1a;顺时针90度旋转 情况三&#xff1…

10min速通Linux文件传输

实验环境 在Linux中传输文件需要借助网络以及sshd&#xff0c;我们可通过systemctl status sshd来查看sshd状态 若服务未开启我们可通过systemctl enable --now sshd来开启sshd服务 将/etc/ssh/sshd_config中的PermitRootLogin 状态修改为yes 传输文件 scp scp &#xff08;Sec…

dify windos,linux下载安装部署,提供百度云盘地址

dify1.0.1 windos安装包百度云盘地址 通过网盘分享的文件&#xff1a;dify-1.0.1.zip 链接: 百度网盘 请输入提取码 提取码: 1234 dify安装包 linux安装包百度云盘地址 通过网盘分享的文件&#xff1a;dify-1.0.1.tar.gz 链接: 百度网盘 请输入提取码 提取码: 1234 1.安装…

使用 TFIDF+分类器 范式进行企业级文本分类(二)

1.开场白 上一期讲了 TF-IDF 的底层原理&#xff0c;简单讲了一下它可以将文本转为向量形式&#xff0c;并搭配相应分类器做文本分类&#xff0c;且即便如今的企业实践中也十分常见。详情请见我的上一篇文章 从One-Hot到TF-IDF&#xff08;点我跳转&#xff09; 光说不练假把…

《车辆人机工程-汽车驾驶操纵实验》

汽车操纵装置有哪几种&#xff0c;各有什么特点 汽车操纵装置是驾驶员直接控制车辆行驶状态的关键部件&#xff0c;主要包括以下几种&#xff0c;其特点如下&#xff1a; 一、方向盘&#xff08;转向操纵装置&#xff09; 作用&#xff1a;控制车辆行驶方向&#xff0c;通过转…

python高级编程一(生成器与高级编程)

@TOC 生成器 生成器使用 通过列表⽣成式,我们可以直接创建⼀个列表。但是,受到内存限制,列表容量肯定是有限的。⽽且,创建⼀个包含100万个元素的列表,不仅占⽤很⼤的存储空间,如果我们仅仅需要访问前⾯⼏个元素,那后⾯绝⼤多数元素占 ⽤的空间都⽩⽩浪费了。所以,如果…

单片机Day05---动态数码管显示01234567

一、原理图 数组索引段码值二进制显示内容00x3f0011 1111010x060000 0110120x5b0101 1011230x4f0100 1111340x660110 0110450x6d0110 1101560x7d0111 1101670x070000 0111780x7f0111 1111890x6f0110 11119100x770111 0111A110x7c0111 1100B120x390011 1001C130x5e0101 1110D140…