恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

手写 BPE Tokenizer:从编码解码到词表设计的完整实践指南

  • 首页
  • 资讯中心
  • /
  • 手写 BPE Tokenizer:从编码解码到词表设计的完整实践指南

相关资讯

SpringBoot+Vue科研管理系统:从数据库建模到权限部署全解析 2026/10/22 2:59:59
用Java技术栈打造大数据机器学习NLP对话系统,多轮交互优化实践 2026/10/22 2:54:58
C# WinForms 可嵌入式界面设计器源码解析 2026/10/22 2:54:58

最新资讯

排序算法工程实践:从时间复杂度到缓存友好性的全栈解析
企业级 Agent 平台如何解决个人级工具无法落地的三大难题
如何借助AI辅助工具,半个月高效完成SCI初稿写作
AI写作中的内容安全准则与高质量博文实践
Agent Infra五层架构拆解:从能跑到好用的智能体基础设施实战指南
智能客服回答率高却效率低?数字员工与中台协同实现效率爆发

今日推荐

电商智能运营体系从0到1:架构设计、落地步骤与避坑指南
压缩空气储能与热网热惯性联合优化调度:热电联产系统建模与求解
若依框架AI代码生成不稳定?用Agent Skill三层结构+五步法搞定

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

手写 BPE Tokenizer:从编码解码到词表设计的完整实践指南

