☰
AI-For-Beginners 课程精讲:从注意力机制到 Transformer 与 BERT 的完整实战指南
2026/10/8 13:49:09 网站建设 项目流程
  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载

注意力机制与 Transformer 是当前自然语言处理(NLP)领域最核心的技术基石,也是本课程 NLP 章节 中承上启下的关键一课。本文以 第 18 课《注意力机制与 Transformer》 为主体,结合课程配套的 PyTorch 笔记本 与 TensorFlow 笔记本 源码级细节,系统讲解注意力机制如何解决 RNN 的序列建模缺陷、Transformer 如何通过位置编码与多头自注意力实现并行化训练,以及如何用预训练 BERT 以迁移学习方式完成真实文本分类任务。读完本文,你将掌握注意力矩阵的含义、Transformer 两大核心组件(位置编码与多头自注意力)的底层实现思路,并能够独立在 PyTorch、TensorFlow 和 HuggingFace 三种框架下搭建或微调 Transformer 模型。

从机器翻译说起:序列到序列任务与 RNN 的局限

NLP 领域最重要的任务之一是机器翻译,它是 Google Translate 这类工具的基础。在本课程的语境下,我们关注的不只是翻译,而是更一般的序列到序列(sequence-to-sequence)任务,也被称为句子转换(sentence transduction)——即把一条输入序列映射为一条输出序列。

在 RNN 时代,序列到序列任务由两个循环网络协作完成:

  • 编码器(encoder):把输入序列压缩成一个隐藏状态(hidden state);
  • 解码器(decoder):把这个隐藏状态展开(unroll)成翻译结果。

然而,这种"一条隐藏状态扛到底"的架构存在两个先天缺陷:

  1. 长句记忆困难:编码器网络的最终状态难以记住句子的开头部分,导致模型在长句上的翻译质量明显下降;
  2. 权重均等:序列中所有单词对结果的影响被一视同仁。但现实中,输入序列里特定的词往往对后续输出的影响远大于其他词。

这两个问题正是注意力机制诞生的直接动因。此外,从本课程 第 16 课 RNN 可知,循环网络的本质是逐步传递上下文,这也为后面的并行化困境埋下了伏笔。

注意力机制:为每个输出动态加权输入上下文

**注意力机制(Attention Mechanism)**提供了一种方法:在 RNN 生成每一个输出预测时,对每个输入向量的"上下文影响力"进行加权。它的实现方式是:在输入 RNN 的中间状态与输出 RNN 之间建立"捷径连接"(shortcut)。

具体来说,当生成输出符号 $y_t$ 时,模型不再只依赖编码器的最终状态,而是把所有输入隐藏状态 $h_i$ 都纳入计算,并为每个状态分配一个权重系数 $\alpha_{t,i}$。这就是经典论文中提出的**加性注意力(additive attention)**结构,最早见于 [Bahdanau et al., 2015](该论文也是 Transformer 出现前序列学习最重要的奠基工作之一):

注意力矩阵 ${\alpha_{i,j}}$ 的每个元素代表:输出序列中某个词的生成,在多大程度上受到输入序列中某个特定词的影响。下图展示了一个真实的注意力对齐示例(来自 RNNsearch-50 模型,即引入注意力机制的翻译模型),可以看到"输出词-输入词"之间的对应关系被清晰地刻画出来:

注意力机制几乎支撑起了当下 NLP 的"最先进水平(state of the art)"。但它的引入也带来一个新问题:模型参数量大幅增加,进而引发 RNN 的扩展性(scaling)危机。RNN 难以扩展的关键约束在于:模型的循环本质使得训练时的批处理(batch)与并行化非常困难——序列中的每个元素都必须按顺序依次处理,无法简单地并行计算。课程配套的动画直观展示了编码器-解码器加注意力的工作方式:

正是"注意力机制被广泛采用 + RNN 并行化受限"这对矛盾,直接催生了我们今天熟知并广泛使用的 Transformer 系列模型,从 BERT 到 Open-GPT3 皆是其产物。

Transformer 模型:摆脱 RNN 的顺序束缚

Transformer 的核心思想之一,就是绕开 RNN 的顺序特性,构建一个在训练时高度可并行化的模型。这个目标通过两大设计实现:

  • 位置编码(positional encoding):显式地把 token 的相对位置信息注入模型;
  • 用自注意力机制(self-attention)取代 RNN(或 CNN)来捕捉模式——这正是那篇提出 Transformer 的论文标题Attention is all you need的含义所在。

