从jieba到Tokenizer:大模型时代中文文本处理的根本变革
2026/8/22 6:58:54 网站建设 项目流程

1. 从jieba到Tokenizer:为什么分词这件事,在大模型时代彻底变了

“公主大人,别再用jieba做分词了!”——这个标题乍一看有点标题党,但背后反映的是一个非常真实且普遍的现象。很多从传统NLP项目转向大模型开发的工程师,在处理中文文本时,第一反应还是调用那个熟悉的import jieba。这本身没错,jieba在过去十年里,凭借其简单易用、效果尚可的特点,成为了中文分词领域的“国民工具”。但当你开始接触ChatGLM、GPT、Llama这些大模型时,如果还试图用jieba的分词结果去“喂”给它们,或者用jieba的逻辑去理解它们的输入输出,那无异于用算盘去理解超级计算机的运算,从一开始就走错了方向。

问题的核心在于,大模型(LLM)的“分词”(Tokenization)和我们传统NLP中的“分词”(Word Segmentation),虽然中文都叫“分词”,但完全是两码事。jieba做的是“词语切分”,它的目标是将一个连续的汉字序列,按照中文的语言习惯,切分成一个个有意义的词语(如“我喜欢吃苹果” -> [“我”, “喜欢”, “吃”, “苹果”])。这个过程依赖于词典、统计模型或深度学习模型来识别词语边界。

而大模型的Tokenizer(如ChatGLM用的、GPT用的tiktoken、Llama用的SentencePiece),做的是“子词切分”(Subword Tokenization)。它的目标不是寻找语言学上的词语,而是将一个文本(无论中文、英文还是混合)切分成模型词汇表(Vocabulary)中存在的、更小的基本单位(Token)。这些Token可能是完整的常见词(如“apple”),也可能是词根(如“un-”)、词缀(如“-ing”),甚至对于中文,可能是单个汉字、汉字部件或高频汉字组合。例如,“我喜欢吃苹果”在大模型Tokenizer看来,可能被切分成 [“我”, “喜欢”, “吃”, “苹”, “果”] 或 [“我”, “喜”, “欢”, “吃”, “苹果”],这完全取决于它的训练语料和词汇表构建算法。

这个根本性的差异,导致了从工具选型、数据处理流程到问题排查思路的全方位变革。继续用jieba的思维去处理大模型,你会遇到一系列令人困惑的问题:为什么我算的文本长度和模型报的input_ids长度对不上?为什么同样的提示词,换了个说法效果天差地别?为什么模型有时会对某些字符或符号产生“幻觉”?要回答这些问题,我们必须深入理解大模型Tokenizer这个“高科技”的核心机制。

2. 拆解Tokenizer:大模型如何“阅读”世界

要理解为什么不能再用jieba,我们必须先搞懂大模型的Tokenizer到底是什么,以及它是如何工作的。这不仅仅是换一个工具,而是换一套底层的数据表示哲学。

2.1 核心目标:在压缩与信息保留间走钢丝

Tokenizer的首要任务,是将人类可读的文本(字符串)转换为模型可处理的数字序列(Token ID)。这个过程需要平衡几个矛盾的目标:

  1. 压缩性:词汇表不能无限大(通常几万到几十万),否则模型参数会爆炸,查找效率也低。因此,需要将海量可能的字符组合,映射到有限的Token集合中。
  2. 信息保留:转换过程要尽可能保留原文的语义信息。一个糟糕的切分可能会改变意思(比如“南京市长江大桥”的不同切分)。
  3. 泛化能力:要能处理训练时没见过的词(OOV, Out-Of-Vocabulary)。基于词语的切分(如jieba)对此无能为力,而子词切分通过将生词拆成已知子词来解决。
  4. 语言无关性:一个好的Tokenizer应对多种语言(至少是训练语料中的语言)都有不错的效果。

基于词语的分词器(如jieba)主要关注目标2,但在目标1、3、4上存在天然缺陷。而子词切分方案,正是为了综合解决这些问题而诞生的。

2.2 主流子词切分算法:Byte-Pair Encoding与它的朋友们

目前主流大模型的Tokenizer,大多基于以下几种算法或其变种:

