transformer模型简介
ansformer的主要组件包括编码器(Encoder)、解码器(Decoder)和注意力层。其核心是利用多头自注意力机制(Multi-Head Self-Attention),使每个位置的表示不仅依赖于当前位置,还能够直接获取其他位置的表示。自从提出以来,Transformer模型在机器翻译、文本生成等自然语言处理任务中均取得了突破性进展,成为NLP领域新的主流模型。以下是我总结的 Transformer 模型的一些关键特点和组成部分:
1. Seq2Seq架构和注意力机制
- Seq2Seq架构:Seq2Seq是一种编码器-解码器架构,广泛应用于机器翻译等序列到序列的任务。编码器负责将输入序列编码成一个固定长度的向量,解码器则根据这个向量生成输出序列。
- 注意力机制:为了解决Seq2Seq模型在处理长距离依赖时的局限性,引入了注意力机制。注意力机制允许模型在生成输出时,动态地关注输入序列中的不同部分,从而提高翻译的准确性。
3. 关键模块
- 位置编码:由于Transformer模型不使用循环或卷积结构,为了使模型能够理解单词在序列中的位置,引入了位置编码。位置编码通常使用正弦和余弦函数的组合来生成,并将这些编码与词嵌入向量相加。 1.位置编码的引入:在将词嵌入送入编码器之前,需要加入位置编码。这使得模型能够理解单词在序列中的位置关系。 2.计算方法:位置编码使用正弦和余弦函数的不同频率来生成,具体公式如下:
- 多头注意力机制:Transformer通过多头注意力机制,允许模型在不同的表示子空间中同时捕捉信息。这增强了模型的表达能力,并允许它从不同的角度理解输入数据。
- 残差网络:Transformer模型中的每个子层都使用残差连接,这有助于在深层网络中更好地传播信息,并减少梯度消失的问题。
- 层标准化:在每个子层之后进行层标准化,有助于稳定训练过程,加快收敛速度。
4. 传统方法的局限性
- 卷积神经网络:卷积神经网络在处理长文本时存在局限性,因为其受限的上下文窗口无法覆盖整个序列。为了描述长距离依赖,需要多层卷积操作,这可能导致信息传递的损失。
- 循环神经网络:循环神经网络通过维护循环单元中的隐状态来处理序列数据。然而,随着序列长度的增加,早期的上下文信息可能会逐渐被遗忘。尽管注意力机制在一定程度上缓解了这个问题,但循环网络在编码效率方面仍存在不足。
5. Transformer的优势
- 并行处理:Transformer模型通过并行处理整个序列,提高了训练和推断的效率。这与传统的循环神经网络形成对比,后者需要逐个处理序列中的每个元素。
- 长距离依赖:Transformer的自注意力机制使得模型能够捕捉序列内部的长距离依赖关系,这对于机器翻译等任务至关重要。
6. 应用
- 机器翻译任务:文章鼓励读者基于Transformer模型实现机器翻译任务。通过了解Transformer的关键组件和工作原理,读者可以更好地应用这一模型来解决实际的翻译问题。
数据预处理
# 定义tokenizer
en_tokenizer = get_tokenizer('spacy', language='en_core_web_trf')
zh_tokenizer = lambda x: list(jieba.cut(x)) # 使用jieba分词
# 读取数据函数
def read_data(file_path: str) -> List[str]:
with open(file_path, 'r', encoding='utf-8') as f:
return [line.strip() for line in f]
# 数据预处理函数
def preprocess_data(en_data: List[str], zh_data: List[str]) -> List[Tuple[List[str], List[str]]]:
processed_data = []
for en, zh in zip(en_data, zh_data):
en_tokens = en_tokenizer(en.lower())[:MAX_LENGTH]
zh_tokens = zh_tokenizer(zh)[:MAX_LENGTH]
if en_tokens and zh_tokens: # 确保两个序列都不为空
processed_data.append((en_tokens, zh_tokens))
return processed_data
# 构建词汇表
def build_vocab(data: List[Tuple[List[str], List[str]]]):
en_vocab = build_vocab_from_iterator(
(en for en, _ in data),
specials=['<unk>', '<pad>', '<bos>', '<eos>']
)
zh_vocab = build_vocab_from_iterator(
(zh for _, zh in data),
specials=['<unk>', '<pad>', '<bos>', '<eos>']
)
en_vocab.set_default_index(en_vocab['<unk>'])
zh_vocab.set_default_index(zh_vocab['<unk>'])
return en_vocab, zh_vocab
模型构建
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)
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0).transpose(0, 1)
self.register_buffer('pe', pe)
def forward(self, x):
x = x + self.pe[:x.size(0), :]
return self.dropout(x)
class TransformerModel(nn.Module):
def __init__(self, src_vocab, tgt_vocab, d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout):
super(TransformerModel, self).__init__()
self.transformer = nn.Transformer(d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout)
self.src_embedding = nn.Embedding(len(src_vocab), d_model)
self.tgt_embedding = nn.Embedding(len(tgt_vocab), d_model)
self.positional_encoding = PositionalEncoding(d_model, dropout)
self.fc_out = nn.Linear(d_model, len(tgt_vocab))
self.src_vocab = src_vocab
self.tgt_vocab = tgt_vocab
self.d_model = d_model
def forward(self, src, tgt):
# 调整src和tgt的维度
src = src.transpose(0, 1) # (seq_len, batch_size)
tgt = tgt.transpose(0, 1) # (seq_len, batch_size)
src_mask = self.transformer.generate_square_subsequent_mask(src.size(0)).to(src.device)
tgt_mask = self.transformer.generate_square_subsequent_mask(tgt.size(0)).to(tgt.device)
src_padding_mask = (src == self.src_vocab['<pad>']).transpose(0, 1)
tgt_padding_mask = (tgt == self.tgt_vocab['<pad>']).transpose(0, 1)
src_embedded = self.positional_encoding(self.src_embedding(src) * math.sqrt(self.d_model))
tgt_embedded = self.positional_encoding(self.tgt_embedding(tgt) * math.sqrt(self.d_model))
output = self.transformer(src_embedded, tgt_embedded,
src_mask, tgt_mask, None, src_padding_mask, tgt_padding_mask, src_padding_mask)
return self.fc_out(output).transpose(0, 1)
训练函数
def train(model, iterator, optimizer, criterion, clip):
model.train()
epoch_loss = 0
for i, batch in enumerate(iterator):
src, tgt = batch
if src.numel() == 0 or tgt.numel() == 0:
continue
src, tgt = src.to(DEVICE), tgt.to(DEVICE)
optimizer.zero_grad()
output = model(src, tgt[:, :-1])
output_dim = output.shape[-1]
output = output.contiguous().view(-1, output_dim)
tgt = tgt[:, 1:].contiguous().view(-1)
loss = criterion(output, tgt)
loss.backward()
clip_grad_norm_(model.parameters(), clip)
optimizer.step()
epoch_loss += loss.item()
return epoch_loss / len(iterator)
def evaluate(model, iterator, criterion):
model.eval()
epoch_loss = 0
with torch.no_grad():
for i, batch in enumerate(iterator):
src, tgt = batch
if src.numel() == 0 or tgt.numel() == 0:
continue
src, tgt = src.to(DEVICE), tgt.to(DEVICE)
output = model(src, tgt[:, :-1])
output_dim = output.shape[-1]
output = output.contiguous().view(-1, output_dim)
tgt = tgt[:, 1:].contiguous().view(-1)
loss = criterion(output, tgt)
epoch_loss += loss.item()
return epoch_loss / len(iterator)