[深度][PyTorch] DDP系列第三篇:实战与技巧

[深度][PyTorch] DDP系列第三篇:实战与技巧

转自:https://zhuanlan.zhihu.com/p/250471767

零. 概览

想要让你的PyTorch神经网络在多卡环境上跑得又快又好?那你definitely需要这一篇!

No one knows DDP better than I do!
– – magic_frog(手动狗头)

本文是DDP系列三篇(基本原理与入门,实现原理与源代码解析,实战)中的第三篇。本系列力求深入浅出,简单易懂,猴子都能看得懂(误)。

在过去的两篇文章里,我们已经对DDP的理论、代码进行了充分、详细的介绍,相信大家都已经了然在胸。但是,实践也是很重要的。正所谓理论联系实践,如果只掌握理论而不进行实践,无疑是纸上谈兵。

在这篇文章里,我们通过几个实战例子,来给大家介绍一下DDP在实际生产中的应用。希望能对大家有所帮助!

  1. 在DDP中引入SyncBN

  2. DDP下的Gradient Accumulation的进一步加速

  3. 多机多卡环境下的inference加速

  4. 保证DDP性能:确保数据的一致性

  5. 和DDP有关的小技巧

    1. 控制不同进程的执行顺序
    2. 避免DDP带来的冗余输出

请欢快地开始阅读吧!

**依赖:**pytorch(gpu)>=1.5,python>=3.6

一. 在DDP中引入SyncBN

在这里插入图片描述

什么是Batch Normalization(BN)? 这里就不多加以介绍。附上BN文章。接下来,让我们来深入了解下BN在多级多卡环境上的完整实现:SyncBN

什么是SyncBN?

SyncBN就是Batch Normalization(BN)。其跟一般所说的普通BN的不同在于工程实现方式:SyncBN能够完美支持多卡训练,而普通BN在多卡模式下实际上就是单卡模式。

我们知道,BN中有moving mean和moving variance这两个buffer,这两个buffer的更新依赖于当前训练轮次的batch数据的计算结果。但是在普通多卡DP模式下,各个模型只能拿到自己的那部分计算结果,所以在DP模式下的普通BN被设计为只利用主卡上的计算结果来计算moving meanmoving variance,之后再广播给其他卡。这样,实际上BN的batch size就只是主卡上的batch size那么大。当模型很大、batch size很小时,这样的BN无疑会限制模型的性能。

为了解决这个问题,PyTorch新引入了一个叫SyncBN的结构,利用DDP的分布式计算接口来实现真正的多卡BN。

SyncBN的原理

SyncBN的原理很简单:SyncBN利用分布式通讯接口在各卡间进行通讯,从而能利用所有数据进行BN计算。为了尽可能地减少跨卡传输量,**SyncBN做了一个关键的优化,即只传输各自进程的各自的 小batch mean小batch variance,而不是所有数据。**具体流程请见下面:

  1. 前向传播

    1. 在各进程上计算各自的 小batch mean小batch variance
    2. 各自的进程对各自的 小batch mean小batch variance进行all_gather操作,每个进程都得到s的全局量。
    3. 注释:只传递mean和variance,而不是整体数据,可以大大减少通讯量,提高速度。
    4. 每个进程分别计算总体mean和总体variance,得到一样的结果
      1. 注释:在数学上是可行的,有兴趣的同学可以自己推导一下。
    5. 接下来,延续正常的BN计算。
      1. 注释:因为从前向传播的计算数据中得到的batch meanbatch variance在各卡间保持一致,所以,running_meanrunning_variance就能保持一致,不需要显式地同步了!
  2. 后向传播:和正常的一样

贴一下关键代码,有兴趣的同学可以研究下:pytorch源码

SyncBN与DDP的关系

**一句话总结,当前PyTorch SyncBN只在DDP单进程单卡模式中支持。**SyncBN用到 all_gather这个分布式计算接口,而使用这个接口需要先初始化DDP环境。

复习一下DDP的伪代码中的准备阶段中的DDP初始化阶段

d. 创建管理器reducer,给每个parameter注册梯度平均的hook。
i. 注释:这一步的具体实现是在C++代码里面的,即reducer.h文件。
e. (可能)为可能的SyncBN层做准备

