初学者理解Transformer,本文is all you need

要问现在AI领域哪个概念最热,必然是openAI推出chatGPT之后引发的大模型。然而这项技术的起源,都来自一篇google公司员工的神作“Attention Is All You Need”——本文标题也是一种致敬^_^,目前已有近12万的引用(还在增长)。

在“Attention Is All You Need”中介绍了一种新的神经网络架构,被命名为变形金刚(也可以叫变压器,只是不那么cool)的Transformer,并应用到语言翻译中取得了很好的效果。这之后,基于Transformer架构的技术层出不穷,不仅在语言翻译中,在图像处理、语音处理、问答系统中都得到了广泛应用,chatGPT就是其中的明星应用。

本文将结合一个中译英例子,通过一步步的节点计算介绍,将这篇论文中提到的Transformer模型进行阐述,后文提到的“原文”就是指代“Attention Is All You Need”这篇论文。

鸟瞰框架

Transformer的模型架构主要由两部分组成,如下图中左边的框是编码器(Encoder),右边的框是解码器(Decoder)。

我们先忽略其中的细节,从最顶层鸟瞰Transfromer结构是这样的(原文N等于6):

输入序列经过N(原文中取值6)层的编码后,产生的编码矩阵结果输入到后续的解码器中,同时将经过嵌入和线性变换处理的输出依次也输入第一层的解码器中,最终产生一个预测值。该预测值和样本之间的差用于权重矩阵的训练,最终得到我们需要的模型。

整个Transformer需要经过如下步骤的计算:

  • 数据输入编码器和解码器前的处理
  1. 输入输出序列的词嵌入(Embedding)
  2. 位置编码(Positional Encoding)
  • 编码器层
  1. 编码器中多头注意力子层
  2. 编码器中前馈子层
  • 解码器层
  1. 解码器中多头注意力(掩码)子层
  2. 解码器中多头注意力子层
  3. 解码器中前馈子层
  • 输出预测值
  1. 线性层
  2. Softmax层

下面从输入序列开始,逐层讲解Transformer的结构和计算。读者需要具备一些基础的线性代数知识(矩阵概念、向量及矩阵乘法)和神经网络知识(网络连接、激活函数、规范化等)。

我们以一个中译英的任务举例,将“我爱编程”翻译成"I love programming"。把中文“我爱编程”拆开看成“我”(位置0)“爱”(位置1)“编”(位置2)“程”(位置3),英文为"I"(位置0)"love"(位置1)"programming"(位置2),如下图所示:

输入的嵌入层

也就是下图中红框部分:

输入Embedding

我们知道计算机并不能直接处理文字,它擅长的只是数值计算,因此我们需要将输入的字符进行编码才能输入到Transformer中。那能否用1、2、3依次编号这些文字呢?答案是不行。因为这只是形式上转成了数字,但每个词之间的关系并不能得到体现。比如“苹果”和“香蕉”,假设它们的编号分别是9527,9528,这看上去他们距离很近应该属于很相近的两个东西(反之,如果编号相差很大也一样存在可能意思相近的上下文)。但“苹果”要是出现在“手机”前,那它跟水果“香蕉”就应该相差很远。

怎么在上下文中体现词与词之间的相似性呢?它的编码就需要进行大量数据的训练,使得词与词之间的关系可以通过这种编码的数值关系来表达出来,在NLP领域就叫这个为embedding(中文翻译有叫嵌入,也有叫编码,大家自行根据上下文理解,本质上都是将文字转换成一个数值向量),比如谷歌开源的Word2Vec工具包就是做这个工作的。

通过embedding,输入的一个个词(实际处理中使用token,也就是先token化,此处简化将token等同为单词)就转换成了向量。也可以先初始化为随机向量,然后在训练Transformer时更新embedding值。原文中使用的是已学习好的embedding方法。

假设我们的embedding模型将输入的词转成的向量维度是512(原文选择的d_{model}值),则“我爱编程”的每个词转成512维的向量如下所示。

实际应用中,这些向量会拼接一块作为输入矩阵X\in \mathbb{R}^{l\times d_{model}},这里的l为序列长度(这里l=4),d_{model}是embedding模型长度。

位置编码(Positional Encoding)

