0%

Pytorch之Transformer

本节主要介绍了Transformer的基本框架,以及相关难以理解的细节

Transformer的基本架构

image-20230824185446460
  • Transformer总体框架可分为四个部分:
    • 输入部分
    • 输出部分
    • 编码器部分
    • 解码器部分

1.输入部分

  • 源文本嵌入层及其位置编码器

  • 目标文本嵌入层及其位置编码器

    image-20230824185730961

2.输出部分

  • 线性层

  • softmax层

    image-20230824185805336

3.编码器部分

  • 由N个编码器堆叠而成

  • 每个编码器层由两个子层连接结构组成

  • 第一个子层连接结构包括一个多头自注意力层和规范化层以及一个残差连接

  • 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

    image-20230824190116917

4.解码器部分

  • 由N个编码器堆叠而成

  • 每个编码器层由三个子层连接结构组成

  • 第一个子层连接结构包括一个多头自注意力层和规范化层以及一个残差连接

  • 第一个子层连接结构包括一个多头注意力层和规范化层以及一个残差连接

  • 第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

    image-20230824190320460


位置编码

  • 位置编码器的作用:因为在Transformer的编码器结构中,并没有针对词汇位置信息的处理,因此需要在embedding层后加入位置编码器,将词汇位置不同而可能产生不同语义的信息加入到词嵌入张量中,以弥补位置信息的缺失

  • 根据视频中所述公式计算:transformer计算位置编码的过程示例_哔哩哔哩_bilibili

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    import torch
    import torch
    import numpy
    import torch.nn as nn
    import torch.nn.functional as F

    #关于word embedding,以序列建模为例
    #考虑source sentence和target sentence
    #构建序列,序列的字符以其在词表中的索引形式表示
    batch_size = 2

    #单词表的大小
    max_num_src_words = 8
    max_num_tgt_words = 8
    model_dim = 7

    #序列的最大长度
    max_src_seq_len = 5
    max_tgt_seq_len = 5
    max_position_len = 5

    src_len = torch.tensor([2, 4], dtype = torch.int32)
    tgt_len = torch.tensor([4, 3], dtype = torch.int32)

    #单词索引构成的句子
    src_seq = torch.cat([torch.unsqueeze(F.pad(torch.randint(0, max_num_src_words, (L,)), (0, max_src_seq_len - L)), 0)
    for L in src_len])
    tgt_seq = torch.cat([torch.unsqueeze(F.pad(torch.randint(0, max_num_tgt_words, (L,)), (0, max_tgt_seq_len - L)), 0)
    for L in src_len])

    #构造embedding
    src_embedding_table = nn.Embedding(max_num_src_words, model_dim, padding_idx = 7)
    tgt_embedding_table = nn.Embedding(max_num_tgt_words, model_dim, padding_idx = 7)
    src_embedding = src_embedding_table(src_seq)
    tgt_embedding = tgt_embedding_table(tgt_seq)

    # print(src_embedding)
    # print(tgt_embedding)

    #构造position embedding
    pos_mat = torch.arange(max_position_len).reshape(-1, 1)
    i_mat_even = torch.pow(10000, torch.arange(0, model_dim, 2).reshape(1, -1)/model_dim)
    i_mat_odd = torch.pow(10000, torch.arange(1, model_dim, 2).reshape(1, -1)/model_dim)
    pe_embedding_table = torch.zeros(max_position_len, model_dim)
    pe_embedding_table[:, 0::2] = torch.sin(pos_mat / i_mat_even)
    pe_embedding_table[:, 1::2] = torch.cos(pos_mat / i_mat_odd)

    pe_embedding = nn.Embedding(max_position_len, model_dim)
    pe_embedding.weight = nn.Parameter(pe_embedding_table, requires_grad = False)

    src_pos = torch.cat([torch.unsqueeze(torch.arange(max(src_len)), 0) for _ in src_len]).to(torch.int32)
    tgt_pos = torch.cat([torch.unsqueeze(torch.arange(max(tgt_len)), 0) for _ in tgt_len]).to(torch.int32)

    src_pe_embedding = pe_embedding(src_pos) #只与句子中词的位置有关
    tgt_pe_embedding = pe_embedding(tgt_pos)

    print(src_pe_embedding)
    print(tgt_pe_embedding)

    结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    tensor([[[0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00,
    1.0000e+00, 0.0000e+00],
    [8.4147e-01, 9.6423e-01, 7.1906e-02, 9.9981e-01, 5.1794e-03,
    1.0000e+00, 3.7276e-04],
    [9.0930e-01, 8.5948e-01, 1.4344e-01, 9.9925e-01, 1.0359e-02,
    1.0000e+00, 7.4552e-04],
    [1.4112e-01, 6.9325e-01, 2.1423e-01, 9.9832e-01, 1.5538e-02,
    9.9999e-01, 1.1183e-03]],

    [[0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00,
    1.0000e+00, 0.0000e+00],
    [8.4147e-01, 9.6423e-01, 7.1906e-02, 9.9981e-01, 5.1794e-03,
    1.0000e+00, 3.7276e-04],
    [9.0930e-01, 8.5948e-01, 1.4344e-01, 9.9925e-01, 1.0359e-02,
    1.0000e+00, 7.4552e-04],
    [1.4112e-01, 6.9325e-01, 2.1423e-01, 9.9832e-01, 1.5538e-02,
    9.9999e-01, 1.1183e-03]]])
    tensor([[[0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00,
    1.0000e+00, 0.0000e+00],
    [8.4147e-01, 9.6423e-01, 7.1906e-02, 9.9981e-01, 5.1794e-03,
    1.0000e+00, 3.7276e-04],
    [9.0930e-01, 8.5948e-01, 1.4344e-01, 9.9925e-01, 1.0359e-02,
    1.0000e+00, 7.4552e-04],
    [1.4112e-01, 6.9325e-01, 2.1423e-01, 9.9832e-01, 1.5538e-02,
    9.9999e-01, 1.1183e-03]],

    [[0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00,
    1.0000e+00, 0.0000e+00],
    [8.4147e-01, 9.6423e-01, 7.1906e-02, 9.9981e-01, 5.1794e-03,
    1.0000e+00, 3.7276e-04],
    [9.0930e-01, 8.5948e-01, 1.4344e-01, 9.9925e-01, 1.0359e-02,
    1.0000e+00, 7.4552e-04],
    [1.4112e-01, 6.9325e-01, 2.1423e-01, 9.9832e-01, 1.5538e-02,
    9.9999e-01, 1.1183e-03]]])