Byte-Pair Encoding (BPE):这是GPT系列、ChatGLM等模型广泛采用的算法。它从字符级别开始,统计训练语料中相邻字节对(最初是UTF-8字节,对于中文就是单字)的出现频率,将最高频的配对合并成一个新的“Token”,加入词汇表,并不断迭代这个过程。

  • 举例:假设语料中“喜欢”这个词出现得非常频繁。BPE算法会先看到“喜”和“欢”两个字,统计发现它们紧挨着出现的次数极高,于是在某一轮迭代中,将“喜欢”合并为一个新的Token,加入词汇表。之后,遇到“喜欢”就不再切分成两个字,而是直接用一个Token表示。对于不常见的词,如“饕餮”,如果语料中不常出现,可能就不会被合并,在编码时依然被切分成“饕”、“餮”两个单独的Token(如果它们在词汇表中存在的话)。
  • 优点:能有效地平衡常见词(合并)和生僻词(拆解),压缩率高,泛化能力强。
  • ChatGLM的应用:ChatGLM系列模型的Tokenizer就是基于BPE算法在大量中英文语料上训练得到的。它的词汇表包含了常见的汉字、词语、英文单词、符号等。这也是为什么它处理中文混合文本时相对流畅的原因。

WordPiece:BERT模型使用的算法。与BPE类似,也是通过合并子词来构建词汇表,但它的合并准则不是频率,而是基于概率,看合并后的子词是否能最大程度地提高语言模型的似然概率。它更倾向于合并能显著减少困惑度的单元。

Unigram Language Model:SentencePiece工具支持的一种算法。它从一个很大的种子词汇表开始,通过迭代移除对整体似然度贡献最小的Token来缩减词汇表,最终得到一个固定大小的最优词汇表。这种方法得到的Token可能更“纯粹”。

Byte-level BPE (BBPE):GPT-2/3/4等采用的tiktoken,本质上是一种BPE,但它是在字节(Byte)级别而非字符级别进行的。这意味着它的词汇表基于是256个字节,理论上可以无损编码任何文本(包括任何语言、任何符号、甚至文件二进制数据),彻底解决了OOV问题。对于中文,一个汉字(通常由3个UTF-8字节组成)可能会被切分成多个字节级的Token。

注意:选择哪种算法,决定了词汇表的构成和Token的切分方式。当你看到模型对同一个词给出不同的Token数量时,很可能就是底层算法不同导致的。例如,一个中文成语,在BPE-based的Tokenizer里可能是一个Token,在BBPE里可能是三四个Token。

2.3 词汇表:大模型的“字典”

词汇表(Vocab)是一个从Token(字符串)到ID(整数)的映射表。例如,在ChatGLM的词汇表中,“人工智能”可能对应ID 12345,“的”对应ID 2043。模型训练和推理时,只认识这些ID。

  • 词汇表大小:通常在3万到10多万之间。例如,ChatGLM-6B的词汇表大小是130528。这个数字是模型设计时的一个超参数,需要在表达能力和模型效率之间权衡。
  • 特殊Token:词汇表中除了常规文本Token,还包含一系列具有特殊功能的Token,这是大模型Tokenizer与jieba等传统工具又一个关键区别。常见的特殊Token包括:
    • [CLS],[SEP]:在BERT中用于分类和分隔句子。
    • <|endoftext|>:在GPT系列中表示文本结束。
    • <s>,</s>:在Llama等模型中表示序列的开始和结束。
    • [UNK]:代表未知Token,当输入中出现词汇表中没有的字符时使用(好的Tokenizer应尽量避免产生此Token)。
    • [PAD]:用于将不同长度的序列填充到相同长度,便于批量计算。
    • ChatGLM的特殊Token:ChatGLM有自己的对话格式,包含像[gMASK][sop]等用于控制生成行为的特殊Token。在构造输入时,必须按照其规定的模板加入这些Token,模型才能正确理解指令和上下文。

理解词汇表和特殊Token,是正确与大模型“对话”的前提。用jieba分词然后拼接,完全无法引入这些关键的控制信息。

3. 实战对比:jieba与ChatGLM Tokenizer处理同一文本的差异

理论说了很多,我们直接上代码,看看用jieba和用ChatGLM的Tokenizer处理同一段文本,结果到底有多大差别。这是最直观的“劝退”环节。

假设我们有一段混合中英文的文本,模拟一个典型的提示词(Prompt):

