(三)一文看懂 Transformer:从 Token 到预测下一个字

本文用 「春眠不觉」→ 补全「晓」 走通 Transformer 从输入到输出的完整链路。文首流程图用 4 字输入演示各步;后文示例多用整句 「春眠不觉晓」 。示例模型为 uer/gpt2-chinese-cluecorpussmall(GPT-2 结构,层数、维度以 config.json 为准)。

Transformer 整体框架

下面用 「春眠不觉」→ 补全「晓」 串起全流程(层数、维度随具体模型而变):

css 复制代码
    输入:"春眠不觉"
        │
        ▼
    ┌─────────────────────────────────────────┐
    │ 1. Tokenize(分词)                      │
    │    ["春", "眠", "不", "觉"]              │
    └─────────────────────────────────────────┘
        │
        ▼
    ┌─────────────────────────────────────────┐
    │ 2. 编码(Token → ID)                    │
    │    [102, 235, 301, 189]                 │
    └─────────────────────────────────────────┘
        │
        ▼
    ┌─────────────────────────────────────────┐
    │ 3. Embedding(如 768 维)                │
    │    查表得 [v1, v2, v3, v4]               │
    └─────────────────────────────────────────┘
        │
        ▼
    ┌─────────────────────────────────────────┐
    │ 4. 位置编码(Position Embedding)        │
    │    与 vi 相加:输入i = vi + pi           │
    └─────────────────────────────────────────┘
        │
        ▼
    ┌─────────────────────────────────────────┐
    │ 5. Decoder × N 层(如 12 层,视模型而定)  │
    │    • Masked Self-Attention              │
    │    • Feed-Forward Network               │
    │    • 残差连接 + LayerNorm                │
    │    (Encoder-Decoder 架构才有            │
    │      Cross-Attention,纯 GPT 无此项)    │
    └─────────────────────────────────────────┘
        │
        ▼
    ┌─────────────────────────────────────────┐
    │ 6. Linear(输出层 / LM Head)            │
    │    隐状态 → 词表大小(如 21128)→ logits  │
    └─────────────────────────────────────────┘
        │
        ▼
    ┌─────────────────────────────────────────┐
    │ 7. Softmax                               │
    │    logits → 词表上的概率分布             │
    └─────────────────────────────────────────┘
        │
        ▼
      概率示意(词表维度,非句中第几位):
      [0, 0, ..., 0.92, ..., 0]  →  「晓」对应下标概率最高
        │
        ▼
      输出 Token:"晓"
      最终句子:"春眠不觉晓"

后文各节按 1 → 7 的顺序展开说明。

文本转换为Token

把一段文字变成一组 Token ,这一过程叫 词元化(Tokenization)

Transformer 处理自然语言的第一步:模型只能对数字做矩阵运算,不能直接处理汉字或英文,因此须先把文本切成离散单元,再映射为整数编号。

分词器做什么?

对应上文流程图的 第 1、2 步 (Tokenize → 编码),由 Tokenizer(分词器) 完成:

人类 模型
输入 「春眠不觉晓」 同一句经词元化后
实际使用 直接理解语义 [101, 2345, 6789, ...] 这样的 Token ID 序列

送进模型的是 ID 序列,不是原始字符串。分词器主要做两件事:

  1. 切分:把字符串拆成 Token 列表;
  2. 编码:把每个 Token 换成词表(Vocabulary)里的整数 ID。

训练与推理须用同一套词元化规则;换模型通常也要换对应的分词器,否则 ID 与词义会对不上。

三种词元化粒度:字、词与子词

模型里的 Token 是词元化后交给大模型的最小单元,按切分方式,常见有三类:

粒度 做法 优点 缺点
字/字符级 每个汉字、字母单独成 Token 词表小,罕见词也能表示 序列很长,语义片段被拆碎
词级 按词典切分为完整词语 符合直觉 词表巨大,未登录词(OOV)难处理
子词级(Subword) BPE、WordPiece、SentencePiece 等 词表可控,能兼顾常见词与生僻词 规则稍复杂,需专门学习

当前主流大模型(GPT、BERT、LLaMA 等)几乎都采用子词分词:常见词整段保留,生僻词拆成更小的片段。

从句子到 ID 序列

以句子 「春眠不觉晓」 为例(实际切分结果因模型而异):

css 复制代码
原文:     春眠不觉晓
Token:   [春, 眠, 不, 觉, 晓]        ← 词元化后的字符串片段
Token ID: [2345, 1890, 45, 67, 891]  ← 词表中的整数编号

模型后续接收的是 Token ID 序列 ,而不是原始字符串。解码时再用分词器的 decode 把 ID 还原成文字。

特殊 Token