这里有三个点需要注意:

  • 这里的为可能的SyncBN层做准备,实际上就是检测当前是否是DDP单进程单卡模式,如果不是,会直接停止。
  • 这告诉我们,SyncBN需要在DDP环境初始化后初始化,但是要在DDP模型前就准备好
  • 为什么当前PyTorch SyncBN只支持DDP单进程单卡模式?
    • 从SyncBN原理中我们可以看到,其强依赖了all_gather计算,而这个分布式接口当前是不支持单进程多卡或者DP模式的。当然,不排除未来也是有可能支持的。

怎么用SyncBN?

怎么样才能在我们的代码引入SyncBN呢?很简单:

# DDP init
dist.init_process_group(backend='nccl')# 按照原来的方式定义模型,这里的BN都使用普通BN就行了。
model = MyModel()
# 引入SyncBN,这句代码,会将普通BN替换成SyncBN。
model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)# 构造DDP模型
model = DDP(model, device_ids=[local_rank], output_device=local_rank)

又是熟悉的模样,像DDP一样,一句代码就解决了问题。这是怎么做到的呢?

convert_sync_batchnorm的原理:

torch.nn.SyncBatchNorm.convert_sync_batchnorm会搜索model里面的每一个module,如果发现这个module是、或者继承了torch.nn.modules.batchnorm._BatchNorm类,就把它替换成SyncBN。也就是说,如果你的Normalization层是自己定义的特殊类,没有继承过_BatchNorm类,那么convert_sync_batchnorm是不支持的,需要你自己实现一个新的SyncBN!

下面给一下convert_sync_batchnorm的源码,可以看到convert的过程中,新的SyncBN复制了原来的BN层的所有参数:

    @classmethoddef convert_sync_batchnorm(cls, module, process_group=None):r"""Helper function to convert all :attr:`BatchNorm*D` layers in the model to:class:`torch.nn.SyncBatchNorm` layers."""module_output = moduleif isinstance(module, torch.nn.modules.batchnorm._BatchNorm):module_output = torch.nn.SyncBatchNorm(module.num_features,module.eps, module.momentum,module.affine,module.track_running_stats,process_group)if module.affine:with torch.no_grad():module_output.weight = module.weightmodule_output.bias = module.biasmodule_output.running_mean = module.running_meanmodule_output.running_var = module.running_varmodule_output.num_batches_tracked = module.num_batches_trackedfor name, child in module.named_children():module_output.add_module(name, cls.convert_sync_batchnorm(child, process_group))del modulereturn module_output

二. DDP下的Gradient Accumulation的进一步加速

什么是Gradient Accmulation?

在这里插入图片描述

Gradient Accumulation,即梯度累加,相信大家都有所了解,是一种增大训练时batch size的技术,造福了无数硬件条件窘迫的我等穷人。不了解的同学请看这个知乎链接。

为什么还能进一步加速?

我们仔细思考一下DDP下的gradient accumulation。

# 单卡模式,即普通情况下的梯度累加
for 每次梯度累加循环optimizer.zero_grad()for _ in range(K):prediction = model(data)loss = loss_fn(prediction, label) / K  # 除以K,模仿loss function中的batchSize方向上的梯度平均,如果本身就没有的话则不需要。loss.backward()  # 积累梯度,不应用梯度改变optimizer.step()  # 应用梯度改变

我们知道,DDP的gradient all_reduce阶段发生在loss_fn(prediction, label).backward()。这意味着,在梯度累加的情况下,假设一次梯度累加循环有K个step,每次梯度累加循环会进行K次 all_reduce!但事实上,每次梯度累加循环只会有一次 optimizer.step(),即只应用一次参数修改,这意味着在每一次梯度累加循环中,我们其实只要进行一次gradient all_reduce即可满足要求,有K-1次 all_reduce**被浪费了!**而每次 all_reduce的时间成本是很高的!

如何加速

**解决问题的思路在于,对前K-1次step取消其梯度同步。**幸运的是,DDP给我们提供了一个暂时取消梯度同步的context函数 no_sync()(源代码)。在这个context下,DDP不会进行梯度同步。

所以,我们可以这样实现加速:

model = DDP(model)for 每次梯度累加循环optimizer.zero_grad()# 前accumulation_step-1个step,不进行梯度同步,累积梯度。for _ in range(K-1)::with model.no_sync():prediction = model(data)loss = loss_fn(prediction, label) / Kloss.backward()  # 积累梯度,不应用梯度改变# 第K个step,进行梯度同步prediction = model(data)loss = loss_fn(prediction, label) / Kloss.backward()  # 积累梯度,不应用梯度改变optimizer.step()

给一个优雅写法(同时兼容单卡、DDP模式哦):

from contextlib import nullcontext
# 如果你的python版本小于3.7,请注释掉上面一行,使用下面这个:
# from contextlib import suppress as nullcontextif local_rank != -1:model = DDP(model)optimizer.zero_grad()
for i, (data, label) in enumerate(dataloader):# 只在DDP模式下,轮数不是K整数倍的时候使用no_syncmy_context = model.no_sync if local_rank != -1 and i % K != 0 else nullcontextwith my_context():prediction = model(data)loss = loss_fn(prediction, label) / Kloss.backward()  # 积累梯度,不应用梯度改变if i % K == 0:optimizer.step()optimizer.zero_grad()

是不是很漂亮!

三. 多机多卡环境下的inference加速

问题

有一些非常现实的需求,相信大家肯定碰到过:

  1. 一般,训练中每几个epoch我们会跑一下inference、测试一下模型性能。在DDP多卡训练环境下,能不能利用多卡来加速inference速度呢?
  2. 我有一堆数据要跑一些网络推理,拿到inference结果。DP下多卡加速比太低,能不能利用DDP多卡来加速呢?

解法

这两个问题实际是同一个问题。答案肯定是可以的,但是,没有现成、省力的方法。

测试和训练的不同在于:

  1. 测试的时候不需要进行梯度反向传播,inference过程中各进程之间不需要通讯。
  2. 测试的时候,不同模型的inference结果、性能指标的类型多种多样,没有统一的形式。
    1. 我们很难定义一个统一的框架,像训练时model=DDP(model)那样方便地应用DDP多卡加速。

解决问题的思路很简单,就是各个进程中各自进行单卡的inference,然后把结果收集到一起。单卡inference很简单,我们甚至可以直接用DDP包装前的模型。问题其实只有两个:

  • 我们要如何把数据split到各个进程中
  • 我们要如何把结果合并到一起

如何把数据split到各个进程中:新的data sampler

大家肯定还记得,在训练的时候,我们用的 torch.utils.data.distributed.DistributedSampler帮助我们把数据不重复地分到各个进程上去。但是,其分的方法是:每段连续的N个数据,拆成一个一个,分给N个进程,所以每个进程拿到的数据不是连续的。这样,不利于我们在inference结束的时候将结果合并到一起。

所以,这里我们需要实现一个新的data sampler。它的功能,是能够连续地划分数据块,不重复地分到各个进程上去。直接给代码:

# 来源:https://github.com/huggingface/transformers/blob/447808c85f0e6d6b0aeeb07214942bf1e578f9d2/src/transformers/trainer_pt_utils.py
class SequentialDistributedSampler(torch.utils.data.sampler.Sampler):"""Distributed Sampler that subsamples indicies sequentially,making it easier to collate all results at the end.Even though we only use this sampler for eval and predict (no training),which means that the model params won't have to be synced (i.e. will not hangfor synchronization even if varied number of forward passes), we still add extrasamples to the sampler to make it evenly divisible (like in `DistributedSampler`)to make it easy to `gather` or `reduce` resulting tensors at the end of the loop."""def __init__(self, dataset, batch_size, rank=None, num_replicas=None):if num_replicas is None:if not torch.distributed.is_available():raise RuntimeError("Requires distributed package to be available")num_replicas = torch.distributed.get_world_size()if rank is None:if not torch.distributed.is_available():raise RuntimeError("Requires distributed package to be available")rank = torch.distributed.get_rank()self.dataset = datasetself.num_replicas = num_replicasself.rank = rankself.batch_size = batch_sizeself.num_samples = int(math.ceil(len(self.dataset) * 1.0 / self.batch_size / self.num_replicas)) * self.batch_sizeself.total_size = self.num_samples * self.num_replicasdef __iter__(self):indices = list(range(len(self.dataset)))# add extra samples to make it evenly divisibleindices += [indices[-1]] * (self.total_size - len(indices))# subsampleindices = indices[self.rank * self.num_samples : (self.rank + 1) * self.num_samples]return iter(indices)def __len__(self):return self.num_samples