位置编码 / 位置嵌入(Positional Encoding / Embedding)

为什么需要位置编码?思路可以梳理为四步:

  1. 使用 RNN 时,token 的相对位置由"步数"天然隐式表示,因此无需显式建模;
  2. 一旦切换到注意力机制,模型本身对顺序没有概念,必须显式知道 token 在序列中的相对位置;
  3. 为了获得位置编码,我们在 token 序列旁边附加一条"位置序列",即 $0, 1, 2, \dots$ 的自然数序列;
  4. 把每个 token 的位置(整数)转换成向量后,与 token 的嵌入(embedding)向量混合。

把整数位置转成向量,有两条主流路线:

  • 可训练嵌入(trainable embedding):与 token 嵌入类似,为位置也配备一个嵌入层。这是本课采用的方案——分别对 token 和位置施加嵌入层,得到两个维度相同的嵌入向量,然后直接相加;
  • 固定位置编码函数(fixed position encoding function):即原论文提出的三角函数式固定编码方案。

位置嵌入的最终结果是:每个 token 的表示向量中,同时编码了"原始 token 本身"和"它在序列中的位置"两类信息:

这一思路在配套的 TensorFlow 笔记本 中有完整的可运行实现。因为 Keras 没有内置 Transformer 层,课程自行实现了TokenAndPositionEmbedding层:

class TokenAndPositionEmbedding(keras.layers.Layer): def __init__(self, maxlen, vocab_size, embed_dim): super(TokenAndPositionEmbedding, self).__init__() self.token_emb = keras.layers.Embedding(input_dim=vocab_size, output_dim=embed_dim) self.pos_emb = keras.layers.Embedding(input_dim=maxlen, output_dim=embed_dim) self.maxlen = maxlen def call(self, x): maxlen = self.maxlen positions = tf.range(start=0, limit=maxlen, delta=1) positions = self.pos_emb(positions) x = self.token_emb(x) return x + positions

该层由两个Embedding层组成:一个嵌入 token,一个嵌入位置(位置由tf.range(0, maxlen)生成的自然数序列构成)。两者维度相同的嵌入向量相加后,得到形状为maxlen × embed_dim的"带位置的输入表示"。

多头自注意力:让词与词之间"相互看见"

Transformer 捕捉序列模式的利器是自注意力(self-attention)——本质上是把注意力作用在"输入与输出同为同一序列"上。自注意力让模型能够考虑句子内部的上下文,看清哪些词彼此相关。例如,它能够识别出代词it等**共指(coreference)**关系具体指向哪些词,并据此理解整句语境:

课程进一步指出:Transformer 中使用的是多头注意力(Multi-Head Attention),目的是赋予网络同时捕捉多种不同类型依赖关系的能力,例如:

  • 长程(long-term)与短程(short-term)词关系;
  • 共指关系与其他类型的关系;
  • 不同注意力头可以分别学习不同的词间关系,从而提升下游 NLP 任务的效果。

在 TensorFlow 笔记本 中,Transformer 块(TransformerBlock)的完整实现如下,它把注意力、前馈网络、层归一化与残差连接组合在一起:

class TransformerBlock(keras.layers.Layer): def __init__(self, embed_dim, num_heads, ff_dim, rate=0.1): super(TransformerBlock, self).__init__() self.att = keras.layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim, name='attn') self.ffn = keras.Sequential( [keras.layers.Dense(ff_dim, activation="relu"), keras.layers.Dense(embed_dim),] ) self.layernorm1 = keras.layers.LayerNormalization(epsilon=1e-6) self.layernorm2 = keras.layers.LayerNormalization(epsilon=1e-6) self.dropout1 = keras.layers.Dropout(rate) self.dropout2 = keras.layers.Dropout(rate) def call(self, inputs, training): attn_output = self.att(inputs, inputs) attn_output = self.dropout1(attn_output, training=training) out1 = self.layernorm1(inputs + attn_output) ffn_output = self.ffn(out1) ffn_output = self.dropout2(ffn_output, training=training) return self.layernorm2(out1 + ffn_output)

代码要点解析:

  • MultiHeadAttention(inputs, inputs)中两个inputs相同,即 Q/K/V 都来自同一序列,实现自注意力;
  • 注意力输出先过Dropout,再与原始输入做残差相加(residual connection),随后经LayerNormalization归一化;
  • 归一化结果送入两层的前馈网络(FFN)(Dense(ff_dim, relu)+Dense(embed_dim)),再次残差相加并归一化。

