图解transformer会更加直观一些,比代码直观,其中的参数也能够更加直观的认识,加深印象,所以是一种比较好的方式来理解。如果说transformer是设计摩天大楼(LLM就是摩天大楼,transformer是摩天大楼中的主体部分)的解决方案,那么这个图就是设计摩天大楼的内部核心图纸,值得一看。
当然,这个图略显简单,其关联众多要素和其逻辑、数据流转方式是背后隐性较为重要的东西。当然,其很神奇,神奇的根源来自其对元素与元素关系的简洁、直接、有效的计算,最后以注意力权重的形式体现,整体以注意力输出来体现,这很奇妙,奇妙的方式计算人类关注事物、联想和建立联系的实质,挖开了人类智能的一个缺口,宝藏被大量释放。
1.模型图解
下面这个图蕴含着丰富的信息,值得好好解读一下,其中每个模块和层及名称都是对应模型设定的一部分。这个图画得很精确,简洁,也很经典,信息量也大。后面的参数和代码实现,都和图中内容一一对应,其中的数据流描述也很精确。可以说,这个图可以看成是打开transformer的车钥匙。
看图说话,从大到小的表达,左边是 的内部结构,右边是的内部结构。
2.模型代码:
玩转代码是操作transformer模型的“驾驶技术”,把握方向盘,开油门,踩刹车,换挡等等,都是通过代码中的设置来操作的。
encoder = TransformerEncoder(
len(src_vocab), key_size, query_size, value_size, num_hiddens,
norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
num_layers, dropout)
decoder = TransformerDecoder(
len(tgt_vocab), key_size, query_size, value_size, num_hiddens,
norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
num_layers, dropout)
net = d2l.EncoderDecoder(encoder, decoder)
TransformerEncoder与 这部分相对应,
其中blk指block(块),与这部分相应,N的意思是N个模块叠加,与下面的代码对应:
for i, blk in enumerate(self.blks):
X = blk(X, valid_lens)
self.attention_weights[i] = blk.attention.attention.attention_weights
return X
模型中这个部分,pos_encoding与对应,
self.embedding与对应,
与图中对应,
对应的代码如下,:
X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
依此类推,代码中的变量与图中的部分都是一一对应关系。
TransformerEncoder的全部代码如下
class TransformerEncoder(d2l.Encoder):
def __init__(self, vocab_size, key_size, query_size, value_size,
num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
num_heads, num_layers, dropout, use_bias=False, **kwargs):
super(TransformerEncoder, self).__init__(**kwargs)
self.num_hiddens = num_hiddens
self.embedding = nn.Embedding(vocab_size, num_hiddens)
self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
self.blks = nn.Sequential()
for i in range(num_layers):
self.blks.add_module("block"+str(i),
EncoderBlock(key_size, query_size, value_size, num_hiddens,
norm_shape, ffn_num_input, ffn_num_hiddens,
num_heads, dropout, use_bias))
def forward(self, X, valid_lens, *args):
# 因为位置编码值在-1和1之间,
# 因此嵌⼊值乘以嵌⼊维度的平⽅根进⾏缩放,
# 然后再与位置编码相加。
X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
self.attention_weights = [None] * len(self.blks)
for i, blk in enumerate(self.blks):
X = blk(X, valid_lens)
self.attention_weights[i] = blk.attention.attention.attention_weights
return X
TransformerDecoder与这部分相应
代码中的DecoderBlock与图中这个部分对应,DecoderBlock 在 TransformerDecoder代码中被“浓缩成”blk,整个代码的执行X, state = blk(X, state),其中state有三个部分:enc_outputs(encoder的输出), enc_valid_lens(encoder的有效长度) = state[0], state[1],state[2]有两种状态下,代表的内容不同,其中训练模式下key_values = X,state[2]没有实质性内容;预测模式下,key_values = torch.cat((state[2][self.i], X), axis=1),state[2][self.i] = key_values,state[2]是前面所有时刻的信息(代表上下文)。
DecoderBlock的代码:
class DecoderBlock(nn.Module):
"""解码器中第i个块"""
def __init__(self, key_size, query_size, value_size, num_hiddens,
norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
dropout, i, **kwargs):
super(DecoderBlock, self).__init__(**kwargs)
self.i = i
self.attention1 = d2l.MultiHeadAttention(
key_size, query_size, value_size, num_hiddens, num_heads, dropout)
self.addnorm1 = AddNorm(norm_shape, dropout)
self.attention2 = d2l.MultiHeadAttention(
key_size, query_size, value_size, num_hiddens, num_heads, dropout)
self.addnorm2 = AddNorm(norm_shape, dropout)
self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,num_hiddens)
self.addnorm3 = AddNorm(norm_shape, dropout)
def forward(self, X, state):
enc_outputs, enc_valid_lens = state[0], state[1]
# 训练阶段,输出序列的所有词元都在同⼀时间处理,
# 因此state[2][self.i]初始化为None。
# 预测阶段,输出序列是通过词元⼀个接着⼀个解码的,
# 因此state[2][self.i]包含着直到当前时间步第i个块解码的输出表⽰
if state[2][self.i] is None:
key_values = X
else:
key_values = torch.cat((state[2][self.i], X), axis=1)
state[2][self.i] = key_values
if self.training:
batch_size, num_steps, _ = X.shape
# dec_valid_lens的开头:(batch_size,num_steps),
# 其中每⼀⾏是[1,2,...,num_steps]
dec_valid_lens = torch.arange(
1, num_steps + 1, device=X.device).repeat(batch_size, 1)
else:
dec_valid_lens = None
# ⾃注意⼒
X2 = self.attention1(X, key_values, key_values, dec_valid_lens)
Y = self.addnorm1(X, X2)
# 编码器-解码器注意⼒。
# enc_outputs的开头:(batch_size,num_steps,num_hiddens)
Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)
Z = self.addnorm2(Y, Y2)
return self.addnorm3(Z, self.ffn(Z)), state
同样的,代码中的 pos_encoding与对应,代码中的self.embedding与图中相对应。
TransformerDecoder具体代码如下,各部分的详细对应情况
class TransformerDecoder(d2l.AttentionDecoder):
def __init__(self, vocab_size, key_size, query_size, value_size,
num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
num_heads, num_layers, dropout, **kwargs):
super(TransformerDecoder, self).__init__(**kwargs)
self.num_hiddens = num_hiddens
self.num_layers = num_layers
self.embedding = nn.Embedding(vocab_size, num_hiddens)
self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
self.blks = nn.Sequential()
for i in range(num_layers):
self.blks.add_module("block" + str(i),
DecoderBlock(key_size, query_size, value_size, num_hiddens,
norm_shape, ffn_num_input, ffn_num_hiddens,
num_heads, dropout, i))
self.dense = nn.Linear(num_hiddens, vocab_size)
def init_state(self, enc_outputs, enc_valid_lens, *args):
return [enc_outputs, enc_valid_lens, [None] * self.num_layers]
def forward(self, X, state):
X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
self._attention_weights = [[None] * len(self.blks) for _ in range(2)]
for i, blk in enumerate(self.blks):
X, state = blk(X, state)
# 解码器⾃注意⼒权重
self._attention_weights[0][
i] = blk.attention1.attention.attention_weights
# “编码器-解码器”⾃注意⼒权重
self._attention_weights[1][
i] = blk.attention2.attention.attention_weights
return self.dense(X), state
def attention_weights(self):
return self._attention_weights
3.参数设置与模型图解对应
参数设置大概相当于汽车的各个部件的参数和操控方式,有发动机、传动装置、底盘、前大灯、空调、变速箱等等。 汽车也是个模型,transformer也是个模型,模型内部的构造如此,参数大体相应。
代码中有相关的一些参数设置。参数设置也与内部的元素有关,比如key_size, query_size, value_size与其中的形状设定有关,num_hiddens与的设定有关,ffn_num_input, ffn_num_hiddens与设定有关,norm_shape与的设定有关。num_layers就是中的N,和中的N。
4.模型图解与数据流运转
Transformer的数据流运转很重要,图中有相对清晰的体现,不过细节需要通过代码的执行过程来熟悉和了解,只有把数据流运转弄懂弄通,你才能真正掌握并熟练使用transformer。