深度学习笔记之BERT(五)TinyBERT

深度学习笔记之TinyBERT

  • 引言
    • 回顾:DistilBERT模型
    • TinyBERT模型结构
    • TinyBERT模型策略
      • Transformer层蒸馏
      • 嵌入层蒸馏
      • 预测层蒸馏
    • TinyBERT模型的训练
    • 效果展示

引言

上一节介绍了 DistilBERT \text{DistilBERT} DistilBERT模型,本节将继续介绍优化性更强的知识蒸馏 BERT \text{BERT} BERT模型—— TinyBERT \text{TinyBERT} TinyBERT模型。

回顾:DistilBERT模型

DistilBERT \text{DistilBERT} DistilBERT模型是一种基于 BERT-base \text{BERT-base} BERT-base知识蒸馏版本,其模型结构表示如下。单从模型结构的角度观察,学生模型神经元的维度没有发生变化 ( 768 ) (768) (768),仅是 Encoder \text{Encoder} Encoder层数减少为 BERT-base \text{BERT-base} BERT-base的一半;并且各层的初始化继承了一部分 BERT-base \text{BERT-base} BERT-base从教师模型的 Encoder \text{Encoder} Encoder层中每两层选择一层作为学生模型 Encoder \text{Encoder} Encoder层的初始化
DistilBERT模型结构
DistilBERT \text{DistilBERT} DistilBERT训练出的学生模型( param:66M \text{param:66M} param:66M)依然可以达到 BERT-base \text{BERT-base} BERT-base模型几乎 97 97 97%的准确度。能够达到这个效果离不开 DistilBERT \text{DistilBERT} DistilBERT的三个核心策略

  • 掩码语言模型策略 ( Masked Language Model ) (\text{Masked Language Model}) (Masked Language Model):根据 RoBERTa \text{RoBERTa} RoBERTa中的描述,摒弃掉下句预测 ( Next Sentence Prediction,NSP ) (\text{Next Sentence Prediction,NSP}) (Next Sentence Prediction,NSP)策略,并使用动态掩码替代静态掩码作为 BERT \text{BERT} BERT模型的训练策略;
  • 蒸馏策略 ( Distillation loss ) (\text{Distillation loss}) (Distillation loss):通过使用 Softmax \text{Softmax} Softmax温度函数教师模型 BERT-base \text{BERT-base} BERT-base学生模型 DistilBERT \text{DistilBERT} DistilBERT输出层的解空间尽可能地相似:
    其中 N N N表示教师模型和学生模型的输出层维度,在 DistilBERT \text{DistilBERT} DistilBERT模型中,两者的维度相同,均为 768 768 768
    T ( x ) = ( t 1 , t 2 , ⋯   , t N ) S ( x ) = ( s 1 , s 2 , ⋯   , s N ) L c r o s s = − ∑ i = 1 N t i ∗ log ⁡ ( s i ) \begin{aligned} & \mathcal T(x) = (t_1,t_2,\cdots,t_N) \\ & \mathcal S(x) = (s_1,s_2,\cdots,s_{N}) \\ & \mathcal L_{cross} = -\sum_{i=1}^{N} t_i * \log (s_i) \end{aligned} T(x)=(t1,t2,,tN)S(x)=(s1,s2,,sN)Lcross=i=1Ntilog(si)
  • 余弦嵌入策略 ( Cosine Embedding loss ) (\text{Cosine Embedding loss}) (Cosine Embedding loss):通过计算输出层分布向量之间夹角的余弦值 cos ⁡ [ T ( x ) , S ( x ) ] \cos [\mathcal T(x),\mathcal S(x)] cos[T(x),S(x)],当该值为 1 1 1时,对应的 L c o s i n e \mathcal L_{cosine} Lcosine达到最小。此时两向量的方向为同一方向,教师和学生模型输出的解空间已被对齐:
    L c o s i n e = 1 − cos ⁡ [ T ( x ) , S ( x ) ] \mathcal L_{cosine} = 1 - \cos[\mathcal T(x),\mathcal S(x)] Lcosine=1cos[T(x),S(x)]

