来源:互联网 更新时间:2026-08-04 14:16
Tokenizer 是自然语言处理中的基础工具,负责把文本数据转换成模型能理解的数字形式。它的核心工作是将输入的文本切分成单词、子词甚至字符,然后映射到对应的编号或词向量上。简单来说,这就是一道“翻译”工序——把人类语言变成机器能读懂的代码。

在深度学习的世界里,神经网络只能处理数值,文本必须经过转换才能喂给模型。下面,我们通过三个步骤来拆解 Tokenizer 的底层实现原理——如果你能跟着走一遍,就会发现它其实没那么神秘。
先说说三个核心环节:
1. 准备数据
2. 为数据做标记
3. 构建一个真正的 Tokenizer 工具
我们先加载一份英文文本 the-verdict.txt。选英文的好处是:单词之间天然有空格或标点隔开,词汇量相对小,边界清晰,准确率高。中文处理起来就复杂多了——单词之间没有明确分隔符,还得考虑上下文语义,初学者容易被绕晕,所以先用英文打个样。
下面这段代码的核心任务就是对文本按空格、逗号、句号等特殊符号进行分割:
import re
with open("the-verdict.txt", "r", encoding="utf-8") as f:
raw_text = f.read()
print("文本的大小:", len(raw_text))
print("查看开头的20个字符为:", raw_text[:20])
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', raw_text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
print(preprocessed[:20])
输出结果如下:
文本的大小: 20479 查看开头的20个字符为: I HAD always thought ['I', 'HAD', 'always', 'thought', 'Jack', 'Gisburn', 'rather', 'a', 'cheap', 'genius', '--', 'though', 'a', 'good', 'fellow', 'enough', '--', 'so', 'it', 'was']
可以看到,文本已经被切成了一个个独立的 token(标记)——包括单词、标点甚至连字符。这一步相当于把原文“拆碎”,为后续构建词典做准备。
接下来要对这些 token 去重和排序。为什么要去重?因为我们要构建一个词典(词汇表),词典里每个单词只出现一次,然后给每个单词分配一个唯一索引。这样就能实现“通过单词查索引,通过索引查单词”的双向映射。
all_words = sorted(list(set(preprocessed)))
# 构建一个词典表,用键值对表示:key为单词,value为索引
vocab = {token:integer for integer,token in enumerate(all_words)}
for i, item in enumerate(vocab.items()):
print(item)
if i >= 50:
break
输出结果的前面部分是这样的:
('!', 0)
('"', 1)
("'", 2)
('(', 3)
(')', 4)
(',', 5)
('--', 6)
('.', 7)
(':', 8)
(';', 9)
('?', 10)
('A', 11)
('Ah', 12)
('Among', 13)
('And', 14)
('Are', 15)
('Arrt', 16)
('As', 17)
('At', 18)
('Be', 19)
('Begin', 20)
排在前面的都是特殊字符,后面跟着按字母顺序排列的单词。这样词典就建好了,每个 token 都有一个唯一的数字 ID。
现在我们来专门写一个类,处理文本数据。它主要提供两个功能:
下面这个工具虽然简单,但已经包含了分词的两个核心操作:
class TokenizerV1:
# 初始化时传入词典,并创建正向、反向两个映射
def __init__(self, vocab):
self.str_to_int = vocab
self.int_to_str = {i:s for s,i in vocab.items()}
# 编码:文本 → 数字列表
def encode(self, text):
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
ids = [self.str_to_int[s] for s in preprocessed]
return ids
# 解码:数字列表 → 文本
def decode(self, ids):
text = " ".join([self.int_to_str[i] for i in ids])
text = re.sub(r'\s+([,.?!"()\'])', r'\1', text) # 修复标点前多余空格
return text
来试一下效果:
tokenizer = TokenizerV1(vocab)
text = """"Why _has_ he chucked painting?" I asked abruptly"""
ids = tokenizer.encode(text)
print(ids)
decodeText = tokenizer.decode(tokenizer.encode(text))
print(decodeText)
输出结果:
[1, 115, 118, 537, 118, 541, 268, 766, 10, 1, 55, 184, 125] " Why _ has _ he chucked painting?" I asked abruptly
可以看到,文本被编码成了数字序列,再解码回来后基本恢复了原样——虽然标点附近多了空格,但核心信息没有丢失。
不过,这个 TokenizerV1 还不够完善。如果遇到一个词典里没有的单词,程序就会报错。怎么处理?我们需要引入特殊的上下文标记。常见的做法有两种:
<|endoftext|>:表示文本结束,用于连接多个不相关的文本片段。<|unk|>:表示未知单词,如果词典里查不到就统一映射成这个标记。我们先把这两个特殊标记加到词典末尾:
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', raw_text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
all_tokens = sorted(list(set(preprocessed)))
# 在词典的最后增加两个特殊标记
all_tokens.extend(["<|endoftext|>", "<|unk|>"])
vocab = {token:integer for integer,token in enumerate(all_tokens)}
for i, item in enumerate(list(vocab.items())[-5:]):
print(item)
输出显示特殊标记已经被添加到了词典末尾:
('younger', 1156)
('your', 1157)
('yourself', 1158)
('<|endoftext|>', 1159)
('<|unk|>', 1160)
接下来我们升级一下 Tokenizer 工具,加入未知单词处理:
class TokenizerV2:
def __init__(self, vocab):
self.str_to_int = vocab
self.int_to_str = { i:s for s,i in vocab.items()}
def encode(self, text):
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
# 如果单词不在词典中,替换为 <|unk|>
preprocessed = [item if item in self.str_to_int
else "<|unk|>" for item in preprocessed]
ids = [self.str_to_int[s] for s in preprocessed]
return ids
def decode(self, ids):
text = " ".join([self.int_to_str[i] for i in ids])
text = re.sub(r'\s+([,.?!"()\'])', r'\1', text)
return text
来测试一下:我们用两个句子拼接,并在中间加上结束标记,观察编码结果:
tokenizer = TokenizerV2(vocab)
text1 = "Hello, do you like tea?"
text2 = "In the sunlit terraces of the palace."
text = " <|endoftext|> ".join((text1, text2))
print(text)
encodeText = tokenizer.encode(text)
print(encodeText)
输出数字序列如下(找找看特殊标记对应的数字是多少?):
[1160, 5, 362, 1155, 642, 1000, 10, 1159, 57, 1013, 981, 1009, 738, 1013, 1160, 7]
注意第一个数字 1160 对应的是 <|unk|>,因为“Hello”不在词典里(原始文本中没有这个词);中间的 1159 是结束标记 <|endoftext|>;最后一个 1160 又是未知标记,其实应该是句号之前的空格和单词?——具体细节可以自己调试看看。
好了,本文带着你走了一遍文本数据预处理的完整流程:从原始文本切割,到构建词典,再到编写一个具备编码和解码功能的 TokenizerV2 工具。虽然这个工具很基础,但它已经抓住了分词的核心思想。想深入学习大语言模型,Tokenzier 是绕不开的第一道坎——理解它,后面的路会顺很多。
Ondo将于今日上线股票永续合约
Intel喜讯连连:18A工艺良率提升到85%、CPU将涨价15%
晶核艾尔莎角色盘点 晶核艾尔莎强度分析与实战表现
区块链OTC交易所有哪几家比较正规?
黄金价格不断创新高!黄金稳定币XAU、PAXG市值达11亿美元
AMD英特尔集体失眠!英伟达Rosa CPU搭载Rigel核:单核性能碾压x86
遗忘之海密室通关教程 遗忘之海密室全关卡解谜思路与难点解析
CC币价格预测(2026-2035):Canton币今日价格走势+长期价格预测
新浪机器学习热点小时报丨2026年07月25日18时_今日实时机器学习热点速递
异环伊洛伊阵容怎么搭配
新破天一剑太极刀任务攻略 破天一剑怎么取太极刀
潜水员戴夫丛林DLC接吻的鱼任务攻略
五千元以下的笔记本几乎消失!经销商:至少一年看不到涨价尽头
Windows环境下Claude Code从C盘迁移至D盘的完整操作教程
华为Mate 70系列首发的红枫镜头下放至千元档:全员普及原色影像
币安投票上币与下币机制解析:买票争议与规则边界
合集38个项目筹集5.406亿美元 Figure融资2亿
微软Copilot AI漏洞可致敏感数据泄露,企业用户需及时更新
《探索流放之路物品等级的奥秘》 揭秘物品等级的效果和怎么查看
逆战未来s3出什么新角色 逆战未来S3赛季新角色爆料
手机号码测吉凶
本站所有软件,都由网友上传,如有侵犯你的版权,请发邮件haolingcc@hotmail.com 联系删除。 版权所有 Copyright@2012-2013 haoling.cc