笔记的参考教程
在自然语言处理(NLP)的世界里,我们见证了从 Encoder-Only 模型(如 BERT)到 Decoder-Only 模型(如 GPT)的演进。BERT 通过双向编码理解上下文,在理解类任务上表现卓越;GPT 则通过自回归解码生成流畅文本,在生成类任务中大放异彩。
然而,这两种架构都有其局限性。BERT 难以直接用于文本生成,而 GPT 对输入的理解是单向的。为了融合两者的优点,研究者们回归了经典的 Encoder-Decoder 架构,并在此基础上提出了 T5 模型,为 NLP 领域带来了革命性的“大一统”思想。
本文将深入探讨 T5 (Text-To-Text Transfer Transformer) 模型,从其模型结构、独特的预训练任务,到其核心的“大一统”哲学,并附上代码示例,让你彻底搞懂 T5 的魅力。
什么是 T5 的“大一统”思想?
T5 模型最核心、最具影响力的理念是 “万物皆可文本生成”(Text-to-Text)。
这个思想非常简洁而强大:无论是什么 NLP 任务,都将其统一转换为“输入一段文本,输出一段文本”的格式。 这种方法极大地简化了模型设计和应用流程,我们不再需要为不同任务(如分类、问答、翻译)设计不同的模型输出层。
我们来看几个例子:
- 翻译 (Translation):
- 输入:
translate English to French: How are you?
- 输出:
Comment ça va?
- 输入:
- 文本分类 (Text Classification):
- 输入:
classify: This is a great product.
- 输出:
Positive
- 输入:
- 摘要 (Summarization):
- 输入:
summarize: [一段很长的新闻文章]
- 输出:
[文章的核心摘要]
- 输入:
- 问答 (Question Answering):
- 输入:
question: Who created the T5 model? context: The T5 model was created by Google.
- 输出:
Google
- 输入:
通过在输入文本前加上一个明确的 “任务前缀”(如 translate English to French:),模型就能知道当前需要执行什么任务。这种设计不仅让一个模型能处理多种任务,还使其在多任务学习和泛化能力上表现得更加出色。
模型结构:强大的 Encoder-Decoder
T5 采用了经典的 Encoder-Decoder 结构,其编码器和解码器均基于 Transformer 构建。T5处理文本序列的整个过程可以分为四个主要阶段:编码输入 -> Encoder理解 -> Decoder生成 -> 解码输出。
我们将以一个具体的翻译任务为例,来贯穿整个流程。
任务示例:
- 输入文本:
translate English to German: The cat sat on the mat.
- 期望输出:
Die Katze saß auf der Matte.
阶段 1: 编码输入 (Tokenization)
这是数据进入模型的入口。模型不认识文字,只认识数字。Tokenizer 的工作就是将文本翻译成模型能懂的“语言”。
-
输入文本处理:
- 原始输入:
"translate English to German: The cat sat on the mat."
- Tokenizer 会将其切分成一系列的子词(tokens),并添加一个句子结束符
</s>
。 - 切分结果:
['translate', ' English', ' to', ' German', ':', ' The', ' cat', ' sat', ' on', ' the', ' mat', '.', '</s>']
- 然后,它会查询一个巨大的词汇表(Vocabulary),将每个 token 转换成一个独一无二的数字 ID。
- 数字ID序列 (Encoder的最终输入):
[2163, 8, 19, 1254, 10, 6, 2369, 1530, 18, 6, 2372, 5, 1]
(注:这里的数字是示意,非真实ID)
- 原始输入:
-
目标文本处理 (为 Decoder 做准备):
- 在训练时,Decoder 也需要一个输入和一个目标。输入是带起始符的、向右偏移一位的目标文本,目标是原始目标文本。这被称为“Teacher Forcing”。
- 在推理/预测时,Decoder 的初始输入只有一个代表“开始”的特殊 token(在 T5 中通常是 pad token,ID为0)。
- 初始输入 (Decoder的最初输入):
[0]
现在,我们有了两组数字序列,它们将分别进入 Encoder 和 Decoder。
阶段 2: Encoder栈 (The “Understanding” Phase)
Encoder 的唯一目标是:透彻理解输入序列的每一个细节。它会产出一个富含上下文信息的向量序列,我们称之为 encoder_hidden_states
。
数据流如下:
-
输入: Encoder 的输入是阶段1生成的数字ID序列
[2163, 8, ..., 1]
。 -
词嵌入 (Embedding): 模型内部有一个巨大的“查询表”(Embedding Layer),它会将每个数字ID转换成一个高维向量(例如,768维)。现在,我们有了一个由13个向量组成的序列,每个向量代表一个输入 token。
-
位置编码 (Positional Encoding): Transformer 本身无法感知顺序,所以我们需要给每个向量加入位置信息,告诉模型 “The” 是第6个词,“cat” 是第7个词。T5 使用的是一种相对位置编码的变体。
-
进入 Encoder Block (循环 N 次): 数据会流经一堆完全相同的 Encoder Block (比如 T5-small 有6个)。在每一个 Block 中,数据会经历:
a. 多头自注意力 (Multi-Head Self-Attention):
* 目的: 计算输入序列中所有词语之间的关联度。
* 流程: 对于 “cat” 这个词的向量,自注意力机制会计算它和 “translate”, “English”, …, “mat” 等所有其他词的 “关注分数”。如果发现 “sat on the mat” 和 “cat” 关系密切,那么这些词的向量信息就会更多地融入到 “cat” 的新向量中。
* 关键点: 这里的查询(Query)、键(Key)、值(Value)都来源于同一个地方——上一个 Block 的输出。它是在 “自己看自己”,从而理解内部的语法和语义关系。详情见大模型学习入门——Day3:注意力机制
b. 残差连接 & RMSNorm (Add & Norm):
* 将自注意力的输出与该 Block 的输入相加(残差连接),然后用 RMSNorm 进行归一化。这能防止网络过深时梯度消失或爆炸,稳定训练。
RMSNorm (Root Mean Square Normalization)。其数学公式为:
RMSNorm ( x ) = x 1 n ∑ i = 1 n x i 2 + ϵ ⋅ γ \text{RMSNorm}(x) = \frac{x}{\sqrt{\frac{1}{n}\sum_{i=1}^{n}x_i^2 + \epsilon}} \cdot \gamma RMSNorm(x)=n1∑i=1nxi2+ϵx⋅γ其中:
- x x x 是输入向量
- n n n 是向量的维度
- g a m m a \\gamma gamma 是一个可学习的缩放参数
- e p s i l o n \\epsilon epsilon 是一个防止除以零的小常数
c. 前馈神经网络 (Feed-Forward Network):
* 对每个位置的向量进行一次非线性变换,进一步提取特征。可以理解为一个简单的两层全连接神经网络。
d. 残差连接 & RMSNorm (Add & Norm):
* 再次执行 Add & Norm 操作。 -
Encoder 输出: 当数据流过所有 Encoder Blocks 后,我们得到最终的
encoder_hidden_states
。这是一个形状为(序列长度, 隐藏维度)
的矩阵,比如(13, 768)
。这个矩阵就是 Encoder 对原始输入的完整理解,它将被发送给 Decoder 的每一个 Block 作为“参考资料”。
阶段 3: Decoder栈 (The “Generation” Phase)
Decoder 是一个自回归 (auto-regressive) 的生成器。它的目标是一次生成一个 token,直到生成 </s>
结束符为止。这是最核心也最复杂的部分。
我们来模拟生成第一个词 Die
的过程:
-
输入: Decoder 的初始输入是
[0]
(起始符)。同样,它会被转换成一个词向量并加上位置编码。 -
进入 Decoder Block (循环 N 次): 数据流经所有 Decoder Block,在每一个 Block 中,它会经历三件事:
a. 带掩码的多头自注意力 (Masked Multi-Head Self-Attention):
* 目的: 让 Decoder 在生成当前词时,只能看到已经生成的词。
* 流程: 此时输入只有一个词,所以它只能“自己看自己”。当未来生成到第3个词时,这个机制通过一个“遮罩”(Mask)确保它只能看到第1、2个词,不能“偷看”未来的答案。
* 关键点: 这里的 Q, K, V 同样来源于同一个地方——Decoder 上一个 Block 的输出。
b. 残差连接 & RMSNorm.c. 交叉注意力 (Cross-Attention):
* 目的: 这是连接 Encoder 和 Decoder 的核心桥梁。它回答了这个问题:“根据我现在想生成的内容(来自a步),我应该去关注原始输入句子中的哪些部分?”
* 流程:
* 查询 (Query): 来自于 Decoder 的上一步(Masked Self-Attention)的输出。它代表了 Decoder 当前的需求。
* 键 (Key) 和 值 (Value): 全部来自于 Encoder 的最终输出encoder_hidden_states
!
* 效果: Decoder 的 Query 会和 Encoder 输出的所有 Key 计算相似度,从而决定从 Value 中提取哪些信息。比如,当它想生成Die
(The) 时,交叉注意力可能会高度关注输入中的The
和cat
。
d. 残差连接 & RMSNorm.e. 前馈神经网络 & Add & Norm (同Encoder).
-
Decoder 输出: 当起始符
[0]
流过所有 Decoder Blocks 后,我们得到一个最终的输出向量。
阶段 4: 最终解码与循环 (Final Output & The Loop)
-
线性层 & Softmax:
- 阶段3得到的 Decoder 输出向量会被送入一个最终的线性层,这个线性层的输出维度等于整个词汇表的大小(比如32128)。
- 然后通过 Softmax 函数,将这个输出转换成一个概率分布。每个词都有一个概率值,概率最高的那个词就是我们预测的下一个词。
- 预测结果:
Die
(ID: 5851)
-
循环继续:
- 现在,模型已经生成了第一个词
Die
。它会将这个新生成的词的 ID[5851]
添加到 Decoder 的输入中。 - 下一次循环中,Decoder 的输入就变成了
[0, 5851]
。 - 整个阶段3会重新执行一遍,用
[0, 5851]
作为输入,去预测下一个词Katze
。 - 这个过程不断重复 (
[0, 5851, 13795] -> saß
…),直到模型生成</s>
结束符,或者达到预设的最大长度。
- 现在,模型已经生成了第一个词
这样,通过 Encoder 的一次性全面理解和 Decoder 的逐字生成与参考,T5 就完成了从输入文本到输出文本的完整转换。
预训练任务:更智能的“完形填空”
T5 的强大能力源于其在大规模无标注文本上的预训练。它使用的预训练数据集是谷歌自己构建的 C4 (Colossal Clean Crawled Corpus),一个约 750GB 的高质量英文文本数据集。
其预训练任务是对 BERT 的 MLM (Masked Language Model) 的一种改进,通常称为 Span Corruption (跨度损坏)。
它和 BERT 的 MLM 有何不同?
- BERT 的做法: 随机遮盖 15% 的词(Token),并用
[MASK]
标签替换。My name is [MASK] and I live in [MASK].
- T5 的做法: 随机遮盖一段连续的文本(Span),并用一个唯一的哨兵令牌 (sentinel token),如
<extra_id_0>
、<extra_id_1>
等来替换。然后,让解码器按顺序生成所有被遮盖的文本段。
举个例子:
- 原始文本:
Thank you for inviting me to your party tonight.
- 损坏后的输入 (Input):
Thank you <extra_id_0> me to your party <extra_id_1>.
- 目标输出 (Target):
<extra_id_0> for inviting <extra_id_1> tonight <extra_id_2>
这种方式有两个好处:
- 更难的任务: 模型需要预测一整段文本,而不仅仅是单个词,这促使模型学习更好的语言结构和连贯性。
- 固定的输出格式: 无论遮盖了多少内容,目标输出始终是一个清晰的、由哨兵令牌和文本组成的序列,完美契合其 Text-to-Text 的思想。
需要注意: T5在预训练阶段不需要指定任务前缀。
T5 代码实战
理论讲了这么多,让我们用 Hugging Face transformers
库来实际操作一下。下面的 Python 代码展示了如何用同一个 T5 模型完成翻译、摘要和问答任务。
首先,确保你已经安装了必要的库:
pip install transformers sentencepiece torch
代码示例
import torch
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
import os
# 解决连不上huggingface的问题
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
# 1. 加载预训练的 T5 模型和分词器
model_name = 'google-t5/t5-base'
tokenizer = AutoTokenizer.from_pretrained(
model_name
)
model = AutoModelForSeq2SeqLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto"
)
# 封装一个函数来执行T5的文本到文本任务
def run_t5_task(task_prefix, text_input, max_length=128):
"""
使用T5模型执行指定的文本到文本任务。
参数:
- task_prefix: 任务前缀,如 "translate English to German: "
- text_input: 待处理的文本
- max_length: 生成文本的最大长度
"""
# 组合前缀和输入文本
input_text = f"{task_prefix}{text_input}"
# 2. 对输入进行分词 (Tokenization)
input_ids = tokenizer(input_text, return_tensors="pt").input_ids
# 3. 模型生成输出
# T5是Encoder-Decoder模型,使用 .generate() 方法
outputs = model.generate(input_ids, max_length=max_length)
# 4. 解码输出,得到人类可读的文本
decoded_output = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(f"任务: {task_prefix.strip()}")
print(f"输入: '{text_input}'")
print(f"输出: '{decoded_output}'\n")
# --- 任务1: 翻译 (English to German) ---
translate_prefix = "translate English to German: "
english_text = "The house is wonderful."
run_t5_task(translate_prefix, english_text)
# --- 任务2: 文本摘要 ---
summarize_prefix = "summarize: "
long_text = (
"The James Webb Space Telescope (JWST) is a space telescope designed primarily to conduct "
"infrared astronomy. As the largest optical telescope in space, its high resolution and "
"sensitivity allow it to view objects too old, distant, or faint for the Hubble Space Telescope. "
"This will enable investigations in many fields of astronomy and cosmology, such as observation "
"of the first stars and the formation of the first galaxies, and detailed atmospheric "
"characterization of potentially habitable exoplanets."
)
run_t5_task(summarize_prefix, long_text, max_length=60)
# --- 任务3: 语法纠错 (CoLA - Corpus of Linguistic Acceptability) ---
cola_prefix = "cola sentence: "
cola_sentence_correct = "This is a correct sentence."
cola_sentence_incorrect = "This sentence has has a mistake."
run_t5_task(cola_prefix, cola_sentence_correct)
run_t5_task(cola_prefix, cola_sentence_incorrect)
总结
T5 模型通过其“大一统”的 Text-to-Text 思想,成功地将各种复杂的 NLP 任务简化为一个统一的框架。它不仅继承了 Transformer 强大的 Encoder-Decoder 结构,还通过创新的预训练任务学习到了深厚的语言知识。
T5 的出现,不仅在多个 NLP 基准测试中取得了顶尖性能,更重要的是,它为后续的多任务、多模态大模型的发展铺平了道路,证明了通过一个通用接口处理多样化任务的可行性和巨大潜力。理解 T5,就是理解现代大语言模型演进道路上的一个关键里程碑。