当前位置: 首页 > news >正文

网站建设价格西安/搜索引擎优化的主要内容

网站建设价格西安,搜索引擎优化的主要内容,汕头网站公司,南宁市建设局网站文章目录 前言一、Transformer1.Transformer概览2.Self-Attention3.Multi-head Attention4.Position-wise Feed-Forward Networks(位置前馈网络)5.残差连接和层归一化6.Positional Encodings(位置编码) 二、Vision Transformer1.Vision Transformer概览2.Embedding层结构&#…

文章目录

  • 前言
  • 一、Transformer
    • 1.Transformer概览
    • 2.Self-Attention
    • 3.Multi-head Attention
    • 4.Position-wise Feed-Forward Networks(位置前馈网络)
    • 5.残差连接和层归一化
    • 6.Positional Encodings(位置编码)
  • 二、Vision Transformer
    • 1.Vision Transformer概览
    • 2.Embedding层结构
      • 🥇 Image Patching(图像分块)
      • 🥈Patch Embedding(图像块嵌入)
      • 🥉Class token
      • 🏅Position Embedding
    • 3.Transformer Encoder
    • 4.MLP Head
    • 5.ViT B/16网络结构
  • 三、Hybrid混合模型
  • 四、ViT网络实现
    • 1.构建ViT网络
    • 2.训练和测试模型
  • 五、实现图像分类
  • 结束语


  • 💂 个人主页:风间琉璃
  • 🤟 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主
  • 💬 如果文章对你有帮助欢迎关注点赞收藏(一键三连)订阅专栏

前言

Vision Transformer(ViT)是将Transformer模型应用于计算机视觉领域的方法,用于图像分类任务。与传统的卷积神经网络(CNN)不同,ViT通过将图像分成固定大小的图块(Image Patches)并展平成序列,然后将序列输入Transformer模型进行处理。在Transformer中,Self-Attention结构被用来捕捉序列中不同位置的关联信息。通过多层的Transformer编码器,ViT能够从输入图像中学习到更高级的特征表示,最终输出图像的类别预测结果。


一、Transformer

Transformer是2017年Google在Computation and Language上发表的,当时主要是针对自然语言处理领域提出的。RNN模型记忆长度有限且无法并行化,只有计算完 t i t_i ti时刻后的数据才能计算 t i + 1 t_{i+1} ti+1时刻的数据,而Transformer都可以做到。

1.Transformer概览

Transformer模型是一种广泛应用于各个领域的深度学习模型,它是一种基于自注意力机制的编码器-解码器架构。与传统的编码器-解码器模型不同,Transformer模型没有使用传统的卷积神经网络(CNN)和循环神经网络(RNN)方法和模块。

首先,让我们先将 Transformer 模型视为一个黑盒,如图所示。
Transformer模型
在机器翻译任务中,将一种语言的一个句子作为输入,然后将其翻译成另一种语言的一个句子作为输出

Transformer模型的核心思想是通过自注意力机制来捕捉输入序列中不同位置之间的依赖关系。Transformer 本质上是一个Encoder-Decoder 架构。因此中间部分的 Transformer 可以分为两个部分:编码组件和解码组件
在这里插入图片描述
编码器部分,输入序列经过多个相同的编码器层进行处理,每个编码器层由一个多头自注意力机制和一个前馈神经网络组成。在解码器部分,输出序列的每个位置通过注意力机制对编码器部分的输出进行查找,以便生成正确的输出。

其中,编码组件由多层编码器(Encoder)组成(在论文中作者使用了 6 层编码器,在实际使用过程中可以尝试其他层数)。解码组件也是由相同层数的解码器(Decoder)组成(在论文也使用了 6 层)。如图所示:
在这里插入图片描述
每个编码器由两个子层组成:Self-Attention 层(自注意力层)Position-wise Feed Forward Network(前馈网络,缩写为 FFN)如下图所示。每个编码器的结构都是相同的,但是它们使用不同的权重参数。
在这里插入图片描述
编码器的输入会先流入 Self-Attention 层,它可以让编码器在对特定词进行编码时使用输入句子中的其他词的信息,可以理解为:当我们翻译一个词时,不仅只关注当前的词,而且还会关注其他词的信息。然后,Self-Attention 层的输出会流入前馈网络。

解码器也有编码器中这两层,但是它们之间还有一个注意力层,即 Encoder-Decoder Attention其用来帮忙解码器关注输入句子的相关部分。
在这里插入图片描述
Transformer模型的优点包括并行计算能力强、捕捉长距离依赖关系能力强、易于训练和扩展性好等。这些特性使得Transformer模型在自然语言处理、机器翻译、语音识别等领域取得了显著的成果。

一个典型的 Transformer 结构如下图所示:
在这里插入图片描述
Transformer由一个编码器和一个解码器组成 。每个编码器块主要由一个多头 self-attention 模块和一个位置前馈网络(FFN)组成。为了构建更深的模型,每个模块周围都采用了残差连接,然后是层归一化模块。与编码器块相比,解码器块在多头 self-attention 模块和位置方面 FFN 之间额外插入了 cross-attention 模块。此外,解码器中的 self-attention 模块用于防止每个位置影响后续位置。

2.Self-Attention

在论文中作者提出了Self-Attention的概念,然后在此基础上提出Multi-Head Attention

首先通过一个例子,来对 Self-Attention 有一个直观的认识。假如,我们要翻译下面这个句子:

The animal didn’t cross the street because it was too tired

这个句子中的 it 指的是什么?是指 animal 还是 street ?对我们来说,这是一个简单的问题,但是算法来说却不那么简单。当模型在处理 it 时,Self-Attention 机制使其能够将 it 和 animal 关联起来

当模型处理每个词(输入序列中的每个位置)时,Self-Attention 机制使得模型不仅能够关注当前位置的词,而且能够关注句子中其他位置的词,从而可以更好地编码这个词。

如果你熟悉 RNN,想想如何维护隐状态,使 RNN 将已处理的先前词/向量的表示与当前正在处理的词/向量进行合并。Transformer 使用 Self-Attention 机制将其他词的理解融入到当前词中
在这里插入图片描述
当编码器对单词”it“进行编码时,有一部分注意力集中在”The animal“上,并将它们的部分信息融入到”it“的编码中

Self-Attention其基本结构如下图所示:
在这里插入图片描述
对于 Self Attention 来讲,Q(Query),K(Key)和 V(Value)三个矩阵均来自同一输入,并按照以下步骤计算:
⋆ \star 首先计算 Q 和 K 之间的点积,为了防止其结果过大,会除以 d k \sqrt{d_{k}} dk ,其中 d k d_{k} dk为 Key 向量的维度。

⋆ \star 然后利用 Softmax 操作将其结果归一化为概率分布,再乘以矩阵 V 就得到权重求和的表示。