多头自注意机制的结构及其原理

  • 注意力的基本结构:

    image-20230903231429391
    • 若Q、K来自不同输入,则是注意力机制,若Q、K输入来自同一输入,则是自注意力机制

    • 其计算公式如下:
      $$
      Z=softmax(\frac{Q\times K^T}{\sqrt{d_k}})\cdot V
      $$

  • 那么Q、K、V是如何计算得到的呢?

    • 假设以下计算均是针对自注意机制,同一输入分别与三个权重矩阵$W^Q,W^K,W^V$相乘,即可得到Q、K、V

      image-20230903232947833
    • 从图中可以很明显的看出来,全连接层能很好的实现这一过程

  • 那么$Q\times K^T$相乘又意味着什么呢?

    • $softmax(\frac{Q\times K^T}{\sqrt{d_k}})$计算后的结果表示的是Q(约等于输入)中每个单词对K(约等于输入)中每个单词的相关性

    • 不难发现,$Q\times K^T$后的维度变成了$(len\times len)$

    • 所以我的理解是,首先通过全连接层将词向量维度变成K的维度,接着通过attention操作变到句长的维度,以便获取单词对单词之间相关性的权重矩阵

    • 最后,用这个权重矩阵对V进行加权求和,而这个V可以理解为一个初始答案,经过权重矩阵的训练之后,得到一个尽可能标准的答案,最终Z的最后两个维度为$(len\times d_v)$

      image-20230903234454290
  • 那么什么是多头注意力机制呢?

    • 就是用不同的矩阵与输入相乘,从而得到不同组的Q、K、V,以此获得更多空间维度的特征
    image-20230904000023238
    • 其实现代码如下,值得细细品味:

      • 首先在self.W_Qnn.Linear中,本来是将d_model维变到d_k维,但是由于是多头,故乘以了一个n_heads
      • 其次在self.linearnn.Linear中,需要将维度变回d_model,以便下一次堆叠计算
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      class MultiHeadAttention(nn.Module):
      def __init__(self):
      super(MultiHeadAttention, self).__init__()
      ## 输入进来的QKV是相等的,我们会使用映射linear做一个映射得到参数矩阵Wq, Wk,Wv
      self.W_Q = nn.Linear(d_model, d_k * n_heads)
      self.W_K = nn.Linear(d_model, d_k * n_heads)
      self.W_V = nn.Linear(d_model, d_v * n_heads)
      self.linear = nn.Linear(n_heads * d_v, d_model)
      self.layer_norm = nn.LayerNorm(d_model)

      def forward(self, Q, K, V, attn_mask):

      ## 这个多头分为这几个步骤,首先映射分头,然后计算atten_scores,然后计算atten_value;
      ##输入进来的数据形状: Q: [batch_size x len_q x d_model], K: [batch_size x len_k x d_model], V: [batch_size x len_k x d_model]
      residual, batch_size = Q, Q.size(0)
      # (B, S, D) -proj-> (B, S, D) -split-> (B, S, H, W) -trans-> (B, H, S, W)

      ##下面这个就是先映射,后分头;一定要注意的是q和k分头之后维度是一致额,所以一看这里都是dk
      q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1,2) # q_s: [batch_size x n_heads x len_q x d_k]
      k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1,2) # k_s: [batch_size x n_heads x len_k x d_k]
      v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1,2) # v_s: [batch_size x n_heads x len_k x d_v]

      ## 输入进行的attn_mask形状是 batch_size x len_q x len_k,然后经过下面这个代码得到新的attn_mask : [batch_size x n_heads x len_q x len_k],就是把pad信息重复了n个头上
      attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)

      ##然后我们计算 ScaledDotProductAttention 这个函数,去7.看一下
      ## 得到的结果有两个:context: [batch_size x n_heads x len_q x d_v], attn: [batch_size x n_heads x len_q x len_k]
      context, attn = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask)
      context = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v) # context: [batch_size x len_q x n_heads * d_v]
      output = self.linear(context)
      return self.layer_norm(output + residual), attn # output: [batch_size x len_q x d_model]

