1. 首页
  2. 后端

PyTorch教程3:如何定义神经网络模型并添加损失以及优化

在PyTorch中,我们使用torch.nn这个包来构建神经网络。

前面我们已经了解了autograd这个包的一些功能作用, nn这个包依赖于autograd包来定义网络模型并对网络进行自动微分。一个 nn.Module包含很多layers, 以及一个可以返回输出值的前向传递方法forward(input)

以下图为例, 我们将展示如何用PyTorch提供的工具定义这个手写数字分类网络模型:

PyTorch教程3:如何定义神经网络模型并添加损失以及优化

这是一个简单的前馈网络.它接受输入,然后将输入一个接一个地通过几个层,最后给出输出。

一个神经网络的典型训练过程包含以下几个步骤:

  • 1、定义一个拥有可学习参数(learnable parameters or weights)的神经网络计算图
  • 2、在一个给定的输入数据集上进行迭代
  • 3、将每一个输入数据沿着神经网络进行前向计算,得到网络的输出
  • 4、计算损失(how far is the output from being correct)
  • 5、误差反向传播,从输出端开始到输入端计算损失相对于每一层网络输入以及每一层网络权重参数的梯度
  • 6、更新网络的权重参数, 典型的使用一个简单的更新规则:
    weight = weight - learning_rate * gradient

Define the network

下面我们来定义网络: 首先自定义的Net要继承nn.Module;其次要把网络的一些可训练参数或者叫可学习参数(learnable or trainable parameters)放在Net类的初始化函数__init__()里面;然后把网络的前向传递(inference)的计算逻辑写到forward这个函数中,要用到torch.nn.functional里面的一些基本函数,比如 “maxpool”,“relu”等;然后我们还可以在自己的Net类里面定一些辅助性函数,如num_flat_features()等。

