From: https://twitter.com/akshay_pachaar/status/1751583417463124020典型的 Dataset 类，如：tabular data表格数据(tabular data)，以 lris 数据集为例：text dataNLP 中，往往将 text 划分为多个小的 pieces，即 tokens，一般是 words 或 subwords.将 raw text 转换为 embedding(vectors) 生成器可以使用的格式，是一个必要的初始化步骤.image data看下图片数据集的格式，其对应的 Dataset Class 如，
原文：Getting Started with PyTorch Image Models (timm): A Practitioner’s Guide - 2022.02.02Github： rwightman/pytorch-image-modelsPyTorch Image Models（timm） 是一个优秀的图像分类 Python 库，其包含了大量的图像模型（Image Models）、Optimizers、Schedulers、Augmentations 等等.timm 提供了参考的 training 和 validation 脚本，用于复现在 ImageNet 上的训练结果；以及更多的 官方文档 和 timmdocs project.https://rwightman.github.io/pytorch-image-models/https://fastai.github.io/timmdocs/但，由于 timm 的功能之多，所以在定制使用时很难知道如何入手. 这里主要进行概述.pip install timm==0.5.41. Modelstimm 提供了大量的模型结构集合，而且很多模型都包含了预训练权重，或 PyTorch 训练、或从Jax和TensorFlow中移植，很方便下载使用.模型列表：https://paperswithcode.com/lib/timm查看模型列表：#打印 timm 提供的模型列表 print(timm.list_models()) print(len(timm.list_models())) #739 #带有预训练权重的模型列表 print(timm.list_models(pretrained=True)) print(len(timm.list_models(pretrained=True))) #592其中，timm.list_models() 函数：list_models(filter='', module='', pretrained=False, exclude_filters='', name_matches_cfg=False)查看特定族模型，如：print(timm.list_models('gluon_resnet*')) print(timm.list_models('*resnext*', 'resnet') ) print(timm.list_models('resnet*', pretrained=True))1.1. create_model 一般用法timm 创建模型最简单的方式是采用 create_model.以 Resnet-D 模型为例（Bag of Tricks for Image Classification For Convolutional Neural Networks paper），其是Resnet 的一种变形，其采用 average pool 进行下采样.model = timm.create_model('resnet50d', pretrained=True) print(model) #查看模型配置参数 print(model.default_cfg) ''' {'url': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/resnet50d_ra2-464e36ba.pth', 'num_classes': 1000, 'input_size': (3, 224, 224), 'pool_size': (7, 7), 'crop_pct': 0.875, 'interpolation': 'bicubic', 'mean': (0.485, 0.456, 0.406), 'std': (0.229, 0.224, 0.225), 'first_conv': 'conv1.0', 'classifier': 'fc', 'architecture': 'resnet50d'} '''1.2. create_model 修改输入通道timm models 有个非常有用的特点，其可以处理任意通道数量的输入图像. 这是很多其他库所不具备的. 其实现原理可参考：https://fastai.github.io/timmdocs/models#So-how-is-timm-able-to-load-these-weights?model = timm.create_model('resnet50d', pretrained=True, in_chans=1) print(model) #test, single channel image x = troch.randn(1, 1, 224, 224) out = model(x) print(out.shape) #torch.Size([1, 1000])1.3. create_model 定制模型timm create_model 函数提供了很多参数，用于模型定制，函数定义如：create_model(model_name, pretrained=False, checkpoint_path='', scriptable=None, exportable=None, no_jit=None, **kwargs)**kwargs 示例参数如，global_pool - 定义最终分类层所采用的 global pooling 类型. 取决于网络结构是否用到了全局池化层.drop_rate - 设定训练时的 dropout 比例，默认是 0.num_classes - 输出类别数1.3.1. 修改类别数查看当前模型输出层：#如果输出层是 fc，则如 print(model.fc) #Linear(in_features=2048, out_features=1000, bias=True) #通用方式，查看输出层， print(model.get_classifier())修改输出层类别数：model = timm.create_model('resnet50d', pretrained=True, num_classes=10) print(model) print(model.get_classifier()) #Linear(in_features=2048, out_features=10, bias=True)如果完全不需要创建最后一层，可以将 num_classes 设为 0，模型将用恒等函数作为最后一层，其对于查看倒数第二层的输出有用.model = timm.create_model('resnet50d', pretrained=True, num_classes=0) print(model) print(model.get_classifier()) #Identity()1.3.2. Global pooling在 model.default_cfg 中出现的 pool_size 设置，说明了在分类器前用到了一个全局池化层，如：print(model.global_pool) #SelectAdaptivePool2d (pool_type=avg, flatten=Flatten(start_dim=1, end_dim=-1))其中，pool_type 支持：avg - 平均池化max - 最大池化avgmax - 平均池化和最大池化的求和，加权 0.5catevgmax - 沿着特征维度的平均池化和最大池化的输出的拼接，特征维度会翻倍'' - 不采用 pooling，其被替换为恒等操作（Identity）pool_types = ['avg', 'max', 'avgmax', 'catavgmax', ''] x = torch.randn(1, 3, 224, 224) for pool_type in pool_types: model = timm.create_model('resnet50d', pretrained=True, num_classes=0, global_pool=pool_type) model.eval() out = model(x) print(out.shape)1.3.3. 修改已有模型如，model = timm.create_model('resnet50d', pretrained=True) print(f'[INFO]Original Pooling: {model.global_pool}') print(f'[INFO]Original Classifier: {model.get_classifier}') model = model.reset_classifier(10, 'max') print(f'[INFO]Modified Pooling: {model.global_pool}') print(f'[INFO]Modified Classifier: {model.get_classifier}')1.3.4. 创建新的分类 head虽然单个线性层已经足够得到比较好的结果，但有些时候需要更大的分类 head 来提升性能.model = timm.create_model('resnet50d', pretrained=True, num_classes=10, global_pool='catavgmax') print(model) num_in_features = model.get_classifier().in_features print(num_in_features) model.fc = nn.Sequential( nn.BatchNorm1d(num_in_features), nn.Linear(in_features=num_in_features, out_features=512, bias=False), nn.ReLU(), nn.BatchNorm1d(512), nn.Dropout(0.4), nn.Linear(in_features=512, out_features=10, bias=False)) model.eval() x = troch.randn(1, 3, 224, 224) out = model(x) print(out.shape)1.4. 特征提取timm 提供了很多不同类型网络中间层的机制，其有助于作为特征提取以应用于下游任务.1.4.1. 最终特征图from PIL import Image import matplotlib.pyplot as plt import numpy as np import torch image = Image.open('test.jpg') image = torch.as_tensor(np.array(image, dtype=np.float32)).transpose(2, 0)[None] model = timm.create_model("resnet50d", pretrained=True) print(model.default_cfg) #如，只查看最终特征图，这里是池化层前的最后一个卷积层的输出 feature_output = model.forward_features(image) def vis_feature_output(feature_output): plt.imshow(feature_output[0]).transpose(0, 2).sum(-1).detach().numpy()) plt.show() # vis_feature_output(feature_output)1.4.2. 多种特征输出model = timm.create_model("resnet50d", pretrained=True, features_only=True) print(model.feature_info.module_name()) #['act1', 'layer1', 'layer2', 'layer3', 'layer4'] print(model.feature_info.reduction()) #[2, 4, 8, 16, 32] print(model.feature_info.channels()) #[64, 256, 512, 1024, 2048] out = model(image) print(len(out)) # 5 for o in out: print(o.shape) plt.imshow(o[0].transpose(0, 2).sum(-1).detach().numpy()) plt.show()1.4.3. 采用 Torch FXTorchVision 新增了一个 FX 模块，其更便于获得输入在前向计算过程中的中间变换. 通过符号性的追踪前向方法，以生成一个图，途中的每个节点表示一个操作. 由于节点是易读的，其可以很方便的准确指定到具体节点.https://pytorch.org/docs/stable/fx.html#module-torch.fxhttps://pytorch.org/blog/FX-feature-extraction-torchvision/#torchvision >= 0.11.0 from torchvision.models.feature_extraction import get_graph_node_names, create_feature_extractor model = timm.create_model("resnet50d", pretrained=True, exportable=True) nodes, _ = get_graph_node_names(model) print(nodes) features = {'layer1.0.act2': 'out'} feature_extractor = create_feature_extractor(model, return_nodes=features) print(feature_extractor) out = feature_extractor(image) plt.imshow(out['out'][0].transpose(0, 2).sum(-1).detach().numpy()) plt.show()1.5. 模型导出不同格式模型训练后，一般推荐将模型导出为优化的格式，以进行推断.1.5.1. 导出 TorchScripthttps://pytorch.org/docs/stable/jit.htmlhttps://pytorch.org/tutorials/beginner/Intro_to_TorchScript_tutorial.htmlmodel = timm.create_model("resnet50d", pretrained=True, scriptable=True) model.eval() #重要 scripted_model = torch.jit.script(model) print(scripted_model) print(scripted_model(torch.rand(8, 3, 224, 224)).shape)1.5.2. 导出 ONNXOpen Neural Network eXchange (ONNX)https://pytorch.org/docs/master/onnx.htmlmodel = timm.create_model("resnet50d", pretrained=True, exportable=True) model.eval() #重要 x = torch.randn(2, 3, 224, 224, requires_grad=True) torch_out = model(x) #Export the model torch.onnx.export(model, #模型 x, #输入 'resnet50d.onnx', #模型导出路径 export_params=True, #模型文件存储训练参数权重 opset_version=10, #ONNX 版本 do_constant_folding=True,#是否执行不断折叠优化 input_names=['input'], #输入名 output_names=['output'], #输出名 dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}} ) #验证导出模型 import onnx onnx_model = onnx.load('resnet50d.onnx') onnx.checker.check_model(onnx_model) traced_model = torch.jit.trace(model, torch.rand(8, 3, 224, 224)) type(traced_model) print(traced_model(torch.rand(8, 3, 224, 224)).shape)2. Augmentationstimm 的数据格式与 TorchVision 类似，PIL 图像作为输入.from timm.data.transforms_factory import create_transform print(create_transform(224, )) ''' Compose( Resize(size=256, interpolation=bilinear, max_size=None, antialias=None) CenterCrop(size=(224, 224)) ToTensor() Normalize(mean=tensor([0.4850, 0.4560, 0.4060]), std=tensor([0.2290, 0.2240, 0.2250])) ) ''' print(create_transform(224, is_training=True)) ''' Compose( RandomResizedCropAndInterpolation(size=(224, 224), scale=(0.08, 1.0), ratio=(0.75, 1.3333), interpolation=bilinear) RandomHorizontalFlip(p=0.5) ColorJitter(brightness=[0.6, 1.4], contrast=[0.6, 1.4], saturation=[0.6, 1.4], hue=None) ToTensor() Normalize(mean=tensor([0.4850, 0.4560, 0.4060]), std=tensor([0.2290, 0.2240, 0.2250])) ) '''2.1. RandAugment对于新任务场景，很难确定要用到哪些数据增强. 且，鉴于如此多的数据增强策略，其组合数量更是庞大.一种好的起点是，采用在其他任务上被验证有效的数据增强pipeline. 如，RandAugmentRandAugment，是一种自动数据增强方法，其从增强方法集合中均匀采样，如， equalization, rotation, solarization, color jittering, posterizing, changing contrast, changing brightness, changing sharpness, shearing, and translations，并按序应用其中的一些.RandAugment: Practical automated data augmentation with a reduced search spaceRandAugment 参数：N - 随机变换的数量（ number of distortions uniformly sampled and applied per-image）M - 变换的幅度（distortion magnitude）timm 中 RandAugment 是通过配置字符串来指定的，以 - 分割符.m - 随机增强的幅度n - 每张图像进行的随机变换数，默认为 2.mstd - 标准偏差的噪声幅度mmax - 设置幅度的上界，默认 10w - 加权索引的概率（index of a set of weights to influence choice of operation）inc - 采用随幅度增加的数据增强，默认为 0如，rand-m9-n3-mstd0.5 - 幅度为9，每张图像 3 种数据增强，mstd 为 0.5rand-mstd1-w0 - mstd 为 1.0，weights 为 0，默认幅度m为10，每张图像 2 种数据增强print(create_transform(224, is_training=True, auto_augment='rand-m9-mstd0.5')) ''' Compose( RandomResizedCropAndInterpolation(size=(224, 224), scale=(0.08, 1.0), ratio=(0.75, 1.3333), interpolation=bilinear) RandomHorizontalFlip(p=0.5) RandAugment(n=2, ops= AugmentOp(name=AutoContrast, p=0.5, m=9, mstd=0.5) AugmentOp(name=Equalize, p=0.5, m=9, mstd=0.5) AugmentOp(name=Invert, p=0.5, m=9, mstd=0.5) AugmentOp(name=Rotate, p=0.5, m=9, mstd=0.5) AugmentOp(name=Posterize, p=0.5, m=9, mstd=0.5) AugmentOp(name=Solarize, p=0.5, m=9, mstd=0.5) AugmentOp(name=SolarizeAdd, p=0.5, m=9, mstd=0.5) AugmentOp(name=Color, p=0.5, m=9, mstd=0.5) AugmentOp(name=Contrast, p=0.5, m=9, mstd=0.5) AugmentOp(name=Brightness, p=0.5, m=9, mstd=0.5) AugmentOp(name=Sharpness, p=0.5, m=9, mstd=0.5) AugmentOp(name=ShearX, p=0.5, m=9, mstd=0.5) AugmentOp(name=ShearY, p=0.5, m=9, mstd=0.5) AugmentOp(name=TranslateXRel, p=0.5, m=9, mstd=0.5) AugmentOp(name=TranslateYRel, p=0.5, m=9, mstd=0.5)) ToTensor() Normalize(mean=tensor([0.4850, 0.4560, 0.4060]), std=tensor([0.2290, 0.2240, 0.2250])) ) '''也可以通过 rand_augment_transform 函数来实现：from timm.data.auto_augment import rand_augment_transform tfm = rand_augment_transform(config_str='rand-m9-mstd0.5', hparams={'img_mean': (124, 116, 104)}) print(tfm) ''' RandAugment(n=2, ops= AugmentOp(name=AutoContrast, p=0.5, m=9, mstd=0.5) AugmentOp(name=Equalize, p=0.5, m=9, mstd=0.5) AugmentOp(name=Invert, p=0.5, m=9, mstd=0.5) AugmentOp(name=Rotate, p=0.5, m=9, mstd=0.5) AugmentOp(name=Posterize, p=0.5, m=9, mstd=0.5) AugmentOp(name=Solarize, p=0.5, m=9, mstd=0.5) AugmentOp(name=SolarizeAdd, p=0.5, m=9, mstd=0.5) AugmentOp(name=Color, p=0.5, m=9, mstd=0.5) AugmentOp(name=Contrast, p=0.5, m=9, mstd=0.5) AugmentOp(name=Brightness, p=0.5, m=9, mstd=0.5) AugmentOp(name=Sharpness, p=0.5, m=9, mstd=0.5) AugmentOp(name=ShearX, p=0.5, m=9, mstd=0.5) AugmentOp(name=ShearY, p=0.5, m=9, mstd=0.5) AugmentOp(name=TranslateXRel, p=0.5, m=9, mstd=0.5) AugmentOp(name=TranslateYRel, p=0.5, m=9, mstd=0.5)) '''2.2. CutMix 和 MixupCutMix Mixuptimm 的 Mixup 类，支持的不同混合策略有：batch - CutMix vs Mixup selection, lambda, and CutMix region sampling are performed per batchpair - mixing, lambda, and region sampling are performed on sampled pairs within a batchelem - mixing, lambda, and region sampling are performed per image within batchhalf - the same as elementwise but one of each mixing pair is discarded so that each sample is seen once per epochMixup 支持的数据增强有：mixup_alpha (float): mixup alpha value, mixup is active if > 0., (default: 1)cutmix_alpha (float): cutmix alpha value, cutmix is active if > 0. (default: 0)cutmix_minmax (List[float]): cutmix min/max image ratio, cutmix is active and uses this vs alpha if not None.prob (float): the probability of applying mixup or cutmix per batch or element (default: 1)switch_prob (float): the probability of switching to cutmix instead of mixup when both are active (default: 0.5)mode (str): how to apply mixup/cutmix params (default: batch)label_smoothing (float): the amount of label smoothing to apply to the mixed target tensor (default: 0.1)num_classes (int): the number of classes for the target variablefrom timm.data import ImageDataset from torch.utils.data import DataLoader def create_dataloader_iterator(): dataset = ImageDataset('pets/images', transform=create_transform(224, )) dl = iter(DataLoader(dataset, batch_size=2)) return dl dataloader = create_dataloader_iterator() inputs, classes = next(dataloader) # out = torchvision.utils.make_grid(inputs) imshow(out, title=[x.item() for x in classes]) # from timm.data.mixup import Mixup mixup_args = {'mixup_alpha': 1., 'cutmix_alpha': 1., 'prob': 1, 'switch_prob': 0.5, 'mode': 'batch', 'label_smoothing': 0.1, 'num_classes': 2} mixup_fn = Mixup(**mixup_args) mixed_inputs, mixed_classes = mixup_fn(inputs.to(torch.device('cuda:0')), classes.to(torch.device('cuda:0'))) out = torchvision.utils.make_grid(mixed_inputs) imshow(out, title=mixed_classes)3. Datasetstimm 中 create_dataset 函数期望有两个输入参数：name - 指定待加载数据集的名字root - 数据集存放根目录其支持不同的数据存储：TorchVisionTensorFlow datasets本地文件夹#TorchVision ds = create_dataset('torch/cifar10', 'cifar10', download=True, split='train') print(ds, type(ds)) print(ds[0]) #TensorFlow ds = create_dataset('tfds/beans', 'beans', download=True, split='train[:10%]', batch_size=2, is_training=True) print(ds) ds_iter = iter(ds) image, label = next(ds_iter) #本地文件夹 ds = create_dataset(name='', root='imagenette/imagenette2-320.tar', transfor=create_transform(224)) image, label = ds[0] print(image.shape)3.1. ImageDataset 类除了 create_dataset，timm 还提供了两个 ImageDataset 和 IterableImageDataset 以适应更多的场景.from timm.data import ImageDataset imagenette_ds = ImageDataset('imagenette/imagenette2-320/train') print(len(imagenette_ds)) print(imagenette_ds.parser) print(imagenette_ds.parser.class_to_idx) from timm.data.parser.parser_image_in_tar import ParserImageTar data_path = 'imagenette' ds = ImageDataset(data_path, parser=ParserImageInTar(data_path))3.1.1. 定制 Parser参考 ParserImageFolder:""" A dataset parser that reads images from folders Folders are scannerd recursively to find image files. Labels are based on the folder hierarchy, just leaf folders by default. Hacked together by / Copyright 2020 Ross Wightman """ import os from timm.utils.misc import natural_key from .parser import Parser from .class_map import load_class_map from .constants import IMG_EXTENSIONS def find_images_and_targets(folder, types=IMG_EXTENSIONS, class_to_idx=None, leaf_name_only=True, sort=True): labels = [] filenames = [] for root, subdirs, files in os.walk(folder, topdown=False, followlinks=True): rel_path = os.path.relpath(root, folder) if (root != folder) else '' label = os.path.basename(rel_path) if leaf_name_only else rel_path.replace(os.path.sep, '_') for f in files: base, ext = os.path.splitext(f) if ext.lower() in types: filenames.append(os.path.join(root, f)) labels.append(label) if class_to_idx is None: # building class index unique_labels = set(labels) sorted_labels = list(sorted(unique_labels, key=natural_key)) class_to_idx = {c: idx for idx, c in enumerate(sorted_labels)} images_and_targets = [(f, class_to_idx[l]) for f, l in zip(filenames, labels) if l in class_to_idx] if sort: images_and_targets = sorted(images_and_targets, key=lambda k: natural_key(k[0])) return images_and_targets, class_to_idx class ParserImageFolder(Parser): def __init__( self, root, class_map=''): super().__init__() self.root = root class_to_idx = None if class_map: class_to_idx = load_class_map(class_map, root) self.samples, self.class_to_idx = find_images_and_targets(root, class_to_idx=class_to_idx) if len(self.samples) == 0: raise RuntimeError( f'Found 0 images in subfolders of {root}. Supported image extensions are {", ".join(IMG_EXTENSIONS)}') def __getitem__(self, index): path, target = self.samples[index] return open(path, 'rb'), target def __len__(self): return len(self.samples) def _filename(self, index, basename=False, absolute=False): filename = self.samples[index][0] if basename: filename = os.path.basename(filename) elif not absolute: filename = os.path.relpath(filename, self.root) return filename如：from pathlib import Path from timm.data.parsers.parser import Parser class ParserImageName(Parser): def __init__(self, root, class_to_idx=None): super().__init__() self.root = Path(root) self.samples = list(self.root.glob("*.jpg")) if class_to_idx: self.class_to_idx = class_to_idx else: classes = sorted( set([self.__extract_label_from_path(p) for p in self.samples]), key=lambda s: s.lower(), ) self.class_to_idx = {c: idx for idx, c in enumerate(classes)} def __extract_label_from_path(self, path): return "_".join(path.parts[-1].split("_")[0:-1]) def __getitem__(self, index): path = self.samples[index] target = self.class_to_idx[self.__extract_label_from_path(path)] return open(path, "rb"), target def __len__(self): return len(self.samples) def _filename(self, index, basename=False, absolute=False): filename = self.samples[index][0] if basename: filename = filename.parts[-1] elif not absolute: filename = filename.absolute() return filename # data_path = 'test' ds = ImageDataset(data_path, parser=ParserImageName(data_path)) print(ds[0]) print(ds.parser.class_to_idx)4. Optimizerstimm 支持的优化器有：SGDAdam AdamWAdamPRMSPropTFLAMB - FusedLAMB optimizer from Apex 的 PyTorch 版AdaBeliefMADGRADAdaHessianimport inspect import timm.optim optims_list = [cls_name for cls_name, cls_obj in inspect.getmembers(timm.optim) if inspect.isclass(cls_obj) if cls_name != 'Lookhead'] print(optims_list)timm 中 create_optimizer_v2 函数.import torch model = torch.nn.Sequential(torch.nn.Linear(2, 1), torch.nn.Flatten(0, 1)) optimizer = timm.optim.create_optimizer_v2(model, opt='sgd', lr=0.01, momentum=0.8) print(optimizer, type(optimizer)) ''' SGD ( Parameter Group 0 dampening: 0 lr: 0.01 momentum: 0.8 nesterov: True weight_decay: 0.0 ) <class 'torch.optim.sgd.SGD'> ''' optimizer = timm.optim.create_optimizer_v2(model, opt='lamb', lr=0.01, weight_decay=0.01) print(optimizer, type(optimizer)) ''' Lamb ( Parameter Group 0 always_adapt: False betas: (0.9, 0.999) bias_correction: True eps: 1e-06 grad_averaging: True lr: 0.01 max_grad_norm: 1.0 trust_clip: False weight_decay: 0.0 Parameter Group 1 always_adapt: False betas: (0.9, 0.999) bias_correction: True eps: 1e-06 grad_averaging: True lr: 0.01 max_grad_norm: 1.0 trust_clip: False weight_decay: 0.01 ) <class 'timm.optim.lamb.Lamb'> '''手工创建优化器，如：optimizer = timm.optim.RMSpropTF(model.parameters(), lr=0.01)4.1. 使用示例# replace # optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # with optimizer = timm.optim.AdamP(model.parameters(), lr=0.01) for epoch in num_epochs: for batch in training_dataloader: inputs, targets = batch outputs = model(inputs) loss = loss_function(outputs, targets) loss.backward() optimizer.step() optimizer.zero_grad() # optimizer = timm.optim.Adahessian(model.parameters(), lr=0.01) is_second_order = ( hasattr(optimizer, "is_second_order") and optimizer.is_second_order ) # True for epoch in num_epochs: for batch in training_dataloader: inputs, targets = batch outputs = model(inputs) loss = loss_function(outputs, targets) loss.backward(create_graph=second_order) optimizer.step() optimizer.zero_grad()4.2. LookaheadLookahead Optimizer: k steps forward, 1 step backoptimizer = timm.optim.create_optimizer_v2(model.parameters(), opt='lookahead_adam', lr=0.01) #或 timm.optim.Lookahead(optimizer, alpha=0.5, k=6) optimizer.sync_lookahead() 示例如，optimizer = timm.optim.AdamP(model.parameters(), lr=0.01) optimizer = timm.optim.Lookahead(optimizer) for epoch in num_epochs: for batch in training_dataloader: inputs, targets = batch outputs = model(inputs) loss = loss_function(outputs, targets) loss.backward() optimizer.step() optimizer.zero_grad() optimizer.sync_lookahead()5. Schedulerstimm 支持的 Schedulers 有：StepLRScheduler: 每 n 次迭代衰减一次学习率，类似于 torch.optim.lr_scheduler.StepLRMultiStepLRScheduler: 设置特定迭代次数，衰减学习率，类似于 torch.optim.lr_scheduler.MultiStepLRPlateauLRScheduler: reduces the learning rate by a specified factor each time a specified metric plateaus; 类似于 torch.optim.lr_scheduler.ReduceLROnPlateauCosineLRScheduler: cosine decay schedule with restarts, 类似于 torch.optim.lr_scheduler.CosineAnnealingWarmRestartsTanhLRScheduler: hyberbolic-tangent decay schedule with restartsPolyLRScheduler: polynomial decay schedule5.1. 使用示例与PyTorch shceduler 不同的是，timm scheduler 每个 epoch 更新两次：.step_update - 每次 optimizer 更新后调用..step - 每个 epoch 结束后调用training_epochs = 300 cooldown_epochs = 10 num_epochs = training_epochs + cooldown_epochs optimizer = timm.optim.AdamP(my_model.parameters(), lr=0.01) scheduler = timm.scheduler.CosineLRScheduler(optimizer, t_initial=training_epochs) for epoch in range(num_epochs): num_steps_per_epoch = len(train_dataloader) num_updates = epoch * num_steps_per_epoch for batch in training_dataloader: inputs, targets = batch outputs = model(inputs) loss = loss_function(outputs, targets) loss.backward() optimizer.step() scheduler.step_update(num_updates=num_updates) optimizer.zero_grad() scheduler.step(epoch + 1)5.2. CosineLRScheduler为了深入阐述 timm 所提供的参数选项，这里以 timm 默认训练脚本中所采用的 sheduler - CosineLRScheduler 为例. timm 的 cosine scheduler 与 PyTorch 中的实现是不同的.5.2.1. PyTorch CosineAnnealingWarmRestartsCosineAnnealingWarmRestarts 需要设定如下参数：T_0 (int): Number of iterations for the first restart.T_mult (int): A factor that increases T_{i} after a restart. (Default: 1)eta_min (float): Minimum learning rate. (Default: 0.)last_epoch (int) — The index of last epoch. (Default: -1)#args num_epochs=300 num_epoch_repeat=num_epochs//2 num_steps_per_epoch=10 def create_model_and_optimizer(): model = torch.nn.Linear(2, 1) optimizer = torch.optim.SGD(model.parameters(), lr=0.05) return model, optimizer #create learning rate scheduler model, optimizer = create_model_and_optimizer() scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts( optimizer, T_0=num_epoch_repeat*num_steps_per_epoch, T_mult=1, eta_min=1e-6, last_epoch=-1) #vis import matplotlib.pyplot as plt lrs = [] for epoch in range(num_epochs): for i in range(num_steps_per_epoch): scheduler.step() lrs.append(optimizer.param_groups[0]['lr']) plt.plot(lrs) plt.show()可以看出，lr 在 150 epoch 前保持衰减，而在第 150 epoch 时重启为初始值，并开始再次衰减.5.2.2. timm CosineLRSchedulertimm CosineLRScheduler 需要设定如下参数：t_initial (int): Number of iterations for the first restart, this is equivalent to T_0 in torch’s implementationlr_min (float): Minimum learning rate, this is equivalent to eta_min in torch’s implementation (Default: 0.)cycle_mul (float): A factor that increases T_{i} after a restart, this is equivalent to T_mult in torch’s implementation (Default: 1)cycle_limit (int): Limit the number of restarts in a cycle (Default: 1)t_in_epochs (bool): Whether the number iterations is given in terms of epochs rather than the number of batch updates (Default: True)#args num_epochs=300 num_epoch_repeat=num_epochs//2 num_steps_per_epoch=10 def create_model_and_optimizer(): model = torch.nn.Linear(2, 1) optimizer = torch.optim.SGD(model.parameters(), lr=0.05) return model, optimizer #create learning rate scheduler model, optimizer = create_model_and_optimizer() scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=num_epoch_repeat*num_steps_per_epoch, lr_min=1e-6, cycle_limit=num_epoch_repeat+1, t_in_epochs=False) #or scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=num_epoch_repeat, lr_min=1e-6, cycle_limit=num_epoch_repeat+1, t_in_epochs=True) #vis import matplotlib.pyplot as plt lrs = [] for epoch in range(num_epochs): num_updates = epoch * num_steps_per_epoch for i in range(num_steps_per_epoch): num_updates += 1 scheduler.step_update(num_updates=num_updates) scheduler.step(epoch+1) lrs.append(optimizer.param_groups[0]['lr']) plt.plot(lrs) plt.show()示例策略：scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=num_epoch_repeat*num_steps_per_epoch, cycle_mul=2., cycle_limit=num_epoch_repeat+1, t_in_epochs=False) scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=num_epoch_repeat*num_steps_per_epoch, lr_min=1e-5, cycle_limit=1) scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=50, lr_min=1e-5, cycle_decay=0.8, cycle_limit=num_epoch_repeat+1) scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=num_epoch_repeat*num_steps_per_epoch, lr_min=1e-5, k_decay=0.5, cycle_limit=num_epoch_repeat+1) scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=num_epoch_repeat*num_steps_per_epoch, lr_min=1e-5, k_decay=2, cycle_limit=num_epoch_repeat+1)5.2.3. 添加 warm up如，设置 20 个 warm up epochs，#args num_epochs=300 num_epoch_repeat=num_epochs//2 num_steps_per_epoch=10 def create_model_and_optimizer(): model = torch.nn.Linear(2, 1) optimizer = torch.optim.SGD(model.parameters(), lr=0.05) return model, optimizer #create learning rate scheduler scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=num_epoch_repeat, lr_min=1e-5, cycle_limit=num_epoch_repeat+1, warmup_lr_init=0.01, warmup_t=20) #vis import matplotlib.pyplot as plt lrs = [] for epoch in range(num_epochs): num_updates = epoch * num_steps_per_epoch for i in range(num_steps_per_epoch): num_updates += 1 scheduler.step_update(num_updates=num_updates) scheduler.step(epoch+1) lrs.append(optimizer.param_groups[0]['lr']) plt.plot(lrs) plt.show()5.2.4. 添加 noise#args num_epochs=300 num_epoch_repeat=num_epochs//2 num_steps_per_epoch=10 def create_model_and_optimizer(): model = torch.nn.Linear(2, 1) optimizer = torch.optim.SGD(model.parameters(), lr=0.05) return model, optimizer #create learning rate scheduler scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=num_epoch_repeat, lr_min=1e-5, cycle_limit=num_epoch_repeat+1, noise_range_t=(0, 150), #noise_range_t：噪声范围 noise_pct=0.1) #noise_pct：噪声程度 #vis import matplotlib.pyplot as plt lrs = [] for epoch in range(num_epochs): num_updates = epoch * num_steps_per_epoch for i in range(num_steps_per_epoch): num_updates += 1 scheduler.step_update(num_updates=num_updates) scheduler.step(epoch+1) lrs.append(optimizer.param_groups[0]['lr']) plt.plot(lrs) plt.show()5.3. timm 默认设置def create_model_and_optimizer(): model = torch.nn.Linear(2, 1) optimizer = torch.optim.SGD(model.parameters(), lr=0.05) return model, optimizer #create learning rate scheduler model, optimizer = create_model_and_optimizer() #args training_epochs=300 cooldown_epochs=10 num_epochs=training_epochs + cooldown_epochs num_steps_per_epoch=10 scheduler = timm.scheduler.CosineLRScheduler( optimizer, t_initial=training_epochs, lr_min=1e-6, t_in_epochs=True, warmup_t=3, warmup_lr_init=1e-4, cycle_limit=1) # no restart #vis import matplotlib.pyplot as plt lrs = [] for epoch in range(num_epochs): num_updates = epoch * num_steps_per_epoch for i in range(num_steps_per_epoch): num_updates += 1 scheduler.step_update(num_updates=num_updates) scheduler.step(epoch+1) lrs.append(optimizer.param_groups[0]['lr']) plt.plot(lrs) plt.show()5.4. 其他 Scheduler#TanhLRScheduler scheduler = timm.scheduler.TanhLRScheduler( optimizer, t_initial=num_epoch_repeat, lr_min=1e-6, cycle_limit=num_epoch_repeat+1) #PolyLRScheduler scheduler = timm.scheduler.PolyLRScheduler( optimizer, t_initial=num_epoch_repeat, lr_min=1e-6, cycle_limit=num_epoch_repeat+1) scheduler = timm.scheduler.PolyLRScheduler( optimizer, t_initial=num_epoch_repeat, lr_min=1e-6, cycle_limit=num_epoch_repeat+1, k_decay=0.5) scheduler = timm.scheduler.PolyLRScheduler( optimizer, t_initial=num_epoch_repeat, lr_min=1e-6, cycle_limit=num_epoch_repeat+1, k_decay=2)6. EMA 模型指数移动平均EMA，Exponential Moving Average Model模型训练时，一种好的方式是，将模型权重值设置为整个训练过程中所有参数的移动平均，而不是仅仅只采用最后一次增量更新的.实际上，这往往是通过保持 EMA 来实现的，其是训练的模型副本.不过，相比于每次更新 step 更新全量的模型参数，一般将这些参数设置为当前参数值和更新参数值的线性组合，公式如下：$$ updated\_EMA\_model\_weights = decay * EMA\_model\_weights + (1. - decay) * updated\_model\_weights $$如，$$ updated\_EMA\_model\_weights = 0.99 * EMA\_model\_weights + 0.01 * updated\_model\_weights $$timm 中 ModelEmaV2 示例，model = create_model().to(gpu_device) ema_model = timm.utils.ModelEmaV2(model, decay=0.9998) for epoch in num_epochs: for batch in training_dataloader: inputs, targets = batch outputs = model(inputs) loss = loss_function(outputs, targets) loss.backward() optimizer.step() optimizer.zero_grad() ema_model.update(model) for batch in validation_dataloader: inputs, targets = batch outputs = model(inputs) validation_loss = loss_function(outputs, targets) ema_model_outputs = ema_model.module(inputs) ema_model_validation_loss = loss_function(ema_model_outputs, targets)
1D CNN，kernel 在 1 个方向移动，输入和输出是 2 维的，通常用于时序（Time-Series）数据2D CNN，kernel 在 2 个方向移动，输入和输出是 3 维的，通常用于图像数据3D CNN，kernel 在 3 个方向移动，输入和输出是4 维的，通用用于 3D 图像数据1D 卷积 - Conv1DConv1D，kernel 沿着一个维度进行滑动.比如，时间序列数据，如图：图：从传感器（accelerometer）获得的时间序列数据以人体穿戴在胳膊上的传感器所采集到的时序数据为例. 数据从 3 个轴表示，1D CNN 可以从传感器数据进行行为识别任务分析，如，人站立、行走、跳等. 该数据有 2 维，第一维是time-steps，另一个是在 3 个轴方向上的传感器值.kernel 在传感器数据上滑动的方式如下图，每一行表示某一轴的时间序列传感器数据. kernel仅能在时间轴方向上移动.''' https://pytorch.org/docs/stable/generated/torch.nn.Conv1d.html ''' import torch from torch import nn model = nn.Conv1d(in_channels=1, out_channels=10, kernel_size=5, stride=1) input = torch.randn(20, 1, 50) output = model(input)1D CNNs 被普遍应用于传感器数据，还可以用于音频和文本数据，因为其可以被表示为时序数据. 如图：2D 卷积 - Conv2DLenet-5 结构中首先提出标准CNN网络. Conv2D 普遍应用于图像数据. 其 2D 是因为，kernel 沿着数据的两个维度进行滑动，如图：CNN 的优势在于，采用 kernel 可以提取数据的空间特征. 例如，CNN 可以检测边缘、颜色分布等，其有助于网络对于图像分类的鲁棒性.''' https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html ''' import torch from torch import nn # With square kernels and equal stride model = nn.Conv2d(16, 33, 3, stride=2) # non-square kernels and unequal stride and with padding model = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2)) # non-square kernels and unequal stride and with padding and dilation model = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1)) input = torch.randn(20, 16, 50, 100) output = model(input)3D 卷积 - Conv3D3D CNN 中，kernel 沿 3 个维度滑动，如图：Conv3D 通常应用于 3D图像数据，如，MRI （Magnetic Resonance Imageing） 数据、CT（Computerized Tomography）扫描数据等.''' https://pytorch.org/docs/stable/generated/torch.nn.Conv3d.html ''' import torch from torch import nn # With square kernels and equal stride model = nn.Conv3d(16, 33, 3, stride=2) # non-square kernels and unequal stride and with padding model = nn.Conv3d(16, 33, (3, 5, 2), stride=(2, 1, 1), padding=(4, 2, 0)) input = torch.randn(20, 16, 10, 50, 100) output = model(input)
Pytorch 低版本环境(如，1.0) 加载高版本(如，1.10) 训练的模型，报错如下：xxx is a zip archive (did you mean to use torch.jit.load()?)原因：Pytorch1.6 版本后模型保存torch.save()参数变更，重新加上参数 _use_new_zipfile_serialization=False 保存，即可：https://pytorch.org/docs/stable/generated/torch.save.htmlimport torch #device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #model.load_state_dict(torch.load(save_path),map_location=device) state_dict = torch.load('pytorch_model.pth') state_dict = torch.load('pytorch_model.pth', map_location="cpu") #state_dict = torch.load('pytorch_model.bin', map_location="cpu") torch.save(state_dict, 'pytorch_model.pth', _use_new_zipfile_serialization=False) #torch.save(state_dict, 'pytorch_model.bin', _use_new_zipfile_serialization=False)
Pytorch1.9 released 里发布了一个 Inference Mode API(Beta).推理模式API ( Inference Mode API ) 可以显著加速推理工作负载的速度，同时保持安全，并确保永远不会计算不正确的梯度. 在不需要 autograd 时，其提供了最好的性能.inference_mode — PyTorch 1.9.0 documentation定义如：class torch.inference_mode(mode=True) #参数 mode: 是否开启推断模式InferenceMode 是类似与 no_grad 的上下文管理器(context manager)，主要用于确定不需要与 autograd 交互时使用. 这种模式下运行的代码，通过禁用试图跟踪(view tracking) 和版本计数器缓冲(version counter bumps) 来获得更好的性能.InferenceMode 上下文管理器是局部线程的(thread local)，其不会影响其他线程中的计算.InferenceMode 也起到装饰器的作用.使用示例:import torch x = torch.ones(1, 2, 3, requires_grad=True) with torch.inference_mode(): y = x * x # y.requires_grad # False y._version #Traceback (most recent call last): #File "<stdin>", line 1, in <module> #RuntimeError: Inference tensors do not track version counter. @torch.inference_mode() def func(x): return x * x out = func(x) out.requires_grad #False
原文：EINSUM IS ALL YOU NEED - EINSTEIN SUMMATION IN DEEP LEARNING - 2018.05.02译文：einsum满足你一切需要：深度学习中的爱因斯坦求和约定作者：TIM ROCKTASCHEL节选，学习.1. einsum 标记法1.1. 矩阵相乘比如，两个矩阵相乘，$A \in R^{I \times K}$ 和 $B \in R^{K \times J}$ ；再计算每列的和，最终得到矩阵 $c \in R^J$，其可以表达为：其中，表达式说明了每个元素 $c_i$ 的计算过程. 列向量 $A_{i:}$ 乘以行向量 $B_{:j}$，再求和.einsum 标记法中，隐式地省略了求和符号，而是累加重复的下标(如，$k$) 和输出中未标出的下标(如 $i$).1.2. 向量点积比如，两个向量 $a, b \in R^{J}$ 的点积，可以表达为：1.3. 高阶张量变换比如，深度学习常见的一种高阶张量(higher-order tensor)，其包含一个 batch 中有 N 个训练样本，每个样本是一个长度为 T 的 K 维词向量序列，期望将词向量投影到一个不同的维度 Q.记，张量为 $T \in R^{N \times T \times K}$，投影矩阵记为 $W \in R^{K \times Q}$，则，einsum 表达式为：1.4. 四阶张量变换比如，对于四阶张量 $T \in R^{N \times T \times K \times M}$，期望使用上述的投影矩阵 W 将第三维投影到 Q 维，并累加到第二维，再对结果中的第一维和最后一维进行转置，最终得到张量 $C \in R^{M \times Q \times N}$. einsum 表达式为：注，这里是通过交换下标 n 和 m ($C_{mqn}$ 而不是 $C_{nqm}$)，转置了张量结果.2. Numpy/PyTorch/TensorFlow 中 einsum 标记法NumPy - einsum 符号标记函数 - AIUAINumpy - np.einsumPyTorch - torch.einsumTensorFlow - tf.einsum表示形式均为：einsum(equation, operands) #equation - einsum 约定字符串 #operands - 张量序列如，1.1.矩阵相乘，$c_j = \sum _i \sum_k A_{ik} B_{kj}$，其表示形式如：equation = 'ik,kj -> j'Numpy/PyTorch/TensorFlow 支持 einsum 的好处在于，可以应用于神经网络架构中的任意计算图，且可以反向传播.典型 einsum 调用形式如：其中，方框是占位符，表示张量维度. 根据该式，可以推断，arg1 和 arg3 是矩阵，arg2 是三阶张量，einsum 计算结果 result 是矩阵.注意，einsum 处理的是可变数量的输入. 以下依PyTorch为例示例介绍.2.1. 矩阵转置import torch a = torch.arange(6).reshape(2, 3) #tensor([[0, 1, 2], # [3, 4, 5]]) b = torch.einsum('ij->ji', [a]) #tensor([[0, 3], # [1, 4], # [2, 5]])2.2. 求和import torch a = torch.arange(6).reshape(2, 3) b = torch.einsum('ij->', [a]) #tensor(15)2.3. 列求和import torch a = torch.arange(6).reshape(2, 3) b = torch.einsum('ij->j', [a]) #tensor([3, 5, 7])2.4. 行求和import torch a = torch.arange(6).reshape(2, 3) b = torch.einsum('ij->i', [a]) #tensor([ 3, 12])2.5. 矩阵-向量相乘import torch a = torch.arange(6).reshape(2, 3) b = torch.arange(3) #tensor([0, 1, 2]) c = torch.einsum('ik,k->i', [a, b]) #tensor([ 5, 14])2.6. 矩阵-矩阵相乘import torch a = torch.arange(6).reshape(2, 3) b = torch.arange(15).reshape(3, 5) #tensor([[ 0, 1, 2, 3, 4], # [ 5, 6, 7, 8, 9], # [10, 11, 12, 13, 14]]) c = torch.einsum('ik,kj->ij', [a, b]) #tensor([[ 25, 28, 31, 34, 37], # [ 70, 82, 94, 106, 118]])2.7. 向量点积import torch a = torch.arange(3) #tensor([0, 1, 2]) b = torch.arange(3,6) #tensor([3, 4, 5]) c = torch.einsum('i,i->', [a, b]) #tensor(14)2.8. 矩阵点积import torch a = torch.arange(6).reshape(2, 3) #tensor([[0, 1, 2], # [3, 4, 5]]) b = torch.arange(6,12).reshape(2, 3) #tensor([[ 6, 7, 8], # [ 9, 10, 11]]) c = torch.einsum('ij,ij->', [a, b]) #tensor(145)2.9. 哈达玛积(hadamard product)逐元素相乘import torch a = torch.arange(6).reshape(2, 3) b = torch.arange(6,12).reshape(2, 3) c = torch.einsum('ij,ij->ij', [a, b]) #tensor([[ 0, 7, 16], # [27, 40, 55]])2.10. 外积import torch a = torch.arange(3) #tensor([0, 1, 2]) b = torch.arange(3,7) #tensor([3, 4, 5, 6]) c = torch.einsum('i,j->ij', [a, b]) #tensor([[ 0, 0, 0, 0], # [ 3, 4, 5, 6], # [ 6, 8, 10, 12]])2.11. batch矩阵相乘import torch a = torch.randn(3,2,5) #tensor([[[ 0.5950, -0.5277, -2.9840, 1.2765, 0.7984], # [-0.6398, -1.2514, 0.7914, -0.0121, -1.7285]], # # [[ 0.2307, -1.4304, 1.4129, 1.5815, 0.9152], # [ 1.1122, -0.8018, -0.7850, -0.3227, -1.3101]], # # [[ 0.4733, 0.0346, 0.5624, -0.4903, -0.2846], # [ 0.5879, -2.5767, -0.9281, -0.2841, -0.7726]]]) b = torch.randn(3,5,3) #tensor([[[ 0.2185, 0.9919, 0.8251], # [ 1.2944, 0.1446, 1.6375], # [-0.3014, 0.7044, -0.6302], # [ 0.8771, 0.5083, 0.2780], # [-0.3134, -0.6291, -0.5817]], # # [[ 0.0386, 0.6317, -0.7736], # [-0.2040, -0.0580, 0.3656], # [ 0.3501, 0.1585, 3.0762], # [-0.3240, 1.7353, -0.6806], # [-0.2196, -3.0822, -0.3082]], # # [[ 0.4036, 0.3139, -0.5903], # [-0.3245, 1.5031, 0.4882], # [-0.5755, -0.4293, 1.4693], # [-0.4305, 0.5471, -1.6474], # [-0.1197, 0.1413, 1.1977]]]) c = torch.einsum('ijk,ikl->ijl', [a, b]) #torch.Size([3, 2, 3]) #tensor([[[ 1.2156, -1.4416, 1.3977], # [-1.4670, 0.8232, -2.0737]], # # [[ 0.0818, 0.3761, 2.2866], # [ 0.3240, 4.1028, -2.9448]], # # [[ 0.1012, -0.3493, 1.0307], # [ 1.8223, -3.5545, -3.4262]]])2.12. 张量缩约(tensor contraction)batch 矩阵相乘是 tensor contraction 特殊情况.比如两个张量，一个 n 阶张量 $A \in R^{I_1 \times \cdot \times I_n }$，一个 m 阶张量 $B \in R^{J_1 \times \cdot \times J_m}$. 举例来说，假设 n=4, m=5，且假定 $I_2 = J_3$ 且 $I_3 = J_5$.可以将这两个张量在这两个维度上相乘(A 张量的第 2、3 维度，B 张量的 3、5 维度)，最终得到一个新张量 $C \in R^{I1 \times I4 \times J1 \times J2 \times J4}$，如下式，import torch a = torch.randn(2,3,5,7) b = torch.randn(11,13,3,17,5) c = torch.einsum('pqrs,tuqvr->pstuv', [a, b]).shape #torch.Size([2, 7, 11, 13, 17])2.13. 双线性变换einsum 可用于超过两个张量的计算，如，双线性变换，import torch a = torch.randn(2,3) b = torch.randn(5,3,7) c = torch.randn(2,7) # d = torch.einsum('ik,jkl,il->ij', [a, b, c]) #tensor([[ 0.8701, -0.7686, 3.2942, 5.8559, -2.3225], # [ 0.9466, 0.5451, 0.4778, -0.4806, -1.8109]])3. 示例3.1. TreeQN比如，TreeQN 中等式 6 的实现. 给定网络层 $l$ 的低维状态表示 $z_l$ 以及每个激活函数 $a$ 的转换函数 $W^a$，期望计算采用了残差链接后的所有下一层状态 $z_{l+1}^a$：实际场景中，想要高效的地酸 batch 为 B 的 K 维状态表示 $Z \in R^{B \times K}$，并同时计算所有的转换函数(即，所有激活函数A)，可以将这些转换函数表示为一个张量 $W \in R^{A \times K \times K}$，并使用 einsum 标记法高效的计算下一层状态表示.import torch.nn.functional as F def random_tensors(shape, num=1, requires_grad=False): tensors = [torch.randn(shape, requires_grad=requires_grad) for i in range(0, num)] return tensors[0] if num == 1 else tensors #参数 #-- [num_actions x hidden_dimension] #-- [激活函数数 x 隐藏层维度] b = random_tensors([5, 3], requires_grad=True) #-- [num_actions x hidden_dimension x hidden_dimension] #-- [激活函数数 x 隐藏层维度 x 隐藏层维度] W = random_tensors([5, 3, 3], requires_grad=True) def transition(zl): #-- [batch大小 x 激活函数数 x 隐藏层维度] #-- [batch_size x num_actions x hidden_dimension] return zl.unsqueeze(1) + F.tanh(torch.einsum("bk,aki->bai", [zl, W]) + b) #随机生成输入 #-- [batch大小 x 隐藏层维度] zl = random_tensors([2, 3]) # out = transition(zl)3.2. Attention论文 Reasoning about Entailment with Neural Attention - ICLR2016 中注意力机制的等式 11- 13:einsum 标记法实现如：#参数 #-- [隐藏层维度] bM, br, w = random_tensors([7], num=3, requires_grad=True) #-- [隐藏层维度 x 隐藏层维度] WY, Wh, Wr, Wt = random_tensors([7, 7], num=4, requires_grad=True) #单次注意力机制 def attention(Y, ht, rt1): #-- [batch大小 x 隐藏层维度] tmp = torch.einsum("ik,kl->il", [ht, Wh]) + torch.einsum("ik,kl->il", [rt1, Wr]) Mt = F.tanh(torch.einsum("ijk,kl->ijl", [Y, WY]) + tmp.unsqueeze(1).expand_as(Y) + bM) #-- [batch大小 x 序列长度] at = F.softmax(torch.einsum("ijk,k->ij", [Mt, w])) #-- [batch大小 x 隐藏层维度] rt = torch.einsum("ijk,ij->ik", [Y, at]) + F.tanh(torch.einsum("ij,jk->ik", [rt1, Wt]) + br) #-- [batch大小 x 隐藏层维度], [batch大小 x 序列维度] return rt, at #随机生成输入 #-- [batch大小 x 序列长度 x 隐藏层维度] Y = random_tensors([3, 5, 7]) # -- [batch大小 x 隐藏层维度] ht, rt1 = random_tensors([3, 7], num=2) rt, at = attention(Y, ht, rt1) print(at) #打印注意力权重3.3. MocoMoCo: Momentum Contrast for Unsupervised Visual Representation Learning 中也有相应的实现.Pytorch 伪代码(部分)：q = f_q.forward(x_q) # queries: NxC k = f_k.forward(x_k) # keys: NxC k = k.detach() # no gradient to keys # positive logits: Nx1 l_pos = bmm(q.view(N,1,C), k.view(N,C,1)) # negative logits: NxK l_neg = mm(q.view(N,C), queue.view(C,K)) # logits: Nx(1+K) logits = cat([l_pos, l_neg], dim=1)Pytorch 中的实现 - moco/builder.py：# compute logits # Einstein sum is more intuitive # positive logits: Nx1 l_pos = torch.einsum('nc,nc->n', [q, k]).unsqueeze(-1) # negative logits: NxK l_neg = torch.einsum('nc,ck->nk', [q, self.queue.clone().detach()]) # logits: Nx(1+K) logits = torch.cat([l_pos, l_neg], dim=1)
Pytorch - 基于torch.distributed.all_gather的梯度反向传播[译]
原文：Gradient backpropagation with torch.distributed.all_gather - 2021.02.07作者：Jianfeng Wang本文主要是关于 torch.distributed.all_gather 的使用与如何确保梯度被正确计算.torch.distributed.all_gather 定义如：https://pytorch.org/docs/stable/_modules/torch/distributed/distributed_c10d.html#all_gatherdef all_gather(tensor_list, tensor, group=None, async_op=False): """ Gathers tensors from the whole group in a list. Complex tensors are supported. Args: tensor_list (list[Tensor]): Output list. It should contain correctly-sized tensors to be used for output of the collective. tensor (Tensor): Tensor to be broadcast from current process. group (ProcessGroup, optional): The process group to work on. If None, the default process group will be used. async_op (bool, optional): Whether this op should be an async op Returns: Async work handle, if async_op is set to True. None, if not async_op or if not part of the group Examples: >>> # All tensors below are of torch.int64 dtype. >>> # We have 2 process groups, 2 ranks. >>> tensor_list = [torch.zero(2, dtype=torch.int64) for _ in range(2)] >>> tensor_list [tensor([0, 0]), tensor([0, 0])] # Rank 0 and 1 >>> tensor = torch.arange(2, dtype=torch.int64) + 1 + 2 * rank >>> tensor tensor([1, 2]) # Rank 0 tensor([3, 4]) # Rank 1 >>> dist.all_gather(tensor_list, tensor) >>> tensor_list [tensor([1, 2]), tensor([3, 4])] # Rank 0 [tensor([1, 2]), tensor([3, 4])] # Rank 1 >>> # All tensors below are of torch.cfloat dtype. >>> # We have 2 process groups, 2 ranks. >>> tensor_list = [torch.zero(2, dtype=torch.cfloat) for _ in range(2)] >>> tensor_list [tensor([0.+0.j, 0.+0.j]), tensor([0.+0.j, 0.+0.j])] # Rank 0 and 1 >>> tensor = torch.tensor([1+1j, 2+2j], dtype=torch.cfloat) + 2 * rank * (1+1j) >>> tensor tensor([1.+1.j, 2.+2.j]) # Rank 0 tensor([3.+3.j, 4.+4.j]) # Rank 1 >>> dist.all_gather(tensor_list, tensor) >>> tensor_list [tensor([1.+1.j, 2.+2.j]), tensor([3.+3.j, 4.+4.j])] # Rank 0 [tensor([1.+1.j, 2.+2.j]), tensor([3.+3.j, 4.+4.j])] # Rank 1 """ _check_tensor_list(tensor_list, "tensor_list") _check_single_tensor(tensor, "tensor") if _rank_not_in_group(group): return tensor_list = [t if not t.is_complex() else torch.view_as_real(t) for t in tensor_list] tensor = tensor if not tensor.is_complex() else torch.view_as_real(tensor) if group is None: default_pg = _get_default_group() work = default_pg.allgather([tensor_list], [tensor]) else: work = group.allgather([tensor_list], [tensor]) if async_op: return work else: work.wait()1. all_gather 不进行梯度反向传播首先，torch.distributed.all_gather 本身是不会进行梯度的反向传播的. 如：test.py:batch_size = 16 rank = int(os.environ.get('OMPI_COMM_WORLD_RANK', '0')) world_size = int(os.environ.get('OMPI_COMM_WORLD_SIZE', '1')) bs_each = batch_size // world_size device_id = int(os.environ.get('OMPI_COMM_WORLD_LOCAL_RANK', '0')) torch.cuda.set_device(device_id) torch.distributed.init_process_group( backend='nccl', init_method='tcp://localhost:12345', rank=rank, world_size=world_size, ) # from torch import nn model = nn.Linear(1, 1, bias=False) model.weight.data[:] = 1. model = model.cuda() x = torch.ones((bs_each, 1), requires_grad=True).cuda() y = model(x) ys = [torch.zeros_like(y) for i in range(get_mpi_size())] # torch.distributed.all_gather(ys, y) print(y.grad_fn) #<MmBackward object at 0x7f2073fc3ba8> for sub_y in ys: print(sub_y.grad_fn) #None运行该代码，首先，其会打印出没采用 all_gather 的真正的梯度函数y.grad_fn. 然后，调用 all_gather 后，ys 的输出是没有 grad_fn 的，可以理解为其是没有梯度反向传播的.实际场景中，推荐采用 torch.no_grad() 封装 all_gather 函数，以显式地表明没有梯度进行反向传播.2. all_gather 如何反向传播由于 all_gather 是没有梯度反向传播的，但实际场景是否需要呢？如果需要的话，如何实现呢？典型设置是，每个 GPU 分别计算输出，基于所有 GPUs 的输出来计算损失函数，而不是每个 GPU 分别计算损失函数. 其可以确保：[1] - 不需要 all_gather 的梯度传播；[2] - 损失函数可以非常易于计算.具体实现如：假设 i-th GPU 的输出是 $x_i$，损失函数的计算取决于所有的 $x_i$，即：$$ f(x_1, x_2, ..., x_n) $$传统的每张 GPU 分别计算损失函数，其形式如：$$ f(x_1, x_2, ..., x_n) = \frac{1}{n} \sum_i g(x_i) $$每个GPU计算 $g(x)$ 损失，然后自动梯度计算(auto grad) 会计算所有参数的梯度. 一般其是与 DistributedDataParallel 一起的，自动进行求平均. 此时，是不需要收集其他 GPU 的输出的.但，如果损失函数不是分别梯度计算的呢？此时，对于每个 GPU，需要收集所有特征. 而，由于收集的输出是没有梯度(grad_fn)的，可以采用如下方式：with torch.no_grad(): all_x = [torch.zeros_like(x) for _ in range(world_size)] torch.distributed.all_gather(all_x, x) all_x[rank] = xall_x 包含了所有 GPUs 输出的 x. 所有的 x 都是没有 grad_fn 的，除了当前 GPU 输出的 x，因为 all_x[rank] = x.然后，即可基于 all_x 和 f 计算损失. 而，梯度计算为：$$ \frac{\partial f}{\partial x_i} \frac{\partial x_i}{\partial \theta} $$其中，$\theta$ 为网络参数. 注，仅对 $x_i$ 有梯度，而其他的 $x$ 是没有梯度的. 因为 all_gather 得到的是没有梯度的.结合 DistributedDataParallel，每个参数的梯度计算为：$$ \frac{1}{N} \sum_i \frac{\partial f}{\partial x_i} \frac{\partial x_i}{\partial \theta} $$其中，$N$ 为 world_size. 但，这并不是期望的. 因为目标损失函数是 $f$，而没有约数$N$. 对此，可以将每个 GPU 的计算作为 $Nf$，而不是 $f$.3. 总结虽然 all_gather 是没有梯度计算的，但可以有效的计算真正的梯度，主要步骤为：[1] - 采用 all_gather 收集所有网络的输出，然后，替换当前网络的输出，而当前网络的输出是有梯度的.[2] - 计算损失函数，并乘以 world_size.4. 相关材料Multi-gpu training:https://github.com/KevinMusgrave/pytorch-metric-learning/issues/10[1] - all_gather embeddings from all replicas.[2] - Because gathered tensors have no gradients, we overwrite the gathered embeddings tensor from the current replica with the embeddings tensor produced on that replica, which has gradients to the encoder.[3] - Concatenate the list of embeddings before computing the loss.实现的伪代码如：import torch import torch.distributed as dist # Dummy code representing the forward pass for some batch of text on one replica. embeddings = model(batch) # Gather the embeddings from every replica. embeddings_list = [torch.ones_like(embeddings) for _ in range(dist.get_world_size())] dist.all_gather(embeddings_list, embeddings) # Gathered tensors have no gradient, so we overwrite the gathered tensor for the current replica. # with the embeddings produced on this replica, which do have gradients. embeddings_list[dist.get_rank()] = embeddings # Finally, concatenate the list of embeddings before computing a loss. embeddings = torch.cat(embeddings_list) # I didn't demonstrate how to generate the labels, this will be task-dependent. loss = some_contrastive_loss(embeddings, labels)
Pytorch - DataParallel和DistributedDataParallel
基于多个 GPUs 的数据并行化处理，其基本思想是，每个 GPU 复制一个模型副本，分别对 batch 数据进行前向和后向计算. 后向计算的梯度发送到服务器，由服务器运行 reduce 操作，计算平均梯度. 然后，将平均梯度传输回 GPU，GPU 使用 SGD 更新模型参数. 采用数据并行和有效的网络通信库(如NCCL)，可以实现训练时间的几乎线性减少.Pytorch 关于数据并行有两种实现：nn.parallel.data_parallel 和 DistributedDataParallel.1. 数据加载并行化深度学习框架，如Pytorch 和 Tensorflow，网络训练从磁盘读取输入数据开始，一般包括四个步骤：[1] - 从磁盘加载数据到 host；[2] - 将数据从可分页的(pageable)数据传输到 host 的固定内存(pinned memory)；[3] - 将数据从固定内存传输到 GPU；[4] - 在 GPU 上进行前向和后向计算.这些步骤中的每一个都必须尽可能并行化，并且每当不存在数据依赖性时，下一个 batch 都应与当前 batch 进行管道化(pipelined).PyTorch 中的 Dataloader 提供了多进程(multi-process)从磁盘加载数据(设置num_works>0)以及多线程(multi-threaded) 将数据从可分页的(pageable)数据传输到 host 的固定内存(设置 pin_memory = True)的能力.对于大批量的数据，若仅有一个线程用于加载数据，则数据加载时间占主导地位，这意味着无论如何加快数据处理速度，性能都会受到数据加载时间的限制. 如图：如，Pytorch 中设置num_workers = 4以及pin_memory = True. 即可使用多进程(multiple processes)从磁盘读取不重叠的数据，并启动生产者-消费者线程(producer-consumer thread) 以将这些进程读取的数据从可分页的内存转移到固定内存.多进程可以更快的加载数据，且，当数据处理的时间足够长时，管道化数据加载几乎能够忽略数据加载延迟. 其原因是，处理当前 batch 数据的同时，下一 batch 的数据已经从磁盘读取并传输到固定内存里. 如果当前 batch 的处理时间足够长，下一 batch 的数据是立即可用的. 基于这种思想，有助于 num_workers 参数的选择，其应是，从磁盘读取 batch 数据的速度比 GPUs 处理当前 batch 数据的速度更快(但，也不能太高，因为多个进程也会浪费系统资源).如图，2. DataParallelnn.parallel.data_parallel2.1. 工作过程如图：DataParallel 的工作过程如：[1] - 将整个 mini-batch 数据加载到主线程(main thread);[2] - 在各 GPU 上分发(scatter) sub mini-batchs 的数据;[3] - 在各 GPU 上复制模型；[4] - 每个 GPU 分别在各自的线程对其 sub mini-batch 的数据进行前向计算;[5] - 在主 GPU 上收集(gather) 网络输出，并通过将网络输出与 batch 内每个元素的真实标签比较，计算损失函数；[6] - 分发损失函数到各 GPU，每个 GPU 进行后向传播，以计算梯度；[7] - 在主 GPU 上 reduce 梯度；[8] - 更新主 GPU 上的模型参数.以上即完成了一次迭代. 由于只在主 GPU 上更新了模型参数，因此，其他GPU上的模型现在并未同步，且必须在下一次迭代开始时，将模型参数广播到其他 GPU.nn.parallel.data_parallel.py 中 forward 函数的实现，对应如：不过，backwords 的实现是 C 的，2.2. 存在问题[1] - 冗余数据副本数据从主机(host) 复制到主GPU，然后将sub mini-batches 分发到其他GPU上[2] - 在前向计算前跨GPUs 进行模型复制由于模型参数是在主GPU上更新的，因此模型必须在每次前向计算开始时重新同步[3] - 每个 batch 的线程创建/销毁开销并行转发是在多个线程中实现的（这可能只是PyTorch问题）[4] - 梯度减少管道机会未利用(Gradient reduction pipelining opportunity left unexploited)Pytorch数据并行实现中，梯度下降发生在后向传播的末尾处.[5] - 在主GPU上不必要地收集模型输出output[6] - GPU利用率不均在主GPU上进行损失函数计算计算在主GPU 上进行梯度下降、参数更新2.3. DP 示例代码import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader # Parameters and DataLoaders input_size = 5 output_size = 2 batch_size = 30 data_size = 100 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # class RandomDataset(Dataset): def __init__(self, size, length): self.len = length self.data = torch.randn(length, size) def __getitem__(self, index): return self.data[index] def __len__(self): return self.len rand_loader = DataLoader( dataset=RandomDataset(input_size, data_size), batch_size=batch_size, shuffle=True) # class Model(nn.Module): def __init__(self, input_size, output_size): super(Model, self).__init__() self.fc = nn.Linear(input_size, output_size) def forward(self, input): output = self.fc(input) print("\tIn Model: input size", input.size(), "output size", output.size()) return output # model = Model(input_size, output_size) if torch.cuda.device_count() > 1: print("Let's use", torch.cuda.device_count(), "GPUs!") model = nn.DataParallel(model) #并行 model.to(device) #训练 for data in rand_loader: input = data.to(device) output = model(input) print("Outside: input size", input.size(), "output_size", output.size())3. DistributedDataParallel3.1. 工作过程如图：DistributedDataParallel 消除了 DataParallel 中上述不足. 其不再需要主 GPU，每个 GPU 分别进行各自任务. 每个 GPU 上的训练是其独立进程，而在 DataParallel 中是采用多线程(multi-thread) 的. DistributedDataParallel 的工作过程如，[1] - 从磁盘加载数据到 host 的page-locked内存. 采用多个 worker 进程并行地数据加载；其中，distributed data sampler 确保了加载的数据在跨进程间是不重叠的.[2] - 将 mini-batch 数据由 page-locked 内存转移到 GPU. 不需要任何数据广播. 因为每个 GPU 分别有模型副本，因此也不需要模型广播.[3] - 分别在各 GPU 独立进行前向计算和损失函数计算. 因此，也不需要收集各 GPUs 的输出.[4] - 后向梯度计算，梯度是跨GPUs all-reduced的. 确保在后向传播结束时，每个 GPU 最终得到相同的平均梯度的副本.[5] - 更新模型参数. 由于每个 GPU 是由相同的模型副本开始的，且梯度是 all-reduced 的，因此所有 GPUs 上的权重更新是相同的，无需再进行模型同步.以上即完成了一次迭代. 这种设计确保了模型参数的更新是相同的，因此消除了每次开始时的模型同步.其中，关于梯度计算的梯度 all-reduce，如图：一个网络层的梯度参数的计算，不再依赖于上一网络层的梯度.3.2. 多机多卡DistributedDataParallel 一般用于多机训练(multi-host)，每个 host 包含多 GPUs，各 host 之间通过网络进行通信. 默认是一个GPU上运行一个进程的操作. 可采用的配置如图：Pytoch 采用多机多卡的 DistributedDataParallel 涉及两个重要方面：[1] - init_process_group - 确保共享文件系统、网络通信同步等.torch.distributed.init_process_group( backend, init_method=None, timeout=datetime.timedelta(0, 1800), world_size=-1, rank=-1, store=None, group_name='')其中，涉及的一些概念：world_size: 全局进程数rank: 进程序号，用于进程间的通讯，表示进程优先级. rank=0 的 host 为 master 节点.local_rank: 进程内 GPU 编号，非显式参数，由torch.distributed.launch内部指定. 如，rank=3, local_rank=0 表示第3个进程内的第一块GPU.[2] - DistributedSampler - 确保每个进程加载不重叠的数据torch.utils.data.distributed.DistributedSampler( dataset, num_replicas=None, rank=None, shuffle=True, seed=0, drop_last=False)示例，假设多机设置为一个进程控制一个 GPU，有 2 个 host，且每个 host 有 3 GPUs. 各进程通过共享文件系统或数据副本进行数据访问. 为了数据的不重叠读取，每个进程必须要知道在进程组(process group) 中的进程数和在组中的rank. 这样，每个进程根据其自己的 rank 作为偏移，进程数作为步长，读取不重叠数据段(chunks). 其是通过 world_size 和 rank 来提供的. 如：#Host 1: python imagenet_distributed_data_parallel.py <location of dataset> --batch-size 256 --workers 16 --arch resnet50 \ --multiprocessing-distributed --world-size 2 --rank 0 --dist-url tcp://192.145.3.1:12345 #Host 2: python imagenet_distributed_data_parallel.py <location of dataset> --batch-size 256 --workers 16 --arch resnet50 \ --multiprocessing-distributed --world-size 2 --rank 1 --dist-url tcp://192.145.3.1:12345这里，world_size 是分布式系统中 hosts 数(2 个 hosts)，rank 是每个 host 的rank. 因此，进程总数的计算为： world_size x 每个 host 的GPUs数=2x3=6. 然后，每个进程的global rank(进程组内唯一) 计算为：local rank(GPU id) + 每个host的GPUs数 x host rank.如图：3.3. DDP 示例代码import torch import torch.distributed as dist import torch.multiprocessing as mp import torch.nn as nn import torch.optim as optim from torch.nn.parallel import DistributedDataParallel as DDP def example(rank, world_size): # create default process group dist.init_process_group("gloo", rank=rank, world_size=world_size) # create local model model = nn.Linear(10, 10).to(rank) # construct DDP model ddp_model = DDP(model, device_ids=[rank]) # define loss function and optimizer loss_fn = nn.MSELoss() optimizer = optim.SGD(ddp_model.parameters(), lr=0.001) # forward pass outputs = ddp_model(torch.randn(20, 10).to(rank)) labels = torch.randn(20, 10).to(rank) # backward pass loss_fn(outputs, labels).backward() # update parameters optimizer.step() def main(): world_size = 2 mp.spawn(example, args=(world_size,), nprocs=world_size, join=True) if __name__=="__main__": main()请注意，到目前为止，我们仅解决了从磁盘加载数据以及从可分页到固定内存的数据传输问题。从固定内存到GPU的数据传输（tensor.cuda()）也可以使用CUDA流进行流水线处理。现在将使用GPU网络检查数据并行处理。基本思想是，网络中的每个GPU使用模型的本地副本对一批数据进行正向和反向传播。反向传播期间计算出的梯度将发送到服务器，该服务器运行reduce归约操作以计算平均梯度。然后将平均梯度结果发送回GPU，GPU使用SGD更新模型参数。使用数据并行性和有效的网络通信软件库（例如NCCL），可以实现使训练时间几乎线性减少。数据并行的操作要求我们将数据划分成多份，然后发送给多个 GPU 进行并行的计算。注意：多卡训练要考虑通信开销的，是个trade off的过程，不见得四块卡一定比两块卡快多少，可能是训练到四块卡的时候通信开销已经占了大头下面是一个简单的示例。要实现数据并行，第一个方法是采用 nn.parallel 中的几个函数，分别实现的功能如下所示：复制(Replicate)：将模型拷贝到多个 GPU 上；分发(Scatter)：将输入数据根据其第一个维度(通常就是 batch 大小)划分多份，并传送到多个 GPU 上；收集(Gather)：从多个 GPU 上传送回来的数据，再次连接回一起；并行的应用(parallel_apply)：将第三步得到的分布式的输入数据应用到第一步中拷贝的多个模型上。参考[1] - Pytorch - 单机多卡数据并行DataParallel - AIUAI[2] - Distributed data parallel training using Pytorch on AWS - 2019.04.04[3] - pytorch(分布式)数据并行个人实践总结 - 2020.05.14
原文：7 tips for squeezing maximum performance from pytorch原文：7个提升PyTorch性能的技巧出处：AI公园 - 微信公众号作者：William Falcon编译：ronghuaiyang为了帮助训练得更快，这里有几个技巧，应该知道它们可能会减慢训练代码.1. 在DataLoader中使用workersdata_loader = DataLoader( dataset, collate_fn=collate_fn, batch_sampler=sampler, num_workers=NUM_WORKERS, #多进程 pin_memory=True, )PyTorch允许同时在多个进程上加载数据. PyTorch 可以通过处理 NUM_WORKERS 个批次绕过GIL锁，每个批次在一个单独的进程上。应该使用多少workers？一个好的经验法则是:num_worker = 4 * num_GPU可以参考：Guidelines for assigning num_workers to DataLoader缺点是，内存使用也会增加.2. 设置 Pin memory有时候GPU显存显示是满的，但模型并没有使用那么多，这种开销称为 pinned memory. 这个内存被保留为一种 “working allocation” 类型.当在一个DataLoader中启用pinned_memory时，它自动将获取的数据张量放在 pinned memory 中，并使数据更快地传输到CUDA-enabled的gpu.图：How to Optimize Data Transfers in CUDA C/C++这意味着不需要不必要的去调用：torch.cuda.empty_cache()3. 避免CPU与GPU间的传输# bad.cpu() .item() .numpy()大量使用 .item() 或 .cpu() 或 .numpy() 调用，对于性能来说是非常糟糕的，因为每个调用都将数据从GPU 传输到 CPU，从而极大地降低了性能.如果试图清除附加的计算图，请使用 .detach() .# good.detach()其不会将内存转移到GPU，会删除任何附加到该变量的计算图.4. 直接在GPUs上构建张量大多数人都是这样在GPUs上创建张量的:t = tensor.rand(2,2).cuda()然而，这首先创建CPU张量，然后将其转移到GPU……这真的很慢. 相反，直接在想要的设备上创建张量：t = tensor.rand(2,2, device=torch.device('cuda:0'))5. 使用DistributedDataParallel使用DistributedDataParallel不要使用DataParallel.PyTorch有两个主要的模式用于在多 GPUs训练：第一种是DataParallel，它将一批数据分割到多个GPUs上. 但这也意味着模型必须复制到每个GPU上，一旦在GPU 0 上计算出梯度，它们必须同步到其他GPU. 这需要大量昂贵的GPU传输. 第二种是，DistributedDataParallel在每个GPU(在它自己的进程中)上创建模型副本，并且只让数据的一部分对该GPU可用. 这就像是让N个独立的模型进行训练，除了一旦每个模型都计算出梯度，它们就会在模型之间同步梯度. 这意味着在每批处理中只在GPUs之间传输一次数据.6. 使用16-bit精度这是另一种加快训练速度的方法，实际上现在还没有很多人使用这种方法.模型训练采用 16bit，数据从32位变到到16位，其有几个优点:[1] - 使用了一半的内存(这意味着可以将batch大小翻倍，并将训练时间减半)[2] - 某些GPU(V100, 2080Ti)可以自动加速(3 -8倍)，因为它们针对16位计算进行了优化.注：在PyTorch 1.6之前，还必须安装Nvidia Apex，现在16位是PyTorch的原生版本.7. 对代码进行Profile如果没有PyTorch-Lightning，最后一条建议可能很难实现，但可以使用cprofiler这样的工具来实现.然而，在 PyTorch-Lightning 中，可以通过两种方式获得所有在训练期间所做的调用的总结：第一种是，内置的basic profiler:Trainer(profile=True)可以给出这样的输出：第二种是，高级的profiler：profiler = AdvancedProfiler() trainer = Trainer(profiler=profiler)得到更细粒度的结果：