值得注意的是,这里的LayerNormalization与计算机视觉章节讲过的BatchNormalization不同:它独立地对每个训练样本归一化上一层的输出,将其拉回 $[-1, 1]$ 的量级,因此非常适合序列数据这种变长、批大小敏感的场景。

编码器-解码器注意力:两处注意力的分工

在 Transformer 中,注意力出现在两个位置,各司其职:

  1. 输入文本内部的自注意力:用于在源文本内捕捉模式;
  2. 编码器与解码器之间的注意力层:用于完成序列翻译,即让解码器在生成每个目标词时,聚焦到源文本中相关的部分。

编码器-解码器注意力与本节开头介绍的 RNN 注意力机制非常相似——只是被嵌入到了完全可并行化的 Transformer 架构中。下面的动画直观解释了 Transformer 模型中这种逐位置"评估-对齐"的执行过程:

由于每个输入位置都被独立映射到每个输出位置,Transformer 比 RNN 拥有强得多的并行能力,这使人们能够训练出远比 RNN 更大、更具表达力的语言模型——这是现代大规模语言模型能够存在的架构前提。

BERT:用迁移学习榨干预训练模型的潜力

BERT(Bidirectional Encoder Representations from Transformers,双向编码器 Transformer 表示)是一个体量巨大的多层 Transformer 网络:

  • BERT-base:12 层;
  • BERT-large:24 层。

其训练分两个阶段:

  1. 无监督预训练:在海量文本语料(Wikipedia + 书籍)上进行,任务是在句子中预测被掩码(masked)的词;
  2. 有监督微调(fine-tuning):利用预训练阶段获得的对语言的深刻理解,在其他数据集上进行下游任务适配。

这一"预训练 + 微调"的整体流程被称为迁移学习(transfer learning),它让 BERT 乃至今天的大语言模型得以以相对低廉的成本适配各种具体任务:

课程明确指出,Transformer 架构存在大量变体——BERT、DistilBERT、BigBird、OpenGPT3 等都可以被微调;HuggingFace 的 transformers 库 为其中许多架构同时提供了 PyTorch 与 TensorFlow 实现,是本课程实战部分的主要工具。

实战一:用 PyTorch 微调 BERT 完成文本分类

PyTorch 笔记本 演示了如何用预训练 BERT 解决课程一贯使用的AG News 新闻主题分类任务(4 个类别:World、Sports、Business、Sci/Tech,数据集加载与词表构建逻辑见课程工具模块 torchnlp.py)。

第一步:加载数据与 BERT 专用分词器。由于使用预训练模型,必须配套使用其专属分词器:

import torch import torchtext from torchnlp import * import transformers train_dataset, test_dataset, classes, vocab = load_dataset() vocab_len = len(vocab) # 从模型名加载(联网自动下载) bert_model = 'bert-base-uncased' # 或从本地目录加载(目录需含分词器参数、config.json、权重文件等) # bert_model = './bert' tokenizer = transformers.BertTokenizer.from_pretrained(bert_model) MAX_SEQ_LEN = 128 PAD_INDEX = tokenizer.convert_tokens_to_ids(tokenizer.pad_token) UNK_INDEX = tokenizer.convert_tokens_to_ids(tokenizer.unk_token)

tokenizer.encode可以直接把文本转成 BERT 词表对应的 id 序列,例如tokenizer.encode('PyTorch is a great framework for NLP')会输出以[CLS](id 101)开头、[SEP](id 102)结尾的整数列表。

第二步:定制批处理。因为 BERT 使用自己的编码函数,需要实现一个与之前padify类似的填充函数pad_bert:对一个批内的样本逐个用tokenizer.encode编码,取批内最大长度l,再通过torch.nn.functional.pad右端补零,保证同批张量形状一致:

def pad_bert(b): v = [tokenizer.encode(x[1]) for x in b] l = max(map(len, v)) return ( torch.LongTensor([t[0] for t in b]), torch.stack([torch.nn.functional.pad(torch.tensor(t), (0, l-len(t)), mode='constant', value=0) for t in v]) ) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=8, collate_fn=pad_bert, shuffle=True) test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=8, collate_fn=pad_bert)