如何把结果合并到一起: all_gather

通过torch.distributed提供的分布式接口all_gather,我们可以把各个进程的prediction结果集中到一起。

难点就在这里。因为世界上存在着千奇百怪的神经网络模型,有着千奇百怪的输出,所以,把数据集中到一起不是一件容易的事情。**但是,如果你的网络输出在不同的进程中有着一样的大小,那么这个问题就好解多了。**下面给一个方法,其要求网络的prediction结果在各个进程中的大小是一模一样的:

# 合并结果的函数
# 1. all_gather,将各个进程中的同一份数据合并到一起。
#   和all_reduce不同的是,all_reduce是平均,而这里是合并。
# 2. 要注意的是,函数的最后会裁剪掉后面额外长度的部分,这是之前的SequentialDistributedSampler添加的。
# 3. 这个函数要求,输入tensor在各个进程中的大小是一模一样的。
def distributed_concat(tensor, num_total_examples):output_tensors = [tensor.clone() for _ in range(torch.distributed.get_world_size())]torch.distributed.all_gather(output_tensors, tensor)concat = torch.cat(output_tensors, dim=0)# truncate the dummy elements added by SequentialDistributedSamplerreturn concat[:num_total_examples]

完整的流程

结合上面的介绍,我们可以得到下面这样一个完整的流程。

