在软件开发领域,需求管理一直是项目成功的核心关键。随着项目复杂度提升和团队规模扩大,传统依赖文档、邮件和会议的需求管理方式显露出明显短板:版本混乱、协作困难、知识难以沉淀。更值得注意的是,行业内能够真正实现需求结构化、资产化,并结合AI技术进行智能化辅助的系统并不多见。我们公司是一家垂直领域专攻企业级需求与非企业级需求管理的公司, 我们公司的大模型应用连接:http://aipoc.chtech.cn:8880/#/login 欢迎试用。

好的,我们来详细、系统地说明大模型(如GPT、BERT等)中分词器的原理与使用方式。这是一个非常核心的基础概念,理解它对于深入掌握大模型至关重要。


第一部分:分词器的原理

1. 为什么需要分词器?

人类的语言是连续的字符序列(英文是单词加空格,中文是连续的汉字)。但计算机模型(尤其是神经网络)无法直接处理原始文本。它们需要将文本转换为数字形式,即数值化表示(Numerical Representation)

这个过程分为两步:

  1. 分词(Tokenization):将原始文本拆分成模型能够理解的更小单元(tokens)。

  2. 编码(Encoding):将这些tokens转换为对应的整数ID(indices),以便通过模型的嵌入层(Embedding Layer)查找对应的向量。

分词器(Tokenizer)就是负责第一步和第二步的组件。

2. 分词的粒度

分词并非简单地按空格切割。不同的分词器采用不同的策略,主要分为以下几种粒度:

  • 单词级(Word-level):将文本按空格和标点分割成单词。例如:"I love deep learning." -> ["I", "love", "deep", "learning", "."]

    • 优点:直观,每个token都有明确的语义。

    • 缺点

      • 词汇表会非常大(尤其是英语有各种时态、单复数),导致模型参数激增。

      • 无法处理未见过的词(Out-of-Vocabulary, OOV),遇到"unhappiness"这样的词如果词汇表里没有,就无法处理。

  • 字符级(Character-level):将文本拆分成单个字符。例如:"猫" -> ["猫"], "cat" -> ["c", "a", "t"]。

    • 优点:词汇表非常小(几十到几百个字符),几乎不会遇到OOV问题。

    • 缺点

      • 每个token的语义信息非常少,序列长度变得极长,建模长距离依赖关系更困难,计算效率低。

      • 对于中文等语言,单个字符有时就有意义,效果比英文稍好,但序列过长问题依然存在。

  • 子词级(Subword-level)这是现代大模型(如BERT、GPT、T5等)的主流方法。它介于单词和字符之间,将单词拆分成更常见的、有意义的子单元。

    • 核心思想:常用词保留为整体,生僻词拆分成子词(如前缀、后缀、词根等)。

    • 优点

      • 在词汇表大小和序列长度之间取得了完美平衡。

      • 能有效地处理OOV问题。例如,即使模型没见过"unhappiness",它也可以拆成["un", "happiness"]或["un", "happy", "ness"],而这些子词都是模型熟悉的。

3. 主流子词分词算法

现代分词器大多基于以下两种算法或其变种:

a) Byte Pair Encoding (BPE) - 由GPT、GPT-2、GPT-3使用

  • 工作原理:一种数据压缩算法,从字符开始,逐步迭代地合并最高频的字节对。

  • 训练过程

    1. 初始化:将每个单词拆分为字符,并在末尾添加一个特殊的结束符(如</w>)以区分单词边界。

    2. 统计:在所有文本中统计所有相邻符号对的出现频率。

    3. 合并:找到频率最高的符号对(如"e""s"),将它们合并成一个新的符号("es"),并将此新符号加入词汇表。

    4. 重复:重复步骤2和3,直到达到预定的合并次数(即词汇表大小)。

  • 例子:假设"low", "lower", "newest", "widest"出现频繁。

    • 第一轮,最高频对是"e""s",合并为"es"(出现在"newest", "widest"中)。

    • 第二轮,最高频对可能是"es""t",合并为"est"

    • 之后可能会合并"l""o"变成"lo",出现在"low"中。

b) WordPiece - 由BERT使用

  • 与BPE的区别:BPE根据频率合并,而WordPiece根据概率合并,它选择能最大化语言模型概率的单元对进行合并。

  • 合并策略:它不是简单地看频率,而是计算并选择使得score = (freq_of_pair) / (freq_of_first * freq_of_second) 最大的对进行合并。这个公式衡量了两个符号的互信息,即它们有多大概率应该在一起。

c) Unigram - 由SentencePiece工具(T5、ALBERT使用)支持

  • 与BPE/WordPiece相反:它从一个大的种子词汇表(包含所有字符和常见组合)开始,逐步移除最不重要的单元,直到达到目标词汇表大小。

  • 原理:它假设每个token的出现是独立的,然后使用期望最大化(EM)算法来优化一个预定义的损失函数,以决定保留哪些token。

d) Byte-level BPE - 由GPT-2使用

  • 这是BPE的一个变种,关键优势在于它是基于字节(Byte) 的,而不是Unicode字符。

  • 巨大优势:它的词汇表大小是256(0-255),可以表示任何文本,永远不会出现未知字符。任何文本都可以被分解为这256个字节。然后在这个基础上应用BPE算法来创建更大的、更有意义的子词单元。这完美解决了传统BPE对Unicode字符处理不佳的问题。


第二部分:分词器的使用方式

