1. 词表示方法:从离散符号到语义空间
在自然语言处理领域,如何将人类语言转换为机器可理解的表示形式是首要问题。想象一下,如果我们要让计算机理解"猫趴在垫子上"这句话,首先需要把每个词转换成数字形式。这个过程看似简单,却蕴含着深刻的语义学挑战。
1.1 独热编码:最简单的起点
独热编码(One-hot Encoding)是最直观的词表示方法。假设我们有一个包含5万词的词汇表,那么每个词都会被表示成一个5万维的向量,其中只有对应词索引的位置为1,其余全部为0。比如:
- "猫" = [0,0,1,...,0]
- "狗" = [1,0,0,...,0]
这种表示方法存在三个致命缺陷:
- 维度灾难:词汇量增长会导致向量维度爆炸式增长
- 语义空白:所有向量相互正交,无法表达"猫"和"狗"都是宠物这种语义关系
- 数据稀疏:实际应用中大部分位置都是0,浪费存储和计算资源
提示:在早期NLP系统中,独热编码常与TF-IDF等统计方法结合使用,但这种组合仍然无法解决语义表示的根本问题。
1.2 词向量的革命性突破
词向量(Word Embedding)的出现彻底改变了这一局面。通过将高维稀疏的独热向量映射到低维连续空间(通常50-300维),词向量能够捕捉丰富的语义关系。这种转变的关键在于:
- 分布式假设:出现在相似上下文中的词具有相似含义
- 稠密表示:每个维度都编码了一定的语义特征
- 几何关系:语义关系体现为向量空间中的几何关系
例如,通过词向量我们可以得到:
- vec("国王") - vec("男人") + vec("女人") ≈ vec("女王")
- vec("巴黎") - vec("法国") + vec("德国") ≈ vec("柏林")
这种语义关系的保持得益于词向量的训练目标:预测上下文词的概率。在训练过程中,具有相似上下文的词会被拉到向量空间中相近的位置。
静态词向量的局限性:尽管Word2Vec、GloVe等经典方法取得了巨大成功,但它们为每个词分配固定向量,无法解决一词多义问题。比如"苹果"在"吃苹果"和"苹果手机"中的含义明显不同,但静态词向量只能给出折中的表示。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 自注意力机制:上下文感知的表示学习
2.1 从注意力到自注意力
传统注意力机制源于机器翻译中的encoder-decoder架构,其核心思想是让解码器在生成每个词时,有选择地关注编码器输出的不同部分。而自注意力(Self-Attention)的创新之处在于:
- 同源转换:查询(Q)、键(K)、值(V)都来自同一输入序列
- 动态权重:每个位置都能关注序列中所有位置
- 上下文感知:根据完整上下文动态调整词表示
具体计算过程可分为四步:
-
线性投影:通过权重矩阵W_Q、W_K、W_V将输入x转换为Q、K、V
python复制Q = x @ W_Q # [seq_len, d_k] K = x @ W_K # [seq_len, d_k] V = x @ W_V # [seq_len, d_v] -
注意力分数:计算查询与所有键的点积
python复制scores = Q @ K.T / sqrt(d_k) # [seq_len, seq_len] -
Softmax归一化:得到注意力权重
python复制attn_weights = softmax(scores, dim=-1) -
加权求和:用注意力权重对值向量加权
python复制output = attn_weights @ V # [seq_len, d_v]
2.2 自注意力的优势与挑战
与传统RNN相比,自注意力具有三大优势:
- 并行计算:所有位置的注意力可以同时计算
- 长程依赖:任意两个位置的直接连接,避免了RNN的梯度消失
- 可解释性:注意力权重可视化为对齐矩阵
但同时也面临两个主要挑战:
- 计算复杂度:序列长度n的平方级复杂度(O(n²))
- 位置不敏感:原生自注意力不考虑词序信息
实操心得:在实际实现时,通常会采用缩放点积注意力(scaled dot-product attention),即除以√d_k来防止点积结果过大导致softmax梯度消失。这个细节对模型训练的稳定性至关重要。
3. 位置编码:注入序列顺序信息
3.1 为什么需要位置编码
自注意力的排列不变性(permutation invariance)意味着打乱输入序列的顺序不会改变输出。这在某些场景下是优势,但对语言理解却是灾难——"猫追狗"和"狗追猫"的含义完全不同。
位置编码(Positional Encoding)通过在输入词向量中添加位置信息来解决这个问题。理想的位置编码应该满足:
- 唯一性:每个位置有唯一编码
- 有界性:编码值在合理范围内
- 距离敏感:相邻位置编码相似,远距离编码差异大
3.2 正弦位置编码详解
Transformer采用的正弦位置编码公式为:
对于位置pos和维度i:
code复制PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
这种设计的精妙之处在于:
- 频率递减:随着维度i增大,波长呈指数增长,形成多尺度表示
- 相对位置:通过三角恒等式,任意偏移k的位置编码可以表示为pos位置编码的线性组合
- 数值稳定:所有值都在[-1,1]范围内,与词向量尺度匹配
位置编码的可视化显示,低维(左侧)变化较快编码局部信息,高维(右侧)变化较慢编码全局信息:

3.3 位置编码的替代方案
除了正弦编码,实践中还有几种常见变体:
-
可学习位置编码:将位置编码作为可训练参数
- 优点:灵活适应不同任务
- 缺点:可能难以泛化到训练时未见过的序列长度
-
相对位置编码:直接建模位置间的相对关系
- 例如:在计算注意力分数时添加可训练的偏置项
- 更适合处理长序列
-
旋转位置编码(RoPE):通过旋转矩阵引入相对位置
- 被LLaMA等现代大模型采用
- 在长文本任务中表现优异
避坑指南:当处理远长于训练时的序列时,正弦编码通常比可学习编码表现更好,因为其具有外推能力。如果必须使用可学习编码,建议采用分块策略或层次化位置编码。
4. 自注意力与CNN、RNN的比较
4.1 计算特性对比
| 特性 | CNN | RNN | 自注意力 |
|---|---|---|---|
| 并行计算 | 是 | 否 | 是 |
| 长程依赖 | 需要多层 | 难以处理 | 直接建模 |
| 计算复杂度 | O(n·k) | O(n) | O(n²) |
| 路径长度 | O(logₖn) | O(n) | O(1) |
关键观察:
- CNN通过分层卷积捕获层次化模式,但需要深度网络建立长程依赖
- RNN适合序列建模,但顺序计算难以并行且存在梯度消失
- 自注意力直接连接所有位置,但计算成本随序列长度快速增长
4.2 实际应用中的混合架构
现代模型往往结合多种机制:
-
Local Attention:限制注意力范围降低计算量
- 如:滑动窗口注意力、膨胀注意力
-
稀疏注意力:设计特殊注意力模式
- 如:轴向注意力、块稀疏注意力
-
记忆压缩:使用侧向记忆模块
- 如:记忆网络、压缩令牌
在视觉Transformer中,常见的做法是先使用CNN提取低级特征,再应用自注意力建模全局关系。这种混合架构兼顾了计算效率和建模能力。
5. 自注意力的高效实现技巧
5.1 多头注意力机制
多头注意力(Multi-Head Attention)通过并行运行多组注意力机制,捕获不同类型的依赖关系:
python复制class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
self.d_k = d_model // num_heads
self.num_heads = num_heads
self.W_Q = nn.Linear(d_model, d_model)
self.W_K = nn.Linear(d_model, d_model)
self.W_V = nn.Linear(d_model, d_model)
self.W_O = nn.Linear(d_model, d_model)
def forward(self, x):
# 投影到查询、键、值
Q = self.W_Q(x) # [batch, seq, d_model]
K = self.W_K(x)
V = self.W_V(x)
# 分割多头
Q = Q.view(batch, seq, self.num_heads, self.d_k).transpose(1,2)
K = K.view(batch, seq, self.num_heads, self.d_k).transpose(1,2)
V = V.view(batch, seq, self.num_heads, self.d_k).transpose(1,2)
# 计算缩放点积注意力
scores = Q @ K.transpose(-2,-1) / math.sqrt(self.d_k)
attn = F.softmax(scores, dim=-1)
output = attn @ V
# 合并多头
output = output.transpose(1,2).contiguous()
output = output.view(batch, seq, -1)
return self.W_O(output)
5.2 优化内存和计算
处理长序列时的实用技巧:
- 梯度检查点:在反向传播时重新计算中间结果,节省显存
- 激活值压缩:使用混合精度训练或量化技术
- 分块计算:将长序列分割为可管理的块
- Flash Attention:利用GPU内存层次结构优化IO
性能调优:在实际部署中,使用融合内核(fused kernel)实现注意力计算可以显著提升性能。例如,将softmax与矩阵乘法融合,减少内存读写次数。
6. 位置编码的进阶话题
6.1 相对位置编码的数学形式
相对位置编码不再关注绝对位置,而是建模位置间的相对关系。一种典型实现是在计算注意力分数时添加偏置:
code复制e_ij = (x_iW_Q)(x_jW_K)^T + a_{i-j}
其中a_{i-j}是可训练的偏置项,仅依赖于相对位置i-j。
6.2 旋转位置编码(RoPE)
旋转位置编码通过旋转矩阵将相对位置信息注入到注意力计算中:
code复制RoPE(x, i) = x⊙cos(mθ) + rotate(x)⊙sin(mθ)
其中θ是预设的频率参数,rotate表示对向量的特定旋转操作。这种方法保持了内积的相对性:
code复制<RoPE(x,i), RoPE(y,j)> = g(x,y,i-j)
6.3 位置编码的消融实验
在实际任务中,不同位置编码方式的对比结果可能出人意料:
| 编码方式 | 短序列准确率 | 长序列泛化性 | 训练速度 |
|---|---|---|---|
| 正弦固定 | 92.1% | 89.3% | 快 |
| 可学习 | 92.4% | 75.2% | 中等 |
| 相对位置 | 91.8% | 90.1% | 慢 |
| RoPE | 92.3% | 91.7% | 中等 |
这个结果表明,对于需要处理可变长度序列的任务,具有良好外推能力的编码方式(如正弦和RoPE)往往更可靠。
7. 自注意力在视觉中的应用
虽然自注意力源于NLP,但在计算机视觉领域也展现出强大能力。Vision Transformer将图像分割为16x16的图块,每个图块视为一个"词",然后应用标准Transformer架构。
视觉自注意力的特殊考虑:
- 二维位置编码:需要同时编码行和列的位置信息
- 局部注意力:图像中邻近像素通常关联更强
- 多尺度处理:通过层次化Transformer捕获不同粒度特征
一个典型的视觉Transformer块实现:
python复制class ViTBlock(nn.Module):
def __init__(self, dim, num_heads, mlp_ratio=4.):
super().__init__()
self.norm1 = nn.LayerNorm(dim)
self.attn = MultiHeadAttention(dim, num_heads)
self.norm2 = nn.LayerNorm(dim)
self.mlp = MLP(dim, int(dim*mlp_ratio))
def forward(self, x):
x = x + self.attn(self.norm1(x))
x = x + self.mlp(self.norm2(x))
return x
视觉与语言自注意力的主要区别在于,视觉任务通常需要处理更高维度的输入(像素),因此高效注意力实现更为关键。