第三步:加载分类头模型。使用BertForSequenceClassification,它会自动带上适配分类任务的最终分类器(因此加载时会提示"分类器权重未初始化、需要训练"——这正是我们要做的微调):

model = transformers.BertForSequenceClassification.from_pretrained(bert_model, num_labels=4).to(device)

第四步:微调训练。关键实践点:BERT 已充分预训练,因此必须用很小的学习率(笔记本中取lr=2e-5),以免破坏已学到的权重。训练循环中,模型一次前向同时返回loss和网络输出out,用loss.backward()做反向传播,用out.argmax(dim=1)与标签对比计算准确率:

optimizer = torch.optim.Adam(model.parameters(), lr=2e-5) report_freq = 50 iterations = 500 # 想训练更久就调大这个数 model.train() i, c = 0, 0 acc_loss, acc_acc = 0, 0 for labels, texts in train_loader: labels = labels.to(device) - 1 # 把标签归一到 0-3 texts = texts.to(device) loss, out = model(texts, labels=labels)[:2] labs = out.argmax(dim=1) acc = torch.mean((labs == labels).type(torch.float32)) optimizer.zero_grad() loss.backward() optimizer.step() acc_loss += loss acc_acc += acc i += 1; c += 1 if i % report_freq == 0: print(f"Loss = {acc_loss.item()/c}, Accuracy = {acc_acc.item()/c}") c = 0; acc_loss = 0; acc_acc = 0 iterations -= 1 if not iterations: break

笔记本中记录的示例训练日志显示,损失从约 1.13 快速降至 0.26 左右,准确率从 0.585 一路提升到 0.92 以上——这正是"BERT 已经很好理解语言结构,只需微调最终分类器"的直观体现。

第五步:评估。评估循环与训练循环几乎一致,但必须调用model.eval()切换到评估模式(笔记本示例中最终测试准确率约为 0.905)。评估代码片段:

model.eval() iterations = 100 acc = 0 i = 0 for labels, texts in test_loader: labels = labels.to(device) - 1 texts = texts.to(device) _, out = model(texts, labels=labels)[:2] labs = out.argmax(dim=1) acc += torch.mean((labs == labels).type(torch.float32)) i += 1 if i > iterations: break print(f"Final accuracy: {acc.item()/i}")

注意事项(来自课程与笔记本):BERT 模型很大,整个训练耗时较长,且需要较强算力(GPU,最好不止一块)。笔记本中使用的bert-base-uncased还属于"最小的预训练 BERT 之一",更大的模型通常能带来更好的效果。若在本地运行本课程,可先按 5-NLP 章节说明 安装依赖(PyTorch 侧依赖清单见 requirements-pytorch.txt);该章节同时给出了 GPU 内存管理的建议,例如在 TensorFlow 中开启显存按需增长:

physical_devices = tf.config.list_physical_devices('GPU') if len(physical_devices)>0: tf.config.experimental.set_memory_growth(physical_devices[0], True)

实战二:TensorFlow 手写 Transformer 与 BERT 微调

TensorFlow 笔记本 提供了另一条完整路径,依赖清单见 requirements-tf.txt(含tensorflow、tensorflow_datasets、tensorflow_text、transformers等)。

1. 从零搭建小规模 Transformer 分类器。数据使用tfds.load('ag_news_subset')。完成前述TokenAndPositionEmbedding与TransformerBlock两个自定义层后,即可组装完整模型:

embed_dim = 32 # 每个 token 的嵌入维度 num_heads = 2 # 注意力头数 ff_dim = 32 # 前馈网络隐藏层维度 maxlen = 256 vocab_size = 20000 model = keras.models.Sequential([ keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size, output_sequence_length=maxlen, input_shape=(1,)), TokenAndPositionEmbedding(maxlen, vocab_size, embed_dim), TransformerBlock(embed_dim, num_heads, ff_dim), keras.layers.GlobalAveragePooling1D(), keras.layers.Dropout(0.1), keras.layers.Dense(20, activation="relu"), keras.layers.Dropout(0.1), keras.layers.Dense(4, activation="softmax") ])

该模型仅约 66 万参数(笔记本model.summary()显示 Total params: 659,592),用 Adam 优化器在 AG News 子集上即可训练到不错的准确率(示例日志 val_acc ≈ 0.91)。它也是理解"小数据+小模型"与"大模型迁移学习"差异的最佳对照。

