PyTorch基础篇:
- PyTorch基础知识 | 安装 | 张量 | 自动求导
- PyTorch主要组成模块 | 数据读入 | 数据预处理 | 模型构建 | 模型初始化 | 损失函数 | 优化器 | 训练与评估
- PyTorch主要组成模块 | hook函数 | 正则化weight decay与Dropout | 标准化
- PyTorch模型定义 | 模型容器 | 模型块 | 修改模型 | 模型读取与保存
- PyTorch进阶技巧 | 自定义损失函数 | 动态调整学习率 | 模型微调 | 半精度训练 | 使用argparse进行调参
- PyTorch可视化 | 可视化网络结构 | 使用TensorBoard可视化训练过程
一、hook函数
1.Hook函数概念
Hook
函数机制:不改变主体,实现额外功能,像一个挂件一样将功能挂到函数主体上。Hook
函数与PyTorch
中的动态图运算机制有关,因为在动态图计算,在运算结束后,中间变量是会被释放掉的,例如:非叶子节点的梯度。但是,我们往往想要提取这些中间变量,这时,我们就可以采用Hook
函数在前向传播与反向传播主体上挂上一些额外的功能(函数),通过这些函数获取中间的梯度,甚至是改变中间的梯度。PyTorch
一共提供了四种Hook
函数:
torch.Tensor.register_hook(hook)
torch.nn.Module.register_forward_hook
torch.nn.Module.register_forward_pre_hook
torch.nn.Module.register_backward_hook
一种是针对Tensor
,其余三种是针对网络的
2.四种Hook函数介绍
-
Tensor.register_hook
def register_hook(self, hook): """ 接受一个hook函数 """ ...
功能:注册一个反向传播
hook
函数,这是因为张量在反向传播的时候,如果不是叶子节点,它的梯度就会消失。由于反向传播过程中存在数据的释放,所以就有了反向传播的hook
函数-
hook
函数仅一个输入参数,为张量的梯度hook(grad) -> Tensor or None
下面,我们通过计算图流程来观察张量梯度的获取以及熟悉
Hook
函数。
y = ( x + w ) ∗ ( w + 1 ) y=(x+w)*(w+1) y=(x+w)∗(w+1)
import torch import torch.nn as nn w = torch.tensor([1.], requires_grad=True) x = torch.tensor([2.], requires_grad=True) a = torch.add(w, x) b = torch.add(w, 1) y = torch.mul(a, b) # 存储张量的梯度 a_grad = list() def grad_hook(grad): """ 定义一个hook函数,将梯度存储到列表中 :param grad: 梯度 :return: """ a_grad.append(grad) # 注册一个反向传播的hook函数,功能是将梯度存储到a_grad列表中 handle = a.register_hook(grad_hook) # 反向传播 y.backward() # 查看梯度 print("gradient:", w.grad, x.grad, a.grad, b.grad, y.grad) print("a_grad[0]: ", a_grad[0]) handle.remove()
tensor([5.]) tensor([2.]) None None None a_grad[0]: tensor([2.])
如果对叶子节点的张量使用
hook
函数,那么会怎么样呢?import torch import torch.nn as nn w = torch.tensor([1.], requires_grad=True) x = torch.tensor([2.], requires_grad=True) a = torch.add(w, x) b = torch.add(w, 1) y = torch.mul(a, b) a_grad = list() def grad_hook(grad): grad *= 2 return grad*3 handle = w.register_hook(grad_hook) y.backward() # 查看梯度 print("gradient:", w.grad, x.grad, a.grad, b.grad, y.grad) print("w.grad: ", w.grad) handle.remove()
gradient: tensor([30.]) tensor([2.]) None None None w.grad: tensor([30.])
与上面比较,发现
hook
函数相当于对已有张量进行原地操作 -
-
Module.register_forward_hook
def register_forward_hook(self, hook): ...
功能:注册
module
的前向传播hook
函数
参数:module
: 当前网络层input
:当前网络层输入数据output
:当前网络层输出数据
-
Module.register_forward_pre_hook
功能:注册module
前向传播前的hook
函数
参数:module
: 当前网络层input
:当前网络层输入数据
-
Module.register_backward_hook
功能:注册module
反向传播的hook
函数
参数:module
: 当前网络层grad_input
:当前网络层输入梯度数据grad_output
:当前网络层输出梯度数据
下面例子展示这三个hook函数
import torch
import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 2, 3)
self.pool1 = nn.MaxPool2d(2, 2)
def forward(self, x):
x = self.conv1(x)
x = self.pool1(x)
return x
def forward_hook(module, data_input, data_output):
"""
定义前向传播hook函数
:param module:网络
:param data_input:输入数据
:param data_output:输出数据
"""
fmap_block.append(data_output)
input_block.append(data_input)
def forward_pre_hook(module, data_input):
"""
定义前向传播前的hook函数
:param module: 网络
:param data_input: 输入数据
:return:
"""
print("forward_pre_hook input:{}".format(data_input))
def backward_hook(module, grad_input, grad_output):
"""
定义反向传播的hook函数
:param module: 网络
:param grad_input: 输入梯度
:param grad_output: 输出梯度
:return:
"""
print("backward hook input:{}".format(grad_input))
print("backward hook output:{}".format(grad_output))
# 初始化网络
net = Net()
# 第一个卷积核全设置为1
net.conv1.weight[0].detach().fill_(1)
# 第二个卷积核全设置为2
net.conv1.weight[1].detach().fill_(2)
# bias不考虑
net.conv1.bias.data.detach().zero_()
# 注册hook
fmap_block = list()
input_block = list()
# 给卷积层注册前向传播hook函数
net.conv1.register_forward_hook(forward_hook)
# 给卷积层注册前向传播前的hook函数
net.conv1.register_forward_pre_hook(forward_pre_hook)
# 给卷积层注册反向传播的hook函数
net.conv1.register_backward_hook(backward_hook)
# inference
fake_img = torch.ones((1, 1, 4, 4)) # batch size * channel * H * W
output = net(fake_img)
loss_fnc = nn.L1Loss()
target = torch.randn_like(output)
loss = loss_fnc(target, output)
loss.backward()
# 观察
print("output shape: {}\noutput value: {}\n".format(output.shape, output))
print("feature maps shape: {}\noutput value: {}\n".format(fmap_block[0].shape, fmap_block[0]))
print("input shape: {}\ninput value: {}".format(input_block[0][0].shape, input_block[0]))
forward_pre_hook input:(tensor([[[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]]]]),)
backward hook input:(None, tensor([[[[0.5000, 0.5000, 0.5000],
[0.5000, 0.5000, 0.5000],
[0.5000, 0.5000, 0.5000]]],
[[[0.5000, 0.5000, 0.5000],
[0.5000, 0.5000, 0.5000],
[0.5000, 0.5000, 0.5000]]]]), tensor([0.5000, 0.5000]))
backward hook output:(tensor([[[[0.5000, 0.0000],
[0.0000, 0.0000]],
[[0.5000, 0.0000],
[0.0000, 0.0000]]]]),)
output shape: torch.Size([1, 2, 1, 1])
output value: tensor([[[[ 9.]],
[[18.]]]], grad_fn=<MaxPool2DWithIndicesBackward>)
feature maps shape: torch.Size([1, 2, 2, 2])
output value: tensor([[[[ 9., 9.],
[ 9., 9.]],
[[18., 18.],
[18., 18.]]]], grad_fn=<MkldnnConvolutionBackward>)
input shape: torch.Size([1, 1, 4, 4])
input value: (tensor([[[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]]]]),)
二、正则化
1.正则化与偏差-方差分解
正则化方法是机器学习(深度学习)中重要的方法,它目的在于减小方差。下面借助周志华老师西瓜书中的对于方差、偏差的定义来进行理解。
泛化误差可分解为:偏差、方差与噪声之和。
- 偏差度量了学习算法的期望预测与真实结果的偏离程度,即刻画了学习算法本身的拟合能力
- 方差度量了同样大小的训练集的变动所导致的学习性能的变化,即刻画了数据扰动所造成的影响
- 噪声则表达了在当前任务上任何学习算法所能达到的期望泛化误差的下界
正则化方法就是减小方差的策略。常见的过拟合就会导致高方差,因此,人们常用正则化降低方差来解决过拟合。
正则化有L1正则化
与L2正则化
,通常就是损失函数加上正则项。
𝑶 𝒃 𝒋 = 𝑪 𝒐 𝒔 𝒕 + R e g u l a r i z a t i o n T e r m 𝑶𝒃𝒋 = 𝑪𝒐𝒔𝒕 + Regula riza tion Term Obj=Cost+RegularizationTerm
L1 Regularization Term: ∑ i N ∣ w i ∣ \sum_i^N|w_i| ∑iN∣wi∣
L2 Regularization Term: ∑ i N w i 2 \sum_i^Nw_i^2 ∑iNwi2
关于L1正则化与L2正则化面试中常涉及到如下问题:百面机器学习—13.L1正则化与稀疏性
2.pytorch中的L2正则项—weight decay(权值衰减)
加入L2正则项后,目标函数:
O b j = L o s s + λ 2 ∗ ∑ i N w i 2 w i + 1 = w i − ∂ O b j ∂ w i = w i ( 1 − λ ) − ∂ L o s s ∂ w i Obj=Loss + \frac{\lambda}{2}*\sum_i^Nw_i^2\\w_{i+1}=w_i-\frac{\partial{Obj}}{\partial{w_i}}=w_i(1-\lambda)-\frac{\partial{Loss}}{\partial{w_i}} Obj=Loss+2λ∗i∑Nwi2wi+1=wi−∂wi∂Obj=wi(1−λ)−∂wi∂Loss
0 < λ < 1 0<\lambda<1 0<λ<1,L2正则项又称为权值衰减。
下面通过一个小例子来试验weight decay
。
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.utils.tensorboard import SummaryWriter
n_hidden = 200
max_iter = 2000
disp_interval