从编码到语义:如何理解Transformer中的Token?

从编码到语义:如何理解Transformer中的Token?

Transformer架构自2017年提出以来,已成为自然语言处理(NLP)领域的基石技术。其核心创新点之一是将输入序列拆解为离散的Token单元,通过自注意力机制实现跨Token的语义关联。然而,Token的本质是什么?它在模型中如何被处理?本文将从编码原理、语义映射、工程实现三个维度展开系统性分析。

一、Token的本质:离散符号与连续向量的桥梁

1.1 Token的原始定义

Token是输入序列的最小语义单元,其形式取决于任务类型:

  • 文本任务:单词、子词(如BPE分词结果)、字符
  • 语音任务:音素、梅尔频谱帧
  • 视觉任务:图像块(如ViT中的patch)
  • 多模态任务:跨模态符号(如文本+图像的联合编码)

以文本为例,句子”Natural Language Processing”经过BPE分词后可能生成:["Natural", "Language", "Pro", "cessing"],其中”Pro”和”cessing”是合并后的子词。

1.2 Token的编码过程

Token需通过嵌入层(Embedding Layer)转换为连续向量:

  1. import torch
  2. import torch.nn as nn
  3. class TokenEmbedding(nn.Module):
  4. def __init__(self, vocab_size, embedding_dim):
  5. super().__init__()
  6. self.embedding = nn.Embedding(vocab_size, embedding_dim)
  7. def forward(self, token_ids):
  8. # token_ids: [batch_size, seq_length]
  9. return self.embedding(token_ids) # [batch_size, seq_length, embedding_dim]

此过程将离散ID映射为d_model维向量(如BERT中d_model=768),实现符号到向量的初始转换。

二、Token在Transformer中的动态处理

2.1 位置编码的必要性

由于自注意力机制本身是位置无关的,需通过位置编码(Positional Encoding)注入序列顺序信息。原始Transformer采用正弦函数生成位置编码:

  1. def positional_encoding(max_len, d_model):
  2. position = torch.arange(max_len).unsqueeze(1)
  3. div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
  4. pe = torch.zeros(max_len, d_model)
  5. pe[:, 0::2] = torch.sin(position * div_term)
  6. pe[:, 1::2] = torch.cos(position * div_term)
  7. return pe

这种确定性编码方式使模型能学习绝对位置与相对位置关系。

2.2 自注意力中的Token交互

在多头注意力层,每个Token的向量通过QKV投影生成查询(Query)、键(Key)、值(Value),并通过缩放点积注意力计算权重:

  1. def scaled_dot_product_attention(Q, K, V, mask=None):
  2. # Q,K,V: [batch_size, num_heads, seq_len, head_dim]
  3. scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(Q.size(-1))
  4. if mask is not None:
  5. scores = scores.masked_fill(mask == 0, float('-inf'))
  6. weights = torch.softmax(scores, dim=-1)
  7. return torch.matmul(weights, V)

此过程允许每个Token动态聚合其他Token的语义信息,实现上下文感知的表示更新。

三、Token的语义演化与模型能力

3.1 浅层与深层的语义差异

研究表明,Transformer不同层对Token的处理存在功能分化:

  • 底层(1-3层):捕捉局部语法特征(如词性、句法角色)
  • 中层(4-6层):聚合短语级语义(如名词短语、动词短语)
  • 高层(7-12层):构建全局语义关系(如指代消解、语义角色标注)

这种分层演化使模型能逐步构建从局部到全局的语义理解。

3.2 特殊Token的作用机制

预训练模型中常引入特殊Token(如[CLS][SEP]<s>)实现特定功能:

  • [CLS]:汇聚全局信息,用于分类任务
  • [SEP]:分隔不同句子,帮助模型识别句子边界
  • <s>:在T5等模型中作为起始符,引导生成过程

以BERT的NSP任务为例:

  1. # 输入示例:["[CLS]", "Hello", "world", "[SEP]", "How", "are", "you", "[SEP]"]
  2. # 模型通过[CLS]的输出判断两句话是否连续

四、工程实践中的Token优化策略

4.1 分词器的选择原则

不同分词策略对模型性能影响显著:
| 分词类型 | 优点 | 缺点 | 适用场景 |
|————————|—————————————|—————————————|————————————|
| 单词级 | 语义明确 | 词汇表大,OOV问题严重 | 资源丰富的语言 |
| 子词级(BPE) | 平衡词汇量与OOV | 可能产生无意义的子词 | 通用NLP任务 |
| 字符级 | 彻底解决OOV | 序列过长,计算效率低 | 形态丰富的语言 |

4.2 序列长度的处理技巧

实际部署中需处理变长序列:

  1. 静态填充:统一填充至最大长度(可能引入冗余计算)
  2. 动态填充:按批次内最长序列填充(需掩码处理)
  3. 分段处理:将长文本拆分为多个片段(需设计重叠机制)
  1. # 动态填充示例
  2. from torch.nn.utils.rnn import pad_sequence
  3. def collate_fn(batch):
  4. # batch: List[Tuple[token_ids, attention_mask]]
  5. token_ids = [item[0] for item in batch]
  6. attention_masks = [item[1] for item in batch]
  7. padded_ids = pad_sequence(token_ids, batch_first=True, padding_value=0)
  8. padded_masks = pad_sequence(attention_masks, batch_first=True, padding_value=0)
  9. return padded_ids, padded_masks

4.3 性能优化方向

  • 量化:将FP32权重转为INT8,减少内存占用
  • 稀疏注意力:限制每个Token的关注范围(如局部窗口、滑动窗口)
  • 知识蒸馏:用大模型指导小模型学习Token表示

五、前沿探索:Token的扩展应用

5.1 多模态Token化

在视觉-语言模型中,图像与文本需统一Token化:

  • 图像Token:将图像切分为固定大小的patch(如ViT的16x16)
  • 跨模态Token:引入可学习的模态嵌入(如FLAMINGO中的<image>Token)

5.2 动态Token生成

某些模型(如DALL-E 2)采用离散VAE将图像压缩为离散Token序列,实现文本到图像的生成:

  1. 文本: "A cat sitting on a mat"
  2. 文本Token序列
  3. 扩散模型生成图像Token序列
  4. 解码为像素图像

总结与最佳实践

理解Transformer中的Token需把握三个核心维度:

  1. 编码维度:选择合适的分词策略与嵌入方式
  2. 处理维度:优化位置编码与注意力机制
  3. 语义维度:设计合理的预训练任务与特殊Token

实践建议

  • 对于资源有限场景,优先采用子词分词(如BPE)
  • 长序列处理时,考虑稀疏注意力或分段机制
  • 多模态任务中,设计统一的Token空间与模态标识
  • 监控Token级别的注意力分布,诊断模型过拟合/欠拟合

通过系统掌握Token的编码、处理与语义演化机制,开发者能更高效地设计Transformer架构,并在具体任务中实现性能与效率的平衡。