输入的序列通过embedding转成向量后不能直接进行后续运算,因为相较于RNN,这里体现不出每个词的位置信息。举例来说“我吃梨”跟“梨吃我”转成的是相同的3个向量,但显然前后两句的主语和宾语是不一样的。为了解决这个问题,需要引入位置信息,即“我吃梨”中的“我”在第一位,“梨”在第三位,这个信息的引入就是位置编码。原文中通过计算来获得,计算公式如下:

其中pos是单词在句子中的位置,比如“我爱编程”中的“我”的pos取值0,“爱”的pos取值1;d是位置编码后向量的维度,取跟输入embedding维度相同,所以这里是 512;i是向量维度的索引,这里的两个公式说明对第偶数维的值采用sin计算,对第奇数维的值采用cos计算。假设“我”的embedding后为(0.85, 0.34, 0.5, 0.61,......)的512维向量,分别计算每一维的位置编码后得到的向量是:

[ sin(0/10000^{(2*0/512)}), cos(0/10000^{(2*1/512)}), sin(0/10000^{(2*2/512)}), cos(0/10000^{(2*3/512)}), ......]^{T},这个位置编码向量也是512维的。

通过将词嵌入embedding向量和位置编码向量相加,得到的向量就是将输入编码器的输入向量x。如果将所有词的embedding和位置编码向量相加放到一个矩阵中,我们就可以最大化利用GPU的矩阵并行运算能力,这时的输入就是矩阵X,它是多个向量的拼接,即:

X=\begin{bmatrix} x_{1}^{t}\\ x_{2}^{t}\\ ...\\ x_{l}^{t} \end{bmatrix}

这里的l是embeding的个数即序列长度,此处为4(“我爱编程”的词序列长度),所以X是个4*512的矩阵。

编码器多头注意力机制(Multi Head Attention)

本节我们介绍多头注意力机制,也就是框架图中红框所示部分,当输入经过嵌入和位置编码后首先进入的这个节点:

这是原文的一个创新点。在这里假设读者掌握向量及矩阵乘积的数学知识,如果能了解向量距离的概念就更好了。

注意力机制(Attention)

字面上理解,注意力就是我们观察对象中的重点部分。当我们从一张图片中去找寻一个对象时,我们会先大致在图片中搜寻色块形状相近的区块,然后再细节判断刚才的区块是否我们找寻的对象,那个查找相近区块过程就是注意力机制。

再拿我们从数据库中查询信息的过程打比方,我们输入的查询语句(Query)会先查询索引(Key),找到匹配项下的值(Value)并返回。如何用数学公式来表示这个过程呢,这就是注意力机制描述的事情。我们注意到整个过程离不开怎么定义匹配,换句话说如何定义Q(文中后续用Q简单代指Query,类似K代替Key, V代替Value)与K和V的相近程度呢?越相近就越匹配,就越需要我们注意。

两个向量之间的相似度可以用他们在向量空间中的距离来表示。定义距离的方法有很多种,原文中使用的是点积,如下图所示:

细心的读者会发现这个图的标题是“Scaled” Dot-Product Attention,这个缩放是在点积结果上除以\sqrt{d_{k}}得到,d_{k}是矩阵Q、K的向量维度(原文中是512),表示成数学公式形式即为:

这里的Q、K、V分别是嵌入层处理后的输出乘以对应的权重矩阵得到的,即:

Q=XW^{q}, K=XW^{k}, V=XW^{v}

其中,X是输入序列经embedding和positional encoding之后得到的向量拼接而出的输入表示矩阵,W^{q}\in \mathbb{R}^{d_{model}\times d_{k}}W^{k}\in \mathbb{R}^{d_{model}\times d_{k}}(Q和K具有相同的d_{k})和W^{v}\in \mathbb{R}^{d_{model}\times d_{v}}为权重矩阵,这里的d_{model}是embedding时的维度(本文即为512),而d_{k}d_{v}可以任意选择(原文中应用到多头注意力机制中,选择的是值为64,介绍多头注意力机制时再解释)。初始化这些矩阵为随机矩阵,在后续的训练中会不断更新权重值。

通过注意力机制,我们就能获得每个词在句子中跟其他词之间的相似度,值越大表明相关性越高。由于这里是计算整个句子中各个词间的相关性,包含了自身的计算,所以又叫自注意力(Self-Attention)。

上图中有个Mask节点,在编码器中不使用,主要在解码器中有一个多头注意力子层在接受输出序列embedding时使用,后面再介绍。

