《Attention Is All You Need》是Google在2017年提出的一篇将Attention思想发挥到极致的论文。该论文提出的Transformer模型,基于encoder-decoder架构,抛弃了传统的RNN、CNN模型,仅由Attention机制实现,并且由于encoder端是并行计算的,训练时间大大缩短
Transformer模型广泛应用于NLP领域,机器翻译、文本摘要、问答系统等等,目前火热的Bert模型就是基于Transformer模型构建的。

Transformer优点:transformer架构完全依赖于Attention机制,解决了输入输出的长期依赖问题,并且拥有并行计算的能力,大大减少了计算资源的消耗。self-attention模块,让源序列和目标序列首先“自关联”起来,这样的话,源序列和目标序列自身的embedding表示所蕴含的信息更加丰富,而且后续的FFN层也增强了模型的表达能力。Muti-Head Attention模块使得Encoder端拥有并行计算的能力.

一 整体架构

Transformer 的代码:Attention Is All You Need | Papers With Code#2 best model for Multimodal Machine Translation on Multi30K (BLUE (DE-EN) metric)https://paperswithcode.com/paper/attention-is-all-you-need这部分我们来看看Transformer的架构。Transformer遵循了Encoder-Decoder的架构。

在Encoder方面,6个编码器组件协同工作,组成一个大的编码器,解码器同样由6个解码器组件组成。

  1. Encoder  6个编码器组件依次排列,每个组件内部都是由一个多头attention加上一个前馈网络,attenion和前馈的输出都经过层归一化(LayerNormalization),并且都有各自的残差网络.
  2. Decoder  组件的配置基本相同, 不同的是Decoder有两个多头attention机制,一个是其自身的mask自注意力机制,另一个则是从Encoder到Decoder的注意力机制,而且是Decoder内部先做一次attention后再接收Encoder的输出。

说完了Encoder和Decoder,再说说输入,模型的输入部分由词向量(embedding)经位置编码(positional Encoding)后输入到Encoder和Decoder。编码器的输出由一个线性层和softmax组成,将浮点数映射成具体的符号输出。

首先,Transformer模型也是使用经典的encoer-decoder架构,由encoder和decoder两部分组成。

上图的左半边用Nx框出来的,就是我们的encoder的一层。encoder一共有6层这样的结构。
上图的右半边用Nx框出来的,就是我们的decoder的一层。decoder一共有6层这样的结构。
输入序列经过word embedding和positional encoding相加后,输入到encoder。
输出序列经过word embedding和positional encoding相加后,输入到decoder。
最后,decoder输出的结果,经过一个线性层,然后计算softmax。

这张图是一个已经简单的翻译模型,将英文翻译成中文.

比如我们输入英文 Why do we work ? 我们需要将其翻译成中文,我们根据序号来看
【1】将英文文本输入到输入到编码器的模型中去
【2】通过编码器得到我们的隐藏层特征
【3】我们输入<start>开始token
【4】解码器结合我们的隐藏层特征输出
【5】我们再将我们得到的 输出到解码器中
【6】解码器结合我们隐藏层特征会输出 
【7】依次按照这种方式不断输入到解码器中,我们将得出我们解码器翻译出来的中文语句。
  链接:https://www.jianshu.com/p/29e31b73528e

二 Encoder 与 Decoder

 编码器和解码器块实际上是相互堆叠在一起的多个相同的编码器和解码器。 编码器堆栈和解码器堆栈都具有相同数量的单元。

编码器和解码器单元的数量是一个超参数。文中使用了 6 个编码器和解码器。

让我们看看编码器和解码器堆栈的设置是如何工作的:

  • 将输入序列的词嵌入(word embeddings)传递给第一个编码器。

  • 然后将它们进行转换并传播到下一个编码器。

  • 编码器堆栈中最后一个编码器的输出将传递给解码器堆栈中所有的解码器,如下图所示:

这里需要注意的一点是,除了自注意力和前馈层外,解码器还有一层解码器 - 解码器注意力层。这有助于解码器将注意力集中在输入序列的适当部分上。

1. Encoder

由 6 层组成,每一层包括两个子层:

「第一层」是 multi-head self-attention 层,

