小白学Pytorch系列--Torch.nn API Utilities(18)

小白学Pytorch系列–Torch.nn API Utilities(18)


From the torch.nn.utils module

方法注释
clip_grad_norm_剪辑参数迭代对象的梯度范数。
clip_grad_value_在指定值处剪辑参数的可迭代对象的梯度。
parameters_to_vector将参数转换为一个向量
vector_to_parameters将一个向量转换为参数
prune.BasePruningMethod抽象基类,用于创建新的修剪技术。
prune.PruningContainer容器,包含用于迭代修剪的修剪方法序列。
prune.Identity实用修剪方法,不修剪任何单位,但生成带有一掩码的修剪参数化。
prune.RandomUnstructured在一个张量中随机修剪(当前未修剪)单位。
prune.L1Unstructured通过将l1范数最低的单位归零来修剪(当前未修剪)张量中的单位。
prune.RandomStructured在一个张量中随机修剪整个(当前未修剪的)通道。
prune.LnStructured在一个张量中根据ln范数修剪整个(目前未修剪的)通道。
prune.CustomFromMask
prune.identity对模块中名为name的参数对应的张量应用剪枝重参数化,而不实际剪枝任何单位。
prune.random_unstructured通过随机移除指定数量的(当前未修剪的)单元,修剪模块中名为name的参数对应的张量。
prune.l1_unstructured通过删除指定数量的(当前未修剪的)具有最低l1范数的单元,修剪模块中名为name的参数对应的张量。
prune.random_structured通过沿着随机选择的指定dim删除指定数量的(当前未修剪的)通道,修剪模块中名为name的参数对应的张量。
prune.ln_structured删除模块中参数name对应的张量,方法是沿着指定的dim以最低ln范数删除指定数量的(当前未修剪的)通道。
prune.global_unstructured采用指定的剪枝方法对参数中所有参数对应的张量进行全局剪枝。
clipprune.custom_from_mask_grad_norm_通过在掩码中应用预先计算的掩码,修剪模块中参数name对应的张量。
prune.remove从模块中移除修剪重参数化,从前向钩子中移除修剪方法。
prune.is_pruned通过查找继承自BasePruningMethod的模块中的前向预钩子来检查模块是否被修剪了。
weight_norm对给定模块中的参数应用权重归一化。
remove_weight_norm从模块中移除权重归一化重新参数化。
spectral_norm对给定模块中的参数应用光谱归一化。
remove_spectral_norm从模块中移除光谱归一化重新参数化。
skip_init给定一个模块类对象和参数,实例化模块而不初始化参数。

clip_grad_norm_

参考:https://blog.csdn.net/zhaohongfei_358/article/details/122820992
torch.nn.utils.clip_grad_norm_(parameters, max_norm, norm_type=2)。三个参数:

  • parameters:希望实施梯度裁剪的可迭代网络参数
  • max_norm:该组网络参数梯度的范数上限
  • norm_type:范数类型

重要的事情:clip_grad_norm_要放在backward和step之间。


从上面文章可以看到,clip_grad_norm最后就是对所有的梯度乘以一个clip_coef,而且乘的前提是clip_coef一定是小于1的,所以,按照这个情况:clip_grad_norm只解决梯度爆炸问题,不解决梯度消失问题

对源码进行了一些修改,将.grad去掉,增加了一些输出,方便进行实验:

import numpy as np
import torch
from torch import nn

def clip_grad_norm_(parameters, max_norm, norm_type=2):
    if isinstance(parameters, torch.Tensor):
        parameters = [parameters]
    parameters = list(filter(lambda p: p is not None, parameters))
    max_norm = float(max_norm)
    norm_type = float(norm_type)
    if norm_type == np.inf:
        total_norm = max(p.data.abs().max() for p in parameters)
    else:
        total_norm = 0
        for p in parameters:
            param_norm = p.data.norm(norm_type)
            total_norm += param_norm.item() ** norm_type
        total_norm = total_norm ** (1. / norm_type)
    clip_coef = max_norm / (total_norm + 1e-6)
    if clip_coef < 1:
        for p in parameters:
            p.data.mul_(clip_coef)
            
    print("max_norm=%s, norm_type=%s, total_norm=%s, clip_coef=%s" % (max_norm, norm_type, total_norm, clip_coef))

测试模型: 参考来自https://blog.csdn.net/zhaohongfei_358/article/details/122820992

class TestModel(nn.Module):
    
    def __init__(self):
        super().__init__()
        
        self.model = nn.Sequential(
            nn.Linear(1,1, bias=False),
            nn.Sigmoid(),
            nn.Linear(1,1, bias=False),
            nn.Sigmoid(),
            nn.Linear(1,1, bias=False),
            nn.Sigmoid(),
            nn.Linear(1,1, bias=False),
            nn.Sigmoid(),
        )
    
    def forward(self, x):
        return self.model(x)