两个输入与标签值的理解


BatchNorm与LayerNorm的区别


Transformer的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
## from https://github.com/graykode/nlp-tutorial/tree/master/5-1.Transformer

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import math


def make_batch(sentences):
input_batch = [[src_vocab[n] for n in sentences[0].split()]]
output_batch = [[tgt_vocab[n] for n in sentences[1].split()]]
target_batch = [[tgt_vocab[n] for n in sentences[2].split()]]
return torch.LongTensor(input_batch), torch.LongTensor(output_batch), torch.LongTensor(target_batch)

## 10
def get_attn_subsequent_mask(seq):
"""
seq: [batch_size, tgt_len]
"""
attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
# attn_shape: [batch_size, tgt_len, tgt_len]
subsequence_mask = np.triu(np.ones(attn_shape), k=1) # 生成一个上三角矩阵
subsequence_mask = torch.from_numpy(subsequence_mask).byte()
return subsequence_mask # [batch_size, tgt_len, tgt_len]


## 7. ScaledDotProductAttention
class ScaledDotProductAttention(nn.Module):
def __init__(self):
super(ScaledDotProductAttention, self).__init__()

def forward(self, Q, K, V, attn_mask):
## 输入进来的维度分别是 [batch_size x n_heads x len_q x d_k] K: [batch_size x n_heads x len_k x d_k] V: [batch_size x n_heads x len_k x d_v]
##首先经过matmul函数得到的scores形状是 : [batch_size x n_heads x len_q x len_k]
scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)

## 然后关键词地方来了,下面这个就是用到了我们之前重点讲的attn_mask,把被mask的地方置为无限小,softmax之后基本就是0,对q的单词不起作用
scores.masked_fill_(attn_mask, -1e9) # Fills elements of self tensor with value where mask is one.
attn = nn.Softmax(dim=-1)(scores)
context = torch.matmul(attn, V)
return context, attn