多头注意力机制(Multi Head Attention)

上述的注意力机制是对一组(Q,K,V)的计算,原文作者发现采用多个这样的权重矩阵来处理输入会取得更好的结果。即通过多组(W^{q_{i}}W^{k_{i}}W^{v_{i}})来计算对应的Q、K、V:

Q_{i}=XW^{q_{i}}, K_{i}=XW^{k_{i}}, V_{i}=XW^{v_{i}}

如下图即使用了h个(W^{q_{i}}W^{k_{i}}W^{v_{i}}),这里原文使用的h值为8。将d_{model}进行h等分,得到d_{k}=d_{v}=d_{model}/h=64

整个过程相当于一个输入X经过h组(Q、K、V)的线性变换后分别输入对应的单头注意力进行计算,再将各个注意力计算出来的结果拼接(Concat)到一块,乘以一个矩阵W^{O}\in \mathbb{R}^{hd_{v}\times d_{model}}(本文中W^{O}为行值8*64,列值为512,即W^{O}是一个512*512的矩阵)进行线性变换后输出。W^{O}也是一个初始化为随机值的矩阵,用于训练更新。

使用公式表达即为:

其中一个head计算出来的结果为4*64的矩阵,h=8个head计算的结果拼接成一个4*512的矩阵。乘以W^{O}这个512*512的矩阵后,结果是一个4*512的矩阵。

为什么要使用多头注意力机制呢?这就类似卷积神经网络中使用多个卷积核来提取不同特征一样,多头注意力机制能够获取到输入序列中不同的信息进行学习。

同时多头注意力不同于RNN,它是可以多个head同时进行计算,这大大提高了计算的并发性,提升了计算效率。

Add&Norm

在框架图中我们看到经过多头注意力计算后的结果需要经过“Add&Norm”节点。如下图红框所示:

这里是两个计算操作:

  • 残差求和
  • 层规范化

通过残差求和与层规范化处理,能很好抑制梯度消失和爆炸,提升模型的质量。

残差求和

使用残差网络连接是一项神经网络老艺能了,主要是为了减少网络退化并提升训练效果的方法。残差求和即将输入的X和经过多头注意力机制计算的结果求和。

层规范化

Layer Normalization与Batch Normalization方法不同,它对单个样本进行规范化。具体操作就是对矩阵中每一层样本x求均值\mu和方差\sigma

\mu =\frac{1}{N}\sum_{i=1}^{N}x_{i}

\sigma=\sqrt{\frac{1}{N}\sum_{i=1}^{N}(x-\mu )^{2})}

这时计算每一个样本x规范化值的公式是:

\hat{x}_{i}=\frac{x_{i}-\mu}{\sigma}

实际使用中会引入两个学习参数\gamma\beta,在训练中进行更新,这时规范化值的计算是:

y_{i}=\hat{x}_{i} \gamma + \beta

我们例子中的X和多头注意力结果做残差求和后是4*512的矩阵,因此需要做4次在512维样本上的规范化计算。

Add&Norm层的公式表达为:

LayerNorm(X + MultiHeadAttention(X))

前馈层(Feed Forward)

通过编码器第一个子层之后,下一个编码器子层就是前馈层。

前馈层比较简单,是一个两层的全连接层,第一层使用ReLU作为激活函数,第二层不使用激活函数,仅做线性变换。数学公式表达即为:

FFN(X) = ReLU(XW_{1} + b_{1})W_{2} + b_{2}

前馈层之后又做一次Add&Norm,所以这次的Add&Norm公式是:

LayerNorm(X+FFN(X))

具体计算公式跟前面的Add&Norm是一样的,不再赘述。

至此,我们完成了编码器其中一个Layer的计算,这个Layer的输出将输入到下一个Layer中作为输入X。这样的Layer有N个(原文中N取值为6),我们只需要将前一个编码器Layer的输出作为下一个编码器Layer的输入,反复计算,得到最终的编码输出矩阵C

以上是编码器部分,在某些应用场景下,比如特征提取,可以单独拆出编码器使用。在翻译任务中,需要将编码的内容解码,下面开始计算解码器部分。

Outputs嵌入层

在介绍解码器层的计算前,我们注意到输入到解码器的Outputs和输入到编码器的Inputs既相似又有不同。如红框所示:

