在PyTorch中,我们使用torch.nn
这个包来构建神经网络。
前面我们已经了解了autograd
这个包的一些功能作用, nn
这个包依赖于autograd
包来定义网络模型并对网络进行自动微分。一个 nn.Module
包含很多layers, 以及一个可以返回输出值的前向传递方法forward(input)
。
以下图为例, 我们将展示如何用PyTorch提供的工具定义这个手写数字分类网络模型:
这是一个简单的前馈网络.它接受输入,然后将输入一个接一个地通过几个层,最后给出输出。
一个神经网络的典型训练过程包含以下几个步骤:
- 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()
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 单通道输入图像, 6个输出通道, 5x5方形卷积核
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# 最后的分类器是两个全连接层: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# Max pooling over a (2, 2) window,之后x的空间分辨率就从32x32变成了14x14: (32-5+1)/2
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 如果maxpool的size是方的,你可以只输入一个参数,经过maxpool之后,x从14x14变成5x5:(14-5+1)/2
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
# 将N个三维立方体式的特征向量(N x 16 x 5 x 5)拉伸成N个一维特征向量,N x 16*5*5,N是batch size
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] #x.size()[0]是batch的大小,剩余的是 C x H x W
num_features = 1
for s in size:
num_features *= s
return num_features
# 创建 Net()类的实例,并输出网络的信息
net = Net()
print(net)
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_features=400, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
在上面的模型中,我们只需要定义前向计算forward
,而后向计算过程backward
将通过autograd
这个包为你自动定义好。
你可以在forward
中使用PyTorch提供的任意的Tensor操作算子。autograd
将会自动为其匹配相对应的求导算子。
模型的可学习参数可以通过net.parameters()
来获得。
print(type(net.parameters()))#是一个 生成器 类型
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
print(params[1].size()) # conv1's .bias
print(params[8].size()) # fc3's .weight
print(params[9].size()) # fc3's .bias
# 要注意这里 nn.Conv2d以及nn.Linear都是带有偏置bias的
<class 'generator'>
10
torch.Size([6, 1, 5, 5])
torch.Size([6])
torch.Size([10, 84])
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 个样本
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
tensor([[-0.0821, 0.0832, 0.0610, -0.1031, 0.0854, -0.0978, 0.0695, -0.1073,
0.0032, 0.0666]], grad_fn=<ThAddmmBackward>)
Zero the gradient buffers of all parameters and backprops with random
gradients:
把所有可训练参数的梯度缓存清零,然后使用一个随机梯度开始向后传播
net.zero_grad()
# 因为我们现在还不知道损失,所以我们随便随机的产生一个shape和out的shape一致的随机向量作为梯度
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 likebackward()
. 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 aTensor
and encodes its history.
目前,我们已经学会:
- 定义一个神经网络
- 处理前向推断以及调用backward进行反向传播
剩余部分:
- 计算损失
- 更新网络权重
Loss Function
一个损失函数以(output, target)为输入,计算一个用于评估模型实际输出与目标期望输出的不一致的值(value)
PyTorch的torch.nn
package提供了各种不同的损失函数:loss functions <https://pytorch.org/docs/nn.html#loss-functions>
.
一种比较简单的损失是: nn.MSELoss
他计算模型实际输出和目标期望输出之间的均方误差(mean-squared error).
如下所示:
output = net(input)
print(output.size())
target = torch.randn(10) # a dummy target, for example
print(target.size())
target = target.view(1, -1) # make it the same shape as output
print(target.size())
criterion = nn.MSELoss() # 创建一个损失的对象实例
loss = criterion(output, target) #计算损失,MSELoss这个类内部实现了魔法函数 __call__(),所以直接可以把criterion当函数一样调用
print(loss)
torch.Size([1, 10])
torch.Size([10])
torch.Size([1, 10])
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:
::
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> 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:
print(loss.grad_fn) # MSELoss
print(loss.grad_fn.next_functions[0][0]) # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU
<MseLossBackward object at 0x7feeefcc0a58>
<ThAddmmBackward object at 0x7feeefcc0a90>
<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.
net.zero_grad() # zeroes the gradient buffers of all parameters
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0031, 0.0071, -0.0074, -0.0079, 0.0172, 0.0058])
现在我们已经知道如何使用损失函数了!接下来的事情就是如何更新网络权重!
Update the weights
在实际中用的最简单的更新规则就是随机梯度下降法 Stochastic Gradient Descent (SGD):
``weight = weight - learning_rate * gradient``
我们可以使用Python代码轻松实现:
.. code:: python
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)
然而,我们有各种各样的SGD的变体版本: SGD, Nesterov-SGD, Adam, RMSProp, etc.
为了方便,我们构建了一个小的package: torch.optim
来实现这些不同的优化算法。他们的使用非常简单。
import torch.optim as optim
# 创建一个优化器对象实例:传入要优化的参数以及学习率
optimizer = optim.SGD(net.parameters(), lr=0.01)
# 在你的train loop里面这样写:
optimizer.zero_grad() # 清空所有梯度缓存
output = net(input) # 前向推断过程
loss = criterion(output, target)# 计算损失
loss.backward() # 损失误差后向传播,求梯度
optimizer.step() # 根据梯度更新可学习参数
.. Note::
Observe how gradient buffers had to be manually set to zero using
``optimizer.zero_grad()``. This is because gradients are accumulated
as explained in `Backprop`_ section.
本文来自投稿,不代表程序员编程网立场,如若转载,请注明出处:http://www.cxybcw.com/187578.html