## 6. MultiHeadAttention
class MultiHeadAttention(nn.Module):
def __init__(self):
super(MultiHeadAttention, self).__init__()
## 输入进来的QKV是相等的,我们会使用映射linear做一个映射得到参数矩阵Wq, Wk,Wv
self.W_Q = nn.Linear(d_model, d_k * n_heads)
self.W_K = nn.Linear(d_model, d_k * n_heads)
self.W_V = nn.Linear(d_model, d_v * n_heads)
self.linear = nn.Linear(n_heads * d_v, d_model)
self.layer_norm = nn.LayerNorm(d_model)

def forward(self, Q, K, V, attn_mask):

## 这个多头分为这几个步骤,首先映射分头,然后计算atten_scores,然后计算atten_value;
##输入进来的数据形状: Q: [batch_size x len_q x d_model], K: [batch_size x len_k x d_model], V: [batch_size x len_k x d_model]
residual, batch_size = Q, Q.size(0)
# (B, S, D) -proj-> (B, S, D) -split-> (B, S, H, W) -trans-> (B, H, S, W)

##下面这个就是先映射,后分头;一定要注意的是q和k分头之后维度是一致额,所以一看这里都是dk
q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1,2) # q_s: [batch_size x n_heads x len_q x d_k]
k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1,2) # k_s: [batch_size x n_heads x len_k x d_k]
v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1,2) # v_s: [batch_size x n_heads x len_k x d_v]

## 输入进行的attn_mask形状是 batch_size x len_q x len_k,然后经过下面这个代码得到新的attn_mask : [batch_size x n_heads x len_q x len_k],就是把pad信息重复了n个头上
attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)

##然后我们计算 ScaledDotProductAttention 这个函数,去7.看一下
## 得到的结果有两个:context: [batch_size x n_heads x len_q x d_v], attn: [batch_size x n_heads x len_q x len_k]
context, attn = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask)
context = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v) # context: [batch_size x len_q x n_heads * d_v]
output = self.linear(context)
return self.layer_norm(output + residual), attn # output: [batch_size x len_q x d_model]


## 8. PoswiseFeedForwardNet
class PoswiseFeedForwardNet(nn.Module):
def __init__(self):
super(PoswiseFeedForwardNet, self).__init__()
self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1)
self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1)
self.layer_norm = nn.LayerNorm(d_model)

def forward(self, inputs):
residual = inputs # inputs : [batch_size, len_q, d_model]
output = nn.ReLU()(self.conv1(inputs.transpose(1, 2)))
output = self.conv2(output).transpose(1, 2)
return self.layer_norm(output + residual)



## 4. get_attn_pad_mask

## 比如说,我现在的句子长度是5,在后面注意力机制的部分,我们在计算出来QK转置除以根号之后,softmax之前,我们得到的形状
## len_input * len*input 代表每个单词对其余包含自己的单词的影响力

## 所以这里我需要有一个同等大小形状的矩阵,告诉我哪个位置是PAD部分,之后在计算计算softmax之前会把这里置为无穷大;

## 一定需要注意的是这里得到的矩阵形状是batch_size x len_q x len_k,我们是对k中的pad符号进行标识,并没有对k中的做标识,因为没必要

## seq_q 和 seq_k 不一定一致,在交互注意力,q来自解码端,k来自编码端,所以告诉模型编码这边pad符号信息就可以,解码端的pad信息在交互注意力层是没有用到的;

def get_attn_pad_mask(seq_q, seq_k):
batch_size, len_q = seq_q.size()
batch_size, len_k = seq_k.size()
# eq(zero) is PAD token
pad_attn_mask = seq_k.data.eq(0).unsqueeze(1) # batch_size x 1 x len_k, one is masking
return pad_attn_mask.expand(batch_size, len_q, len_k) # batch_size x len_q x len_k


## 3. PositionalEncoding 代码实现
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout=0.1, max_len=5000):
super(PositionalEncoding, self).__init__()

## 位置编码的实现其实很简单,直接对照着公式去敲代码就可以,下面这个代码只是其中一种实现方式;
## 从理解来讲,需要注意的就是偶数和奇数在公式上有一个共同部分,我们使用log函数把次方拿下来,方便计算;
## pos代表的是单词在句子中的索引,这点需要注意;比如max_len是128个,那么索引就是从0,1,2,...,127
##假设我的demodel是512,2i那个符号中i从0取到了255,那么2i对应取值就是0,2,4...510
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[:, 0::2]这个用法,就是从0开始到最后面,补长为2,其实代表的就是偶数位置
pe[:, 1::2] = torch.cos(position * div_term)##这里需要注意的是pe[:, 1::2]这个用法,就是从1开始到最后面,补长为2,其实代表的就是奇数位置
## 上面代码获取之后得到的pe:[max_len*d_model]