## 构造测试集
# 假定我们的数据集是这个
transform = torchvision.transforms.Compose([torchvision.transforms.ToTensor(),torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
my_testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
# 使用我们的新sampler
test_sampler = SequentialDistributedSampler(my_testset, batch_size=16)
testloader = torch.utils.data.DataLoader(my_testset, batch_size=16, sampler=test_sampler)# DDP和模型初始化,略。
# ......# 正式训练和evaluation
for epoch in range(total_epoch_size):# 训练代码,略# .......# 开始测试with torch.no_grad():# 1. 得到本进程的predictionpredictions = []labels = []for data, label in testloader:data, label = data.to(local_rank), label.to(local_rank)predictions.append(model(data))labels.append(label)# 进行gatherpredictions = distributed_concat(torch.concat(predictions, dim=0), len(test_sampler.dataset))labels = distributed_concat(torch.concat(labels, dim=0), len(test_sampler.dataset))# 3. 现在我们已经拿到所有数据的predictioin结果,进行evaluate!my_evaluate_func(predictions, labels)

更简化的解法

  1. 如果我们的目的只是得到性能数字,那么,我们甚至可以直接在各个进程中计算各自的性能数字,然后再合并到一起。上面给的解法,是为了更通用的情景。一切根据你的需要来定!
  2. 我们可以单向地把predictions、labels集中到 rank=0的进程,只在其进行evaluation并输出。PyTorch也提供了相应的接口(链接,send和recv)。

四. 保证DDP性能:确保数据的一致性

性能期望

从原理上讲,当没有开启SyncBN时,(或者更严格地讲,没有BN层;但一般有的话影响也不大),以下两种方法训练出来的模型应该是性能相似的:

  • 进程数为N的DDP训练
  • accumulation为N、其他配置完全相同的单卡训练

如果我们发现性能对不上,那么,往往是DDP中的某些设置出了问题。在DDP系列第二篇中,我们介绍过一个check list,可以根据它检查下自己的配置。其中,在造成性能对不齐的原因中,最有可能的是数据方面出现了问题。

DDP训练时,数据的一致性必须被保证:各个进程拿到的数据,要像是accumulation为N、其他配置完全相同的单卡训练中同个accumulation循环中不同iteration拿到的数据。想象一下,如果各个进程拿到的数据是一样的,或者分布上有任何相似的地方,那么,这就会造成训练数据质量的下降,最终导致模型性能下降。

容易错的点:随机数种子

为保证实验的可复现性,一般我们会在代码在开头声明一个固定的随机数种子,从而使得同一个配置下的实验,无论启动多少次,都会拿到同样的结果。

import random
import numpy as np
import torchdef init_seeds(seed=0, cuda_deterministic=True):random.seed(seed)np.random.seed(seed)torch.manual_seed(seed)# Speed-reproducibility tradeoff https://pytorch.org/docs/stable/notes/randomness.htmlif cuda_deterministic:  # slower, more reproduciblecudnn.deterministic = Truecudnn.benchmark = Falseelse:  # faster, less reproduciblecudnn.deterministic = Falsecudnn.benchmark = Truedef main():# 一般都直接用0作为固定的随机数种子。init_seeds(0)

但是在DDP训练中,如果还是像以前一样,使用0作为随机数种子,不做修改,就会造成以下后果:

  1. DDP的N个进程都使用同一个随机数种子

  2. 在生成数据时,如果我们使用了一些随机过程的数据扩充方法,那么,各个进程生成的数据会带有一定的同态性。

    1. 比如说,YOLOv5会使用mosaic数据增强(从数据集中随机采样3张图像与当前的拼在一起,组成一张里面有4张小图的大图)。这样,因为各卡使用了相同的随机数种子,你会发现,各卡生成的图像中,除了原本的那张小图,其他三张小图都是一模一样的!
  3. 同态性的数据,降低了训练数据的质量,也就降低了训练效率!最终得到的模型性能,很有可能是比原来更低的。

所以,我们需要给不同的进程分配不同的、固定的随机数种子:

def main():rank = torch.distributed.get_rank()# 问题完美解决!init_seeds(1 + rank)

五. 和DDP有关的小技巧

控制不同进程的执行顺序

一般情况下,各个进程是各自执行的,速度有快有慢,只有在gradient all-reduce的时候,快的进程才会等一下慢的进程,也就是进行同步。那么,如果我们需要在其他地方进行同步呢?比如说,在加载数据前,如果数据集不存在,我们要下载数据集:

  1. 我们只需要在唯一一个进程中开启一次下载
  2. 我们需要让其他进程等待其下载完成,再去加载数据

怎么解决这个问题呢?torch.distributed提供了一个barrier()的接口,利用它我们可以同步各个DDP中的各个进程!当使用barrier函数时,DDP进程会在函数的位置进行等待,知道所有的进程都跑到了 barrier函数的位置,它们才会再次向下执行。

只在某进程执行,无须同步:

这是最简单的,只需要一个简单的判断,用不到barrier()

if rank == 0:code_only_run_in_rank_0()

简单的同步:

没什么好讲的,只是一个示范

code_before()
# 在这一步同步
torch.distributed.barrier()
code_after()

在某个进程中执行A操作,其他进程等待其执行完成后再执行B操作:

也简单。

if rank == 0:do_A()torch.distributed.barrier()
else:torch.distributed.barrier()do_B()

在某个进程中优先执行A操作,其他进程等待其执行完成后再执行A操作:

这个值得深入讲一下,因为这个是非常普遍的需求。利用contextlib.contextmanager,我们可以把这个逻辑给优雅地包装起来!

from contextlib import contextmanager@contextmanager
def torch_distributed_zero_first(rank: int):"""Decorator to make all processes in distributed training wait for each local_master to do something."""if rank not in [-1, 0]:torch.distributed.barrier()# 这里的用法其实就是协程的一种哦。yieldif rank == 0:torch.distributed.barrier()

然后我们就可以这样骚操作:

with torch_distributed_zero_first(rank):if not check_if_dataset_exist():download_dataset()load_dataset()

优雅地解决了需求!

避免DDP带来的冗余输出

问题:

当我们在自己的模型中加入DDP模型时,第一的直观感受肯定是,终端里的输出变成了N倍了。这是因为我们现在有N个进程在同时跑整个程序。这不光是对有洁癖的同学造成困扰,其实对所有人都会造成困扰。因为各个进程的速度并不一样快,在茫茫的输出海洋中,我们难以debug、把控实验状态。

解法:

那么,有什么办法能避免这个现象呢?下面,笔者给一个可行的方法:**logging模块+输出信息等级控制。**即用logging输出代替所有print输出,并给不同进程设置不同的输出等级,只在0号进程保留低等级输出。举一个例子:

import logging# 给主要进程(rank=0)设置低输出等级,给其他进程设置高输出等级。
logging.basicConfig(level=logging.INFO if rank in [-1, 0] else logging.WARN)
# 普通log,只会打印一次。
logging.info("This is an ordinary log.")
# 危险的warning、error,无论在哪个进程,都会被打印出来,从而方便debug。
logging.error("This is a fatal log!")

simple but powerful!

六. 总结

既然看到了这里,不妨点个赞/喜欢吧!

不畏浮云遮望眼,只缘身在最高层

现在你已经系统地学习了DDP多机多卡加速的原理、源码实现、实战技巧,相信,在DDP上面,已经没有什么问题能够难倒你了。请为勤学苦练的自己鼓个掌!

DDP系列三篇就全部结束啦,谢谢大家捧场,.

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

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

相关文章

机器学习:系统设计与实现 分布式训练

机器学习系统:设计与实现 分布式训练 转自:https://openmlsys.github.io/chapter_distributed_training/index.html 随着机器学习的进一步发展,科学家们设计出更大型,更多功能的机器学习模型(例如说,GPT-3)…

从零Makefile落地算法大项目,完整案例教程

从零Makefile落地算法大项目,完整案例教程 转自:从零Makefile落地算法大项目,完整案例教程 作者:手写AI 前言 在这里,你能学到基于Makefile的正式大项目的使用方式和考虑,相信我,其实可以很简单…

PyTorch扩展自定义PyThonC++(CUDA)算子的若干方法总结

PyTorch扩展自定义PyThon/C(CUDA)算子的若干方法总结 转自:https://zhuanlan.zhihu.com/p/158643792 作者:奔腾的黑猫 在做毕设的时候需要实现一个PyTorch原生代码中没有的并行算子,所以用到了这部分的知识,再不总结就要忘光了 &a…

给 Python 算法插上性能的翅膀——pybind11 落地实践

给 Python 算法插上性能的翅膀——pybind11 落地实践 转自:https://zhuanlan.zhihu.com/p/444805518 作者:jesonxiang(向乾彪),腾讯 TEG 后台开发工程师 1. 背景 目前 AI 算法开发特别是训练基本都以 Python 为主&…

chrome自动提交文件_收集文档及提交名单统计

知乎文章若有排版问题请见谅,原文放在个人博客中【欢迎互踩!】文叔叔文档收集使用动机在我们的学习工作中,少不了要让大家集体提交文件的情况,举个最简单的例子:收作业。 传统的文件收集流程大致是:群内发出…

惠普800g1支持什么内存_惠普黑白激光打印机哪种好 惠普黑白激光打印机推荐【图文详解】...

打印机的出现让我们在生活和日常工作中变得越来越方便,不过随着科技的发展,打印机的类型也变得非常多,其中就有黑白激光打印机,而黑白激光打印机的品牌也有很多,比如我们的惠普黑白激光打印机,今天小编就给…

控制台输出颜色控制

控制台输出颜色控制 转自:https://cloud.tencent.com/developer/article/1142372 前端时间,写了一篇 PHP 在 Console 模式下的进度显示 ,正好最近的一个数据合并项目需要用到控制台颜色输出,所以就把相关的信息整理下,…

idea连接跳板机_跳板机服务(jumpserver)

一、跳板机服务作用介绍1、有效管理用户权限信息2、有效记录用户登录情况3、有效记录用户操作行为二、跳板机服务架构原理三、跳板机服务安装过程第一步:安装跳板机依赖软件yum -y install git python-pip mariadb-devel gcc automake autoconf python-devel readl…

【详细图解】再次理解im2col

【详细图解】再次理解im2col 转自:https://mp.weixin.qq.com/s/GPDYKQlIOq6Su0Ta9ipzig 一句话:im2col是将一个[C,H,W]矩阵变成一个[H,W]矩阵的一个方法,其原理是利用了行列式进行等价转换。 为什么要做im2col? 减少调用gemm的次数。 重要…

反思 大班 快乐的机器人_幼儿园大班教案《快乐的桌椅》含反思

大班教案《快乐的桌椅》含反思适用于大班的体育主题教学活动当中,让幼儿提高协调性和灵敏性,创新桌椅的玩法,正确爬的方法,学会匍匐前进,快来看看幼儿园大班《快乐的桌椅》含反思教案吧。幼儿园大班教案《快乐的桌椅》…

DCN可形变卷积实现1:Python实现

DCN可形变卷积实现1:Python实现 我们会先用纯 Python 实现一个 Pytorch 版本的 DCN ,然后实现其 C/CUDA 版本。 本文主要关注 DCN 可形变卷积的代码实现,不会过多的介绍其思想,如有兴趣,请参考论文原文: …

蓝牙耳机声音一顿一顿的_线控耳机党阵地转移成功,OPPO这款TWS耳机体验满分...

“你看到我手机里3.5mm的耳机孔了吗”,这可能是许多线控耳机党最想说的话了。确实,如今手机在做“减法”,而厂商们首先就拿3.5mm耳机孔“开刀”,我们也丧失了半夜边充电边戴耳机打游戏的乐趣。竟然如此,那如何在耳机、…

AI移动端优化之Im2Col+Pack+Sgemm

AI移动端优化之Im2ColPackSgemm 转自:https://blog.csdn.net/just_sort/article/details/108412760 这篇文章是基于NCNN的Sgemm卷积为大家介绍Im2ColPackSgemm的原理以及算法实现,希望对算法优化感兴趣或者做深度学习模型部署的读者带来帮助。 1. 前言 …

elementui的upload组件怎么获取上传的文本流、_抖音feed流直播间引流你还不会玩?实操讲解...

本文由艾奇在线明星优化师写作计划出品在这个全民惊恐多灾多难且带有魔幻的2020,一场突如其来的疫情改变了人们很多消费习惯,同时加速了直播电商的发展,现在直播已经成为商家必争的营销之地,直播虽然很火,但如果没有流…

FFmpeg 视频处理入门教程

FFmpeg 视频处理入门教程 转自:https://www.ruanyifeng.com/blog/2020/01/ffmpeg.html 作者: 阮一峰 日期: 2020年1月14日 FFmpeg 是视频处理最常用的开源软件。 它功能强大,用途广泛,大量用于视频网站和商业软件&…

checkbox wpf 改变框的大小_【论文阅读】倾斜目标范围框(标注)的终极方案

前言最常用的斜框标注方式是在正框的基础上加一个旋转角度θ,其代数表示为(x_c,y_c,w,h,θ),其中(x_c,y_c )表示范围框中心点坐标,(w,h)表示范围框的宽和高[1,2,7]。对于该标注方式,如果将w和h的值互换,再将θ加上或者…

彻底理解BP之手写BP图像分类你也行

彻底理解BP之手写BP图像分类你也行 转自:https://zhuanlan.zhihu.com/p/397963213 第一节:用矩阵的视角,看懂BP的网络图 1.1、什么是BP反向传播算法 BP(Back Propagation)误差反向传播算法,使用反向传播算法的多层感知器又称为B…

梯度下降法和牛顿法计算开根号

梯度下降法和牛顿法计算开根号 本文将介绍如何不调包,只能使用加减乘除法实现对根号x的求解。主要介绍梯度下降和牛顿法者两种方法,并给出 C 实现。 梯度下降法 思路/步骤 转化问题,将 x\sqrt{x}x​ 的求解转化为最小化目标函数&#xff…

汇博工业机器人码垛机怎么写_全自动码垛机器人在企业生产中的地位越来越重要...

全自动码垛机器人在企业生产中的地位越来越重要在智能化的各种全自动生产线中,全自动码垛机器人成了全自动生产线的重要机械设备,在各种生产中发挥着不可忽视的作用。全自动码垛机器人主要用于生产线上的包装过程中,不仅能够提高企业的生产率…

小说中场景的功能_《流浪地球》:从小说到电影

2019年春节贺岁档冒出一匹黑马:国产科幻片《流浪地球》大年初一上映后口碑、票房双丰收:截至9日下午,票房已破15亿,并获得9.2的高评分。著名导演詹姆斯卡梅隆通过社交媒体对我国春节期间上映的科幻影片《流浪地球》发出的祝愿&…