2023年可以说是AI备受关注的一年,ChatGPT(GPT3.5)的问世引起了世界的轰动,近期GPT4的出现更是颠覆了各界对深度学习的认知,AI不止停留在特定任务的学习上,诸如此类的大模型已经初步符合人们最开始对AI的幻想——通用模型,通用模型可以处理多种场景下的难题。总所周知这些大模型背后的基础就是Transformer,于2017年提出后就席卷了整个学术界,各领域学者纷纷投入到Transformer的研究中,比如用Transformer取代ResNet作为Backbone取得更好的下游任务,或是单纯就Transformer的网络架构进行改进。“树要长多高,根就要扎多深”,在Transformer构建的金碧辉煌的大厦逐步建起时,不能忘记其最原初的根本。
正值论文写作稍微告一段落,有时间深入理解Transformer工作原理,复现Transformer和Transformer相关问题在工作面试中也是比较常见的,对于深度学习初学者,从零开始实现Transformer即Code the Transformer from scratch可能会是一个很艰巨的任务,甚至望而却步,不可否认在写这篇博客前我也是这样。Transformer的衍生比如BERT、Vision Transformer这种比较大的模型,下意识就会被这个“大”所吓到,觉得凭自己贫瘠的代码功底应该是难以实现。可实际做起来并没有想象那么难,“Just do it!”
希望每位读者看完这篇文章后能手撕Transformer或者对其有更深入的理解。
Transformer的模型流程图如上,取自原论文 Attention is all you need,目前引用量是69364。论文于2017年发表,由Google Brain和Google Research团队研发。据我所知,Transformer最初就是设计出来解决NLP领域问题的,比如机器翻译。
Tip:对于学习的建议是首先观看视频讲解,然后手打打一遍代码,最后看些博客对每个模块细致理解。(推荐视频和博客将放到文末参考文献部分)
接下来会简短介绍一下流程图中的各个部分,之后在下一节详细说明并实现代码。
此处的Embedding包含上图中的Input Embedding和Output Embedding,实际上这部分代码的实现是一样的。按Input Embedding为例,其分为Word Embedding和Positional Embedding(即图中Positional Encoding)。Word Embedding作用是将一个个单词转化为向量,即词嵌入,位置编码Positional Encoding这是给单词句子对应词向量添加位置信息。
论文对位置编码这块并没有解释说明,就直接通过sin和cos对词向量的每个位置元素进行操作,偶数位(2i)使用sin,奇数位(2i+1)使用cos,然后将位置编码直接加(+)到词向量上。
多头注意力机制,也可称为Self-attention自注意力机制,叫法不一。仔细看可以看到图中在Embedding后引申出4路,有3路进入Multi-Head Attention模块,另1路在之后和多头注意力模块的结果Norm后汇合(其实就是相加)。Multi-Head Attention是Transformer的核心,而这3路信息则是Multi-Head Attention的核心。3路分别为Query,Key和Value。
举个例子,图源自The Illustrated Transformer,
假如“Thinking”和“Machines”这两个单词经过Word Embedding和Positional Embedding后为x1和x2,
Tip:其实这里也体现了“Multi”,多头注意力模块把一整个Embedding(比如一个句子的词向量)切分为若干份(比如Heads为8就均分为8段)作为输入,旨在学习不同维度的特征(并没有证明,合情推理)。
然后将x1、x2分别和预先定义好的 W Q W^Q WQ, W K W^K WK, W V W^V WV矩阵进行矩阵乘(或者说线性组合),即可得到q1,k1,v1(同理,q2,k2,v2)。从变量命名不难看出这是基于一套字典检索匹配的策略,给一个Query,然后找到对应的Key,而这个Value呢其实就是所要保存的值。Query是网络当前待查询的值,Key记录着上一次迭代网络的值,Value则负责保存这些值,可能听上去很抽象,类比RNN循环神经网络,Q是现在,K是过去,V用于记录和更新。而注意力所体现的地方就在于矩阵的乘法,类似余弦相似度,Q和K的转置相乘即可得到相似性,相似性越大则Q和K越匹配。如有不懂,可以观看第二节代码帮助理解。
Transformer Block也指Encoder Block,在经过Multi-Head Attention和Add&Norm后,此处必要一提,算是一个细节,Multi-Head Attention后的结果和之前未曾细说的那1路即Value进行相加(Add),这里足以体现Value的记录和存储作用,可见Value在模型中重要性。
这种让模型某一路直接跳到某个模块后相加的操作(ShortCut操作)灵感来源于何恺明大神的ResNet,这是毋庸置疑的。残差的操作避免了梯度弥散的问题,同时也起到特征融合的作用,保留原本的特征。
必要一提,不同于ResNet这些卷积神经网络使用BatchNorm,Transformer使用的是LayerNorm。
LayerNorm论文发表于2016年,见Layer Normalization,可以直接用于RNN神经网络的训练,因为RNN特质对整个Batch的归一化并不现实,这也决定LayerNorm对。
BatchNorm和LayerNorm的比较见下图,图源于paperswithcode.com/method/layer-normalization
N为batch size,C为channel(通道数),H,W代表每个特征向量的高和宽。
简单的理解就是BatchNorm是对一个batch size样本内的每个特征做归一化,LayerNorm是对每个样本的所有特征做归一化。(后续会补充细讲)
使用两层全连接层(Linear)和ReLU激活函数。
Decoder Block的架构大体上和Encoder Block是一致的,唯独不同的是Decoder比Encoder多了Masked Multi-Head Attention。
那么为什么要在Decode阶段对特征(或者说tokens)做mask呢?
Mask的样式如下,白格为0,蓝格为1, 1表示保留的部分,0表示mask的部分:
Transformer针对序列化特征,比如文本数据,这些特征是有前后关系的,为了不让Decoder通过未来的单词(或者说特征)来猜测出当前的特征,将后面的token进行mask,这样模型只能通过过去的词或者说特征元素来预测当前特征元素。
本节的代码取自https://www.kaggle.com/code/arunmohan003/transformer-from-scratch-using-pytorch/notebook,但代码会有些许不同但保证是正确的(因为原来代码一些地方有瑕疵),本人做的工作更多是翻译和整理网上对Transformer的讲解。
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
import warnings
warnings.simplefilter("ignore")
首先,我们需要将输入序列中的每个单词转换为embedding向量。
假设每个embedding维数(embed_dim)是512维,vocabulary词汇表大小(vocab_size)是100,那么embedding词嵌入矩阵的大小将是100x512(行x列)。这些矩阵将在训练中学习,在inference即推理过程中,每个单词将被映射到相应的512维向量。如果batch size为32,序列长度(seq_len)为10(10个单词)。输出将是32x10x512。
Tip:inference推理过程在深度学习中指把从训练中学习到的能⼒应⽤到⼯作中去,也就是在训练完模型后将模型对实际的输入(可以是训练集里的数据也可以是其他数据)进行在线推演输出结果的过程。
对于词嵌入矩阵的构造有专门的nn.Embedding,pytorch官方文档将其称为Sparse Layer(稀疏层),是一个保存了固定字典和大小的简单查找表。这个模块常用来保存词嵌入和用下标检索它们。模块的输入是一个下标的列表,输出是对应的词嵌入。
class WordEmbedding(nn.Module):
def __init__(self, vocab_size, embed_dim):
"""
Args:
vocab_size: size of vocabulary
embed_dim: dimension of embeddings
"""
super(WordEmbedding, self).__init__()
self.embed = nn.Embedding(vocab_size, embed_dim)
def forward(self, x):
"""
Args:
x: input vector
Returns:
out: embedding vector
"""
out = self.embed(x)
return out
回顾到上一节位置编码的公式,公式细节解释:
pos -->指的是当前词在句子中的顺序 [0,max_seq_len)
i -->指沿embedding维度的位置,即 [0,512)
这里注意pos范围不是[0,10),10是输入序列的长度seq_len,因为输入的序列长度一般是小于等于max_seq_len的,也就是位置编码矩阵是固定大小,输入embedding序列是随机长度。
前面我们已经得到32 x 10 x 512的embedding向量。类似地,我们将有尺寸为32 x 10 x 512的位置编码向量。然后我们把两者相加(实际上位置编码矩阵会更大)。
class PositionalEmbedding(nn.Module):
def __init__(self, max_seq_len, embed_model_dim):
"""
Args:
seq_len: length of input sequence
embed_model_dim: demension of embedding
"""
super(PositionalEmbedding, self).__init__()
self.embed_dim = embed_model_dim
pe = torch.zeros(max_seq_len, self.embed_dim)
for pos in range(max_seq_len):
for i in range(0, self.embed_dim, 2):
pe[pos, i] = math.sin(pos / (10000 ** ((2 * i) / self.embed_dim)))
pe[pos, i + 1] = math.cos(pos / (10000 ** ((2 * (i + 1)) / self.embed_dim)))
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
"""
Args:
x: input vector
Returns:
out : output
"""
# make embeddings relatively larger
x = x * math.sqrt(self.embed_dim)
# add constant to embedding
seq_len = x.size(1)
out = x + torch.autograd.Variable(self.pe[:, :seq_len], requires_grad=False)
return out
什么是self-attention自注意力机制?
对于某个句子比如“Dog is crossing the street because it saw the kitchen”,人类可以很容易理解这句话重点是在说狗,而对机器来说就不是那么容易的了。
当模型处理每个单词时,self-attention可以查看输入序列中的其他位置以寻找线索。它将根据每个单词与其他单词的依赖关系创建一个向量。也就是所谓的“完形填空”。这里插播一些非计算机知识,算是一种联想来帮助读者理解。
格式塔系德文“Gestalt”的音译,主要指完形,即具有不同部分分离特性的有机整体。格式塔作为心理学术语,具有两种含义:一指事物的一般属性,即形式;一指事物的个别实体,即分离的整体,形式仅为其属性之一。也就是说,“假使有一种经验的现象,它的每一成分都牵连到其他成分;而且每一成分之所以有其特性,即因为它和其他部分具有关系,这种现象便称为格式塔。”总之,格式塔不是孤立不变的现象,而是指通体相关的完整的现象。完整的现象具有它本身完整的特性,它既不能割裂成简单的元素,同时它的特性又不包含于任何元素之内。——百度百科
下面进入到实例的讲解:
Step1:首先从Encoder的每个输入向量(在本例中,是每个单词的嵌入)中创建三个向量,分别是Query向量、Key向量和Value向量,每个向量的维数都是1x64。这里64是配合上文 512 / 8 = 64 512/8=64 512/8=64,其中8为多头注意力的头的数量。我们将有一个Key矩阵、Query矩阵和一个Value矩阵来生成Key、Query和Value向量,这些矩阵都是在训练中可学习的。
对于经过WordEmbedding和PositionEncoding后的输入张量32x10x512,将其调整为32x10x8x64。
Step2:对应Transformer原论文的Scaled Dot-Product Attention:
Mask是optional(即可选项),Encoder时不需要Mask,Decoder需要Mask。
根据公式:
将Query矩阵与Key矩阵相乘,[Q x K T K^T KT]
当前Key、Query和Key矩阵维度是32x10x8x64。QK相乘之前,我们将对它们进行转置(transpose)以方便相乘(32x8x10x64)。
现在将Query矩阵与转置的Key矩阵相乘。即(32x8x10x64) x (32x8x64x10) -> (32x8x10x10)。
然后将矩阵乘的结果除以Key维度,将32x8x10x10向量除以8,即除以64的平方根(Key矩阵的维数)
接下来是softmax之后结果和Value矩阵(32x8x10x64)相乘:
(32x8x10x10) x (32x8x10x64) -> (32x8x10x64)
Step3:通过Step2得到矩阵,将多头的结果进行合并(concat),然后让其通过线性层,这就形成了多头注意力的输出。图中h表示number of heads,即多头注意力的数量。
(32x8x10x64)张量被转置回为(32x10x8x64),然后Concat为(32x10x512)。最后通过Linear线性层得到(32x10x512)的输出。
class MultiHeadAttention(nn.Module):
def __init__(self, embed_dim=512, n_heads=8):
"""
Args:
embed_dim: dimension of embeding vector output
n_heads: number of self attention heads
"""
super(MultiHeadAttention, self).__init__()
self.embed_dim = embed_dim # 512 dim
self.n_heads = n_heads # 8
self.single_head_dim = int(self.embed_dim / self.n_heads) # 512/8 = 64 . each key,query, value will be of 64d
# key,query and value matrixes #64 x 64
self.query_matrix = nn.Linear(self.single_head_dim, self.single_head_dim,
bias=False) # single key matrix for all 8 keys #512x512
self.key_matrix = nn.Linear(self.single_head_dim, self.single_head_dim, bias=False)
self.value_matrix = nn.Linear(self.single_head_dim, self.single_head_dim, bias=False)
self.out = nn.Linear(self.n_heads * self.single_head_dim, self.embed_dim)
def forward(self, key, query, value, mask=None): # batch_size x sequence_length x embedding_dim # 32 x 10 x 512
"""
Args:
key : key vector
query : query vector
value : value vector
mask: mask for decoder
Returns:
output vector from multihead attention
"""
batch_size = key.size(0)
seq_length = key.size(1)
# query dimension can change in decoder during inference.
# so we cant take general seq_length
seq_length_query = query.size(1)
# 32x10x512
key = key.view(batch_size, seq_length, self.n_heads,
self.single_head_dim) # batch_size x sequence_length x n_heads x single_head_dim = (32x10x8x64)
query = query.view(batch_size, seq_length_query, self.n_heads, self.single_head_dim) # (32x10x8x64)
value = value.view(batch_size, seq_length, self.n_heads, self.single_head_dim) # (32x10x8x64)
k = self.key_matrix(key) # (32x10x8x64)
q = self.query_matrix(query)
v = self.value_matrix(value)
q = q.transpose(1, 2) # (batch_size, n_heads, seq_len, single_head_dim) # (32 x 8 x 10 x 64)
k = k.transpose(1, 2) # (batch_size, n_heads, seq_len, single_head_dim)
v = v.transpose(1, 2) # (batch_size, n_heads, seq_len, single_head_dim)
# computes attention
# adjust key for matrix multiplication
k_adjusted = k.transpose(-1, -2) # (batch_size, n_heads, single_head_dim, seq_ken) #(32 x 8 x 64 x 10)
product = torch.matmul(q, k_adjusted) # (32 x 8 x 10 x 64) x (32 x 8 x 64 x 10) = #(32x8x10x10)
# fill those positions of product matrix as (-1e20) where mask positions are 0
if mask is not None:
product = product.masked_fill(mask == 0, float("-1e20"))
# divising by square root of key dimension
product = product / math.sqrt(self.single_head_dim) # / sqrt(64)
# applying softmax
scores = F.softmax(product, dim=-1)
# mutiply with value matrix
scores = torch.matmul(scores, v) ##(32x8x 10x 10) x (32 x 8 x 10 x 64) = (32 x 8 x 10 x 64)
# concatenated output
concat = scores.transpose(1, 2).contiguous().view(batch_size, seq_length_query,
self.single_head_dim * self.n_heads) # (32x8x10x64) -> (32x10x8x64) -> (32,10,512)
output = self.out(concat) # (32,10,512) -> (32,10,512)
return output
Warning: 在学习Vision Transformer代码时,回过头来发现以上的MultiHeadAttention这部分代码是有些许问题,因为这部分代码是借用Kaggle上的,所以写作当时没有意识到。不过也谈不上错误,只能说那么设计有些不恰当,不知道读者能否找出问题所在。答案将在Pytorch动手实现Transformer机器翻译这篇博客中给出,历时请对比代码。
class TransformerBlock(nn.Module):
def __init__(self, embed_dim, expansion_factor=4, n_heads=8):
super(TransformerBlock, self).__init__()
"""
Args:
embed_dim: dimension of the embedding
expansion_factor: fator ehich determines output dimension of linear layer
n_heads: number of attention heads
"""
self.attention = MultiHeadAttention(embed_dim, n_heads)
self.norm1 = nn.LayerNorm(embed_dim)
self.norm2 = nn.LayerNorm(embed_dim)
self.feed_forward = nn.Sequential(
nn.Linear(embed_dim, expansion_factor * embed_dim),
nn.ReLU(),
nn.Linear(expansion_factor * embed_dim, embed_dim)
)
self.dropout1 = nn.Dropout(0.2)
self.dropout2 = nn.Dropout(0.2)
def forward(self, key, query, value):
"""
Args:
key: key vector
query: query vector
value: value vector
norm2_out: output of transformer block
"""
attention_out = self.attention(key, query, value) # 32x10x512
attention_residual_out = attention_out + value # 32x10x512
norm1_out = self.dropout1(self.norm1(attention_residual_out)) # 32x10x512
feed_fwd_out = self.feed_forward(norm1_out) # 32x10x512 -> #32x10x2048 -> 32x10x512
feed_fwd_residual_out = feed_fwd_out + norm1_out # 32x10x512
norm2_out = self.dropout2(self.norm2(feed_fwd_residual_out)) # 32x10x512
return norm2_out
class TransformerEncoder(nn.Module):
"""
Args:
seq_len : length of input sequence
embed_dim: dimension of embedding
num_layers: number of encoder layers
expansion_factor: factor which determines number of linear layers in feed forward layer
n_heads: number of heads in multihead attention
Returns:
out: output of the encoder
"""
def __init__(self, seq_len, vocab_size, embed_dim, num_layers=2, expansion_factor=4, n_heads=8):
super(TransformerEncoder, self).__init__()
self.embedding_layer = WordEmbedding(vocab_size, embed_dim)
self.positional_encoder = PositionalEmbedding(seq_len, embed_dim)
self.layers = nn.ModuleList([TransformerBlock(embed_dim, expansion_factor, n_heads) for i in range(num_layers)])
def forward(self, x):
embed_out = self.embedding_layer(x)
out = self.positional_encoder(embed_out)
for layer in self.layers:
out = layer(out, out, out)
return out # 32x10x512
在Decoder中有两种multi-head attention,一种叫decoder attention,另一种叫encoder decoder attention。
首先是使用带mask的multi-head attention即decoder attention。
为什么要使用Mask呢?
使用mask掩码是因为在创建目标词的注意力时,我们不需要一个词来查看将来的词来检查依赖关系。其实也是一个历史问题,在NLP发展史中对文本序列token的处理类似GPT2的Auto-regressive(自回归)的Mask,就只对先前的tokens进行注意力学习,而不让模型通过未知的后续token来猜测当前token,否则模型会直接copy下一个单词的token。后来的BERT用的就是双向的策略,而不只是看过去的token而是从过去到未来再从未来到过去两个方向进行预测。
例如:在单词“I am a student”中,我们不需要单词“a”来查找单词“student”,没有mask的话当模型看到“a”就会直接给出"student"的预测。
对于Mask这一三角矩阵,如第一节中Decoder Block的图所示,对于所有零位置,用负无穷大填充,而在代码中,用一个非常小的数字填充它以避免除法错误(比如 -1e20)。
对于这个多头注意力,从Encoder的输出中创建Key和Value向量。Query是从上一个Decoder层的输出创建得到的。流程就如下图所示:
class DecoderBlock(nn.Module):
def __init__(self, embed_dim, expansion_factor=4, n_heads=8):
super(DecoderBlock, self).__init__()
"""
Args:
embed_dim: dimension of the embedding
expansion_factor: fator ehich determines output dimension of linear layer
n_heads: number of attention heads
"""
self.attention = MultiHeadAttention(embed_dim, n_heads=8)
self.norm = nn.LayerNorm(embed_dim)
self.dropout = nn.Dropout(0.2)
self.transformer_block = TransformerBlock(embed_dim, expansion_factor, n_heads)
def forward(self, key, query, x, mask):
"""
Args:
key: key vector
query: query vector
x: value vector
mask: mask to be given for multi head attention
Returns:
out: output of transformer block
"""
# we need to pass mask mask only to fst attention
attention = self.attention(x, x, x, mask=mask) # 32x10x512
value = self.dropout(self.norm(attention + x))
out = self.transformer_block(key, query, value)
return out
class TransformerDecoder(nn.Module):
def __init__(self, target_vocab_size, embed_dim, seq_len, num_layers=2, expansion_factor=4, n_heads=8):
super(TransformerDecoder, self).__init__()
"""
Args:
target_vocab_size: vocabulary size of taget
embed_dim: dimension of embedding
seq_len : length of input sequence
num_layers: number of encoder layers
expansion_factor: factor which determines number of linear layers in feed forward layer
n_heads: number of heads in multihead attention
"""
self.word_embedding = WordEmbedding(target_vocab_size, embed_dim)
self.position_embedding = PositionalEmbedding(seq_len, embed_dim)
self.layers = nn.ModuleList(
[
DecoderBlock(embed_dim, expansion_factor=4, n_heads=8)
for _ in range(num_layers)
]
)
self.fc_out = nn.Linear(embed_dim, target_vocab_size)
self.dropout = nn.Dropout(0.2)
def forward(self, x, enc_out, mask):
"""
Args:
x: input vector from target
enc_out : output from encoder layer
trg_mask: mask for decoder self attention
Returns:
out: output vector
"""
x = self.word_embedding(x) # 32x10x512
x = self.position_embedding(x) # 32x10x512
x = self.dropout(x)
for layer in self.layers:
x = layer(enc_out, x, enc_out, mask)
out = F.softmax(self.fc_out(x))
return out
class Transformer(nn.Module):
def __init__(self, embed_dim, src_vocab_size, target_vocab_size, seq_length, num_layers=2, expansion_factor=4,
n_heads=8):
super(Transformer, self).__init__()
"""
Args:
embed_dim: dimension of embedding
src_vocab_size: vocabulary size of source
target_vocab_size: vocabulary size of target
seq_length : length of input sequence
num_layers: number of encoder layers
expansion_factor: factor which determines number of linear layers in feed forward layer
n_heads: number of heads in multihead attention
"""
self.target_vocab_size = target_vocab_size
self.encoder = TransformerEncoder(seq_length, src_vocab_size, embed_dim, num_layers=num_layers,
expansion_factor=expansion_factor, n_heads=n_heads)
self.decoder = TransformerDecoder(target_vocab_size, embed_dim, seq_length, num_layers=num_layers,
expansion_factor=expansion_factor, n_heads=n_heads)
def make_trg_mask(self, trg):
"""
Args:
trg: target sequence
Returns:
trg_mask: target mask
"""
batch_size, trg_len = trg.shape
# returns the lower triangular part of matrix filled with ones
trg_mask = torch.tril(torch.ones((trg_len, trg_len))).expand(
batch_size, 1, trg_len, trg_len
)
return trg_mask
def decode(self, src, trg):
"""
for inference
Args:
src: input to encoder
trg: input to decoder
out:
out_labels : returns final prediction of sequence
"""
trg_mask = self.make_trg_mask(trg)
enc_out = self.encoder(src)
out_labels = []
batch_size, seq_len = src.shape[0], src.shape[1]
# outputs = torch.zeros(seq_len, batch_size, self.target_vocab_size)
out = trg
for i in range(seq_len): # 10
out = self.decoder(out, enc_out, trg_mask) # bs x seq_len x vocab_dim
# taking the last token
out = out[:, -1, :]
out = out.argmax(-1)
out_labels.append(out.item())
out = torch.unsqueeze(out, 0)
return out_labels
def forward(self, src, trg):
"""
Args:
src: input to encoder
trg: input to decoder
out:
out: final vector which returns probabilities of each target word
"""
trg_mask = self.make_trg_mask(trg)
enc_out = self.encoder(src)
outputs = self.decoder(trg, enc_out, trg_mask)
return outputs
验证模型是否可正常运行,可以通过如下代码进行测试。如果没有报错,能输出结果那就没有问题。
if __name__ == '__main__':
src_vocab_size = 11
target_vocab_size = 11
num_layers = 6
seq_length = 12
# let 0 be sos(start operation signal) token and 1 be eos(end operation signal) token
src = torch.tensor([[0, 2, 5, 6, 4, 3, 9, 5, 2, 9, 10, 1],
[0, 2, 8, 7, 3, 4, 5, 6, 7, 2, 10, 1]])
target = torch.tensor([[0, 1, 7, 4, 3, 5, 9, 2, 8, 10, 9, 1],
[0, 1, 5, 6, 2, 4, 7, 6, 2, 8, 10, 1]])
print(src.shape, target.shape)
model = Transformer(embed_dim=512, src_vocab_size=src_vocab_size,
target_vocab_size=target_vocab_size, seq_length=seq_length,
num_layers=num_layers, expansion_factor=4, n_heads=8)
# print(model)
out = model(src, target)
print(out)
print(out.shape)
print("=" * 50)
# inference
src = torch.tensor([[0, 2, 5, 6, 4, 3, 9, 5, 2, 9, 10, 1]])
trg = torch.tensor([[0]])
print(src.shape, trg.shape)
out = model.decode(src, trg)
print(out)
即使现在ChatGPT等大模型的问世引起了社会广泛关注,但仍理性看待Transformer及其应用范围,在工业界仍旧是以ResNet为首的卷积神经网络占大头。而Transformer在学术界的地位是无法取代的,因此了解最基础的Transformer(Naive Transformer)也是很有必要性的。后续可以关注一下本博客衍生出的Vision Transformer的介绍和实现(挖个坑)……
2023.3.26.18:43 由于时间关系,先发布出来,后面会继续更进补充每个模块的要点和细节讲解。
2023.3.27.13:22 补充Word Embedding和Position Encoding的讲解。
2023.3.27.19:23 补充Multi-Head Attention讲解
2023.3.28.15:27 补充Transformer Decoder讲解
B站视频链接:Transformer从零详细解读(可能是你见过最通俗易懂的讲解)
YouTube视频链接:Pytorch Transformers from Scratch (Attention is all you need) 【友情提示:此视频的代码有略微错误】
Ba J L, Kiros J R, Hinton G E. Layer normalization[J]. arXiv preprint arXiv:1607.06450, 2016.
Vaswani A, Shazeer N, Parmar N, et al. Attention is all you need[J]. Advances in neural information processing systems, 2017, 30.
https://www.kaggle.com/code/arunmohan003/transformer-from-scratch-using-pytorch/notebook
http://jalammar.github.io/illustrated-transformer/
https://theaisummer.com/transformer/
https://towardsdatascience.com/7-things-you-didnt-know-about-the-transformer-a70d93ced6b2
http://nlp.seas.harvard.edu/2018/04/03/attention.html
https://peterbloem.nl/blog/transformers
https://towardsdatascience.com/how-to-code-the-transformer-in-pytorch-24db27c8f9ec
https://github.com/hyunwoongko/transformer