「第二层」是一个简单的全连接前馈网络。在每个子层后,都接了一个「残差连接以及归一化」,即每个子层的输出为 LayerNorm(X + Sublayer(X)) 为了方便残差连接,模型中的所有子层,包括 embedding 层(初始词嵌入) 输出向量维度均为 d_{model}=512

2. Decoder

同样由 6 层组成,每一层包括三个子层:

「第一层」是 masked multi-head self-attention 层,注意其输入仅包含「当前位置之前的词语信息」,这样设计的目的是解码器是按顺序解码的,其当前输出只能基于已输出的部分。

「第二层」是 multi-head self-attention 层,其输入包含编码器的输出信息(矩阵 K 和矩阵 V ),「第三层」是全连接前馈网络。每个子层后同样加入了「残差连接和归一化」。下图给出了编码器和解码器的内部结构,注意前馈神经网络对于序列每个位置的独立性。

三 Positional Encoding

[深度学习] 自然语言处理---Transformer 位置编码介绍_小墨鱼的专栏-CSDN博客_自然语言处理位置编码https://zengwenqi.blog.csdn.net/article/details/120294257

四 Self Attention 与 Mutil Head Attention

自注意力,有时也称为内部注意力(intra-attention),是一种注意力机制,它将一个序列的不同位置联系起来,以计算出序列的表示形式。

它指的是 street 还是 animal?这对我们来说是一个简单的问题,但对算法来说可不是这样的。当模型处理到“it”这个单词时,自注意力试图将“it”与同一句话中的“animal”联系起来。

熟悉attention原理的童鞋都知道,attention可由以下形式表示

首先说明一下我们的K、Q、V是什么:

  • 在encoder的self-attention中,Q、K、V都来自同一个地方(相等),他们是上一层encoder的输出。对于第一层encoder,它们就是word embedding和positional encoding相加得到的输入。
  • 在decoder的self-attention中,Q、K、V都来自于同一个地方(相等),它们是上一层decoder的输出。对于第一层decoder,它们就是word embedding和positional encoding相加得到的输入。但是对于decoder,我们不希望它能获得下一个time step(即将来的信息),因此我们需要进行sequence masking
  • 在encoder-decoder attention中,Q来自于decoder的上一层的输出,K和V来自于encoder的输出,K和V是一样的。
  • Q、K、V三者的维度一样,即 dq = dk = dv

scaled dot-product attentiondot-product attention唯一的区别就是,scaled dot-product attention有一个缩放因子 1/\sqrt{d_{k}}  上面公式中的dk表示的是K的维度,在论文里面,默认是64

那么为什么需要加上这个缩放因子呢?

论文里给出了解释:对于dk很大的时候,点积得到的结果维度很大,使得结果处于softmax函数梯度很小的区域。我们知道,梯度很小的情况,这对反向传播不利。为了克服这个负面影响,除以一个缩放因子,可以一定程度上减缓这种情况。

其思想和attention类似,但是self-attention是Transformer用来将其他相关单词的“理解”转换成我们正在处理的单词的一种思路

我们看个例子:

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

2. The animal didn’t cross the street because it was too wide

两句话中的单词 it 指代不同,第一句话 it 指代 animal 而第二句指代 street。对于我们来说能很简单的判断出来,但是对于机器来说,是很难判断的,尤其是相对于传统seq2seq模型。两句话在单词 it 之前的内容是一样的,传统seq2seq模型encoder的顺序输入导致模型无法区分这种差别。而self-attention机制通过计算单词it与其他词之间的联系得知it的具体指代,最终结果如下图所示。

Self Attention就是句子中的某个词对于本身的所有词做一次Attention。算出每个词对于这个词的权重,然后将这个词表示为所有词的加权和。每一次的Self Attention操作,就像是为每个词做了一次Convolution操作或Aggregation操作。具体操作如下:

1.首先,self-attention会计算出三个新的向量,在论文中,向量的维度是512维,我们把这三个向量分别称为Query、Key、Value,这三个向量是用embedding向量与一个矩阵相乘得到的结果,这个矩阵是随机初始化的,维度为(64,512)注意第二个维度需要和embedding的维度一样,其值在BP的过程中会一直进行更新,得到的这三个向量的维度是64。

2. 计算self-attention的分数值,该分数值决定了当我们在某个位置encode一个词时,对输入句子的其他部分的关注程度。这个分数值的计算方法是Query与Key做点成,以下图为例,首先我们需要针对Thinking这个词,计算出其他词对于该词的一个分数值,首先是针对于自己本身即q1·k1,然后是针对于第二个词即q1·k2。

