【超详细】初学者包会的VisionTransformer(ViT)的PyTorch实现代码学习本⽂是博主跟着b站up霹雳吧啦Wz的学习的⾃我记录,图⽚均为该视频截图。
代码来源timm库(PyTorchImageModels,简称timm)是⼀个巨⼤的PyTorch代码集合,已经被官⽅使⽤了。
放⼀些链接:up霹雳吧啦Wz针对ViT写的,论⽂原⽂,timm库作者的,timm库,timm库的,以及⼀个⾮官⽅的timm库的。
招商引资工作方案模型⽰意图(Ba16为例)
图⽚来⾃视频截图和论⽂截图
PatchEmbed模块
class PatchEmbed(nn.Module):
""" 2D Image to Patch Embedding
"""
def__init__(lf, img_size=224, patch_size=16, in_chans=3, embed_dim=768, norm_layer=None, flatten=True):
super().__init__()
自制粉蒸肉
img_size = to_2tuple(img_size)
patch_size = to_2tuple(patch_size)
lf.img_size = img_size
lf.patch_size = patch_size
lf.num_patches = lf.grid_size[0]* lf.grid_size[1]
#num_patches=14*14
lf.flatten = flatten
对岗位的认识lf.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)
#proj使⽤卷积,embed_dimension这⼀参数在vision transformer的ba16模型⽤到的是768,所以默认是768。但是如果是large或者huge模型的话embe d_dim也会变。
< = norm_layer(embed_dim)if norm_layer el nn.Identity()
#norm_layer默认是None,就是进⾏nn.Identity()也就是不做任何操作;如果有传⼊(⾮None),则会进⾏初始化⼀个norm_layer。
def forward(lf, x):
B, C, H, W = x.shape
asrt H == lf.img_size[0]and W == lf.img_size[1], \
f"Input image size ({H}*{W}) doesn't match model ({lf.img_size[0]}*{lf.img_size[1]})."
#asrt:进⾏判断,如果代码模型定义和实际输⼊尺⼨不同则会报错
x = lf.proj(x)#⽤卷积实现序列化
if lf.flatten:
x = x.flatten(2).transpo(1,2)# BCHW -> BNC
#flatten(2)操作实现了[B,C,H,W,]->[B,C,HW],指从维度2开始进⾏展平
#transpo(1,2)操作实现了[B,C,HW]->[B,HW,C]
x = lf.norm(x)
#通过norm层输出
return x
Attention模块
该模块实现多头注意⼒机制。
class Attention(nn.Module):
def__init__(lf,
dim,#输⼊token的dim
num_heads=8,#多头注意⼒中head的个数
qkv_bias=Fal,#在⽣成qkv时是否使⽤偏置,默认否
attn_drop=0.,
proj_drop=0.):
super().__init__()
lf.num_heads = num_heads
head_dim = dim // num_heads #计算每⼀个head需要传⼊的dim
lf.scale = head_dim **-0.5#head_dim的-0.5次⽅,即1/根号d_k,即理论公式⾥的分母根号d_k
lf.qkv = nn.Linear(dim, dim *3, bias=qkv_bias)#qkv是通过1个全连接层参数为dim和3dim进⾏初始化的,也可以使⽤3个全连接层参数为dim和dim进⾏初始化,⼆者没有区别,
有关爱的故事
lf.attn_drop = nn.Dropout(attn_drop)#定义dp层⽐率attn_drop
篮球哪个牌子好lf.proj = nn.Linear(dim, dim)#再定义⼀个全连接层,是将每⼀个head的结果进⾏拼接的时候乘的那个矩阵W^O
lf.proj_drop = nn.Dropout(proj_drop)#定义dp层⽐率proj_drop
def forward(lf, x):#正向传播过程
#输⼊是[batch_size,
# num_patches+1, (ba16模型的这个数是14*14)
# total_embed_dim(ba16模型的这个数是768)]
B, N, C = x.shape社会责任
qkv = lf.qkv(x).reshape(B, N,3, lf.num_heads, C // lf.num_heads).permute(2,0,3,1,4)
#qkv->[batchsize, num_patches+1, 3*total_embed_dim]
#reshape->[batchsize, num_patches+1, 3, num_heads, embed_dim_per_head]颋
#permute->[3, batchsize, num_heads, num_patches+1, embed_dim_per_head]
q, k, v = qkv[0], qkv[1], qkv[2]
# make torchscript happy (cannot u tensor as tuple)
#q、k、v⼤⼩均[batchsize, num_heads, num_patches+1, embed_dim_per_head]
attn =(q @ k.transpo(-2,-1))* lf.scale
#现在的操作都是对每个head进⾏操作
#transpo是转置最后2个维度,@就是矩阵乘法的意思
#q [batchsize, num_heads, num_patches+1, embed_dim_per_head]
#k^T[batchsize, num_heads, embed_dim_per_head, num_patches+1]
#q*k^T=[batchsize, num_heads, num_patches+1, num_patches+1]
#lf.scale=head_dim的-0.5次⽅
#⾄此完成了(Q*K^T)/根号d_k的操作
attn = attn.softmax(dim=-1)
#dim=-1表⽰在得到的结果的每⼀⾏上进⾏softmax处理,-1就是最后1个维度
#⾄此完成了softmax[(Q*K^T)/根号d_k]的操作
attn = lf.attn_drop(attn)
x =(attn @ v).transpo(1,2).reshape(B, N, C)
#@->[batchsize, num_heads, num_patches+1, embed_dim_per_head]
#这⼀步矩阵乘积就是加权求和
#transpo->[batchsize, num_patches+1, num_heads, embed_dim_per_head]
#reshape->[batchsize, num_patches+1, num_heads*embed_dim_per_head]即[batchsize, num_patches+1, total_embed_dim]
#reshape实际上就实现了concat拼接
x = lf.proj(x)
#将上⼀步concat的结果通过1个线性映射,通常叫做W,此处⽤全连接层实现
x = lf.proj_drop(x)
#dropout
王文浩#⾄此完成了softmax[(Q*K^T)/根号d_k]*V的操作
#⼀个head的attention的全部操作就实现了
return x
MLP Block(图中的名字)/FeedForward类(代码中的实现)
class FeedForward(nn.Module):
#全连接层1+GELU+dropout+全连接层2+dropout
#全连接层1的输出节点个数是输⼊节点个数的4倍,即mlp_ratio=4.
#全连接层2的输⼊节点个数是输出节点个数的1/4
def__init__(lf, dim, hidden_dim, dropout =0.):
super().__init__()
lf = nn.Sequential(
nn.Linear(dim, hidden_dim),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(hidden_dim, dim),
nn.Dropout(dropout)
)
def forward(lf, x):
return lf(x)
Encoder Block主模块
实现了对EncoderBlock重复堆叠L次的时候的每⼀次的结构,如下:
左图为视频截图,右图为原论⽂截图