2-计算图、autograd机制、损失函数和优化器

Pytorch中的模块类 nn.Module

一个基于模块类的简单线性回归类的实现

import torch
import torch.nn as nn

class LinearModel(nn.Module):
    """输入x的维度是(1,ndim)"""
    def __init__(self,ndim):
        self.weights = nn.Parameter(torch.randn(ndim,1))
        self.bias = nn.Parameter(torch.randn(1))
    
    def forward(self,x):
        return x.mm(self.weights)+self.bias

常用方法:

方法名 功能
named_parametersparameters 获取模型参数,返回结果是生成器,可以直接传入优化器
traineval 切换训练和测试状态,dropout层和BN层在测试和训练时有所不同
named_bufferbuffer 显示计算图中缓存的张量,例如BN层中的Mean和Std
named_childrenchildren 模型的子模块
apply 传入函数/匿名函数,对模块类递归的调用该函数,见下文

apply方法应用案例

import torch
import torch.nn as nn

# 注意,torch.no_grad既可以做上下文管理器,也可以做装饰器
@torch.no_grad()
def init_weights(m):
    """将所有的线性层的参数设置为1.0"""
    print(m)
    if type(m) == nn.Linear:
        m.weight.fill_(1.0)
        print(m.weight)

net = nn.Sequential(nn.Linear(2,2),nn.Linear(2,2))
net.apply(init_weights)

PyTorch的计算图和自动求导机制

PyTorch的计算图是动态图,即每次反向传播后会自动释放前向传播的计算图,每次前向传播不得不重新规划计算图,这点与TensorFlow正好相反,但是两者在数据结构上,同属于有向无环图(DAG),其结点就是张量,分为三种类型:

  1. 创建张量时指定requires_grad=True,会被自动作为叶子结点(leaf node)添加到计算图,可通过a.is_leaf查看,一般有两种指定方法,a = torch.tensor(3,4,requires_grad=True)或者a = torch.tensor(3,4).requires_grad_(),其梯度会保存在grad属性中;
  2. 计算图开始的节点(需指定)称作根节点
  3. 其余未特殊指定的张量,会根据其是否在 根节点和叶子节点 的运算链条上被自动标记 是否需要计算梯度,可称作 未标记的中间节点,其梯度会保存在buffer中随用随空;

PyTorch如何实现计算图上节点的跟踪

autograd根据用户对variable的操作构建其计算图。对变量的操作抽象为Function,节点默认不需要求导,即requires_grad属性默认为False,如果某一个节点requires_grad被设置为True,那么所有依赖它的节点requires_grad都为True。

计算图通过张量自带的grad_fn方法,携带计算图信息,该方法的next_functions可跟踪该节点的下一个/下一批节点;张量的梯度信息则会被存储在grad属性中。

在使用grad_fn.next_functions进行溯源时,需要求导的leaf_node会具有AccumulateGrad标识,这表明它们的梯度会随着backward的增加而不断累加(前提是retain_graph=True)

torch.autograd模块

PyTorch提供了自动求导的包torch.autograd用以简化繁琐的计算图求解,主要用到的是两个函数

  1. torch.autograd.backward 反向传播

    传入两个参数,根节点张量+对应形状的根节点初始梯度,如果根节点是标量(tensor.scalar),则默认初始梯度等于1;然后会计算 根节点 和 所有叶子节点 之间的梯度;结果存储在叶子节点的grad属性中。

    注意:

    1. 每次反向传播后,计算图会被自动释放,需重新forward之后才能backward,如果想要保存forward的计算图,可设置retain_graph=True

    2. 反向传播也是一个计算过程,也可以得到对应的计算图,可设置create_graph=True,这在求高阶导数时会用到

    叶子节点的梯度会累积,需要手动清空;中间节点的梯度会作为buffer被自动释放。

    不过一般不会使用torch.autograd.backward(z,1),而是使用z.backward(1),如果z是标量,默认缺省参数grad_variables为1。

  2. torch.autograd.grad

    传入 根节点和特定的叶子节点,该方法不会改变叶子节点的grad属性,而是直接返回结果。

    同理,可设置是否释放forward计算图 和 是否保留backward计算图

如何避免计算图上节点的跟踪

有些节点,我们不想其参加求导,可以设置比reqiure_grad优先级更高的volatile属性,默认为False,如果被设置为True,则所有依赖它的节点的volatile属性都为True,即不需要求导。

测试推理时,我们一般不需要打开autograd,此时可以将代码放在上下文管理器torch.no_grad()的作用域内,或者使用torch.set_grad_enabled(False),或者使用装饰器@torch.no_grad()