3. 接下来,把点成的结果除以一个常数,这里我们除以8,这个值一般是采用上文提到的矩阵的第一个维度的开方即64的开方8,当然也可以选择其他的值,然后把得到的结果做一个softmax的计算。得到的结果即是每个词对于当前位置的词的相关性大小,当然,当前位置的词相关性肯定会会很大。

4.下一步就是把Value和softmax得到的值进行相乘,并相加,得到的结果即是self-attetion在当前节点的值。

在实际的应用场景,为了提高计算速度,我们采用的是矩阵的方式,直接计算出Query, Key, Value的矩阵,然后把embedding的值与三个矩阵直接相乘,把得到的新矩阵 Q 与 K 相乘,除以一个常数,做softmax操作,最后乘上 V 矩阵。

归一化之前需要通过除以向量的维度dk来进行标准化,所以最终Self Attention用矩阵变换的方式可以表示为

最终每个Self Attention接受n个词向量的输入,输出n个Aggregated的向量。

这种通过 query 和 key 的相似性程度来确定 value 的权重分布的方法被称为scaled dot-product attention。

上文提到Encoder中的Self Attention与Decoder中的有所不同,Encoder中的Q、K、V全部来自于上一层单元的输出,而Decoder只有Q来自于上一个Decoder单元的输出,K与V都来自于Encoder最后一层的输出。也就是说,Decoder是要通过当前状态与Encoder的输出算出权重后,将Encoder的编码加权得到下一层的状态。

三 Multi-head Attention

理解了Scaled dot-product attention,Multi-head attention也很简单了。Multi-Head Attention就是将上述的Attention做h遍,然后将h个输出进行concat得到最终的输出。这样做可以很好地提高算法的稳定性,在很多Attention相关的工作中都有相关的应用。

论文进一步增加了multi-headed的机制到self-attention上,在如下两个方面提高了attention层的效果:

  1. 多头机制扩展了模型集中于不同位置的能力。在上面的例子中,z1只包含了其他词的很少信息,仅由实际自己词决定。在其他情况下,比如翻译 “The animal didn’t cross the street because it was too tired”时,我们想知道单词"it"指的是什么。
  2. 多头机制赋予attention多种子表达方式。像下面的例子所示,在多头下有多组query/key/value-matrix,而非仅仅一组(论文中使用8-heads)。每一组都是随机初始化,经过训练之后,输入向量可以被映射到不同的子表达空间中。

论文提到,他们发现将Q、K、V通过一个线性映射之后,分成 h份,对每一份进行scaled dot-product attention效果更好。然后,把各个部分的结果合并起来,再次经过线性映射,得到最终的输出。这就是所谓的multi-head attention。上面的超参数 h就是heads数量。论文默认是8

这会带来点麻烦,前向网络并不能接收八个矩阵,而是希望输入是一个矩阵,所以要有种方式处理下八个矩阵合并成一个矩阵。

Transformer的实现中,为了提高Multi-Head的效率,将W扩大了h倍,然后通过view(reshape)和transpose操作将相同词的不同head的k、q、v排列在一起进行同时计算,完成计算后再次通过reshape和transpose完成拼接,相当于对于所有的head进行了一个并行处理。

值得注意的是,上面所说的分成 h 份是在 dk,kq,dv 维度上面进行切分的。因此,进入到scaled dot-product attention的 dk 实际上等于未进入之前的 Dk/h, self-attention则是取Q,K,V相同。

作者同样提到了另一种复杂度相似但计算方法additive attention,在 dk 很小的时候和dot-product结果相似,dk大的时候,如果不进行缩放则表现更好,但dot-product的计算速度更快,进行缩放后可减少影响(由于softmax使梯度过小,具体可见论文中的引用)

同维度中的单头与多头的区别?

Attention是将query和key映射到同一高维空间中去计算相似度,而对应的multi-head attention把query和key映射到高维空间\alpha,的不同子空间 (\alpha _{1}, \alpha _{2},......, \alpha _{h}) 中去计算相似度。

为什么要做multi-head attention?论文原文里是这么说的:

Multi-head attention allows the model to jointly attend to information from different representation subspaces at different positions. With a single attention head, averaging inhibits this.