text = “请用Python编写一个函数,计算斐波那契数列的第n项。要求高效,并给出时间复杂度分析。”

3.1 使用jieba进行分词

import jieba seg_list = jieba.lcut(text) print(“jieba分词结果:”, seg_list) print(“分词数量:”, len(seg_list))

输出可能类似于

jieba分词结果: [‘请’, ‘用’, ‘Python’, ‘编写’, ‘一个’, ‘函数’, ‘,’, ‘计算’, ‘斐波那契’, ‘数列’, ‘的’, ‘第’, ‘n’, ‘项’, ‘。’, ‘要求’, ‘高效’, ‘,’, ‘并’, ‘给出’, ‘时间’, ‘复杂度’, ‘分析’, ‘。’] 分词数量: 24

jieba将文本切分成了24个“词语”单元。它识别出了“斐波那契”作为一个专有名词,分开了“时间复杂度”。对于英文“Python”和“n”,它将其视为整体。标点符号也被单独切分出来。

3.2 使用ChatGLM的Tokenizer进行编码

这里以ChatGLM3-6B的Transformers实现为例:

from transformers import AutoTokenizer # 加载ChatGLM3的Tokenizer tokenizer = AutoTokenizer.from_pretrained(“THUDM/chatglm3-6b”, trust_remote_code=True) # 对文本进行编码(Tokenization) encoded_input = tokenizer(text) tokens = tokenizer.convert_ids_to_tokens(encoded_input[‘input_ids’]) print(“ChatGLM Tokenizer Token列表:”, tokens) print(“Token数量:”, len(tokens)) print(“对应的Token IDs:”, encoded_input[‘input_ids’])

输出可能类似于

ChatGLM Tokenizer Token列表: [‘请’, ‘用’, ‘Python’, ‘编写’, ‘一个’, ‘函数’, ‘,’, ‘计算’, ‘斐’, ‘波’, ‘那’, ‘契’, ‘数列’, ‘的’, ‘第’, ‘n’, ‘项’, ‘。’, ‘要求’, ‘高效’, ‘,’, ‘并’, ‘给出’, ‘时间’, ‘复杂度’, ‘分析’, ‘。’] Token数量: 27 对应的Token IDs: [‘请’对应的ID, ‘用’对应的ID, … ‘。’对应的ID]

差异立刻显现

  1. 切分粒度不同:ChatGLM的Tokenizer将“斐波那契”拆成了‘斐’, ‘波’, ‘那’, ‘契’四个独立的Token!而jieba将其视为一个整体。这是因为在ChatGLM训练语料中,“斐波那契”作为一个整体出现的频率可能不够高,未能被BPE算法合并成一个Token。相反,“数列”被合并成了一个Token。
  2. Token数量不同:jieba分出24个单元,ChatGLM Tokenizer分出27个Token。这个数字至关重要,因为大模型是按Token数量计费和消耗计算资源的(对于按量付费的API)或受上下文长度限制的。错误地使用jieba来估算长度,会导致严重的预算或性能误判。
  3. 特殊结构:我们这里只是编码了纯文本。在实际调用ChatGLM时,我们需要用tokenizer.apply_chat_template()或手动按照[Round 1]\n\n问:{用户输入}\n\n答:这样的格式来构造输入,这些格式符号(如[Round 1]\n\n)都会被Tokenizer转换成特定的Token,进一步增加总Token数。这是jieba完全无法处理的。

3.3 关键影响:长度计算与成本控制

这个差异在以下场景中会带来实际问题:

场景一:上下文窗口限制假设你使用的模型上下文窗口是4096个Token。你用jieba统计你的提示词有2000个“词”,以为绰绰有余。但实际上,经过大模型Tokenizer编码后,它可能变成了2800个Token。如果你还要预留生成答案的空间,很可能在生成过程中就触发了长度限制,导致生成截断或失败。

场景二:API调用成本像OpenAI的API是按输入和输出的总Token数计费的。如果你用基于字符或jieba分词的数量去估算成本,会严重低估实际费用。一个精确的预算工具,必须调用官方的Tokenizer(如tiktoken)来计算。

正确的长度计算姿势

# 对于ChatGLM input_ids = tokenizer.encode(text) token_count = len(input_ids) print(f”实际Token数量(不含特殊控制符): {token_count}”) # 对于OpenAI GPT (使用tiktoken) import tiktoken enc = tiktoken.encoding_for_model(“gpt-4”) token_count_openai = len(enc.encode(text)) print(f”GPT-4编码下的Token数量: {token_count_openai}”)