整个计算过程表示如下:
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V) = softmax(\cfrac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V

如上图所示,QKV矩阵是在自注意力机制(Self-Attention Mechanism)中用于计算注意力权重的三个矩阵。这三个矩阵通常是通过对输入序列进行线性变换得到的。它们分别是:

⋆ \star Q矩阵(Query Matrix): Q矩阵用于生成查询向量,每个查询向量代表一个小块(Patch)。在注意力机制中的查询,即用寻找与当前小块相关的信息

⋆ \star K矩阵(Key Matrix): K矩阵用于生成键向量,每个键向量代表一个小块(Patch)。在注意力机制中的键,即用于表示当前小块与其他小块之间的关系

⋆ \star V矩阵(Value Matrix): V矩阵用于生成值向量,每个值向量代表一个小块(Patch。在注意力机制中的值,即用于表示当前小块的特征信息。

在自注意力机制中,输入序列首先通过三个不同的线性变换,分别得到查询矩阵Q、键矩阵K和值矩阵V。这三个矩阵将用于计算注意力权重从而对输入序列进行加权求和,得到最终的表示。其中,Q和K的点乘得到的矩阵就是注意力权重矩阵A。假设如果只有V矩阵,不经过Q和K的过程,那么这就算是普通的网络,没有加入注意力机制。

举例:假设输入的序列长度为2,输入就两个节点 x 1 , x 2 x_1,x_2 x1x2,然后通过Input Embedding,即图中的f(x)将输入映射 a 1 , a 2 a_1,a_2 a1a2。紧接着分别将 a 1 , a 2 a_1,a_2 a1a2分别通过三个变换矩阵$W_q,W_k,W_v$(这三个参数是可训练的,是共享的)得到对应的 q i , k i , v i q^i,k^i,v^i qikivi(这里在源码中是直接使用全连接层实现的,这里为了方便理解,忽略偏执)。
在这里插入图片描述
⋆ \star q代表query,后续会去和每一个k进行匹配
⋆ \star k代表key,后续会被每个q匹配
⋆ \star v代表从a中提取得到的信息,即输入的数据
⋆ \star q和k匹配的过程可以理解成计算两者的相关性,相关性越大对应v的权重也就越大,q和k一系列运算都是为了计算v的权重。

假设: a 1 = ( 1 , 1 ) , a 2 = ( 1 , 0 ) , W q = ( 1 1 0 1 ) a_1=(1,1),a_2=(1,0), W^q = \begin{pmatrix} 1 & 1 \\ 0 & 1 \end{pmatrix}\\ a1=(1,1)a2=(1,0),Wq=(1011)
则(Transformer可以并行化):
( q 1 q 2 ) \qquad \qquad \qquad \qquad \qquad \begin{pmatrix} q^1 \\ q^2 \end{pmatrix} (q1q2) = ( 1 1 1 0 ) \begin{pmatrix} 1&1 \\ 1&0 \end{pmatrix} (1110) ( 1 1 0 1 ) \begin{pmatrix} 1&1 \\ 0&1 \end{pmatrix} (1011) = ( 1 2 1 1 ) \begin{pmatrix} 1&2 \\ 1&1 \end{pmatrix} (1121)

( q 1 q 2 ) \begin{pmatrix} q^1 \\ q^2 \end{pmatrix} (q1q2)即为论文公式中的Q, ( k 1 k 2 ) \begin{pmatrix} k^1 \\ k^2 \end{pmatrix} (k1k2)即为论文公式中的K, ( v 1 v 2 ) \begin{pmatrix} v^1 \\ v^2 \end{pmatrix} (v1v2)即为论文公式中的V。然后用 q 1 q_1 q1和每个k进行点乘操作,并除以 d \sqrt{d} d 就可以得到对应的 α \alpha α, 其中d代表向量 k i k^i ki的长度(k=2),除以 d \sqrt{d} d 的原因是因为进行点乘后的数值很大,会导致通过softmax后梯度变得很小,所以除以 d \sqrt{d} d 进行缩放。比如 α 1 , 1 \alpha_{1,1} α1,1的计算:
α 1 , 1 = q 1 ⋅ k 1 d = 1 ⋅ 1 + 2 ⋅ 0 2 = 0.71 \alpha_{1,1}=\cfrac{q^1·k^1}{\sqrt{d}} = \cfrac{1·1+2·0}{\sqrt{2}} = 0.71 α1,1=d q1k1=2 11+20=0.71
使用矩阵计算:
( α 1 , 1 α 1 , 2 α 2 , 1 α 2 , 2 ) = ( q 1 q 2 ) ( k 1 k 2 ) T d \begin{pmatrix} \alpha_{1,1} & \alpha_{1,2} \\ \alpha_{2,1} & \alpha_{2,2} \end{pmatrix} = \cfrac{\begin{pmatrix} q^1 \\ q^2 \end{pmatrix} \begin{pmatrix} k^1 \\ k^2 \end{pmatrix}^{T} }{ \sqrt{d}} (α1,1α2,1α1,2α2,2)=d (q1q2)(k1k2)T

完成步骤1,接着对每一行即( α 1 , 1 , α 1 , 2 \alpha_{1,1} ,\alpha_{1,2} α1,1,α1,2)( α 2 , 1 , α 2 , 2 \alpha_{2,1} ,\alpha_{2,2} α2,1,α2,2)分别进行softmax处理得到( α ^ 1 , 1 , α ^ 1 , 2 \widehat{\alpha}_{1,1} ,\widehat{\alpha}_{1,2} α 1,1,α 1,2)( α ^ 2 , 1 , α ^ 2 , 2 \widehat{\alpha}_{2,1} ,\widehat{\alpha}_{2,2} α 2,1,α 2,2), 其中$\widehat{\alpha}$相当于计算得到针对每个v的权重。至此,完成Attention(Q,K,V)公式的 s o f t m a x ( Q K T d k ) softmax(\cfrac{QK^T}{\sqrt{d_k}}) softmax(dk QKT)部分计算。
具体计算流程如下图所示:
在这里插入图片描述
上面已经计算得到 α \alpha α即针对每个v的权重,接着进行加权得到最终结果:
( b 1 b 2 ) \qquad \qquad \qquad \qquad \qquad \begin{pmatrix} b^1 \\ b^2 \end{pmatrix} (b1b2) = ( α ^ 1 , 1 α ^ 1 , 2 α ^ 2 , 1 α ^ 2 , 2 ) \begin{pmatrix} \widehat{\alpha}_{1,1} &\widehat{\alpha}_{1,2} \\ \widehat{\alpha}_{2,1} &\widehat{\alpha}_{2,2} \end{pmatrix} (α 1,1α 2,1α 1,2α 2,2) ( v 1 v 2 ) \begin{pmatrix} v^1 \\ v^2 \end{pmatrix} (v1v2)
在这里插入图片描述
至此,Self-Attention的公式计算完成。

3.Multi-head Attention

在 Transformer 论文中,通过添加一种多头注意力机制使用多头注意力机制能够联合来自不同head部分学习到的信息, 进一步完善了自注意力层。具体做法:首先,通过h个不同的线性变换对 Query、Key 和 Value 进行映射;然后,将不同的 Attention 拼接起来;最后,再进行一次线性变换。基本结构如图所示:
在这里插入图片描述
每一组注意力用于将输入映射到不同的子表示空间,这使得模型可以在不同子表示空间中关注不同的位置。整个计算过程可表示为:
M u l t i H e a d ( Q , K , V ) = C o n c a t ( h e a d 1 1 , . . . , h e a d h ) W O MultiHead(Q,K,V) = Concat(head1_1,...,head_h)W^O MultiHead(Q,K,V)=Concat(head11,...,headh)WO w h e r e h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) where \qquad head_i = Attention(QW_i^Q,KW_i^K,VW_i^V) whereheadi=Attention(QWiQ,KWiK,VWiV) 其中 W i Q ∈ R d m o d e l × d k W_i^Q \in \Bbb{R}^{d_{model} \times d_k} WiQRdmodel×dk W i K ∈ R d m o d e l × d k W_i^K \in \Bbb{R}^{d_{model} \times d_k} WiKRdmodel×dk W i V ∈ R d m o d e l × d v W_i^V \in \Bbb{R}^{d_{model} \times d_v} WiVRdmodel×dv W i O ∈ R h d v × d m o d e l W_i^O \in \Bbb{R}^{hd_v \times d_{model}} WiORhdv×dmodel
在论文中,指定h=8,即使用8个注意力头。 d k = d v = d m o d e l / h = 64 d_k = d_v = d_{model} / h = 64 dk=dv=dmodel/h=64

注意:
⋆ \star d m o d e l d_{model} dmodel表示Multi-Head Self-Attention输入输出的token维度(向量长度)

⋆ \star d k , d v d_k, d_v dk,dv 表示Multi-Head Self-Attention中每个head的key(K)以及query(Q)的维度

举例:首先和Self-Attention模块一样将 a i a_i ai分别通过 W q , W k , W v W^q,W^k,W^v WqWkWv得到对应的 q i , k i , v i q^i,k^i,v^i qiki,vi,然后根据使用的head的数目h进一步把得到的 q i , k i , v i q^i,k^i,v^i qiki,vi均分成h份。
在这里插入图片描述
上图假设h=2,将得到的 q 1 = 1 , 1 , 0 , 1 q^1={1,1,0,1} q1=1101分为 q 1 , 1 = ( 1 , 1 ) , q 1 , 2 = ( 0 , 1 ) q^{1,1} = (1,1),q^{1,2} = (0,1) q1,1=(11)q1,2=(0,1) q 1 , 1 q^{1,1} q1,1属于head1, q 1 , 2 q^{1,2} q1,2属于head2。

但是在论文中 W i Q , W i K , W i V W_i^Q,W_i^K, W_i^V WiQ,WiK,WiV映射$得到每一个head的 Q i , K i , V i Q_i,K_i, V_i Qi,Ki,Vi : h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) head_i = Attention(QW_i^Q,KW_i^K,VW_i^V) headi=Attention(QWiQ,KWiK,VWiV)
其实可以进行简单的均分,也可以通过 W i Q , W i K , W i V W_i^Q,W_i^K, W_i^V WiQ,WiK,WiV设置成对应值来实现均分,如下图:
在这里插入图片描述
通过上述方法就能得到每个 h e a d i head_i headi对应的 W i Q , W i K , W i V W_i^Q,W_i^K, W_i^V WiQ,WiK,WiV。接下来针对每个head使用和Self-Attention中相同的方法即可得到对应的结果。
在这里插入图片描述
接着将每个head得到的结果进行concat拼接,比如上图中 b 1 , 1 b_{1,1} b1,1 b 2 , 1 b_{2,1} b2,1拼接起来。如下图所示:
在这里插入图片描述
接着将拼接后的结果通过 W O W^O WO(可学习的参数)进行融合,如下图所示,融合后得到最终的结果 b 1 , b 2 b_1,b_2 b1,b2
在这里插入图片描述
至此Multi-Head Attention公式计算完毕。

