PyTorch 模型训练实用教程 PDF
Document Details
Uploaded by BestSellingViolet895
余霆嵩
Tags
Summary
这是一本关于PyTorch模型训练的实用教程,作者余霆嵩。教程涵盖了模型训练的各个方面,包括数据预处理、数据增强、模型定义、权重初始化、模型微调、学习率调整策略以及可视化工具的使用,旨在帮助读者掌握清晰的模型训练结构,并通过可视化诊断模型问题。
Full Transcript
PyTorch 模型训练实用教程 作者:余霆嵩 PyTorch 模型训练实用教程 前言: 自 2017 年 1 月 PyTorch 推出以来,其热度持续上升,一度有赶超 TensorFlow 的趋势。PyTorch 能在短时间内被众多研究人员和工程师接受并推 崇是因为其有着诸多优点,如采用 Python 语言、动态图机制、网络构建灵活...
PyTorch 模型训练实用教程 作者:余霆嵩 PyTorch 模型训练实用教程 前言: 自 2017 年 1 月 PyTorch 推出以来,其热度持续上升,一度有赶超 TensorFlow 的趋势。PyTorch 能在短时间内被众多研究人员和工程师接受并推 崇是因为其有着诸多优点,如采用 Python 语言、动态图机制、网络构建灵活以 及拥有强大的社群等。因此,走上学习 PyTorch 的道路已刻不容缓。 本教程以实际应用、工程开发为目的,着重介绍模型训练过程中遇到的实 际问题和方法。如上图所示,在机器学习模型开发中,主要涉及三大部分,分 别是数据、模型和损失函数及优化器。本文也按顺序的依次介绍数据、模型和 损失函数及优化器,从而给大家带来清晰的机器学习结构。 通过本教程,希望能够给大家带来一个清晰的模型训练结构。当模型训练 遇到问题时,需要通过可视化工具对数据、模型、损失等内容进行观察,分析 并定位问题出在数据部分?模型部分?还是优化器?只有这样不断的通过可视 化诊断你的模型,不断的对症下药,才能训练出一个较满意的模型。 为什么写此教程: 前几年一直在用 Caffe 和 MatConvNet,近期转 PyTorch。当时只想快速地 用上 PyTorch 进行模型开发,然而搜了一圈 PyTorch 的教程,并没有找到一款 I 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 适合的。很多 PyTorch 教程是从学习机器学习(深度学习)的角度出发,以 PyTorch 为工具进行编写,里面介绍很多模型,并且附上模型的 demo。 然而,工程应用开发中所遇到的问题并不是跑一个模型的 demo 就可以的, 模型开发需要对数据的预处理、数据增强、模型定义、权值初始化、模型 Finetune、学习率调整策略、损失函数选取、优化器选取、可视化等等。鉴于 此,我只能自己对着官方文档,一步一步地学习。 起初,只是做了一些学习笔记,后来觉得这些内容应该对大家有些许帮 助,毕竟在互联网上很难找到这类内容的分享,于是此教程就诞生了。 本教程内容及结构: 本教程内容主要为在 PyTorch 中训练一个模型所可能涉及到的方法及函 数,并且对 PyTorch 提供的数据增强方法(22 个)、权值初始化方法(10 个)、损失函数(17 个)、优化器(6 个)及 tensorboardX 的方法(13 个) 进行了详细介绍。 本教程分为四章,结构与机器学习三大部分一致。 第一章,介绍数据的划分,预处理,数据增强; 第二章,介绍模型的定义,权值初始化,模型 Finetune; 第三章,介绍各种损失函数及优化器; 第四章,介绍可视化工具,用于监控数据、模型权及损失函数的变化。 本教程适用读者: 1.想熟悉 PyTorch 使用的朋友; 2.想采用 PyTorch 进行模型训练的朋友; 3.正采用 PyTorch,但无有效机制去诊断模型的朋友; 干货直达: 1.6 transforms 的二十二个方法 2.2 权值初始化的十种方法 3.1 PyTorch 的十七个损失函数 3.3 PyTorch 的十个优化器 3.4 PyTorch 的六个学习率调整方法 4.1 TensorBoardX 项目代码:https://github.com/tensor-yu/PyTorch_Tutorial II 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 意见反馈:[email protected] 学习交流 QQ 群:为了更好的帮助大家学习和理解 PyTorch 以及机器学 习相关知识,特建立一个 QQ 群,供大家交流,本文的最新修改也会同步到 QQ 群及 GitHub。QQ 群号:671103375 修改记录: 版本 日期 修改内容 1. 1.6 小节勘误,将 36*36 改为 40*40; 2. 2.3 小节删除注释; 0.0.5 2018.12.31 夜 3. 修改权值初始化杂谈 中的理解错误; 4. 全文代码缩进。 III 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 目录 第一章 数 据........................................................ 1 1.1 Cifar10 转 png................................................ 1 1.2 训练集、验证集和测试集的划分................................ 2 1.3 让 PyTorch 能读你的数据集.................................... 2 1.4 图片从硬盘到模型............................................ 5 1.5 数据增强 与 数据标准化...................................... 7 1.6 transforms 的二十二个方法.................................... 10 1.随机裁剪:transforms.RandomCrop........................... 11 2.中心裁剪:transforms.CenterCrop............................. 12 3.随机长宽比裁剪 transforms.RandomResizedCrop................ 12 4.上下左右中心裁剪:transforms.FiveCrop...................... 12 5.上下左右中心裁剪后翻转: transforms.TenCrop.................. 13 6.依概率 p 水平翻转 transforms.RandomHorizontalFlip............. 13 7.依概率 p 垂直翻转 transforms.RandomVerticalFlip............... 13 8.随机旋转:transforms.RandomRotation........................ 13 9.resize:transforms.Resize.................................... 14 10.标准化:transforms.Normalize............................... 14 11.转为 tensor:transforms.ToTensor............................ 14 12.填充:transforms.Pad...................................... 14 13.修改亮度、对比度和饱和度:transforms.ColorJitter............ 15 14.转灰度图:transforms.Grayscale............................. 15 15.线性变换:transforms.LinearTransformation().................. 15 16.仿射变换:transforms.RandomAffine......................... 15 17.依概率 p 转为灰度图:transforms.RandomGrayscale............ 16 18.将数据转换为 PILImage:transforms.ToPILImage.............. 16 19.transforms.Lambda........................................ 16 20.transforms.RandomChoice(transforms)......................... 16 21.transforms.RandomApply(transforms, p=0.5).................... 16 22.transforms.RandomOrder.................................... 16 第二章 模 型....................................................... 17 IV 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 2.1 模型的搭建................................................. 17 2.1.1 模型定义的三要素...................................... 17 2.1.2 模型定义多说两句...................................... 18 2.1.3 nn.Sequetial............................................ 21 2.2 权值初始化的十种方法....................................... 22 2.2.1 权值初始化流程........................................ 22 2.2.2 常用初始化方法........................................ 23 1. Xavier 均匀分布........................................... 24 2. Xavier 正态分布........................................... 24 3. kaiming 均匀分布......................................... 24 4. kaiming 正态分布......................................... 25 5. 均匀分布初始化.......................................... 25 6. 正态分布初始化.......................................... 25 7. 常数初始化.............................................. 25 8. 单位矩阵初始化.......................................... 25 9. 正交初始化.............................................. 26 10. 稀疏初始化............................................. 26 11. 计算增益............................................... 26 权值初始化杂谈............................................. 26 2.3 模型 Finetune.............................................. 27 第三章 损失函数与优化器............................................ 31 3.1 PyTorch 的十七个损失函数.................................... 31 1. L1loss................................................... 31 2. MSELoss................................................. 31 3. CrossEntropyLoss.......................................... 32 4. NLLLoss................................................. 33 5. PoissonNLLLoss........................................... 34 6. KLDivLoss............................................... 35 7. BCELoss................................................. 36 8. BCEWithLogitsLoss........................................ 37 9. MarginRankingLoss........................................ 37 V 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 10. HingeEmbeddingLoss...................................... 38 11. MultiLabelMarginLoss..................................... 38 12. SmoothL1Loss........................................... 39 13. SoftMarginLoss........................................... 40 14. MultiLabelSoftMarginLoss.................................. 40 15. CosineEmbeddingLoss..................................... 41 16. MultiMarginLoss......................................... 41 17. TripletMarginLoss........................................ 42 3.2 优化器基类:Optimizer...................................... 43 3.3 PyTorch 的十个优化器....................................... 45 1. torch.optim.SGD......................................... 45 2. torch.optim.ASGD........................................ 46 3. torch.optim.Rprop....................................... 46 4. torch.optim.Adagrad..................................... 47 5. torch.optim.Adadelta.................................... 47 6. torch.optim.RMSprop..................................... 47 7. torch.optim.Adam(AMSGrad)............................... 48 8. torch.optim.Adamax...................................... 48 9. torch.optim.SparseAdam.................................. 48 10.torch.optim.LBFGS....................................... 49 3.4 PyTorch 的六个学习率调整方法............................... 49 1. lr_scheduler.StepLR..................................... 49 2. lr_scheduler.MultiStepLR................................ 50 3. lr_scheduler.ExponentialLR.............................. 50 4. lr_scheduler.CosineAnnealingLR.......................... 50 5. lr_scheduler.ReduceLROnPlateau.......................... 52 6. lr_scheduler.LambdaLR................................... 53 学习率调整小结............................................. 54 step 源码阅读.............................................. 55 第四章 监控模型——可视化........................................... 57 4.1 TensorBoardX............................................... 57 1. add_scalar()............................................ 58 VI 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 2. add_scalars()........................................... 59 3. add_histogram()......................................... 60 4. add_image()............................................. 62 补充 torchvision.utils.make_grid()......................... 62 5. add_graph()............................................. 63 6. add_embedding()......................................... 64 7. add_text().............................................. 65 8. add_video()............................................. 66 9. add_figure()............................................ 66 10. add_image_with_boxes()................................. 66 11. add_pr_curve()......................................... 66 12. add_pr_curve_raw()..................................... 66 13. export_scalars_to_json()............................... 66 4.2 卷积核可视化............................................... 67 4.3 特征图可视化............................................... 69 4.4 梯度及权值分布可视化....................................... 71 4.5 混淆矩阵及其可视化......................................... 75 VII 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 第一章 数 据 1.1 Cifar10 转 png 俗话说得好,巧妇难为无米之炊,若没有数据,我们什么也做不了。在本 教程中,为了统一大家的数据,这里采用 cifar-10 的测试集,共 10000 张图片 作为源数据,模拟真实场景中的数据。 第一步:下载 cifar-10-python.tar.gz 下载 cifar-10-python.tar.gz,存放到 /Data 文件夹下,并且解压,获得文件 夹/Data/cifar-10-batches-py/ 下载方式: 1. 官网:http://www.cs.toronto.edu/~kriz/cifar.html 2. 百度云: https://pan.baidu.com/s/1NGV8g2iBAhHwjQZWTjGEbg 提取 码: p3rh 第二步:运行 1_1_cifar10_to_png.py 运行代码:Code/1_data_prepare/1_1_cifar10_to_png.py 可在文件夹 Data/cifar-10-png/raw_test/下看到 0-9 个文件夹,对应 9 个类别。 脚本中未将训练集解压出来,这里只是为了实验,因此未使用过多的数据。这里仅将 测试集中的 10000 张图片解压出来,作为原始图片,将从这 10000 张图片中划分出训练集 (train),验证集(valid),测试集(test)。 运行完成,在 Data/cifar-10-png/raw_test 下将有 10 个文件夹,对应 10 个类别. └── raw_test ├── 0 ├── 1 ├── 2 ├── 3 1 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 ├── 4 ├── 5 ├── 6 ├── 7 ├── 8 └── 9 接着进入下一步:划分训练集、验证集和测试集。 1.2 训练集、验证集和测试集的划分 上一小节,把 cifar-10 的测试集转换成了 png 图片,充当实验的原始数据。本小节, 将把原始数据按 8:1:1 的比例划分为训练集(train set)、验证集(valid/dev set)和测试集(test set)。关于训练集、验证集和测试集的作用,可阅读博客: https://blog.csdn.net/u011995719/article/details/77451213 运行 Code/1_data_prepare/1_2_split_dataset.py,将会获得以下三个文件夹/Data/train/ /Data/valid/ /Data/test/ 数据划分完毕,下一步是制作存放有图片路径及其标签的 txt,PyTorch 依据该 txt 上 的信息进行寻找图片,并读取图片数据和标签数据。 1.3 让 PyTorch 能读你的数据集 上一小节中,将源数据(10000 张图片)划分为训练集、验证集和测试集,接下来就要让 PyTorch 能读取这批数据。想让 PyTorch 能读取我们自己的数据,首先要了解 pytroch 读取 图片的机制和流程,然后按流程编写代码。 Dataset 类 PyTorch 读取图片,主要是通过 Dataset 类,所以先简单了解一下 Dataset 类。Dataset 类作为所有的 datasets 的基类存在,所有的 datasets 都需要继承它,类似于 C++中的虚基 类。 源码如下: 2 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 class Dataset(object): """An abstract class representing a Dataset. All other datasets should subclass it. All subclasses should override ``__len__``, that provides the size of the dataset, and ``__getitem__``, supporting integer indexing in range from 0 to len(self) exclusive. """ def __getitem__(self, index): raise NotImplementedError def __len__(self): raise NotImplementedError def __add__(self, other): return ConcatDataset([self, other]) 这里重点看 getitem 函数,getitem 接收一个 index,然后返回图片数据和标签,这个 index 通常指的是一个 list 的 index,这个 list 的每个元素就包含了图片数据的路径和标签信 息。 然而,如何制作这个 list 呢,通常的方法是将图片的路径和标签信息存储在一个 txt 中,然后从该 txt 中读取。 那么读取自己数据的基本流程就是: 1. 制作存储了图片的路径和标签信息的 txt 2. 将这些信息转化为 list,该 list 每一个元素对应一个样本 3. 通过 getitem 函数,读取数据和标签,并返回数据和标签 在训练代码里是感觉不到这些操作的,只会看到通过 DataLoader 就可以获取一个 batch 的数据,其实触发去读取图片这些操作的是 DataLoader 里的__iter__(self),后面会详 细讲解读取过程。在本小节,主要讲 Dataset 子类。 因此,要让 PyTorch 能读取自己的数据集,只需要两步: 1. 制作图片数据的索引 2. 构建 Dataset 子类 1. 制作图片数据的索引 这个比较简单,就是读取图片路径,标签,保存到 txt 文件中,这里注意格式就好 3 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 特别注意的是,txt 中的路径,是以训练时的那个 py 文件所在的目录为工作目录,所以这 里需要提前算好相对路径! 运行代码 Code/1_data_prepare/1_3_generate_txt.py,即会在/Data/文件夹下面看到 train.txt valid.txt txt 中是这样的: 2. 构建 Dataset 子类 下面是本实验构建的 Dataset 子类——MyDataset 类: # coding: utf-8 from PIL import Image from torch.utils.data import Dataset class MyDataset(Dataset): def __init__(self, txt_path, transform = None, target_transform = None): fh = open(txt_path, 'r') imgs = [] for line in fh: line = line.rstrip() words = line.split() imgs.append((words, int(words))) self.imgs = imgs self.transform = transform self.target_transform = target_transform def __getitem__(self, index): fn, label = self.imgs[index] img = Image.open(fn).convert('RGB') if self.transform is not None: img = self.transform(img) return img, label def __len__(self): return len(self.imgs) 4 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 首先看看初始化,初始化中从我们准备好的 txt 里获取图片的路径和标签,并且存储 在 self.imgs,self.imgs 就是上面提到的 list,其一个元素对应一个样本的路径和标签,其实 就是 txt 中的一行。 初始化中还会初始化 transform,transform 是一个 Compose 类型,里边有一个 list,list 中就会定义了各种对图像进行处理的操作,可以设置减均值,除标准差,随机裁剪,旋 转,翻转,仿射变换等操作。 在这里我们可以知道,一张图片读取进来之后,会经过数据处理(数据增强),最终 变成输入模型的数据。这里就有一点需要注意,PyTorch 的数据增强是将原始图片进行了 处理,并不会生成新的一份图片,而是“覆盖”原图,当采用 randomcrop 之类的随机操作 时,每个 epoch 输入进来的图片几乎不会是一模一样的,这达到了样本多样性的功能。 然后看看核心的 getitem 函数: 第一行:self.imgs 是一个 list,也就是一开始提到的 list,self.imgs 的一个元素是一个 str, 包含图片路径,图片标签,这些信息是从 txt 文件中读取 第二行:利用 Image.open 对图片进行读取,img 类型为 Image ,mode=‘RGB’ 第三行与第四行: 对图片进行处理,这个 transform 里边可以实现 减均值,除标准差,随 机裁剪,旋转,翻转,放射变换,等等操作,这个放在后面会详细讲解。 当 Mydataset 构建好,剩下的操作就交给 DataLoder,在 DataLoder 中,会触发 Mydataset 中的 getiterm 函数读取一张图片的数据和标签,并拼接成一个 batch 返回,作为 模型真正的输入。下一小节将会通过一个小例子,介绍 DataLoder 是如何获取一个 batch, 以及一张图片是如何被 PyTorch 读取,最终变为模型的输入的。 1.4 图片从硬盘到模型 上小节中介绍了如何构建自己的 Dataset 子类——MyDataset,在 MyDataset 中,主要 获取图片的索引以及定义如何通过索引读取图片及其标签。但是要触发 MyDataset 去读取 图片及其标签却是在数据加载器 DataLoder 中。本小节,将进行单步调试,学习图片是如 何从硬盘上流到模型的输入口的,并观察图片经历了哪些处理。 对应代码: /Code/main_training/main.py 大体流程: 1. main.py: train_data = MyDataset(txt_path=train_txt_path,...) ---> 5 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 2. main.py: train_loader = DataLoader(dataset=train_data,...) ---> 3. main.py: for i, data in enumerate(train_loader, 0) ---> 4. dataloder.py: class DataLoader(): def __iter__(self): return _DataLoaderIter(self) ---> 5. dataloder.py: class _DataLoderIter(): def __next__(self): batch = self.collate_fn([self.dataset[i] for i in indices]) ---> 6. tool.py: class MyDataset(): def __getitem__(): img = Image.open(fn).convert('RGB') ---> 7. tool.py: class MyDataset(): img = self.transform(img) ---> 8. main.py: inputs, labels = data inputs, labels = Variable(inputs), Variable(labels) outputs = net(inputs) 一句话概括就是,从 MyDataset 来,到 MyDataset 去。 一开始通过 MyDataset 创建一个实例,在该实例中有路径,有读取图片的方法(函 数)。 然后需要 pytroch 的一系列规范化流程,在第 6 步中,才会调用 MyDataset 中的 __getitem__()函数,最终通过 Image.open()读取图片数据。 然后对原始图片数据进行一系列预处理(transform 中设置),最后回到 main.py,对数据 进行转换成 Variable 类型,最终成为模型的输入。 流程详细描述: 1. 从 MyDataset 类中初始化 txt,txt 中有图片路径和标签 2. 初始化 DataLoder 时,将 train_data 传入,从而使 DataLoder 拥有图片的路径 3. 在一个 iteration 进行时,才读取一个 batch 的图片数据 enumerate()函数会返回可迭代数 据的一个“元素” 在这里 data 是一个 batch 的图片数据和标签,data 是一个 list 喔 4. class DataLoader()中再调用 class _DataLoderIter() 5. 在 _DataLoderiter()类中会跳到__next__(self)函数,在该函数中会通过 indices = next(self.sample_iter) 获取一个 batch 的 indices 再通过 batch = self.collate_fn([self.dataset[i] for i in indices])获取一个 batch 的数据 在 batch = self.collate_fn([self.dataset[i] for i in indices])中会调用 self.collate_fn 函数 6. self.collate_fn 中会调用 MyDataset 类中的__getitem__()函数,在__getitem__()中通过 Image.open(fn).convert('RGB')读取图片 6 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 7. 通过 Image.open(fn).convert('RGB')读取图片之后,会对图片进行预处理,例如减均值, 除以标准差,随机裁剪等等一系列提前设置好的操作。 具体 transform 的用法将用单独一小节介绍,最后返回 img,label,再通过 self.collate_fn 来 拼接成一个 batch。一个 batch 是一个 list,有两个元素,第一个元素是图片数据,是一个 4D 的 Tensor,shape 为(64,3,32,32),第二个元素是标签 shape 为(64)。 8. 将图片数据转换成 Variable 类型,然后称为模型真正的输入 inputs, labels = Variable(inputs), Variable(labels) outputs = net(inputs) 通过了解图片从硬盘到模型的过程,我们可以更好的对数据做处理(减均值,除以标准 差,裁剪,翻转,放射变换等等),也可以灵活的为模型准备数据,最后总结两个需要注意 的地方。 1. 图片是通过 Image.open()函数读取进来的,当涉及如下问题: 图片的通道顺序(RGB ? BGR ?) 图片是 w*h*c ? c*w*h ? 像素值范围[0-1] or [0-255] ? 就要查看 MyDataset()类中 __getitem__()下读取图片用的是什么方法 2. 从 MyDataset()类中 __getitem__()函数中发现,PyTorch 做数据增强的方法是在原 始图片上进行的,并覆盖原始图片,这一点需要注意。 1.5 数据增强与数据标准化 在实际应用过程中,我们会在数据进入模型之前进行一些预处理,例如数据中心化(仅 减均值),数据标准化(减均值,再除以标准差),随机裁剪,旋转一定角度,镜像等一系列 操作。PyTorch 有一系列数据增强方法供大家使用,下面将介绍这些方法。 在 PyTorch 中,这些数据增强方法放在了 transforms.py 文件中。这些数据处理可以满 足我们大部分的需求,通过熟悉 transforms.py,以及 1.4 节中的内容,我们也可以自定义 数据处理函数,实现自己的数据增强。 在本小节,从宏观地介绍 transform 的使用,在下一小节,将会详细介绍 transform 的 所有操作。 transform 的使用 请查看/Code/main_training/main.py 中代码: 7 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 normMean = [0.4948052, 0.48568845, 0.44682974] normStd = [0.24580306, 0.24236229, 0.2603115] normTransform = transforms.Normalize(normMean, normStd) trainTransform = transforms.Compose([ transforms.Resize(32), transforms.RandomCrop(32, padding=4), transforms.ToTensor(), normTransform ]) validTransform = transforms.Compose([ transforms.ToTensor(), normTransform ]) 前三行设置均值,标准差,以及数据标准化:transforms.Normalize()函数,这里是以通 道为单位进行计算均值,标准差。 然后用 transforms.Compose 将所需要进行的处理给 compose 起来,并且需要注意顺 序! 在训练时,依次对图片进行以下操作: 1. 随机裁剪 2. Totensor 3. 数据标准化(减均值,除以标准差) 1. 随机裁剪 第一个处理是随机裁剪,在裁剪之前先对图片的上下左右均填充上 4 个 pixel,值为 0,即变成一个 40*40 的数据,然后再随机进行 32*32 的裁剪。 例如下图,是经过 transforms.RandomCrop(32, padding=4),之后的图片,其中红色框是 原始图片数据,31 列是填充的 0,28-31 行也是填充的 0. 8 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 2. Totensor 第二个处理是 transforms.ToTensor() 在这里会对数据进行 transpose,原来是 h*w*c,会经过 img = img.transpose(0, 1).transpose(0, 2).contiguous(),变成 c*h*w 再除以 255,使得像素值归一化至[0-1]之间,来 看看 Red 通道。 来看看 27 行,30 列 原来是 8 的, 经过 ToTensor 之后变成:8/255= 0.03137255 3. 数据标准化(减均值,除以标准差) 9 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 第三个处理是对图像进行标准化,通过标准化之后,再来看看 Red 通道的数据: 至此,数据预处理完毕,最后转换成 Variable 类型,就是输入网络模型的数据了。 细心的朋友可能会发现,在进行 Normalize 时,需要设置均值和方差,在这里直接给 出了,但在实际应用中是要去训练集中计算的,天下可没有免费的午餐。这里给出计算训 练集的均值和方差的脚本:/Code/1_data_prepare/1_5_compute_mean.py 1.6 transforms 的二十二个方法 本小节对 transforms.py 中的各个预处理方法进行介绍和总结。主要从官方文档中总结 而来,官方文档只是将方法陈列,没有归纳总结,顺序很乱,这里总结一共有四大类,方 便大家索引: 1. 裁剪——Crop 中心裁剪:transforms.CenterCrop 随机裁剪:transforms.RandomCrop 随机长宽比裁剪:transforms.RandomResizedCrop 上下左右中心裁剪:transforms.FiveCrop 上下左右中心裁剪后翻转,transforms.TenCrop 2. 翻转和旋转——Flip and Rotation 10 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 依概率 p 水平翻转:transforms.RandomHorizontalFlip(p=0.5) 依概率 p 垂直翻转:transforms.RandomVerticalFlip(p=0.5) 随机旋转:transforms.RandomRotation 3.图像变换 resize:transforms.Resize 标准化:transforms.Normalize 转为 tensor,并归一化至[0-1]:transforms.ToTensor 填充:transforms.Pad 修改亮度、对比度和饱和度:transforms.ColorJitter 转灰度图:transforms.Grayscale 线性变换:transforms.LinearTransformation() 仿射变换:transforms.RandomAffine 依概率 p 转为灰度图:transforms.RandomGrayscale 将数据转换为 PILImage:transforms.ToPILImage transforms.Lambda:Apply a user-defined lambda as a transform. 4.对 transforms 操作,使数据增强更灵活 transforms.RandomChoice(transforms), 从给定的一系列 transforms 中选一个进行操作 transforms.RandomApply(transforms, p=0.5),给一个 transform 加上概率,依概率进行操作 transforms.RandomOrder,将 transforms 中的操作随机打乱 一、 裁剪——Crop 1.随机裁剪:transforms.RandomCrop class torchvision.transforms.RandomCrop(size, padding=None, pad_if_needed=False, fill=0, padding_mode='constant') 功能:依据给定的 size 随机裁剪 参数: size- (sequence or int),若为 sequence,则为(h,w),若为 int,则(size,size) padding-(sequence or int, optional),此参数是设置填充多少个 pixel。 11 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 当为 int 时,图像上下左右均填充 int 个,例如 padding=4,则上下左右均填充 4 个 pixel, 若为 32*32,则会变成 40*40。 当为 sequence 时,若有 2 个数,则第一个数表示左右扩充多少,第二个数表示上下的。当 有 4 个数时,则为左,上,右,下。 fill- (int or tuple) 填充的值是什么(仅当填充模式为 constant 时有用)。int 时,各通道均填 充该值,当长度为 3 的 tuple 时,表示 RGB 通道需要填充的值。 padding_mode- 填充模式,这里提供了 4 种填充模式,1.constant,常量。2.edge 按照图片 边缘的像素值来填充。3.reflect,暂不了解。 4. symmetric,暂不了解。 2.中心裁剪:transforms.CenterCrop class torchvision.transforms.CenterCrop(size) 功能:依据给定的 size 从中心裁剪 参数: size- (sequence or int),若为 sequence,则为(h,w),若为 int,则(size,size) 3.随机长宽比裁剪 transforms.RandomResizedCrop class torchvision.transforms.RandomResizedCrop(size, scale=(0.08, 1.0), ratio=(0.75, 1.33333 33333333333), interpolation=2) 功能:随机大小,随机长宽比裁剪原始图片,最后将图片 resize 到设定好的 size 参数: size- 输出的分辨率 scale- 随机 crop 的大小区间,如 scale=(0.08, 1.0),表示随机 crop 出来的图片会在的 0.08 倍至 1 倍之间。 ratio- 随机长宽比设置 interpolation- 插值的方法,默认为双线性插值(PIL.Image.BILINEAR) 4.上下左右中心裁剪:transforms.FiveCrop class torchvision.transforms.FiveCrop(size) 功能:对图片进行上下左右以及中心裁剪,获得 5 张图片,返回一个 4D-tensor 参数: size- (sequence or int),若为 sequence,则为(h,w),若为 int,则(size,size) 12 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 5.上下左右中心裁剪后翻转: transforms.TenCrop class torchvision.transforms.TenCrop(size, vertical_flip=False) 功能:对图片进行上下左右以及中心裁剪,然后全部翻转(水平或者垂直),获得 10 张图 片,返回一个 4D-tensor。 参数: size- (sequence or int),若为 sequence,则为(h,w),若为 int,则(size,size) vertical_flip (bool) - 是否垂直翻转,默认为 flase,即默认为水平翻转 二、翻转和旋转——Flip and Rotation 6.依概率 p 水平翻转 transforms.RandomHorizontalFlip class torchvision.transforms.RandomHorizontalFlip(p=0.5) 功能:依据概率 p 对 PIL 图片进行水平翻转 参数: p- 概率,默认值为 0.5 7.依概率 p 垂直翻转 transforms.RandomVerticalFlip class torchvision.transforms.RandomVerticalFlip(p=0.5) 功能:依据概率 p 对 PIL 图片进行垂直翻转 参数: p- 概率,默认值为 0.5 8.随机旋转:transforms.RandomRotation class torchvision.transforms.RandomRotation(degrees, resample=False, expand=False, cente r=None) 功能:依 degrees 随机旋转一定角度 参数: degress- (sequence or float or int) ,若为单个数,如 30,则表示在(-30,+30)之间随机旋 转 若为 sequence,如(30,60),则表示在 30-60 度之间随机旋转 13 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 resample- 重采样方法选择,可选 PIL.Image.NEAREST, PIL.Image.BILINEAR, PIL.Image.BICUBIC,默认为最近邻 expand- ? center- 可选为中心旋转还是左上角旋转 三、图像变换 9.resize:transforms.Resize class torchvision.transforms.Resize(size, interpolation=2) 功能:重置图像分辨率 参数: size- If size is an int, if height > width, then image will be rescaled to (size * height / width, size),所以建议 size 设定为 h*w interpolation- 插值方法选择,默认为 PIL.Image.BILINEAR 10.标准化:transforms.Normalize class torchvision.transforms.Normalize(mean, std) 功能:对数据按通道进行标准化,即先减均值,再除以标准差,注意是 h*w*c 11.转为 tensor:transforms.ToTensor class torchvision.transforms.ToTensor 功能:将 PIL Image 或者 ndarray 转换为 tensor,并且归一化至[0-1] 注意事项:归一化至[0-1]是直接除以 255,若自己的 ndarray 数据尺度有变化,则需要自行 修改。 12.填充:transforms.Pad class torchvision.transforms.Pad(padding, fill=0, padding_mode='constant') 功能:对图像进行填充 参数: padding-(sequence or int, optional),此参数是设置填充多少个 pixel。 当为 int 时,图像上下左右均填充 int 个,例如 padding=4,则上下左右均填充 4 个 pixel, 若为 32*32,则会变成 40*40。 14 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 当为 sequence 时,若有 2 个数,则第一个数表示左右扩充多少,第二个数表示上下的。当 有 4 个数时,则为左,上,右,下。 fill- (int or tuple) 填充的值是什么(仅当填充模式为 constant 时有用)。int 时,各通道均填 充该值,当长度为 3 的 tuple 时,表示 RGB 通道需要填充的值。 padding_mode- 填充模式,这里提供了 4 种填充模式,1.constant,常量。2.edge 按照图片 边缘的像素值来填充。3.reflect,? 4. symmetric,? 13.修改亮度、对比度和饱和度:transforms.ColorJitter class torchvision.transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0) 功能:修改修改亮度、对比度和饱和度 14.转灰度图:transforms.Grayscale class torchvision.transforms.Grayscale(num_output_channels=1) 功能:将图片转换为灰度图 参数: num_output_channels- (int) ,当为 1 时,正常的灰度图,当为 3 时, 3 channel with r == g == b 15.线性变换:transforms.LinearTransformation() class torchvision.transforms.LinearTransformation(transformation_matrix) 功能:对矩阵做线性变化,可用于白化处理! whitening: zero-center the data, compute the data covariance matrix 参数: transformation_matrix (Tensor) – tensor [D x D], D = C x H x W 16.仿射变换:transforms.RandomAffine class torchvision.transforms.RandomAffine(degrees, translate=None, scale=None, shear=Non e, resample=False, fillcolor=0) 功能:仿射变换 15 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 17.依概率 p 转为灰度图:transforms.RandomGrayscale class torchvision.transforms.RandomGrayscale(p=0.1) 功能:依概率 p 将图片转换为灰度图,若通道数为 3,则 3 channel with r == g == b 18.将数据转换为 PILImage:transforms.ToPILImage class torchvision.transforms.ToPILImage(mode=None) 功能:将 tensor 或者 ndarray 的数据转换为 PIL Image 类型数据 参数: mode- 为 None 时,为 1 通道, mode=3 通道默认转换为 RGB,4 通道默认转换为 RGBA 19.transforms.Lambda Apply a user-defined lambda as a transform. 暂不了解,待补充。 四、对 transforms 操作,使数据增强更灵活 PyTorch 不仅可设置对图片的操作,还可以对这些操作进行随机选择、组合 20.transforms.RandomChoice(transforms) 功能:从给定的一系列 transforms 中选一个进行操作,randomly picked from a list 21.transforms.RandomApply(transforms, p=0.5) 功能:给一个 transform 加上概率,以一定的概率执行该操作 22.transforms.RandomOrder 功能:将 transforms 中的操作顺序随机打乱 16 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 第二章 模 型 第二章介绍关于网络模型的一系列内容,包括模型的定义,模型参数初始化方法,模 型的保存和加载,模型的 finetune(本质上还是模型权值初始化),首先介绍模型的定义。 2.1 模型的搭建 2.1.1 模型定义的三要 首先,必须继承 nn.Module 这个类,要让 PyTorch 知道这个类是一个 Module。 其次,在__init__(self)中设置好需要的“组件"(如 conv、pooling、Linear、BatchNorm 等)。 最后,在 forward(self, x)中用定义好的“组件”进行组装,就像搭积木,把网络结构搭建 出来,这样一个模型就定义好了。 接下来,请看代码,在/Code/main_training/main.py 中可以看到定义了一个类 class Net(nn.Module),先看__init__(self)函数 def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.pool1 = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.pool2 = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) 第一行是初始化,往后定义了一系列组件,如由 Conv2d 构成的 conv1,有 MaxPool2d 构成的 poo1l,这些操作均由 torch.nn 提供,torch.nn 中的操作可查看文档: https://PyTorch.org/docs/stable/nn.html#。 当这些组件定义好之后,就可以定义 forward()函数,用来搭建网络结构,请看代码: 17 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 def forward(self, x): x = self.pool1(F.relu(self.conv1(x))) x = self.pool2(F.relu(self.conv2(x))) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x x 为模型的输入,第一行表示,x 经过 conv1,然后经过激活函数 relu,再经过 pool1 操 作; 第二行于第一行一样;第三行,表示将 x 进行 reshape,为了后面做为全连接层的输入; 第四,第五行的操作都一样,先经过全连接层 fc,然后经过 relu; 第六行,模型的最终输出是 fc3 输出。 至此,一个模型定义完毕,接着就可以在后面进行使用。 例如,实例化一个模型 net = Net(),然后把输入 inputs 扔进去,outputs = net(inputs),就可 以得到输出 outputs。 2.1.2 模型定义多说两句 上面只是介绍了模型定义的要素和过程,但是在工程应用中会碰到各种各样的网络模型, 这时,我们就需要一些实用工具来帮助我们定义模型了。 这里以 Resnet34 为例介绍“复杂”模型的定义,这部分代码从 github: https://github.com/yuanlairuci110/PyTorch-best-practice-master/blob/master/models/ResNet34.py 上获取。 class ResidualBlock(nn.Module): ''' 实现子 module: Residual Block ''' def __init__(self, inchannel, outchannel, stride=1, shortcut=None): super(ResidualBlock, self).__init__() self.left = nn.Sequential( 18 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 nn.Conv2d(inchannel, outchannel, 3, stride, 1, bias=False), nn.BatchNorm2d(outchannel), nn.ReLU(inplace=True), nn.Conv2d(outchannel, outchannel, 3, 1, 1, bias=False), nn.BatchNorm2d(outchannel)) self.right = shortcut def forward(self, x): out = self.left(x) residual = x if self.right is None else self.right(x) out += residual return F.relu(out) class ResNet34(BasicModule): ''' 实现主 module:ResNet34 ResNet34 包含多个 layer,每个 layer 又包含多个 Residual block 用子 module 来实现 Residual block,用_make_layer 函数来实现 layer ''' def __init__(self, num_classes=2): super(ResNet34, self).__init__() self.model_name = 'resnet34' # 前几层: 图像转换 self.pre = nn.Sequential( nn.Conv2d(3, 64, 7, 2, 3, bias=False), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(3, 2, 1)) # 重复的 layer,分别有 3,4,6,3 个 residual block self.layer1 = self._make_layer(64, 128, 3) self.layer2 = self._make_layer(128, 256, 4, stride=2) self.layer3 = self._make_layer(256, 512, 6, stride=2) self.layer4 = self._make_layer(512, 512, 3, stride=2) 19 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 # 分类用的全连接 self.fc = nn.Linear(512, num_classes) def _make_layer(self, inchannel, outchannel, block_num, stride=1): ''' 构建 layer,包含多个 residual block ''' shortcut = nn.Sequential( nn.Conv2d(inchannel, outchannel, 1, stride, bias=False), nn.BatchNorm2d(outchannel)) layers = [] layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut)) for i in range(1, block_num): layers.append(ResidualBlock(outchannel, outchannel)) return nn.Sequential(*layers) def forward(self, x): x = self.pre(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = F.avg_pool2d(x, 7) x = x.view(x.size(0), -1) return self.fc(x) 还是从三要素出发看看是怎么定义 Resnet34 的。 首先,继承 nn.Module; 其次,看__init__()函数,在__init__()中,定义了这些组件,self.pre,self.layer1-4, self.fc ; 最后,看 forward(),分别用了在__init__()中定义的一系列组件,并且用了 torch.nn.functional.avg_pool2d 这个操作 至此,网络定义完成。 20 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 以为就完了?怎么可能,__init__()函数中的组件是怎么定义的,在__init__()中出现了 torch.nn.Sequential 组件定义还调用函数_make_layer(),其中也用到了 torch.nn.Sequential,其中还调用了 ResidualBlock(nn.Module),在 ResidualBlock(nn.Module)中有一次调用了 torch.nn.Sequential。 torch.nn.Sequential 到底是什么呢?为什么都在用呢? 2.1.3 nn.Sequetial torch.nn.Sequential 其实就是 Sequential 容器,该容器将一系列操作按先后顺序给包起 来,方便重复使用, 例如 Resnet 中有很多重复的 block,就可以用 Sequential 容器把重复的地方包起来。 官方文档中给出两个使用例子: # Example of using Sequential model = nn.Sequential( nn.Conv2d(1,20,5), nn.ReLU(), nn.Conv2d(20,64,5), nn.ReLU() ) # Example of using Sequential with OrderedDict model = nn.Sequential(OrderedDict([ ('conv1', nn.Conv2d(1,20,5)), ('relu1', nn.ReLU()), ('conv2', nn.Conv2d(20,64,5)), ('relu2', nn.ReLU()) ])) 小结: 模型的定义就是先继承,再构建组件,最后组装。 21 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 其中基本组件可从 torch.nn 中获取,或者从 torch.nn.functional 中获取,同时为了方便 重复使用组件,可以使用 Sequential 容器将一系列组件包起来,最后在 forward()函数中将 这些组件组装成你的模型。 2.2 权值初始化的十种方法 上一小节介绍了模型定义的方法,模型定义完成后,通常我们还需要对权值进行初始 化,才能开始训练。 初始化方法会直接影响到模型的收敛与否,在本小节,将介绍如何对模型进行初始 化。 2.2.1 权值初始化流程 总共两步, 第一步,先设定什么层用什么初始化方法,初始化方法在 torch.nn.init 中给出; 第二步,实例化一个模型之后,执行该函数,即可完成初始化。 我们的重点是在第一步,请看 main.py 中第一步的代码: # 定义权值初始化 def initialize_weights(self): for m in self.modules(): if isinstance(m, nn.Conv2d): torch.nn.init.xavier_normal_(m.weight.data) if m.bias is not None: m.bias.data.zero_() elif isinstance(m, nn.BatchNorm2d): m.weight.data.fill_(1) m.bias.data.zero_() elif isinstance(m, nn.Linear): torch.nn.init.normal_(m.weight.data, 0, 0.01) # m.weight.data.normal_(0, 0.01) m.bias.data.zero_() 22 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 这段代码基本流程是这样,先从 self.modules()中遍历每一层,然后判断各层属于什么 类型,例如,是否是 nn.Conv2d、nn.BatchNorm2d、nn.Linear 等,然后根据不同类型的 层,设定不同的权值初始化方法,例如,Xavier,kaiming,normal_,uniform_等。 Ps: kaiming 也称之为 MSRA 初始化,当年何恺明还在微软亚洲研究院,因而得名。 来看看第一行代码中的 self.modules(),源码在 torch/nn/modules/module.py 中 def modules(self): for name, module in self.named_modules(): yield module 功能是:Returns an iterator over all modules in the network. 能依次返回模型中的各层, 例如: 接着,判断 m 的类型,属于什么类型,可以看到当前 m 属于 Conv2d 类型,则进行如 下初始化: torch.nn.init.xavier_normal(m.weight.data) if m.bias is not None: m.bias.data.zero_() 以上代码表示采用 torch.nn.init.xavier_normal 方法对该层的 weight 进行初始化, 并判断是否存在偏置(bias),若存在,将 bias 初始化为全 0。 这样,该层就初始化完毕,参照以上流程,不断遍历模型的每一层,最终完成模型的 初始化。 2.2.2 常用初始化方法 PyTorch 在 torch.nn.init 中提供了常用的初始化方法函数,这里简单介绍,方便查询使 用。 介绍分两部分: 1. Xavier,kaiming 系列; 2. 其他方法分布 23 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 Xavier 初始化方法,论文在《Understanding the difficulty of training deep feedforward neural networks》 公式推导是从“方差一致性”出发,初始化的分布有均匀分布和正态分布两种。 1. Xavier 均匀分布 torch.nn.init.xavier_uniform_(tensor, gain=1) xavier 初始化方法中服从均匀分布 U(−a,a) ,分布的参数 a = gain * sqrt(6/fan_in+fan_out), 这里有一个 gain,增益的大小是依据激活函数类型来设定 eg:nn.init.xavier_uniform_(w, gain=nn.init.calculate_gain('relu')) PS:上述初始化方法,也称为 Glorot initialization 2. Xavier 正态分布 torch.nn.init.xavier_normal_(tensor, gain=1) xavier 初始化方法中服从正态分布, mean=0,std = gain * sqrt(2/fan_in + fan_out) kaiming 初始化方法,论文在《 Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification》,公式推导同样从“方差一致性”出法,kaiming 是针对 xavier 初始化方法在 relu 这一类激活函数表现不佳而提出的改进,详细可以参看论 文。 3. kaiming 均匀分布 torch.nn.init.kaiming_uniform_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu') 此为均匀分布,U~(-bound, bound), bound = sqrt(6/(1+a^2)*fan_in) 其中,a 为激活函数的负半轴的斜率,relu 是 0 mode- 可选为 fan_in 或 fan_out, fan_in 使正向传播时,方差一致; fan_out 使反向传播时, 方差一致 nonlinearity- 可选 relu 和 leaky_relu ,默认值为 。 leaky_relu nn.init.kaiming_uniform_(w, mode='fan_in', nonlinearity='relu') 24 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 4. kaiming 正态分布 torch.nn.init.kaiming_normal_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu') 此为 0 均值的正态分布,N~ (0,std),其中 std = sqrt(2/(1+a^2)*fan_in) 其中,a 为激活函数的负半轴的斜率,relu 是 0 mode- 可选为 fan_in 或 fan_out, fan_in 使正向传播时,方差一致;fan_out 使反向传播时, 方差一致 nonlinearity- 可选 relu 和 leaky_relu ,默认值为 。 leaky_relu nn.init.kaiming_normal_(w, mode='fan_out', nonlinearity='relu') 2.其他 5. 均匀分布初始化 torch.nn.init.uniform_(tensor, a=0, b=1) 使值服从均匀分布 U(a,b) 6. 正态分布初始化 torch.nn.init.normal_(tensor, mean=0, std=1) 使值服从正态分布 N(mean, std),默认值为 0,1 7. 常数初始化 torch.nn.init.constant_(tensor, val) 使值为常数 val nn.init.constant_(w, 0.3) 8. 单位矩阵初始化 torch.nn.init.eye_(tensor) 将二维 tensor 初始化为单位矩阵(the identity matrix) 25 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 9. 正交初始化 torch.nn.init.orthogonal_(tensor, gain=1) 使得 tensor 是正交的,论文:Exact solutions to the nonlinear dynamics of learning in deep linear neural networks” - Saxe, A. et al. (2013) 10. 稀疏初始化 torch.nn.init.sparse_(tensor, sparsity, std=0.01) 从正态分布 N~(0. std)中进行稀疏化,使每一个 column 有一部分为 0 sparsity- 每一个 column 稀疏的比例,即为 0 的比例 nn.init.sparse_(w, sparsity=0.1) 11. 计算增益 torch.nn.init.calculate_gain(nonlinearity, param=None) nonlinearity gain Linear / Identity 1 Conv{1,2,3}D 1 Sigmoid 1 Tanh 5/3 ReLU sqrt(2) Leaky Relu sqrt(2/1+neg_slop^2) 权值初始化杂谈 1. 从代码中发现,即使不进行初始化,我们模型的权值也不为空,而是有值的,这些值是 在什么时候赋的呢? 其实,在创建网络实例的过程中, 一旦调用 nn.Conv2d 的时候就会有对权值进行初始化 26 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 初始化过程是在 Conv2d 的基类_ConvNd 中进行的,来看看关系: class Conv2d(_ConvNd): --> 在_ConvNd 中: --> self.reset_parameters() ---> def reset_parameters(self) ---> self.weight.data.uniform_(-stdv, stdv) 可看到采用的是均匀分布,其中-stdv 与 kernel 的 size 有关,有兴趣的朋友可以阅读 torch/nn/modules/conv.py 中的源代码。 补充:在 PyTorch1.0 版本中,这里改用了 kaiming_uniform_()进行初始化,大家均可在 torch/nn/modules/conv.py 中的_ConvNd 类中的函数 reset_parametersz()中看到初始化方 式。 2. 按需定义初始化方法,例如: if isinstance(m, nn.Conv2d): n = m.kernel_size * m.kernel_size * m.out_channels m.weight.data.normal_(0, math.sqrt(2. / n)) 2.3 模型 Finetune 上一小节,介绍了模型权值初始化,以及 PyTorch 自带的权值初始化方法函数。我们 知道一个良好的权值初始化,可以使收敛速度加快,甚至可以获得更好的精度。而在实际 应用中,我们通常采用一个已经训练模型的模型的权值参数作为我们模型的初始化参数, 也称之为 Finetune,更宽泛的称之为迁移学习。迁移学习中的 Finetune 技术,本质上就是 让我们新构建的模型,拥有一个较好的权值初始值。 finetune 权值初始化三步曲,finetune 就相当于给模型进行初始化,其流程共用三步: 第一步:保存模型,拥有一个预训练模型; 第二步:加载模型,把预训练模型中的权值取出来; 第三步:初始化,将权值对应的“放”到新模型中 一、Finetune 之权值初始化 27 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 在进行 finetune 之前我们需要拥有一个模型或者是模型参数,因此需要了解如何保存 模型。官方文档中介绍了两种保存模型的方法,一种是保存整个模型,另外一种是仅保存 模型参数(官方推荐用这种方法),这里采用官方推荐的方法。 第一步:保存模型参数 若拥有模型参数,可跳过这一步。 假设创建了一个 net = Net(),并且经过训练,通过以下方式保存: torch.save(net.state_dict(), 'net_params.pkl') 第二步:加载模型 进行三步曲中的第二步,加载模型,这里只是加载模型的参数: pretrained_dict = torch.load('net_params.pkl') 第三步:初始化 进行三步曲中的第三步,将取到的权值,对应的放到新模型中: 首先我们创建新模型,并且获取新模型的参数字典 net_state_dict: net = Net() # 创建 net net_state_dict = net.state_dict() # 获取已创建 net 的 state_dict 接着将 pretrained_dict 里不属于 net_state_dict 的键剔除掉: pretrained_dict_1 = {k: v for k, v in pretrained_dict.items() if k in net_state_dict} 然后,用预训练模型的参数字典 对 新模型的参数字典 net_state_dict 进行更新: net_state_dict.update(pretrained_dict_1) 最后,将更新了参数的字典 “放”回到网络中: net.load_state_dict(net_state_dict) 这样,利用预训练模型参数对新模型的权值进行初始化过程就做完了。 28 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 采用 finetune 的训练过程中,有时候希望前面层的学习率低一些,改变不要太大,而 后面的全连接层的学习率相对大一些。这时就需要对不同的层设置不同的学习率,下面就 介绍如何为不同层配置不同的学习率。 二、不同层设置不同的学习率 在利用 pre-trained model 的参数做初始化之后,我们可能想让 fc 层更新相对快一些, 而希望前面的权值更新小一些,这就可以通过为不同的层设置不同的学习率来达到此目 的。 为不同层设置不同的学习率,主要通过优化器对多个参数组进行设置不同的参数。所 以,只需要将原始的参数组,划分成两个,甚至更多的参数组,然后分别进行设置学习 率。 这里将原始参数“切分”成 fc3 层参数和其余参数,为 fc3 层设置更大的学习率。 请看代码: ignored_params = list(map(id, net.fc3.parameters())) # 返回的是 parameters 的 内存地址 base_params = filter(lambda p: id(p) not in ignored_params, net.parameters()) optimizer = optim.SGD([ {'params': base_params}, {'params': net.fc3.parameters(), 'lr': 0.001*10}], 0.001, momentum=0.9, weight_decay=1e-4) 第一行+ 第二行的意思就是,将 fc3 层的参数 net.fc3.parameters()从原始参数 net.parameters()中剥离出来 base_params 就是剥离了 fc3 层的参数的其余参数,然后在优化器中为 fc3 层的参数单独设 定学习率。 optimizer = optim.SGD(......)这里的意思就是 base_params 中的层,用 0.001, momentum=0.9, weight_decay=1e-4 fc3 层设定学习率为: 0.001*10 完整代码位于 /Code/2_model/2_finetune.py 29 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 补充: 挑选出特定的层的机制是利用内存地址作为过滤条件,将需要单独设定的那部分参 数,从总的参数中剔除。 base_params 是一个 list,每个元素是一个 Parameter 类 net.fc3.parameters() 是一个 ignored_params = list(map(id, net.fc3.parameters())) net.fc3.parameters() 是一个 所以迭代的返回其中的 parameter,这里有 weight 和 bias 最终返回 weight 和 bias 所在内存的地址 30 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 第三章 损失函数与优化器 通过前两章,我们准备好数据,设计好模型,接下来就是选择合适的损失函数,并且 采用合适的优化器进行优化(训练)模型。本章中,将介绍 PyTorch 中的十七个损失函数, 十个优化器和六个学习率调整方法。 3.1 PyTorch 的十七个损失函数 我们所说的优化,即优化网络权值使得损失函数值变小。但是,损失函数值变小是否 能代表模型的分类/回归精度变高呢?那么多种损失函数,应该如何选择呢?请来了解 PyTorch 中给出的十七种损失函数吧。 请运行配套代码,代码中有详细解释,有手动计算,这些都有助于理解损失函数原理。 本小节配套代码: /Code/3_optimizer/3_1_lossFunction 1. L1loss class torch.nn.L1Loss(size_average=None, reduce=None) 官方文档中仍有 reduction='elementwise_mean'参数,但代码实现中已经删除该参数 功能: 计算 output 和 target 之差的绝对值,可选返回同维度的 tensor 或者是一个标量。 计算公式: 参数: reduce(bool)- 返回值是否为标量,默认为 True size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False 时,返回的各样本的 loss 之和。 实例: /Code/3_optimizer/3_1_lossFunction/1_L1Loss.py 2. MSELoss class torch.nn.MSELoss(size_average=None, reduce=None, reduction='elementwise_mean') 31 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 官方文档中仍有 reduction='elementwise_mean'参数,但代码实现中已经删除该参数 功能: 计算 output 和 target 之差的平方,可选返回同维度的 tensor 或者是一个标量。 计算公式: 参数: reduce(bool)- 返回值是否为标量,默认为 True size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False 时,返回的各样本的 loss 之和。 实例: /Code/3_optimizer/3_1_lossFunction/2_MSELoss.py 3. CrossEntropyLoss class torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=- 100, reduce=None, reduction='elementwise_mean') 功能: 将输入经过 softmax 激活函数之后,再计算其与 target 的交叉熵损失。即该方法将 nn.LogSoftmax()和 nn.NLLLoss()进行了结合。严格意义上的交叉熵损失函数应该是 nn.NLLLoss()。 补充:小谈交叉熵损失函数 交叉熵损失(cross-entropy Loss) 又称为对数似然损失(Log-likelihood Loss)、对数损 失;二分类时还可称之为逻辑斯谛回归损失(Logistic Loss)。交叉熵损失函数表达式为 L = - sigama(y_i * log(x_i))。pytroch 这里不是严格意义上的交叉熵损失函数,而是先将 input 经 过 softmax 激活函数,将向量“归一化”成概率形式,然后再与 target 计算严格意义上交叉熵 损失。 在多分类任务中,经常采用 softmax 激活函数+交叉熵损失函数,因为交叉熵描述了两个概 率分布的差异,然而神经网络输出的是向量,并不是概率分布的形式。所以需要 softmax 激活函数将一个向量进行“归一化”成概率分布的形式,再采用交叉熵损失函数计算 loss。 32 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 再回顾 PyTorch 的 CrossEntropyLoss(),官方文档中提到时将 nn.LogSoftmax() 和 nn.NLLLoss()进行了结合,nn.LogSoftmax() 相当于激活函数 , nn.NLLLoss()是损失函 数,将其结合,完整的是否可以叫做 softmax+交叉熵损失函数呢? 计算公式: 参数: weight(Tensor)- 为每个类别的 loss 设置权值,常用于类别不均衡问题。weight 必须是 float 类型的 tensor,其长度要于类别 C 一致,即每一个类别都要设置有 weight。带 weight 的计 算公式: size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False 时,返回的各样本的 loss 之和。 reduce(bool)- 返回值是否为标量,默认为 True ignore_index(int)- 忽略某一类别,不计算其 loss,其 loss 会为 0,并且,在采用 size_average 时,不会计算那一类的 loss,除的时候的分母也不会统计那一类的样本。 实例: /Code/3_optimizer/3_1_lossFunction/3_CroosEntropyLoss.py 补充: output 不仅可以是向量,还可以是图片,即对图像进行像素点的分类,这个例子可以 从 NLLLoss()中看到,这在图像分割当中很有用。 4. NLLLoss class torch.nn.NLLLoss(weight=None, size_average=None, ignore_index=- 100, reduce=None, reduction='elementwise_mean') 功能: 不好用言语描述其功能!请看计算公式:loss(input, class) = -input[class]。举个例,三 分类任务,input=[-1.233, 2.657, 0.534], 真实标签为 2(class=2),则 loss 为-0.534。就是 对应类别上的输出,取一个负号!感觉被 NLLLoss 的名字欺骗了。 33 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 实际应用: 常用于多分类任务,但是 input 在输入 NLLLoss()之前,需要对 input 进行 log_softmax 函数 激活,即将 input 转换成概率分布的形式,并且取对数。其实这些步骤在 CrossEntropyLoss 中就有,如果不想让网络的最后一层是 log_softmax 层的话,就可以采用 CrossEntropyLoss 完全代替此函数。 参数: weight(Tensor)- 为每个类别的 loss 设置权值,常用于类别不均衡问题。weight 必须是 float 类型的 tensor,其长度要于类别 C 一致,即每一个类别都要设置有 weight。 size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为除以权重之和的平均 值;为 False 时,返回的各样本的 loss 之和。 reduce(bool)- 返回值是否为标量,默认为 True。 ignore_index(int)- 忽略某一类别,不计算其 loss,其 loss 会为 0,并且,在采用 size_average 时,不会计算那一类的 loss,除的时候的分母也不会统计那一类的样本。 实例: /Code/3_optimizer/3_1_lossFunction/4_NLLLoss.py 特别注意: 当带上权值,reduce = True, size_average = True, 其计算公式为: 例如当 input 为[[0.6, 0.2, 0.2], [0.4, 1.2, 0.4]],target= [0, 1], weight = [0.6, 0.2, 0.2] l1 = - 0.6*0.6 = - 0.36 l2 = - 1.2*0.2 = - 0.24 loss = -0.36/(0.6+0.2) + -0.24/(0.6+0.2) = -0.75 5. PoissonNLLLoss class torch.nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e- 08, reduce=None, reduction='elementwise_mean') 功能: 用于 target 服从泊松分布的分类任务。 计算公式: 34 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 参数: log_input(bool)- 为 True 时,计算公式为:loss(input,target)=exp(input) - target * input; 为 False 时,loss(input,target)=input - target * log(input+eps) full(bool)- 是否计算全部的 loss。例如,当采用斯特林公式近似阶乘项时,此为 target*log(target) - target+0.5∗log(2πtarget) eps(float)- 当 log_input = False 时,用来防止计算 log(0),而增加的一个修正项。即 loss(input,target)=input - target * log(input+eps) size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False 时,返回的各样本的 loss 之和。 reduce(bool)- 返回值是否为标量,默认为 True 实例: /Code/3_optimizer/3_1_lossFunction/5_PoissonNLLLoss.py 6. KLDivLoss class torch.nn.KLDivLoss(size_average=None, reduce=None, reduction='elementwise_mean') 功能: 计算 input 和 target 之间的 KL 散度( Kullback–Leibler divergence) 。 计算公式: (后面有代码手动计算,证明计算公式确实是这个,但是为什么没有对 x_n 计算对数 呢?) 补充:KL 散度 KL 散度( Kullback–Leibler divergence) 又称为相对熵(Relative Entropy),用于描述两个 概率分布之间的差异。计算公式(离散时): 其中 p 表示真实分布,q 表示 p 的拟合分布, D(P||Q)表示当用概率分布 q 来拟合真实 分布 p 时,产生的信息损耗。这里的信息损耗,可以理解为损失,损失越低,拟合分布 q 越接近真实分布 p。同时也可以从另外一个角度上观察这个公式,即计算的是 p 与 q 之间 的对数差在 p 上的期望值。 特别注意,D(p||q) ≠ D(q||p), 其不具有对称性,因此不能称为 K-L 距离。 35 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 信息熵 = 交叉熵 - 相对熵 从信息论角度观察三者,其关系为信息熵 = 交叉熵 - 相对熵。在机器学习中,当训练数 据固定,最小化相对熵 D(p||q) 等价于最小化交叉熵 H(p,q) 。 参数: size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值,平均值为 element-wise 的,而不是针对样本的平均;为 False 时,返回是各样本各维度的 loss 之和。 reduce(bool)- 返回值是否为标量,默认为 True。 使用注意事项: 要想获得真正的 KL 散度,需要如下操作: 1. reduce = True ;size_average=False 2. 计算得到的 loss 要对 batch 进行求平均 实例: /Code/3_optimizer/3_1_lossFunction/6_KLDivLoss.py 7. BCELoss class torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='element wise_mean') 功能: 二分类任务时的交叉熵计算函数。此函数可以认为是 nn.CrossEntropyLoss 函数的特 例。其分类限定为二分类,y 必须是{0,1}。还需要注意的是,input 应该为概率分布的形 式,这样才符合交叉熵的应用。所以在 BCELoss 之前,input 一般为 sigmoid 激活层的输 出,官方例子也是这样给的。该损失函数在自编码器中常用。 计算公式: 参数: weight(Tensor)- 为每个类别的 loss 设置权值,常用于类别不均衡问题。 size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False 时,返回的各样本的 loss 之和。 36 本教程仅限于学习交流使用,严禁用于商业用途! PyTorch 模型训练实用教程 作者:余霆嵩 reduce(bool)- 返回值是否为标量,默认为 True 8. BCEWithLogitsLoss class torch.nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reductio n='elementwise_mean', pos_weight=None) 功能: 将 Sigmoid 与 BCELoss 结合,类似于 CrossEntropyLoss(将 nn.LogSoftmax()和 nn.NLLLoss() 进行结合)。即 input 会经过 Sigmoid 激活函数,将 input 变成概率分布的形式。 计算公式: σ() 表示 Sigmoid 函数 特别地,当设置 weight 时: 参数: weight(Tensor)- : 为 batch 中单个样本设置权值,If given, has to be a Tensor of size “nbatch”. pos_weight-: 正样本的权重, 当 p>1,提高召回率,当 P