也就是说,这样可以在不改变参数量的情况下增强每一层attention的表现力。

在多头注意力中,对于初学者来说一个比较经典的问题就是,在相同维度下使用单头和多头的区别是什么?这句话什么意思呢?以图1-10中示例为例,此时的自注意力中使用了两个头,每个头的维度为dq,即采用了多头的方式。另外一种做法就是,只是用一个头,但是其维度为2*dq,即采用单头的方式。那么在这两种情况下有什么区别呢?

首先,从论文中内容可知,作者在头注意力机制与多头个数之间做了如下的限制

可以看出,单个头注意力机制的维度dq乘上多头的个数h就等于模型的维度[公式]

那现在的问题是图1-16中的Z能够计算得到吗?答案是不能。为什么?因为我没有告诉你这里的h等于多少。如果我告诉你多头h=2,那么毫无疑问图1-16的计算过程就等同于图1-14的计算过程. 且此时dk=dm/2。

但是如果我告诉你多头h=3,那么图1-16的计算过程会变成

且此时dk=dm/3。

现在回到一开始的问题上,根据上面的论述我们可以发现,在dm固定的情况下,不管是使用单头还是多头的方式,在实际的处理过程中直到进行注意力权重矩阵计算前,两者之前没有任何区别。当进行进行注意力权重矩阵计算时,h越大那么Q,K,V就会被切分得越小,进而得到的注意力权重分配方式越多,

同时,当h不一样时,dk的取值也不一样,进而使得对权重矩阵的scale的程度不一样。例如,如果dm=768,那么当[公式]时,则[公式];当[公式]时,则[公式]

所以,当模型的维度dm确定时,一定程度上h越大整个模型的表达能力越强,越能提高模型对于注意力权重的合理分配。

Multi-head Attention的本质是,在参数总量保持不变的情况下,将同样的query, key, value映射到原来的高维空间的不同子空间中进行attention的计算,在最后一步再合并不同子空间中的attention信息。这样降低了计算每个head的attention时每个向量的维度,在某种意义上防止了过拟合;由于Attention在不同子空间中有不同的分布,Multi-head Attention实际上是寻找了序列之间不同角度的关联关系,并在最后concat这一步骤中,将不同子空间中捕获到的关联关系再综合起来。

三 Position-wise Feed Forward

每一层经过attention之后,还会有一个FFN,这个FFN的作用就是空间变换。FFN包含了2层linear transformation层,中间的激活函数是ReLu。

曾经我在这里有一个百思不得其解的问题:attention层的output最后会和 Wo相乘,为什么这里又要增加一个2层的FFN网络?

其实,FFN的加入引入了非线性(ReLu激活函数),变换了attention output的空间, 从而增加了模型的表现能力。把FFN去掉模型也是可以用的,但是效果差了很多。

相关问题

Transformer为什么需要进行Multi-head Attention?

原论文中说到进行Multi-head Attention的原因是将模型分为多个头,形成多个子空间,可以让模型去关注不同方面的信息,最后再将各个方面的信息综合起来。其实直观上也可以想到,如果自己设计这样的一个模型,必然也不会只做一次attention,多次attention综合的结果至少能够起到增强模型的作用,也可以类比CNN中同时使用多个卷积核的作用,直观上讲,多头的注意力有助于网络捕捉到更丰富的特征/信息

Transformer相比于RNN/LSTM,有什么优势?为什么?

  • RNN系列的模型,并行计算能力很差。RNN并行计算的问题就出在这里,因为 T 时刻的计算依赖 T-1 时刻的隐层计算结果,而 T-1 时刻的计算依赖 T-2 时刻的隐层计算结果,如此下去就形成了所谓的序列依赖关系。

  • Transformer的特征抽取能力比RNN系列的模型要好。

    具体实验对比可以参考:放弃幻想,全面拥抱Transformer:自然语言处理三大特征抽取器(CNN/RNN/TF)比较

    但是值得注意的是,并不是说Transformer就能够完全替代RNN系列的模型了,任何模型都有其适用范围,同样的,RNN系列模型在很多任务上还是首选,熟悉各种模型的内部原理,知其然且知其所以然,才能遇到新任务时,快速分析这时候该用什么样的模型,该怎么做好。