model = TestModel()

定义好模型后,固定一下模型参数:

for param in model.parameters():
    param.data = torch.Tensor([[0.5]])
    print("param=%s" % (param.data.item()))

-------------------------------------------
param=0.5
param=0.5
param=0.5
param=0.5

可以看目前四个线性层的权重参数都为0.5。之后对模型进行一轮训练,并进行反向传播:

criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1)
predict_y = model(torch.Tensor([0.1]))
loss = criterion(predict_y, torch.Tensor([1]))
model.zero_grad()
loss.backward()

反向传播过后,再次打印模型参数,可以看到反向传播后计算好的各个参数的梯度:

for param in model.parameters():
    print("param=%s, grad=%s" % (param.data.item(), param.grad.item()))
----------------------
param=0.5, grad=-3.959321111324243e-05
param=0.5, grad=-0.0016243279678747058
param=0.5, grad=-0.014529166743159294
param=0.5, grad=-0.11987950652837753

重点来了,各个参数的梯度如上图所示(越靠近输入的位置,梯度越小,虽然没有出现梯度爆炸,反而出现了梯度消失,但不影响本次实验),现在对其进行梯度裁剪:

nn.utils.clip_grad_norm_(model.parameters(), max_norm=0.01208, norm_type=2)
-----------------------
tensor(0.1208)

在上面,我传入的max_norm=0.01208,而total_norm=0.1208,所以可得clip_coef=0.1,即所有的梯度都会缩小一倍,此时我们再打印一下梯度:

for param in model.parameters():
    print("param=%s, grad=%s" % (param.data.item(), param.grad.item()))
------------------------------------
param=0.5, grad=-3.960347839893075e-06
param=0.5, grad=-0.00016247491294052452
param=0.5, grad=-0.001453293371014297
param=0.5, grad=-0.01199105940759182

看到没,所有的梯度都减小了10倍。之后我们执行step()操作,其就会将进行param=param-lr*grad操作来进行参数更新。再次打印网络参数:

optimizer.step()

for param in model.parameters():
    print("param=%s, grad=%s" % (param.data.item(), param.grad.item()))

--------------------------------------
param=0.5000039339065552, grad=-3.960347839893075e-06
param=0.5001624822616577, grad=-0.00016247491294052452
param=0.5014532804489136, grad=-0.001453293371014297
param=0.5119910836219788, grad=-0.01199105940759182

可以看到,在执行step后,执行了param=param-grad操作(我设置的lr为1)。同时,grad并没有清0,所以这也是为什么要显式的调用zero_grad的原因。

clip_grad_value_

在指定值处剪辑参数的可迭代对象的梯度。


torch.nn.utils.clip_grad_value_(parameters, clip_value)

parameters_to_vector

将参数转换为一个向量

for lg in model_params:
   if len(lg) != 0:
       mp = parameters_to_vector([param.data.float() for param in lg])
       mp = torch.nn.Parameter(mp, requires_grad=True)

vector_to_parameters

将一个向量转换为参数

prune.BasePruningMethod

关于剪支的官方教程 https://pytorch.org/tutorials/intermediate/pruning_tutorial.html

  • pytorch提供了一个剪枝的抽象基类‘‘class BasePruningMethod(ABC)’,所有剪枝策略都需要继承该基类,并重载部分函数就可以了
  • 一般情况下需要重载init和compute_mask,apply方法,call, apply_mask, apply, prune和remove不需要重载,例如官方提供的
  • 剪枝的API接口,可以看到支持用户自定义的剪枝mask,接口为custom_from_mask
  • API的实现,使用classmethod的方法,剪枝策略的实例化在框架内部完成,不需要用户实例化
  • remove接口 pytorch还提供各类一个remove接口,目的是把之前的剪枝结果持久化,具体操作就是删除之前生成的跟剪枝相关的缓存或者是回调hook接口,设置被剪枝的name参数(如bias)为最后一次训练的值


参考来自:
剪枝的大致过程:https://blog.csdn.net/m00102981/article/details/106479957/

  1. 根据用户选择的剪枝API生成对应的策略实例,此时会判断需要做剪枝操作的module上是否已经挂有前向回调函数,没有则生成新的,有了就在老的上面添加,并且生成PruningContainer。从这里可以看出,对于同一个module使用多个剪枝策略时,pytorch通过PruningContainer来对剪枝策略进行管理。PruningContainer本身也是继承自BasePruningMethod。同时设置前向计算的回调,便于后续训练时调用。
  2. 接着根据用户输入的module和name,找到对应的参数tensor。如果是第一次剪枝,那么需要生成_orig结尾的tensor,然后删除原始的module上的tensor。如name为bias,那么生成bias_orig存起来,然后删除module.bias属性。
  3. 获取defaultmask,然后调用method.computemask生成当前策略的mask值。生成的mask会被存在特定的缓存module.register_buffer(name + “_mask”, mask)。这里的compute_mask可能是两种情况:如果只有一个策略,那么调用的时候对应剪枝策略的compute_mask方法,如果一个module有多个剪枝策略组合,那么调用的应该是PruningContainer的compute_mask
  4. 执行剪枝,保存剪枝结果到module的属性,注册训练时的剪枝回调函数,剪枝完成。新的mask应用在orig的tensor上面生成新的tensor保存的对应的name属性

