Jieba

jieba

为什么要用jieba

  1. 中文里面没有空格,比如:我喜欢机器学习,他不知道:我 | 喜欢 | 机器学习。还是:我喜 | 欢机器 | 学习。 所以需要中文分词
  2. 为什么不直接按字处理,会丢失语义。
  3. 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 总图

graph LR 0((0)) -- 研 --> 1((1)) 0 -- 研究 --> 2((2)) 1((1)) -- 究 --> 2 2((2)) -- 生 --> 3((3)) 2 -- 生命 --> 4((4)) 3((3)) -- 命 --> 4 4((4)) -- 起 --> 5((5)) 4 -- 起源 --> 6((6)) 5((5)) -- 源 --> 6

第 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

posted @ 2026-08-05 15:25  闲不住咸鱼  阅读(0)  评论(0)    收藏  举报