二、Transformer之位置编码

前言
在Transformer中使用位置编码表示 , 语义前后关联关系
一、Transformer 原文的位置编码公式
P E ( p o s , 2 i ) = sin ( p o s 10000 2 i / d model ) { \begin{aligned} PE(pos, 2i) &= \sin\left(\frac{pos}{10000^{2i / d_{\text{model}}}}\right) \\ \end{aligned} } PE(pos,2i)=sin(100002i/dmodelpos)
P E ( p o s , 2 i + 1 ) = cos ( p o s 10000 2 i / d model ) {\begin{aligned}PE(pos, 2i+1) &= \cos\left(\frac{pos}{10000^{2i / d_{\text{model}}}}\right)\end{aligned}} PE(pos,2i+1)=cos(100002i/dmodelpos)
其中 ( i ) 表示维度索引(从 0 开始),( d_{\text{model}} ) 是词嵌入的维度。
为了让计算更高效,通常预先计算好一个除数向量:
div_term [ i ] = 1 10000 2 i / d model \text{div\_term}[i] = \frac{1}{10000^{2i / d_{\text{model}}}} div_term[i]=100002i/dmodel1
这样在编码每个位置 ( pos ) 时,只需要计算 ( pos \times \text{div_term}[i] )。
二、代码实现
"""
Transformer1的位置嵌入项链的
"""
import torch
import torch.nn as nn
import math
import math
import copy
from torch.autograd import Variable
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
import torch
import torch.nn as nn
from torch.autograd import Variable
g_device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout=0.1, max_len=5000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
# 初始化 一个size 为max_len(设定的最大长度)* embedding维度 的全部
# 来存放所有小于这个长度位置对应的positional embedding
pe = torch.zeros(max_len, d_model, device=g_device)
print("pe shape:", pe.shape) # pe shape: torch.Size([5000, 512])
print("pe:", pe);
# 生成一个位置下标的tensor矩阵(每一行都是一个位置下标)
# torch.arange(start=0, end, step=1, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
position = torch.arange(0., max_len, device=g_device).unsqueeze(1)
print("PositionalEncoding: d_model={}, max_len={}".format(d_model, max_len));
print("position shape:", position.shape) # position shape: torch.Size([5000, 1])
# 这里幂运算太多,我们使用exp和log来转换实现公式中pos下面要除以的分母(由于是分母,要注意带负号)
div_term = torch.exp(torch.arange(0., d_model, 2, device=g_device) * -(math.log(10000.0) / d_model))
# 根据公式,计算各个位置在各embedding维度上的位置纹理值,存放到pe矩阵中
pe[:, 0::2] = torch.sin(position * div_term)
print("pe after even:", pe);
pe[:, 1::2] = torch.cos(position * div_term)
print("pe after odd:", pe);
# 加1个维度,使得pe维度变为:1×max_len×embedding维度
# (方便后续与一个batch的句子所有词的embedding批量相加)
pe = pe.unsqueeze(0)
# 将pe矩阵以持久的buffer状态存下(不会作为要训练的参数)
self.register_buffer('pe', pe)
def forward(self, x):
# 将一个batch的句子所有词的embedding与已构建好的positional embeding相加
# (这里按照该批次数据的最大句子长度来取对应需要的那些positional embedding值)
x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False )
# return self.dropout(x)
return self.dropout(x)
if __name__ == "__main__":
# ------------------------------------------------------------
# 批大小 (batch_size)
# ------------------------------------------------------------
# 含义:一次前向传播/反向传播中,同时处理的独立序列样本数量。
# 作用:
# - 影响训练速度:更大的 batch 能更好利用 GPU 并行计算,但会消耗更多显存。
# - 影响梯度稳定性:适中的 batch 可使梯度估计更稳定,训练收敛更平滑。
# - 这里是 batch_size = 2,意味着每次迭代会同时输入 2 条独立的序列。
# 示例:若你有 1000 条句子,batch_size=2,则需要 500 次迭代完成一个 epoch。
batch_size = 2
# ------------------------------------------------------------
# 序列长度 (seq_len)
# ------------------------------------------------------------
# 含义:每个序列中包含的 token(词/子词/时间步)的数量。
# 作用:
# - 决定注意力机制的计算范围:Transformer 自注意力的复杂度是 O(seq_len^2 * d_model)。
# - 影响模型能够捕捉的长期依赖关系:更长的序列需要更多位置编码信息。
# - 这里是 seq_len = 10,意味着每条样本有 10 个 token(例如 10 个单词或 10 个时间步)。
# 注意:实际应用中通常需要统一序列长度,通过截断或补零(padding)来实现。
seq_len = 10
# ------------------------------------------------------------
# 模型维度 (d_model)
# ------------------------------------------------------------
# 含义:词嵌入向量以及 Transformer 各子层输出/输入的维度(即隐藏层的宽度)。
# 作用:
# - 决定模型的表示能力:更大的 d_model 可以学习更复杂的特征,但参数量和计算量也会显著增加。
# - 影响多头注意力的头数:通常 d_model 需要能被 num_heads 整除(例如 d_model=512,num_heads=8)。
# - 这里是 d_model = 512,这是 Transformer 原文(“Attention Is All You Need”)中 base model 使用的标准维度。
# - 常见取值范围:256、512、768(BERT-base)、1024(BERT-large)等。
d_model = 512
# 模拟Embedding输出
x = torch.randn(
batch_size,
seq_len,
d_model
).to(g_device)
pe = PositionalEncoding(
d_model=d_model,
dropout=0.1,
max_len=5000
)
output = pe(x)
print(f"x shape: {x.shape}, x:{x}")
print("Input Shape :", x.shape)
print("Output Shape:", output.shape)
三、代码逐行解释
div_term = torch.exp(torch.arange(0., d_model, 2, device=DEVICE) * -(math.log(10000.0) / d_model))
-
torch.arange(0., d_model, 2, device=DEVICE)
生成一维张量:[0, 2, 4, ..., d_model-2],对应公式中的 ( 2i )。 -
-(math.log(10000.0) / d_model)
即 ( − ln ( 10000 ) d model ) (-\frac{\ln(10000)}{d_{\text{model}}}) (−dmodelln(10000)),是一个常数。 -
torch.arange(...) * ...
得到向量: ( − ln ( 10000 ) d model × 2 i ) ( -\frac{\ln(10000)}{d_{\text{model}}} \times 2i ) (−dmodelln(10000)×2i)。 -
torch.exp(...)
对上述向量逐元素取指数:
exp ( − ln ( 10000 ) ⋅ 2 i d model ) = 10000 − 2 i / d model \exp\left(-\frac{\ln(10000) \cdot 2i}{d_{\text{model}}}\right) = 10000^{-2i / d_{\text{model}}} exp(−dmodelln(10000)⋅2i)=10000−2i/dmodel
这正是上面所说的 div_term(分母部分)。
总结
Transformer原理分析:https://chensongpoixs.github.io/artificial_intelligence/Transfomer

1639

被折叠的 条评论
为什么被折叠?