当我们定义好一个Net类以后,我们就可以用这类来实例化一个神经网络计算模型了:net = Net()

  1. import torch
  2. import torch.nn as nn
  3. import torch.nn.functional as F
  4. class Net(nn.Module):
  5. def __init__(self):
  6. super(Net, self).__init__()
  7. # 单通道输入图像, 6个输出通道, 5x5方形卷积核
  8. self.conv1 = nn.Conv2d(1, 6, 5)
  9. self.conv2 = nn.Conv2d(6, 16, 5)
  10. # 最后的分类器是两个全连接层: y = Wx + b
  11. self.fc1 = nn.Linear(16 * 5 * 5, 120)
  12. self.fc2 = nn.Linear(120, 84)
  13. self.fc3 = nn.Linear(84, 10)
  14. def forward(self, x):
  15. # Max pooling over a (2, 2) window,之后x的空间分辨率就从32x32变成了14x14: (32-5+1)/2
  16. x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
  17. # 如果maxpool的size是方的,你可以只输入一个参数,经过maxpool之后,x从14x14变成5x5:(14-5+1)/2
  18. x = F.max_pool2d(F.relu(self.conv2(x)), 2)
  19. # 将N个三维立方体式的特征向量(N x 16 x 5 x 5)拉伸成N个一维特征向量,N x 16*5*5,N是batch size
  20. x = x.view(-1, self.num_flat_features(x))
  21. x = F.relu(self.fc1(x))
  22. x = F.relu(self.fc2(x))
  23. x = self.fc3(x)
  24. return x
  25. def num_flat_features(self, x):
  26. size = x.size()[1:] #x.size()[0]是batch的大小,剩余的是 C x H x W
  27. num_features = 1
  28. for s in size:
  29. num_features *= s
  30. return num_features
  31. # 创建 Net()类的实例,并输出网络的信息
  32. net = Net()
  33. print(net)
  1. Net(
  2. (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  3. (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  4. (fc1): Linear(in_features=400, out_features=120, bias=True)
  5. (fc2): Linear(in_features=120, out_features=84, bias=True)
  6. (fc3): Linear(in_features=84, out_features=10, bias=True)
  7. )

在上面的模型中,我们只需要定义前向计算forward,而后向计算过程backward将通过autograd这个包为你自动定义好。

你可以在forward中使用PyTorch提供的任意的Tensor操作算子。autograd将会自动为其匹配相对应的求导算子。

模型的可学习参数可以通过net.parameters()来获得。

  1. print(type(net.parameters()))#是一个 生成器 类型
  2. params = list(net.parameters())
  3. print(len(params))
  4. print(params[0].size()) # conv1's .weight
  5. print(params[1].size()) # conv1's .bias
  6. print(params[8].size()) # fc3's .weight
  7. print(params[9].size()) # fc3's .bias
  8. # 要注意这里 nn.Conv2d以及nn.Linear都是带有偏置bias的
  1. <class 'generator'>
  2. 10
  3. torch.Size([6, 1, 5, 5])
  4. torch.Size([6])
  5. torch.Size([10, 84])
  6. torch.Size([10])

让我们给网络提供一个32×32的随机输入

Note: Expected input size to this net(LeNet) is 32×32. To use this net on
MNIST dataset, please resize the images from the dataset to 32×32. MNIST的图像分辨率是28×28

  1. # 第一个 1 代表我们只产生 1 个样本
  2. input = torch.randn(1, 1, 32, 32)
  3. out = net(input)
  4. print(out)
  1. tensor([[-0.0821, 0.0832, 0.0610, -0.1031, 0.0854, -0.0978, 0.0695, -0.1073,
  2. 0.0032, 0.0666]], grad_fn=<ThAddmmBackward>)

Zero the gradient buffers of all parameters and backprops with random
gradients:

把所有可训练参数的梯度缓存清零,然后使用一个随机梯度开始向后传播

  1. net.zero_grad()
  2. # 因为我们现在还不知道损失,所以我们随便随机的产生一个shape和out的shape一致的随机向量作为梯度
  3. out.backward(torch.randn(1, 10))

特别注意:

torch.nn only supports mini-batches. The entire torch.nn
package only supports inputs that are a mini-batch of samples, and not
a single sample.

For example, nn.Conv2d will take in a 4D Tensor of
nSamples x nChannels x Height x Width.

If you have a single sample, just use input.unsqueeze(0) to add
a fake batch dimension.

Before proceeding further, let’s recap all the classes you’ve seen so far.

回顾一下:

  • torch.Tensor – A multi-dimensional array with support for autograd
    operations like backward(). Also holds the gradient w.r.t. the
    tensor.
  • nn.Module – Neural network module. *提供了一个便捷的方式实现模型参数的封装,CPU与GPU之间的转移,模型的加载和导出等辅助性功能, etc.
  • nn.Parameter – A kind of Tensor, that is automatically
    registered as a parameter when assigned as an attribute to a

    Module.
  • autograd.Function – 实现自动梯度操作的前向和后向定义。 每个 Tensor 操作, 至少创建一个 Function node, that connects to functions that created a Tensor and encodes its history.

目前,我们已经学会:

  • 定义一个神经网络
  • 处理前向推断以及调用backward进行反向传播

剩余部分:

  • 计算损失
  • 更新网络权重

Loss Function

一个损失函数以(output, target)为输入,计算一个用于评估模型实际输出与目标期望输出的不一致的值(value)

PyTorch的torch.nnpackage提供了各种不同的损失函数:loss functions <https://pytorch.org/docs/nn.html#loss-functions>.

一种比较简单的损失是: nn.MSELoss 他计算模型实际输出和目标期望输出之间的均方误差(mean-squared error).

如下所示:

  1. output = net(input)
  2. print(output.size())
  3. target = torch.randn(10) # a dummy target, for example
  4. print(target.size())
  5. target = target.view(1, -1) # make it the same shape as output
  6. print(target.size())
  7. criterion = nn.MSELoss() # 创建一个损失的对象实例
  8. loss = criterion(output, target) #计算损失,MSELoss这个类内部实现了魔法函数 __call__(),所以直接可以把criterion当函数一样调用
  9. print(loss)
  1. torch.Size([1, 10])
  2. torch.Size([10])
  3. torch.Size([1, 10])
  4. tensor(0.6416, grad_fn=<MseLossBackward>)

Now, if you follow loss in the backward direction, using its
.grad_fn attribute, you will see a graph of computations that looks
like this:

::

  1. input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
  2. -> view -> linear -> relu -> linear -> relu -> linear
  3. -> MSELoss
  4. -> loss

So, when we call loss.backward(), the whole graph is differentiated
w.r.t. the loss, and all Tensors in the graph that has requires_grad=True
will have their .grad Tensor accumulated with the gradient.

For illustration, let us follow a few steps backward:

  1. print(loss.grad_fn) # MSELoss
  2. print(loss.grad_fn.next_functions[0][0]) # Linear
  3. print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU
  1. <MseLossBackward object at 0x7feeefcc0a58>
  2. <ThAddmmBackward object at 0x7feeefcc0a90>
  3. <ExpandBackward object at 0x7feeefcc0a58>

Backprop

为了进行误差的反向传播,我们只需要调用 loss.backward()
You need to clear the existing gradients though, else gradients will be
accumulated to existing gradients.

Now we shall call loss.backward(), and have a look at conv1’s bias
gradients before and after the backward.

  1. net.zero_grad() # zeroes the gradient buffers of all parameters
  2. print('conv1.bias.grad before backward')
  3. print(net.conv1.bias.grad)
  4. loss.backward()
  5. print('conv1.bias.grad after backward')
  6. print(net.conv1.bias.grad)
  1. conv1.bias.grad before backward
  2. tensor([0., 0., 0., 0., 0., 0.])
  3. conv1.bias.grad after backward
  4. tensor([ 0.0031, 0.0071, -0.0074, -0.0079, 0.0172, 0.0058])

现在我们已经知道如何使用损失函数了!接下来的事情就是如何更新网络权重!

Update the weights

在实际中用的最简单的更新规则就是随机梯度下降法 Stochastic Gradient Descent (SGD):

  1. ``weight = weight - learning_rate * gradient``

我们可以使用Python代码轻松实现:

.. code:: python

  1. learning_rate = 0.01
  2. for f in net.parameters():
  3. f.data.sub_(f.grad.data * learning_rate)

然而,我们有各种各样的SGD的变体版本: SGD, Nesterov-SGD, Adam, RMSProp, etc.
为了方便,我们构建了一个小的package: torch.optim 来实现这些不同的优化算法。他们的使用非常简单。

  1. import torch.optim as optim
  2. # 创建一个优化器对象实例:传入要优化的参数以及学习率
  3. optimizer = optim.SGD(net.parameters(), lr=0.01)
  4. # 在你的train loop里面这样写:
  5. optimizer.zero_grad() # 清空所有梯度缓存
  6. output = net(input) # 前向推断过程
  7. loss = criterion(output, target)# 计算损失
  8. loss.backward() # 损失误差后向传播,求梯度
  9. optimizer.step() # 根据梯度更新可学习参数

.. Note::

  1. Observe how gradient buffers had to be manually set to zero using
  2. ``optimizer.zero_grad()``. This is because gradients are accumulated
  3. as explained in `Backprop`_ section.

本文来自投稿,不代表程序员编程网立场,如若转载,请注明出处:http://www.cxybcw.com/187578.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code