发布时间:2026/10/22 2:59:59
手写 BPE Tokenizer:从编码解码到词表设计的完整实践指南 1. tokenizer是模型的“翻译官”不是跑在数据前面的一个正则表达式我第一次把人话和数字之间这层翻译单独拎出来做作业是在一门 LLM 基础课上。作业标题写得很直接assignment1第一题就是 tokenizer。当时有不少人觉得“这有什么好写的调用现成词表不就行了”结果真下笔才发现一个能被反复加载、编码、解码、不回错也不丢信息的 tokenizer远比想象中绕。先别急着想神经网络。模型真正能处理的东西只有数字。你给它一段“我喜欢自然语言”它拿到手的是[102, 313, 87, 211]这样一串整数。这些整数从哪来tokenizer。它要做的事是把原始文本切分成一个个 token再去词表里查到对应的 ID。反过来模型生成了一串 IDtokenizer 还要能把它还原成可读文本。两步都做到才叫完整。我见过很多新手把注意力全放在“切分”上觉得切得越准越好。这是一个非常容易踩进去的误区。tokenizer 真正要保证的不是切得“语义正确”而是“可逆”。你 encode 之后 decode必须逐字、逐空格、逐标点还原成输入原文。这条要求看起来不起眼却是检验实现是否靠谱的第一标准。1.1 从“一篇文章”到“一串 ID”其实只做了一半工作假设输入是“hello world!”。一个最简单的 tokenizer 流程是先按空白和标点切出[hello, world, !]再映射成[123, 456, 7]。到此为止你会发现空格丢了。如果模型生成文本之后你拿 decode 去还原得到的是helloworld!。字符都在但空格没了这就是“不可逆”。作业里常见的验收动作是随机抽 10 万字符先编码再解码然后逐字符比对。你会发现空间、换行、制表符、连续多个空格都是最容易出bug的地方。它们不是内容的核心却是真实文本里躲不掉的部分。一个能在小样例上跑赢的 tokenizer不一定能在真实语料上还原成功。所以说tokenizer 不是一个“分词规则集合”它更接近一套压缩-解压系统。理解这个定位后面看 BPE 等算法时就不会只盯着频率统计而是会同时想合并之后我还拆得回来吗1.2 “反正模型会自动适应上下文”是个危险假设有同学会辩护切分不完美又怎样模型不是能根据上下文学到吗这句话只在一个条件下成立训练和推理用完全相同的 tokenizer。坏就坏在很多人会一边改词表、一边继续跑实验或者训练时用一套预切分规则、评测时换了一套。结果模型在训练期看到的 ID 分布和推理期完全不同效果直接崩给你看。这不是理论恐吓。实际实验里如果训练时“喜欢”是一个 token、ID 是 300推理时却被切成了“喜”和“欢”两个 ID模型看到的就是两个从未单独出现过的输入符号。哪怕上下文提供再多的信息它也很难恢复出“喜欢”这个整体的语义表示。只要 encode/decode 往返能 100% 还原就能确保同一个句子的任何两次切分结果一致这是稳定训练的前提。2. 字符级、BPE、WordPiece、Unigram作业为什么总从 BPE 下手我知道你想问四种主流方案作业为什么不都学一遍不是不学而是第一题通常只让你做一个能跑的闭环。这时选 BPE 是最理性的决定。2.1 四种切词思路的关键差别把它们放在一张表里看差异就很清楚方案最小单位学习方式好处代价/风险字符级字符或字节无学习过程词表固定任何文本都能表示序列被拉得很长模型学习负担大BPE字符/字节合并出的子词反复统计相邻对合并最高频对实现直观可解释性强合并顺序对少数切分结果有影响基于似然的子词方案子词以语料似然为准则挑选合并与语言模型目标更贴合额外计算和实现复杂度双高概率式词表方案子词从大头词表逐步裁剪生僻 token 保留更灵活逻辑复杂不适合初次练手字符级看似简单但一个常见的后果是 token 数量暴增。比如一个 7 个字的中文句子字符级切法就是 7 个 token而字节级 BPE 可能只用 4~5 个 token。序列越长self-attention 的计算量就越大训练效率也越差。2.2 BPE 能成为第一题靠的是“学习过程完全可解释”字节对编码的核心逻辑一句话就能说清不断统计文本里相邻字符对的出现次数把出现最多的那对合并成一个新符号反复进行直到词表达到目标大小。整个过程和语言模型本身无关但天然解决了两个问题高频词被压缩成少数 token低频词又可被拆成更小成分不至于丢失。对一份作业来说可解释性尤其重要。写完后你可以一行行打印亲眼看到“喜”“欢”合并成“喜欢”的过程也可以随时打开 merges 文件确认第 5000 次合并到底做了什么。这种透明性在调试时价值很大。而基于似然的方案词表形成过程很像一个黑盒出现 bug 时很难判断是统计问题还是概率计算问题。所以初学阶段BPE 是最稳妥的选择。3. 手写 BPE 的分步拆解训练循环、合并优先级与保存三件套下面我写一个能跑通完整训练流程的最小 BPE 实现骨架。它不追求工程级性能但足以覆盖训练、合并、保存、加载和往返验证。3.1 先把语料切成可以计数的“单词”import re from collections import Counter, defaultdict def pre_tokenize(text): # 按连续字母/数字以及非空白标点把文本切成初步片段 pattern r[a-zA-Z0-9]|[^\s] return re.findall(pattern, text) def build_initial_vocab(corpus): word_freq Counter() for text in corpus: for word in pre_tokenize(text): # 把每个单词拆成字符元组方便后续合并 word_freq[tuple(word)] 1 return word_freq这里有个设计选择把每个单词变成“字符元组”比如(h, e, l, l, o)。后续所有合并操作都围绕元组相邻位置进行比直接用字符串拼接更安全统计相邻对时也更方便。预切分只负责定义合并的边界真正的学习发生在 BPE 训练循环里。如果你用这个 pattern 处理中文注意它会把标点和汉字拆开也能把英文单词内聚在一起。这已经足够作业验证不需要更像工程版本的复杂正则想加深理解时再换成[\p{L}\p{N}]这类细粒度规则也不难。3.2 相邻对统计与合并循环def get_pair_counts(word_freq): counts defaultdict(int) for chars, frequency in word_freq.items(): for i in range(len(chars) - 1): counts[(chars[i], chars[i 1])] frequency return counts def merge_pair(word_freq, pair): merged_unit pair[0] pair[1] new_freq Counter() for chars, frequency in word_freq.items(): merged_chars [] i 0 while i len(chars): if (i len(chars) - 1 and chars[i] pair[0] and chars[i 1] pair[1]): merged_chars.append(merged_unit) i 2 else: merged_chars.append(chars[i]) i 1 new_freq[tuple(merged_chars)] frequency return new_freq主循环def train_bpe(corpus, vocab_size, merge_orderNone): word_freq build_initial_vocab(corpus) merges [] merge_order merge_order or {} while len(merges) vocab_size: counts get_pair_counts(word_freq) if not counts: break best_pair max( counts, keylambda p: (counts[p], merge_order.get(p, 0)) ) word_freq merge_pair(word_freq, best_pair) merges.append(best_pair) return merges, word_freq合并循环里的稳定性是一个容易忽略的细节。如果两个相邻对的出现次数相同必须有一个稳定的第二排序键来分出先后。常见做法是记录“这对符号在整个语料中第一次出现的位置”越早出现越优先。如果你只写max(counts.items(), keylambda x: x[1])字典在 Python 不同版本里的迭代顺序不一致就会导致同一份语料在 A 机器和 B 机器上训练出不同词表。这种问题在作业阶段不明显但到了团队协作时极难排查。3.3 光有 merges 不够要保存三样东西文件作用使用阶段vocab.jsontoken 到 ID 的映射编码、解码merges.txt每次合并的 pair 顺序编码special_tokens_map.jsonendoftext为什么 decode 时不需要 merges因为训练结束后每个 ID 已经对应一个最终 token 串。解码只需把 ID 挨个转成 token再拼接成一个长字符串即可不需要再走一遍合并优先级。而 encode 时如果遇到的字符串在词表里查不到才需要参考 merges 顺序把输入逐步切到能在词表中查到的最细粒度。很多实现只训练不保存导致换一台机器就要重新训练。这是作业里一个很低级的失分点。正确的做法是训练完成后立刻生成上述三份文件并写一个能独立加载它们完成 encode/decode 的简单 API。4. 编码和解码把编号还原回原文比“切分”更容易翻车BPE 的训练你可能一次就写对了真正的问题反而出现在 encode/decode 的边界细节上。这里我集中说三个最容易翻车的点。4.1 空格也要有“可见”的占位符号前面提到的空格问题我在这类练习里几乎每次都遇到。解决办法不是“预处理时保留空格”而是要给空格一个真实的符号身份。比如某些实现里会用特殊的空格占位符让hello world变成类似hello ␠ world的序列参与训练解码时再把占位符还原成空格。这个做法背后的逻辑很朴素空格是原始文本的一部分不是噪声。把它去掉再训练压缩率是自己的骗术把它当成普通边界decode 就丢失信息。一个合格的 tokenizer 必须让 encode 后 decode 的结果和输入完全一致空格、换行、制表符一个都不能少。4.2 中文和生僻字靠 UTF-8 字节级方案兜底字符级 BPE 面对中文时有一个很现实的问题汉字总量太大语料稍微小一点就容易遇到生僻字不在词表里。常规解法是字节级 BPE先把任意输入字符串转成 UTF-8 字节序列以 0~255 的字节作为初始符号再在这个序列上做 BPE 合并。def text_to_bytes(text: str) - list: return list(text.encode(utf-8)) def bytes_to_text(byte_list) - str: return bytes(byte_list).decode(utf-8, errorsstrict)中文的一个字在 UTF-8 里占据 3 个字节比如“言”会变成三个字节。字节级 BPE 的好处是无论输入什么 Unicode 字符最终都能用若干字节组合表示词表永远不会出现查不到的情况。我见过一次典型错误是只把英文字母和数字做了字节编码遇到中文就回退到字符级映射。结果测试文本里出现“霰”“龘”这类字时查不到直接返回 unknown。改成统一在 UTF-8 字节层训练后问题消失且压缩率没有明显下降。4.3 特殊 token 不是普通文本|endoftext|、|pad|这类 token 是训练流程里的控制信号不能和普通文本混在同一套 BPE 合并逻辑里。如果它们进入语料库参与统计很可能会被拆成、|、end这些碎片导致解码时完全无法正确识别。正确做法是在 BPE 训练开始前把特殊 token 直接放入初始词表并且在统计相邻对时排除它们。保存文件时也要单独用一个字段记录特殊 token 的 ID不要让它和普通 token 混在同一个映射表里。这样模型输出|endoftext|时tokenizer 才能原样返回而不是拆得七零八落。5. 词表选多大用压缩率、OOV 率和还原率说话很多课程里大家会争论“到底用 8K 还是 32K 词表”。我的经验是不要靠感觉而是三个可量化指标一起看。5.1 三个可以量化的验收指标压缩率原始字符数 / token 数。中文场景下8K~32K 词表的压缩率通常在 1.5~3 之间。如果低于 1.2说明词表太小序列太长高于 4要检查是否切分粒度太粗把不相关的子串硬拼成了一个大 token。OOV 率独立验证集上出现未知 ID 的 token 比例。字节级实现应该直接是 0如果不是 0优先检查特殊字符和极端 Unicode 处理。往返还原率随机取 10 万字符先 encode 再 decode逐字符比对必须 100%。这是最硬性的指标只要不是 100%就说明实现里有 bug。这三个指标可以写成一个简单的评测脚本。每个阶段调完词表大小重新跑一遍比盯着单条例子的切分结果可靠得多。5.2 词表大小并不是越大越好词表大表达力强但 embedding 矩阵和输出层也会跟着变大。课程作业如果直接用 100K 词表单机训练经常会被内存卡住。我在小规模中文语料上测过几个配置大致的规律如下词表大小中文压缩率参考实际影响建议场景512约 1.1训练很快但表达弱只用来验证流程4096约 1.6均衡第一次作业首选32768约 2.4内存明显上升中规模预训练100000约 3.2资源和时间压力大大规模场景第一次写 tokenizer我建议先跑 4K 词表把训练、保存、加载、往返验证全部打通再换 32K 做对比。直接上大词表往往会因为某个隐藏 bug 浪费大量时间。5.3 不要让语料分布骗了你如果训练语料全是一堆英文技术文档中文高频双字词的合并就会非常少你拿到的词表对中文任务几乎无用。这不算算法错误只是语料分布的正常结果。避免它的办法是调整语料配比并在验证集里加入与目标场景接近的数据。否则你会发现下载来的“通用中文语料”上压缩率很漂亮换到真实业务数据时立刻失效。6. 交作业前我会跑一遍的“调皮输入”自测清单最后分享我的固定测试方案。这些输入看起来不像正经文本但每一个都能精准踩中常见实现里的坑。6.1 一组简单但很能打的测试用例test_inputs [ , , 。, 自然语言处理, hello world hello, a * 1000, 霰龘, # 生僻汉字考验字节级能力 |endoftext|, ] for text in test_inputs: ids tokenizer.encode(text) restored tokenizer.decode(ids) assert restored text, fmismatch: {text!r} - {ids} - {restored!r}空字符串和纯空白要放在最前面。很多实现在空输入时直接报错这就说明边界处理不过关。含中文生僻字的用例是为了确认字节级实现真的有效如果只做了字符级词表这一条大概率会失败。最后一条特殊 token 必须原样返回否则模型的结束符输出会莫名其妙变成普通文本。6.2 让合并过程变直观的小技巧我调试时很喜欢在每间隔 100 次合并后打印当前词表里最长的 3 个 token。你能亲眼看到“自然语言”从单个汉字一步步拼成完整子词的过程。这个观察比看任何文档都有用它会让你真正理解“子词”到底意味着什么也能帮你快速定位 merges 顺序是否混乱。还有一个经验当合并次数接近目标词表上限时剩余候选对的频率普遍很低继续训练的时间成本很高但对效果影响很小。此时可以让训练提前停止质量不会明显下降。如果实验里只是为了对比词表大小的效果设一个较小的 stop threshold 可以省下不少时间。我个人对 tokenizer 的体会是它不像模型结构那样有炫酷的技术点却决定了整套系统最底层的稳定性。把 encode/decode 往返一致性做到 100%把 OOV 压到接近 0再认真比较几次词表大小的变化你对所谓“子词”的理解就会比单纯读论文深得多。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号