引言
自从 Google 在 2017 年发表了一篇题为《Attention Is All You Need》的论文以来,Transformer 已经成为了自然语言处理领域的一个重要里程碑。与传统的 RNN 和 LSTM 不同,Transformer 通过自注意力机制(Self-Attention Mechanism)实现了更快、更高效的数据处理方式。本文将详细介绍 Transformer 的原理、架构以及如何在实际项目中使用 Transformer 模型。
1. Transformer 的背景
在 Transformer 问世之前,大多数 NLP 任务都是基于循环神经网络(RNN)或长短期记忆网络(LSTM)完成的。这些模型能够很好地处理序列数据,但在训练大型数据集时存在效率低下和难以并行化的问题。Transformer 的出现解决了这些问题,同时也极大地推动了自然语言处理的发展。
2. Transformer 的核心概念
- 自注意力机制:Transformer 使用自注意力机制来捕捉输入序列中的全局上下文关系。
- 位置编码:由于 Transformer 没有像 RNN 那样的序列依赖性,因此需要位置编码来保留序列的位置信息。
- 编码器-解码器架构:Transformer 采用了编码器-解码器架构,其中编码器负责处理输入序列,而解码器则负责生成输出序列。
3. Transformer 的架构
Transformer 的架构主要包括以下几个部分:
- 编码器(Encoder):由多层相同的子层组成,每一层包括一个多头自注意力机制和一个前馈神经网络。
- 解码器(Decoder):同样由多层相同的子层组成,除了多头自注意力机制和前馈神经网络外,还包括一个额外的多头注意力层,用于关注编码器的输出。
- 多头注意力(Multi-Head Attention):通过将注意力分成多个头,每个头独立计算注意力权重,从而提高模型的并行性和表达能力。
- 前馈网络(Feed Forward Network):每个子层之后都有一个前馈网络,用于增加模型的非线性变换能力。
- 残差连接与层归一化(Residual Connections & Layer Normalization):每个子层前后都加入了残差连接,并在其之后使用了层归一化来加速训练过程。
4. Transformer 的工作流程
- 输入嵌入:每个输入词被转换为一个固定大小的向量。
- 位置编码:为了保留词的位置信息,添加了位置编码。
- 多头自注意力:编码器和解码器的第一步是通过多头自注意力机制来处理输入序列。
- 前馈网络:通过前馈网络进行非线性变换。
- 解码器处理:解码器不仅处理自己的输入序列,还通过多头注意力层关注编码器的输出。
5. 使用 Transformer 的实践
为了展示如何在实际项目中使用 Transformer,我们将使用 Python 和 PyTorch 框架来构建一个简单的翻译模型。
5.1 环境搭建
确保你的环境中已安装 Python 和 PyTorch。你可以通过 pip 安装 PyTorch:
Bash
1pip install torch
5.2 数据准备
使用一个简单的翻译数据集,例如英语到法语的翻译数据集。
Python
1import torch
2from torchtext.data import Field, TabularDataset, BucketIterator
3from torchtext.data.metrics import bleu_score
4
5# 定义字段
6SRC = Field(tokenize='spacy', tokenizer_language='en_core_web_sm', init_token='<sos>', eos_token='<eos>', lower=True)
7TRG = Field(tokenize='spacy', tokenizer_language='fr_core_news_sm', init_token='<sos>', eos_token='<eos>', lower=True)
8
9# 加载数据
10train_data, valid_data, test_data = TabularDataset.splits(
11 path='data/', train='train.csv', validation='val.csv', test='test.csv',
12 format='csv', fields=[('src', SRC), ('trg', TRG)])
13
14# 构建词汇表
15SRC.build_vocab(train_data, min_freq=2)
16TRG.build_vocab(train_data, min_freq=2)
17
18# 创建迭代器
19device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
20train_iterator, valid_iterator, test_iterator = BucketIterator.splits(
21 (train_data, valid_data, test_data), batch_size=128, device=device)
5.3 构建 Transformer 模型
接下来,我们将定义 Transformer 模型。
Python
1import torch.nn as nn
2
3class Transformer(nn.Module):
4 def __init__(self, input_dim, output_dim, hid_dim, n_layers, n_heads, pf_dim, dropout, device, max_length=100):
5 super().__init__()
6
7 self.tok_embedding = nn.Embedding(input_dim, hid_dim)
8 self.pos_embedding = nn.Embedding(max_length, hid_dim)
9
10 self.layers = nn.ModuleList([EncoderLayer(hid_dim, n_heads, pf_dim, dropout, device) for _ in range(n_layers)])
11
12 self.fc_out = nn.Linear(hid_dim, output_dim)
13 self.dropout = nn.Dropout(dropout)
14 self.device = device
15
16 def forward(self, src, src_mask):
17 # src = [batch size, src len]
18 # src_mask = [batch size, 1, 1, src len]
19
20 batch_size = src.shape[0]
21 src_len = src.shape[1]
22
23 pos = torch.arange(0, src_len).unsqueeze(0).repeat(batch_size, 1).to(self.device)
24
25 # pos = [batch size, src len]
26
27 src = self.dropout((self.tok_embedding(src) + self.pos_embedding(pos)))
28
29 # src = [batch size, src len, hid dim]
30
31 for layer in self.layers:
32 src = layer(src, src_mask)
33
34 # src = [batch size, src len, hid dim]
35
36 output = self.fc_out(src)
37
38 # output = [batch size, src len, output dim]
39
40 return output
41
42class EncoderLayer(nn.Module):
43 def __init__(self, hid_dim, n_heads, pf_dim, dropout, device):
44 super().__init__()
45
46 self.self_attn_layer_norm = nn.LayerNorm(hid_dim)
47 self.ff_layer_norm = nn.LayerNorm(hid_dim)
48 self.self_attention = MultiHeadAttentionLayer(hid_dim, n_heads, dropout, device)
49 self.positionwise_feedforward = PositionwiseFeedforwardLayer(hid_dim, pf_dim, dropout)
50 self.dropout = nn.Dropout(dropout)
51
52 def forward(self, src, src_mask):
53 # src = [batch size, src len, hid dim]
54 # src_mask = [batch size, 1, 1, src len]
55
56 _src, _ = self.self_attention(src, src, src, src_mask)
57
58 # _src = [batch size, src len, hid dim]
59
60 src = self.self_attn_layer_norm(src + self.dropout(_src))
61
62 # src = [batch size, src len, hid dim]
63
64 _src = self.positionwise_feedforward(src)
65
66 # _src = [batch size, src len, hid dim]
67
68 src = self.ff_layer_norm(src + self.dropout(_src))
69
70 # src = [batch size, src len, hid dim]
71
72 return src
5.4 训练模型
现在我们来训练我们的 Transformer 模型。
Python
1import math
2
3def train(model, iterator, optimizer, criterion, clip):
4 model.train()
5
6 epoch_loss = 0
7
8 for i, batch in enumerate(iterator):
9 src = batch.src
10 trg = batch.trg
11
12 optimizer.zero_grad()
13
14 output = model(src, trg[:, :-1])
15
16 # output = [batch size, trg len - 1, output dim]
17 # trg = [batch size, trg len]
18
19 output_dim = output.shape[-1]
20
21 output = output.contiguous().view(-1, output_dim)
22 trg = trg[:, 1:].contiguous().view(-1)
23
24 # output = [batch size * trg len - 1, output dim]
25 # trg = [batch size * trg len - 1]
26
27 loss = criterion(output, trg)
28 loss.backward()
29
30 torch.nn.utils.clip_grad_norm_(model.parameters(), clip)
31
32 optimizer.step()
33 epoch_loss += loss.item()
34
35 return epoch_loss / len(iterator)
36
37def evaluate(model, iterator, criterion):
38 model.eval()
39
40 epoch_loss = 0
41
42 with torch.no_grad():
43 for i, batch in enumerate(iterator):
44 src = batch.src
45 trg = batch.trg
46
47 output = model(src, trg[:, :-1])
48
49 # output = [batch size, trg len - 1, output dim]
50 # trg = [batch size, trg len]
51
52 output_dim = output.shape[-1]
53
54 output = output.contiguous().view(-1, output_dim)
55 trg = trg[:, 1:].contiguous().view(-1)
56
57 # output = [batch size * trg len - 1, output dim]
58 # trg = [batch size * trg len - 1]
59
60 loss = criterion(output, trg)
61
62 epoch_loss += loss.item()
63
64 return epoch_loss / len(iterator)
65
66def epoch_time(start_time, end_time):
67 elapsed_time = end_time - start_time
68 elapsed_mins = int(elapsed_time / 60)
69 elapsed_secs = int(elapsed_time - (elapsed_mins * 60))
70 return elapsed_mins, elapsed_secs
71
72INPUT_DIM = len(SRC.vocab)
73OUTPUT_DIM = len(TRG.vocab)
74HID_DIM = 256
75ENC_LAYERS = 3
76DEC_LAYERS = 3
77ENC_HEADS = 8
78DEC_HEADS = 8
79ENC_PF_DIM = 512
80DEC_PF_DIM = 512
81ENC_DROPOUT = 0.1
82DEC_DROPOUT = 0.1
83
84enc = Encoder(INPUT_DIM, HID_DIM, ENC_LAYERS, ENC_HEADS, ENC_PF_DIM, ENC_DROPOUT, device)
85dec = Decoder(OUTPUT_DIM, HID_DIM, DEC_LAYERS, DEC_HEADS, DEC_PF_DIM, DEC_DROPOUT, device)
86
87model = Seq2Seq(enc, dec, device).to(device)
88
89def count_parameters(model):
90 return sum(p.numel() for p in model.parameters() if p.requires_grad)
91
92print(f'The model has {count_parameters(model):,} trainable parameters')
93
94optimizer = torch.optim.Adam(model.parameters())
95
96PAD_IDX = TRG.vocab.stoi['<pad>']
97
98criterion = nn.CrossEntropyLoss(ignore_index=PAD_IDX)
99
100N_EPOCHS = 10
101CLIP = 1
102
103best_valid_loss = float('inf')
104
105for epoch in range(N_EPOCHS):
106
107 start_time = time.time()
108
109 train_loss = train(model, train_iterator, optimizer, criterion, CLIP)
110 valid_loss = evaluate(model, valid_iterator, criterion)
111
112 end_time = time.time()
113
114 epoch_mins, epoch_secs = epoch_time(start_time, end_time)
115
116 if valid_loss < best_valid_loss:
117 best_valid_loss = valid_loss
118 torch.save(model.state_dict(), 'tut6-model.pt')
119
120 print(f'Epoch: {epoch+1:02} | Time: {epoch_mins}m {epoch_secs}s')
121 print(f'\tTrain Loss: {train_loss:.3f} | Train PPL: {math.exp(train_loss):7.3f}')
122 print(f'\t Val. Loss: {valid_loss:.3f} | Val. PPL: {math.exp(valid_loss):7.3f}')
6. 结论
Transformer 模型彻底改变了自然语言处理领域的面貌,它不仅提高了模型的训练速度,而且在许多任务上取得了显著的效果。通过本文的介绍,你应该对 Transformer 的原理和使用有了更深入的理解,并能够将其应用于实际项目中。如果你有任何疑问或想要了解更多细节,请随时查阅官方文档或社区资源。