第 25 章 计算机怎样处理文字
文章目录
- [第 25 章 计算机怎样处理文字](#第 25 章 计算机怎样处理文字)
-
- [🌈 关于《AI 零基础 36 讲》](#🌈 关于《AI 零基础 36 讲》)
- 同一句话,为什么会切出不同数量
- 用子词覆盖未见过的组合
- 编号只是向量表的行号
- 先用一张透明的教学向量表
- 余弦看方向,字面计数看重合
- 看五组句子的真实计算结果
- 未知词和零向量怎样处理
前面几章的图片已经是一组像素数字。现在把输入换成"我用电脑学习 Python",程序不能直接用汉字做矩阵乘法。它需要先确定输入单位,给这些单位编号,再把编号转换成向量。
本章从字符、词和子词切分开始,运行一个小型子词合并程序,再比较几组句子的相似度。需要第 13 章的数组与向量知识,以及第 21 章的输入、参数和网络直觉。随附 Notebook已经实际执行。

🌈 关于《AI 零基础 36 讲》
🧠 零基础也能开始学 AI
《AI 零基础 36 讲》是一套面向初学者的 AI 入门教程。课程从认识和使用 AI 开始,逐步学习
Python、数据处理、机器学习与深度学习,再进入大模型应用开发,最后完成个人知识库助手的综合实践。
📖 本篇是正式讲解文章
通过具体例子、原创图示和操作结果讲清概念。涉及编程的章节提供可运行代码,方便你边读边试。
🎯 读完以后,动手检验
建议按专栏顺序学习,并完成对应的课后训练,检查自己能否独立运用本章知识。
博客专栏:《AI 零基础 36 讲》
同一句话,为什么会切出不同数量
看一条原创教学句子"我用电脑学习Python"。按字符切分,会得到 12 个字符;按本章给定的词表,可以切成"我、用、电脑、学习、Python"五个单位。Token 就是模型处理文本时采用的切分单位,可能是一个字符、一个词,也可能是一个词的一部分。
python
from examples.text_core import load_data, tokenize_words
data = load_data()
text = data["sentences"][0]
print(list(text))
print(tokenize_words(text, data["vectors"]))
词表切分结果为 ['我', '用', '电脑', '学习', 'Python']。这里的函数采用从当前位置选最长已知词的规则。例如词表同时有"学"和"学习",遇到"学习"时优先选择两个字的词。
中文没有用空格标出所有词的边界,固定词表可能遇到歧义。英文的空格也不能保证一个完整单词只占一个 Token,因为实际分词器可能继续做子词切分。因此 Token 数量依赖具体规则、词表与输入文字,本章五个 Token 的结果不能套到所有模型。代码中的 list(text) 是 Python 字符切分,对于本章普通文字足够直观,复杂组合 emoji 的显示单元还可能由多个 Unicode 字符组成。
用子词覆盖未见过的组合
按完整词建立词表,会遇到新词、词形变化和拼写组合。子词方法把经常出现的片段保存下来,用多个片段表达更长的输入。BPE 是一种常见的相邻片段合并方法,入门规则可在 Hugging Face 的 BPE 教程学习。
我们自行设置六个英文教学词及频次,low 出现 5 次,lower 2 次,lowest 1 次,new 3 次,newer 2 次,newest 1 次。先把每个词切成字母,统计相邻片段对的出现次数,优先合并最常见的一对,再重新统计。
第一轮中 l 与 o 相邻的次数是 5+2+1=8,o 与 w 也有 8 次。为了得到固定结果,本章并列时按字典序选取,因此先合成 lo。下一轮把 lo 和 w 合成 low。六轮真实运行结果如下。
| 轮次 | 合并的两个片段 | 加权次数 | 新片段 |
|---|---|---|---|
| 1 | l 与 o | 8 | lo |
| 2 | lo 与 w | 8 | low |
| 3 | e 与 w | 6 | ew |
| 4 | n 与 ew | 6 | new |
| 5 | e 与 r | 4 | er |
| 6 | e 与 s | 2 | es |
python
from examples.text_core import load_data, train_bpe, tokenize_bpe
rules, splits, alphabet = train_bpe(load_data()["bpe_word_counts"])
print(splits["lower"])
print(splits["lowest"])
print(tokenize_bpe("newlow", rules, alphabet))
结果依次为 ['low', 'er']、['low', 'es', 't']、['new', 'low']。newlow 是专门构造的未见组合字符串,不是本章要求读者记忆的真实英语词。它没有进入词频表,但能用已有片段表示。lowest 中保留下来的单个 t 也说明,子词不一定恰好等于有语言意义的词根或词缀。
这里实现的是字符级、词内合并的缩小版 BPE,没有真实模型的全部预处理、特殊 Token 和字节回退。它只能处理给定基础字符,不覆盖任意文字。实际字节级 BPE 还会用字节作基本单位,本章程序没有实现那一部分。随附合并记录保留每轮的片段与次数,读者无需下载模型就能复核算法。
编号只是向量表的行号
词表是已知 Token 的集合。给词表里的每个 Token 分配一个整数编号,程序就能用编号定位一行向量。Embedding 通常指这种把离散单位映射成连续向量的表示。

本章词表按固定字符串顺序排列。"我、用、电脑、学习、Python"的编号为 13、20、21、10、0。这些数字是任意编号,不表达含义远近。编号 20 和 21 相邻,不能推出"用"和"电脑"语义最接近;交换词表编号并同步交换向量表行,查到的表示仍然相同。
python
from examples.text_core import load_data, vocabulary_and_matrix
data = load_data()
ids, matrix = vocabulary_and_matrix(data["vectors"])
print(matrix.shape)
print(ids["电脑"], ids["计算机"])
print(matrix[ids["电脑"]], matrix[ids["计算机"]])
向量表为 26×4,两词的编号不同,但查到的向量均为 [1, 0, 0, 0]。词数和维度分别是两个轴,编号只决定取哪一行。
先用一张透明的教学向量表
真实模型中的词向量往往通过训练得到,各维度通常不能直接读成一个确定主题。为了看清机制,本章手工指定四个方向,分别代表电脑与编程学习、猫与家具、下雨与雨具、购物与水果。这张表是原创教学设定,没有训练或下载语义模型,也不能当作实际语义模型测评。
"电脑"和"计算机"都取 [1,0,0,0],"小猫""猫咪""桌子""书桌"都取 [0,1,0,0]。"我""在""上"等词取零向量。这种设置刻意把相近主题放到同一方向,却也把猫和桌子压成了同样的表示。它保留了我们选择的内容,丢掉了我们没有表示的差别。
句向量是整句话的向量。本章把各 Token 向量取平均。"我用电脑学习Python"中五个 Token,有三个落在第一方向,另外两个为零,所以得到 [0.6,0,0,0]。平均是为了便于演示,不能理解成所有句向量模型的完整计算方式。真实的句子表示方法还可能结合上下文和专门训练目标。
python
from examples.text_core import load_data, sentence_vector
data = load_data()
vector, tokens, ids = sentence_vector(data["sentences"][0], data["vectors"])
print(tokens)
print(vector)
输出五个词和 [0.6 0. 0. 0.]。这一步将变长 Token 序列压成固定四维向量。平均同时丢掉词序,"猫在桌上"和调整顺序后的文字可能得到相同表示,后面要始终记着这个限制。
余弦看方向,字面计数看重合
第 13 章介绍过点积。余弦相似度用两向量的点积除以各自长度的乘积,考察方向关系。对非零实向量,它在 -1 至 1 之间,同方向为 1,垂直为 0,反方向为 -1。本章所有计数和人工向量都非负,因此这里实际结果在 0 至 1 之间。
python
from examples.text_core import cosine
print(round(cosine([1, 2], [10, 20]), 6))
print(cosine([1, 0], [0, 1]))
print(cosine([1, 0], [-1, 0]))
结果为 1.0、0.0、-1.0。把向量整体乘以正数不会改变方向,所以第一个结果仍为 1。余弦也没有自动计算事实真假,它只能比较已经选定的表示。
我们另外建立字符计数向量。所有句子使用同一张字符表,每个位置记录某个字符在句子中出现几次。这样计算出的相似度反映字符重合,是词法层面的近似比较。本章使用"词法"这个词概括文字表面的重合,具体实现采用字符列计数。
看五组句子的真实计算结果
| 比较 | 字符计数余弦 | 人工主题向量余弦 |
|---|---|---|
| 我用电脑学习Python/我在计算机上学编程 | 约 0.192450 | 1.000000 |
| 我用电脑学习Python/我今天在电脑上买水果 | 约 0.273861 | 约 0.447214 |
| 我用电脑学习Python/今天下雨带雨伞 | 0.000000 | 0.000000 |
| 小猫坐在桌子上/猫咪趴在书桌旁 | 约 0.428571 | 1.000000 |
| 猫在桌上/猫不在桌上 | 约 0.894427 | 1.000000 |

第一组用了不同说法,字符重合低。人工表预先把电脑、计算机、学习、编程等词放在同方向,因此主题余弦为 1。这个结果来自我们指定的表示,不能用它证明程序自主学会了近义关系。
第二组共享"我"和"电脑"等字面信息,字符分数反而比第一组高。但它多了购物与水果方向,和编程学习句向量夹角增大,主题分数降到约 0.447214。第三组在本章表中没有共享方向,因此两个分数都为零。
第四组谈的是相近场景,但"坐"和"趴"并非相同动作。人工向量把动作词设为零,丢掉了这个区别。第五组则更直接,"不"同样被设为零,肯定句与否定句的主题向量方向完全相同,分数仍为 1。高分只说明表示相近,没有检查两句陈述是否能同时成立。
因此词法相似和语义相似要分别观察。真实训练出的语义表示能够利用比字符重合更丰富的信息,但结果仍受训练、上下文和任务影响。拿到一个很高的相似度分数以后,还需单独检查否定、数字、时间和主体关系。本章的透明小表让这些遗漏直接显露出来。
未知词和零向量怎样处理
本章词表遇到"量子"会明确报错,不会悄悄丢掉两个字。修复示例先用已有词"电脑"运行;若只想看新文字的字符,可以得到 ['量','子'],但这并没有为两个字提供语义向量。真实分词器可能使用未知 Token、子词或字节规则,具体方法要按所选模型核对。
零向量没有方向,余弦分母为零。本章"我在上"的词全被设成零,不能拿它计算方向相似。修复版捕获错误,明确说明原因,再对有内容的向量运算,没有擅自把零向量相似度写成 1。
运行 Notebook 后,试着给数据文件中的"坐"和"趴"增加不同方向,观察第四组的结果怎么变化。保存原文件副本,并把修改说成新的教学设定。完成【零基础学AI】第 25 章课后练习与答案后,下一章会让每个 Token 根据其他位置的信息,计算注意力权重并汇总向量。