永远不要相信基于空格、字符或传统分词工具的长度估算。

4. 超越分词:Tokenizer在大模型工作流中的核心作用

理解了Tokenizer是什么以及它如何工作之后,我们来看看它在整个大模型应用开发流程中,扮演着哪些jieba无法替代的关键角色。这不仅仅是“编码”和“解码”那么简单。

4.1 提示工程(Prompt Engineering)的基石

提示工程的核心是构造模型能“理解”的输入序列。Tokenizer直接决定了模型“看到”的是什么。

  • 符号与空格敏感:英文中,”word”” word”(前面有个空格)在BPE Tokenizer下可能是完全不同的Token。例如,在GPT的词汇表里,”hello”” hello”(带空格)的ID是不同的。一个提示词中多余或少一个空格,可能导致模型接收到不同的信号。
  • 格式模板:如之前提到的,ChatGLM、Llama Chat、Qwen等对话模型都有严格的对话模板。这些模板由一系列特殊Token和文本结构组成。你必须使用官方的apply_chat_template方法或严格按照文档拼接,才能让模型识别出角色(用户/助手)和对话轮次。手动拼接字符串极易出错。
  • 思维链(Chain-of-Thought)触发:研究发现,在提示词中加入”Let’s think step by step”这类短语,能激发模型的推理能力。这背后的原因之一,是这些短语作为特定的Token序列,在训练数据中常常与高质量的推理过程相关联。Tokenizer确保了这些“魔法短语”被准确地传递。

4.2 微调(Fine-tuning)中的数据预处理

当你用自己的数据对大模型进行微调时,数据预处理的第一步就是Tokenization。

  • 统一长度与填充(Padding):为了进行批量训练,需要将样本处理成等长。Tokenizer提供了padding=True, truncation=True, max_length=512这样的参数,自动完成截断和填充,并生成对应的attention_mask来告诉模型哪些是真实内容,哪些是填充的无效部分。
  • 标签对齐(对于序列标注任务):如果你在做命名实体识别(NER)微调,你的标签是针对原始文本的字符或词语的。但模型处理的是Token。这就出现了对齐问题:一个实体词可能被拆成多个Token。你需要将字符/词级别的标签,精确地映射到Token级别(例如,采用BIO标注法,并将被拆分词的首个Token标为B-,后续Token标为I-)。这个过程必须依赖同一个Tokenizer来完成,用jieba分词的结果去对齐大模型的Token,注定会失败。

4.3 解码(Decoding)与生成控制

模型生成文本是一个循环过程:根据已有的Token ID序列,预测下一个Token的概率分布,然后根据某种策略(如贪婪搜索、束搜索、采样)选择一个Token ID,将其追加到序列中,再继续预测下一个。Tokenizer在这里负责:

  • 将ID转换为可读文本:在每一步生成后,我们需要将新生成的Token ID通过Tokenizer的decode方法转换成字符串,展示给用户。decode方法会智能地处理子词合并,比如将[‘喜’, ‘欢’]正确地还原成“喜欢”。
  • 处理特殊Token:解码过程需要识别停止符(如<|endoftext|>),当生成该Token时,停止生成。不同的Tokenizer定义了不同的停止符。
  • 对数概率计算:在一些高级应用中,我们需要计算生成文本的困惑度(Perplexity)或特定序列的概率。这需要基于Tokenizer切分后的Token序列来计算每个Token的预测概率。

4.4 检索增强生成(RAG)中的向量化

在RAG架构中,需要将文档库切成块(Chunk)并编码成向量(Embedding)存入向量数据库。这个“切块”的最佳单位,不是字符数,也不是jieba的词数,而是Token数

  • 为什么?因为文本的向量表示(Embedding)是由模型产生的,而模型的输入单位是Token。以Token数为准来切分,能保证每个文本块被模型处理时,不会因为意外的长Token(如一个长单词被拆成很多子词)而超出模型输入限制,也能更均匀地分配语义信息。
  • 实操:在构建RAG系统时,应该使用与大模型配套的Tokenizer来计算文本块的Token长度,并以此作为切分和筛选的依据。