词表里除了普通字词,还预留了一些控制用的符号,例如:

符号(示例) 常见含义
[CLS] BERT 等用于分类/句首聚合(GPT 生成模型通常不用)
[SEP] 句子分隔(多用于 BERT 双句输入)
`` 补齐到同一长度(批处理时)
[MASK] 掩码语言模型训练时遮盖的位置
<endoftext> GPT 类模型生成结束

用 Hugging Face 亲手试一次

AutoTokenizer 加载与模型配套的分词器:

python 复制代码
    from transformers import AutoTokenizer

    model_name = "uer/gpt2-chinese-cluecorpussmall"
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    text = "春眠不觉晓"
    # 编码:文本 → Token ID
    encoded = tokenizer(text)
    print("Token ID:", encoded["input_ids"])

    # 看每个 ID 对应什么片段(便于理解子词切分)
    tokens = tokenizer.tokenize(text)
    print("Token 片段:", tokens)

    # 解码:Token ID → 文本
    decoded = tokenizer.decode(encoded["input_ids"])
    print("还原文本:", decoded)

典型输出形态(具体数字以本机为准;GPT-2 中文多为按字切分,一般不含 BERT 的 [CLS]/[SEP]):

less 复制代码
Token 片段: ['春', '眠', '不', '觉', '晓']
Token ID:   [2345, 1890, 45, 67, 891]

要点小结:

  • Tokenizer 与模型成对使用from_pretrained 同一模型名,会下载 vocab.txttokenizer_config.json 等(见下一篇文章模型目录说明)。
  • 词表大小(如 21128)决定 Embedding 矩阵行数,即模型最多认识多少个不同 Token。

Token转换为向量

Embedding(嵌入) 把每个 Token ID 映射为一条 hidden_size 维向量(如 768 维)。实现上是查表:矩阵 [词表大小, hidden_size],每个 ID 取一行;n 个 Token 得到 [n, hidden_size]

用代码看形状

python 复制代码
    import torch
    from transformers import AutoModel, AutoTokenizer

    model_name = "uer/gpt2-chinese-cluecorpussmall"
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModel.from_pretrained(model_name)

    text = "春眠不觉晓"
    inputs = tokenizer(text, return_tensors="pt")
    input_ids = inputs["input_ids"]
    print("Token ID 张量:", input_ids.shape)

    # 取出模型的词嵌入层
    embed_layer = model.get_input_embeddings()
    token_vectors = embed_layer(input_ids)
    print("Token 向量张量:", token_vectors.shape)
    print("hidden_size:", model.config.hidden_size)

hidden_size 为 768、分词后序列长度为 5,则 token_vectors.shape 多为 torch.Size([1, 5, 768])

维度 含义
第 1 维 1 batch,一次 1 条句子
第 2 维 5 序列中 5 个 Token
第 3 维 768 每个 Token 一条 768 维向量

要点小结:

  • Embedding 把离散 ID 变成连续向量,是第一个可学习的语义表示层。
  • 走完整 Transformer 后,同一词在不同上下文里向量还会被后续层改写;本节是刚查表时的初始向量。

向量中加入位置信息

Embedding 只按 Token ID 查表,同一字在不同位置查到的向量相同------还须知道顺序 。例如「 咬人」与「人咬」,词一样,顺序不同,意思相反。

怎么做:位置向量与 Token 向量相加

css 复制代码
    输入表示 = Token Embedding + Position Embedding
方式 说明
正弦位置编码 原始论文用 sin/cos 生成,不增加可训练参数
可学习位置编码 GPT-2、BERT:每个位置一行可训练向量

相加后,同一 Token 在不同位置上的输入向量不同,后续自注意力才能区分先后。

用代码看一眼(GPT-2)

GPT-2 使用可学习位置编码,内部大致是「词嵌入 + 位置嵌入」:

python 复制代码
    from transformers import AutoModel, AutoTokenizer

    model_name = "uer/gpt2-chinese-cluecorpussmall"
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModel.from_pretrained(model_name)

    inputs = tokenizer("春眠不觉晓", return_tensors="pt")
    # 模型 forward 时会自动把 wte(词嵌入)与 wpe(位置嵌入)相加
    outputs = model(**inputs)
    print(outputs.last_hidden_state.shape)  # [1, 序列长度, hidden_size]

要点小结:

  • Embedding 管「是什么词」,位置编码管「在第几位」
  • 没有位置信息,模型无法稳定区分语序。

深度理解语义

「春」在「春眠不觉晓」和「春色满园」里含义不同------须结合整句其它词 更新每个位置的表示。核心是 自注意力(Self-Attention) :每个 Token 按相关程度汇总句中信息。

Decoder 生成时还用 掩码自注意力(Masked Self-Attention) :当前位置不能看见后面的词,避免「偷看答案」。