总结:
ALBERT \text{ALBERT} ALBERT模型中介绍过,虽然 ALBERT \text{ALBERT} ALBERT也是 BERT \text{BERT} BERT的简化版本,但它们的解空间并不相同
解空间:ALBERT vs BERT
与此相反, DistilBERT \text{DistilBERT} DistilBERT中除了继承了 BERT \text{BERT} BERT中的掩码语言模型策略外,剩余的两条策略均是围绕牢牢绑定教师模型与学生模型的解空间而设计。

重新观察学生模型 DistilBERT \text{DistilBERT} DistilBERT,它能够达到如此精炼的模型结构 ( param:110M -> 66M ) (\text{param:110M -> 66M}) (param:110M -> 66M),但依然保持极高的准确性,没有出现欠拟合的情况。这至少意味着: DistilBERT \text{DistilBERT} DistilBERT模型中的神经元被利用得更加充分

在这种情况下,是否可以百尺竿头更进一步 ?  TinyBERT ?\text{ TinyBERT} ? TinyBERT模型给了我们一个更精进的答案。

TinyBERT模型结构

相比于 DistilBERT \text{DistilBERT} DistilBERT模型中 Encoder \text{Encoder} Encoder层数减半的严肃操作, TinyBERT \text{TinyBERT} TinyBERT模型可以自定义学生模型的层数。并且还可以设置隐藏层单元中神经元的维度,从而使模型更加精简。那么它是如何实现在如此精简的模型结构下,不仅没有欠拟合,而且还能保持优秀的训练结果呢 ? ? ? 自然是依靠更加严苛的策略作为约束

TinyBERT \text{TinyBERT} TinyBERT模型的教师-学生模型结构表示如下:
TinyBERT structure
其中索引 0 0 0表示嵌入层 1 1 1表示第一个 Encoder \text{Encoder} Encoder,以此类推。最后 N+1,M+1 \text{N+1,M+1} N+1,M+1分别表示教师、学生模型的预测层

该蒸馏结构与 DistilBERT \text{DistilBERT} DistilBERT之间没有太大区别,只不过没有 DistilBERT \text{DistilBERT} DistilBERT中的初始化操作。教师与学生模型中各层的迁移过程可以表示为如下式子:
n = G ( m ) n = \mathcal G(m) n=G(m)
其表达的含义是:将教师模型中的第 n n n层迁移到学生模型的第 m m m。例如:

  • 0 = G ( 0 ) 0 = \mathcal G(0) 0=G(0)表示将教师模型的嵌入层知识迁移到学生模型的嵌入层;
  • N + 1 = G ( M + 1 ) N+1 = \mathcal G(M+1) N+1=G(M+1)表示将教师模型的预测层知识迁移到学生模型的预测层;
  • n = G ( m ) n = \mathcal G(m) n=G(m)表示将教师模型的第 n n n Encoder \text{Encoder} Encoder层知识迁移到学生模型的第 m m m Encoder \text{Encoder} Encoder层。

TinyBERT模型策略

那么 TinyBERT \text{TinyBERT} TinyBERT如何制定策略的呢 ? ? ? 主要围绕三个部分制定策略:

  • Transformer \text{Transformer} Transformer ( Encoder ) (\text{Encoder}) (Encoder)
  • 嵌入层 ( Embedding Layer ) (\text{Embedding Layer}) (Embedding Layer)
  • 预测层 ( Predict Layer ) (\text{Predict Layer}) (Predict Layer)

Transformer层蒸馏

