深度学习笔记之Transformer(四)铺垫:LayerNormalization

这篇具有很好参考价值的文章主要介绍了深度学习笔记之Transformer(四)铺垫:LayerNormalization。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言

在介绍 Transformer \text{Transformer} Transformer模型架构之前,首先介绍 Transformer \text{Transformer} Transformer的核心架构之一:层标准化 ( Layer Normalization ) (\text{Layer Normalization}) (Layer Normalization)

回顾:批标准化

问题描述

批标准化 ( Batch Normalization ) (\text{Batch Normalization}) (Batch Normalization)我们在正则化系列中介绍过这个概念。批标准化主要针对两个问题:

  • 各层输出特征的偏移问题与梯度消失问题。
    这里以一个局部的神经网络为例:
    为简单起见,这里各隐藏层 ⋯   , k − 1 , k \cdots,k-1,k ,k1,k均仅包含一个神经元。
    深度学习笔记之Transformer(四)铺垫:LayerNormalization,深度学习,深度学习,笔记,transformer
    其中,关于该局部神经网络的前馈计算过程表示如下:
    其中 σ \sigma σ表示激活函数。
    Y = f ( W ) = σ { [ W K + 1 ] T h K + b K + 1 } ⏟ Output Layer = σ { [ W K + 1 ] T σ [ ( W K ) T h K − 1 + b K − 1 ] ⏟ Layers  K + b K + 1 } = ⋯ \begin{aligned} \mathcal Y = f(\mathcal W) & = \underbrace{\sigma \left\{[\mathcal W_{\mathcal K+1}]^T h_{\mathcal K} + b_{\mathcal K+1}\right\}}_{\text{Output Layer}} \\ & = \sigma \left\{[\mathcal W_{\mathcal K+1}]^T \underbrace{\sigma\left[(\mathcal W_{\mathcal K})^T h_{\mathcal K-1} + b_{\mathcal K-1}\right]}_{\text{Layers } \mathcal K} + b_{\mathcal K+1} \right\} \\ & = \cdots \end{aligned} Y=f(W)=Output Layer σ{[WK+1]ThK+bK+1}=σ [WK+1]TLayers K σ[(WK)ThK1+bK1]+bK+1 =
    如果使用梯度下降法 ( Gradient Descent,GD ) (\text{Gradient Descent,GD}) (Gradient Descent,GD)更新权重,需要计算各时刻权重的梯度信息。假设关于 Y \mathcal Y Y损失函数表示为 J ( W ) \mathcal J(\mathcal W) J(W),关于隐藏层 K \mathcal K K的权重 W K \mathcal W_{\mathcal K} WK t + 1 t+1 t+1时刻的更新过程表示如下:
    其中 η \eta η表示学习率 ( Learning Rate ) (\text{Learning Rate}) (Learning Rate)
    W K ( t + 1 ) ⇐ W K ( t ) − η ⋅ ∇ W K J ( W ) \mathcal W_{\mathcal K}^{(t+1)} \Leftarrow \mathcal W_{\mathcal K}^{(t)} - \eta \cdot \nabla_{\mathcal W_{\mathcal K}} \mathcal J(\mathcal W) WK(t+1)WK(t)ηWKJ(W)
    根据上述的局部神经网络,关于 W K \mathcal W_{\mathcal K} WK t t t时刻的梯度信息 ∇ W K J ( W ) \nabla_{\mathcal W_{\mathcal K}} \mathcal J(\mathcal W) WKJ(W)反向传播过程的梯度表示如下:
    其中 Z K + 1 = ( W K + 1 ) T h K + b K + 1 \mathcal Z_{\mathcal K+1} = (\mathcal W_{\mathcal K+1})^T h_{\mathcal K} + b_{\mathcal K + 1} ZK+1=(WK+1)ThK+bK+1,以此类推 Z K , Z K − 1 , ⋯ \mathcal Z_{\mathcal K},\mathcal Z_{\mathcal K - 1},\cdots ZK,ZK1,
    ∇ W K J ( W ) = ∂ J ( W ) ∂ W K = ∂ J ( W ) ∂ Y ⋅ ∂ Y ∂ Z K + 1 ⋅ ∂ Z K + 1 ∂ h K ⋅ ∂ h K ∂ Z K ⋅ ∂ Z K ∂ W K = ∂ J ( W ) ∂ Y ⋅ σ ′ ( Z K + 1 ) ⋅ ( W K + 1 ) T ⋅ σ ′ ( Z K ) ⋅ h K − 1 \begin{aligned} \nabla_{\mathcal W_{\mathcal K}} \mathcal J(\mathcal W) & = \frac{\partial \mathcal J(\mathcal W)}{\partial \mathcal W_{\mathcal K}} \\ & = \frac{\partial \mathcal J(\mathcal W)}{\partial \mathcal Y} \cdot \frac{\partial \mathcal Y}{\partial \mathcal Z_{\mathcal K + 1}} \cdot \frac{\partial \mathcal Z_{\mathcal K + 1}}{\partial h_{\mathcal K}} \cdot \frac{\partial h_{\mathcal K}}{\partial \mathcal Z_{\mathcal K}} \cdot \frac{\partial \mathcal Z_{\mathcal K}}{\partial \mathcal W_{\mathcal K}} \\ & = \frac{\partial \mathcal J(\mathcal W)}{\partial \mathcal Y} \cdot \sigma'(\mathcal Z_{\mathcal K + 1}) \cdot (\mathcal W_{\mathcal K+1})^T \cdot \sigma'(\mathcal Z_{\mathcal K}) \cdot h_{\mathcal K - 1} \end{aligned} WKJ(W)=WKJ(W)=YJ(W)ZK+1YhKZK+1ZKhKWKZK=YJ(W)σ(ZK+1)(WK+1)Tσ(ZK)hK1
    很明显,我们发现:在计算 W K \mathcal W_{\mathcal K} WK梯度计算的过程中,我们使用到了 W K + 1 \mathcal W_{\mathcal K+1} WK+1参与运算
    同理,如果是更深层的权重,例如前馈计算过程的 1 1 1个权重 W 1 \mathcal W_1 W1,它的反向传播过程会涉及到后续隐藏层的所有权重信息:
    其中 X \mathcal X X表示输入特征信息,它不会更新梯度信息。
    ∇ W 1 J ( W ) = ∂ J ( W ) ∂ Y ⋅ ∏ k = 1 K + 1 σ ′ ( Z k ) ⋅ ∏ k = 2 K W k ⋅ X \nabla_{\mathcal W_1} \mathcal J(\mathcal W) = \frac{\partial \mathcal J(\mathcal W)}{\partial \mathcal Y} \cdot \prod_{k = 1}^{\mathcal K+1} \sigma'(\mathcal Z_k) \cdot \prod_{k=2}^{\mathcal K} \mathcal W_{k} \cdot \mathcal X W1J(W)=YJ(W)k=1K+1σ(Zk)k=2KWkX
    这会导致:随着反向传播的加深,每一层的权重变化会得到累积,从而使深层网络中权重的偏移程度越大

    继续观察上式,影响梯度 ∇ W 1 J ( W ) \nabla_{\mathcal W_1}\mathcal J(\mathcal W) W1J(W)不仅包含各层权重的累积项,还包含各层输出激活函数的导数项 ∏ k = 1 K + 1 σ ′ ( Z k ) \begin{aligned}\prod_{k=1}^{\mathcal K + 1} \sigma'(\mathcal Z_k)\end{aligned} k=1K+1σ(Zk)。在介绍激活函数时我们提到它是一个尽量对于变化量小的值维持恒等映射情况下的非线性函数

    • 在反向传播过程中,恒等映射部分几乎不是我们关注的对象,因为他们的梯度值较大,甚至无限趋近于 1 1 1(因而丢失的梯度信息较小甚至不会丢失梯度信息)。
    • 非线性较强的部分(饱和区)甚至是常数近似区(例如 Sigmoid \text{Sigmoid} Sigmoid激活函数),映射到该部分的特征梯度结果很小甚至趋近于 0 0 0,这导致在连乘计算 ∇ W 1 J ( W ) \nabla_{\mathcal W_1}\mathcal J(\mathcal W) W1J(W)过程中,没有办法将更新信息影响到更深层的权重中,这是一种梯度消失现象。
  • 关于梯度信息比例不平衡问题:
    假设某样本集 X \mathcal X X存在两类随机变量 x 1 , x 2 x_1,x_2 x1,x2,我们使用神经网络对样本 x ( i ) ∈ X x^{(i)} \in \mathcal X x(i)X进行描述:
    深度学习笔记之Transformer(四)铺垫:LayerNormalization,深度学习,深度学习,笔记,transformer
    关于神经网络中权重 W 1 , W 2 \mathcal W_1,\mathcal W_2 W1,W2的梯度表示为如下形式:
    { ∂ Y ∂ W 1 = ∂ Y ∂ Z ⋅ ∂ Z ∂ W 1 = σ ′ ( Z ) ⋅ x 1 ∂ Y ∂ W 2 = ∂ Y ∂ Z ⋅ ∂ Z ∂ W 2 = σ ′ ( Z ) ⋅ x 2 \begin{cases} \begin{aligned} \frac{\partial \mathcal Y}{\partial \mathcal W_1} & = \frac{\partial \mathcal Y}{\partial \mathcal Z} \cdot \frac{\partial \mathcal Z}{\partial \mathcal W_1} = \sigma'(\mathcal Z) \cdot x_1 \\ \frac{\partial \mathcal Y}{\partial \mathcal W_2} & = \frac{\partial \mathcal Y}{\partial \mathcal Z} \cdot \frac{\partial \mathcal Z}{\partial \mathcal W_2} = \sigma'(\mathcal Z) \cdot x_2 \end{aligned} \end{cases} W1YW2Y=ZYW1Z=σ(Z)x1=ZYW2Z=σ(Z)x2
    由于反向传播 W 1 , W 2 \mathcal W_1,\mathcal W_2 W1,W2共用相同的 Z \mathcal Z Z,因此将 σ ′ ( Z ) \sigma'(\mathcal Z) σ(Z)视作常数,从而两梯度之间的差异性归结在 x 1 , x 2 x_1,x_2 x1,x2上面。首先,我们不否认 x 1 , x 2 x_1,x_2 x1,x2可能是线性无关的,甚至是两两正交的。但是需要注意的是:它们描述的信息可能是天差地别

    例如两个随机变量一个表示身高(单位:米),一个表示年龄(单位:岁)。两者之间关联性明显不大,但是二者的取值范围相差较大。如下图:
    身高的范围一般情况下可能是 ( 0 , 2 ) (0,2) (0,2);年龄的范围可能是 ( 0 , 80 ) (0,80) (0,80)(示例) 深度学习笔记之Transformer(四)铺垫:LayerNormalization,深度学习,深度学习,笔记,transformer

上图描述的是样本的特征空间,也是 W 1 , W 2 \mathcal W_1,\mathcal W_2 W1,W2的梯度映射空间 ∂ Z ∂ W 1 , ∂ Z ∂ W 2 \begin{aligned}\frac{\partial \mathcal Z}{\partial \mathcal W_1},\frac{\partial \mathcal Z}{\partial \mathcal W_2}\end{aligned} W1Z,W2Z,其中等高线表示损失函数,对应的红色区域表示损失函数的最优解区域。由于两特征的映射范围差别很大,导致等高线不规则程度也很大。

在权重初始化时,空间中的任意一点都有可能是权重点的初始化位置。这导致:不同位置的权重点,其收敛代价可能不同

  • r 1 r_1 r1这种距离最优解区域较近的点,由于其位置更加陡峭,使用较小的学习率仅在有限的步骤之内即可达到最优解;
  • 相反,如 r 2 r_2 r2这种距离最优解区域较远的点,并且位置较为平滑,这可能导致较小学习率无法使其收敛(或者收敛方向不稳定),如果使用较大学习率,可能会更容易逃离平滑区域,但在最优解区域中可能会产生震荡现象而无法获取最优解
    并且 r 2 r_2 r2同样会出现这种情况,并且因 r 2 r_2 r2的位置、陡峭程度导致其更容易出现这种现象。

因而最终仅能通过权衡选择一个尽量使 r 1 r_1 r1不出现震荡的情况下,选择一个使 r 2 r_2 r2收敛最快的学习率。但它明显不是最优解,因为我们希望它们都收敛得又快又好

问题处理

关于批标准化公式表示如下:

  • 首先对样本执行中心化,就使将分布平移至特征空间原点位置
    x ( i ) − μ B μ B = 1 N ∑ i = 1 N x ( i ) x^{(i)} - \mu_{\mathcal B} \quad \mu_{\mathcal B} = \frac{1}{N}\sum_{i=1}^Nx^{(i)} x(i)μBμB=N1i=1Nx(i)
  • 中心化后得结果除以分布的方差信息,从而将各维度分布方差结果压缩至 1 1 1
    个人错觉纠误:即便各维度特征分布’均在原点附近,并且其方差为 1 1 1,但和‘各维度分布相同’不是同一个概念。这个‘压缩’后的分布依然保持着对应特征分布的原始形状,只不过位置和大小通过规则进行约束。
    { σ B = ϵ + 1 N ∑ i = 1 N ( x ( i ) − μ B ) 2 x ^ ( i ) = x ( i ) − μ B σ B y ( i ) = γ x ^ ( i ) + β \begin{cases} \begin{aligned} \sigma_{\mathcal B} & = \sqrt{\epsilon + \frac{1}{N} \sum_{i=1}^N(x^{(i)} - \mu_{\mathcal B})^2} \\ \hat x^{(i)} & = \frac{x^{(i)} - \mu_{\mathcal B}}{\sigma_{\mathcal B}} \\ y^{(i)} & = \gamma \hat{x}^{(i)} + \beta \end{aligned} \end{cases} σBx^(i)y(i)=ϵ+N1i=1N(x(i)μB)2 =σBx(i)μB=γx^(i)+β

首先解决的第一个问题:关于特征空间中的所有随机点,它的梯度比例均保持平衡状态。观察下图:
深度学习笔记之Transformer(四)铺垫:LayerNormalization,深度学习,深度学习,笔记,transformer
此时,无论是 r 1 r_1 r1还是 r 2 r_2 r2,均能够以相同代价取得最优解。基于此,我们可以降低学习率,得到更精确的最优解。

第二个问题:权重空间偏移与梯度消失问题

  • 首先,在初始反向传播的过程中,由于分布被压缩并约束在特定位置 ( 0 ) (0) (0),导致权重信息不会发生较大偏移;
  • 在压缩与约束的过程中,各层的输出分布重新地回归到原点附近,而原点附近正是激活函数的恒等映射区间(线性近似区),这使仅一些非线性程度过大的样本点才能被激活函数映射成非线性结果,从而使绝大多数样本点在反向传播过程中有梯度进行传播,缓解了梯度消失问题。

层标准化

层标准化 ( LayerNorm ) (\text{LayerNorm}) (LayerNorm)是一种自然语言处理中常用的一种正则化方式。它的操作与批标准化基本相同:
整合一下上面的公式~

  • 其中原先 Batch \text{Batch} Batch内描述某维度的期望 μ B \mu_{\mathcal B} μB替换为关于该 Batch \text{Batch} Batch内所有样本的期望 E ( X B ) \mathbb E(\mathcal X_{\mathcal B}) E(XB)
  • 同理,对应某维度方差 σ B \sigma_{\mathcal B} σB替换为关于该 Batch \text{Batch} Batch内所有样本的方差 Var ( X B ) \text{Var}(\mathcal X_{\mathcal B}) Var(XB)
    y ( i ) = x ( i ) − E ( X B ) Var ( X B ) + ϵ ⋅ γ + β y^{(i)} = \frac{x^{(i)} - \mathbb E(\mathcal X_{\mathcal B})}{\sqrt{\text{Var}(\mathcal X_{\mathcal B}) + \epsilon}} \cdot \gamma + \beta y(i)=Var(XB)+ϵ x(i)E(XB)γ+β

既然和批标准化共享相同的公式,为什么要单独将层标准化列出来呢 ? ? ?

批标准化无法处理的问题

层标准化作为 Transformer \text{Transformer} Transformer的核心构件,它与批标准化的和核心区别在于:

  • 批标准化是对基于同一 Batch \text{Batch} Batch下所有样本的各维度分别执行标准化操作。例如:对某一 Batch \text{Batch} Batch下所有样本的 1 1 1维度做标准化操作。对应描述图表示如下:
    其中 BatchSize \text{BatchSize} BatchSize表示 Batch \text{Batch} Batch内所有样本的数量; MaxLength \text{MaxLength} MaxLength表示各样本内元素的数量; DimensionNum \text{DimensionNum} DimensionNum表示各元素的维度特征数量。
    深度学习笔记之Transformer(四)铺垫:LayerNormalization,深度学习,深度学习,笔记,transformer
    很明显:所有样本(句子)中的所有元素(词),它们的第 1 1 1维度特征均来自于同一特征分布;也就是说,每个元素的第 1 1 1个特征均可看作是 1 1 1维(词)向量分布产生的独立样本

    从上面的描述可以看出,可以按照各维度独立做标准化的底层逻辑在于:各维度(词)向量分布之间线性无关甚至是两两相互正交

  • 层标准化是对基于 Batch \text{Batch} Batch中各样本所有维度共同执行标准化操作。例如: Batch \text{Batch} Batch下第一个样本的所有维度做标准化操作。对应描述图表示如下:
    深度学习笔记之Transformer(四)铺垫:LayerNormalization,深度学习,深度学习,笔记,transformer
    根据批标准化的描述,被标准化的特征信息并不属于同一维度,从而也不属于同一特征分布,但为什么可以执行这种标准化操作呢 ? ? ?

    个人理解:从样本角度观察:这里执行的是对一个完整样本特征的标准化,可以将其理解为样本空间中,一个样本经过标准化的平移和缩放,达到了标准化后的位置;而所有样本均独立执行该标准化操作,那么所有样本达到标准化后的新位置所组成的分布相当于将整个样本分布执行了标准化操作。
    虽然样本真实分布中样本数量是无穷大的,是数据集远远比不上的,但是我们仅能使用有限的数据集来对样本分布进行描述。

    从上面的描述可以看出,可以按照各样本独立做标准化的底层逻辑在于:在同一样本分布下,各样本之间独立同分布

综上,无论是批标准化还是层标准化,都是对输出分布(样本分布、特征分布)的正则化操作。回归到最初始的问题:在 Transformer \text{Transformer} Transformer模型中,为什么要使用层标准化,而不是批标准化 ? ? ?

由于 Transformer \text{Transformer} Transformer处理的是序列数据,这里以文本数据为例。在不执行填充 ( Padding ) (\text{Padding}) (Padding)的情况下,各文本的序列长度之间可能存在差异性。如果使用 Batch \text{Batch} Batch进行表达,可能会出现如下形式:
深度学习笔记之Transformer(四)铺垫:LayerNormalization,深度学习,深度学习,笔记,transformer
上图描述的是 Batch \text{Batch} Batch内基于不同长度序列内的各元素第 1 1 1维度特征信息。如果使用批标准化来执行正则化操作,它仅会对公共部分的长度执行标准化,剩余的部分将会缺失:
深度学习笔记之Transformer(四)铺垫:LayerNormalization,深度学习,深度学习,笔记,transformer
如果使用层标准化,依然是上述 Batch \text{Batch} Batch的情况,可能出现如下几种标准化形状
深度学习笔记之Transformer(四)铺垫:LayerNormalization,深度学习,深度学习,笔记,transformer
很明显,在 Batch \text{Batch} Batch内序列长度不相等的情况下,使用层标准化不会出现信息丢失的情况。

相关参考:
详解 transformer—LayerNorm
nn.LayerNorm的实现及原理文章来源地址https://www.toymoban.com/news/detail-541355.html

到了这里,关于深度学习笔记之Transformer(四)铺垫:LayerNormalization的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深度学习——Transformer的理解整理

    transformer刚被提出的时候就是被用于处理机器翻译的。在transformer架构中的不同位置Q,K,V指代的变量是不一样的。 假设现在处理的是英文-德文的翻译任务。 在encoder的输入端,这里执行的是 self-attention , Q、K、V 都是指代英 文的embedding 。 在decoder的输入端,这里执行的是

    2024年04月28日
    浏览(30)
  • 【深度学习 | Transformer】Transformers 教程:pipeline一键预测

    Transformers 是用于自然语言处理 (NLP)、计算机视觉以及音频和语音处理任务的预训练最先进模型库。该库不仅包含 Transformer 模型,还包含非 Transformer 模型,例如用于计算机视觉任务的现代卷积网络。 pipeline() 可以加载多个模型让进行推理变得简单,即使没有使用特定模态的经

    2024年02月05日
    浏览(31)
  • 深度学习从入门到精通—Transformer

    1.1 传统的RNN网络 传统的RNN(递归神经网络)主要存在以下几个问题: 梯度消失和梯度爆炸 :这是RNN最主要的问题。由于序列的长距离依赖,当错误通过层传播时,梯度可以变得非常小(消失)或非常大(爆炸),这使得网络难以学习。 计算效率低 :RNN由于其递归性质,必

    2024年04月26日
    浏览(36)
  • 深度学习实战24-人工智能(Pytorch)搭建transformer模型,真正跑通transformer模型,深刻了解transformer的架构

    大家好,我是微学AI,今天给大家讲述一下人工智能(Pytorch)搭建transformer模型,手动搭建transformer模型,我们知道transformer模型是相对复杂的模型,它是一种利用自注意力机制进行序列建模的深度学习模型。相较于 RNN 和 CNN,transformer 模型更高效、更容易并行化,广泛应用于神

    2023年04月22日
    浏览(39)
  • 《动手学深度学习 Pytorch版》 10.7 Transformer

    自注意力同时具有并行计算和最短的最大路径长度这两个优势。Transformer 模型完全基于注意力机制,没有任何卷积层或循环神经网络层。尽管 Transformer 最初是应用于在文本数据上的序列到序列学习,但现在已经推广到各种现代的深度学习中,例如语言、视觉、语音和强化学习

    2024年02月08日
    浏览(31)
  • 深入理解深度学习——Transformer:解码器(Decoder)部分

    分类目录:《深入理解深度学习》总目录 相关文章: ·注意力机制(Attention Mechanism):基础知识 ·注意力机制(Attention Mechanism):注意力汇聚与Nadaraya-Watson核回归 ·注意力机制(Attention Mechanism):注意力评分函数(Attention Scoring Function) ·注意力机制(Attention Mechanism):

    2024年02月10日
    浏览(36)
  • 深入理解深度学习——Transformer:编码器(Encoder)部分

    分类目录:《深入理解深度学习》总目录 相关文章: ·注意力机制(AttentionMechanism):基础知识 ·注意力机制(AttentionMechanism):注意力汇聚与Nadaraya-Watson核回归 ·注意力机制(AttentionMechanism):注意力评分函数(AttentionScoringFunction) ·注意力机制(AttentionMechanism):Bahda

    2024年02月08日
    浏览(53)
  • 机器学习&&深度学习——BERT(来自transformer的双向编码器表示)

    👨‍🎓作者简介:一位即将上大四,正专攻机器学习的保研er 🌌上期文章:机器学习深度学习——transformer(机器翻译的再实现) 📚订阅专栏:机器学习深度学习 希望文章对你们有所帮助 我们首先理解一下相关的一些概念,首先我们知道在自然语言系统中,词是意义的基

    2024年02月12日
    浏览(37)
  • 【论文阅读】基于深度学习的时序异常检测——Anomaly Transformer

    系列文章链接 数据解读参考:数据基础:多维时序数据集简介 论文一:2022 Anomaly Transformer:异常分数预测 论文二:2022 TransAD:异常分数预测 论文三:2023 TimesNet:基于卷积的多任务模型 论文链接:Anomaly Transformer.pdf 代码链接:https://github.com/thuml/Anomaly-Transformer 视频讲解(原

    2024年02月14日
    浏览(26)
  • 深度学习与计算机视觉:从CNN到Transformer

    计算机视觉是人工智能领域的一个重要分支,它涉及到计算机自动识别和理解人类视觉中的图像和视频。深度学习是计算机视觉的核心技术之一,它借鉴了人类的思维和学习过程,通过神经网络模拟人类大脑中的神经元活动,实现了对图像和视频的自动处理和理解。 深度学习

    2024年02月02日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包