相同的是它也需要进行Embedding和Positional Encoding两步操作,这里不再赘述,大家可以翻看前面“输入的嵌入层”内容。

与Inputs不同的是下面有个标注“(shifted right)”,这是因为在翻译任务中需要根据之前的预测来计算预测下一个输出。为了让解码器能感知到输出语句的开始和结束,需要引入<start>和<end>两个标志token,所以咱们的例子是:

上图中,首先"<start>"的嵌入及位置编码输入解码器第一个多头注意力层,预测的目标值是"I"。第二次计算预测值时,将"<start> I"的嵌入及位置编码输入解码器第一个多头注意力层,预测的目标值是"love"。如此直到遇到"<end>"完成解码计算。

实际计算中是将"<start> I love programming"编码转换后一并输入多头注意力层,但这样会出现一个问题,就是我们预测"love"的时候,它只依赖"<start> I",而不能预先知道"programming"。也就是第i个字符的预测依赖[0, i]这段的注意力值,而需要摒弃i之后的字符。这就需要引入掩码矩阵。

解码器掩码多头注意力机制(Masked Multi-Head Attention)

这是解码器的第一个多头注意力层,如下图红框中所示:

与编码器中的多头注意力计算相似,分别计算它的h组(Q、K、V)线性变换,计算过程可以参考编码器多头注意力机制一节的内容。不同的是这次在注意力计算\frac{QK^{T}}{\sqrt{d_{k}}}之后需要再进行掩码运算,也就是前面提到的一个可选Mask节点在编码器中没有使用,在解码器中这个就必须应用上,用来防止预测第i位字符时“偷看”了i之后的预测值。

具体做法可以给\frac{QK^{T}}{\sqrt{d_{k}}}运算结果乘以矩阵\begin{bmatrix} 1 & 0 & ... & 0 \\ 1& 1 &0 & ...\\ ... & 1 &1 & 0 \\ 1& ... & 1 & 1 \end{bmatrix},这样保留i及之前的注意力值。也可以像原文那样做相加运算,不过上述矩阵1的位置改为0,0的位置改为一个很大的负数-\infty,这样矩阵相加后i之后的位置影响也被消除。

编码器输入的多头注意力机制

这一层的注意力值计算方式跟编码器中的多头注意力机制类似,不同的是Q来自解码器前面掩码多头注意力层的输出计算线性变换后得到,K和V来自编码器输出的结果线性变换后得到。其他计算与之前的多头注意力计算并无二致。

这一层的作用是在解码时也引入了编码器的输入信息,将输入和第i个位置之前的输出结合起来预测第i+1位的输出。

解码器中前馈层及Add&Norm运算和前面编码器中介绍的对应内容是一样的,此处不再重复介绍。总之经过一系列的注意力值和Add&Norm及前馈运算,如此反复N次(原文中N为6),最终数据来到线性层及Softmax层。

线性和Softmax预测

完成解码器的运算之后,结果矩阵Z是一个行数与解码器输入字符数(本文为3)一致、列数为任意某一个值的矩阵。矩阵Z将进行线性运算和Softmax计算得到预测值,如下图红框中所示部分:

Linear层

线性层的目的是将解码器的输出矩阵Z计算出一个logits向量,表示词库中各词的分值。首先将Z的每行打平,假设Z\in \mathbb{R}^{l\times m},其中l是输入字符数(此处为3),m是选择的值,假设为64。则将Z打平为一个1\times 192的向量(3*64=192)。

LinearLayer=X\cdot W

上式中W\in \mathbb{R}^{192\times n},n为目标词库的大小(比如这里翻译成英文,所以词库可以设置为某英文词库大小)。

Linear层计算出logits向量,对词库中每一个对应的词都提供了表示相关性的数值。

Softmax层

经过Linear层计算的结果,需要进行softmax计算来确定每个词对应的概率,最高概率所在位置的词即为预测的词。Softmax函数定义如下:

S(x_{i}) = \frac{e^{x_{i}}}{\sum _{j=1}^{n}e^{x_{j}}}

其中x_{i}x_{j}为第i和第j位上的logits值,S(x_{i})为对应logits向量第i维度上的softmax值。由此得到的向量中,以最大值所在位置为索引从词库中查找对应的词即为预测到的目标词。

后记

至此,我们完成了所有Transformer各层的计算。这里只简单介绍了每一步的计算方法,并没有讲解如何train和validation和test,只是提供一种理解Transformer这个模型架构的思路。参考资料中2和3相关示例和视频,可以相互借鉴。