自注意力 :以 「春」 为例(见下图),按 Q / K / V 三步融合全句------

  1. 算相关度 :「春」的 Q 与每个词的 K 比对;
  2. 得权重:Softmax 归一,权重和为 1;
  3. 加权求和 :对 V 加权,得到 春'
符号 含义(通俗理解)
Q 当前词「想找什么」
K 每个词「能提供什么匹配信息」
V 每个词「实际贡献的语义内容」

句中每个词都会走一遍(也会看自己),因此叫「自」注意力。

一层 Block 与多层堆叠 :把 多头注意力(MHA)前馈网络(FFN) 组成一层 Transformer Block (各带一次 Add & Norm),再 叠 N 层 ------对应文首第 5 步与整体架构图(1-1.png)里 Decoder 的 Nx

多头 是同一层内多路并行,多层 是同一种 Block 重复很多遍(二者含义不同)。叠完后得到 last_hidden_state ,供下文映射到词表。本文为 仅 Decoder 的 GPT;理解类常用 Encoder(如 BERT),翻译等用 Encoder-Decoder。

python 复制代码
    outputs = model(**inputs)
    hidden = outputs.last_hidden_state   # [1, 序列长度, hidden_size]

生成"下一个字"的权重分布

对应文首流程图 第 6 步 。最后一层每个位置是一条 hidden_size 维向量,经 Linear(LM Head) 映射到整个词表 ,得到每个候选 Token 的 logits(未归一化的得分)。

python 复制代码
    from transformers import AutoModelForCausalLM, AutoTokenizer

    model_name = "uer/gpt2-chinese-cluecorpussmall"
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForCausalLM.from_pretrained(model_name)

    inputs = tokenizer("春眠不觉", return_tensors="pt")
    outputs = model(**inputs)
    logits = outputs.logits              # [1, 序列长度, vocab_size]
    print("logits 形状:", logits.shape)
    print("词表大小:", model.config.vocab_size)
    # 通常取最后一个位置的 logits,用于预测「下一个字」
    next_logits = logits[0, -1, :]

将权重分布转换成概率分布

对应文首 第 7 步 。对 logits 做 Softmax ,得到和为 1 的概率 ;再 取概率最大 (贪心)或按概率采样,得到下一个 Token。

以补全 「晓」 为例:词表中「晓」对应位置概率最高,即输出该 Token;再拼回输入,继续预测,直到遇到结束符------这叫 自回归生成

python 复制代码
    import torch

    probs = torch.softmax(next_logits, dim=-1)
    pred_id = torch.argmax(probs).item()
    print("预测 Token ID:", pred_id)
    print("预测片段:", tokenizer.decode([pred_id]))

也可用 model.generate 自动完成多轮预测(见 下一篇文章)。

要点回顾

步骤 做什么 本文对应
1--2 词元化 → Token ID Tokenizer
3 ID → 向量 Embedding
4 加位置信息 Position Embedding
5 N 层 Decoder 理解上下文 自注意力 + Block × N
6 映射到词表得分 LM Head → logits
7 得分 → 概率 → 下一个字 Softmax + 采样/贪心

「春眠不觉」 经上述链路,模型在词表上给 「晓」 较高概率,补全为 「春眠不觉晓」。下一章节中用 Hugging Face 完整跑通加载、生成与本地部署。

相关推荐
如去3 小时前
第十篇《AI与环境保护:从“末端治理”到“全链条防控”的技术突围》
人工智能
1368木林森3 小时前
RAG查询改写①【第九篇】:工业级Query全链路优化,抖音深度扩写生产方案
人工智能·rag
Agent产品评测局3 小时前
本地化部署vs云端部署,制造业AI Agent方案对比:2026企业级自动化选型全景解析
运维·人工智能·ai·chatgpt·自动化
UXbot3 小时前
评审前2小时完成页面布局:前端AI工具快速出图工作流
前端·人工智能·交互·产品经理·web app·ui设计
@蔓蔓喜欢你3 小时前
React Server Components实战:提升首屏渲染性能
人工智能·ai
MacroZheng3 小时前
IDEA + 阿里 Qoder = 王炸!
java·人工智能·后端
我是宝库3 小时前
SCI论文可不可以先用免费系统检测重复率和AI率?
人工智能·aigc·英文论文·sci论文·论文查重·turnitin系统·ithenticate
zzhongcy3 小时前
Flyway 数据库版本管理工具使用指南
数据库·人工智能
数智工坊3 小时前
【SigLIP论文阅读】:重新定义视觉-语言预训练的损失函数——VLA模型的“语言理解“基石
论文阅读·人工智能·算法·计算机视觉·语言模型