为什么说Transformer可以代替seq2seq?

seq2seq缺点:这里用代替这个词略显不妥当,seq2seq虽已老,但始终还是有其用武之地,seq2seq最大的问题在于将Encoder端的所有信息压缩到一个固定长度的向量中,并将其作为Decoder端首个隐藏状态的输入,来预测Decoder端第一个单词(token)的隐藏状态。在输入序列比较长的时候,这样做显然会损失Encoder端的很多信息,而且这样一股脑的把该固定向量送入Decoder端,Decoder端不能够关注到其想要关注的信息。

Transformer优点:transformer不但对seq2seq模型这两点缺点有了实质性的改进(多头交互式attention模块),而且还引入了self-attention模块,让源序列和目标序列首先“自关联”起来,这样的话,源序列和目标序列自身的embedding表示所蕴含的信息更加丰富,而且后续的FFN层也增强了模型的表达能力,并且Transformer并行计算的能力是远远超过seq2seq系列的模型,因此我认为这是transformer优于seq2seq模型的地方。

Transformer如何并行化的?

Transformer的并行化我认为主要体现在self-attention模块,在Encoder端Transformer可以并行处理整个序列,并得到整个输入序列经过Encoder端的输出,在self-attention模块,对于某个序列x1,x2,…,xn,self-attention模块可以直接计算xi,xj的点乘结果,而RNN系列的模型就必须按照顺序从x1​计算到xn。

训练-模型的参数在哪里?

Transformer的工作流程就是上面介绍的每一个子流程的拼接

  • 输入的词向量首先叠加上Positional Encoding,然后输入至Transformer内
  • 每个Encoder Transformer会进行一次Multi-head self attention->Add & Normalize->FFN->Add & Normalize流程,然后将输出输入至下一个Encoder中
  • 最后一个Encoder的输出将会作为memory保留
  • 每个Decoder Transformer会进行一次Masked Multi-head self attention->Multi-head self attention->Add & Normalize->FFN->Add & Normalize流程,其中Multi-head self attention时的K、V至来自于Encoder的memory。根据任务要求输出需要的最后一层Embedding。
  • Transformer的输出向量可以用来做各种下游任务

Encoder端可以并行计算,一次性将输入序列全部encoding出来,但Decoder端不是一次性把所有单词(token)预测出来的,而是像seq2seq一样一个接着一个预测出来的。

transformer的核心点积是没有参数,transformer结构的训练,会优化的参数主要在:

  1. 嵌入层-Word Embedding
  2. 前馈(Feed Forward)层
  3. 多头注意力中的“切片”操作(映射成多个/头小向量)实际是一个全连接层(线性映射矩阵),以及多头输出拼接结果(Concat)后会经过一个Linear全连接层。这两个全连接层也是残差块有意义的地方,如果没有这一层,那这个注意力机制中就没有参数,残差就没有意义了。

参考文献

  • Attention is all your need
  • 《Attention is All You Need》浅读(简介+代码)
  • Attention机制概念学习笔记
  • Attention和Transformer
  • 0基础看懂BERT中attention机制的套路
  • Attention机制详解(二)——Self-Attention与Transformer
  • 最详细的Transformer英文版解释(内涵高清大图)
  • Multilingual Hierarchical Attention Networks for Document Classification
  • Layer Normalization
  • Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift
  • 为什么ResNet和DenseNet可以这么深?一文详解残差块为何有助于解决梯度弥散问题
  • Attention?Attention!
  • Transformer原理和实现
  • 一文看懂Transformer内部原理(含PyTorch实现)
  • Building the Mighty Transformer for Sequence Tagging in PyTorch : Part I
  • Building the Mighty Transformer for Sequence Tagging in PyTorch : Part II
  • Transformer 模型的 PyTorch 实现
  • 9012年,该用bert打比赛了
  • BERT中文翻译PDF版
  • Transformer模型详解
  • 图解Transformer(完整版)
  • 关于Transformer的若干问题整理记录
  • Transformer各层网络结构详解!面试必备!(附代码实现)