当然,这是Transformer初次提出的论文内容,其结构中依然有很多需要改进的地方,还有一些更多的应用场景。关于Transformer的综述,可以看看参考资料4。

参考资料

  1. Attention Is All You Need
  2. Solving Transformer by Hand: A Step-by-Step Math Example
  3. Transformers for beginners | What are they and how do they work
  4. A Survey of Transformers

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/9569.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二叉树-堆

树 在数据库中&#xff0c;树是一种数据结构&#xff0c;用于组织和存储数据&#xff0c;使得可以高效地进行插入、删除和查找操作。它通常用于表示层次关系或者有序集合。 基本概念 节点&#xff1a;树结构中的每个元素都称为节点。 根节点&#xff1a;树的最顶端节点。 子…

RazorSQL for Mac:强大而全面的数据库管理工具

RazorSQL for Mac是一款功能强大、操作简便的数据库管理工具。它专为Mac用户设计&#xff0c;支持连接超过30种不同类型的数据库&#xff0c;包括MySQL、Oracle、PostgreSQL等&#xff0c;为用户提供了全面的数据库管理解决方案。 RazorSQL具有强大的数据库浏览功能&#xff0c…

陈文自媒体:创业如何才能持续小成功?

最近看了一些书&#xff0c;听了一些人讲的鸡汤&#xff0c;有点启发&#xff0c;写一些个人的心得让大家参考一下。 1、对于所谓的创业来说&#xff0c;不是追求成功&#xff0c;正确的姿势是&#xff0c;应该立于不败之地&#xff0c;只有不败才有机会成功&#xff0c;怎么才…

Java基础知识(12)

Java基础知识&#xff08;12&#xff09; &#xff08;包括&#xff1a;多线程&#xff09; 目录 Java基础知识&#xff08;12&#xff09; 一.多线程 1. 多线程基础 2. 多线程的实现方式 【1】继承Thread类的方式进行实现 【2】实现Runnable接口的方式进行实现 【3】…

一文搞懂什么是外贸企业邮箱?

一文搞懂什么是外贸企业邮箱&#xff1f;外贸企业邮箱&#xff0c;也就是外贸行业使用的企业邮箱系统&#xff0c;一般需要具备海外抵达率高、安全稳定等特点&#xff0c;通过外贸企业邮箱&#xff0c;企业可以和国内国外的客户或者同事进行业务的沟通交流。 一、什么是外贸企…

asp.net mvc使用IHttpModule拦截所有请求,包括资源文件

目录 HttpApplication 类 添加App_Code文件夹 MyHttpModel2 Web.config添加配置&#xff0c;在iis模块中生效 项目发布后&#xff0c;察看注册的自定义模块 框架集&#xff1a;.NET Framework 4.7web框架&#xff1a;asp.net mvc 5 HttpApplication 类 HttpApplication 类…

TensorFlow与PyTorch:哪个更适合深度学习项目?

TensorFlow和PyTorch都是当前深度学习领域中非常流行和强大的框架&#xff0c;它们各有优势和特点。选择哪一个框架往往取决于具体的项目需求、团队熟悉度以及社区支持等因素。下面是对两者的一些比较&#xff0c;帮助你根据不同的需求选择最合适的框架&#xff1a; ### 1. 易…

gtk_overviewGTK入门

GTK入门 Gtk概述 GUI GUI 含义&#xff1a; &#xff08;Graphics User Interface&#xff09; 图形用户界面&#xff0c; 是计算机与使用者之间的对话接口&#xff0c; 是计算机重要的组成部分&#xff0c; 比如说咱们使用电脑或手机看到的 Windows 的桌面或 wps 软件显示…

记录下搭高可用集群中Hadoop的几个配置

不断补充中... DataNode的配置&#xff1a; 假设我有5台服务器&#xff0c;分别是hadoop100-104&#xff0c;我现在需要在100和101上配置NameNode&#xff0c;在102-104上配DataNode&#xff0c;我需要在我的workers文件中增加如下内容 [atguiguhadoop102 hadoop]$ vim /opt…

YOLOV5加入Convnext模块,助力涨点!