有时候,我们也想只修改个别leaf node的值,但是不想被autograd跟踪,可以选择修改张量的data属性,例如a.data.sigmoid_()可以避免被跟踪;有时候我们只是想查看/统计个别leaf node的值,可以使用b=a.detach() b.mean()

上述只是涉及到查看/修改叶子节点,而中间节点的梯度信息随用随空,如果想查看中间节点的梯度信息,有两种方法

  1. 利用torch.autograd.grad(z,y),隐式调用z.backward()

  2. 注册hook【推荐】

    # 第二种方法:使用hook
    # hook是一个函数,输入是梯度,不应该有返回值
    def variable_hook(grad):
        print('y的梯度:',grad)
    
    x = t.ones(3, requires_grad=True)
    w = t.rand(3, requires_grad=True)
    y = x * w
    # 注册hook
    hook_handle = y.register_hook(variable_hook)
    z = y.sum()
    z.backward()
    
    # 除非你每次都要用hook,否则用完之后记得移除hook
    hook_handle.remove()
    
    

Pytorch 损失函数和优化器

损失函数

有两种损失函数,一个是torch.nn.functional中的函数,一个是torch.nn中的模块,两者基本等价。

损失函数因为要从多维归约到一维的标量,要么是求和,要么是取平均,后者居多。

二分类常用损失函数

Loss function 功能
torch.nn.BCELoss 二分类交叉熵损失函数,接受的一般是sigmoid的输出,第一个参数是正分类标签的概率值,第二个参数是以0为负样本标签,1为正样本标签的target,注意都必须是 浮点型
torch.nn.BCEWithLogitsLoss 带对数的二分类交叉熵损失函数,相当于是torch.nn.BCELosssigmoid的结合,但是又做了进一步优化,以及增加了一个pos_weight参数,用来在precision和recall之间取一个trade-off。
BCEWithLogitsLoss — PyTorch 1.10.0 documentation

多分类常用损失函数

Loss function 功能
torch.nn.NLLLoss 负对数似然函数,预测值 需要 经过softmax然后取对数,目标值是 one-hot编码
torch.nn.CrossEntropyLoss If provided, the optional argument weight should be a 1D Tensor assigning weight to each of the classes. This is particularly useful when you have an unbalanced training set.

ℓ ( x , y ) = L = { l 1 , … , l N } ⊤ ℓ(x,y)=L=\{l _1,…,l _N\} ^⊤ (x,y)=L={ l1,,lN}
l n = − w y n l o g e x p ( x n , y n ) ∑ c = 1 C e x p ( x n , c ) ⋅ 1 { y n ≠ i g n o r e i n d e x } l_n = -w_{yn}log{\frac{exp(x_{n,yn})}{\sum_{c=1}^{C}exp(x_{n,c})}}\cdot1\{y_n\neq {ignore_index}\} ln=wynlogc=1Cexp(xn,c)exp(xn,yn)1{ yn=ignoreindex}

其中的 ℓ ( x , y ) ℓ(x,y) (x,y)可以根据reduction是’mean’还是’sum’确定归约形式,C表示总分类数,weight可设置不同类别的权重,可以应用在数据不均衡的案例中。

CrossEntropyLoss — PyTorch 1.10.0 documentation

优化器

demo: 数据集是Boston的房价,共有13个特征

import torch
from torch._C import dtype
import torch.nn as nn
from sklearn.datasets import load_boston

class LinearModel(nn.Module):
    """输入x的维度是(1,ndim)"""
    def __init__(self,ndim):
        self.weights = nn.Parameter(torch.randn(ndim,1),requires_grad=True)
        self.bias = nn.Parameter(torch.randn(1),requires_grad=True)
    
    def forward(self,x):
        return x.mm(self.weights)+self.bias

boston = load_boston()
lm = LinearModel(13)
optimizer = torch.optim.SGD(lm.parameters(),lr=1e-3)
criterion = torch.nn.CrossEntropyLoss()

train_data = torch.tensor(boston['data'],requires_grad = True,dtype = torch.float32)
target = torch.tensor(boston['target'],dtype=torch.float32)

for i in range(10000):
    predict = lm(train_data)
    loss = criterion(predict,target)
    if i%1000 == 0:
        print("loss:%.3f"%loss.item())
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

注意: 优化之前,需要先将 叶子节点的梯度清零,然后从根节点loss开始backward

当然可以可设置针对不同模块设置不同的学习率,例如

optimizer = torch.optim.SGD([
    {
    
    'params':model.base.parameters()},
    {
    
    'params':model.classifier.parameters(),'lr':1e-2}
],lr=1e-3)

常见优化器

Adagrad — PyTorch 1.10.0 documentation

RMSprop — PyTorch 1.10.0 documentation

Adam — PyTorch 1.10.0 documentation

Guess you like

Origin blog.csdn.net/weixin_43721070/article/details/120962379