## 下面这个代码之后,我们得到的pe形状是:[max_len*1*d_model]
pe = pe.unsqueeze(0).transpose(0, 1)

self.register_buffer('pe', pe) ## 定一个缓冲区,其实简单理解为这个参数不更新就可以

def forward(self, x):
"""
x: [seq_len, batch_size, d_model]
"""
x = x + self.pe[:x.size(0), :]
return self.dropout(x)


## 5. EncoderLayer :包含两个部分,多头注意力机制和前馈神经网络
class EncoderLayer(nn.Module):
def __init__(self):
super(EncoderLayer, self).__init__()
self.enc_self_attn = MultiHeadAttention()
self.pos_ffn = PoswiseFeedForwardNet()

def forward(self, enc_inputs, enc_self_attn_mask):
## 下面这个就是做自注意力层,输入是enc_inputs,形状是[batch_size x seq_len_q x d_model] 需要注意的是最初始的QKV矩阵是等同于这个输入的,去看一下enc_self_attn函数 6.
enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs, enc_self_attn_mask) # enc_inputs to same Q,K,V
enc_outputs = self.pos_ffn(enc_outputs) # enc_outputs: [batch_size x len_q x d_model]
return enc_outputs, attn


## 2. Encoder 部分包含三个部分:词向量embedding,位置编码部分,注意力层及后续的前馈神经网络

class Encoder(nn.Module):
def __init__(self):
super(Encoder, self).__init__()
self.src_emb = nn.Embedding(src_vocab_size, d_model) ## 这个其实就是去定义生成一个矩阵,大小是 src_vocab_size * d_model
self.pos_emb = PositionalEncoding(d_model) ## 位置编码情况,这里是固定的正余弦函数,也可以使用类似词向量的nn.Embedding获得一个可以更新学习的位置编码
self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)]) ## 使用ModuleList对多个encoder进行堆叠,因为后续的encoder并没有使用词向量和位置编码,所以抽离出来;

def forward(self, enc_inputs):
## 这里我们的 enc_inputs 形状是: [batch_size x source_len]

## 下面这个代码通过src_emb,进行索引定位,enc_outputs输出形状是[batch_size, src_len, d_model]
enc_outputs = self.src_emb(enc_inputs)

## 这里就是位置编码,把两者相加放入到了这个函数里面,从这里可以去看一下位置编码函数的实现;3.
enc_outputs = self.pos_emb(enc_outputs.transpose(0, 1)).transpose(0, 1)

##get_attn_pad_mask是为了得到句子中pad的位置信息,给到模型后面,在计算自注意力和交互注意力的时候去掉pad符号的影响,去看一下这个函数 4.
enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)
enc_self_attns = []
for layer in self.layers:
## 去看EncoderLayer 层函数 5.
enc_outputs, enc_self_attn = layer(enc_outputs, enc_self_attn_mask)
enc_self_attns.append(enc_self_attn)
return enc_outputs, enc_self_attns

## 10.
class DecoderLayer(nn.Module):
def __init__(self):
super(DecoderLayer, self).__init__()
self.dec_self_attn = MultiHeadAttention()
self.dec_enc_attn = MultiHeadAttention()
self.pos_ffn = PoswiseFeedForwardNet()

def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask):
dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs, dec_inputs, dec_self_attn_mask)
dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs, enc_outputs, dec_enc_attn_mask)
dec_outputs = self.pos_ffn(dec_outputs)
return dec_outputs, dec_self_attn, dec_enc_attn

## 9. Decoder
class Decoder(nn.Module):
def __init__(self):
super(Decoder, self).__init__()
self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model)
self.pos_emb = PositionalEncoding(d_model)
self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])

def forward(self, dec_inputs, enc_inputs, enc_outputs): # dec_inputs : [batch_size x target_len]
dec_outputs = self.tgt_emb(dec_inputs) # [batch_size, tgt_len, d_model]
dec_outputs = self.pos_emb(dec_outputs.transpose(0, 1)).transpose(0, 1) # [batch_size, tgt_len, d_model]