4.Position-wise Feed-Forward Networks(位置前馈网络)

位置前馈网络就是一个全连接前馈网络,每个位置的词都单独经过这个完全相同的前馈神经网络。其由两个线性变换组成,即两个全连接层组成,第一个全连接层的激活函数为 ReLU 激活函数。可以表示为:
F F N ( x ) = m a x ( 0 , x W 1 + b 1 ) W 2 + b 2 FFN(x)=max(0, xW_1+b_1)W_2 + b_2 FFN(x)=max(0,xW1+b1)W2+b2
在每个编码器和解码器中,虽然这个全连接前馈网络结构相同,但是不共享参数。整个前馈网络的输入和输出维度都是 d m o d e l d_{model} dmodel=512,第一个全连接层的输出和第二个全连接层的输入维度为 d f f d_{ff} dff=2048。

5.残差连接和层归一化

编码器结构中有一个需要注意的细节:每个编码器的每个子层(Self-Attention 层和 FFN 层)都有一个残差连接,再执行一个层标准化操作,整个计算过程可以表示为:
s u b l a y e r o u t p u t = L a y e r N o r m ( x + S u b L a y e r ( x ) ) sub_layer_output = LayerNorm(x + SubLayer(x)) sublayeroutput=LayerNorm(x+SubLayer(x))
在这里插入图片描述
将向量和自注意力层的层标准化操作可视化,如下图所示:
在这里插入图片描述
上面的操作也适用于解码器的子层。假设一个 Transformer 是由 2 层编码器和 2 层解码器组成,其如下图所示
在这里插入图片描述
为了方便进行残差连接,编码器和解码器中的所有子层和嵌入层的输出维度需要保持一致,在 Transformer 论文中 d m o d e l d_{model} dmodel = 512。

6.Positional Encodings(位置编码)

到目前为止,我们所描述的模型中缺少一个东西:表示序列中词顺序的方法。为了解决这个问题,Transformer 模型为每个输入的词嵌入向量添加一个向量。这些向量遵循模型学习的特定模式,有助于模型确定每个词的位置,或序列中不同词之间的距离。
在这里插入图片描述
从上图可知位置编码t是直接加在输入的x上的,然后再进行编码的。
在这里插入图片描述
位置编码是直接加在输入的a={ a 1 , . . . , a n a_1,...,a_n a1...an}上的,所以pe={ p e 1 , . . . , p e n pe_1,...,pe_n pe1...pen}和a={ a 1 , . . . , a n a_1,...,a_n a1...an}有相同的维度大小。

关于位置编码在原论文中有提出两种方案,一种是原论文中使用的固定编码,即论文中给出的sine and cosine functions方法,按照该方法可计算出位置编码;另一种是可训练的位置编码

transformer就介绍到这里,有了上面的大概了解,下面进入今天的主题Vision Transformer。
transformer参考文章:transformer

二、Vision Transformer

1.Vision Transformer概览

ViT是2020年Google团队提出的将Transformer应用在图像分类的模型,其模型简单且效果好,可扩展性强(scalable,模型越大效果越好),成为了transformer在CV领域应用的里程碑著作,也引爆了后续相关研究。

ViT原论文中最核心的结论是:当拥有足够多的数据进行预训练的时候,ViT的表现就会超过CNN,突破transformer缺少归纳偏置的限制,可以在下游任务中获得较好的迁移效果。但是当训练数据集不够大的时候,ViT的表现通常比同等大小的ResNets要差一些因为Transformer和CNN相比缺少归纳偏置(inductive bias),即一种先验知识,提前做好的假设

CNN具有两种归纳偏置,一种是局部性(locality/two-dimensional neighborhood structure),即图片上相邻的区域具有相似的特征;一种是平移不变形(translation equivariance), f ( g ( x ) ) = g ( f ( x ) ) f(g(x)) = g(f(x)) f(g(x))=g(f(x))其中g代表卷积操作,f代表平移操作。当CNN具有以上两种归纳偏置,就有了很多先验信息,需要相对少的数据就可以学习一个比较好的模型。

Vision Transformer(ViT)的模型框架
在这里插入图片描述
模型由三个模块组成:
⋆ \star Linear Projection of Flattened Patches(Embedding层)
⋆ \star Transformer Encoder(图右侧)
⋆ \star MLP Head(最终用于分类的层结构)

按照上面的流程图,一个ViT block可以分为以下几个步骤:

(1) Patch Embedding:假设输入图片大小为224x224,将图片分为固定大小的patch,然后将每个 Patch 拉成一维向量,patch大小为16x16,则每张图像会生成224x224/16x16=196个patch,即输入序列长度为196,每个patch维度16x16x3=768

考虑到一维向量维度较大,需要将拉伸后的 Patch 序列经过线性投影 (nn.Linear) 压缩维度,同时也可以实现特征变换功能,这两个步骤可以称为图片 Token 化过程 (Patch Embedding)

线性投射层的维度为768xN (N=768),因此输入通过线性投射层之后的维度依然为196x768,即一共有196个token,每个token的维度是768。为了方便后续分类,作者还额外引入一个可学习的 Class Token该 Token 插入到图片 token 化后所得序列的开始位置。现在,已经通过Patch Embedding将一个视觉问题转化为了一个seq2seq问题

(2) Positional Encoding(standard learnable 1D position embeddings):ViT同样需要加入位置编码,位置编码可以理解为一张表,表一共有N行,N的大小和输入序列长度相同,每一行代表一个向量,向量的维度和输入序列embedding的维度相同(768)。注意位置编码的操作是sum,而不是concat。加入位置编码信息之后,维度依然是197x768。

(3) LN/multi-head attention/LN:LN输出维度依然是197x768。多头自注意力时,先将输入映射到q,k,v,如果只有一个头,qkv的维度都是197x768,如果有12个头(768/12=64),则qkv的维度是197x64,一共有12组qkv,最后再将12组qkv的输出拼接起来,输出维度是197x768,然后在过一层LN,维度依然是197x768

(4) MLP:将维度放大再缩小回去,197x768放大为197x3072,再缩小变为197x768。一个block之后维度依然和输入相同,都是197x768,因此可以堆叠多个block。将最后一个 Transformer 编码器输出序列的第 0 位置( Class Token 位置对应输出)提取出来,后面接 MLP 分类后,然后正常分类即可。

2.Embedding层结构

🥇 Image Patching(图像分块)