prune.PruningContainer

容器,包含用于迭代修剪的修剪方法序列。跟踪应用修剪方法的顺序,并处理组合连续的修剪调用。

prune.Identity

实用修剪方法,不修剪任何单位,但生成带有一掩码的修剪参数化。
参数:
module(torch.nn.Module) -包含要修剪的張量的模塊。
name(str) -module 中的參數名稱,將對其進行修剪。

prune.RandomUnstructured

在一个张量中随机修剪(当前未修剪)单位

prune.L1Unstructured

通过将l1范数最低的单位归零来修剪(当前未修剪)张量中的单位。

prune.RandomStructured

在一个张量中随机修剪整个(当前未修剪的)通道。

prune.LnStructured

在一个张量中根据ln范数修剪整个(目前未修剪的)通道。

prune.CustomFromMask

增加前向预挂钩,可以动态剪枝,并根据原始张量和剪枝掩码重新参数化张量。

prune.identity

对模块中名为name的参数对应的张量应用剪枝重参数化,而不实际剪枝任何单位。通过在适当的位置修改模块(并返回修改后的模块)

>>> m = prune.identity(nn.Linear(2, 3), 'bias')
>>> print(m.bias_mask)
tensor([1., 1., 1.])

prune.random_unstructured

通过随机移除指定数量的(当前未修剪的)单元,修剪模块中名为name的参数对应的张量。通过在适当的位置修改模块(并返回修改后的模块)

>>> m = prune.random_unstructured(nn.Linear(2, 3), 'weight', amount=1)
>>> torch.sum(m.weight_mask == 0)
tensor(1)

prune.l1_unstructured

通过删除指定数量的(当前未修剪的)具有最低l1范数的单元,修剪模块中名为name的参数对应的张量。通过在适当的位置修改模块(并返回修改后的模块)

>>> m = prune.l1_unstructured(nn.Linear(2, 3), 'weight', amount=0.2)
>>> m.state_dict().keys()
odict_keys(['bias', 'weight_orig', 'weight_mask'])

prune.random_structured

通过沿着随机选择的指定dim删除指定数量的(当前未修剪的)通道,修剪模块中名为name的参数对应的张量。就地修改模块(并返回修改后的模块)

>>> m = prune.random_structured(
...     nn.Linear(5, 3), 'weight', amount=3, dim=1
... )
>>> columns_pruned = int(sum(torch.sum(m.weight, dim=0) == 0))
>>> print(columns_pruned)
3

prune.ln_structured

删除模块中参数name对应的张量,方法是沿着指定的dim以最低ln范数删除指定数量的(当前未修剪的)通道。就地修改模块(并返回修改后的模块)

>>> from torch.nn.utils import prune
>>> m = prune.ln_structured(
...     nn.Conv2d(5, 3, 2), 'weight', amount=0.3, dim=1, n=float('-inf')
... )

prune.global_unstructured

采用指定的剪枝方法对参数中所有参数对应的张量进行全局剪枝。通过修改模块

>>> from torch.nn.utils import prune
>>> from collections import OrderedDict
>>> net = nn.Sequential(OrderedDict([
...     ('first', nn.Linear(10, 4)),
...     ('second', nn.Linear(4, 1)),
... ]))
>>> parameters_to_prune = (
...     (net.first, 'weight'),
...     (net.second, 'weight'),
... )
>>> prune.global_unstructured(
...     parameters_to_prune,
...     pruning_method=prune.L1Unstructured,
...     amount=10,
... )
>>> print(sum(torch.nn.utils.parameters_to_vector(net.buffers()) == 0))
tensor(10)

prune.custom_from_mask

通过在掩码中应用预先计算的掩码,修剪模块中参数name对应的张量。就地修改模块(并返回修改后的模块)

>>> from torch.nn.utils import prune
>>> m = prune.custom_from_mask(
...     nn.Linear(5, 3), name='bias', mask=torch.tensor([0, 1, 0])
... )
>>> print(m.bias_mask)
tensor([0., 1., 0.])

prune.remove

