Pytorch 60分钟入门之(三) NEURAL NETWORKS 神经网络

目录

  • NEURAL NETWORKS 神经网络
  • Define the network 定义网络
  • Loss Function 损失函数
  • Backprop 反向传播
  • Update the weights 更新网络权重
  • 补充1. 网络的parameter是如何组织的?

NEURAL NETWORKS 神经网络

神经网络是使用torch.nn包构造的.
nn使用autograd定义模型和对模型做微分. 一个nn.module对象包含layers, 一个返回输出的forward(input)方法.
例如, 看一个分类数字图片的网络:
Pytorch 60分钟入门之(三) NEURAL NETWORKS 神经网络_第1张图片convnet

这是简单的前馈(feed-forward)网络. 它获取输入, 将它送给几个layer一层接一层的处理, 最后给出输出.
一个典型的神经网络训练过程如下:

  1. 定义神经网络, 含一些可学习的参数(权重)
  2. 在输入数据集上迭代
  3. 输入送入网络处理
  4. 计算loss函数(输出和真实值的差异)
  5. 将梯度反向传播到网络的参数中
  6. 更新网络权重, 典型地使用weight = weight - learning_rate * gradient

Define the network 定义网络

定义这个网络:

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


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 3x3 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # an affine operation: 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):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single 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:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)

Out:

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函数. backward函数(计算梯度的地方)会在使用autograd时自动定义. 你可以在forward函数中使用任意的Tensor操作.
可训练的参数由net.parameters()返回:

params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight

Out:

10
torch.Size([6, 1, 3, 3])

用一个随机的32x32输入试一下.

注意:
这个网络(LeNet)的期望输入size是32x32. 如果想要在MNIST数据集上使用这个网络, 需要将图像resize到32x32.

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

Out:

tensor([[-0.0029,  0.0154,  0.0369,  0.0380, -0.0812, -0.0062, -0.0510, -0.0086,
          0.0619,  0.0218]], grad_fn=<AddmmBackward>)

将所有参数的梯度buffer清0, 用随机梯度来反向传播:

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

注意
torch.nn 只支持mini-batches. 整个torch.nn包只支持样本的mini-batch的输入, 而不是单个的样本.
例如, nn.Conv2d会接受4D Tensor, size是 nSamples x nChannels x Height x Width.
如果你有一个样本, 使用input.unsqueeze(0) 加入一个假的维度.

往下进行前, 先简单回顾一下.
回顾:

  • torch.Tensor- 一个多维度的 array, 支持autograd操作, 例如backward(). 也能保存tensor的梯度.
  • nn.Module - 神经网络模块. 封装参数, 可以将参数移动到GPU, 导出, 载入等.
  • nn.Parameter - 一种Tensor, 作为属性赋给一个Module时, 会自动登记为训练参数.
  • autograd.Function -autograd操作的forwardbackward定义的实现. 每一个Tensor操作会创建至少一个Function 节点. 节点连接到创建Tensor的函数并记录运算历史.

现在,我们学习了:

  • 定义一个神经网络
  • 处理输入并调用backward

还剩下:

  • 计算loss
  • 更新网络权重

Loss Function 损失函数

loss函数 将(output,target) 作为输入, 计算一个值, 表征output距离target有多远的估计.
nn包里有多种loss函数. 一个简单的loss是nn.MSELoss 计算output和target间的均方误差.

例如:

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)

Out:

tensor(0.4715, grad_fn=<MseLossBackward>)

如果你使用loss的.grad_fn属性, 在backward的方向上跟踪loss, 你会发现一个类似这样的一个计算图:

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss

所以, 当你调用loss.backward()时, 整个图都对loss求差分, 图中所有requires_grad=TrueTensor都会累加梯度.
为了清晰, 来跟踪几步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

Out:

<MseLossBackward object at 0x7fa7a4719080>
<AddmmBackward object at 0x7fa7a47193c8>
<AccumulateGrad object at 0x7fa7a47193c8>

Backprop 反向传播

为了将误差反向传播, 我们只需要调用 loss.backward(). 然而需要先清零存在的梯度, 否则新梯度会累加到原来的梯度上.
现在我们调用loss.backward(), 看看conv1的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)

Out:

conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0005,  0.0012, -0.0029,  0.0029, -0.0079,  0.0023])

现在我们知道了如何使用loss函数.

稍后阅读:

nn包有很多modules和loss函数. 全部列表见这里.

只剩下了:

  • 更新网络权重

Update the weights 更新网络权重

最简单的更新方法是随机梯度下降法(Stochastic Gradient Descent , SGD):

weight = weight - learning_rate * gradient

用简单的Python代码实现:

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

然而, 还有其他的更新方法: 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

注意:
注意梯度buffer是如何用optimizer.zero_grad()手动清零的. 如反向传播部分所讲的, 这是因为梯度是累加的.

Total running time of the script: ( 0 minutes 3.761 seconds)

补充1. 网络的parameter是如何组织的?

在上述LeNet网络定义后, 可以观察net.parameters()的组织方式:

params = list(net.parameters())
print(len(params))
for i in range(len(params)):
    print(params[i].size())

Out:

10
torch.Size([6, 1, 3, 3])
torch.Size([6])
torch.Size([16, 6, 3, 3])
torch.Size([16])
torch.Size([120, 576])
torch.Size([120])
torch.Size([84, 120])
torch.Size([84])
torch.Size([10, 84])
torch.Size([10])

可以看出, 10个Tensor矩阵分别代表了各层的kernel和bias参数.

你可能感兴趣的:(深度学习)