[深度学习] 自然语言处理---Transformer原理(一)相关推荐

  1. [深度学习] 自然语言处理---Transformer实现(二)

    目录 Encoder-Decoder框架 一 整体架构 动态流程图 二 Encoder 2.1 Encoder Layer和残差网络 Residual Connection 2.2 Attention ...

  2. [深度学习] 自然语言处理---Transformer 位置编码介绍

    2017年来自谷歌的Vaswani等人提出了Transformer模型,一种新颖的纯粹采用注意力机制实现的Seq2Seq架构,它具备并行化训练的能力,拥有非凡的性能表现,这些特点使它深受NLP研究人员 ...

  3. [深度学习] 自然语言处理 --- Self-Attention(一) 基本介绍

    [深度学习] 自然语言处理 --- Self-Attention(一) 基本介绍_小墨鱼的专栏-CSDN博客https://zengwenqi.blog.csdn.net/article/detail ...

  4. 好书荐读:阿里达摩院算法专家领衔《深度学习与图像识别:原理与实践》

    点上方蓝字计算机视觉联盟获取更多干货 在右上方 ··· 设为星标 ★,与你不见不散 编辑:Sophia计算机视觉联盟  报道  | 公众号 CVLianMeng 这本书现在当当新书榜排名前三 既然为大 ...

  5. 机器学习/深度学习/自然语言处理学习路线

    原文地址:http://www.cnblogs.com/cyruszhu/p/5496913.html 未经允许,请勿用于商业用途!相关请求,请联系作者:yunruizhu@126.com转载请附上原 ...

  6. 解析深度学习:卷积神经网络原理与视觉实践

    解析深度学习:卷积神经网络原理与视觉实践 魏秀参 著 ISBN:9787121345289 包装:平装 开本:16开 正文语种:中文 出版社: 电子工业出版社 出版时间:2018-11-01

  7. 【NAACL2021】Graph4NLP:图深度学习自然语言处理(附ppt)

    来源:专知本文约1500字,建议阅读5分钟 最新图深度学习在自然语言处理应用的概述报告,不可错过! 深度学习已经成为自然语言处理(NLP)研究的主导方法,特别是在大规模语料库中.在自然语言处理任务中, ...

  8. 重磅推荐!机器学习|深度学习|自然语言处理 书籍/课程/资料/资源大分享!

    深度学习是如今最火热的技术之一,但是对于有心入门却不得其法的同学来说,选择适合自己的书籍至关重要. 本着乐于助人.无私奉献的精神,小编特意为大家精选了 10本深度学习相关的书籍.这些书籍中,有些非常注 ...

  9. 【深度学习】Swin Transformer结构和应用分析

    [深度学习]Swin Transformer结构和应用分析 文章目录 1 引言 2 Swin Transformer结构 3 分析3.1 Hierarchical Feature Representa ...

最新文章

  1. IIS 部署 node.js ---- 基础安装部署
  2. QTP连接oracle
  3. UIBarButtonSystemItem 各种款式
  4. 公共技术点之 Java 反射 Reflection
  5. Spring 3.1和Hibernate的持久层
  6. 人生没有后悔药,云主机可以有“时光机”
  7. 【重难点】【事务 03】分布式事务
  8. popoverController简单介绍
  9. 我敢打赌,你对ConcurrentHashMap不了解?
  10. js获取request参数值(javascript 获取request参数值的方法)
  11. 07到09程序员对自己工资的态度···
  12. linux 编译chromium,简易编译Chromium OS内核教程
  13. 西门子数控系统变量刀补输入——使用$TC_DP函数
  14. 美通企业日报 | 洲际集团酒店将撤除一次性小包装洗护用品;新能源汽车同时面临新老质量问题...
  15. android 实现ble蓝牙自动配对连接
  16. ib课程可以申请哪些国家的大学?
  17. OpenCore电池显示正在充电:1% 建议维修
  18. office 文档解析
  19. 【观察】从鞍钢数字化转型升级,看如何打造智能制造数据坚实底座?
  20. 3.1、随机森林之随机森林实例

热门文章

  1. 深入php内核,从底层c语言剖析php实现原理
  2. java并发中的延迟初始化
  3. docker第二天:管理docker镜像与容器(上)
  4. 新一代 Tor发布, 它牛在哪里
  5. 开源NAS系统使用总结
  6. mysql还原数据报错:
  7. Windows 10体验:文件资源管理器变成了首页
  8. Kevin专栏---自定义安装对话框的界面
  9. python flask源码解析_浅谈flask源码之请求过程
  10. Spring源码:AOP(1)