从模块中移除修剪重参数化,从前向钩子中移除修剪方法。名称为name的已修剪参数将保持永久修剪,名称为name+‘_orig’的参数将从参数列表中删除。类似地,命名为name+’_mask’的缓冲区将从缓冲区中移

>>> m = random_unstructured(nn.Linear(5, 7), name='weight', amount=0.2)
>>> m = remove(m, name='weight')

prune.is_pruned

通过查找继承自BasePruningMethod的模块中的前向预钩子来检查模块是否被修剪了。

>>> from torch.nn.utils import prune
>>> m = nn.Linear(5, 7)
>>> print(prune.is_pruned(m))
False
>>> prune.random_unstructured(m, name='weight', amount=0.2)
>>> print(prune.is_pruned(m))
True

weight_norm

对给定模块中的参数应用权重归一化。

>>> m = weight_norm(nn.Linear(20, 40), name='weight')
>>> m
Linear(in_features=20, out_features=40, bias=True)
>>> m.weight_g.size()
torch.Size([40, 1])
>>> m.weight_v.size()
torch.Size([40, 20])

remove_weight_norm

从模块中移除权重归一化重新参数化。

>>> m = weight_norm(nn.Linear(20, 40))
>>> remove_weight_norm(m)

spectral_norm

对给定模块中的参数应用光谱归一化。
Spectral Normalization的做法就很简单了: 将神经网络的每一层的参数 W W W 作 SVD 分解,然后将其最大的奇异值限定为1, 具体地,在每一次更新 W W W 之后都除以 W W W 最大的奇异值。 这样,每一层对输入 x x x最大的拉伸系数不会超过 1。

经过 Spectral Norm 之后,神经网络的每一层 g l ( x ) g_l(x) gl(x),都满足

>>> m = spectral_norm(nn.Linear(20, 40))
>>> m
Linear(in_features=20, out_features=40, bias=True)
>>> m.weight_u.size()
torch.Size([40])

remove_spectral_norm

从模块中移除光谱归一化重新参数化。

>>> m = spectral_norm(nn.Linear(40, 10))
>>> remove_spectral_norm(m)

skip_init

>>> import torch
>>> m = torch.nn.utils.skip_init(torch.nn.Linear, 5, 1)
>>> m.weight
Parameter containing:
tensor([[0.0000e+00, 1.5846e+29, 7.8307e+00, 2.5250e-29, 1.1210e-44]],
       requires_grad=True)
>>> m2 = torch.nn.utils.skip_init(torch.nn.Linear, in_features=6, out_features=1)
>>> m2.weight
Parameter containing:
tensor([[-1.4677e+24,  4.5915e-41,  1.4013e-45,  0.0000e+00, -1.4677e+24,
          4.5915e-41]], requires_grad=True)
### PyTorch Padding Usage and Examples Padding is an essential operation when working with convolutional neural networks (CNNs) to ensure that the spatial dimensions of feature maps remain consistent or are adjusted as required. In PyTorch, padding can be applied through various methods depending on where it's needed. #### Using `nn.Conv2d` Layer With Built-in Padding The simplest way to apply padding is by specifying a padding parameter within layers such as `Conv2d`. This method automatically adds zeros around the borders of the input tensor before performing convolutions: ```python import torch.nn as nn conv_layer = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1) ``` This example sets up a 2D convolution layer with a single-pixel wide zero-padding added all around the edges[^1]. #### Functional Approach via `F.pad()` For more control over how padding should occur, one may use functional utilities provided under `torch.nn.functional`, specifically `pad()` function which allows customizing not only the size but also type of padding used: ```python from torch import Tensor import torch.nn.functional as F input_tensor: Tensor = ... # Your input data here padded_tensor = F.pad(input=input_tensor, pad=(0, 0, 1, 1), mode='constant', value=0.) ``` In this snippet, padding has been specified explicitly for each dimension; note that values must appear in pairs representing start/end paddings respectively along axes starting from last moving towards first. Here we add one row at top/bottom while leaving columns untouched. #### Custom Padding Layers Beyond built-ins, developers have flexibility creating specialized classes inheriting from base class `Module` offered by PyTorch framework itself allowing implementation-specific behaviors including non-zero constant fillers among other possibilities: ```python class ReflectionPad(nn.Module): def __init__(self, padding): super().__init__() self.padding = padding def forward(self, x): return F.pad(x, tuple([item for sublist in zip(*[(i,i)*2 for i in reversed(self.padding)]) for item in sublist]), 'reflect') ``` Here reflection-based padding logic implemented demonstrates extending core functionalities beyond what’s readily available within standard library offerings. --related questions-- 1. How does padding affect the output shape of CNN layers? 2. What types of padding modes exist besides 'constant' in PyTorch? 3. Can you provide scenarios where different kinds of padding would be beneficial during model training?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发呆的比目鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值