日期:2020.10.30
主题:PyTorch入门
内容:
-
根据PyTorch官方教程文档,学习PyTorch中神经网络
包括:定义网络、损失函数、反向传播、更新权重。 -
根据自己的理解和试验,为代码添加少量注解。
具体代码如下 ↓
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.functional as F
"""
{神经网络}
通过torch.nn包来构建神经网络
它依赖于autograd包来定义模型并对它们求导。
一个nn.Module包含各个层和一个forward(input)方法,该方法返回output。
"""
"""
一个神经网络的典型训练过程如下:
1.定义包含一些可学习参数(或者叫权重)的神经网络;
2.在输入数据集上迭代;
3.通过网络处理输入;
4.计算loss(输出和正确答案的距离);
5.将梯度反向传播给网络的参数;
6.更新网络的权重,一般使用一个简单的规则:
weight = weight - learning_rate * gradient
"""
"""
【定义网络】
"""
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 输入图像channel:1;输出channel:6;5x5卷积核
self.conv1 = nn.Conv2d(1, 6, 5) # 定义二维卷积 1 -> 6
self.conv2 = nn.Conv2d(6, 16, 5) # 定义二维卷积 6 -> 16
# an affine operation: 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):
# 2x2 Max pooling 池化操作
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 = 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
# 只需要定义forward函数,backward函数会在使用autograd时自动定义,backward函数用来计算导数。
# 可以在 forward 函数中使用任何针对张量的操作和计算
net = Net()
print(net)
print('-'*40, '\n')
"""
二维卷积
torch.nn.Conv2d(in_channels: int, out_channels: int,
kernel_size: Union[T, Tuple[T, T]])
*channel
即通道,最初是指电子图片中RGB通道这样的配色方案,
例如一张RGB图片可以用一个64x64x3的张量来表示,其中,channel=3,
分别为红色(Red)、绿色(Green)、蓝色(Blue)三个通道。
进一步,对RGB图片进行卷积操作后,根据过滤器的数量可产生更多的通道——特征图。
故,一个通道是对某个特征的检测,通道中某一处数值的强弱就是对当前特征强弱的反应。
in_channels
输入的四维张量[N, C, H, W]中的C,即输入张量的channels数。
这个形参是确定权重等可学习参数的shape所必需的。
out_channels
即期望的四维输出张量的channels数。
这里卷积层的权重和偏置初始化都是采用He初始化的,适合于ReLU函数。
*kernel
即核函数K(kernel function),指K(x, y) = <f(x), f(y)>,
其中x和y是n维的输入值,f() 是从n维到m维的映射(通常而言,m>>n)。
<x, y>是x和y的内积(inner product),亦称点积(dot product)
它有助于省去在高维空间里进行繁琐计算的“简便运算法”。
甚至,它能解决无限维空间无法计算的问题!
(因为有时f()会把n维空间映射到无限维空间)
kernel_size
即卷积核的大小,一层卷积核的中心pixel可以“看到”输入图 a*b 的区域(连通性)
一般使用5x5、3x3这种左右两个数相同的卷积核,
因此这种情况只需要写kernel_size = 5即可。
若左右两个数不同,比如3x5的卷积核,
则写作kernel_size = (3, 5),
注意需要写一个tuple,而不能写一个列表(list)。
"""
"""
线性变换(连同偏置),即 y = x * (W)^T + b
torch.nn.Linear(in_features: int, out_features: int)
in_features
输入张量的大小
out_features
输出张量的大小
"""
# 通过net.parameters()返回模型的可学习参数
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
print('-'*40, '\n')
# 尝试一个随机的32x32的输入(该网络(LeNet)的期待输入是32x32的张量)。
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
print('-'*40, '\n')
#清零所有参数的梯度缓存,然后进行随机梯度的反向传播
net.zero_grad()
out.backward(torch.randn(1, 10))
"""
torch.nn只支持小批量处理(mini-batches)。整个torch.nn包只支持小批量样本的输入,不支持单个样本的输入。
比如,nn.Conv2d 接受一个4维的张量,即nSamples x nChannels x Height x Width
如果是一个单独的样本,只需要使用input.unsqueeze(0)来添加一个“假的”批大小维度。
"""
"""
<回顾>
torch.Tensor
一个多维数组,支持诸如backward()等的自动求导操作,同时也保存了张量的梯度。
nn.Module
神经网络模块。是一种方便封装参数的方式,具有将参数移动到GPU、导出、加载等功能。
nn.Parameter
张量的一种,当它作为一个属性分配给一个Module时,它会被自动注册为一个参数。
autograd.Function
实现了自动求导前向和反向传播的定义,
每个Tensor至少创建一个Function节点,该节点连接到创建Tensor的函数并对其历史进行编码。
"""
"""
【损失函数】
函数接受一对(output, target)作为输入,计算一个值来估计网络的输出和目标值相差多少。
"""
# nn.MSELoss是比较简单的一种损失函数,计算输出和目标的均方误差(mean-squared error)。
output = net(input)
target = torch.randn(10) # 本例子中使用模拟数据
target = target.view(1, -1) # 使目标值与数据值尺寸一致
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
print('-'*40, '\n')
"""
如果使用loss的.grad_fn属性跟踪反向传播过程,会看到计算图如下
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
所以,当调用loss.backward(),整张图开始关于loss微分,
图中所有设置了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
print('-'*40, '\n')
"""
【反向传播】
我们只需要调用loss.backward()来反向传播误差。
我们需要清零现有的梯度,否则梯度将会与已有的梯度累加。
"""
# 调用loss.backward(),并查看conv1层的偏置(bias)在反向传播前后的梯度。
net.zero_grad() # 清零所有参数(parameter)的梯度缓存
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
print('-'*40, '\n')
"""
【更新权重】
最简单的更新规则是随机梯度下降法(SGD):
weight = weight - learning_rate * gradient
"""
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
# 创建优化器(optimizer)
optimizer = optim.SGD(net.parameters(), lr=0.01)
# 在训练的迭代中:
optimizer.zero_grad() # 清零梯度缓存
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # 更新参数