Transformer \text{Transformer} Transformer层也就是编码器层,需要使用多头注意力机制计算注意力矩阵,再使用 FeedForward Network \text{FeedForward Network} FeedForward Network进行一个前馈计算,并将最终计算得到的隐藏状态特征作为该编码器的输出。在 TinyBERT \text{TinyBERT} TinyBERT除了将教师模型中 Encoder \text{Encoder} Encoder内的注意力矩阵迁移到学生模型相应的 Encoder \text{Encoder} Encoder中,也同时将相应的隐藏状态特征迁移到学生模型中。因而 Transformer \text{Transformer} Transformer层蒸馏包括两次知识蒸馏

  • 基于注意力的蒸馏
    通过最小化对应学生 Encoder \text{Encoder} Encoder和教师 Encoder \text{Encoder} Encoder内注意力矩阵的均方误差来训练对应学生 Encoder \text{Encoder} Encoder层:
    • 其中 h h h表示注意力机制头的数量; A i S \mathcal A_i^{\mathcal S} AiS表示学生 Encoder \text{Encoder} Encoder内第 i i i个头的注意力矩阵; A i T \mathcal A_i^{\mathcal T} AiT表示教师 Encoder \text{Encoder} Encoder内第 i i i个头的注意力矩阵; MSE \text{MSE} MSE表示均方误差操作。
    • 个人疑问:当学生模型隐藏层维度变化的时候 A i S , A i T \mathcal A_i^{\mathcal S},\mathcal A_i^{\mathcal T} AiS,AiT是一样大的吗?但书中并没有解释。
      L a t t n = 1 h ∑ i = 1 h MSE ( A i S , A i T ) \mathcal L_{attn} = \frac{1}{h} \sum_{i=1}^{h} \text{MSE}(\mathcal A_i^{\mathcal S}, \mathcal A_i^{\mathcal T}) Lattn=h1i=1hMSE(AiS,AiT)
      需要注意的是,这里的注意力矩阵 A i S , A i T \mathcal A_i^{\mathcal S},\mathcal A_i^{\mathcal T} AiS,AiT使用的是执行 Layer Norm \text{Layer Norm} Layer Norm映射前的矩阵,这样做的目的是保证信息的完整性,并且更快地收敛
  • 基于隐藏状态的蒸馏
    隐藏状态是当前 Encoder \text{Encoder} Encoder的输出,我们同样需要将教师 Encoder \text{Encoder} Encoder的隐藏层知识迁移到学生 Encoder \text{Encoder} Encoder的隐藏层状态中
    其中 H S \mathcal H_{\mathcal S} HS表示学生 Encoder \text{Encoder} Encoder内的隐藏层状态; H T \mathcal H_{\mathcal T} HT表示教师 Encoder \text{Encoder} Encoder内的隐藏层状态。同样使用均方误差使 H S \mathcal H_{\mathcal S} HS H T \mathcal H_{\mathcal T} HT方向拟合。
    L h i d n = MSE ( H S , H T ) \mathcal L_{hidn} = \text{MSE}(\mathcal H_{\mathcal S},\mathcal H_{\mathcal T}) Lhidn=MSE(HS,HT)
    但需要注意的是:当学生 Encoder \text{Encoder} Encoder隐藏层维度发生变化时, H S \mathcal H_{\mathcal S} HS H T \mathcal H_{\mathcal T} HT两者之间的维度之间存在差异,因而需要训练一个新的权重矩阵 W h \mathcal W_{h} Wh使两者处于同一级别的维度空间
    相当于作用在损失函数上的权重矩阵,反向传播过程中同样存在梯度更新。
    L h i d n = MSE ( H S W h , H T ) \mathcal L_{hidn} = \text{MSE}(\mathcal H_{\mathcal S}\mathcal W_h, \mathcal H_{\mathcal T}) Lhidn=MSE(HSWh,HT)

嵌入层蒸馏

关于嵌入层的蒸馏与隐藏状态的蒸馏相似,当学生模型设置的隐藏层维度与教师模型维度不同时,两者对应的 Embedding \text{Embedding} Embedding也不同。同样在损失函数中添加一个新的权重参数 W E \mathcal W_{\mathcal E} WE,使两个 Embedding \text{Embedding} Embedding处于同一级别的维度空间
其中 E S \mathcal E_{\mathcal S} ES表示学生模型的 Embedding \text{Embedding} Embedding矩阵; E T \mathcal E_{\mathcal T} ET表示教师模型的 Embedding \text{Embedding} Embedding矩阵。 MSE \text{MSE} MSE表示均方误差。

L e m b = MSE ( E S W E , E T ) \mathcal L_{emb} = \text{MSE}(\mathcal E_{\mathcal S} \mathcal W_{\mathcal E} ,\mathcal E_{\mathcal T}) Lemb=MSE(ESWE,ET)

预测层蒸馏

