摘要:
最近要结合Transformer 做一个项目预言, 这篇主要结合现有的网上Transformer 三种不同的代码结构,总结一下
并行计算:
- 传统的循环神经网络(RNN)在处理序列数据时,每个时间步都需要等待前一个时间步的计算结果,因此无法进行高效的并行计算。
- 而Transformer模型中的自注意力机制使得每个时间步的计算只依赖于输入的向量,因此可以实现完全并行的计算,大大提高了计算效率。
捕捉长距离依赖:
- RNN在处理长序列时容易出现梯度消失或爆炸的问题,导致难以捕捉长距离的依赖关系。
- Transformer模型中的自注意力机制可以直接计算任意两个位置之间的依赖关系,避免了梯度传播的问题,能够更好地捕捉长距离的依赖关系。
全局上下文建模:
- RNN在每个时间步只能看到之前的信息,无法同时考虑整个序列的上下文。
- Transformer模型通过自注意力机制,可以同时考虑所有位置的信息,从而更全面地建模上下文关系,提高了模型对序列的理解能力。
可扩展性强:
- Transformer模型的结构非常灵活,可以根据具体任务的需要对其进行调整,如增加或减少层数、调整注意力机制的头数等,使得模型具有更好的可扩展性。
预训练效果好:
- Transformer模型在自然语言处理等任务上的预训练效果非常好,例如BERT和GPT等模型在各种NLP任务上取得了卓越的效果,甚至在某些方面超过了人类水平。
无需特征工程:
- 在传统的机器学习模型中,需要进行繁琐的特征工程来提取有用的特征。
- 而Transformer模型可以直接处理原始数据,无需进行特征工程,从而减少了人工干预的成本。
处理多模态数据:
- 除了在自然语言处理领域表现出色外,Transformer模型还可以处理图像、音频等多模态数据。通过对多模态数据进行融合,可以进一步提高模型的表现。
支持迁移学习:
- Transformer模型具有很好的可迁移性,可以通过在大规模数据上进行预训练,然后在其他任务上进行微调,从而快速适应新的任务和数据集。
一 目录:
- Transformer 架构
- 输入层
- Encoder
- Decoder
- 学习率
- PyTorch参考代码
一 Transformer 模型架构
由Encoder 和 Decoder 组成,
Encoder 由N个相同的Sublayer 堆叠而成
Decoder 由N个相同的Sublayer 堆叠而成
二 输入层
由两个部分组成: token Embedding , Positional Embedding
2.1 TE(token Embedding)
输入: Input token,形状为
输出: 通过编码得到词向量,
2.2 Positional Encoding(PE)
RNN, LSTM 依次输入不同时刻的向量,自带位置信息.Transformer 需要通过
位置编码提供位置信息PE
最后得到
三 Encoder
Encoder 由N个相同的 Sublayers堆叠而成
每个sublayer 由两部分组成:
Sublayer1: 多头注意力机制(MHA)+Add&Norm
Sublayer2: 前馈全连接网络(FFN)+ Add&Norm
3.1 Add&Norm
step1: Residual connection 残差连接
step2: LayerNorm 层归一化
3.2 MHA(Multihead Attention)
- 信息多样性:多头并行处理能够捕捉到输入数据的不同方面或特征。每个头部(head)关注的信息角度不同,这增加了模型对复杂关系的理解能力。这种多样性有助于模型在面对复杂数据时,能够从多个角度进行解析和处理,从而提高预测的准确性。
- 缓解维度灾难:通过将高维空间分解成多个低维空间,多头注意力机制减少了每个头部需要处理的数据量。这有助于降低过拟合风险,并且有利于梯度传播。每个头部在低维空间中学习到的特征表示更加简洁且有效,从而提高了模型的泛化能力。
- 增强表达能力:多头注意力结合了注意力的不同视角,提高了综合表示的质量。这种综合表示有助于模型更好地理解和处理序列数据,特别是在自然语言处理和图像分类等任务中。通过结合多个头部的输出结果,模型能够生成更加丰富和准确的特征表示。
- 并行计算:每个独立的头部可以并行计算,这极大地提升了训练速度。尤其是在大规模数据集和GPU硬件环境中,多头注意力机制的并行计算能力使得模型能够更快地收敛和达到更优的性能。
- 兼容性强:在Transformer等深度神经网络架构中,多头注意力已经成为核心组成部分。它与其他层如位置编码、归一化层等无缝协作,共同构成了强大的深度学习模型。这种兼容性使得多头注意力机制能够在各种任务中广泛应用,并取得优异的性能。
- 优化表达和解释性:虽然多头注意力增加了模型的复杂性,但也提高了对模型决策过程的解释性和透明度。通过对各个头部的学习结果进行分析,可以更好地理解模型如何从输入中抽取关键特征,并做出预测或生成文本。这种解释性有助于调试和改进模型,以及在实际应用中更好地理解和应用模型的结果。
- 自适应注意力分配:多头机制允许模型自适应地调整各个头部之间的权衡,以满足特定任务的需求。这种自适应性有助于优化模型的性能,特别是在处理需要同时考虑多种不同类型关系的数据集时。通过动态地调整注意力的分配,模型能够更灵活地应对各种复杂情况。
3.3 Sublayer 2
由三部分组成:
Feed Forward Network
Residual Connection
Layer Normalization
四 Decoder
解码器由N个相同的Sublayers 堆叠而成
每个Sublayers 主要包括三部分:
1: MHA++Add&Norm
2: MHA(cross-attention) +Add&Norm
3: FFN+Add&Norm
4.1 Cross-Attention
4.2 Self-Attention sequence Mask
一、防止未来信息泄露
在自回归生成任务中,如文本生成或翻译模型的解码阶段,模型在预测下一个词时只能利用当前词及其之前的词,而不能看到未来的词。Sequence Mask通过掩盖序列中未来位置的注意力权重,确保模型在训练时不会“偷看”未来的信息。这通常是通过将未来位置的注意力权重设为负无穷来实现的,这样在softmax操作后,这些位置的权重为零,从而避免了未来信息的泄露。
二、保持自回归特性
Sequence Mask在解码器的自注意力层中使用,可以确保在计算每个位置的注意力时,只考虑当前位置及之前的位置。这种机制对于保持自回归特性至关重要,因为自回归模型需要基于之前的输出逐步生成新的输出。
五 Training 学习率
Transformer 学习率使用的是Warm-up 技术,先增加再降低。
warmup_step 论文里面设置的是4000
Warm-up stage是Transformer模型训练初期的一个阶段,在这个阶段,学习率从一个较低的值逐渐增加到预设的最大值。这个过程有助于模型在训练初期逐渐适应数据,避免因为学习率过高而导致的训练不稳定。
二、Warm-up stage的作用
减缓模型在初始阶段对mini-batch的提前过拟合现象:
- 在训练初期,模型对数据的分布和特征尚未完全掌握,如果直接采用较高的学习率进行训练,可能会导致模型对mini-batch数据的过拟合,从而影响整体的训练效果。
- 通过Warm-up stage,学习率逐渐增加,模型可以逐步适应数据,减缓这种提前过拟合的现象。
保持模型深层的稳定性:
- 对于深层神经网络来说,训练过程中的稳定性是一个重要的问题。如果学习率设置不当,可能会导致模型在训练过程中震荡,甚至崩溃。
- Warm-up stage通过逐渐增加学习率,使得模型在训练初期能够保持较好的稳定性,有助于后续的训练过程。
提高训练效率:
- 在Warm-up stage之后,学习率通常会逐渐降低,但此时模型已经逐渐适应了数据,并且具备了一定的泛化能力。
- 因此,在后续的训练过程中,模型可以更加高效地学习和优化,提高整体的训练效率
六 参考代码
参考: 用PyTorch构建Transformer模型实战(非常详细)零基础入门到精通,收藏这篇就够了
链接: 用PyTorch构建Transformer模型实战(非常详细)零基础入门到精通,收藏这篇就够了_pytorch transformer-CSDN博客
主要改动:
增加了位置编码
增加了mask
增加了model 初始化部分
# -*- coding: utf-8 -*-
"""
Created on Thu Oct 10 17:10:10 2024
@author: chengxf2
"""
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import math
import copy
import torch.nn.functional as F
class PositionalEncoding(nn.Module):
#位置编码
def __init__(self, d_model=512, max_seq_length=1000):
super(PositionalEncoding,self).__init__()
pe = torch.zeros(max_seq_length, d_model)
position = torch.arange(0, max_seq_length).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2)*(-math.log(10000)/d_model))
pe[:,0::2]= torch.sin(position*div_term)
pe[:,1::2]= torch.cos(position*div_term)
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x):
#x.shape batchsz,seq_length, d_model
output = x+ self.pe[:,:x.size(1)]
return output
class PositionWiseFeedForward(nn.Module):
def __init__(self, d_model, d_ff):
super(PositionWiseFeedForward,self).__init__()
self.layer1 = nn.Linear(d_model, d_ff)
self.layer2 = nn.Linear(d_ff, d_model)
self.relu = nn.ReLU()
def forward(self, x):
#前馈全连接网络
hidden = F.relu(self.layer1(x))
output = self.layer2(hidden)
return output
class MultiHeadAttention(nn.Module):
#多头注意力机制
def __init__(self, d_model=512, num_heads=8):
super(MultiHeadAttention, self).__init__()
assert d_model%num_heads ==0
#初始化维度
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model//num_heads
#用于转换输入的线性层
self.layer_query = nn.Linear(d_model, d_model)
self.layer_key = nn.Linear(d_model, d_model)
self.layer_value = nn.Linear(d_model, d_model)
self.layer_out = nn.Linear(d_model, d_model)
def scaled_dot_product_attention(self, q, k, v, mask=None):
scores = torch.matmul(q,k.transpose(-2,-1))/math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask==0, -1e9)
attn_probs = F.softmax(scores, dim=-1)
output = torch.matmul(attn_probs, v)
return output
def split_heads(self, x):
#分割头,实现不同的语义空间
batchsz, seq_length, d_model = x.size()
out = x.view(batchsz, seq_length, self.num_heads, self.d_k).transpose(1,2)
return out
def concatenate_heads(self, x):
#组合头
batchsz, num_heads, seq_length, d_k = x.size()
out = x.transpose(1,2).contiguous().view(batchsz,seq_length,self.d_model)
return out
def forward(self, query, key, value, mask=None):
q = self.split_heads(self.layer_query(query))
k = self.split_heads(self.layer_key(key))
v = self.split_heads(self.layer_value(value))
attn_out = self.scaled_dot_product_attention(q, k, v,mask)
output = self.concatenate_heads(attn_out)
output = self.layer_out(output)
return output
class AddNorm(nn.Module):
def __init__(self, d_model,dropout):
super(AddNorm,self).__init__()
self.dropout = nn.Dropout(dropout)
self.norm = nn.LayerNorm(d_model)
def forward(self, x, y):
out = x+y
output = self.norm(out)
return self.dropout(output)
class EncoderLayer(nn.Module):
#编码器层
def __init__(self, d_model=512, num_heads=8, d_ff=1024, dropout=0.1):
super(EncoderLayer,self).__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
self.addNorm1 = AddNorm(d_model, dropout)
self.addNorm2 = AddNorm(d_model, dropout)
def forward(self, x, mask):
#sublayer 1
y = self.self_attn(x, x, x, mask)
x = self.addNorm1(x,y)
#sublayer 2
y = self.feed_forward(x)
output = self.addNorm2(x,y)
return output
class DecoderLayer(nn.Module):
def __init__(self, d_model=512, num_heads=8, d_ff=1024, dropout=0.1):
super(DecoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.cross_attn = MultiHeadAttention(d_model, d_model)
self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
self.addNorm1 = AddNorm(d_model, dropout)
self.addNorm2 = AddNorm(d_model, dropout)
self.addNorm3= AddNorm(d_model, dropout)
def forward(self, x, memory, src_mask, tgt_mask):
y = self.self_attn(x,x,x,tgt_mask)
query = self.addNorm1(x,y)
y = self.cross_attn(query,memory,memory,src_mask)
x = self.addNorm2(query,y)
y = self.feed_forward(x)
output = self.addNorm3(x,y)
return output
class Transformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):
super(Transformer, self).__init__()
self.encoder_embedding = nn.Embedding(src_vocab_size, d_model)
self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model)
self.positional_encoding = PositionalEncoding(d_model, max_seq_length)
self.encoder_layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
self.decoder_layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
self.fc = nn.Linear(d_model, tgt_vocab_size)
self.dropout = nn.Dropout(dropout)
def generate_mask(self, src, tgt):
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
tgt_mask = (tgt != 0).unsqueeze(1).unsqueeze(3)
seq_length = tgt.size(1)
nopeak_mask = (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal=1)).bool()
tgt_mask = tgt_mask & nopeak_mask
return src_mask, tgt_mask
def forward(self, src, tgt):
src_mask, tgt_mask = self.generate_mask(src, tgt)
src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))
enc_output = src_embedded
for enc_layer in self.encoder_layers:
enc_output = enc_layer(enc_output, src_mask)
dec_output = tgt_embedded
for dec_layer in self.decoder_layers:
dec_output = dec_layer(dec_output, enc_output, src_mask, tgt_mask)
output = self.fc(dec_output)
return output
src_vocab_size = 5000
tgt_vocab_size = 5000
d_model = 512
num_heads = 8
num_layers = 6
d_ff = 2048
max_seq_length = 100
dropout = 0.1
transformer = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout)
# 生成随机样本数据
src_data = torch.randint(1, src_vocab_size, (64, max_seq_length)) # (batch_size, seq_length)
tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length)) # (batch_size, seq_length)
参考:
1 :https://www.youtube.com/watch?v=RsuSOylfN2I
2: https://www.youtube.com/watch?v=1h7T_-V5GI43: https://www.youtube.com/@PyMLstudio/videos
4: https://www.youtube.com/watch?v=pVP0bu8QA2w
5: https://www.youtube.com/watch?v=U0s0f995w14
6: https://www.youtube.com/watch?v=XfpMkf4rD6E