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 如，
1. 分布式训练的基本概念分布式训练 - Colossal-AI分布式训练需要多台机器/GPU. 在模型训练期间，这些设备之间会有通信. 为了更好地理解分布式训练，有几个重要的术语需要了解清楚:host: 主机(host)是通信网络中的主要设备. 在初始化分布式环境时，经常需要它作为一个参数。port: 这里的端口(port)主要是指主机上用于通信的主端口.rank: 在网络中赋予设备的唯一ID.world size: 网络中设备的数量.process group: 进程组(process group)是一个通信网络，包括设备的一个子集。总是有一个默认的进程组，它包含所有的设备. 一个子集的设备可以形成一个进程组，以便它们只在组内的设备之间进行通信.示例如图：为了说明这些概念，假设有2台机器（也称为节点Node），每台机器有4个 GPU. 当在这两台机器上初始化分布式环境时，基本上启动了8个进程（每台机器上有4个进程），每个进程被绑定到一个 GPU 上.在初始化分布式环境之前，需要指定主机（主地址）和端口（主端口）.这里，可以让主机为节点0，端口为一个数字，如29500. 所有的8个进程将寻找地址和端口并相互连接，默认的进程组将被创建. 默认进程组的 world size 为8，细节如下:process IDrankNode indexGPU index00001101220233034410551166127713还可以创建一个新的进程组. 这个新的进程组可以包含任何进程的子集. 例如，可以创建一个只包含偶数进程的组:process IDrankNode indexGPU index0000210242106312注意，rank 是相对于进程组而言的，一个进程在不同的进程组中可以有不同的 rank. 最大的 rank 始终是 world size of the process group - 1.在进程组中，各进程可以通过两种方式进行通信.point-to-point: 一个进程向另一个进程发送数据.collective: 一组进程一起执行分散、聚集、all-reduce、广播等操作.1.1. point-to-point communication应用如，Baidu’s DeepSpeech 和 Facebook’s large-scale experiments.1.2. Collective Communication2. Broadcast/Scatter/Gatter/Reduce/All-reduceBroadcast,Scatter,Gather,Reduce,All-reduce分别是什么？ - 2019.09.102.1. Broadcast把同一份数据分发广播给所有人.2.2. Scatter不同于Broadcast， scatter可以将不同数据分发给不同的进程.2.3. Gather把多个进程的数据拼凑在一起.2.4. Reduce将多个进程中的数据按照指定的映射函数进行运算得到最后的结果存在一个进程中，例如下面两个图中的归约(Reduce)操作都是求和，将4个不同进程的数据归约求和后存在了第一个进程.2.5. All-reduceAll-reduce与Reduce的区别就在于后者最后的结果是只保存在一个进程中，而All-reduce需要每个进程都有同样的结果. 所以All-reduce 一般包含 scatter操作，所以有时候也会看到 reduce-scatter 这种说法，其实reduce-scatter可以看成是all reduce的一种实现方式.3. torch.distributedPYTORCH DISTRIBUTED OVERVIEWPyTorch v1.6.0 中 torch.distributed 包含三种：[1] - Distributed Data-Parallel Training (DDP) DDP 是被普遍采用的单程序多数据训练范式( single-program multiple-data training paradigm).采用 DDP 时，模型被复制到每个进程(process)，然后每个模型副本被输入不同的输入数据样本.DDP 负责梯度通信(gradient communication)，以保持模型副本的同步，并将其与梯度计算重叠，以加快训练速度.[2] - RPC-Based Distributed Training (RPC)RPC 支持无法适应数据并行训练的通用训练结构，例如分布式管道并行(distributed pipeline parallelism)、参数服务器范式(parameter server paradigm)、以及 DDP与其他训练范式的组合.RPC 有助于管理远程对象的生命周期，并将自动梯度引擎(autograd engine)扩展到机器边界之外.[3] - Collective Communication (c10d)c10d 库支持跨进程以组(group)的形式发送 tensor. 其同时提供了 collective communication APIs (e.g., all_reduce 与 all_gather) 和 P2P communication APIs (e.g., send 与 isend). DDP 和 RPC 都是建立在 c10d 之上，DDP 采用了 collective communication；RPC 采用了 P2P communication.
https://github.com/open-mmlab/mmcv/blob/master/mmcv/utils/progressbar.py1. 计时器 Timertimer.py:from time import time class TimerError(Exception): def __init__(self, message): self.message = message super(TimerError, self).__init__(message) class Timer: def __init__(self, start=True, print_tmpl=None): self._is_running = False self.print_tmpl = print_tmpl if print_tmpl else '{:.3f}' if start: self.start() @property def is_running(self): """bool: indicate whether the timer is running""" return self._is_running def __enter__(self): self.start() return self def __exit__(self, type, value, traceback): print(self.print_tmpl.format(self.since_last_check())) self._is_running = False def start(self): """Start the timer.""" if not self._is_running: self._t_start = time() self._is_running = True self._t_last = time() def since_start(self): """Total time since the timer is started. Returns: float: Time in seconds. """ if not self._is_running: raise TimerError('timer is not running') self._t_last = time() return self._t_last - self._t_start def since_last_check(self): """Time since the last checking. Either :func:`since_start` or :func:`since_last_check` is a checking operation. Returns: float: Time in seconds. """ if not self._is_running: raise TimerError('timer is not running') dur = time() - self._t_last self._t_last = time() return dur示例，如：from .timer import Timer timer_ins = Timer(start=True) print(timer_ins.since_start()) print(timer_ins.since_last_check()) print(timer_ins.since_start())2. 进度条 ProgressBar#!/usr/bin/python3 #!--*-- coding: utf-8 --*-- import sys from shutil import get_terminal_size from .timer import Timer class ProgressBar: """ A progress bar which can print the progress. """ def __init__(self, task_num=0, bar_width=50, start=True, file=sys.stdout): self.task_num = task_num self.bar_width = bar_width self.completed = 0 self.file = file if start: self.start() @property def terminal_width(self): width, _ = get_terminal_size() return width def start(self): if self.task_num > 0: self.file.write(f'[{" " * self.bar_width}] 0/{self.task_num}, ' 'elapsed: 0s, ETA:') else: self.file.write('completed: 0, elapsed: 0s') self.file.flush() self.timer = Timer() def update(self, num_tasks=1): assert num_tasks > 0 self.completed += num_tasks elapsed = self.timer.since_start() if elapsed > 0: fps = self.completed / elapsed else: fps = float('inf') if self.task_num > 0: percentage = self.completed / float(self.task_num) eta = int(elapsed * (1 - percentage) / percentage + 0.5) msg = f'\r[{{}}] {self.completed}/{self.task_num}, ' \ f'{fps:.1f} task/s, elapsed: {int(elapsed + 0.5)}s, ' \ f'ETA: {eta:5}s' bar_width = min(self.bar_width, int(self.terminal_width - len(msg)) + 2, int(self.terminal_width * 0.6)) bar_width = max(2, bar_width) mark_width = int(bar_width * percentage) bar_chars = '>' * mark_width + ' ' * (bar_width - mark_width) self.file.write(msg.format(bar_chars)) else: self.file.write( f'completed: {self.completed}, elapsed: {int(elapsed + 0.5)}s,' f' {fps:.1f} tasks/s') self.file.flush()3. 任务执行进度条from collections.abc import Iterable def track_progress(func, tasks, bar_width=50, file=sys.stdout, **kwargs): """ Track the progress of tasks execution with a progress bar. Tasks are done with a simple for-loop. Args: func (callable): The function to be applied to each task. tasks (list or tuple[Iterable, int]): A list of tasks or (tasks, total num). bar_width (int): Width of progress bar. Returns: list: The task results. """ if isinstance(tasks, tuple): assert len(tasks) == 2 assert isinstance(tasks[0], Iterable) assert isinstance(tasks[1], int) task_num = tasks[1] tasks = tasks[0] elif isinstance(tasks, Iterable): task_num = len(tasks) else: raise TypeError( '"tasks" must be an iterable object or a (iterator, int) tuple') prog_bar = ProgressBar(task_num, bar_width, file=file) results = [] for task in tasks: results.append(func(task, **kwargs)) prog_bar.update() prog_bar.file.write('\n') return results4. 多进程任务执行进度条from collections.abc import Iterable from multiprocessing import Pool def init_pool(process_num, initializer=None, initargs=None): if initializer is None: return Pool(process_num) elif initargs is None: return Pool(process_num, initializer) else: if not isinstance(initargs, tuple): raise TypeError('"initargs" must be a tuple') return Pool(process_num, initializer, initargs) def track_parallel_progress(func, tasks, nproc, initializer=None, initargs=None, bar_width=50, chunksize=1, skip_first=False, keep_order=True, file=sys.stdout): """ Track the progress of parallel task execution with a progress bar. The built-in :mod:`multiprocessing` module is used for process pools and tasks are done with :func:`Pool.map` or :func:`Pool.imap_unordered`. Args: func (callable): The function to be applied to each task. tasks (list or tuple[Iterable, int]): A list of tasks or (tasks, total num). nproc (int): Process (worker) number. initializer (None or callable): Refer to :class:`multiprocessing.Pool` for details. initargs (None or tuple): Refer to :class:`multiprocessing.Pool` for details. chunksize (int): Refer to :class:`multiprocessing.Pool` for details. bar_width (int): Width of progress bar. skip_first (bool): Whether to skip the first sample for each worker when estimating fps, since the initialization step may takes longer. keep_order (bool): If True, :func:`Pool.imap` is used, otherwise :func:`Pool.imap_unordered` is used. Returns: list: The task results. """ if isinstance(tasks, tuple): assert len(tasks) == 2 assert isinstance(tasks[0], Iterable) assert isinstance(tasks[1], int) task_num = tasks[1] tasks = tasks[0] elif isinstance(tasks, Iterable): task_num = len(tasks) else: raise TypeError( '"tasks" must be an iterable object or a (iterator, int) tuple') pool = init_pool(nproc, initializer, initargs) start = not skip_first task_num -= nproc * chunksize * int(skip_first) prog_bar = ProgressBar(task_num, bar_width, start, file=file) results = [] if keep_order: gen = pool.imap(func, tasks, chunksize) else: gen = pool.imap_unordered(func, tasks, chunksize) for result in gen: results.append(result) if skip_first: if len(results) < nproc * chunksize: continue elif len(results) == nproc * chunksize: prog_bar.start() continue prog_bar.update() prog_bar.file.write('\n') pool.close() pool.join() return results
原文：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)
出处：Paddle文档平台 - 计算机视觉与卷积神经网络1. 计算机视觉综述计算机视觉作为一门让机器学会如何去“看”的学科，具体的说，就是让机器去识别摄像机拍摄的图片或视频中的物体，检测出物体所在的位置，并对目标物体进行跟踪，从而理解并描述出图片或视频里的场景和故事，以此来模拟人脑视觉系统。因此，计算机视觉也通常被叫做机器视觉，其目的是建立能够从图像或者视频中“感知”信息的人工系统。计算机视觉技术经过几十年的发展，已经在交通（车牌识别、道路违章抓拍）、安防（人脸闸机、小区监控）、金融（刷脸支付、柜台的自动票据识别）、医疗（医疗影像诊断）、工业生产（产品缺陷自动检测）等多个领域应用，影响或正在改变人们的日常生活和工业生产方式。未来，随着技术的不断演进，必将涌现出更多的产品和应用，为我们的生活创造更大的便利和更广阔的机会。图1 计算机视觉技术在各领域的应用2. 计算机视觉的发展历程计算机视觉的发展历程要从生物视觉讲起。对于生物视觉的起源，目前学术界尚没有形成定论。有研究者认为最早的生物视觉形成于距今约7亿年前的水母之中，也有研究者认为生物视觉产生于距今约5亿年前寒武纪。寒武纪生物大爆发的原因一直是个未解之谜，不过可以肯定的是在寒武纪动物具有了视觉能力，捕食者可以更容易地发现猎物，被捕食者也可以更早的发现天敌的位置。视觉系统的形成有力地推动了食物链的演化，加速了生物进化过程，是生物发展史上重要的里程碑。经过几亿年的演化，目前人类的视觉系统已经具备非常高的复杂度和强大的功能，人脑中神经元数目达到了1000亿个，这些神经元通过网络互相连接，这样庞大的视觉神经网络使得我们可以很轻松的观察周围的世界，如 图2 所示。图2 人类视觉感知对人类来说，识别猫和狗是件非常容易的事。但对计算机来说，即使是一个精通编程的高手，也很难轻松写出具有通用性的程序（比如：假设程序认为体型大的是狗，体型小的是猫，但由于拍摄角度不同，可能一张图片上猫占据的像素比狗还多）。那么，如何让计算机也能像人一样看懂周围的世界呢？研究者尝试着从不同的角度去解决这个问题，由此也发展出一系列的子任务，如 图3 所示。图3 计算机视觉子任务示意图(a) Image Classification： 图像分类，用于识别图像中物体的类别（如：bottle、cup、cube）。(b) Object Localization： 目标检测，用于检测图像中每个物体的类别，并准确标出它们的位置。(c) Semantic Segmentation： 语义分割，用于标出图像中每个像素点所属的类别，属于同一类别的像素点用一个颜色标识。(d) Instance Segmentation： 实例分割，值得注意的是，目标检测任务只需要标注出物体位置，而实例分割任务不仅要标注出物体位置，还需要标注出物体的外形轮廓。这里以图像分类任务为例，为大家介绍计算机视觉技术的发展历程。在早期的图像分类任务中，通常是先人工提取图像特征，再用机器学习算法对这些特征进行分类，分类的结果强依赖于特征提取方法，往往只有经验丰富的研究者才能完成，如 图4 所示。图4 早期的图像分类任务在这种背景下，基于神经网络的特征提取方法应运而生。Yann LeCun是最早将卷积神经网络应用到图像识别领域的，其主要逻辑是使用卷积神经网络提取图像特征，并对图像所属类别进行预测，通过训练数据不断调整网络参数，最终形成一套能自动提取图像特征并对这些特征进行分类的网络，如 图5 所示。图5 早期的卷积神经网络处理图像任务示意这一方法在手写数字识别任务上取得了极大的成功，但在接下来的时间里，却没有得到很好的发展。其主要原因一方面是数据集不完善，只能处理简单任务，在大尺寸的数据上容易发生过拟合；另一方面是硬件瓶颈，网络模型复杂时，计算速度会特别慢。目前，随着互联网技术的不断进步，数据量呈现大规模的增长，越来越丰富的数据集不断涌现。另外，得益于硬件能力的提升，计算机的算力也越来越强大。不断有研究者将新的模型和算法应用到计算机视觉领域。由此催生了越来越丰富的模型结构和更加准确的精度，同时计算机视觉所处理的问题也越来越丰富，包括分类、检测、分割、场景描述、图像生成和风格变换等，甚至还不仅仅局限于2维图片，包括视频处理技术和3D视觉等。3. 卷积神经网络卷积神经网络是目前计算机视觉中使用最普遍的模型结构。图6 是一个典型的卷积神经网络结构，多层卷积和池化层组合作用在输入图片上，在网络的最后通常会加入一系列全连接层，ReLU激活函数一般加在卷积或者全连接层的输出上，网络中通常还会加入Dropout来防止过拟合。图6 卷积神经网络经典结构卷积层：卷积层用于对输入的图像进行特征提取。卷积的计算范围是在像素点的空间邻域内进行的，因此可以利用输入图像的空间信息。卷积核本身与输入图片大小无关，它代表了对空间邻域内某种特征模式的提取。比如，有些卷积核提取物体边缘特征，有些卷积核提取物体拐角处的特征，图像上不同区域共享同一个卷积核。当输入图片大小不一样时，仍然可以使用同一个卷积核进行操作。池化层：池化层通过对卷积层输出的特征图进行约减，实现了下采样。同时对感受域内的特征进行筛选，提取区域内最具代表性的特征，保留特征图中最主要的信息。激活函数：激活函数给神经元引入了非线性因素，对输入信息进行非线性变换，从而使得神经网络可以任意逼近任何非线性函数，然后将变换后的输出信息作为输入信息传给下一层神经元。全连接层：全连接层用于对卷积神经网络提取到的特征进行汇总，将多维的特征映射为二维的输出。其中，高维代表样本批次大小，低维代表分类或回归结果。
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)