对于标准的Transformer模块,要求输入的是token(向量)序列,即二维矩阵[num_token, token_dim],如下图,token0-9对应的都是向量,以ViT-B/16为例,每个token向量长度为768。
在这里插入图片描述
对于图像数据,其数据格式为[H, W, C],不满足Transformer输入要求。所以需要先通过Image Patching来对图像数据处理,将图像划分为固定大小的patch。如下图所示,首先将一张图片按给定大小分成一堆Patches。图像分块(Image Patches)过程如下图所示:
在这里插入图片描述
以ViT-B/16为例,将输入图片(224x224)按照16x16大小的Patch进行划分,划分后会得到 ( 224 / 16 ) 2 (224/16)^2 (224/16)2=196个Patches。

将图像分成小块(即Patch)可以带来的优势:

⋆ \star 特征提取: 在一些任务中,特定区域的信息比整个图像更有用。通过对每个Patch进行特征提取,可以获得更细粒度的信息,有助于更好地理解图像内容

⋆ \star 处理大尺寸图像: 对于非常大的图像,可能会遇到计算和存储方面的限制。将图像分成小的Patch可以帮助降低计算复杂度,并且可以更轻松地处理这些小尺寸的块

⋆ \star 自适应性: 在一些自适应处理的算法中,对于不同的图像区域采取不同的策略是很常见的。将图像划分成Patch可以使算法在局部区域上更加灵活和自适应

🥈Patch Embedding(图像块嵌入)

Patch Embedding与图像处理和卷积神经网络(CNN)相关。CNN对图像数据进行处理是在像素级上的处理通过卷积核在图像上滑动进行特征提取。而Patch Embedding,则引入了更高级的特征表示方式。它先将一张图片按给定大小分成一堆Patches,然后将每个小块转换为低维的向量表示。这种向量表示可以用作后续任务的输入。

Patch Embedding的目的在于降低计算复杂度并提高特征提取的效率在卷积神经网络中,相邻的像素通常会有大量重叠,而Patch Embedding将图像分成块后,可以减少冗余计算,同时保留了重要的特征信息。

Patch Embedding过程如下图所示:
在这里插入图片描述
通过线性映射将每个Patch映射到一维向量中,以ViT-B/16为例,每个Patche数据shape为[16, 16, 3]通过映射得到一个长度为768的向量(后面都直接称为token)。[16, 16, 3] -> [768]

在代码实现中,直接通过一个卷积层来实现。 以ViT-B/16为例,使用一个卷积核大小为16x16,步距为16,卷积核个数为768的卷积来实现。通过卷积[224, 224, 3] -> [14, 14, 768],然后把H以及W两个维度展平即可[14, 14, 768] -> [196, 768],此时正好变成了一个二维矩阵,符合Transformer输入数据要求。

🥉Class token

前面说过,为了方便后续分类,作者还额外引入一个可学习的 Class Token,是一个可训练的参数,数据格式和其他token一样都是一个向量,用于表示整个图像的类别信息,以辅助后续的图像分类或生成任务,该 Class Token 插入到图片 token 化后(Patch Embedding操作后)所得序列的开始位置。

在Transformer模型中,Patch Embedding操作后,Class foken通常被添加在输入序列的开头,并且在训练过程中会经过特定的注意力机制,以使得模型能够对类别信息进行编码和利用
在这里插入图片描述
以ViT-B/16为例,假设Patch Embedding后得到196个向量,添加Class Token(一个长度为768的向量)后输入序列为:[Class Token,v1,v2,…,v196]。整个输入序列的第一个向量就是Class Token,它包含了整个图像的类别信息,网络模型在训练过程中可以利用这个类别信息,进行图像分类任务。

🏅Position Embedding

在Vision Transformer 模型中,PE表示位置编码(Positonal Encoding)用于将图像中的每个Patch Embedding 向量与其位置信息相关联,将整个图像的全局位置信息引入到Transformer模型中。

Position Embedding和Transformer中讲到的Positional Encoding一样,这里的Position Embedding采用的是一个可训练的参数(1D Pos. Emb.),直接叠加在tokens上的(add),所以shape要求相同。

以ViT-B/16为例,输入序列添加Class Token后shape是[197, 768],则Position Embedding的shape也要是[197, 768]

Position Embedding作用:为了给Transformer模型提供输入序列中的位置信息。 在Transformer模型没有像卷积神经网络那样显式地保留位置信息。在自然语言处理任务中,输入是一个词语序列,为了保留词语的位置信息,通常会添加位置编码。同理在ViT中,输入是图像的Patch Embedding 序列,为了保留Patch的位置信息,也需要添加位置编码

对于Position Embedding作者做了一系列对比试验,在源码中默认使用的是1D Pos. Emb.,对比不使用Position Embedding准确率提升了0.3。
在这里插入图片描述

3.Transformer Encoder

Transformer Encoder是重复堆叠Encoder Block L次,Encoder Block如下图所示,
在这里插入图片描述
主要由以下几个部分组成:
⋆ \star Layer Norm,这种Normalization方法主要是针对NLP领域提出的,这里是对每个token进行Norm处理,在图像处理领域中BN比LN是更有效的,但现在越来越多的人将自然语言领域的模型用来处理图像,比如Vision Transformer,此时还是会涉及到LN。

⋆ \star ·Multi-Head Attention·,这个结构在讲前面Transformer中讲过。

⋆ \star ·Dropout/DropPath·,在原论文的代码中是直接使用的Dropout层,在但rwightman大佬实现的代码中使用的是DropPath(stochastic depth),可能后者会更好一点。

⋆ \star ·MLP Block·,如图右侧所示,全连接+GELU激活函数+Dropout组成,需要注意的是第一个全连接层会把输入节点个数翻4倍[197, 768] -> [197, 3072],第二个全连接层会还原回原节点个数[197, 3072] -> [197, 768]

4.MLP Head

Transformer Encoder后输出的shape和输入的shape是保持不变的,以ViT-B/16为例,输入的是[197, 768]输出的还是[197, 768]。

在Transformer Encoder后还有一个Layer Norm,这里我们只是需要分类的信息,所以只需要提取出Class Token生成的对应结果就行,即[197, 768]中抽取出Class Token对应的[1, 768]。接着通过MLP Head得到的分类结果
在这里插入图片描述
MLP Head原论文中说在训练ImageNet21K时是由Linear+tanh激活函数+Linear组成。但是迁移到ImageNet1K上或者自己的数据上时,只用一个Linear即可。

5.ViT B/16网络结构

在这里插入图片描述
ViT B/16 的网络结构如上图所示,假设输入图为 224 × 224 × 3的RGB彩色图片。

Embedding层:首先经过一个16x16大小的卷积核、步距为16的卷积层,得到14x14x768的特征图,然后进行高度和宽度方向的展平处理,得到196x768的特征向量。紧接着 concat 一个 Class token,其尺寸变为197x768,再加上 Position Embedding 的相加操作,因为尺寸完全相同,可以理解为数值上的相加,这里的 Position Embedding 也是可训练的参数。

Transformer Encoder:将以上的输入序列经过 Dropout 后输入 12 个堆叠的 Encoder Block。Encoder 输出经过 LN 层得到的输出为 197 × 768,即是不变的。然后切片提取Class token信息,切片之后即变成了 1 × 768。

MLP Head:将提取Class token输入 MLP Head层得到最终的输出。如果在 ImageNet21K 预训练的时候,Pre-Logits 是由一个全连接层+tanh 激活函数构成,然后通过一个全连接层得到最终的输出。如果是在 ImageNet1k 或者自己的数据集上的时候训练的时候,可以不需要 Pre-Logits。

在论文中给出三个模型(Base/ Large/ Huge)的参数,ViT B 对应的就是 ViT-Base,ViT L 对应的是 ViT-Large,ViT H 对应的是 ViT-Huge。
在这里插入图片描述
⋆ \star patch size 是图片切片大小(源码中还有 32 × 32 )

⋆ \star Layers是Transformer Encoder中重复堆叠Encoder Block的次数

⋆ \star Hidden Size就是对应通过Embedding层后每个token的dim(向量的长度)

⋆ \star MLP size是Transformer Encoder中MLP Block第一个全连接的节点个数(是Hidden Size的四倍)

⋆ \star Heads代表Transformer中Multi-Head Attention的heads数

三、Hybrid混合模型

混合模型是指首先使用传统的卷积神经网络提取特征,然后通过Vit模型得到最终的结果