在预测层蒸馏部分,迁移的是输出层的知识信息。这里和 DistilBERT \text{DistilBERT} DistilBERT模型关于预测层的损失类似。对于教师模型的输出 Z T \mathcal Z^{\mathcal T} ZT学生模型的输出 Z S \mathcal Z^{\mathcal S} ZS

  • 使用 Softmax \text{Softmax} Softmax温度函数分别获取对应的软目标 P T \mathcal P^{\mathcal T} PT软预测 P S \mathcal P^{\mathcal S} PS结果:
    同理, Z T \mathcal Z^{\mathcal T} ZT对应的软目标结果 P T \mathcal P^{\mathcal T} PT不再赘述。
    { P i S = exp ⁡ ( Z i S / T ) ∑ j exp ⁡ ( Z j S ) / T P S = ( P 1 S , P 2 S , ⋯   , P N S ) \begin{cases} \begin{aligned} \mathcal P_{i}^{\mathcal S} = \frac{\exp(\mathcal Z_i^{\mathcal S} / \mathcal T)}{\sum_{j} \exp(\mathcal Z_j^{\mathcal S}) / \mathcal T} \end{aligned} \\ \quad \\ \mathcal P^{\mathcal S} = (\mathcal P_1^{\mathcal S},\mathcal P_{2}^{\mathcal S},\cdots,\mathcal P_{N}^{\mathcal S}) \end{cases} PiS=jexp(ZjS)/Texp(ZiS/T)PS=(P1S,P2S,,PNS)
  • 再使用交叉熵损失函数 P S \mathcal P^{\mathcal S} PS P T \mathcal P^{\mathcal T} PT进行描述:
    L p r e d = − P T ⋅ log ⁡ ( P S ) \mathcal L_{pred} = - \mathcal P^{\mathcal T} \cdot \log \left(\mathcal P^{\mathcal S} \right) Lpred=PTlog(PS)

最终, TinyBERT \text{TinyBERT} TinyBERT包含所有层的损失函数表示如下:
这里 [ S m , T G ( m ) ] [\mathcal S_{m},\mathcal T_{\mathcal G(m)}] [Sm,TG(m)]表示学生模型的第 m m m层与教师模型第 G ( m ) \mathcal G(m) G(m)之间的迁移关系。
L [ S m , T G ( m ) ] = { L e m b ( S 0 , T 0 ) m = 0 L h i d n ( S m , T G ( m ) ) M ≥ m > 0 L p r e d ( S M + 1 , T N + 1 ) m = M + 1 \mathcal L \left[ \mathcal S_{m},\mathcal T_{\mathcal G(m)}\right]= \begin{cases} \mathcal L_{emb}(\mathcal S_0,\mathcal T_0) \quad m = 0 \\ \mathcal L_{hidn}(\mathcal S_m,\mathcal T_{\mathcal G(m)}) \quad M \geq m > 0 \\ \mathcal L_{pred} (\mathcal S_{M+1},\mathcal T_{N+1}) \quad m = M + 1 \end{cases} L[Sm,TG(m)]= Lemb(S0,T0)m=0Lhidn(Sm,TG(m))Mm>0Lpred(SM+1,TN+1)m=M+1
可以看出:

  • TinyBERT \text{TinyBERT} TinyBERT损失函数数量是不确定的。它取决于设计学生模型 ( TinyBERT ) (\text{TinyBERT}) (TinyBERT)的层的数量;
  • 相比于 DistilBERT \text{DistilBERT} DistilBERT TinyBERT \text{TinyBERT} TinyBERT需要为削减隐藏层状态维度层数付出相应的代价——设计的策略需要与教师模型关系更加紧密,并精确到注意力矩阵和隐藏层状态,从而得到一个与教师模型关联更加紧密的、学生模型的解空间

TinyBERT模型的训练

在文章中作者描述的训练流程表示如下:
train and fune-tuning
TinyBERT \text{TinyBERT} TinyBERT模型中,使用两个阶段进行训练:

  • 通用蒸馏:在该阶段,使用 BERT-base \text{BERT-base} BERT-base预训练模型作为教师,并使用 BERT-base \text{BERT-base} BERT-base的训练集对学生模型 ( TinyBERT ) (\text{TinyBERT}) (TinyBERT)进行蒸馏。并将该模型称作通用 TinyBERT \text{TinyBERT} TinyBERT模型
  • 特定任务蒸馏:在微调阶段,将基于一项具体任务对通用 TinyBERT \text{TinyBERT} TinyBERT模型进行微调 ( fine-tuning ) (\text{fine-tuning}) (fine-tuning)。具体微调过程方式为:
    • 使用预训练 BERT-base \text{BERT-base} BERT-base模型针对具体任务进行微调,并将这个微调后的 BERT-base \text{BERT-base} BERT-base模型作为教师
    • 将上述经过通用蒸馏得到的通用 TinyBERT \text{TinyBERT} TinyBERT模型作为学生,经过蒸馏,得到的 TinyBERT \text{TinyBERT} TinyBERT模型称作微调的 TinyBERT \text{TinyBERT} TinyBERT模型

效果展示

论文中关于 TinyBERT \text{TinyBERT} TinyBERT对于各下游任务中,与各模型比较结果如下:
tinyBERT result
其中, DistilBERT 4 \text{DistilBERT}_4 DistilBERT4表示学生模型包含 4 4 4 Encoder \text{Encoder} Encoder,其他同理。可以发现:

  • 相比于 DistilBERT 4 \text{DistilBERT}_4 DistilBERT4 TinyBERT 4 \text{TinyBERT}_4 TinyBERT4使用不到其 30 30 30%,但准确率却远高于 DistilBERT \text{DistilBERT} DistilBERT模型
  • TinyBERT 6 \text{TinyBERT}_6 TinyBERT6参数数量是 BERT-base \text{BERT-base} BERT-base 60 60 60%左右,但其准确性基本与 BERT-base \text{BERT-base} BERT-base持平。

Reference \text{Reference} Reference
论文链接
《BERT基础教程——Transformer大模型实战》

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

30.串联所有单词的子串 python

串联所有单词的子串 题目题目描述示例 1:示例 2:示例 3:提示:题目链接 题解解题思路python实现代码解释:提交结果 题目 题目描述 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的…

【LeetCode】498.对角线遍历

无论何时何地,我都认为对于一道编程题,思考解法的时间用于是实际动手解决问题的2倍!如果敲键盘编码需要5min,那么思考解法的过程至少就需要10分钟。 1. 题目 2. 思想 其实这就是一道模拟题,难度中等。做这种题的关键就…

uniapp中父组件传参到子组件页面渲染不生效问题处理实战记录

上篇文件介绍了,父组件数据更新正常但是页面渲染不生效的问题,详情可以看下:uniapp中父组件数组更新后与页面渲染数组不一致实战记录 本文在此基础上由于新增需求衍生出新的问题.本文只记录一下解决思路. 下面说下新增需求方便理解场景: 商品信息设置中添加抽奖概率设置…

Flutter提示错误:无效的源发行版17

错误描述 Flutter从3.10.1 升级到3.19.4,在3.10.1的时候一切运行正常,但是当我将Flutter版本升级到3.19.4后,出现了下方的错误 FAILURE: Build failed with an exception.* What went wrong: Execution failed for task :device_info_plus:…

etcd的dbsize引起的集群故障

故障现象 k8s集群不能访问,具体表现kubectl命令不能使用。 思路 检查apiserver服务状态,检查etcd集群状态中errors列中存在一个alarm:NOSPACE的告警 解决&分析 具体表现 恢复使用第一,先尝试解除告警看能否恢复 etcdctl --endpoin…

Redis性能优化18招

Redis性能优化的18招 目录 前言选择合适的数据结构避免使用过大的key和value[使用Redis Pipeline](#使用Redis Pipeline)控制连接数量合理使用过期策略使用Redis集群充分利用内存优化使用Lua脚本监控与调优避免热点key使用压缩使用Geo位置功能控制数据的持久化尽量减少事务使…

Docker 安装 Yapi

Docker 安装系列 Docker已安装。 1、场景Yapi使用的MongoDB用户信息 1.1 创建自定义 Docker 网络 首先,创建一个自定义的 Docker 网络,以便 MongoDB 和 YApi 容器可以相互通信 [rootflexusx-328569 data]# docker network create yapi-networ…

深度学习(2)前向传播与反向传播

这一次我们重点讲解前向传播与反向传播,对这里还是有点糊涂 前向传播(Forward Propagation)和反向传播(Backward Propagation)是深度学习中神经网络训练的核心过程。它们分别负责计算神经网络的输出以及更新神经网络的…

Mock神器:Easy-Mock 私有化部署及使用介绍

在现代前后端分离的开发模式中,后端接口的数据模拟是一个常见且必要的需求。尤其是在后端接口尚未开发完成时,前端开发需要依赖模拟数据进行开发与测试。Easy-Mock 是一个非常流行的开源工具(虽然它已经停止更新好长时间了)&#…

个人IP建设:简易指南

许多个体创业者面临的一个关键挑战是如何为其企业创造稳定的需求。 作为个体创业者,您无法使用营销团队,因此许多人通过推荐和他们的网络来产生需求。因此,扩大您的网络是发展您的业务和产生持续需求的最佳策略。 这就是个人IP和品牌发挥作…

二一(GIT4)、echarts(地图)、黑马就业数据平台(学生页-增 删 改)

1. echarts 地图 echarts社区:makeapie echarts社区图表可视化案例 社区模板代码地址:自定义 tooltip-轮播 - category-work,series-map地图,tooltip提示框,visualMap视觉映射 - makeapie echarts社区图表可视化案例 // todo: 籍贯分布 地图 function…

iPhone 17 Air基本确认,3个大动作

近段时间,果粉圈都在讨论一个尚未发布的新品:iPhone 17 Air,苹果又要来整新活了。 从供应链消息来看,iPhone 17 Air本质上是Plus的替代品,主要是在维持“大屏”这一卖点的同时,增加了“轻薄”属性&#xff…

数据结构之初始二叉树(1)

找往期文章包括但不限于本期文章中不懂的知识点: 个人主页:我要学编程(ಥ_ಥ)-CSDN博客 所属专栏:数据结构(Java版) 目录 树型结构 树的概念 与树的有关概念 树的表示形式 树的应用 二叉树 概念 两种特殊的…

RabbitMQ七种工作模式之 RPC通信模式, 发布确认模式

文章目录 六. RPC(RPC通信模式)客户端服务端 七. Publisher Confirms(发布确认模式)1. Publishing Messages Individually(单独确认)2. Publishing Messages in Batches(批量确认)3. Handling Publisher Confirms Asynchronously(异步确认) 六. RPC(RPC通信模式) 客⼾端发送消息…

深入理解 SQL 注入:原理、攻击流程与防御措施

深入理解 SQL 注入:原理、攻击流程与防御措施 在当今数字化的时代,数据安全已成为每个企业和开发者必须面对的重要课题。SQL 注入(SQL Injection)作为一种常见的网络攻击方式,给无数企业带来了巨大的损失。本文将深入…

【项目实战】基于python+爬虫的电影数据分析及可视化系统

注意:该项目只展示部分功能,如需了解,文末咨询即可。 本文目录 1.开发环境2 系统设计 2.1 设计背景2.2 设计内容 3 系统页面展示 3.1 用户页面3.2 后台页面3.3 功能展示视频 4 更多推荐5 部分功能代码 5.1 爬虫代码5.2 电影信息代码 1.开发环…

JCR一区牛顿-拉夫逊优化算法+分解对比!VMD-NRBO-Transformer-BiLSTM多变量时序光伏功率预测

JCR一区牛顿-拉夫逊优化算法分解对比!VMD-NRBO-Transformer-BiLSTM多变量时序光伏功率预测 目录 JCR一区牛顿-拉夫逊优化算法分解对比!VMD-NRBO-Transformer-BiLSTM多变量时序光伏功率预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.中科院…

深入探索 Compose 渲染流程:从 UI 树到 Skia 绘制的实现解析

文章目录 前言Compose 渲染流程概述1. Compose 解析1.1 Compose 声明性 UI1.2 Compose 编译1.2.1 Compose 编译概述1.2.2 代码示例1.2.3 编译过程细节 1.3 组合与重组合1.3.1 组合(Composition)1.3.2 重组合1.3.3 组合与重组合的区别1.3.4 组合与重组合的…

数据结构排序算法详解

数据结构排序算法详解 1、冒泡排序(Bubble Sort)2、选择排序(Selection Sort)2、插入排序(Insertion Sort)4、快速排序(Quick Sort) 1、冒泡排序(Bubble Sort&#xff09…

命令模式的理解和实践

在软件开发中,设计模式是开发者们经过长期实践总结出来的、可复用的解决方案,用于解决常见的设计问题。命令模式(Command Pattern)是行为型设计模式之一,它通过将一个请求封装成一个对象,从而允许用户用不同…