2. 用 TensorFlow Hub 接入预训练 BERT。关键实践点:必须使用与原模型训练时完全相同的向量化器。BERT 向量化器返回三个分量:

  • input_word_ids:输入句子的 token 编号序列;
  • input_mask:标记序列中哪些位置是真实输入、哪些是 padding(类似Masking层的掩码);
  • input_type_ids:用于语言建模任务,允许在同一序列中指定两个输入句子。

加载 BERT 特征提取器后,它会返回多个有用的输出:pooled_output(整句的智能语义嵌入,等价于我们手写模型中的GlobalAveragePooling1D输出)、sequence_output(最后一个 Transformer 层的输出)以及encoder_outputs(所有 Transformer 层的输出列表)。然后可以用 Keras 函数式 API 定义端到端分类模型,并把 BERT 权重固定为不可训练、只训练最后的分类器(这样训练参数仅 516 个,模型总计约 478 万参数):

inp = keras.Input(shape=(), dtype=tf.string) x = vectorizer(inp) x = bert(x) x = keras.layers.Dropout(0.1)(x['pooled_output']) out = keras.layers.Dense(4, activation='softmax')(x) model = keras.models.Model(inp, out) bert.trainable = False

3. 解冻 BERT 权重做完整微调。冻结权重训练效果有限(笔记本示例 val_acc ≈ 0.79),课程接着演示了解冻全部权重、用AdamW 优化器 + warmup的规范微调策略——这是 Transformer 微调的标准做法:

from official.nlp import optimization bert.trainable = True epochs = 3 opt = optimization.create_optimizer( init_lr=3e-5, num_train_steps=epochs * len(ds_train), num_warmup_steps=0.1 * epochs * len(ds_train), optimizer_type='adamw') model.compile(loss='sparse_categorical_crossentropy', metrics=['acc'], optimizer=opt) model.fit(ds_train.map(tupelize).batch(128), validation_data=ds_test.map(tupelize).batch(128))

笔记本示例显示,解冻后单轮训练 val_acc 提升至约 0.82;由于 BERT 特征提取计算量大,即使可训练参数不多,训练依然较慢,课程建议可尝试训练 5–10 个 epoch 以获得更好效果。

4. HuggingFace 路线(TF/PyTorch 通用)。笔记本最后展示了更简洁的 HuggingFace 用法:BertTokenizer.from_pretrained('bert-base-uncased')加载分词器,TFBertForSequenceClassification.from_pretrained(bert_model, num_labels=4)加载模型(其summary()显示约 1.095 亿参数,其中分类器仅 3076 个可训练参数)。数据管线通过tf.py_function调用分词器,配合padding='max_length', max_length=MAX_SEQ_LEN, truncation=True完成定长编码,之后即可用常规model.fit训练。

课后实践:挑战与作业

课程的收尾环节给你留下了充分的动手空间:

  • 挑战(Challenge):尝试不同的 Transformer 架构与超参数组合,观察它们对下游任务的影响;
  • 作业(Assignment):完整要求见 assignment.md——使用 HuggingFace 提供的脚本,在其平台上的各类模型中挑选一个进行实验,尝试官方数据集,或从本课程中挑选一个数据集(甚至从 Kaggle 导入自己的数据),观察模型能否生成有趣文本,并产出一份记录实验发现与分析结论的笔记本。

总结

通过本课,你掌握了 NLP 工具箱中两大关键武器:

  1. 注意力机制:通过加权输入上下文解决 RNN 编码器-解码器"记不住句子开头、词权均等"的缺陷,其注意力矩阵 ${\alpha_{i,j}}$ 可视化地刻画了输入与输出词之间的对齐关系;
  2. Transformer 架构:以位置编码注入顺序信息,以多头自注意力替代 RNN 捕捉模式,配合编码器-解码器注意力完成翻译类任务,从而获得 RNN 无法企及的并行化训练能力。

在此基础上,BERT 等预训练模型 + 迁移学习(微调)的范式,让你能够以很小的训练成本获得当前 NLP 的最佳实践效果。正如 PyTorch 笔记本 的结论所指出的:Transformer 模型是当下 NLP 的 SOTA 方案,在做自定义 NLP 解决方案时,它应当是你最先尝试的技术路线;而理解 RNN 等基础原理,依然是构建更高级神经模型不可或缺的功底。除文本分类外,同样的 BERT 技术栈还可以迁移到实体抽取(NER)、问答(QA)等更丰富的任务上——这正是整个 5-NLP 章节 为你铺好的进阶之路。

  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询