ResNet50 + ViT-B/16网络结构如下所示:
在这里插入图片描述
上图以ResNet50作为特征提取器的混合模型,但这里的Resnet与之前讲的Resnet有些不同。首先这里的R50的卷积层采用的StdConv2d不是Conv2d,然后将所有的BatchNorm层替换成GroupNorm层。在原Resnet50网络中,stage1重复堆叠3次,stage2重复堆叠4次,stage3重复堆叠6次,stage4重复堆叠3次,但在这里的R50中,把stage4中的3个Block移至stage3中,所以stage3中共重复堆叠9次。

通过R50 Backbone进行特征提取后,得到的特征矩阵shape是[14, 14, 1024],接着再输入Patch Embedding层,注意Patch Embedding中卷积层Conv2d的kernel_size和stride都变成了1,只是用来调整channel,经过1x1卷积核变为14x14x768,然后经过Flatten就得到token。后面的部分和ViT处理流程一样。

在这里插入图片描述
上表是论文用来对比ViT,Resnet以及Hybrid模型的效果。通过对比发现,在训练epoch较少时Hybrid优于ViT,但当epoch增大后ViT优于Hybrid。因此,如果训练迭代次数少,混合模型的效果表现比较好。如果训练迭代次数较多的话,纯ViT的效果更佳

四、ViT网络实现

1.构建ViT网络


def drop_path(x, drop_prob: float = 0., training: bool = False):"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).This is the same as the DropConnect impl I created for EfficientNet, etc networks, however,the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper...See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... I've opted forchanging the layer and argument names to 'drop path' rather than mix DropConnect as a layer name and use'survival rate' as the argument."""if drop_prob == 0. or not training:return xkeep_prob = 1 - drop_probshape = (x.shape[0],) + (1,) * (x.ndim - 1)  # work with diff dim tensors, not just 2D ConvNetsrandom_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device)random_tensor.floor_()  # binarizeoutput = x.div(keep_prob) * random_tensorreturn outputclass DropPath(nn.Module):"""Drop paths (Stochastic Depth) per sample  (when applied in main path of residual blocks)."""def __init__(self, drop_prob=None):super(DropPath, self).__init__()self.drop_prob = drop_probdef forward(self, x):return drop_path(x, self.drop_prob, self.training)class PatchEmbed(nn.Module):"""2D Image to Patch Embedding"""def __init__(self, img_size=224, patch_size=16, in_c=3, embed_dim=768, norm_layer=None):super().__init__()img_size = (img_size, img_size)patch_size = (patch_size, patch_size)self.img_size = img_sizeself.patch_size = patch_sizeself.grid_size = (img_size[0] // patch_size[0], img_size[1] // patch_size[1])  # [14,14](224/16)self.num_patches = self.grid_size[0] * self.grid_size[1]  # 14x14 = 196self.proj = nn.Conv2d(in_c, embed_dim, kernel_size=patch_size, stride=patch_size)self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()def forward(self, x):B, C, H, W = x.shapeassert H == self.img_size[0] and W == self.img_size[1], \f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."# flatten: [B, C, H, W] -> [B, C, HW]# transpose: [B, C, HW] -> [B, HW, C]x = self.proj(x).flatten(2).transpose(1, 2)x = self.norm(x)return x# 多头自注意力机制
class Attention(nn.Module):def __init__(self,dim,   # 输入token的dimnum_heads=8,qkv_bias=False,qk_scale=None,attn_drop_ratio=0.,proj_drop_ratio=0.):super(Attention, self).__init__()# 每个heda对应qkv的dimself.num_heads = num_headshead_dim = dim // num_headsself.scale = qk_scale or head_dim ** -0.5# 得到qkvself.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)self.attn_drop = nn.Dropout(attn_drop_ratio)self.proj = nn.Linear(dim, dim)self.proj_drop = nn.Dropout(proj_drop_ratio)def forward(self, x):# [batch_size, num_patches + 1(Class token), total_embed_dim]B, N, C = x.shape# qkv(): -> [batch_size, num_patches + 1, 3 * total_embed_dim]# reshape: -> [batch_size, num_patches + 1, 3, num_heads, embed_dim_per_head]# permute: -> [3, batch_size, num_heads, num_patches + 1, embed_dim_per_head] 调整维度qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)# [batch_size, num_heads, num_patches + 1, embed_dim_per_head]q, k, v = qkv[0], qkv[1], qkv[2]  # make torchscript happy (cannot use tensor as tuple)# transpose: -> [batch_size, num_heads, embed_dim_per_head, num_patches + 1]# @: multiply -> [batch_size, num_heads, num_patches + 1, num_patches + 1]attn = (q @ k.transpose(-2, -1)) * self.scale   # scale是除以根号那一部分attn = attn.softmax(dim=-1)  # dim=-1:每一行进行softmax处理attn = self.attn_drop(attn)# @: multiply -> [batch_size, num_heads, num_patches + 1, embed_dim_per_head]# transpose: -> [batch_size, num_patches + 1, num_heads, embed_dim_per_head]# reshape: -> [batch_size, num_patches + 1, total_embed_dim]x = (attn @ v).transpose(1, 2).reshape(B, N, C)x = self.proj(x)x = self.proj_drop(x)return x# 这里是Encoder Block 中的 MLP Block
class Mlp(nn.Module):"""MLP as used in Vision Transformer, MLP-Mixer and related networks"""def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):super().__init__()out_features = out_features or in_featureshidden_features = hidden_features or in_featuresself.fc1 = nn.Linear(in_features, hidden_features)self.act = act_layer()self.fc2 = nn.Linear(hidden_features, out_features)self.drop = nn.Dropout(drop)def forward(self, x):x = self.fc1(x)x = self.act(x)x = self.drop(x)x = self.fc2(x)x = self.drop(x)return x# Encoder Block
class Block(nn.Module):def __init__(self,dim,num_heads,mlp_ratio=4.,qkv_bias=False,qk_scale=None,drop_ratio=0.,attn_drop_ratio=0.,drop_path_ratio=0.,act_layer=nn.GELU,norm_layer=nn.LayerNorm):super(Block, self).__init__()self.norm1 = norm_layer(dim)self.attn = Attention(dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale,attn_drop_ratio=attn_drop_ratio, proj_drop_ratio=drop_ratio)# NOTE: drop path for stochastic depth, we shall see if this is better than dropout hereself.drop_path = DropPath(drop_path_ratio) if drop_path_ratio > 0. else nn.Identity()self.norm2 = norm_layer(dim)mlp_hidden_dim = int(dim * mlp_ratio)self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop_ratio)def forward(self, x):x = x + self.drop_path(self.attn(self.norm1(x)))x = x + self.drop_path(self.mlp(self.norm2(x)))return xclass VisionTransformer(nn.Module):def __init__(self, img_size=224, patch_size=16, in_c=3, num_classes=1000,embed_dim=768, depth=12, num_heads=12, mlp_ratio=4.0, qkv_bias=True,qk_scale=None, representation_size=None, distilled=False, drop_ratio=0.,attn_drop_ratio=0., drop_path_ratio=0., embed_layer=PatchEmbed, norm_layer=None,act_layer=None):"""Args:img_size (int, tuple): input image sizepatch_size (int, tuple): patch sizein_c (int): number of input channelsnum_classes (int): number of classes for classification headembed_dim (int): embedding dimensiondepth (int): depth of transformernum_heads (int): number of attention headsmlp_ratio (int): ratio of mlp hidden dim to embedding dimqkv_bias (bool): enable bias for qkv if Trueqk_scale (float): override default qk scale of head_dim ** -0.5 if setrepresentation_size (Optional[int]): enable and set representation layer (pre-logits) to this value if setdistilled (bool): model includes a distillation token and head as in DeiT modelsdrop_ratio (float): dropout rateattn_drop_ratio (float): attention dropout ratedrop_path_ratio (float): stochastic depth rateembed_layer (nn.Module): patch embedding layernorm_layer: (nn.Module): normalization layer"""super(VisionTransformer, self).__init__()self.num_classes = num_classesself.num_features = self.embed_dim = embed_dim  # num_features for consistency with other modelsself.num_tokens = 2 if distilled else 1norm_layer = norm_layer or partial(nn.LayerNorm, eps=1e-6)act_layer = act_layer or nn.GELUself.patch_embed = embed_layer(img_size=img_size, patch_size=patch_size, in_c=in_c, embed_dim=embed_dim)num_patches = self.patch_embed.num_patches# class token:1x768self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))   # Parameter使用可训练参数self.dist_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) if distilled else None# Position Embedding :197x768self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + self.num_tokens, embed_dim))self.pos_drop = nn.Dropout(p=drop_ratio)dpr = [x.item() for x in torch.linspace(0, drop_path_ratio, depth)]  # stochastic depth decay rule# 堆叠Encoder blockself.blocks = nn.Sequential(*[Block(dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,drop_ratio=drop_ratio, attn_drop_ratio=attn_drop_ratio, drop_path_ratio=dpr[i],norm_layer=norm_layer, act_layer=act_layer)for i in range(depth)])self.norm = norm_layer(embed_dim)# Representation layer(MLP Head)if representation_size and not distilled:self.has_logits = Trueself.num_features = representation_sizeself.pre_logits = nn.Sequential(OrderedDict([("fc", nn.Linear(embed_dim, representation_size)),("act", nn.Tanh())]))else:   # MLP Head就没有pre-logitsself.has_logits = Falseself.pre_logits = nn.Identity()# Classifier head(s)self.head = nn.Linear(self.num_features, num_classes) if num_classes > 0 else nn.Identity()self.head_dist = Noneif distilled:self.head_dist = nn.Linear(self.embed_dim, self.num_classes) if num_classes > 0 else nn.Identity()# Weight initnn.init.trunc_normal_(self.pos_embed, std=0.02)if self.dist_token is not None:nn.init.trunc_normal_(self.dist_token, std=0.02)nn.init.trunc_normal_(self.cls_token, std=0.02)self.apply(_init_vit_weights)def forward_features(self, x):# [B, C, H, W] -> [B, num_patches, embed_dim]x = self.patch_embed(x)  # [B, 196, 768]# [1, 1, 768] -> [B, 1, 768]cls_token = self.cls_token.expand(x.shape[0], -1, -1)if self.dist_token is None:x = torch.cat((cls_token, x), dim=1)  # [B, 197, 768]else:x = torch.cat((cls_token, self.dist_token.expand(x.shape[0], -1, -1), x), dim=1)x = self.pos_drop(x + self.pos_embed)x = self.blocks(x)x = self.norm(x)if self.dist_token is None:return self.pre_logits(x[:, 0])else:return x[:, 0], x[:, 1]def forward(self, x):x = self.forward_features(x)if self.head_dist is not None:x, x_dist = self.head(x[0]), self.head_dist(x[1])if self.training and not torch.jit.is_scripting():# during inference, return the average of both classifier predictionsreturn x, x_distelse:return (x + x_dist) / 2else:x = self.head(x)  # 全连接层return xdef _init_vit_weights(m):"""ViT weight initialization:param m: module"""if isinstance(m, nn.Linear):nn.init.trunc_normal_(m.weight, std=.01)if m.bias is not None:nn.init.zeros_(m.bias)elif isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, mode="fan_out")if m.bias is not None:nn.init.zeros_(m.bias)elif isinstance(m, nn.LayerNorm):nn.init.zeros_(m.bias)nn.init.ones_(m.weight)def vit_base_patch16_224(num_classes: int = 1000, has_logits: bool = True):"""ViT-Base model (ViT-B/16) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-1k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:链接: https://pan.baidu.com/s/1zqb08naP0RPqqfSXfkB2EA  密码: eu9f"""model = VisionTransformer(img_size=224,patch_size=16,embed_dim=768,  # 16x16x3depth=12,  # encoder block 重复堆叠的次数num_heads=12,representation_size=None,  # MLP Head 中 pre-logits全连接层的个数num_classes=num_classes)return modeldef vit_base_patch16_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Base model (ViT-B/16) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_base_patch16_224_in21k-e5005f0a.pth"""model = VisionTransformer(img_size=224,patch_size=16,embed_dim=768,depth=12,num_heads=12,representation_size=768 if has_logits else None,num_classes=num_classes)return modeldef vit_base_patch32_224(num_classes: int = 1000):"""ViT-Base model (ViT-B/32) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-1k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:链接: https://pan.baidu.com/s/1hCv0U8pQomwAtHBYc4hmZg  密码: s5hl"""model = VisionTransformer(img_size=224,patch_size=32,embed_dim=768,depth=12,num_heads=12,representation_size=None,num_classes=num_classes)return modeldef vit_base_patch32_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Base model (ViT-B/32) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_base_patch32_224_in21k-8db57226.pth"""model = VisionTransformer(img_size=224,patch_size=32,embed_dim=768,depth=12,num_heads=12,representation_size=768 if has_logits else None,num_classes=num_classes)return modeldef vit_large_patch16_224(num_classes: int = 1000):"""ViT-Large model (ViT-L/16) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-1k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:链接: https://pan.baidu.com/s/1cxBgZJJ6qUWPSBNcE4TdRQ  密码: qqt8"""model = VisionTransformer(img_size=224,patch_size=16,embed_dim=1024,depth=24,num_heads=16,representation_size=None,num_classes=num_classes)return modeldef vit_large_patch16_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Large model (ViT-L/16) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_large_patch16_224_in21k-606da67d.pth"""model = VisionTransformer(img_size=224,patch_size=16,embed_dim=1024,depth=24,num_heads=16,representation_size=1024 if has_logits else None,num_classes=num_classes)return modeldef vit_large_patch32_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Large model (ViT-L/32) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_large_patch32_224_in21k-9046d2e7.pth"""model = VisionTransformer(img_size=224,patch_size=32,embed_dim=1024,depth=24,num_heads=16,representation_size=1024 if has_logits else None,num_classes=num_classes)return modeldef vit_huge_patch14_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Huge model (ViT-H/14) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.NOTE: converted weights not currently available, too large for github release hosting."""model = VisionTransformer(img_size=224,patch_size=14,embed_dim=1280,depth=32,num_heads=16,representation_size=1280 if has_logits else None,num_classes=num_classes)return model

