做推广用那个网站,wordpress缩略图错乱,网站分站如何做,wordpress apple主题PyTorch的简洁设计使得它易于入门#xff0c;在深入介绍PyTorch之前#xff0c;本文先介绍一些PyTorch的基础知识#xff0c;以便读者能够对PyTorch有一个大致的了解#xff0c;并能够用PyTorch搭建一个简单的神经网络。 1 Tensor Tensor是PyTorch中最重要的数据结构#… PyTorch的简洁设计使得它易于入门在深入介绍PyTorch之前本文先介绍一些PyTorch的基础知识以便读者能够对PyTorch有一个大致的了解并能够用PyTorch搭建一个简单的神经网络。 1 Tensor Tensor是PyTorch中最重要的数据结构它可以是一个数标量、一维数组向量、二维数组如矩阵、黑白图片等或者更高维的数组如彩色图片、视频等。Tensor与NumPy的ndarrays类似但Tensor可以使用GPU加速。下面通过几个示例了解Tensor的基本使用方法 In: import torch as tt.__version__ # 查看pytorch的版本信息Out:1.8.0In: # 构建一个2×3的矩阵只分配了空间未初始化其数值取决于内存空间的状态x t.Tensor(2, 3) # 维度2×3xOut:tensor([[7.9668e-37, 4.5904e-41, 7.9668e-37],[4.5904e-41, 0.0000e00, 0.0000e00]])注意torch.Tensor()可以使用int类型的整数初始化矩阵的行、列数torch.tensor()需要确切的数据值进行初始化。 In: y t.Tensor(5)print(y.size())z t.tensor([5]) # torch.tensor需要确切数值进行初始化print(z.size())
Out:torch.Size([5])torch.Size([1])
In: # 使用正态分布初始化二维数组x t.rand(2, 3) x
Out:tensor([[0.1533, 0.9600, 0.5278],[0.5453, 0.3827, 0.3212]])In: print(x.shape) # 查看x的形状x.size()[1], x.size(1) # 查看列的个数, 这两种写法等价Out:torch.Size([2, 3])(3, 3)
In: y t.rand(2, 3)# 加法的第一种写法x y
Out:tensor([[1.1202, 1.6476, 1.1220],[1.0161, 1.1325, 0.3405]])
In: # 加法的第二种写法t.add(x, y)
Out:tensor([[1.1202, 1.6476, 1.1220],[1.0161, 1.1325, 0.3405]])
In: # 加法的第三种写法指定加法结果的输出目标为resultresult t.Tensor(2, 3) # 预先分配空间t.add(x, y, outresult) # 输入到resultresult
Out:tensor([[1.1202, 1.6476, 1.1220],[1.0161, 1.1325, 0.3405]])
In: print(初始的y值)print(y)print(第一种加法y的结果)y.add(x) # 普通加法不改变y的值print(y)print(第二种加法y的结果)y.add_(x) # inplace加法y改变了print(y)
Out:初始的y值tensor([[0.9669, 0.6877, 0.5942],[0.4708, 0.7498, 0.0193]])
第一种加法y的结果tensor([[0.9669, 0.6877, 0.5942],[0.4708, 0.7498, 0.0193]])
第二种加法y的结果tensor([[1.1202, 1.6476, 1.1220],[1.0161, 1.1325, 0.3405]])注意函数名后面带下划线_的函数称为inplace操作会修改Tensor本身。例如x.add_(y)和x.t_()会改变 xx.add(y)和x.t()返回一个新的Tensorx不变。 In: # Tensor的索引操作与NumPy类似x[:, 1]
Out:tensor([0.8969, 0.7502, 0.7583, 0.3251, 0.2864])Tensor和NumPy数组之间的相互操作非常容易且快速。对于Tensor不支持的操作可以先转为NumPy数组进行处理之后再转回Tensor。
In: a t.ones(5) # 新建一个全1的Tensora
Out:tensor([1., 1., 1., 1., 1.])
In: b a.numpy() # Tensor → NumPyb
Out:array([1., 1., 1., 1., 1.], dtypefloat32)
In: import numpy as npa np.ones(5)b t.from_numpy(a) # NumPy → Tensorprint(a)print(b)
Out:[1. 1. 1. 1. 1.]tensor([1., 1., 1., 1., 1.], dtypetorch.float64)因为Tensor和NumPy对象大多数情况下共享内存所以它们之间的转换很快几乎不会消耗资源。这也意味着其中一个发生了变化另外一个会随之改变。 In: b.add_(1) # 以下划线结尾的函数会修改自身print(b)print(a) # Tensor和NumPy共享内存Out:tensor([2., 2., 2., 2., 2.], dtypetorch.float64)[2. 2. 2. 2. 2.]如果想获取Tensor中某一个元素的值那么可以使用索引操作得到一个零维度的Tensor一般称为scalar再通过scalar.item()获取具体数值。 In: scalar b[0]scalar
Out:tensor(2., dtypetorch.float64)
In: scalar.shape # 0-dim
Out:torch.Size([])
In: scalar.item() # 使用scalar.item()可以从中取出Python对象的数值Out:2.0
In: tensor t.tensor([2]) # 注意和scalar的区别tensor, scalar
Out:(tensor([2]), tensor(2., dtypetorch.float64))
In: tensor.size(), scalar.size()
Out:(torch.Size([1]), torch.Size([]))
In: # 只有一个元素的tensor也可以调用tensor.item()tensor.item(), scalar.item()
Out:(2, 2.0)
In: tensor t.tensor([3,4]) # 新建一个包含34两个元素的Tensorold_tensor tensornew_tensor old_tensor.clone()new_tensor[0] 1111old_tensor, new_tensor
Out:(tensor([3, 4]), tensor([1111, 4]))注意t.tensor()与tensor.clone()总是会进行数据拷贝新的Tensor和原来的数据不再共享内存。如果需要共享内存那么可以使用torch.from_numpy()或者tensor.detach()新建一个Tensor。 In: new_tensor old_tensor.detach()new_tensor[0] 1111old_tensor, new_tensorOut:(tensor([1111, 4]), tensor([1111, 4]))在深度学习中Tensor的维度特征十分重要。有时需要对Tensor的维度进行变换针对该问题PyTorch提供了许多快捷的变换方式例如维度变换view、reshape维度交换permute、transpose等。 在维度变换中可以使用view操作与reshape操作来改变Tensor的维度二者之间有以下区别。 view只能用于内存中连续存储的Tensor。如果Tensor使用了transpose、permute等维度交换操作那么Tensor在内存中会变得不连续。此时不能直接使用view操作应该先将其连续化即tensor.contiguous.view()。reshape操作不要求Tensor在内存中是连续的直接使用即可。 下面举例说明几种维度变换操作 In: x t.randn(4, 4)y x.view(16)z x.view(-1, 8) # -1表示由其他维度计算决定print(x.size(), y.size(), z.size())
Out:torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])In: p x.reshape(-1, 8)print(p.shape)
Out:torch.Size([2, 8])In: x1 t.randn(2, 4, 6)o1 x1.permute((2, 1, 0))o2 x1.transpose(0, 2)print(fo1 size {o1.size()})print(fo2 size {o2.size()})
Out:o1 size torch.Size([6, 4, 2])o2 size torch.Size([6, 4, 2])除了对Tensor进行维度变换还可以针对Tensor的某些维度进行其他的操作。例如tensor.squeeze() 可以进行Tensor的维度压缩、tensor.unsqueeze()可以扩展Tensor的维度、torch.cat()可以在Tensor指定维度上进行拼接等。 In: x t.randn(3, 2, 1, 1)y x.squeeze(-1) # 将最后一维进行维度压缩z x.unsqueeze(0) # 在最前面增加一个维度w t.cat((x, x), 0) # 在第一维度连接两个xprint(fy size {y.shape})print(fz size {z.shape})print(fw size {w.shape})Out:y size torch.Size([3, 2, 1])z size torch.Size([1, 3, 2, 1, 1])w size torch.Size([6, 2, 1, 1])Tensor可以通过.cuda()方法或者.to(device)方法转为GPU的Tensor从而享受GPU带来的加速运算。 In: # 在不支持CUDA的机器下下一步还是在CPU上运行device t.device(cuda:0 if t.cuda.is_available() else cpu)x x.to(device)y y.to(x.device)z x y此时读者可能会发现GPU运算的速度并未提升太多这是因为x和y的规模太小、运算简单而且将数据从内存转移到显存需要额外的开销。GPU的优势需要在大规模数据和复杂运算下才能体现出来。 2 autograd自动微分 在深度学习中反向传播算法被用来计算梯度其主要流程为通过梯度下降法来最小化损失函数以此更新网络参数。PyTorch中的autograd模块实现了自动反向传播的功能optim模块实现了常见的梯度下降优化方法。几乎所有的Tensor操作autograd都能为它们提供自动微分避免手动计算导数的复杂过程。 如果想要使用autograd功能那么需要对求导的Tensor设置tensor.requries_gradTrue下面举例说明autograd模块的用法 In: # 为Tensor设置requires_grad标识代表着需要求导数# PyTorch会自动调用autograd对Tensor求导x t.ones(2, 2, requires_gradTrue)# 上一步等价于# x t.ones(2,2)# x.requires_grad TruexOut:tensor([[1., 1.],[1., 1.]], requires_gradTrue)In: y x.sum()yOut:tensor(4., grad_fnSumBackward0)In: y.grad_fn
Out:SumBackward0 at 0x7fca878c8748
In: y.backward() # 反向传播计算梯度In: # y x.sum() (x[0][0] x[0][1] x[1][0] x[1][1])# 每个值的梯度都为1x.grad
Out:tensor([[1., 1.],[1., 1.]])注意grad在反向传播过程中是累加的accumulated。也就是说反向传播得到的梯度会累加之前的梯度。因此每次在进行反向传播之前需要把梯度清零。 In: y.backward()x.gradOut:tensor([[2., 2.],[2., 2.]])In: y.backward()x.gradOut:tensor([[3., 3.],[3., 3.]])In: # 以下划线结束的函数是inplace操作会修改自身的值如add_x.grad.data.zero_()Out:tensor([[0., 0.],[0., 0.]])In: y.backward()x.grad # 清零后计算得到正确的梯度值Out:tensor([[1., 1.],[1., 1.]])In: a t.randn(2, 2)a ((a * 3) / (a - 1))print(a.requires_grad)a.requires_grad_(True)print(a.requires_grad)b (a * a).sum()print(b.grad_fn)
Out:FalseTrueSumBackward0 object at 0x7fca87873128 3 神经网络 虽然autograd实现了反向传播功能但是直接用它来写深度学习的代码还是稍显复杂。torch.nn是专门为神经网络设计的模块化接口它构建于autograd之上可以用来定义和运行神经网络。nn.Module是nn中最重要的类它可以看作是一个神经网络的封装包含神经网络各层的定义以及前向传播forward方法通过forward(input)可以返回前向传播的结果。下面以最早的卷积神经网络LeNet1为例来看看如何用nn.Module实现该网络结构LeNet的网络结构如图2-10所示。 [^1]:
article{lecun1998gradient,title{Gradient-based learning applied to document recognition},author{LECUN Y, BOTTOU L, BENGIO Y, et al},journal{Proceedings of the IEEE},volume{86},number{11},pages{2278--2324},year{1998},publisher{Ieee}
}LeNet共有7层它的输入图像的大小为$32 \times 32$共经过2个卷积层、2次下采样操作以及3个全连接层得到最终的10维输出。在实现该网络之前这里先对神经网络的通用训练步骤进行说明。 1定义一个包含可学习参数的神经网络。 2加载用于训练该网络的数据集。 3进行前向传播得到网络的输出结果计算损失网络输出结果与正确结果的差距。 4进行反向传播更新网络参数。 5保存网络模型。 3.1 定义网络 在定义网络时模型需要继承nn.Module并实现它的forward方法。其中网络里含有可学习参数的层应该放在构造函数__init__()中如果某一层如ReLU不含有可学习参数那么它既可以放在构造函数中又可以放在forward方法中。这里将这些不含有可学习参数的层放在forward方法中并使用nn.functional实现 In: import torch.nn as nnimport torch.nn.functional as Fclass Net(nn.Module):def __init__(self):# nn.Module子类的函数必须在构造函数中执行父类的构造函数# 下式等价于nn.Module.__init__(self)super().__init__()# 卷积层1表示输入图片为单通道, 6表示输出通道数5表示卷积核为5×5self.conv1 nn.Conv2d(1, 6, 5) # 卷积层6表示输入图片为单通道, 16表示输出通道数5表示卷积核为5×5self.conv2 nn.Conv2d(6, 16, 5) # 仿射层/全连接层y Wx bself.fc1 nn.Linear(16 * 5 * 5, 120) self.fc2 nn.Linear(120, 84)self.fc3 nn.Linear(84, 10)def forward(self, x): # 卷积 - 激活 - 池化 x F.max_pool2d(F.relu(self.conv1(x)), (2, 2))x F.max_pool2d(F.relu(self.conv2(x)), 2) # 改变Tensor的形状-1表示自适应x x.view(x.size()[0], -1) x F.relu(self.fc1(x))x F.relu(self.fc2(x))x self.fc3(x) return x net Net()print(net)Out:Net((conv1): Conv2d(1, 6, kernel_size(5, 5), stride(1, 1))(conv2): Conv2d(6, 16, kernel_size(5, 5), stride(1, 1))(fc1): Linear(in_features400, out_features120, biasTrue)(fc2): Linear(in_features120, out_features84, biasTrue)(fc3): Linear(in_features84, out_features10, biasTrue))用户只需要在nn.Module的子类中定义了forward函数backward函数就会自动实现利用autograd。在forward函数中不仅可以使用Tensor支持的任何函数还可以使用if、for、print、log等Python语法写法和标准的Python写法一致。 使用net.parameters()可以得到网络的可学习参数使用net.named_parameters()可以同时得到网络的可学习参数及其名称下面举例说明 In: params list(net.parameters())print(len(params))
Out:10In: for name, parameters in net.named_parameters():print(name, :, parameters.size())
Out:conv1.weight : torch.Size([6, 1, 5, 5])conv1.bias : torch.Size([6])conv2.weight : torch.Size([16, 6, 5, 5])conv2.bias : torch.Size([16])fc1.weight : torch.Size([120, 400])fc1.bias : torch.Size([120])fc2.weight : torch.Size([84, 120])fc2.bias : torch.Size([84])fc3.weight : torch.Size([10, 84])fc3.bias : torch.Size([10])In: input t.randn(1, 1, 32, 32)out net(input)out.size()
Out:torch.Size([1, 10])In: net.zero_grad() # 所有参数的梯度清零out.backward(t.ones(1, 10)) # 反向传播注意torch.nn只支持输入mini-batch不支持一次只输入一个样本。如果只输入一个样本那么需要使用 input.unsqueeze(0)将batch_size设为1。例如 nn.Conv2d的输入必须是4维形如$\text{nSamples} \times \text{nChannels} \times \text{Height} \times \text{Width}$ 。如果一次输入只有一个样本那么可以将$\text{nSample}$ 设置为1即$1 \times \text{nChannels} \times \text{Height} \times \text{Width}$ 。 3.2 损失函数 torch.nn实现了神经网络中大多数的损失函数例如nn.MSELoss用来计算均方误差nn.CrossEntropyLoss用来计算交叉熵损失等下面举例说明 In: output net(input)target t.arange(0, 10).view(1, 10).float() criterion nn.MSELoss()loss criterion(output, target)loss
Out:tensor(28.1249, grad_fnMseLossBackward)对loss进行反向传播溯源使用gradfn属性可以看到上文实现的LeNet的计算图如下 input - conv2d - relu - maxpool2d - conv2d - relu - maxpool2d - view - linear - relu - linear - relu - linear - MSELoss- loss当调用loss.backward()时计算图会动态生成并自动微分自动计算图中参数parameters的导数示例如下 In: # 运行.backward观察调用之前和调用之后的gradnet.zero_grad() # 把net中所有可学习参数的梯度清零print(反向传播之前 conv1.bias的梯度)print(net.conv1.bias.grad)loss.backward()print(反向传播之后 conv1.bias的梯度)print(net.conv1.bias.grad)
Out:反向传播之前 conv1.bias的梯度tensor([0., 0., 0., 0., 0., 0.])反向传播之后 conv1.bias的梯度tensor([ 0.0020, -0.0619, 0.1077, 0.0197, 0.1027, -0.0060])3.3 优化器 在完成反向传播中所有参数的梯度计算后需要使用优化方法来更新网络的权重和参数。常用的随机梯度下降法SGD的更新策略如下 weight weight - learning_rate * gradient 用户可以手动实现这一更新策略 learning_rate 0.01
for f in net.parameters():f.data.sub_(f.grad.data * learning_rate) # inplace减法torch.optim中实现了深度学习中大多数优化方法例如RMSProp、Adam、SGD等因此通常情况下用户不需要手动实现上述代码。下面举例说明如何使用torch.optim进行网络的参数更新 In: import torch.optim as optim#新建一个优化器指定要调整的参数和学习率optimizer optim.SGD(net.parameters(), lr 0.01)# 在训练过程中# 先梯度清零(与net.zero_grad()效果一样)optimizer.zero_grad() # 计算损失output net(input)loss criterion(output, target)#反向传播loss.backward()#更新参数optimizer.step()3.4 数据加载与预处理 在深度学习中数据加载及预处理是非常繁琐的过程。幸运的是PyTorch提供了一些可以极大简化和加快数据处理流程的工具Dataset与DataLoader。同时对于常用的数据集PyTorch提供了封装好的接口供用户快速调用这些数据集主要保存在torchvision中。torchvision是一个视觉工具包它提供了许多视觉图像处理的工具主要包含以下三部分。 datasets提供了常用的数据集如MNIST、CIFAR-10、ImageNet等。models提供了深度学习中经典的网络结构与预训练模型如ResNet、MobileNet等。transforms提供了常用的数据预处理操作主要包括对Tensor、PIL Image等的操作。 读者可以使用torchvision方便地加载数据然后进行数据预处理这部分内容会在本书第5章进行详细介绍。