1.3 Neural Networks
使用torch.nn包来构建神经网络。
上一讲已经讲过了autograd,nn包依赖autograd包来定义模型并求导。 一个nn.Module包含各个层和一个forward(input)方法,该方法返回output。
例如:
关于卷积神经网络的基础介绍,可以参考这里:https://github.com/zergtant/pytorch-handbook/blob/master/chapter2/2.4-cnn.ipynb
import torch
torch.__version__ #打印版本
'1.1.0'
在这里,我们通常有一些约定:
import torch.nn as nn
将torch.nn别名为nn
import torch.nn.functional as F
将torch.nn.functional别名为F
这样别名方便后面调用。
torch.nn.functional,这个包中包含了神经网络中使用的一些常用函数,这些函数的特点是,不具有可学习的参数(如ReLU,pool,DropOut等),这些函数可以放在构造函数中,也可以不放,但是这里建议不放。
简单的前馈神经网络,它接受一个输入,然后一层接着一层地传递,最后输出计算的结果。
神经网络的典型训练过程如下:
定义包含一些可学习的参数(或者叫权重)神经网络模型;
在数据集上迭代;
通过神经网络处理输入;
计算损失(输出结果和正确值的差值大小);
将梯度反向传播回网络的参数;
更新网络的参数,主要使用如下简单的更新原则: weight = weight - learning_rate * gradient
super() 函数是用于调用父类(超类)的一个方法,通过该函数调用Net的超类,然后通过 super(Net,self).init()进行初始化,因为__init__()也称为构造方法中的属性无法被继承,所以通过这种方式调用超类中的属性,如果在超类中找不到该属性会继续查找超类的超类等。
关于super函数的详细介绍:https://www.runoob.com/python/python-func-super.html
torch.nn.Conv2d函数:
Torch.nn.Conv2d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True)
in_channels:输入通道数
out_channels:输出通道数
kernel_size:滤波器(卷积核)大小,用f表示,宽和高相等的卷积核可以用一个数字表示,例如kernel_size=3;否则用不同数字表示,例如kernel_size=(5,3)
stride : 表示滤波器滑动的步长,用s表示;
padding:是否进行零填充,padding=0表示四周不进行零填充,padding=1表示四周进行1个像素点的零填充,用p表示
bias:默认为True,表示使用偏置
假设输入图像大小为n*n,那么,用卷积核对输入图像卷积之后,输出图像的大小是多少呢?公式如下:
n
−
f
+
2
p
s
+
1
\frac{n-f+2 p}{s}+1
sn−f+2p+1
关于stride、padding、dilation的理解可以看这里的动画演示:https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md#dilated-convolution-animations
groups:这个参数是指将输入沟道分为几组进行计算输出沟道,如果groups为1,就是只有一组,就是说每一个输出沟道的计算要用到所有的输入沟道;如果groups为2,就是把输入沟道分为2组,在计算每一个输出沟道时,每一次只用其中一组的输入沟道来计算,以此类推。
torch.nn.Linear函数:
torch.nn.Linear(in_features,out_features,bias = True )
对传入数据应用线性变换:y = Ax+ b
举例子:
m = torch.nn.Linear(20, 30)
x=torch.randn(128, 20)
out=m(x)
产生的变量有weight和bias,其中weight的形状是[30, 20]
,bias的形状是[30]
其功能就是:out1=torch.mm(x, m.weight.t()) + m.bias
,torch.mm
表示两个矩阵相乘,
out.shape:[128,30]
也就是,out=out1
参数:
in_features - 每个输入样本的大小
out_features - 每个输出样本的大小
bias - 如果设置为False,则图层不会学习附加偏差。默认值:True
Shape:
Input: (N,in_features)
Output: (N,out_features)
Variables:
Linear.weight,学习权重,shape:(out_features,in_features).
Linear.bias,偏置,shape:out_features
note:设置完初始值,根据反向传播梯度值修正参数
详细可见:https://pytorch.org/docs/master/nn.html?highlight=nn linear#torch.nn.Linear
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False):
kernel_size –核的大小,用f表示
stride – 滑动步长,用s表示
padding – 是否填充0,用p表示
dilation – a parameter that controls the stride of elements in the window
return_indices – if True, will return the max indices along with the outputs. Useful for torch.nn.MaxUnpool3d later
ceil_mode – when True, will use ceil instead of floor to compute the output shape
假设输入大小为n*n,经过池化后的输出大小为,公式: n − f s + 1 \frac{n-f}{s}+1 sn−f+1
原理如下:
torch.nn.ReLU(inplace=False)
修正线性单元(Rectified linear unit,ReLU)
非线性激活函数的一种,同类型的函数还有sigmoid函数,tanh函数,softplus函数等等。
公式:ReLU(x)=max(0,x),具有单侧抑制作用
sigmoid函数为sigmoid(x)= 1/(1+e^-x),而Softplus(x)=log(1+ex)。
它的作用是如果计算出的值小于0,就让它等于0,否则保持原来的值不变,训练后的网络完全具备适度的稀疏性。
定义网络
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, 5x5 square convolution
# kernel
self.conv1 = nn.Conv2d(1,6,5) #构造输入通道为1,输出通道为6,卷积核大小5*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,2))
x = F.max_pool2d(F.relu(self.conv2(x)),2) #如果卷积核尺寸长宽相同,只需要指定一个
x = x.view(-1,self.num_flat_features(x)) #x.view调整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:]
num_features = 1
for s in size:
num_features *= s
return num_features
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)
)
<bound method Module.parameters of 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,如果使用MNIST数据集来训练这个网络,请把图片大小重新调整到32×32。
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
tensor([[-2.5615, -1.4061, 0.5991, ..., 1.1176, 0.4557, 1.8589],
[-1.7022, -2.6855, -0.1328, ..., 0.3439, -0.1854, 0.4611],
[-0.6718, 1.2699, 0.6530, ..., -0.3008, 0.3943, 2.4400],
...,
[-1.5000, -0.7443, -0.9159, ..., -1.1712, 0.1082, 0.6138],
[ 0.7130, 0.2771, -0.7458, ..., -0.2253, -0.1215, -0.5637],
[ 0.1070, 0.0779, 0.1620, ..., 0.0665, 1.4905, -0.2640]])
tensor([[ 0.0322, 0.0024, -0.0632, 0.0245, -0.0616, 0.0039, 0.0120, 0.0593,
-0.0247, 0.0740]], grad_fn=<AddmmBackward>)
将所有参数的梯度缓存清零,然后进行随机梯度的的反向传播:
net.zero_grad()
out.backward(torch.randn(1,10))
NOTE:
torch.nn
只支持小批量输入。整个 torch.nn
包都只支持小批量样本,而不支持单个样本。 例如,nn.Conv2d
接受一个4维的张量, 每一维分别是sSamples * nChannels * Height * Width(样本数*通道数*高*宽)
。 如果你有单个样本,只需使用 input.unsqueeze(0)
来添加其它的维数.
其中,通道数的概念,对一幅灰度图来说,通道数为1;对一幅RGB彩色图来说,通道数为3.
在继续之前,我们回顾一下到目前为止用到的类。
回顾:
torch.Tensor:一个用过自动调用 backward()实现支持自动梯度计算的多维数组 , 并且保存关于这个向量的梯度 w.r.t.
nn.Module:神经网络模块。封装参数、移动到GPU上运行、导出、加载等。
nn.Parameter:一种变量,当把它赋值给一个Module时,被 自动 地注册为一个参数。
autograd.Function:实现一个自动求导操作的前向和反向定义,每个变量操作至少创建一个函数节点,每一个Tensor的操作都回创建一个接到创建Tensor和 编码其历史 的函数的Function节点。
重点如下:
定义一个网络
处理输入,调用backword
还剩:
计算损失
更新网络权重
损失函数
一个损失函数接受一对 (output, target) 作为输入,计算一个值来估计网络的输出和目标值相差多少。
output为网络的输出,target为实际值
nn包中有很多不同的损失函数。 nn.MSELoss是一个比较简单的损失函数,它计算输出和目标间的均方误差,
例如
output = net(input)
target = torch.randn(10) # 随机值作为样例
target = target.view(1, -1) # 使target和output的shape相同
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
tensor(0.7265, grad_fn=<MseLossBackward>)
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 0x0000013F5B232438>
<AddmmBackward object at 0x0000013F5B232198>
<AccumulateGrad object at 0x0000013F5B232438>
反向传播
调用loss.backward()获得反向传播的误差。
但是在调用前需要清除已存在的梯度,否则梯度将被累加到已存在的梯度。
现在,我们将调用loss.backward(),并查看conv1层的偏差(bias)项在反向传播前后的梯度。
net.zero_grad() #将所有模型参数梯度置零
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.])
梯度下降(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等,PyTorch中构建了一个包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()