%matplotlib inline

神经网络

可以使用torch.nn package构造进行构造神经网络。

刚刚我们简单介绍了autograd,nn依赖于autograd来定义模型并区分它们。一个nn.Module包括你定义的网络层和一个forward(input)方法,这个方法返回output。

我们看下这个数字图片分类的网络的例子:

它是一个简单的前馈网络。它接受输入,并且每层的输入都是上一层的输出,最后给出输出。

一个典型的神经网络的训练过程如下:

  • 定义具有学习参数(或权重)的神经网络
  • 迭代输入数据集
  • 根据神经网络对输入数据集进行运算
  • 计算损失(输出与真实结果的距离。损失越小说明模型越准确。)
  • 将梯度反向传播给神经网络的参数。
  • 更新网络的权重(或参数)。通常使用一种简单的更新规则:权重=权重-学习率*梯度。

定义一个神经网络

import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 定义2个卷积层
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # 定义了3个线性层,即y=wx+b
        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension 
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # 在第一层卷积网络和第二层之间增加了relu激活函数和池化层
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 如果池化层是方块形的可以用一个number指定
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        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:]  # 切片0里面放的是当前训练batch的number,不是特征信息
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)
Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, 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方法,后向函数(自动计算微分的函数)就会自动定义,当然你需要设置autograd参数为True。在forward方法中你可以使用张量的任何运算。

模型的学习参数由net.parameters()返回。

params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight
10
torch.Size([6, 1, 3, 3])

这个网络适合3232的数据集(13232经过一个核心为5的卷积层,大小变为62828;经过一个核心为2的池化层,大小变为61414;再经过一个核心为5的卷积层,大小变为161010;池化层,165*5,正好是下一个线性层的输入特征数),我们随机一个输入数据集,运行一下我们的模型吧:

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
tensor([[ 0.0219,  0.0002,  0.0533, -0.0077, -0.0179, -0.0888, -0.0075,  0.0175,
          0.0084,  0.0613]], grad_fn=<AddmmBackward>)

将梯度清0并使用随机梯度进行反向传播:

net.zero_grad()
out.backward(torch.randn(1, 10))

注意:

torch.nn 只支持对批量数据的处理,不支持单个样本。

例如,nn.Conv2d需要一个4维的张量作为输入:[ nSamples,nChannels,Height,Width. ]

如果你只有一个样本,你可以使用input.unsqueeze(0)来增加一个假的batch维。

在继续进行之前,我们回顾下刚学到的几个类。

  • torch.Tensor:一个支持自动微分(或梯度)操作(例如backward())的多维数组。

  • nn.Module: 神经网络模型,帮助我们封装参数,移植到GPU,导出,加载等的便捷方式。

  • nn.Parameter: 张量,当你给Module定义属性时,参数会自动生成。

  • autograd.Function:实现自动梯度运算的前向后向定义。每一个张量运算,产生至少一个Function节点,连接到创建张量的函数并对其历史进行编码。

在这个基础上,我们已经做了:

  • 定义神经网络

  • 处理输入和调用backward方法

还剩下:

  • 计算损失

  • 更新网络的权重

损失函数

一个损失函数包括两个输入(预测值和实际值),然后返回这两个值的距离。

在nn package里面有不同的损失函数。一个简单的损失函数是:nn.MSELoss,它返回的是均方差,即每一个分量相减的平方累计最后除分量的个数。

output = net(input)
target = torch.randn(10)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)
tensor(1.1062, grad_fn=<MseLossBackward>)

所以,当你调用loss.backward()时,在整个计算图都会进行微分,所有requires_grad=True的张量的.grad属性都会累加。

为了更好的理解它,列出很少的几步:

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 0x000001AF9D9C56D8>
<AddmmBackward object at 0x000001AF9D9C5710>
<AccumulateGrad object at 0x000001AF9D9C56D8>

反向传播

为了反向传播误差我们所要做的就只是调用loss.backward()。也需要清除已经存在的梯度,不然梯度会和已经存在的梯度进行累计。

我们调用loss.backward(),看下conv1‘s的bias的梯度在反向传播前后的变化。

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.0098,  0.0003, -0.0068,  0.0056, -0.0279, -0.0121])

现在我们已经知道如何使用损失函数了。

稍后阅读:

nn package包含各种深度神经网络的模块和损失函数。这里有一个完整的列表 http://pytorch.org/docs/nn

我们要做的最后一步是:

更新网络模型的权重(参数)

更新权重

实践中常用的最简单的更新规则是Stochastic Gradient Descent(SGD):

weight = weight - learning_rate * gradient

上式用python代码实现:

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

然而,在你使用神经网络时,你想要使用各种不同的更新规则例如SGD(随机梯度下降法),Nesterov-SGD,Adam,RMSProp(均方根传播)等。我们实现了所有的这些方法,在库torch.optim中。使用起来也很简单:

import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

注意:

观察到我们必须手动的把梯度缓存设置为0,即optimizer.zero_grad(),这是因为我们在反向传播那一节也提到过的,梯度会被累计。