Transformer模型(pytorch代码详解)
⽬录
Transformer
模型基本介绍
与q2q相⽐transformer是⼀个纯粹基于注意⼒的架构(⾃注意⼒同时具有并⾏计算和最短的最⼤路径长度这两个优势),没有⽤到任何
CNN和RNN。
如下图所⽰,transformer是由编码器和解码器组成的。transformer的编码器和解码器是基于⾃注意⼒的模块叠加⽽成的,源(输⼊)序
列和⽬标(输出)序列的嵌⼊表⽰将加上位置编码,再分别输⼊到编码器和解码器中。
多头注意⼒
对同⼀key,value,query,希望可以抽取到不同信息
如短距离关系和长距离关系(与卷积时的多输出通道相似)
多头注意⼒使⽤h个独⽴的注意⼒池化
合并各个头(head)输出得到最终结果
具体思路:
我们可以⽤独⽴学习得到的h组不同的线性投影来变换查询、键和值。然后,这h组变换后的查询、键和值将并⾏地送到注意⼒汇聚中。最
后,将这h个注意⼒汇聚的输出拼接在⼀起,并且通过另⼀个可以学习的线性投影进⾏变换,以产⽣最终输出。这种设计被称为多头注意
⼒:
模型:
如上所⽰,额外加⼊了可学习参数W
该参数将query的维度从Dq映射为Pq,将key的维度从Dq映射为Kq,将value的维度从Dq映射为Vq。(这个映射通常会使数量减少)
最终再将输出的可学习参数Wo与hi的拼接结果相乘最终得到多头注意⼒的输出(Po)
有掩码的多头注意⼒
解码器对序列中⼀个元素输出时,不应该考虑该元素之后的元素
可以通过掩码来实现
也就是在计算Xi输出时,假装当前序列长度为i(将i以后的内容盖住/掩蔽)
基于位置的前馈⽹络
本质是⼀个全连接层
将输⼊形状由(b,n,d)变化为(bn,d)b为batch_sizen为序列长度d为特征维度
作⽤两个全连接层
输出形状由(bn,d)变化回(b,n,d)
等价于两层核窗⼝为1的⼀维卷积层
层归⼀化
批量归⼀化对每个特征/通道⾥的元素进⾏归⼀化(⽅差变1均值变0)
不适合序列长度会变得nlp应⽤(bn中n序列长度在不断变化)
层归⼀化对每个样本的元素进⾏归⼀化
如上图所⽰:
BatchNormalization处理的是d中的每⼀个b*len的矩阵(左图蓝⾊部分)⽅差变1均值变0。操作范围在每⼀个特征维度中。
LayerNormalization处理的是每⼀个batch中的len*d的矩阵(右图蓝⾊部分)⽅差变1均值变0。操作范围在单个样本内部。在变化长度
时较BN更加稳定
信息传递(对应结构图中连接解码器与编码器的线)
编码器中输出y1…yn
将其作为解码中第i个Transformer块中多头注意⼒的key和value(query来⾃⽬标序列)
意味着编码器和解码器中块的个数和输出维度都是⼀样的
预测
在预测t+1个输出时,解码器中输⼊前t个预测值(在⾃注意⼒中,前t个预测值作为key和value,第t个预测值作为query)
在进⾏第t+1个预测时,已经知道了前t个预测的值
在训练的时候可以是并⾏的,在预测时是顺序的。
⼩结:
Transformer是⼀个纯使⽤注意⼒的编码-解码器
编码器和解码器都有n个transformer块
每个块⾥使⽤多头(⾃)注意⼒,基于位置的前馈⽹络和层归⼀化
多头注意⼒实现
选择缩放点积注意⼒作为每⼀个注意⼒头
importmath
importtorch
fromtorchimportnn
fromd2limporttorchasd2l
#@save
classMultiHeadAttention():
"""多头注意⼒"""
def__init__(lf,key_size,query_size,value_size,num_hiddens,
num_heads,dropout,bias=Fal,**kwargs):
super(MultiHeadAttention,lf).__init__(**kwargs)
_heads=num_heads
ion=ductAttention(dropout)
lf.W_q=(query_size,num_hiddens,bias=bias)
lf.W_k=(key_size,num_hiddens,bias=bias)
lf.W_v=(value_size,num_hiddens,bias=bias)
lf.W_o=(num_hiddens,num_hiddens,bias=bias)
defforward(lf,queries,keys,values,valid_lens):
#queries,keys,values的形状:
#(batch_size,查询或者“键-值”对的个数,num_hiddens)
#valid_lens 的形状:
#(batch_size,)或(batch_size,查询的个数)
#经过变换后,输出的queries,keys,values 的形状:
#(batch_size*num_heads,查询或者“键-值”对的个数,
#num_hiddens/num_heads)
queries=transpo_qkv(lf.W_q(queries),_heads)
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
Transformer实现
queries=transpo_qkv(lf.W_q(queries),_heads)
keys=transpo_qkv(lf.W_k(keys),_heads)
values=transpo_qkv(lf.W_v(values),_heads)
ifvalid_lensisnotNone:
#在轴0,将第⼀项(标量或者⽮量)复制num_heads次,
#然后如此复制第⼆项,然后诸如此类。
valid_lens=_interleave(
valid_lens,repeats=_heads,dim=0)
#output的形状:(batch_size*num_heads,查询的个数,
#num_hiddens/num_heads)
output=ion(queries,keys,values,valid_lens)
#output_concat的形状:(batch_size,查询的个数,num_hiddens)
output_concat=transpo_output(output,_heads)
returnlf.W_o(output_concat)
#@save
deftranspo_qkv(X,num_heads):
"""为了多注意⼒头的并⾏计算⽽变换形状"""
#输⼊X的形状:(batch_size,查询或者“键-值”对的个数,num_hiddens)
#输出X的形状:(batch_size,查询或者“键-值”对的个数,num_heads,
#num_hiddens/num_heads)
X=e([0],[1],num_heads,-1)
#输出X的形状:(batch_size,num_heads,查询或者“键-值”对的个数,
#num_hiddens/num_heads)
X=e(0,2,1,3)
#最终输出的形状:(batch_size*num_heads,查询或者“键-值”对的个数,
#num_hiddens/num_heads)
e(-1,[2],[3])
#@save
deftranspo_output(X,num_heads):
"""逆转transpo_qkv函数的操作"""
X=e(-1,num_heads,[1],[2])
X=e(0,2,1,3)
e([0],[1],-1)
num_hiddens,num_heads=100,5
attention=MultiHeadAttention(num_hiddens,num_hiddens,num_hiddens,
num_hiddens,num_heads,0.5)
()
batch_size,num_queries=2,4
num_kvpairs,valid_lens=6,([3,2])
X=((batch_size,num_queries,num_hiddens))
Y=((batch_size,num_kvpairs,num_hiddens))
attention(X,Y,Y,valid_lens).shape
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
##from/graykode/nlp-tutorial/tree/master/ormer
importnumpyasnp
importtorch
soptim
asplt
importmath
defmake_batch(ntences):
1
2
3
4
5
6
7
8
9
10
defmake_batch(ntences):
input_batch=[[src_vocab[n]forninntences[0].split()]]
output_batch=[[tgt_vocab[n]forninntences[1].split()]]
target_batch=[[tgt_vocab[n]forninntences[2].split()]]
nsor(input_batch),nsor(output_batch),nsor(target_batch)
##10
defget_attn_subquent_mask(q):
"""
q:[batch_size,tgt_len]
"""
attn_shape=[(0),(1),(1)]
#attn_shape:[batch_size,tgt_len,tgt_len]
subquence_mask=((attn_shape),k=1)#⽣成⼀个上三⾓矩阵
subquence_mask=_numpy(subquence_mask).byte()
returnsubquence_mask#[batch_size,tgt_len,tgt_len]
##DotProductAttention
classScaledDotProductAttention():
def__init__(lf):
super(ScaledDotProductAttention,lf).__init__()
defforward(lf,Q,K,V,attn_mask):
##输⼊进来的维度分别是[batch_sizexn_headsxlen_qxd_k]K:[batch_sizexn_headsxlen_kxd_k]V:[batch_sizexn_headsxlen_kxd_v]
##⾸先经过matmul函数得到的scores形状是:[batch_sizexn_headsxlen_qxlen_k]
scores=(Q,o(-1,-2))/(d_k)
##然后关键词地⽅来了,下⾯这个就是⽤到了我们之前重点讲的attn_mask,把被mask的地⽅置为⽆限⼩,softmax之后基本就是0,对q的单词不起作⽤
_fill_(attn_mask,-1e9)#Filllementsoflftensorwithvaluewheremaskisone.
attn=x(dim=-1)(scores)
context=(attn,V)
returncontext,attn
##eadAttention
classMultiHeadAttention():
def__init__(lf):
super(MultiHeadAttention,lf).__init__()
##输⼊进来的QKV是相等的,我们会使⽤映射linear做⼀个映射得到参数矩阵Wq,Wk,Wv
lf.W_Q=(d_model,d_k*n_heads)
lf.W_K=(d_model,d_k*n_heads)
lf.W_V=(d_model,d_v*n_heads)
=(n_heads*d_v,d_model)
_norm=orm(d_model)
defforward(lf,Q,K,V,attn_mask):
##这个多头分为这⼏个步骤,⾸先映射分头,然后计算atten_scores,然后计算atten_value;
##输⼊进来的数据形状:Q:[batch_sizexlen_qxd_model],K:[batch_sizexlen_kxd_model],V:[batch_sizexlen_kxd_model]
residual,batch_size=Q,(0)
#(B,S,D)-proj->(B,S,D)-split->(B,S,H,W)-trans->(B,H,S,W)
##下⾯这个就是先映射,后分头;⼀定要注意的是q和k分头之后维度是⼀致额,所以⼀看这⾥都是dk
q_s=lf.W_Q(Q).view(batch_size,-1,n_heads,d_k).transpo(1,2)#q_s:[batch_sizexn_headsxlen_qxd_k]
k_s=lf.W_K(K).view(batch_size,-1,n_heads,d_k).transpo(1,2)#k_s:[batch_sizexn_headsxlen_kxd_k]
v_s=lf.W_V(V).view(batch_size,-1,n_heads,d_v).transpo(1,2)#v_s:[batch_sizexn_headsxlen_kxd_v]
##输⼊进⾏的attn_mask形状是batch_sizexlen_qxlen_k,然后经过下⾯这个代码得到新的attn_mask:[batch_sizexn_headsxlen_qxlen_k],就是
attn_mask=attn_eze(1).repeat(1,n_heads,1,1)
##然后我们计算ScaledDotProductAttention这个函数,去7.看⼀下
##得到的结果有两个:context:[batch_sizexn_headsxlen_qxd_v],attn:[batch_sizexn_headsxlen_qxlen_k]
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
##得到的结果有两个:context:[batch_sizexn_headsxlen_qxd_v],attn:[batch_sizexn_headsxlen_qxlen_k]
context,attn=ScaledDotProductAttention()(q_s,k_s,v_s,attn_mask)
context=o(1,2).contiguous().view(batch_size,-1,n_heads*d_v)#context:[batch_sizexlen_qxn_heads*d_v]
output=(context)
_norm(output+residual),attn#output:[batch_sizexlen_qxd_model]
##eFeedForwardNet
classPoswiFeedForwardNet():
def__init__(lf):
super(PoswiFeedForwardNet,lf).__init__()
1=1d(in_channels=d_model,out_channels=d_ff,kernel_size=1)
2=1d(in_channels=d_ff,out_channels=d_model,kernel_size=1)
_norm=orm(d_model)
defforward(lf,inputs):
residual=inputs#inputs:[batch_size,len_q,d_model]
output=()(1(o(1,2)))
output=2(output).transpo(1,2)
_norm(output+residual)
##_attn_pad_mask
##⽐如说,我现在的句⼦长度是5,在后⾯注意⼒机制的部分,我们在计算出来QK转置除以根号之后,softmax之前,我们得到的形状
##len_input*len*input代表每个单词对其余包含⾃⼰的单词的影响⼒
##所以这⾥我需要有⼀个同等⼤⼩形状的矩阵,告诉我哪个位置是PAD部分,之后在计算计算softmax之前会把这⾥置为⽆穷⼤;
##⼀定需要注意的是这⾥得到的矩阵形状是batch_sizexlen_qxlen_k,我们是对k中的pad符号进⾏标识,并没有对k中的做标识,因为没必要
##q_q和q_k不⼀定⼀致,在交互注意⼒,q来⾃解码端,k来⾃编码端,所以告诉模型编码这边pad符号信息就可以,解码端的pad信息在交互注意⼒层是
defget_attn_pad_mask(q_q,q_k):
batch_size,len_q=q_()
batch_size,len_k=q_()
#eq(zero)isPADtoken
pad_attn_mask=q_(0).unsqueeze(1)#batch_sizex1xlen_k,oneismasking
returnpad_attn_(batch_size,len_q,len_k)#batch_sizexlen_qxlen_k
##onalEncoding代码实现
classPositionalEncoding():
def__init__(lf,d_model,dropout=0.1,max_len=5000):
super(PositionalEncoding,lf).__init__()
##位置编码的实现其实很简单,直接对照着公式去敲代码就可以,下⾯这个代码只是其中⼀种实现⽅式;
##从理解来讲,需要注意的就是偶数和奇数在公式上有⼀个共同部分,我们使⽤log函数把次⽅拿下来,⽅便计算;
##pos代表的是单词在句⼦中的索引,这点需要注意;⽐如max_len是128个,那么索引就是从0,1,2,...,127
##假设我的demodel是512,2i那个符号中i从0取到了255,那么2i对应取值就是0,2,4...510
t=t(p=dropout)
pe=(max_len,d_model)
position=(0,max_len,dtype=).unsqueeze(1)
div_term=((0,d_model,2).float()*(-(10000.0)/d_model))
pe[:,0::2]=(position*div_term)##这⾥需要注意的是pe[:,0::2]这个⽤法,就是从0开始到最后⾯,补长为2,其实代表的就是偶数位置
pe[:,1::2]=(position*div_term)##这⾥需要注意的是pe[:,1::2]这个⽤法,就是从1开始到最后⾯,补长为2,其实代表的就是奇数位置
##上⾯代码获取之后得到的pe:[max_len*d_model]
##下⾯这个代码之后,我们得到的pe形状是:[max_len*1*d_model]
pe=eze(0).transpo(0,1)
er_buffer('pe',pe)##定⼀个缓冲区,其实简单理解为这个参数不更新就可以
defforward(lf,x):
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
defforward(lf,x):
"""
x:[q_len,batch_size,d_model]
"""
x=x+[:(0),:]
t(x)
##rLayer:包含两个部分,多头注意⼒机制和前馈神经⽹络
classEncoderLayer():
def__init__(lf):
super(EncoderLayer,lf).__init__()
_lf_attn=MultiHeadAttention()
_ffn=PoswiFeedForwardNet()
defforward(lf,enc_inputs,enc_lf_attn_mask):
##下⾯这个就是做⾃注意⼒层,输⼊是enc_inputs,形状是[batch_sizexq_len_qxd_model]需要注意的是最初始的QKV矩阵是等同于这个输⼊的,去
enc_outputs,attn=_lf_attn(enc_inputs,enc_inputs,enc_inputs,enc_lf_attn_mask)#enc_inputstosameQ,K,V
enc_outputs=_ffn(enc_outputs)#enc_outputs:[batch_sizexlen_qxd_model]
returnenc_outputs,attn
##r部分包含三个部分:词向量embedding,位置编码部分,注意⼒层及后续的前馈神经⽹络
classEncoder():
def__init__(lf):
super(Encoder,lf).__init__()
_emb=ing(src_vocab_size,d_model)##这个其实就是去定义⽣成⼀个矩阵,⼤⼩是src_vocab_size*d_model
_emb=PositionalEncoding(d_model)##位置编码情况,这⾥是固定的正余弦函数,也可以使⽤类似词向量的ing获得⼀个可以更新学
=List([EncoderLayer()for_inrange(n_layers)])##使⽤ModuleList对多个encoder进⾏堆叠,因为后续的encoder并没有使⽤词向量
defforward(lf,enc_inputs):
##这⾥我们的enc_inputs形状是:[batch_sizexsource_len]
##下⾯这个代码通过src_emb,进⾏索引定位,enc_outputs输出形状是[batch_size,src_len,d_model]
enc_outputs=_emb(enc_inputs)#把数字索引转化为对应的向量
##这⾥就是位置编码,把两者相加放⼊到了这个函数⾥⾯,从这⾥可以去看⼀下位置编码函数的实现;3.
enc_outputs=_emb(enc_o(0,1)).transpo(0,1)
##get_attn_pad_mask是为了得到句⼦中pad的位置信息,给到模型后⾯,在计算⾃注意⼒和交互注意⼒的时候去掉pad符号的影响,去看⼀下这个函数4.
enc_lf_attn_mask=get_attn_pad_mask(enc_inputs,enc_inputs)
enc_lf_attns=[]
:
##去看EncoderLayer层函数5.
enc_outputs,enc_lf_attn=layer(enc_outputs,enc_lf_attn_mask)
enc_lf_(enc_lf_attn)
returnenc_outputs,enc_lf_attns
##10.
classDecoderLayer():
def__init__(lf):
super(DecoderLayer,lf).__init__()
_lf_attn=MultiHeadAttention()
_enc_attn=MultiHeadAttention()
_ffn=PoswiFeedForwardNet()
defforward(lf,dec_inputs,enc_outputs,dec_lf_attn_mask,dec_enc_attn_mask):
dec_outputs,dec_lf_attn=_lf_attn(dec_inputs,dec_inputs,dec_inputs,dec_lf_attn_mask)
dec_outputs,dec_enc_attn=_enc_attn(dec_outputs,enc_outputs,enc_outputs,dec_enc_attn_mask)
dec_outputs=_ffn(dec_outputs)
returndec_outputs,dec_lf_attn,dec_enc_attn
##r
classDecoder():
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
def__init__(lf):
super(Decoder,lf).__init__()
_emb=ing(tgt_vocab_size,d_model)
_emb=PositionalEncoding(d_model)
=List([DecoderLayer()for_inrange(n_layers)])
defforward(lf,dec_inputs,enc_inputs,enc_outputs):#dec_inputs:[batch_sizextarget_len]
dec_outputs=_emb(dec_inputs)#[batch_size,tgt_len,d_model]
dec_outputs=_emb(dec_o(0,1)).transpo(0,1)#[batch_size,tgt_len,d_model]
##get_attn_pad_mask⾃注意⼒层的时候的pad部分
dec_lf_attn_pad_mask=get_attn_pad_mask(dec_inputs,dec_inputs)
##get_attn_subquent_mask这个做的是⾃注意层的mask部分,就是当前单词之后看不到,使⽤⼀个上三⾓为1的矩阵
dec_lf_attn_subquent_mask=get_attn_subquent_mask(dec_inputs)
##两个矩阵相加,⼤于0的为1,不⼤于0的为0,为1的在之后就会被fill到⽆限⼩
dec_lf_attn_mask=((dec_lf_attn_pad_mask+dec_lf_attn_subquent_mask),0)
##这个做的是交互注意⼒机制中的mask矩阵,enc的输⼊是k,我去看这个k⾥⾯哪些是pad符号,给到后⾯的模型;注意哦,我q肯定也是有pad符号,但
dec_enc_attn_mask=get_attn_pad_mask(dec_inputs,enc_inputs)
dec_lf_attns,dec_enc_attns=[],[]
:
dec_outputs,dec_lf_attn,dec_enc_attn=layer(dec_outputs,enc_outputs,dec_lf_attn_mask,dec_enc_attn_mask)
dec_lf_(dec_lf_attn)
dec_enc_(dec_enc_attn)
returndec_outputs,dec_lf_attns,dec_enc_attns
##1.从整体⽹路结构来看,分为三个部分:编码层,解码层,输出层
classTransformer():
def__init__(lf):
super(Transformer,lf).__init__()
r=Encoder()##编码层
r=Decoder()##解码层
#输出层d_model是我们解码层每个token输出的维度⼤⼩,之后会做⼀个tgt_vocab_size⼤⼩的softmax
tion=(d_model,tgt_vocab_size,bias=Fal)
defforward(lf,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_lf_attns这⾥没记错的是QK转置相乘之后softmax之后的矩阵值,代表的是每个单词和其他单词相关性;
enc_outputs,enc_lf_attns=r(enc_inputs)
#dec_outputs是decoder主要输出,⽤于后续的linear映射;dec_lf_attns类⽐于enc_lf_attns是查看每个单词对decoder中输⼊的其余单词的相关性;
dec_outputs,dec_lf_attns,dec_enc_attns=r(dec_inputs,enc_inputs,enc_outputs)
#dec_outputs做映射到词表⼤⼩
dec_logits=tion(dec_outputs)#dec_logits:[batch_sizexsrc_vocab_sizextgt_vocab_size]
returndec_(-1,dec_(-1)),enc_lf_attns,dec_lf_attns,dec_enc_attns
if__name__=='__main__':
##句⼦的输⼊部分,
ntences=['ichmochteeinbierP','Siwantabeer','iwantabeerE']
#TransformerParameters
#PaddingShouldbeZero
##构建词表
src_vocab={'P':0,'ich':1,'mochte':2,'ein':3,'bier':4}
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
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#lengthofsource
tgt_len=5#lengthoftarget
#模型参数
d_model=512#EmbeddingSize
d_ff=2048#FeedForwarddimension
d_k=d_v=64#dimensionofK(=Q),V
n_layers=6#numberofEncoderofDecoderLayer
n_heads=8#numberofheadsinMulti-HeadAttention
model=Transformer()
criterion=ntropyLoss()
optimizer=(ters(),lr=0.001)
enc_inputs,dec_inputs,target_batch=make_batch(ntences)
forepochinrange(20):
_grad()
outputs,enc_lf_attns,dec_lf_attns,dec_enc_attns=model(enc_inputs,dec_inputs)
loss=criterion(outputs,target_uous().view(-1))
print('Epoch:','%04d'%(epoch+1),'cost=','{:.6f}'.format(loss))
rd()
()
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
本文发布于:2022-12-30 07:18:28,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/58232.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |