深度神经网络中的混合精度训练

news2024/11/27 4:34:41

Mixed-Precision Training of Deep Neural Networks | NVIDIA Technical Blog

目录

混合精度成功训练的技术

FP32 累加

损失缩放 loss scaling

FP32 Master Copy of Weights

混合精度训练迭代过程

AMP混合精度训练介绍

FP16和FP32的区别

FP16的优势

FP16的问题

解决P16的精度问题策略

1、混合精度计算

2、损失缩放(Loss scaling)

3、权重备份

Apex

代码格式

不同的训练模式说明

O0 纯FP32

O1 混合精度

O2混合精度

O3纯FP16

torch.cuda.amp

参考连接


深度神经网络 (DNN) 在许多领域取得了突破,包括图像处理和理解、语言建模、语言翻译、语音处理、游戏等。为了实现这些结果,DNN 的复杂性一直在增加,这反过来又增加了训练这些网络所需的计算资源。混合精度训练通过使用较低精度的计算(FP16)来降低所需的资源,这具有以下优点。

  • 减少所需的内存量。半精度浮点格式 (FP16) 使用 16 位,而单精度 (FP32) 使用 32 位。降低所需的内存可以训练更大的模型或使用更大的小批量进行训练。
  • 缩短训练或推理时间。执行时间可能对内存或算术带宽敏感。半精度将访问的字节数减半,从而减少了在内存受限层中花费的时间。与单精度相比,NVIDIA GPU 的半精度算术吞吐量提高了 8 倍,从而加快了数学受限层的速度。

图 1.bigLSTM 英语语言模型的训练曲线显示了本文中描述的混合精度训练技术的好处。Y 轴是训练损失。不带损耗缩放的混合精度(灰色)在一段时间后会发散,而带损耗缩放的混合精度(绿色)与单精度模型(黑色)匹配。

图 1.大型LSTM 英语语言模型的训练曲线显示了本文中描述的混合精度训练技术的好处。Y 轴是训练损失。不带损耗缩放的混合精度(灰色)在一段时间后会发散,而带损耗缩放的混合精度(绿色)与单精度模型(黑色)匹配。

由于 DNN 训练传统上依赖于 IEEE 单精度格式,因此本文的重点是半精度训练,同时保持单精度实现的网络精度(如图 1 所示)。这种技术称为混合精度训练,因为它同时使用单精度和半精度表示。

混合精度成功训练的技术

半精度浮点格式由 1 个符号位、5 个指数位和 10 个小数位组成。支持的指数值属于 [-24, 15] 范围,这意味着该格式支持 [2-24,65,504]范围。由于这比 [2-149, ~3.4×1038] 范围支持单精度格式,训练某些网络需要额外考虑。本节介绍了成功训练半精度 DNN 的三种技术:将 FP16 产品累积到 FP32 中;损失缩放;以及砝码的 FP32 主副本。借助这些技术, NVIDIA 和百度研究院能够匹配所有经过训练的网络的单精度结果准确性(混合精度训练)。请注意,并非所有网络都需要使用所有这些技术进行训练。

有关如何在各种框架中应用这些技术的详细说明,包括可用的代码示例,请参阅混合精度训练用户指南。

FP32 累加

NVIDIA Volta GPU 架构引入了 Tensor Core 指令,该指令将半精度矩阵相乘,将结果累积为单精度或半精度输出。我们发现,累积到单个精度对于获得良好的训练结果至关重要。累积值在写入内存之前转换为半精度。cuDNN 和 CUBLAS 库提供了多种依赖于 Tensor Core 进行算术运算的函数。

损失缩放 loss scaling

训练 DNN 时会遇到四种类型的张量:激活、激活梯度、权重和权重梯度。根据我们的经验,激活、权重和权重梯度落在半精度表示的值大小范围内。然而,对于某些网络,小幅度激活梯度低于半精度范围。例如,考虑图 2 中训练 Multibox SSD 检测网络时遇到的激活梯度直方图,该直方图显示了 log2 刻度上值的百分比。小于 2-24 的值在半精度格式中变为零。

请注意,激活梯度不使用大多数半精度范围,激活梯度往往是幅度小于 1 的小值。因此,我们可以通过将激活梯度乘以比例因子 S 来将它们“移位”到 FP16 表示的范围内。在SSD网络的情况下,将梯度乘以8就足够了。这表明激活梯度值小于 2-27与该网络的训练无关,而保留 [2-27, 2-24) 范围。

图2.以单精度训练 Multibox SSD 检测器网络时记录的激活梯度直方图。Y 轴是对数刻度上所有值的百分比。X 轴是绝对值的对数刻度,也是零的特殊条目。例如,在此训练课程中,66.8% 的值为零,而 4% 的值介于 2-32 和 2-30 之间。

图2.以单精度训练 Multibox SSD 检测器网络时记录的激活梯度直方图。Y 轴是对数刻度上所有值的百分比。X 轴是绝对值的对数刻度,也是零的特殊条目。例如,在此培训课程中,66.8% 的值为零,而 4% 的值介于 2 之间-32和 2-30.

确保梯度落入半精度表示的范围内的一种非常有效的方法是将训练损失乘以比例因子。这仅增加了一次乘法,并且通过链式规则,它确保所有梯度都按比例放大(或向上移动),而无需额外费用。损失缩放可确保恢复丢失到零的相关梯度值。在权重更新之前,权重梯度需要按相同的因子 S 缩小。缩减操作可以与权重更新本身融合(导致没有额外的内存访问)或单独执行。有关详细信息,请参阅《混合精度训练用户指南》和《混合精度训练》白皮书。

FP32 Master Copy of Weights

DNN 训练的每次迭代都会通过添加相应的权重梯度来更新网络权重。权重梯度幅度通常明显小于相应的权重,尤其是在与学习率相乘(或Adam或Adagrad等优化器的自适应计算因子)相乘之后。如果其中一个加法太小而无法产生半精度表示差异,则此幅度差异可能导致不会发生更新(例如,由于指数差大,较小的加法在移位以对齐二进制点后变为零)。

对于以这种方式丢失更新的网络,一个简单的补救措施是以单精度维护和更新权重的主副本。在每次迭代中,都会制作一个主权重的半精度副本,并将其用于正向和反向传播,从而获得性能优势。在权重更新期间,计算出的权重梯度将转换为单精度,并用于更新主副本,并在下一次迭代中重复该过程。因此,我们只在需要的地方将半精度存储与单精度存储混合使用。

混合精度训练迭代过程

上面介绍的三种技术可以组合到每个训练迭代的以下步骤序列中。对传统迭代过程的补充以粗体显示。

  1. 制作权重的 FP16 副本
  2. 使用 FP16 权重和激活进行前向传播
  3. 将产生的损失乘以比例因子 S
  4. 使用 FP16 权重、激活及其梯度向后传播
  5. 将权重梯度乘以 1/S
  6. (可选)处理权重梯度(梯度裁剪、权重衰减等)
  7. 更新 FP32 中权重的主副本

AMP混合精度训练介绍

AMP(Automatic mixed precision):自动混合精度,该方法在训练网络时将单精度(FP32)与半精度(FP16)结合在一起,它使用FP16即半精度浮点数存储和计算,从而实现节省显存和加快训练速度的目的。

常用的两种实现amp的方式:

  • NVIDIA Apex使用apex.amp (O1模式开启自研kernel会出现Nan值,禁用自研kernel之后没有Nan值;O2没有Nan值)
  • Pytorch 1.6版本后自带torch.cuda.amp (开启自研kernel 也会出现Nan值;禁用自研kernel之后没有Nan值)

FP16和FP32的区别

FP16和FP32在计算机的不同存储方法:

半精度浮点数 (FP16): 计算机使用 2 字节 (16 位) 存储,表示范围为 [5.9e-8,65504]

单精度浮点数 (FP32) :计算机使用 4 字节 (32 位) 存储,表示范围为 [1.4e-45,3.4e38],FP32 能够表示的范围要比 FP16 大的多得多。

FP16的优势

默认情况下,大多数深度学习框架都采用FP32进行训练。相比与FP32,FP16具有一下优势:

  •  1.减少显存占用,这使得我们可以用更大的 batch size
  •  2.加快训练和推断的计算
  •  3.NVIDIA Tensor Core支持

FP16的问题

如果我们简单地把模型权重和输入从 FP32 转化成 FP16,虽然可以加快速度,但是模型的精度会被严重影响,原因如下:

  • 1. 溢出错误:由于FP16的动态范围比FP32位的狭窄很多,因此,在计算过程中很容易出现上溢出和下溢出,溢出之后就会出现"NaN"的问题。

    Multibox SSD网络训练过程中激活梯度值的直方图。2%的值在[2−34,2−32)范围内,2%的数值在[2–24,2−23)范围内,以及67%的数值为零。

  • 2. 舍入误差:当梯度过小时,小于当前区间内的最小间隔时,该次梯度更新可能会失败

解决P16的精度问题策略

论文里提到下面三个策略:Micikevicius, Paulius, Sharan Narang, et al. “Mixed Precision Training.” ArXiv:1710.03740 [Cs, Stat], February 15, 2018. https://arxiv.org/abs/1710.03740

1、混合精度计算

 在内存中用FP16做储存和乘法从而加速计算,而用FP32做累加避免舍入误差。混合精度训练的策略有效地缓解了舍入误差的问题。

2、损失缩放(Loss scaling)

为了解决下溢出的问题,对计算出来的 loss 值进行缩放 (scale),由于链式法则的存在,对 loss 的缩放会作用在每个梯度上,这些梯度会平移到 FP16 的有效范围内。这样就可以用 FP16 存储梯度而又不会溢出了。此外,在进行更新之前,需要先将缩放后的梯度转化为 FP32,再将梯度反缩放(unscale)回去。 

反向传播前:将loss手动增大缩放因子 (loss_scale)倍

反向传播后:将权重梯度缩小缩放因子 (loss_scale)倍,恢复正常值

3、权重备份

将模型权重、激活值、梯度等数据用 FP16 来存储,同时维护一份 FP32 的模型权重副本(master-weight)用于更新。前向使用FP16,在反向传播得到 FP16 的梯度以后,将其转化成 FP32 并 unscale,最后更新 FP32 的模型权重。

尽管与单精度训练相比,保持额外的权重副本会使权重的内存需求增加50%,但对整体内存使用的影响要小得多。对于训练来说,由于更大的batch size和每层的输出值被保存以在反向传播过程中重复使用,因此内存消耗主要由这些输出值决定。由于输出值也以半精度格式存储,因此训练深度神经网络的总内存消耗大致减半。

Apex

代码格式

APEX中,用户不需要手动将模型或数据类型转换为.half(),只需要从现有的默认 (FP32) 脚本开始,添加与 Amp API 对应的三行,然后就可以使用混合精度进行训练。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

from apex.fp16_utils import *

from apex import amp,optimizers

model = ...

optimizer  = ...

model, optimizer = amp.initialize(model, optimizer,

                      opt_level= ,

                 keep_batchnorm_fp32= ,

                 loss_scale=,

                 )

with amp.scale_loss(loss, optimizer) as scaled_loss:

    scaled_loss.backward()

optimizer.step()

optimizer.zero_grad()

不同的训练模式说明

NVIDIA的APEX混合精度库为例,里面提供了多种策略,可以根据不同的场景进行使用:

  • opt_level:O0(纯FP32),O1和O2是混合精度的不同实现,O3(纯FP16),还有O4,O5使用BFLOAT16

  • cast_model_type:将模型的参数转换为所需的类型。

  • patch_torch_functions:patch所有 Torch 函数和 Tensor 方法以执行 Tensor Core 友好的操作,例如 FP16 中的 GEMM 和卷积,以及任何受益于 FP32 中的 FP32 精度的操作。

  • keep_batchnorm_fp32:将 batchnorm 权重保持在 FP32 ,模型的其余部分是 FP16。

  • master_weights:保持 FP32 权重。

  • loss_scale:float值 or "dynamic"(自适应调整损失比例)。动态损失放大(dynamic loss scaling),为了充分利用FP16的范围,缓解舍入误差,将loss*loss_scale。如果产生上溢出,则跳出参数更新,缩小放大倍数使其不溢出。在2000步后再尝试使用大的scale来充分利用FP16的范围。

蓝色为默认值

opt_level

O0

O1

O2

O3

cast_model_typetorch.float32Nonetorch.float16torch.float16
patch_torch_functionsFalseTrueFalseFalse

patch_torch_functions_type

None

torch.float16

NoneNone
keep_batchnorm_fp32NoneNone(自动设为TRUE)TrueFalse
master_weightsFalseNoneTrueFalse
loss_scale

1.0

"dynamic"

"dynamic"

1.0

O0 纯FP32

纯FP32训练,可作为accuracy的baseline

O1 混合精度

  • 权重为FP32
  • 前向:对Pytorch func和Tensor method进行自动转换,根据黑白名单自动决定使用FP16(GEMM,卷积),还是FP32(softmax)进行计算。
  • 白名单函数强制 FP16(将conv的input,weight,bias转换为FP16类型),黑名单函数使用FP32。
  • 其余函数则根据参数类型自动判断,如果参数都是 FP16,则以 FP16 运行,如果有一个参数为 FP32,则以 FP32 运行。 对于那些在 FP16 环境中运行不稳定的模块,我们会将其添加到黑名单中,强制它在 FP32 的精度下运行。
  • 动态损失缩放(dynamic loss scaling)

O1步骤:

  1. 根据黑白名单对 PyTorch 内置的函数和一些tensor进行包装 
  2.  将 loss_scale 初始化为一个很大的值 
  3.  对于每次迭代 
  • (a). 前向传播:按照黑白名单自动选择数据类型进行计算。 白名单:拷贝FP32模型并且转换成 FP16 进行计算
  • (b). 将 loss 乘以 loss_scale
  • (c). 反向传播: 计算出梯度FP16
  • (d). 将梯度 unscale ,即除以 loss_scale 
  • (e). 每次更新前检查溢出问题(检查梯度中有没有infnan),如果检测到 inf 或 nan

                      i. loss_scale /= 2

                      ii. 跳过此次更新

  • (f). optimizer.step(),利用 FP16 的梯度更新 FP32 的模型参数

  • (g). 如果连续2000次迭代都没有出现 inf 或 nan,则 loss_scale *= 2

以 nn.Linear 为例, 这个模块有两个权重参数 weight 和 bias,输入为 input,前向传播就是调用了 torch.nn.functional.linear(input, weight, bias),对于白名单来说,就是把权重参数 weight 和 bias和input转换为 FP16再进行计算。

黑白名单

lists里有三个文件:functional_overrides.py,tensor_overrides.py,torch_overrides.py分别定义了黑白名单(FP16/FP32的适用情况)

O2混合精度

  • 除了 BN 层以外的模型权重为 FP16
  • 创建一个FP32的权重副本

O2步骤:

  1. 除了 BN 层以外的模型权重和梯度转化为 FP16输入类型也转化为 FP16,模型输出类型不设置为FP32
  2. 维护一个 FP32 的模型权重副本用于更新
  3. 将 loss_scale 初始化为一个很大的值;
  4. 对于每次迭代
  • (a). 前向传播: 除了 BN 层权重是 FP32,模型其它部分都是 FP16,得到FP32的loss
  • (b). 将 loss 乘以 loss_scale转换为FP16
  • (c). loss反向传播,计算得到 FP16 的梯度 
  • (d). 将 FP16 梯度转化为 FP32,并unscale
  • (e). 如果检测到梯度 inf 或 nan

                  i. loss_scale /= 2

                  ii. 跳过此次更新

  • (f). optimizer.step(),利用 FP16 的梯度更新 FP32 的模型参数
  • sgd中params[i].add_(grads[i], alpha=-lr)FP32+FP16=FP32

  • (g). 如果连续2000次迭代都没有出现 inf 或 nan,则 loss_scale *= 2

O3纯FP16

纯FP16来当速度的baseline

torch.cuda.amp

PyTorch 从 1.6 以后开始支持amp,即torch.cuda.amp module,类似于apex的O1模式:
torch.cuda.amp 给用户提供了较为方便的混合精度训练机制,“方便”体现在两个方面:

1.amp 会自动为算子选择合适的数值精度(FP32、FP16)

该名单在 torch\testing\_internal\autocast_test_lists.py里定义

  • CUDA Ops that can autocast to float16
    __matmul__, addbmm, addmm, addmv, addr, baddbmm, bmm, chain_matmul, multi_dot, conv1d, conv2d, conv3d, conv_transpose1d, conv_transpose2d, conv_transpose3d, GRUCell, linear, LSTMCell, matmul, mm, mv, prelu, RNNCell
  • CUDA Ops that can autocast to float32
    __pow__, __rdiv__, __rpow__, __rtruediv__, acos, asin, binary_cross_entropy_with_logits, cosh, cosine_embedding_loss, cdist, cosine_similarity, cross_entropy, cumprod, cumsum, dist, erfinv, exp, expm1, group_norm, hinge_embedding_loss, kl_div, l1_loss, layer_norm, log, log_softmax, log10, log1p, log2, margin_ranking_loss, mse_loss, multilabel_margin_loss, multi_margin_loss, nll_loss, norm, normalize, pdist, poisson_nll_loss, pow, prod, reciprocal, rsqrt, sinh, smooth_l1_loss, soft_margin_loss, softmax, softmin, softplus, sum, renorm, tan, triplet_margin_loss
  • CUDA Ops that promote to the widest input type
    这些操作不需要特定的 dtype 来保持稳定性,但需要多个输入并要求输入的 dtype 匹配。如果所有输入都是 float16,则运算在 中运行float16。如果任何输入是float32,autocast 将所有输入转换为float32并运行 op in float32。

    addcdiv addcmul_ atan2_ bilinear_ cross_ dot_ grid_sample_ index_put_ scatter_add_ tensordot

2.amp 提供了loss_scaling 操作 ,为了防止下溢,将loss乘以一个比例因子,并对缩放后的loss反向传播,然后将梯度除以相同的比例因子

代码格式:

model = Net().cuda()

optimizer = optim.SGD(model.parameters(), ...)

scaler = torch.cuda.amp.GradScaler()

for epoch in epochs:

    for input, target in data:

        optimizer.zero_grad()

        with autocast():

            output = model(input)

            loss = loss_fn(output, target)

        # 用scaler,scale loss(FP16),backward得到scaled的梯度(FP16)

        scaler.scale(loss).backward()

        # scaler 更新参数,会先自动unscale梯度,如果有nan或inf,自动跳过

        scaler.step(optimizer)

        scaler.update() # scaler factor更新

参考连接

apex官方代码:GitHub - NVIDIA/apex: A PyTorch Extension: Tools for easy mixed precision and distributed training in Pytorch

nvidia apex官方文档: Apex (A PyTorch Extension) — Apex 0.1.0 documentation

自动混合精度简介 - 墨天轮

全网最全-混合精度训练原理 - 知乎

由浅入深的混合精度训练教程

Micikevicius, Paulius, Sharan Narang, et al. “Mixed Precision Training.” ArXiv:1710.03740 [Cs, Stat], February 15, 2018. https://arxiv.org/abs/1710.03740

torch.amp文档  https://pytorch.org/docs/stable/amp.html#

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

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

相关文章

shell sshpass 主机交互 在另外一台主机上执行某个命令 批量管理主机 以及一些案例

目录 作用安装 sshpasssshpass 用法在远程主机执行某个命令 案例批量传输密匙批量拷贝文件批量修改密码 作用 就是用一台主机 控制另外一台主机免交互任务管理工具方便批量管理主机使用方法就是在ssh 前边加一个 sshpass 安装 sshpass # 安装 sshpass yum -y install sshpas…

MongoDB快速实战与基本原理

MongoDB 介绍 什么是 MongoDB MongoDB 是一个文档数据库(以 JSON 为数据模型),由 C 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。文档来自于“JSON Document”,并非我们一般理解的 PDF、WORD 文档…

【K8S 云原生】Pod资源限制、Pod容器健康检查(探针)

目录 一、docker的重启方式和K8S重启方式 1、Pod的重启方式: 2、docker的重启策略: 二、yaml文件快速生成: 三、pod的状态: 四、Pod的资源限制 1、限制的方式和种类 2、CPU的限制的格式: 五、K8S拉取镜像的策…

电源板设计方案怎么写 (评审文件)

1. 首先是大致的图形模块化说明。 1. 大致的框图 2. 统计项目需要的功率和需求 此表格数据是假的,只是为了展示 电源种类是: 板子需要供电需要的电压和对应电压最大的电流。 电源时序是: 板子…

智能穿戴时代 | 米客方德SD NAND的崭新优势

SD NAND在智能穿戴上的优势 SD NAND是一种可以直接焊接在智能穿戴设备主板上的存储芯片,其小型化设计有助于紧凑设备尺寸,同时提供可靠的嵌入式存储解决方案。 这种集成设计减少了空间占用,同时确保设备在高度活动的环境中更为稳定。SD NAND…

【C#】知识点实践序列之UrlEncode在线URL网址编码、解码

欢迎来到《小5讲堂》,大家好,我是全栈小5。 这是2024年第8篇文章,此篇文章是C#知识点实践序列文章, 博主能力有限,理解水平有限,若有不对之处望指正! 地址编码大家应该比较经常遇到和使用到&…

rk3566 armbian修复usb2.0并挂载U盘

文章目录 usb接口修复一 执行命令二 修改rk3566-panther-x2.dts⽂件三 查看是否识别 U盘格式化、挂载一 U盘格式化1.1 查看U盘1.2 查看U盘文件系统类型1.3 格式化为ext4系统 二 挂载U盘2.1 手动挂载2.2 自动挂载(可选) usb接口修复 一 执行命令 将位于…

【数据库系统概论】数据库恢复机制

系统文章目录 数据库的四个基本概念:数据、数据库、数据库管理系统和数据库系统 数据库系统的三级模式和二级映射 数据库系统外部的体系结构 数据模型 关系数据库中的关系操作 SQL是什么?它有什么特点? 数据定义之基本表的定义/创建、修改和…

泽攸科技完全自主研制的电子束光刻机取得阶段性成果

国产电子束光刻机实现自主可控,是实现我国集成电路产业链自主可控的重要一环。近日,泽攸科技联合松山湖材料实验室开展的全自主电子束光刻机整机的开发与产业化项目取得重大进展,成功研制出电子束光刻系统,实现了电子束光刻机整机…

FreeRTOS——互斥信号量知识总结及其实战

1互斥信号量的概念 1)互斥信号量:是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中! 2)优先级继承:当一个互斥信号量正在被一个低优先级的…

MySQL 8.0 开关 Redo Logging

一 前言 前几天有客户测试使用云数据库的时候提出 要禁止mydumper 关闭redo log的操作 (说白了就是导入数据时保持MySQL 实例的redo logging功能), 这才想起 在 MySQL 8.0.21 版本中,开启了一个新特性 “Redo Logging 动态开关”。 在新实例导数据的场…

【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 概述

【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 概述-CSDN博客 【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 运行环境搭建-CSDN博客 【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 运行模式-CSDN博客 1、 Hadoop 是什么 (1)Hadoop是一个由Apache基…

第二百四十六回

我们在上一章回中介绍了"修改页面导航中遇到的问题"沉浸式状态样相关的内容,本章回中将介绍如何修改Avatar的大小.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在正常使用CirCleAvatar组件时可以通过该组件的radius属性来修改它的…

ssm基于web的素材网的设计与实现+vue论文

基于web的素材网站的设计与实现 摘要 当下,正处于信息化的时代,许多行业顺应时代的变化,结合使用计算机技术向数字化、信息化建设迈进。传统的素材信息管理模式,采用人工登记的方式保存相关数据,这种以人力为主的管理…

C语言中关于strcpy函数的理解

strcpy的功能是将源指向的字符串复制到另外一个字符串中 目标指向的数组的大小应该要足够长&#xff0c;避免让源字符串中的数据溢出 关于这个函数的具体用法&#xff0c;我们可以看看下面这个程序 注意&#xff1a;strcpy函数的头文件是<string.h>&#xff0c;我们在用…

rollup 插件输出生成钩子

✨专栏介绍 Rollup专栏是一个专门介绍Rollup打包工具的系列文章。Rollup是一个现代化的JavaScript模块打包工具&#xff0c;它可以将多个模块打包成一个或多个文件&#xff0c;以提高应用程序的性能和加载速度。 在Rollup专栏中&#xff0c;您将学习到如何安装和配置Rollup&a…

面向对象的三大特征之一多态

多态 概念 多态是同一个对象&#xff0c;在不同时刻表现出来不同的形态&#xff0c;称之为多态。 例如&#xff1a;水&#xff0c;我们把水理解成为一个对象&#xff0c;而水会有不同的形态&#xff0c;比如 液态水、冰块、水蒸气 多态的前提 有继承/实现关系&#xff08;继承…

OpenCV-15位运算

OpenCV中的逻辑运算就是对应位置的元素进行与、或、非和异或。 Opencv与Python不同的是&#xff1a;OpenCV中0的非反过来是255&#xff0c;255反过来是0。 但是Python中255非为-256。 一、非运算 使用API---cv.bitwise_not(str) 示例代码如下&#xff1a; import cv2 imp…

EasyRecovery2024永久免费版电脑数据恢复软件

EasyRecovery是一款操作安全、价格便宜、用户自主操作的非破坏性的只读应用程序&#xff0c;它不会往源驱上写任何东西&#xff0c;也不会对源驱做任何改变。它支持从各种各样的存储介质恢复删除或者丢失的文件&#xff0c;其支持的媒体介质包括&#xff1a;硬盘驱动器、光驱、…

小程序测试和APP测试的区别

今天看了一下关于如何测试小程序的教学视频&#xff0c;里面讨论了一个很经典的面试题&#xff1a;小程序测试和APP测试的区别&#xff0c;包括在之前的面试过程中也确实是遇到过这个问题&#xff0c;所以这次打算把它记录下来&#xff0c;也算是知识巩固了。 首先从测试的内容…