2.训练和测试模型


def main(args):# 检测是否支持CUDA,如果支持则使用第一个可用的GPU设备,否则使用CPUdevice = torch.device(args.device if torch.cuda.is_available() else "cpu")print(args)print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/')# tensorboard --logdir=F:/NN/Learn_Pytorch/ShuffleNetV2/runs/Oct11_13-22-17_DESKTOP-64L888R# 记录训练过程中的指标和可视化结果tb_writer = SummaryWriter()# 创建一个用于存储模型权重文件的目录if os.path.exists("./weights") is False:os.makedirs("./weights")# 获取训练和验证数据集的文件路径和标签train_images_path, train_images_label, val_images_path, val_images_label = read_split_data(args.data_path)# 数据预处理/增强的操作data_transform = {"train": transforms.Compose([transforms.RandomResizedCrop(224),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]),"val": transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])}# 实例化训练数据集train_dataset = MyDataSet(images_path=train_images_path,images_class=train_images_label,transform=data_transform["train"])# 实例化验证数据集val_dataset = MyDataSet(images_path=val_images_path,images_class=val_images_label,transform=data_transform["val"])batch_size = args.batch_sizenw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])  # number of workersprint('Using {} dataloader workers every process'.format(nw))# 加载数据集,指定了批处理大小、是否打乱数据、数据加载的并行工作进程数(num_workers)# 以及如何合并批次数据的函数(collate_fn)train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size,shuffle=True,pin_memory=True,num_workers=nw,collate_fn=train_dataset.collate_fn)val_loader = torch.utils.data.DataLoader(val_dataset,batch_size=batch_size,shuffle=False,pin_memory=True,num_workers=nw,collate_fn=val_dataset.collate_fn)# 如果存在预训练权重则载入model = create_model(num_classes=args.num_classes, has_logits=False).to(device)if args.weights != "":assert os.path.exists(args.weights), "weights file: '{}' not exist.".format(args.weights)weights_dict = torch.load(args.weights, map_location=device)# 删除不需要的权重del_keys = ['head.weight', 'head.bias'] if model.has_logits \else ['pre_logits.fc.weight', 'pre_logits.fc.bias', 'head.weight', 'head.bias']for k in del_keys:del weights_dict[k]print(model.load_state_dict(weights_dict, strict=False))if args.freeze_layers:for name, para in model.named_parameters():# 除head, pre_logits外,其他权重全部冻结if "head" not in name and "pre_logits" not in name:para.requires_grad_(False)else:print("training {}".format(name))# 创建一个包含所有需要进行梯度更新的参数的列表pg = [p for p in model.parameters() if p.requires_grad]optimizer = optim.SGD(pg, lr=args.lr, momentum=0.9, weight_decay=4E-5)# Scheduler https://arxiv.org/pdf/1812.01187.pdf# 学习率调度策略,将学习率在训练过程中按余弦函数的方式进行调整lf = lambda x: ((1 + math.cos(x * math.pi / args.epochs)) / 2) * (1 - args.lrf) + args.lrf  # cosine# 根据余弦函数的形状调整学习率scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)best_acc = 0.0for epoch in range(args.epochs):# trainmean_loss = train_one_epoch(model=model,optimizer=optimizer,data_loader=train_loader,device=device,epoch=epoch)scheduler.step()# validateacc = evaluate(model=model,data_loader=val_loader,device=device)print("[epoch {}] accuracy: {}".format(epoch, round(acc, 3)))tags = ["loss", "accuracy", "learning_rate"]tb_writer.add_scalar(tags[0], mean_loss, epoch)tb_writer.add_scalar(tags[1], acc, epoch)tb_writer.add_scalar(tags[2], optimizer.param_groups[0]["lr"], epoch)# 保存准确率最高的权重if round(acc, 3) > best_acc:best_acc = round(acc, 3)torch.save(model.state_dict(), "./weights/model-{}.pth".format(epoch))if __name__ == '__main__':parser = argparse.ArgumentParser()parser.add_argument('--num_classes', type=int, default=5)parser.add_argument('--epochs', type=int, default=100)parser.add_argument('--batch-size', type=int, default=16)parser.add_argument('--lr', type=float, default=0.01)parser.add_argument('--lrf', type=float, default=0.1)# 数据集所在根目录# https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgzparser.add_argument('--data-path', type=str,default=r"F:/NN/Learn_Pytorch/flower_photos")parser.add_argument('--model-name', default='', help='create model name')# 预训练权重路径,如果不想载入就设置为空字符parser.add_argument('--weights', type=str, default='./vit_base_patch16_224_in21k.pth',help='initial weights path')# 是否冻结权重parser.add_argument('--freeze-layers', type=bool, default=True)parser.add_argument('--device', default='cuda:0', help='device id (i.e. 0 or 0,1 or cpu)')opt = parser.parse_args()main(opt)