我们找到models文件夹中的common.py文件,添加CNeB模块,如下 ########################convnext############################# class Block(nn.Module):r""" ConvNeXt Block. There are two equivalent implementations:(1) DwConv -> LayerNorm (channels…

gorm-sharding分表插件升级版

代码地址&#xff1a; GitHub - 137/gorm-sharding: Sharding 是一个高性能的 Gorm 分表中间件。它基于 Conn 层做 SQL 拦截、AST 解析、分表路由、自增主键填充&#xff0c;带来的额外开销极小。对开发者友好、透明&#xff0c;使用上与普通 SQL、Gorm 查询无差别.解决了原生s…

传统鞋业如何转型?3D数字化技术让鞋业品牌焕发新机!

数字经济时代&#xff0c;3D数字化技术在各行业都得到广泛应用&#xff0c;这其中&#xff0c;传统的鞋服行业的发展也受到了3D数字化技术的影响&#xff0c;产生了深刻的变化&#xff0c;越来越多的鞋企品牌开始尝试3D数字化营销。 比如&#xff0c;时尚运动品牌VANS就在官网上…

论文AIGC检测让毕业生头疼,如何有效降低AI查重率!

在准备毕业论文的过程中&#xff0c;不知道大家有没有跟我一样&#xff0c;遇到这样棘手的问题。我们都知道在撰写完论文后&#xff0c;进行论文查重是我们必不可少的一步。于是&#xff0c;我拿着论文进行了论文重复率的检测&#xff0c;发现重复率只有2.8%&#xff0c;看到这…

探案录 | KingbaseES+SqlSugar为医疗用户排忧解难

在2024年的初春&#xff0c;某大型三甲医院的CT预约系统上线测试&#xff0c;如同新芽破土&#xff0c;充满了希望与活力。然而&#xff0c;仅仅两天后&#xff0c;一个技术难题如同迷雾中的幽灵&#xff0c;悄然出现&#xff1a;The connection pool has been exhausted…… 福…

Python 继承顺序

继承顺序的逻辑是非常重要的,它决定了在使用子类的属性和方法时,Python 解释器的搜索顺序。 在 Python 中,当一个类继承自多个父类时,解释器会按照一定的顺序搜索属性和方法。这个搜索顺序被称为方法解析顺序(Method Resolution Order, MRO)。 假设我们有以下三个类: class …

三相两电平逆变器的Simulink仿真建模及SPWM

三相两电平逆变器的介绍 三相两电平逆变器的电路结构如下图所示&#xff0c;作为非常基本的电力电子电路&#xff0c;众多教科书中均有对该电路的原理介绍&#xff0c;本文不再对原理进行赘述&#xff0c;主要目的在于提供simulink仿真电路。下图即为三相两电平逆变器电路结构…

图文并茂:解析Spring Boot Controller返回图片的三种方式

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 图文并茂&#xff1a;解析Spring Boot Controller返回图片的三种方式 前言使用Base64编码返回图片使用byte数组返回图片使用Resource对象返回图片图片格式转换与性能对比 前言 在互联网的世界里&…

AI雷达智能销售名片小程序源码系统+企业商城+公司动态 带完整的安装代码包以及安装搭建教程

在数字化时代&#xff0c;企业的销售模式正在经历一场深刻的变革。为了更好地满足市场需求&#xff0c;提升销售效率&#xff0c;罗峰给大家分享一款集AI雷达智能销售名片、企业商城、公司动态于一体的源码系统。该系统不仅配备了完整的安装代码包&#xff0c;还附有详细的安装…

批量自定义重命名,一键添加顺序编号,文件夹管理更高效!

我们经常需要对文件夹进行管理和整理。然而&#xff0c;当面对大量需要改名的文件夹时&#xff0c;手动逐个修改不仅效率低下&#xff0c;还容易出错。那么&#xff0c;有没有一种方法能够批量自定义重命名文件夹&#xff0c;并在名称后自动添加顺序编号呢&#xff1f;答案是肯…

Makefile解析(ARM LINLON V5/V7 VPU firmware tools例)

根目录Makefile 初始化一些变量 TARGETS : model executiontb cpu ROOT_DIR?$(abspath $(CURDIR)) OUT_DIR?$(abspath $(CURDIR)) ADDR_FILE:$(ROOT_DIR)/build/mmu_addr.txtmake all 执行 make help all: help.PHONY后面跟的目标都被称为伪目标&#xff0c;也就是说我们 mak…