2. Tokenization在NLP中的位置:从文本到模型输入的流程

做 NLP 项目久了,我有一个很深的体会:Tokenizer 才是真正的主角。不管你后面用 Transformer 还是 LSTM,第一步走错了,后面全白搭。

今天咱们就聊聊,一段原始文本是怎么一步步变成模型能吃的数字的。说白了,就是让你看清 Tokenization 在整个流水线里到底站在哪个坑位上。

2.1 从字符串到张量,中间隔了几道坎?

你先想想看:模型只认识数字,对吧?那“我爱北京天安门”这句话,怎么变成一堆数字?

嗯,大致得经过这么几步:

  1. 原始文本 —— 用户输入的那一串字符。
  2. 文本清洗 —— 去掉乱七八糟的符号、统一大小写、处理空格。
  3. Tokenization —— 把字符串切成一个个小片段(词、子词、或者字符)。
  4. 词表映射 —— 把每个小片段替换成词表里的编号(ID)。
  5. Padding / 截断 —— 对齐长度,搞成固定长度的序列。
  6. 输入向量 —— 查 Embedding 表,变成模型能往下传的高维向量。

我当年刚入门的时候,以为只要把单词塞给模型就行。结果第一个项目就翻车了——中文句子没分词,直接按字输入,模型效果奇差。后来才明白,Tokenizer 的质量直接决定了模型的上限。

下面这个表格帮你快速对齐每个步骤的作用:

步骤 输入 输出 关键动作
文本清洗 原始字符串 干净字符串 去除HTML标签、统一Unicode、转小写
Tokenization 干净字符串 列表 按规则切分(词/子词/字符)
词表映射 列表 ID列表 查词汇表,OOV处理
Padding/截断 ID列表 定长ID序列 补0或裁剪到max_len
嵌入层 定长ID序列 Embedding张量 查表得到向量

2.2 Tokenization 到底站在流水线的哪个位置?

如果画一条完整的 NLP 处理流水线,Tokenization 差不多处于 中间偏前 的位置。它的前面是文本预处理,后面是特征映射。但你别小看它——它其实是个分水岭。

我个人的习惯是,先确定用哪种 Tokenization 策略,再反过来决定预处理该做到什么程度。举个例子:如果你用 BPE 子词分词,那清洗时保留大小写可能就没必要;如果你用整词分词,那大小写归一化就得好好做。

这里要注意一个常见的坑:Tokenization 不是单纯的切分,它还在帮你定义“词汇”的边界。你想想看,模型看到的“I”、“love”、“you”和我看到的“Iloveyou”是完全不同的信息结构。切得好,模型才会学得顺。

我曾经踩过的坑:在做一个多语言翻译项目时,我直接用空格分词处理所有语言。结果日语和中文完全崩了。后来才意识到,Tokenization 必须针对语言特性选策略——中文需要提前分好词或使用子词,日文则需要处理没有空格的问题。千万别迷信“通用分词器”。

2.3 可视化流程:从文本到模型输入

下面这张图把整个过程串起来了。你看一眼就能明白 Tokenization 在哪个位置,前后分别是啥。

从原始文本到模型输入的完整流程 原始文本 “我爱NLP” 文本清洗 去噪、归一化 Tokenization 词/子词/字符 + 词表映射 Padding / 截断 对齐长度 模型输入 [ID₁, ID₂, …, IDₙ] OOV → 特殊 [UNK] 或回退到字粒度
一个小窍门:很多新手会忽略“Padding/截断”这一步对模型效果的影响。我做过实验,同一个模型,把 max_len 从 64 调到 128,准确率能涨 5 个点。但也别贪心——序列太长,显存就爆了。找个平衡点,一般看训练数据里 95% 的句子长度就够了。

2.4 三个绕不开的概念

你一旦开始接触 Tokenization,下面这三个词就会天天见。我先帮你理清楚:

  • 词表(Vocabulary):模型能认识的所有 的集合。常见大小是 3万~10万。太小了 OOV 多,太大了算力受不了。
  • OOV(Out of Vocabulary):词表里没有的 。处理方式一般有两个——要么标为 [UNK] 特殊 ,要么拆成更细的子词(能认出部分信息)。我推荐后者。
  • Padding:把短的序列补齐到固定长度。通常补 0 或者 [PAD] 的 ID。注意——Padding 的位置会影响 Attention,所以 Transformer 里都要加 attention mask 告诉模型别关注 padding 位。

我曾经在一个文本分类项目里,忘了处理 OOV,结果模型看到生僻词就直接输出 [UNK],测试集准确率暴跌 15%。后来改用 BPE 子词分词,OOV 几乎消失,效果立马上来了。所以啊,选 Tokenizer 时一定得关注它对 OOV 的处理策略。

2.5 代码看一眼就懂

说那么多抽象的东西,不如直接看代码。下面这段 Python 演示了最简单的手写单词 ID 映射流程:

# 手工做一个迷你词表
vocab = {"我": 1, "爱": 2, "NLP": 3, "[PAD]": 0, "[UNK]": 4}

def tokenize_and_encode(text, vocab, max_len=5):
    # 简单按空格切分(仅演示)
    tokens = text.split()
    ids = []
    for  in tokens:
        if  in vocab:
            ids.append(vocab[])
        else:
            ids.append(vocab["[UNK]"])
    # Padding / 截断
    if len(ids) < max_len:
        ids += [vocab["[PAD]"]] * (max_len - len(ids))
    else:
        ids = ids[:max_len]
    return ids

# 试试
print(tokenize_and_encode("我 爱 NLP", vocab))
# 输出: [1, 2, 3, 0, 0]

print(tokenize_and_encode("我 爱 深度学习", vocab))
# 输出: [1, 2, 4, 0, 0]   (因为“深度学习”不在词表里,变成[UNK])

看到没?Tokenization 就是把非结构化的文字,变成了结构化的、固定长度的整数序列。后面模型就能拿这些整数去查 Embedding,再扔进 Transformer 里做计算了。

总结一下本章的核心:

  • Tokenization 处于文本预处理和数值映射之间的关键衔接点。
  • 它决定了模型看到的“词汇”粒度(词/子词/字符),直接影响 OOV 和语义保留。
  • 从原始文本到模型输入,必须经过:清洗 → Tokenization → 词表映射 → Padding/截断。
  • 实操中,选对 Tokenizer 比调模型超参数更值——我这两年项目经验证明了这一点。