5. 避坑指南:从jieba思维迁移到Tokenizer思维的常见问题

习惯了jieba的开发者,在刚接触大模型Tokenizer时,肯定会踩不少坑。下面我总结几个最常见的问题和解决方案,希望能帮你平稳过渡。

5.1 坑一:错误估算输入长度,导致请求失败或成本超标

这是最普遍的问题。如前所述,用字符数/词数估算Token数极不准确。

  • 解决方案
    1. 预计算:在构建提示词或处理用户输入前,先用目标模型的Tokenizer进行编码,获取精确的Token数。
    2. 设置安全边际:如果你的上下文窗口是4096,建议将系统提示词、用户输入和预留的回答空间总和控制在3500-3800左右,为模型内部处理留出缓冲。
    3. 动态截断:对于可能超长的用户输入,实现一个截断逻辑。不是简单地从中间截断字符,而是用Tokenizer编码后,从头部或尾部移除一部分Token,再解码回文本,这样能保证截断后的文本依然是合法的Token序列。

5.2 坑二:手动拼接对话格式,导致模型无法理解角色

很多开发者会像下面这样手动构造ChatGLM的输入:

# 错误示范! bad_prompt = “用户:你好\n助手:” input_ids = tokenizer.encode(bad_prompt, …)

这忽略了ChatGLM需要的特殊控制Token和结构。

  • 解决方案永远使用官方推荐的格式构造方法
# 正确做法(以ChatGLM3为例): from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(“THUDM/chatglm3-6b”, trust_remote_code=True) messages = [ {“role”: “system”, “content”: “你是一个乐于助人的助手。”}, {“role”: “user”, “content”: “你好,请介绍一下你自己。”} ] # 使用apply_chat_template方法,自动添加所有必要的特殊Token和格式 input_ids = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors=“pt”)

对于不支持apply_chat_template的老版本或某些模型,必须严格查阅其模型卡(Model Card)或源码中的tokenization_xxx.py文件,找到对话模板(如”[Round 1]\n\n问:{query}\n\n答:”),并原样使用。

5.3 坑三:处理包含特殊符号或罕见字的文本时出现[UNK]

当文本中含有词汇表外的字符(如某些特殊数学符号、罕见古汉字、新造网络用语)时,Tokenizer可能会将其替换为[UNK](未知Token),导致信息丢失。

  • 解决方案
    1. 选择字节级Tokenizer:像GPT-4使用的tiktoken(BBPE),理论上不会产生[UNK],因为任何字节都能被编码。如果你的应用场景涉及非常规文本,优先考虑使用这类模型。
    2. 文本清洗与规范化:在预处理阶段,将全角符号转为半角,统一繁体字与简体字,过滤或替换掉极端罕见的字符。
    3. 后处理检查:在编码后,检查input_ids中是否包含tokenizer.unk_token_id。如果有,可以对原始文本进行高亮提示或采用回退策略。

5.4 坑四:微调时标签与Token无法对齐

这是序列标注微调中的经典难题。

  • 解决方案:使用Tokenizer提供的offset_mapping功能。在编码时设置return_offsets_mapping=True,它会返回每个Token在原始文本中的(起始位置,结束位置)元组。利用这个映射关系,可以将字符级别的标签精确地分配到对应的Token上。
encoding = tokenizer(text, return_offsets_mapping=True, truncation=True, padding=True) offset_mapping = encoding[“offset_mapping”] input_ids = encoding[“input_ids”] # offset_mapping 是一个列表,每个元素如 (0, 1) 代表第一个Token对应原文本第0到第1个字符(左闭右开) # 根据这个映射,将你的字符级标签列表转换为Token级标签列表

这个过程需要仔细处理被拆分的词(其offset是连续的)以及被[CLS]、[SEP]、[PAD]等特殊Token占用的位置。

从jieba到Tokenizer,不仅仅是工具的切换,更是思维模式的升级。它要求我们从“语言学家”的视角(如何切分出有意义的词),转向“模型架构师”的视角(如何用有限的离散单元最有效地表示无限的语言信息)。理解并熟练运用Tokenizer,是构建高效、稳定、可控的大模型应用的必备技能。下次当你准备处理文本时,第一件事不再是import jieba,而是先问自己:我的目标模型是什么?它的Tokenizer该如何调用?这才是通往“大模型时代”的正确起点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询