安装环境 Pytorch 的安装可参考官方教程
查看安装版本:
1 2 import torchprint (torch.__version__)
Cuda 统一计算设备架构 (Compute Unified Device Architecture, CUDA ),是由NVIDIA 推出的通用并行计算架构。通过Cuda,我们可以利用设备的GPU实现更高效的并行计算。
pytorch支持cuda,因此我们如果有此需求则需要为我们的设备安装cuda 。
使⽤ nvidia-smi 命令查看当前Cuda版本。通过如下代码检验GPU是否可用:
1 2 3 import torchprint (torch.version.cuda)print (torch.cuda.is_available())
注意:PyTorch的安装版本需要与系统的 Cuda 版本相匹配,见官方网站:Previous PyTorch Versions | PyTorch 。
Tensors Tensors,译为张量 ,是一种特殊的数据结构,它在结构上和n n n 维数组十分相似。
具有⼀个轴的张量对应数学上的向量(vector);具有两个轴的张量对应数学上的矩阵(matrix);具有两个轴以上的张量没有特殊的数学名称。
在 Pytorch 中,我们使用 Tensors 来给模型的输入输出以及参数进行编码,还可以跟踪计算图和计算梯度,可以在 GPU 或其他专用硬件上运行从而加速计算。除此之外,它的其他用法基本类似于 Numpy 中的 ndarrays。
创建 Tensor 直接创建 1 2 3 4 5 torch.arange(n) torch.zeros((m, n, l)) torch.ones((m, n, l)) torch.randn(m,n) torch.tensor([[2 , 1 , 4 , 3 ], [1 , 2 , 3 , 4 ], [4 , 3 , 2 , 1 ]])
从Numpy中创建 1 2 3 4 5 6 7 8 9 import torchimport numpy as npx = np.array([1 ,2 ,3 ]) a = torch.from_numpy(x) b = torch.Tensor(x) c = torch.tensor(x)
🧬 torch.Tensor() 是默认张量类型 torch.FloatTensor() 的别名,与小写 t 作区分
🤖 默认类型也可进行自定义修改:
1 torch.set_default_tensor_type(torch.DoubleTensor)
当然,我们也可以通过 numpy() 方法 把 Tensor 转换为 numpy 类型:
1 2 3 numpy_array = x.numpy() numpy_array = x.cpu.numpy() print (type (numpy_array))
导出&加载 Tensor PyTorch中的 Tensor 可以保存成 .pt 或者 .pth 格式的文件,使用 torch.save()方法保存张量,使用 torch.load()来读取张量:
1 2 3 4 5 x = torch.rand(4 ,5 ) torch.save(x, "./myTensor.pt" ) y = torch.load("./myTensor.pt" ) print (y)
更多:PyTorch教程:PyTorch中保存与加载tensor和模型详解
运算符 按元素运算 值得注意的是,在PyTorch Tensors里,形如 X = X+Y 的代码实际上会生成新的引用指向新开辟的内存 X ,旧 X 依然存在。而 X += Y 或 X[:] = X+Y 则直接对原内存数据进行更新 。
可以用 id() 函数进行验证:
1 2 3 4 5 6 7 8 9 10 11 import torchX = torch.tensor([10 ]) Y = torch.tensor([10 ]) print (1 ,id (X))X = X + Y print (2 ,id (X))X[:] = X + Y print (3 ,id (X))X += Y print (4 ,id (X))
其他运算符,如 +, -, *, / 和 ** 以及 .exp(x),均默认是对张量按元素进行计算(而非矩阵意义上的)。
判断语句 类似于 X == Y 这样的判断语句,则返回和X , Y X,Y X , Y 形状一样的二值张量,在x i = y i x_i=y_i x i = y i 的位置值为 True。
求和运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 x = torch.arange(12 , dtype=torch.float32) y = x.clone() y.reshape(3 ,4 ) x.sum () y.sum (axis=0 ) y.sum (axis=1 ) y.sum (axis=[0 , 1 ]) y.sum (axis=1 , keepdims=True ) y.cumsum(axis=0 )
均值计算 1 2 3 x.mean() x.sum () / x.numel()
同样也可以根据指定参数 axis 的值来实现按某一轴(axis)/维度 求均值。
向量点积 向量x \mathbf x x 与向量y \mathbf y y 的点积可以表示为x T y \mathbf x^T\mathbf y x T y 或( x , y ) (\mathbf x,\mathbf y) ( x , y ) 。在数值上相当于二者按元素相乘并求和。
矩阵相乘 在线性代数中,矩阵与向量的乘积 和 矩阵与矩阵的乘积 别无二致。也是因为线性代数里一般默认向量是指列向量。
而在 Torch 中,所谓向量就是指一维张量,默认轴 axis=0,所以可以看作“行向量”。因此,在 Torch 中独立出一个运算方法给矩阵与向量的乘积 :
最终返回的依然是一维张量。
而一般的矩阵乘法 所用的语句是:
此外还有:
torch.mul(a, b) 是矩阵 a 和 b 对应位相乘,即阿达玛积 ,a和b的形状必须相同;torch.bmm(a, b) a batch matrix-matrix product,针对三维张量 a和b ,要求它们的第0维(dim=0)的大小相等——批次大小,然后剩下的两个维度作矩阵乘法 。a (b,m,n) * b (b,n,l) -> output (b,m,l);torch.matmul() 没有强制规定维度和大小,可以用利用广播机制 进行不同维度的相乘操作详见:torch.mul() 、 torch.mm() 及torch.matmul()的区别 - 简书
L2范数 Torch 中提供了直接计算向量/一维张量 的 L2范数的方法:
事实上,对于矩阵的Frobenius范数,也同样适用,即可以使用 torch.norm(A) 计算矩阵A A A 的Frobenius范数。
此外,根据L1范数的定义,我们也可以直接用 torch.abs(x).sum() 来计算向量的 L1范数。
🔔连结/拼接 和 Pandas.DataFrame 一样,我们也可以把多个张量连结(concatenate)在⼀起,把它们端对端地叠起来形成⼀个更⼤的张量。
1 2 3 4 5 X = torch.arange(12 , dtype=torch.float32).reshape((3 ,4 )) Y = torch.tensor([[2.0 , 1 , 4 , 3 ], [1 , 2 , 3 , 4 ], [4 , 3 , 2 , 1 ]]) torch.cat((X, Y), dim=0 ), torch.cat((X, Y), dim=1 )
1 2 3 4 5 6 7 8 9 10 11 12 # output (tensor([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [ 2., 1., 4., 3.], [ 1., 2., 3., 4.], [ 4., 3., 2., 1.]]), #按行拼接/dim=0 tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.], [ 4., 5., 6., 7., 1., 2., 3., 4.], [ 8., 9., 10., 11., 4., 3., 2., 1.]])) #按列拼接/dim=1
🔔维度转换 升维和降维 1 2 3 4 5 6 7 8 squeeze unsqueeze view reshape
交换维度 (推荐)使用 einops 包
待更:https://blog.csdn.net/qq_37297763/article/details/120348764
自动求导 假设欲对函数y = 2 x T x y = 2\mathbf x^T\mathbf x y = 2 x T x 关于列向量x \mathbf x x 求导。 事实上我们已经有先验知识了,即∇ y = 4 x \nabla y=4\mathbf x ∇ y = 4 x ,接下来使用 Torch 进行验证即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import torchx = torch.arange(4.0 ) x.requires_grad_(True ) y = 2 *torch.dot(x, x) print (x.grad) y.backward() print (x.grad)x.grad.zero_()
在我们计算y y y 关于x \mathbf x x 的梯度之前,需要⼀个地⽅来存储梯度:x.requires_grad_(True)。
重要的是,我们不会在每次对⼀个参数求导时都分配新的内存。因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。 所以,我们调用的是带有下划线 _ 后缀的函数,这种可以改变 tensor 变量的操作都带有后缀,例如 x.copy_(y), x.t_() 都可以直接在原内存上改变 x 变量。
非标量反向传播 在PyTorch中有个简单的规定,不让张量对张量求导,只允许标量对张量求导。 因此,目标量对一个非标量调用 backward(),则需要传入一个gradient参数用于把张量对张量的求导转换为标量对张量的求导。
具体来说,其运作机制如下: 若y = f ( x ) \mathbf y=\boldsymbol f(\mathbf x) y = f ( x ) ,其中y , x \mathbf y,\mathbf x y , x 均为列向量,则在 PyTorch 中,给定 gradient 这个0-1向量:v , v i ∈ { 0 , 1 } \mathbf v,v_i\in\{0,1\} v , v i ∈ { 0 , 1 } ,在 y.backward(v) 之后得到的 x.grad 为:
∇ ( v T y ) = ∇ ( ∑ y i ⋅ v i ) \nabla(\mathbf v^T\mathbf y)=\nabla\left(\sum y_i· v_i\right) ∇ ( v T y ) = ∇ ( ∑ y i ⋅ v i )
因此,若想计算非标量y \mathbf y y 关于x \mathbf x x 实际的雅可比矩阵,则需要依次对v i v_i v i 进行置1(其余置0)计算得到y i y_i y i 的梯度,最后拼接起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 x = torch.tensor([2 , 3 ], dtype=torch.float , requires_grad=True ) y = torch.zeros(1 , 2 ) J = torch.zeros(2 , 2 ) y[0 , 0 ] = x[0 ] ** 2 + 3 * x[1 ] y[0 , 1 ] = x[1 ] ** 2 + 2 * x[0 ] y.backward(torch.Tensor([[1 , 0 ]]), retain_graph=True ) J[0 ] = x.grad x.grad.zero_() y.backward(torch.Tensor([[0 , 1 ]])) J[1 ] = x.grad print ("J雅克比矩阵的值:{}" .format (J))
注意:这里因为重复使用backward(),需要使参数retain_graph=True 避免计算图被清除。
分离计算 有时,我们希望将某些计算移动到记录的计算图之外。 例如,函数z z z 包含有x \mathbf x x 和y y y ,而y y y 本身又是x \mathbf x x 的函数。我们想计算z z z 关于x \mathbf x x 的梯度,但不希望y y y 作为x \mathbf x x 的函数被纳入计算,而是作为常数计算。
举例说明,假设z = y + x , y = x 2 z=y+x,\;y=x^2 z = y + x , y = x 2 ,当x = 3 x=3 x = 3 时,有y = 9 y=9 y = 9 。 我们希望计算得到z x ′ = ( 9 + x ) ′ ∣ x = 3 = 1 z'_x=(9+x)'|_{x=3}=1 z x ′ = ( 9 + x ) ′ ∣ x = 3 = 1 ,而不是z x ′ = ( x 2 + x ) ′ ∣ x = 3 = 7 z_x'=(x^2+x)'|_{x=3}=7 z x ′ = ( x 2 + x ) ′ ∣ x = 3 = 7
为了应对这种问题,PyTorch提供了 detach() 方法,分离y y y 来返回⼀个新变量u u u ,该变量与y y y 具有相同的值,但丢弃了计算图中如何计算y y y 的任何信息。
1 2 3 4 5 6 7 8 9 10 import torchx = torch.tensor([3. ], requires_grad=True ) y = x**2 u = y.detach() z = u + x z.backward() print (x.grad == torch.tensor([1. ]))
控制流的梯度 在 PyTorch 中,函数y = f ( x ) y=f(\mathbf x) y = f ( x ) 即使是很难写出清晰明朗的公式,只能利用 Python 的基本控制流语句,如for, while ,if 等,来对f f f 进行定义,那么也同样能计算梯度。
例如:f = { ∑ i = 0 4 x i + 114 , x 5 > 114514 ∑ i = 0 5 , x 5 ≤ 114514 \begin{aligned}f=\begin{cases}\sum_{i=0}^4 x_i+114,x_5>114514\\\sum_{i=0}^5,x_5\leq 114514\end{cases}\end{aligned} f = { ∑ i = 0 4 x i + 114 , x 5 > 114514 ∑ i = 0 5 , x 5 ≤ 114514 这类复杂函数,我们可以在Python中定义:
1 2 3 4 5 6 7 8 9 def f (x ): y = 0 for i in range (5 ): y += x[i] if (x[5 ] > 114514 ): y += 114 else : y += x[5 ] return y
当x = ( 1 , 1 , 1 , 1 , 1 , 120000 ) T \mathbf x=(1,1,1,1,1,120000)^T x = ( 1 , 1 , 1 , 1 , 1 , 120000 ) T 时,手工计算梯度可得,∇ f = [ 1 , 1 , 1 , 1 , 1 , 0 ] \begin{aligned}\nabla f=[1,1,1,1,1,0]\end{aligned} ∇ f = [ 1 , 1 , 1 , 1 , 1 , 0 ] 。
自动求导验证一下:
1 2 3 4 5 6 7 x = torch.tensor([1 ,1 ,1 ,1 ,1 ,120000 ], dtype=torch.float32, requires_grad=True ) y = f(x) y.backward() ans = (x.grad == torch.cat((torch.ones(5 ),torch.zeros(1 )),dim=0 )) print (ans)
数据集处理 批量读取加载器 在深度学习中,我们常常需要从大量原始数据集中取出样本数为B \cal B B (batch size)的小批量样本数据,用于展示、梯度下降优化损失函数、可视化等等。
待更:https://blog.csdn.net/qq_45634934/article/details/125408600
构建数据集对象 为了应对自己的任务(而不是利用现有的开源数据集进行实验),我们需要构建自己的数据集从而在PyTorch中实现批量读取和后续的模型训练。
PyTorch的 torch.utils.Data.DataSet 是可用于后续任务的数据集抽象类 。其源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Dataset (object ): r"""An abstract class representing a :class:`Dataset`. All datasets that represent a map from keys to data samples should subclass it. All subclasses should overwrite :meth:`__getitem__`, supporting fetching a data sample for a given key. Subclasses could also optionally overwrite :meth:`__len__`, which is expected to return the size of the dataset by many :class:`~torch.utils.data.Sampler` implementations and the default options of :class:`~torch.utils.data.DataLoader`. .. note:: :class:`~torch.utils.data.DataLoader` by default constructs a index sampler that yields integral indices. To make it work with a map-style dataset with non-integral indices/keys, a custom sampler must be provided. """ def __getitem__ (self, index ): raise NotImplementedError def __add__ (self, other ): return ConcatDataset([self, other])
我们要想构建自己的数据集,就必须编写自己的数据集类,并且继承自 Datasets 。
源码还指出,我们必须在自己的数据集类里重载 __init__() 和 __getitim__() ,前者一般用于连接文件路径和文件本身,后者 __getitim__() 应该编写支持数据集索引的函数,使得通过它可以直接读取指定索引下的数据。一般地,我们还需要实现 __len__() 返回数据集的样本数。
以图像数据集为例,下面是 CIFAR10 数据集的构建方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import jsonimport matplotlib.pyplot as pltimport numpy as npfrom torch.utils.data import Dataset,DataLoaderclass CIFAR10_IMG (Dataset ): def __init__ (self, root, train=True , transform = None , target_transform=None ): super (CIFAR10_IMG, self).__init__() self.train = train self.transform = transform self.target_transform = target_transform if self.train : file_annotation = root + '/annotations/cifar10_train.json' img_folder = root + '/train_cifar10/' else : file_annotation = root + '/annotations/cifar10_test.json' img_folder = root + '/test_cifar10/' fp = open (file_annotation,'r' ) data_dict = json.load(fp) assert len (data_dict['images' ])==len (data_dict['categories' ]) num_data = len (data_dict['images' ]) self.filenames = [] self.labels = [] self.img_folder = img_folder for i in range (num_data): self.filenames.append(data_dict['images' ][i]) self.labels.append(data_dict['categories' ][i]) def __getitem__ (self, index ): img_name = self.img_folder + self.filenames[index] label = self.labels[index] img = plt.imread(img_name) img = self.transform(img) return img, label def __len__ (self ): return len (self.filenames)
文件读写 保存训练的模型可以使得将来在各种环境中模型得以使⽤(⽐如在部署中进⾏预测)。 此外,当运⾏⼀个耗时较⻓的训练过程时,最佳的做法是定期保存中间结果,以确保在服务器电源被不⼩⼼断掉时,不会损失⼏天的计算结果。
因此, PyTorch 同样提供了对张量、参数、以及整个模型的保存与加载 功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 x = torch.arange(4 ) y = torch.zeros(4 ) torch.save(x, 'x-file' ) x2 = torch.load('x-file' ) torch.save([x, y],'x-files' ) x2, y2 = torch.load('x-files' ) mydict = {'x' : x, 'y' : y} torch.save(mydict, 'mydict' ) mydict2 = torch.load('mydict' )
模型的读写 保存单个权重向量(或其他张量)确实有⽤,但是如果我们想保存整个模型,并在以后加载它们,单独保存每个向量则会变得很⿇烦。毕竟,我们可能有数百个参数散布在各处。因此,深度学习框架提供了内置函数来保存和加载整个⽹络。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import torch.nn as nnimport torch.nn.functional as Fclass MLP (nn.Module): def __init__ (self ): super ().__init__() self.hidden = nn.Linear(20 , 256 ) self.out = nn.Linear(256 , 10 ) def forward (self, X ): return self.out(F.relu(self.hidden(X))) net = MLP() X = torch.randn(size=(2 , 20 )) Y = net(X) torch.save(net.state_dict(), 'mlp.params' ) clone = MLP() clone.load_state_dict(torch.load('mlp.params' )) clone.eval () Y_clone = clone(X) Y_clone == Y
深度学习训练模板 总体框架 自定义网络 为了实现复杂的神经网络搭建,深度学习中引⼊了神经⽹络块的概念。块(block)可以描述单个层、由多个层组成的组件或整个模型本⾝。
每个块必须提供的基本功能如下:
将输⼊数据作为其前向传播函数的参数。 通过前向传播函数来⽣成输出。请注意,输出的形状可能与输⼊的形状不同。 计算其输出关于输⼊的梯度,可通过其反向传播函数进⾏访问。通常这是⾃动发⽣的。 存储和访问前向传播计算所需的参数。 根据需要初始化模型参数 以 MLP 为例进行自定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import torch.nn as nnimport torch.nn.functional as Fclass MyLinear (nn.Module): def __init__ (self, in_units, units ): super ().__init__() self.weight = nn.Parameter(torch.randn(in_units, units)) self.bias = nn.Parameter(torch.randn(units,)) def forward (self, X ): linear = torch.matmul(X, self.weight.data) + self.bias.data return F.relu(linear) class MLP (nn.Module): def __init__ (self ): super ().__init__() self.hidden = MyLinear(20 , 256 ) self.out = MyLinear(256 , 10 ) def forward (self, X ): return self.out(F.relu(self.hidden(X)))
访问参数 当通过 Sequential 类定义模型时,我们可以通过索引来访问模型的任意层:
1 2 3 4 5 6 7 8 9 10 print (net[2 ].state_dict()) print (type (net[2 ].bias))print (net[2 ].bias)print (net[2 ].bias.data) print (net.state_dict()['2.bias' ].data) print (net[2 ].weight.grad) print (*[(name, param.shape) for name, param in net.named_parameters()])
参数的常见初始化 通过 net.apply() 可以实现很多个性化的参数初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from torch.nn import initdef init_normal (m ): if type (m) == nn.Linear: nn.init.normal_(m.weight, mean=0 , std=0.01 ) nn.init.zeros_(m.bias) def init_constant (m ): if type (m) == nn.Linear: nn.init.constant_(m.weight, 1 ) nn.init.zeros_(m.bias) def init_xavier (m ): if type (m) == nn.Linear: nn.init.xavier_uniform_(m.weight) net.apply(init_normal) net[0 ].weight.data[:] += 1 net[0 ].weight.data[0 , 0 ] = 42
参数绑定 有时候我们需要共享模型参数,比如在循环神经网络中同一个层循环使用。事实上,模型参数其实就是一个 Tensor 的子类,也就是一个张量,要共享它,只需要多次调用同一个层即可。 因为调用同一个层,计算的时候就是使用的这个层代表的张量,所以多次调用同一个层相当于共享了模型参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 shared = nn.Linear(8 , 8 ) net = nn.Sequential(nn.Linear(4 , 8 ), nn.ReLU(), shared, nn.ReLU(), shared, nn.ReLU(), nn.Linear(8 , 1 )) net(X) print (net[2 ].weight.data[0 ] == net[4 ].weight.data[0 ])net[2 ].weight.data[0 , 0 ] = 100 print (net[2 ].weight.data[0 ] == net[4 ].weight.data[0 ])
当参数绑定时,梯度会发⽣什么情况? 答案是由于模型参数包含梯度,因此在反向传播期间第⼆个隐藏层(即第三个神经⽹络层)和第三个隐藏层(即第五个神经⽹络层)的梯度会加在⼀起 。
优化器选取 Pytorch优化器全总结(四)常用优化器性能对比 含代码_优化器比较-CSDN博客
GPU加速 首先确保⾄少安装了⼀个NVIDIA GPU 并且下载安装了 NVIDIA 驱动 和 CUDA (本文开头已提及)
终端中可以使⽤ nvidia-smi 命令查看显卡信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 +---------------------------------------------------------------------------------------+ | NVIDIA-SMI 535.98 Driver Version: 535.98 CUDA Version: 12.2 | |-----------------------------------------+----------------------+----------------------+ | GPU Name TCC/WDDM | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+======================+======================| | 0 NVIDIA GeForce RTX 4070 WDDM | 00000000:01:00.0 Off | N/A | | 0% 41C P8 6W / 200W | 0MiB / 12282MiB | 0% Default | | | | N/A | +-----------------------------------------+----------------------+----------------------+ +---------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=======================================================================================| | No running processes found | +---------------------------------------------------------------------------------------+
在PyTorch中,每个数组都有⼀个设备(device),我们通常将其称为环境(context)。默认情况下,所有变量和相关的计算都分配给CPU。
CPU 和 GPU 可以⽤ torch.device('cpu') 和 torch.device('cuda')表⽰; 如果有多个 GPU,我们使⽤ torch.device(f'cuda:{i}') 来表⽰第i i i 块GPU(i i i 从0开始),其中 cuda:0 和 cuda 等价。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 torch.cuda.device_count() def try_gpu (i=0 ): """如果存在,则返回gpu(i),否则返回cpu()""" if torch.cuda.device_count() >= i + 1 : return torch.device(f'cuda:{i} ' ) return torch.device('cpu' ) def try_all_gpus (): """返回所有可⽤的GPU,如果没有GPU,则返回[cpu(),]""" devices = [torch.device(f'cuda:{i} ' ) for i in range (torch.cuda.device_count())] return devices if devices else [torch.device('cpu' )]
我们可以通过 .to(device) 方法将数据安排在 GPU 下进行处理和运算。
模型的保存和载入 内置网络层 待更:Models and pre-trained weights — Torchvision 0.16 documentation (pytorch.org)
预训练模型修改 待更:https://blog.csdn.net/andyL_05/article/details/108930240
学习率调整 lr_scheduler模块PyTorch 中的学习率调整策略通过 torch.optim.lr_scheduler 模块实现。
torch.optim.lr_scheduler.LambdaLR1 torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1 )
根据下列公式逐步更新学习率:
η ( t ) = λ × η ( 0 ) \eta^{(t)}=\lambda\times\eta^{(0)} η ( t ) = λ × η ( 0 )
其中,η ( 0 ) \eta^{(0)} η ( 0 ) 是初始学习率。
参数:
optimizer (Optimizer):要更改学习率的优化器;lr_lambda(function or list):提供关于 epoch的函数λ \lambda λ ;或者是一个 List 给出各个parameter groups 的学习率更新用到的λ \lambda λ ;last_epoch (int):最后一个 epoch 的 index,如果是训练了很多个 epoch 后中断了,继续训练,这个值就等于加载的模型的 epoch。默认为 -1 表示从头开始训练,即从epoch=1开始。1 2 3 4 5 6 7 8 9 10 11 12 13 14 optimizer_1 = torch.optim.Adam(net_1.parameters(), lr = initial_lr) scheduler_1 = LambdaLR(optimizer_1, lr_lambda=lambda epoch: 1 /(epoch+1 )) print ("初始化的学习率:" , optimizer_1.defaults['lr' ])for epoch in range (1 , 11 ): optimizer_1.zero_grad() optimizer_1.step() print ("第%d个epoch的学习率:%f" % (epoch, optimizer_1.param_groups[0 ]['lr' ])) scheduler_1.step()
torch.optim.lr_scheduler.StepLR1 torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1 , last_epoch=-1 )
每过 step_size 个 epoch,做一次更新:
η ( t ) = γ ⌊ e p o c h s t e p _ s i z e ⌋ × η ( 0 ) \eta^{(t)}=\gamma^{\lfloor{\frac{epoch}{step\_size}}\rfloor}\times\eta^{(0)} η ( t ) = γ ⌊ s t e p _ s i ze e p oc h ⌋ × η ( 0 )
使用方法同上,此后不再给出示例。
torch.optim.lr_scheduler.MultiStepLR1 2 torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1 , last_epoch=-1 )
这是一个按需调整学习率的方法。与 StepLR只能每间隔 step_size 个 epoch 再变更学习率不同,该方法变换学习率的时间是可以灵活自定义调节的——通过传入 milestiones 参数(列表类型),很适合后期调试使用:观察 loss 曲线,为每个实验定制学习率调整时机。
例如,取milestiones=[10,20,50,80],若当前 epoch=34,它坐落在区间[ 20 , 50 ) [20,50) [ 20 , 50 ) 内,那么因子γ \gamma γ 的指数就是2 2 2 ,2 2 2 是 milestiones[x]==20 的索引 x。
事实上,上述查找 epoch 处于第几个区间的方法可以用 Python 的 bisect.bisect_right(a, x) 实现——这个函数表示利用二分查找法查找元素x x x 在有序序列 a a a 中的插入位置。
故更新公式可以写为:
η ( t ) = γ bisect_right ( m i l e s t o n e s , e p o c h ) × η ( 0 ) \eta^{(t)}=\gamma^{\text{bisect\_right}(milestones,\;epoch)}\times\eta^{(0)} η ( t ) = γ bisect_right ( mi l es t o n es , e p oc h ) × η ( 0 )
ExponentialLR & CosineAnnealingLR 1 2 3 torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma, last_epoch=-1 ) torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max, eta_min=0 , last_epoch=-1 )
即 指数衰减法 和 余弦退火法。
指数衰减法相当于每隔一个 epoch 就衰减一次,等同于 StepLR 方法中取 step_size=1。
余弦退火法的更新公式如下:
η ( t ) = η min + ( η ( 0 ) − η min ) × ( 1 − cos e p o c h T max π ) \eta^{(t)}=\eta_{\min}+(\eta^{(0)}-\eta_{\min})\times\left(1-\cos{\frac{epoch}{T_{\max}}\pi}\right) η ( t ) = η m i n + ( η ( 0 ) − η m i n ) × ( 1 − cos T m a x e p oc h π )
其中,\eta_\min 即参数 eta_min 表示最小学习率,T_\max 即参数 T_max 代表每隔 T_max 个 epoch 走过一个cos x \cos x cos x 函数的1 / 2 1/2 1/2 周期,即学习率下降到最小值,然后再过 T_max 个 epoch 上升到学习率的最大值。(可回忆余弦函数的图像)
自适应调整学习率 1 torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min' , factor=0.1 , patience=10 , verbose=False , threshold=0.0001 , threshold_mode='rel' , cooldown=0 , min_lr=0 , eps=1e-08 )
不依赖 epoch 的学习率调整方法只有 ReduceLROnPlateau。
当某指标 不再变化(下降或升高)时才开始调整学习率,这是非常实用的学习率调整策略。 例如,当验证集的 loss 不再下降时,进行学习率调整;或者监测验证集的 accuracy,当其不再上升时,才调整学习率。
部分参数解释:
mode(str):只能是 min 或 max,指定是看指标上升还是下降;factor(float):等同于其他方法中的 gamma;patience(int):在指标停止优化 patience 个 epoch 后减小lr;verbose(bool):如果为 True,在更新 lr后 print一个更新信息,默认为False;threshold_mode (str):选择判断指标是否达最优的模式,有两种模式, rel 和 abs。pytorch_warmup模块Warmup是在 ResNet 论文中提到的一种学习率预热的方法,它在训练开始的时候先选择使用一个较小的学习率,训练了一些 epoches 或者 steps (比如 :4epoches,10000steps) 之后再修改为预先设置的学习率来进行训练。
由于刚开始训练时,模型的权重是随机初始化的,此时若选择一个较大的学习率可能带来模型的不稳定(振荡)。如果选择Warmup的方式,可以使得开始训练的一段时间内学习率较小,模型可以慢慢趋于稳定;等模型相对稳定后再选择预先设置的学习率进行训练,这又使得模型收敛速度变得更快,模型效果更佳。
更多原理:为什么训练的时候warm up这么重要?一文理解warm up原理 - 知乎
constant warmup Resnet 论文中使用一个110层的 ResNet 在 cifar10 上训练时,先用 0.01 的学习率训练直到训练误差低于80%(大概训练了400个steps),然后使用 0.1 的学习率进行训练。
gradual warmup constant warmup的不足之处在于从一个很小的学习率一下变为比较大的学习率可能会导致训练误差突然增大。 于是2018年 Facebook 提出了gradual warmup来解决这个问题,即从最初的小学习率开始,每个step增大一点点,直到达到最初设置的比较大的学习率 时,再采用最初设置的学习率进行训练。 接着,随着训练的进行,我们希望再逐渐减小学习率,也就是学习率此后是衰减 的,这有助于使模型收敛速度变快,效果更佳。
这里的衰减策略又可以分为:指数衰减调整 (Exponential) 和 余弦退火 (CosineAnnealing)。
代码实现 虽然 PyTorch 官方提供了学习率各种调整的方法,但是并没有自带 Warmup 的方法。 我们可以通过 pip install -U pytorch_warmup 可安装此模块。
作者也提供了不同的预热方法,可参见仓库 README.md。下面我们给出简单的使用方法(搭配 scheduler 使用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import torchimport pytorch_warmup as warmupoptimizer = torch.optim.AdamW(params, lr=0.001 , betas=(0.9 , 0.999 ), weight_decay=0.01 ) num_steps = len (dataloader) * num_epochs lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_steps) warmup_scheduler = warmup.UntunedLinearWarmup(optimizer) for epoch in range (1 ,num_epochs+1 ): for batch in dataloader: optimizer.zero_grad() loss = ... loss.backward() optimizer.step() with warmup_scheduler.dampening(): lr_scheduler.step()
超参数优化 超参数优化完整指南 (超长文) - 知乎 (zhihu.com)
TensorBoard 可视化训练 待更:【pytorch】使用tensorboard进行可视化训练-CSDN博客 How to use TensorBoard with PyTorch — PyTorch Tutorials 2.1.1+cu121 documentation Tensorboard的使用 ---- SummaryWriter类(pytorch版)-CSDN博客
部分提速技巧 改动一行代码,PyTorch训练三倍提速,这些「高级技术」是关键 - 机器之心
用上Pytorch Lightning的这六招,深度学习pipeline提速10倍! - 知乎
Pytorch如何加速,让GPU利用率维持较高水平? - 知乎
附加:PyTorch Lightning PyTorch Lightning (PL) 是面向专业人工智能研究人员和机器学习工程师的深度学习框架。它继承自 PyTorch,是 PyTorch 再一次经过封装后得到的、更易于快速搭建模型 的库。此外,它也更易于在多卡训练时进行调试工作。
官方文档:Welcome to ⚡ PyTorch Lightning — PyTorch Lightning documentation
有学者针对 From PyTorch to PyTorchLightning 做了一些更加便于 PL 新手快速迁移 PyTorch 代码和搭建项目的工作。本章也将根据该方法进行总结和修改,形成自用的一套逻辑。
Trainer 在 Lightning 中可以通过简单的“函数调用”来对模型进行训练。具体来说是通过一个名为 Trainer 的类实现了。
实际上 Trainer 的功能远不止训练这么简单。它还可以处理所有循环体 (下一节介绍)中的其他细节,比如:
自动开启或关闭梯度 Automatically enabling/disabling grads 驱动训练集、验证集和测试集的迭代器 Running the training, validation and test dataloaders 在适当的时机调用可自定义的 CallBack 函数 Calling the Callbacks at the appropriate times 将批量数据和计算结果运行在适当的 CPU/GPU/TPU 上 Putting batches and computations on the correct devices 模型训练 最基本的使用情形下,在主函数中,可以通过 .fit() 方式开启模型训练:
1 2 3 4 model = MyLightningModule() trainer = Trainer() Trainer.fit(model, train_dataloader=..., val_dataloaders=None , datamodule=None )
输入第一变量一定是LightningModule对象,然后可以跟一个 LigntningDataModule 对象,或一个普通的Train DataLoader用于训练。如果定义了验证循环体,也要有用于验证的 Val DataLoader。
LightningModule PL 中的 LightningModule 类是构建整个模型架构的核心。它除了建立模型外,还需要定义深度学习流程中的其他循环体,从而直接将 PyTorch 的下列对应的相关代码统合起来了。
初始化 Initialization (__init__() and setup()) 前向传播 Forward(forward()) 训练循环体 Train Loop (training_step()) 验证循环体 Validation Loop (validation_step()) 测试循环体 Test Loop (test_step()) 预测循环体 Prediction Loop (predict_step()) 优化器与学习率 Optimizers and LR Schedulers (configure_optimizers()) 模型构建 Class LightningModule 中的 __init__(), steup(), forward() 方法都是为模型构建服务的。
实际上,此处的代码填写方式和 PyTorch 完全没有区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import torchimport lightning as plclass MyLightningModule (pl.LightningModule): def __init__ (self, dim_in, dim_out, **kargs ): super ().__init__() self.save_hyperparameters() self.model = ... def forward (self, x ): return self.model(x) def print_text (self ): if hasattr (self.hparams, 'text' ): print (self.hparams.text)
值得注意的是,使用了 self.save_hyperparameters() 之后,传入这个 Class 的参数得以保存在 self.hparams 中。
例如,我们在生成Class的实例时,除了 dim_in 和 dim_out 外,还传入了参数 text。那么我们可以通过 self.hparams.text 访问它。
训练循环体 在LightningModule类中实现training_step()方法:
1 2 3 4 5 6 7 8 9 def training_step (self, batch, batch_idx ): inputs, target = batch output = self(inputs) loss = self.loss_function(output, target) self.log("train_loss" , loss, on_step=True , on_epoch=True , prog_bar=True , logger=True ) return loss
在训练时,PL会根据training_step()自动通过下面这段伪代码 进行训练:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 torch.set_grad_enabled(True ) for batch_idx, batch in enumerate (train_dataloader): loss = training_step(batch, batch_idx) optimizer.zero_grad() loss.backward() optimizer.step()
优化器和学习率 Early stopping Tuner Tuner — PyTorch Lightning 2.1.2 documentation
1 2 tuner = Tuner(trainer) tuner.lr_find(model=model, train_dataloaders=...)
LightningDataModule CheckPoint Callback 可以通过自定义函数,在训练过程中定义好的时间触发该函数。一个简单的示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from lightning.pytorch.callbacks import Callbackclass PrintCallback (Callback ): def on_train_start (self, trainer, pl_module ): print ("Training is started!" ) def on_train_end (self, trainer, pl_module ): print ("Training is done." ) trainer = Trainer(callbacks=PrintCallback()) trainer = Trainer(callbacks=[PrintCallback()])
模板架构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 root- |-data |-__init__.py |-data_interface.py |-xxxdataset1.py |-xxxdataset2.py |-... |-model |-__init__.py |-model_interface.py |-xxxmodel1.py |-xxxmodel2.py |-... |-main.py |-utils.py
参考 动手学习深度学习|D2L Discussion - Dive into Deep Learning 60分钟快速入门 PyTorch - 知乎 PyTorch中的非标量反向传播-CSDN博客 pytorch之warm-up预热学习策略_pytorch warmup_还能坚持的博客-CSDN博客 torch.optim.lr_scheduler:调整学习率-CSDN博客 Pytorch Lightning 完全攻略 - 知乎