Neural Networks
1. 神经网络
torch.nn
包可以用来构建神经网络,nn
包依赖于autograd
包去定义模型并对其进行求导
一个nn.Module
包含很多层,和一个以input
作为输入的前向方法forward
,并返回输出output
下面是一个分类数字图像的网络
卷积网络
这是一个简单的前馈神经网络,网络接受输入数据,一层接着一层的传输输入数据,最后给出输出结果。
一个神经网络的典型训练过程如下:
- 定义包括可学习参数(或权重)的网络,
- 用数据集做迭代
- 计算损失(输出值和真实值之间的差异)
- 将梯度反向传播进网络的参数
- 更新网络的参数,经典的一个简单的更新规则: w e i g h t = w e i g h t − l e a r n i n g _ r a t e ∗ g r a d i e n t weight = weight - learning\_rate *gradient weight=weight−learning_rate∗gradient
2. 定义网络
定义网络
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
# 输入通道:1,输出通道:6,卷积核:3*3
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):
# 最大池化层(2,2)
x = F.max_pool2d(F.relu(self.conv1(x)),2)
# 如果是方形,可以使用单个数字定义
x = F.max_pool2d(F.relu(self.conv2(x)),2)
# 除batch维度外,展开所有维度
x = torch.flatten(x,1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
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
函数中可以使用任何针对Tensor的操作
net.parameters()
返回一个模型需要学习的参数
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1的权重
10
torch.Size([6, 1, 5, 5])
随机构造32×32大小的输入数据,这个网络结构(LeNet)期待的输入数据是32×32。如果要在MINIST数据集上使用这个网络,需要把数据集中的图像调整到32×32
import torch
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
tensor([[ 0.0242, -0.0113, -0.1382, -0.1108, 0.1062, 0.0663, 0.0473, 0.0761, 0.1049, -0.0412]], grad_fn=<AddmmBackward0>)
将所有参数的梯度缓存清零,然后进行随机梯度的反向传播
net.zero_grad()
out.backward(torch.randn(1,10))
torch.nn
只支持小批量输入,整个torch.nn
包都只支持小样本的输入,而不是单个样本例如:
nn.Conv2d
将接受一个四维的张量:nSamples×nChannels×Height×Width如果有单个样本,使用
input.unsqueeze(0)
来添加一个假batch维度
回归一下到目前为止见过的所有类
torch.Tensor
:支持autograd操作(比如backward()
)的多维数组,同时保存梯度(tensor形式)nn.Module
:神经网络模块,方便封装参数,帮助移到到GPU上运行、导出和加载等nn.Parameter
:一种张量,当把它赋值给Module的属性时,会自动注册为参数autograd.Function
:实现autograd操作的前向和后向定义,每个Tensor
操作会创造至少一个Function
节点,这个节点连接到创建tensor
并对其历史进行编码的函数
现在,我们已覆盖如下内容
- 定义一个神经网络
- 处理输入,调用反向传播函数
还剩下:
- 计算损失
- 更新网络的权重参数
3. 损失函数
损失函数接受(output,target)作为输入(output为网络的输出,target为实际值),计算值来评估output和target的误差
在nn
包下有几种不同的损失函数,一个简单的损失是:nn.MSELoss
,计算output和target之间的均方误差
output = net(input)
target = torch.randn(10)
target = target.view(1,-1) # 保持和output输出形状相同
criterion = nn.MSELoss()
loss = criterion(output,target)
print(loss)
tensor(0.3965, grad_fn=<MseLossBackward0>)
现在,如果你反向跟踪loss,使用.grad_fn
属性,将会看到下面这样的计算图
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> flatten -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
所以,当调用loss.backward()
的时候,整个图被分为神经网络参数和图中设置requires_grad=True
的所有tensor,帮助.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
<MseLossBackward0 object at 0x0000019DABCB18B0>
<AddmmBackward0 object at 0x0000019DABCB1250>
<AccumulateGrad object at 0x0000019DABCB18B0>
4. 反向传播
为了反向传播误差,要调用loss.backward()
,你需要清除已经存在的梯度,不然梯度就会被累加到已经存在的梯度
现在调用loss.backward()
,看看conv1在反向传播前后的偏置梯度
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.0079, 0.0134, 0.0143, 0.0054, 0.0081, -0.0033])
现在,我们已经知道如何使用损失函数
接下来最后一件要学习的是:更新网络权重
5. 更新权重
在实践中最简单的更新规则是随机梯度下降(SGD)
w e i g h t = w e i g h t − l e a r n i n g _ r a t e ∗ g r a d i e n t weight=weight−learning\_rate∗gradient weight=weight−learning_rate∗gradient
使用Python代码简单实现:
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)
torch.optim
包里面有各种更新方法的实现
import torch.optim as optim
# 创建优化器
optimizer = optim.SGD(net.parameters(),lr=0.01)
# 训练循环中
optimizer.zero_grad()
output = net(input)
loss = criterion(output,target)
loss.backward()
optimizer.step() # 更新参数
梯度是可以累积的,所以要使用
optimizer.zero_grad()
手动将梯度缓冲区设置为零