Jieba
jieba
为什么要用jieba
- 中文里面没有空格,比如:我喜欢机器学习,他不知道:我 | 喜欢 | 机器学习。还是:我喜 | 欢机器 | 学习。 所以需要中文分词
- 为什么不直接按字处理,会丢失语义。
- NLP任务为什么依赖分词,比如:我喜欢机器学习,可能匹配机器、学习而不是机器学习。可能会影响情感分析。
jieba到底是什么
一个基于词典 + 统计模型的中文分词工具。
基于词典(最基础)
jieba 内部有个超大词典。
匹配规则:
- 按词表去最长匹配(尽量保存最长语义)
- 给定一句话,多个切分方案,寻找概率最高的词组合,寻找组合的方法
| 贪心 | DP | |
|---|---|---|
| 思路 | 每步选当前最优词 | 记下"到每个点的最佳路径" |
| 后果 | 局部最优 ≠ 全局最优 | 保证全局最优 |
| 依赖 | 只看下一步 | 看完整路径可能性 |
!image.png
!image.png
建 DAG 的两阶段:
阶段 1:暴力列举(所有 1-3 字组合)
→ 15 条边
阶段 2:字典筛选
→ 只留字典里有的
→ 9 条边
下一步:DP 在这 9 条边里选最佳路径
一步步拆给你看,**每一步都有"为什么这么算"**:
---
## 第 0 步:先理解我们要干啥
**目标**:把 `"研究生命起源"` 这 6 个字切成有意义的词。
**思路**:找出"所有可能的切法" → "用 DP 算哪种切法最可能" → "回溯出最终结果"。
---
## 第 1 步:准备工具
### ① 文本 + 位置
字符: 研 究 生 命 起 源
位置: 0 1 2 3 4 5 6
↑ ↑
第4个字 第6个字(结束)
**位置 0 = 文本开头**,**位置 6 = 文本结尾**。`text[i]` = 第 i 个字,`text[0:2]` = 前 2 个字 = "研究"。
### ② 字典(频率表)
研=10 究=10 研究=1000
生=5 命=5 生命=1000
起=5 源=5 起源=1000
**为什么单字频率低(5-10)、整词高(1000)?**
| 词 | 频率 | 实际语言中 |
|---|---|---|
| 研究 | 1000 | 高频词(科研文章天天见) |
| 研 | 10 | 很少单独用("我研一下",几乎不出现) |
| 生命 | 1000 | 高频词 |
| 生 | 5 | 单独出现极少("一生"算别的) |
**这样设计字典,DP 自然倾向切长词**。
### ③ log 函数
为啥要用 log?
研究 + 生命 + 起源 概率 = 1000 × 1000 × 1000 = 1,000,000,000
= 太大,容易溢出
log 之后:
log(1000) + log(1000) + log(1000) = 6.91 + 6.91 + 6.91 = 20.73
= 容易算
**log 让"相乘"变"相加"**,**数值更稳**。
参考值:
log(5) ≈ 1.61
log(10) ≈ 2.30
log(1000) ≈ 6.91
---
## 第 2 步:建 DAG(找所有可能的词)
### 核心代码(jieba 内部)
```python
for start in range(n): # 每个位置都试
frag = ""
for end in range(start, n): # 从 start 往后试 1-多个字
frag += text[end] # 加一个字
if frag in dict: # 字典里有?
DAG[start].append(end + 1) # 加边:start → end+1
从位置 0("研")开始试
| 试的片段 | 长度 | 字典里有? | 加边? |
|---|---|---|---|
| 研 | 1 | ✅ | 0 → 1 |
| 研究 | 2 | ✅ | 0 → 2 |
| 研究生 | 3 | ❌ | 跳过 |
| 研究生命 | 4 | ❌ | 跳过 |
DAG[0] = [1, 2]
从位置 1("究")开始试
| 片段 | 有? | 加边? |
|---|---|---|
| 究 | ✅ | 1 → 2 |
| 究生 | ❌ | 跳过 |
DAG[1] = [2]
从位置 2("生")开始试
| 片段 | 有? | 加边? |
|---|---|---|
| 生 | ✅ | 2 → 3 |
| 生命 | ✅ | 2 → 4 |
| 生命力 | ❌ | 跳过 |
DAG[2] = [3, 4] ← 关键!有 2 条出路
从位置 3("命")开始试
DAG[3] = [4]
从位置 4("起")开始试
| 片段 | 有? | 加边? |
|---|---|---|
| 起 | ✅ | 4 → 5 |
| 起源 | ✅ | 4 → 6 |
DAG[4] = [5, 6] ← 关键!有 2 条出路
从位置 5("源")开始试
DAG[5] = [6]
DAG 总图
第 3 步:DP 算最佳路径
核心思想:
dp[i] = 走到位置 i 的最佳(log 概率最大)分数
path[i] = 这个最佳路径是从哪个 j 来的
递推公式:
dp[i] = max(dp[j] + log(freq[text[j:i]])) for 所有从 j 能走到 i 的边
path[i] = 那个让 max 成立的 j
算 dp[1]
能到 1 的边:只有 0 → 1(词 = "研")
dp[1] = dp[0] + log(研) = 0 + 2.30 = 2.30
path[1] = 0
| i | 0 | 1 |
|---|---|---|
| 字符 | 起点 | 研 |
| dp | 0 | 2.30 |
| path | - | 0 |
算 dp[2]
能到 2 的边:0 → 2(研究),1 → 2(究)
| 来路 | 词 | 计算 | 结果 |
|---|---|---|---|
| 0 → 2 | 研究 | 0 + 6.91 = 6.91 | 6.91 ✅ |
| 1 → 2 | 究 | 2.30 + 2.30 = 4.60 | 4.60 |
为什么 6.91 赢? 因为 "研究" 的 log 频率(6.91)远大于 "研"+"究"(2.30+2.30=4.60)。
结论:
dp[2] = 6.91
path[2] = 0
| i | 0 | 1 | 2 |
|---|---|---|---|
| dp | 0 | 2.30 | 6.91 |
| path | - | 0 | 0 |
算 dp[3]
能到 3 的边:只有 2 → 3(生)
dp[3] = dp[2] + log(生) = 6.91 + 1.61 = 8.52
path[3] = 2
| i | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| dp | 0 | 2.30 | 6.91 | 8.52 |
| path | - | 0 | 0 | 2 |
算 dp[4](关键步骤)
能到 4 的边:2 → 4(生命),3 → 4(命)
| 来路 | 词 | 计算 | 结果 |
|---|---|---|---|
| 2 → 4 | 生命 | 6.91 + 6.91 = 13.82 | ✅ |
| 3 → 4 | 命 | 8.52 + 1.61 = 10.13 |
为什么 13.82 赢?
路径 A:研究 + 生命 = 6.91 + 6.91 = 13.82 ← 整词"生命"频率高
路径 B:研究 + 生 + 命 = 6.91 + 1.61 + 1.61 = 10.13 ← 单字频率低
DP 选整体最优,而不是局部最优——这就是 DP 的威力。
结论:
dp[4] = 13.82
path[4] = 2
| i | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| dp | 0 | 2.30 | 6.91 | 8.52 | 13.82 |
| path | - | 0 | 0 | 2 | 2 |
算 dp[5]
能到 5 的边:只有 4 → 5(起)
dp[5] = dp[4] + log(起) = 13.82 + 1.61 = 15.43
path[5] = 4
算 dp[6](最终结果)
能到 6 的边:4 → 6(起源),5 → 6(源)
| 来路 | 词 | 计算 | 结果 |
|---|---|---|---|
| 4 → 6 | 起源 | 13.82 + 6.91 = 20.73 | ✅ |
| 5 → 6 | 源 | 15.43 + 1.61 = 17.04 |
为什么 20.73 赢? 跟 dp[4] 一样的原因——整词"起源"频率远高于"起"+"源"。
结论:
dp[6] = 20.73
path[6] = 4
第 4 步:完整 dp / path 表
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| 字符 | - | 研 | 究 | 生 | 命 | 起 | 源 |
| dp[i] | 0 | 2.30 | 6.91 | 8.52 | 13.82 | 15.43 | 20.73 |
| path[i] | -1 | 0 | 0 | 2 | 2 | 4 | 4 |
第 5 步:回溯(从终点往回走)
代码
result = []
pos = 6 # 终点开始
while pos > 0:
prev = path[pos] # 上一个位置
result.append(text[prev:pos]) # 切出这个词
pos = prev # 跳上去
result.reverse() # 反转
4 步轨迹
第 1 步:pos = 6
prev = path[6] = 4
切出 text[4:6] = "起源" ← 第 1 个词
pos 变成 4
第 2 步:pos = 4
prev = path[4] = 2
切出 text[2:4] = "生命" ← 第 2 个词
pos 变成 2
第 3 步:pos = 2
prev = path[2] = 0
切出 text[0:2] = "研究" ← 第 3 个词
pos 变成 0
第 4 步:pos = 0
停!
收集并反转
result = ["起源", "生命", "研究"] # 倒着收集的
result = result.reverse() # 反转
# → ["研究", "生命", "起源"] ✅
第 6 步:图解整个回溯过程
位置: 0 1 2 3 4 5 6
研 究 生 命 起 源
DAG: 0 ──研究──> 2 ──生命──> 4 ──起源──> 6
↑
这条是最佳路径
走法:4 → 2 → 0
回溯顺序:6 → 4 → 2 → 0
切出词: 起源 生命 研究
反转后: 研究 生命 起源
整个流程总结(5 步)
| 步骤 | 干啥 | 输出 |
|---|---|---|
| 1. 准备 | 文本 + 字典 + log 函数 | 工具 |
| 2. 建 DAG | 每个起点试 1-N 个字,字典里有的留边 | 有向无环图 |
| 3. DP | 算每个位置的最佳分数 + 记录前驱 | dp 数组 + path 数组 |
| 4. 回溯 | 从终点按 path 倒推到起点 | 切出的词列表 |
| 5. 输出 | 反转词列表 | ['研究', '生命', '起源'] |
一句话
建 DAG(找所有可能切法)→ DP(挑最可能的那条)→ 回溯(把那条切出来)。每一步用到的都是"从上一个最佳状态"推出这一步——所以叫动态规划。
匹配过程:看到"自然语言处理"在词典里 → 作为一个整体,不切。
什么是词典:jieba内部有一个词库。
```python
每个词都有:
词语
+
出现频率
+
概率
| 词 | 频率 |
|---|---|
| 的 | 1000000 |
| 人工智能 | 50000 |
| 人工 | 30000 |
| 智能 | 40000 |
基于统计(处理新词)
词典里没有的词(比如新出现的"内卷"、"yyds"),jieba 用统计模型算:
- 看相邻字一起出现的概率HMM(隐马尔可夫模型)
| 标签 | 含义 | 说明 |
| -- | ------ | ---------- |
| B | Begin | 一个词的开头 |
| M | Middle | 一个词的中间 |
| E | End | 一个词的结尾 |
| S | Single | 单独一个字就是一个词 |
为什么叫“隐马尔可夫模型”?
-------------------HMM是怎么猜的?-------------------
假设输入:
张三丰
模型会比较很多可能性。
可能性一
张 / 三 / 丰
S S S
意思是三个单字词。
可能性二
张三 / 丰
B E S
可能性三
张 / 三丰
S B E
可能性四
张三丰
B M E
HMM会给每一种方案计算一个概率,然后选择概率最大的方案。
| 标签组合 | 分词结果 | 假设概率 |
| ----- | --------- | ---: |
| S-S-S | 张 / 三 / 丰 | 0.05 |
| B-E-S | 张三 / 丰 | 0.10 |
| S-B-E | 张 / 三丰 | 0.15 |
| B-M-E | 张三丰 | 0.70 |
--------------------------------------HMM根据什么计算概率?---------------------------
它主要参考三类概率。
第一类:一个字常出现在哪个位置
例如,训练数据中可能观察到:
“张”经常出现在姓名或词语开头;
“丰”有时出现在词语结尾;
某些字经常单独成词。
这类信息叫作“发射概率”,通俗地说就是:
看到某个汉字时,它属于B、M、E、S的可能性分别有多大?
例如:
看到“张”
→ 是B的概率较高
→ 是M的概率较低
但它不是简单地说“张永远是B”。例如:
紧张
这里“张”更适合标记为E:
紧 张
B E
因此,模型需要结合上下文。
第二类:标签之间能不能自然连接
B、M、E、S并不是随便排列的。
例如:
B → M → E
是合理的,表示一个三个字或更长的词。
B → E
也是合理的,表示一个两个字的词。
S → B
也合理,表示一个单字词后面出现了一个新词。
但下面这种组合通常不合理:
B → S
因为既然B表示“词语刚开始”,下一个字一般应该是M或E,而不应该突然变成单字词。
标签之间的变化概率叫作“转移概率”,通俗地说就是:
一个B后面最可能跟M还是E?一个E后面最可能跟B还是S?
第三类:一句话开头通常是什么状态
一句话的第一个字通常只能是:
B:一个多字词的开头;
S:一个单字词。
通常不能一上来就是M或E,因为还没有出现词语开头。
这叫作“初始概率”。
完整流程
1. 加载字典
↓
2. 建 DAG:从每个位置出发,看后面能组成哪些词
选词是不是遍历这个有向无环图,然后把所有词取字典里面查询,只保留存在的
↓
3. DP 算最佳路径(Viterbi 算法)
- 每个位置 i:max(dp[j] + log(freq[text[j:i]])) for all j < i
- 同时记 path[i] = 最佳 j
↓
4. 回溯得 DAG 部分的分词
↓
5. HMM 处理未登录词(字典里没有的)
↓
输出:['研究', '生命', '起源']
怎么用法
| 方法 | 返回 | |
|---|---|---|
| cut | generator | 大文本 |
| lcut | list | 小文本 |
分词模式
精确模式(默认)
特点:找到合理拆分
import jieba
text = "南京市长江大桥"
print(
jieba.lcut(text)
)
输出:
南京市
长江大桥
适合:
普通文本。
全模式
特点:把所有可能词都找出来。
print(
jieba.lcut(
"南京市长江大桥",
cut_all=True
)
)
南京
南京市
长江
长江大桥
大桥
搜索引擎模式
特点:在精确切分基础上,把长词再细切
jieba.lcut_for_search(
"南京市长江大桥"
)
特点:
切得更细。
适合:
搜索系统
| 模式 | 切分结果 | 词数 |
|---|---|---|
| 精确模式 | 南京市 / 长江大桥 | 2 |
| 全模式 | 南京 / 南京市 / 市长 / 长江 / 长江大桥 / 大桥 | 6 |
| 搜索引擎模式 | 南京 / 市 / 长江 / 大桥 | 4 |
DP

浙公网安备 33010602011771号