## get_attn_pad_mask 自注意力层的时候的pad 部分
dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs)

## get_attn_subsequent_mask 这个做的是自注意层的mask部分,就是当前单词之后看不到,使用一个上三角为1的矩阵
dec_self_attn_subsequent_mask = get_attn_subsequent_mask(dec_inputs)

## 两个矩阵相加,大于0的为1,不大于0的为0,为1的在之后就会被fill到无限小
dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask + dec_self_attn_subsequent_mask), 0)

## 这个做的是交互注意力机制中的mask矩阵,enc的输入是k,我去看这个k里面哪些是pad符号,给到后面的模型;注意哦,我q肯定也是有pad符号,但是这里我不在意的,之前说了好多次了哈
dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)

dec_self_attns, dec_enc_attns = [], []
for layer in self.layers:
dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask)
dec_self_attns.append(dec_self_attn)
dec_enc_attns.append(dec_enc_attn)
return dec_outputs, dec_self_attns, dec_enc_attns


## 1. 从整体网路结构来看,分为三个部分:编码层,解码层,输出层
class Transformer(nn.Module):
def __init__(self):
super(Transformer, self).__init__()
self.encoder = Encoder() ## 编码层
self.decoder = Decoder() ## 解码层
self.projection = nn.Linear(d_model, tgt_vocab_size, bias=False) ## 输出层 d_model 是我们解码层每个token输出的维度大小,之后会做一个 tgt_vocab_size 大小的softmax
def forward(self, enc_inputs, dec_inputs):
## 这里有两个数据进行输入,一个是enc_inputs 形状为[batch_size, src_len],主要是作为编码段的输入,一个dec_inputs,形状为[batch_size, tgt_len],主要是作为解码端的输入

## enc_inputs作为输入 形状为[batch_size, src_len],输出由自己的函数内部指定,想要什么指定输出什么,可以是全部tokens的输出,可以是特定每一层的输出;也可以是中间某些参数的输出;
## enc_outputs就是主要的输出,enc_self_attns这里没记错的是QK转置相乘之后softmax之后的矩阵值,代表的是每个单词和其他单词相关性;
enc_outputs, enc_self_attns = self.encoder(enc_inputs)

## dec_outputs 是decoder主要输出,用于后续的linear映射; dec_self_attns类比于enc_self_attns 是查看每个单词对decoder中输入的其余单词的相关性;dec_enc_attns是decoder中每个单词对encoder中每个单词的相关性;
dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(dec_inputs, enc_inputs, enc_outputs)

## dec_outputs做映射到词表大小
dec_logits = self.projection(dec_outputs) # dec_logits : [batch_size x src_vocab_size x tgt_vocab_size]
return dec_logits.view(-1, dec_logits.size(-1)), enc_self_attns, dec_self_attns, dec_enc_attns


if __name__ == '__main__':

## 句子的输入部分,
sentences = ['ich mochte ein bier P', 'S i want a beer', 'i want a beer E']

# Transformer Parameters
# Padding Should be Zero
## 构建词表
src_vocab = {'P': 0, 'ich': 1, 'mochte': 2, 'ein': 3, 'bier': 4}
src_vocab_size = len(src_vocab)

tgt_vocab = {'P': 0, 'i': 1, 'want': 2, 'a': 3, 'beer': 4, 'S': 5, 'E': 6}
tgt_vocab_size = len(tgt_vocab)

src_len = 5 # length of source
tgt_len = 5 # length of target

## 模型参数
d_model = 512 # Embedding Size
d_ff = 2048 # FeedForward dimension
d_k = d_v = 64 # dimension of K(=Q), V
n_layers = 6 # number of Encoder of Decoder Layer
n_heads = 8 # number of heads in Multi-Head Attention

model = Transformer()

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

enc_inputs, dec_inputs, target_batch = make_batch(sentences)

for epoch in range(20):
optimizer.zero_grad()
outputs, enc_self_attns, dec_self_attns, dec_enc_attns = model(enc_inputs, dec_inputs)
loss = criterion(outputs, target_batch.contiguous().view(-1))
print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
loss.backward()
optimizer.step()

Reference

欢迎来到ssy的世界