在实际应用中,我们通常使用Hugging Face的transformers库,它为我们封装了各种模型对应的分词器,使用起来非常方便。

1. 核心步骤:分词与编码

一个典型的分词器使用流程包含以下步骤:

python

from transformers import AutoTokenizer
​
# 1. 加载分词器(与模型配套)
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") # 例如:BERT的分词器
# 或 "gpt2", "t5-small", "google/flan-t5-large"
​
# 2. 输入文本
text = "I love natural language processing! 我喜欢自然语言处理。"
​
# 3. 分词与编码(核心方法)
encoded_input = tokenizer(text)
print(encoded_input)
# 输出: {'input_ids': [101, 1045, 2293, 3019, 2653, 2732, 999, 102, 100, 100, 100, 100, 100, 102], 'token_type_ids': ..., 'attention_mask': ...}

tokenizer()方法一次性完成了多个步骤:

  1. 分词(Tokenize):将文本拆分成tokens。

  2. 映射(Map to ID):将每个token转换为其在词汇表中对应的整数ID(input_ids)。

  3. 添加特殊符号(Add Special Tokens):例如,BERT会在开头加[CLS](ID:101),结尾加[SEP](ID:102)。

  4. 生成注意力掩码(Attention Mask)attention_mask,1表示真实token,0表示填充符(padding),用于告诉模型哪些部分需要关注。

  5. 生成token类型ID(Token Type IDs)token_type_ids,用于区分句子对中的两个句子(如问答、自然语言推理任务)。

你可以使用tokenizer.tokenize()来只看分词结果,不看编码:

python

tokens = tokenizer.tokenize(text)
print(tokens)
# BERT输出: ['i', 'love', 'natural', 'language', 'processing', '!', '[UNK]', '喜', '欢', '自', '然', '语', '言', '处', '理', '.'] 
# 注意:中文被拆成了单个字符,因为BERT的词汇表主要是针对英语构建的。

2. 处理不同输入格式

分词器可以灵活处理多种输入情况:

  • 单句tokenizer("Hello world.")

  • 句子对(用于句子关系任务):

    python

    # 方式一:传入两个字符串
    tokenizer("How are you?", "I'm fine, thanks.")
    # 方式二:显式指定
    tokenizer(text="Sentence 1", text_pair="Sentence 2")

  • 批处理(一次处理多个句子):

    python

    batch_sentences = ["Hello world.", "How are you?"]
    encoded_batch = tokenizer(batch_sentences)

3. 填充(Padding)与截断(Truncation)

为了保证批处理中的所有样本长度一致(才能组成一个张量),需要进行填充或截断。

python

batch_sentences = [
    "Hello world.",
    "How are you doing today? I hope everything is going well.",
    "Short one."
]
​
# 动态填充:设置为True,会在组成batch时动态填充到该batch内的最大长度,效率最高。
# 但通常我们在预处理时固定一个最大长度。
encoded_batch = tokenizer(
    batch_sentences,
    padding=True,         # 填充到本batch中最长的序列
    # padding='max_length', # 填充到max_length参数指定的长度
    truncation=True,      # 如果序列超过模型最大长度,则截断
    max_length=10,        # 指定最大长度
    return_tensors="pt"   # 返回PyTorch张量 (也可以是 "tf" for TensorFlow 或 "np" for NumPy)
)
​
print(encoded_batch['input_ids'])
# 假设最大长度为10,输出可能类似于:
# tensor([
#   [101, 7592, 2088,  102,    0,    0,    0,    0,    0,    0], # 第一句被填充
#   [101, 2129, 2024,  2017, 2458, 1029, 1045, 2403,  ... ],     # 第二句被截断
#   [101, 3010, 2028,  102,    0,    0,    0,    0,    0,    0]  # 第三句被填充
# ])

4. 解码(Decoding):从ID回文本

这是编码的逆过程,将模型输出的input_ids转换回可读的文本。

python

# 假设 model_output 是模型生成的一串ID
model_output_ids = encoded_batch['input_ids'][0] # 取batch中的第一个样本
​
# 解码
decoded_text = tokenizer.decode(model_output_ids, skip_special_tokens=True)
print(decoded_text)
# 输出: "hello world."

skip_special_tokens=True会跳过[CLS], [PAD], [SEP]等特殊符号,让输出更干净。


总结与关键点

特性 说明
目的 将原始文本转换为模型可处理的数字ID序列。
主流方法 子词分词(Subword Tokenization),尤其是BPE及其变种(如Byte-level BPE)。
核心组件 词汇表(Vocabulary):一个映射字典,key是token,value是对应的ID。
特殊符号 [CLS](分类)、[SEP](分隔)、[PAD](填充)、[MASK](掩码)、[UNK](未知)。
使用流程 加载分词器 -> 调用tokenizer()进行编码 -> 模型处理 -> tokenizer.decode()进行解码。
重要参数 padding, truncation, max_length, return_tensors

重要提醒

  • 必须使用与模型配套的分词器。用BERT的分词器处理GPT的输入,或者用中文预训练模型的分词器处理英文文本,结果会非常糟糕。

  • 分词是有损压缩。解码后的文本可能与原始输入不完全一致(大小写、空格、标点等可能会被标准化)。tokenizerdecode方法会尽力还原,但无法保证100%一致。

  • 分词是NLP pipeline的第一步,它的质量直接影响到模型性能。理解其原理有助于你更好地调试模型和处理异常情况。

Logo

更多推荐