这里使用了预训练权重,在其基础上训练自己的数据集。训练100epoch的准确率能到达98%左右。
在这里插入图片描述

五、实现图像分类

这里使用花朵数据集,下载连接:https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz

def main():device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 与训练的预处理一样data_transform = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])# 加载图片img_path = 'daisy2.jpg'assert os.path.exists(img_path), "file: '{}' does not exist.".format(img_path)image = Image.open(img_path)# image.show()# [N, C, H, W]img = data_transform(image)# 扩展维度img = torch.unsqueeze(img, dim=0)# 获取标签json_path = 'class_indices.json'assert os.path.exists(json_path), "file: '{}' does not exist.".format(json_path)with open(json_path, 'r') as f:# 使用json.load()函数加载JSON文件的内容并将其存储在一个Python字典中class_indict = json.load(f)# create modelmodel = create_model(num_classes=5, has_logits=False).to(device)# load model weightsmodel_weight_path = "./weights/model-2.pth"model.load_state_dict(torch.load(model_weight_path, map_location=device))model.eval()with torch.no_grad():# 对输入图像进行预测output = torch.squeeze(model(img.to(device))).cpu()# 对模型的输出进行 softmax 操作,将输出转换为类别概率predict = torch.softmax(output, dim=0)# 得到高概率的类别的索引predict_cla = torch.argmax(predict).numpy()res = "class: {}   prob: {:.3}".format(class_indict[str(predict_cla)], predict[predict_cla].numpy())draw = ImageDraw.Draw(image)# 文本的左上角位置position = (10, 10)# fill 指定文本颜色draw.text(position, res, fill='green')image.show()for i in range(len(predict)):print("class: {:10}   prob: {:.3}".format(class_indict[str(i)], predict[i].numpy()))

分类结果:
在这里插入图片描述

结束语

感谢阅读吾之文章,今已至此次旅程之终站 🛬。

吾望斯文献能供尔以宝贵之信息与知识也 🎉。

学习者之途,若藏于天际之星辰🍥,吾等皆当努力熠熠生辉,持续前行。

然而,如若斯文献有益于尔,何不以三连为礼?点赞、留言、收藏 - 此等皆以证尔对作者之支持与鼓励也 💞。

相关文章:

Pytorvh之Vision Transformer图像分类

文章目录 前言一、Transformer1.Transformer概览2.Self-Attention3.Multi-head Attention4.Position-wise Feed-Forward Networks(位置前馈网络)5.残差连接和层归一化6.Positional Encodings(位置编码) 二、Vision Transformer1.Vision Transformer概览2.Embedding层结构&#…...

LabVIEW为什么不能在RT机箱内看到NI-IMAQ设备

LabVIEW为什么不能在RT机箱内看到NI-IMAQ设备 最近把NI-IMAQ更新到最新的1394版本。这个新驱动工作良好。但是,当打开MAX,NII MAQ设备却在RT PXI机箱里找不到。 问题最有可能是NIIMAQ服务器的版本跟主机PC和RT目标设备是不同的。为保证通信正常NII MAQ服…...

three.js入门 ---- 相机控件OrbitControls

前言: 自用!!! 文档中描述:OrbitControls本质上就是改变相机的参数,比如相机的位置属性,改变相机位置可以改变相机拍照场景中模型的角度,实现模型的360度旋转预览效果,改…...

数字IC/FPGA面试题目合集解析(一)

数字IC/FPGA面试题目合集解析(一) 题目概述题目1,计算题2,计算题3,选择题 答案与解析1,计算题2,计算题3,选择题 题目概述 1,计算题:计算该触发器等效的建立保…...

20231014后台面经总结

1.Spring怎么解决循环依赖 形象地解释 为什么三层缓存 我的简单理解: 1.A依赖B,B生成时先注入A未注入属性的原始对象earlySingletonObject 2.引入三级缓存SingletonFacotry的目的是解决aop提前创建代理的步骤,不然它注入的对象跟真实的不一致…...

RabbitMQ的七种工作模式和分别概述

MQ(Message Queuing)的七种工作模式如下: Point-to-Point (P2P)模式:在这种模式下,消息传递是单向的,即消息从一个生产者发送到一个消费者。消息被放置在队列中,消费者从该队列中取出消息进行处…...

使用Vscode开发C#没有代码提示问题

很多小伙伴在使用Vscode去编写C#脚本的时候会发现没有代码提示,这样你的敲代码速度会变的很慢! 错误使用方法 直接把C#脚本拖入到Vscode中进行编写,这样是不会有代码提示的! 正确打开办式 Edit(编辑)…...

统信UOS 1060上通过Fail2Ban来Ban IP

原文链接:统信UOS 1060上通过Fail2Ban来Ban IP hello,大家好啊,今天给大家带来一篇在统信UOS 1060上安装Fail2Ban并且当ip被ban后通过邮件发送通知的文章。Fail2Ban 是一个用于防止暴力攻击的开源软件。它可以扫描日志文件(例如&a…...

Linux系统编程:文件描述符以及IO多路复用

书接上回,我们之前学习的文件系统编程都是在内存空间中的文件流(用户态文件缓冲区)内进行操作的,比如使用的fopen、fclose、fread和fwrite等等都是库函数,并没有用到内核态的功能(实际上库函数中调用的是内…...

python基于django的留学生服务管理平台

留学服务管理平台的用户是系统最根本使用者,按需要分析系统包括三类用户:学生、教师、管理员。这三类用户对系统的需求简要如下。技术栈 后端:pythondjango 前端:vueCSSJavaScriptjQueryelementui 开发语言:Python 框架…...

C++ 之 Map

Map map 是C STL中的关联容器&#xff0c; key-Value键值对存储&#xff0c;一对一的映射关系。 内部结构采用的是红黑树&#xff0c; 它会实现对数据的自动排序 &#xff0c;所以map内部所有的数据都是有序的。 #include <map>定义的结构: map<数据类型1, 数据类型…...

MongoDB——centOS7环境Mongodb权限管理(图解版)

目录 一、MongDB权限概述1.1、MongDB权限概述1.2、MongDB权限列表 二、Mongodb权限管理示例2.1、创建账号2.1.1、创建管理员用户2.1.2、开启认证2.1.3、创建普通账号 一、MongDB权限概述 1.1、MongDB权限概述 mongodb是没有默认管理员账号&#xff0c;所以要先添加管理员账号…...

AndroidX项目接入穿山甲广告填坑

本文旨在记录初步接入过程遇到的一点点小问题&#xff0c;更详细的文档还是要参考官方文档。 一、项目版本 GradlePlugin&#xff1a;8.0.2 Gradle&#xff1a;8.0 Kotlin&#xff1a;1.7.20 compileSdk&#xff1a;34 compose-bom:2022.10.00 AS&#xff1a;2022.2.1 …...

汽车电子 - matlab - 用法

汽车电子 - matlab - 用法 面向对象 面向对象 % Man.m classdef Man < handle%UNTITLED2 此处显示有关此类的摘要% 此处显示详细说明properties(Accessprivate) %私有变量scoreendpropertiesageheightendmethodsfunction obj Man(inputArg1,inputArg2)%UNTITLED2 构造此…...

freefilesync文件同步软件

下载 下载链接 https://freefilesync.org/download.php 往下拉&#xff0c;看到下载的链接 下载windows版本 下载地址&#xff1a; https://freefilesync.org/download/FreeFileSync_13.0_Windows_Setup.exe 直接复制到浏览器中访问就能下载 安装 双击安装包&#xff0c;一路默…...

【2023】M1/M2 Mac 导入Flac音频到Pr的终极解决方案

介绍 原作者链接&#xff1a;https://github.com/fnordware/AdobeOgg 很早之前就发现了这个插件&#xff0c;超级好用&#xff0c;在windows上完全没有问题&#xff0c;可惜移植到mac就不行了&#xff08;然后我给作者发了一个Issue&#xff0c;后来就有大佬把m1的编译出来了&…...

C# 图解教程 第5版 —— 第6章 方法

文章目录 6.1 方法的结构6.2 方法体内部的代码执行6.3 局部变量6.3.1 类型推断和 var 关键字6.3.2 嵌套块中的局部变量 6.4 局部常量6.5 控制流6.6 方法调用&#xff08;*&#xff09;6.7 返回值&#xff08;*&#xff09;6.8 返回语句和 void 方法6.9 局部函数6.10 参数&#…...

283 移动零

解题思路&#xff1a; \qquad 适用双指针&#xff0c;l&#xff1a;最左边‘0’元素坐标&#xff1b;r&#xff1a;l右边第一个非零元素坐标。 \qquad 最初的思路&#xff1a;将l和r初始化为0&#xff0c;遍历数组nums若任意一个指针到达数组末尾时停止。若当前nums[l] 0则移…...

maven 编译.../maven-metadata.xml 报错

文章目录 问题解决 问题 突然编译报错: 解决 打开maven的里离线工作模式,感觉就是下载包到本地. 一个是在maven设置里面 或者直接在maven编译的窗口:...

【Rust笔记】Rust与Java交互-JNI模块编写-实践总结

近期工作中有Rust和Java互相调用需求&#xff0c;这篇文章主要介绍如何用Rust通过JNI和Java进行交互&#xff0c;还有记录一下开发过程中遇到的一些坑。 JNI简单来说是一套Java与其他语言互相调用的标准&#xff0c;主要是C语言&#xff0c;官方也提供了基于C的C接口。 既然是C…...

uniapp:幸运大转盘demo

<template><view class"index"><image src"../../static/img/158.png" mode"" class"banner"></image><view class"title">绿色积分加倍卡拿到手软</view><almost-lottery :lottery…...

android 13.0 通过系统自定义服务控制屏幕亮屏和灭屏操作

1.前言 在13.0的产品开发中, 需要提供亮屏和灭屏的接口在8.0以后系统对于屏幕亮灭屏做了限制,直接调用亮屏和灭屏的方法就调不到了,所有就需要通过增加自定义服务的功能,来实现 通过系统服务的方法来调用系统关于控制屏幕亮屏灭屏的相关操作 2.通过系统自定义服务控制屏幕…...

【SQL】新建库表时,报错attempt to write a readonly database

目录 1.问题背景 2.问题原因 3.解决方式 4.结果 windows64位 Navicat sql vscode c 1.问题背景 需求是这样&#xff1a; 希望在调用初始化数据库方法时&#xff0c;查看是否有名为【POCT_PROCESS_CONFIG】的数据库表&#xff0c;如果没有就新建 我的数据库格式是这样 …...

C++ --STL

STL STL&#xff08;Standard Template Library,标准模板库&#xff09;STL从广义上分为&#xff1a; 容器&#xff08;container&#xff09;算法 (algorithm)迭代器 (iterator) 容器 和 算法之间通过迭代器进行无缝连接。STL几乎所有的代码都采用模板类或者模板函数 1、ST…...

一卷到底,大明哥带你横扫 Netty

上一个死磕 Java 专栏【死磕 NIO】(当然写的不是很好&#xff0c;争取今年将它重写一遍)是**【死磕 Netty】**的铺垫&#xff0c;对于我们 Java 程序员而言&#xff0c;我们在实际开发过程一般都不会直接使用 Java NIO 作为我们的网络编程框架&#xff0c;因为写出一套高质量的…...

Python Opencv实践 - 车辆统计(1)读取视频,移除背景,做预处理

示例中的图像的腐蚀、膨胀和闭运算等需要根据具体视频进行实验得到最佳效果。代码仅供参考。 import cv2 as cv import numpy as np#读取视频文件 video cv.VideoCapture("../../SampleVideos/Traffic.mp4") FPS 10 DELAY int(1000 / FPS) kernel cv.getStructu…...

ROS-6.参数的使用

参数的使用 参数服务结构命令行的使用方式运行小海龟命令介绍查看参数获取参数值设置参数保存参数到文件从文件导入参数 通过程序操作创建节点修改cmake编译运行 参数服务结构 ros中存在参数服务管理服务&#xff0c;管理这所有参数&#xff0c;所有节点剋订阅和发布这些节点 …...

机器视觉在自动驾驶汽车中的应用与挑战

机器视觉在自动驾驶汽车中扮演着至关重要的角色&#xff0c;它使车辆能够感知和理解周围环境&#xff0c;以便自主驾驶。以下是机器视觉在自动驾驶汽车中的应用以及相关挑战&#xff1a; 应用&#xff1a; 障碍物检测与避让&#xff1a; 机器视觉系统可以检测和识别路上的障碍…...

欠拟合、过拟合及优化:岭回归

问题:训练数据训练的很好啊,误差也不大,为什么在测试集上面有问题呢? 当算法在某个数据集当中出现这种情况,可能就出现了过拟合现象。 1、 什么是过拟合与欠拟合 欠拟合 过拟合 分析 第一种情况:因为机器学习到的天鹅特征太少了,导致区分标准太粗糙,不能准确识别出天鹅…...

Mybatis学习笔记注解/xml映射/动态SQL%%%Mybatis教程

介绍 Mybatis 是一款优秀的持久层框架&#xff0c;用于简化 JDBC 的开发 MyBatis中文网 Mybatis 入门 快速入门 步骤 创建 SpringBoot 工程、数据库表 user、实体类 User引入 Mybatis 相关依赖&#xff0c;配置 Mybatis&#xff08;数据库连接信息&#xff09;编写 SQL 语…...