前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >torch.nn.utils

torch.nn.utils

作者头像
狼啸风云
修改2022-09-02 22:33:38
1K0
修改2022-09-02 22:33:38
举报
文章被收录于专栏:计算机视觉理论及其实现

torch.nn.utils(nn/utils/)

1、先看一下utils目录下的文件

包括3个文件 init.py, rnn.py, clip_grad.py, weight_norm.py 这里面是一些nn的工具,比如rnn中的序列打包成PackedSequence和解包还原成程度不等序列

2、init.py

代码语言:javascript
复制
from . import rnn
from .clip_grad import clip_grad_norm
from .weight_norm import weight_norm, remove_weight_norm
#三句分别从当前目录的三个文件当中导入需要的函数或者类
#下面先看clip_grad.py

3、clip_grad.py

代码语言:javascript
复制
def clip_grad_norm(parameters, max_norm, norm_type=2):
     #修剪可迭代Parameters的梯度范数
     #范数由所有梯度共同计算得到, 把它们看做一个向量。
     #梯度被in-place operation修改。
     #参数:
     #parameters (Iterable[Variable]): 要进行梯度归一化的可迭代的
     #                                 变量Variable
     #max_norm (float or int):         梯度的最大范数
     #norm_type (float or int):        p范数类型'inf' 代表无穷范数.
     #返回值:
     #所有参数的范数 (看成一个向量).
     parameters = list(filter(lambda p: p.grad is not None, parameters))
     max_norm = float(max_norm)
     norm_type = float(norm_type)
     if norm_type == float('inf'):
          total_norm = max(p.grad.data.abs().max() for p in parameters)      #无穷范数||X||inf = max(|Xi|)
     else:
          total_norm = 0
          for p in parameters:
               param_norm = p.grad.data.norm(norm_type)                              
               # tensor.norm(p)   计算p范数
               total_norm += param_norm ** norm_type
          total_norm = total_norm ** (1. / norm_type)                                     
          #||X||p = Σ(Xi ** p) ** (1/p)
     clip_coef = max_norm / (total_norm + 1e-6)                                          
     #防止total_norm等于0
     if clip_coef < 1:
          for p in parameters:
               p.grad.data.mul_(clip_coef)
     return total_norm

这个函数的作用是归一化p范数到max_norm,使得parameters的参数的p范数和为max_norm。默认为2范数,返回值为所有参数梯度的p范数  Gradient Clipping的引入是为了处理gradient explosion或者gradients vanishing的问题。  当在一次迭代中权重的更新过于迅猛的话,很容易导致loss divergence。Gradient Clipping的直观作用就是让权重的更新限制在一个合适的范围。所以经常在一个epoch之后加入clip_grad_norm在max_norm范围内。

4、rnn.py

代码语言:javascript
复制
from collections import namedtuple                         
#namedtuple创建一个tuple对象,具备tuple的不变性,可以根据属性来引用。
import torch
from torch.autograd import Variable

PackedSequence_ = namedtuple('PackedSequence', ['data', 'batch_sizes'])

class PackedSequence(PackedSequence_):
     pass
     #这个类包含数据和由每一序列长度的batch_size大小组成的列表
     #所有的RNN都可以接收这种序列作为输入,这种序列是没有补零的,
     #即一个batch中每个样本的长度可以不一致
     # 说明:
     # 这个类的实例不能被创建,只能在`pack_padded_sequence`中创建.
     # 参数:
     #data (Variable):           包含打包序列的Variable
     #batch_sizes (list[int]): 每一个序列长度的batch_size大小所组成的list
     #这个类为之后的补零对齐序列长度的输入打包做准备。目的是为了去掉这些零。RNN可以接收这个类的数据作为输入。

def pack_padded_sequence(input, lengths, batch_first=False):
     #将输入长度不等进行补零后的序列进行pack,即把0去掉。pack理解为压缩数据
     #输入的大小为 ``TxBx*``T是最长序列的序列长度(equal to lengths[0]
     #因为需要按序列长度降序排列)
     #B 是 batch size,大小,* 是包括0在内的任意维,一般是特征维度. 
     #如果 ``batch_first``=True,那么
     #输入就是`BxTx*``,即第一维是batch_size大小。输入需要按照序列
     #长度大小降序排列。
     # 说明:
     #函数只接受最少二维的输入。可以用来打包标签,也可以将使用他们的
     #RNN输出直接计算loss。
     #A Variable可以直接访问PackedSequence的成员data得到。
     #参数:
     #input (Variable):    不同长度补零后的序列.
     #lengths (list[int]):  每个序列长度真实长度组成的list列表
     #batch_first (bool, optional): if True, the input is expected in BxTx* format.
     #返回值:
     #一个`PackedSequence`对象
     if lengths[-1] <= 0:
          raise ValueError("length of all samples has to be greater than 0, " "but found an element in 'lengths' that is <=0")
     if batch_first:
          input = input.transpose(0, 1)
     steps = []
     batch_sizes = []
     lengths_iter = reversed(lengths)  #变成从小到大排列,迭代类型
     current_length = next(lengths_iter)    #遍历下一个元素
     batch_size = input.size(1)
     if len(lengths) != batch_size:
          raise ValueError("lengths array has incorrect size")
     for step, step_value in enumerate(input, 1):           #enumerate(input,1)下标从1开始
          steps.append(step_value[:batch_size])            
          #得到对应索引下的值,即位置(指的是特征在序列的位置)为1的数据
          batch_sizes.append(batch_size)                       
          #batch_sizes保存每个位置的batch大小,第一个位置肯定每个batch都有
          #直到得到最短的序列的位置,都是每个batch都有,所以从current_length
          #开始出现batch_size发生变化
          while step == current_length:
               try:
                    new_length = next(lengths_iter)  #得到下一个长度
               except StopIteration:
                    current_length = None     #迭代完毕得到空
                    break
               if current_length > new_length:                         
               #因为输入是降序排列,reversed之后是升序排列防止输入错误提出异常
                    raise ValueError("lengths array has to be sorted in decreasing order")
               batch_size -= 1               
               #batch_size长度减一, 其实是减去每一种长度的个数,一种长度
               #有n个,就有n个输入序列,即n个样本
               current_length = new_length                            
               #更新current_length,如果长度不变,继续减一
          if current_length is None:       #迭代完毕结束循环
               break
     return PackedSequence(torch.cat(steps), batch_sizes)  
     #返回由step数据和batch_sizes组成的PackedSequence实例
     #这个函数解除了RNN输入需要序列长度需要相等的限制,在补零使得序列长度相等后对数据进行压缩,根据真实的序列长度提取
     #数据,并按照每个位置的batch大小保存结果以便经过RNN后还原成长度相等的序列进行loss的计算。即下一个函数。

def pad_packed_sequence(sequence, batch_first=False, padding_value=0.0):
     #与上一个函数进行相反的操作,给定一个packedSequence,进行padding操作
     #,可以设置默认的padding值为0,即进行补零操作。返回结果为3维Varaible, 
     #TxBx*,T是最长序列的长度,B 是batch size. 如果batch_first== True, 
     #输出数据不转置为BxTx* 格式.
     #输出会按照length的长度进行降序排列.
     #参数:
     #sequence (PackedSequence):     batch to pad
     #batch_first (bool, optional):  if True, 输出会设置为 BxTx*格式
     #padding_value (float, optional): padding的值,默认为0
     #返回值:
     #元组变量包括Variable padded sequence 和每个样本的序列长度
     var_data, batch_sizes = sequence
     max_batch_size = batch_sizes[0]                               
     #从位置0开始填batch,所以batch_size 是越来越小
     output = var_data.data.new(len(batch_sizes), max_batch_size, *var_data.size()[1:]).fill_(padding_value)
     #输出tensor全部填充padding_value
     output = Variable(output)

     lengths = []
     data_offset = 0
     prev_batch_size = batch_sizes[0]
     for i, batch_size in enumerate(batch_sizes):
          output[i, :batch_size] = var_data[data_offset:data_offset + batch_size]
          data_offset += batch_size                                    
          #上一个函数的输出是concat是一个一维向量所以按照batch_size剪开就行
          dec = prev_batch_size - batch_size
          if dec > 0:                                                      
          #dec=0,说明该位置还没有到达最短长度
               lengths.extend((i,) * dec)                        
               #i即为位置,即序列的长度,dec表示该长度也有多少个样本,
               #所以extend((i)*dec)
          prev_batch_size = batch_size
     lengths.extend((i + 1,) * batch_size)                
     #添加序列最长的样本,个数为batch_size个,batch_size为降序,
     #在循环中未能添加
     lengths.reverse()                                              
     #长度反转  输出长度降序排列,是为了之后使用上一个函数
     if batch_first:
          output = output.transpose(0, 1)
     return output, lengths
     #这个函数是为了解压,得到padding的规整的三维tensor数据,可以用来计算loss等。
     #如果在RNN中间加入batch_norm或者线性层,就不要不断地使用这两个函数来进行Variable的变换。

5、weight_norm.py

代码语言:javascript
复制
from torch.nn.parameter import Parameter
def _norm(p, dim):
     #计算除了dim维度之外所有维度的2范数
     if dim is None:
          return p.norm()
     elif dim == 0:                                                                          
     #保持0维不变,计算其他维数的范数
          output_size = (p.size(0),) + (1,) * (p.dim() - 1)
          return p.contiguous().view(p.size(0), -1).norm(dim=1).view(output_size)  
          #先化为p.size(0) * n 计算范数后化为原来维度
     elif dim == p.dim() - 1:
          output_size = (1,) * (p.dim() - 1) + (p.size(-1),)                       
          #保持最后一维不变
          return p.contiguous().view(-1, p.size(-1)).norm(dim=0).view(*output_size)
     else:
          return _norm(p.transpose(0, dim), 0).transpose(0, dim)       
          #其他情况将dim维转置到0维,迭代该函数计算范数再转置回来

class WeightNorm(object):
     def __init__(self, name, dim):
          self.name = name
          self.dim = dim

     def compute_weight(self, module):
          #得到module的name值计算norm
          g = getattr(module, self.name + '_g')             
          #getattr获取类的属性
          v = getattr(module, self.name + '_v')
          return v * (g / _norm(v, self.dim))

     @staticmethod
     def apply(module, name, dim):
          fn = WeightNorm(name, dim)
          weight = getattr(module, name)
          # remove w from parameter list
          del module._parameters[name]
          # add g and v as new parameters and express w as g/||v|| * v
          module.register_parameter(name + '_g', Parameter(_norm(weight, dim).data))   #二范数大小,即模大小数据
          module.register_parameter(name + '_v', Parameter(weight.data))                #weight本身的数据
          #给module._parameters添加weight_g和weight_v 其为OrderedDict类型
          setattr(module, name, fn.compute_weight(module))
          # recompute weight before every forward()
          module.register_forward_pre_hook(fn)
          #每次计算forward(input)前,都会重新计算fn(imodule,input),
          #即调用__call__计算归一化weight
          return fn

     def remove(self, module):
          weight = self.compute_weight(module)
          delattr(module, self.name)
          del module._parameters[self.name + '_g']
          del module._parameters[self.name + '_v']
          module.register_parameter(self.name, Parameter(weight.data))

     def __call__(self, module, inputs):
          #该类的实例化对象被调用时执行该函数,比如实例化module之后,
          #调用先执行了__call__,再调用forward进行前向传播
          setattr(module, self.name, self.compute_weight(module))       
          #setattr给类的属性幅值,不存在则创建

def weight_norm(module, name='weight', dim=0):
     #给定一个module,对参数parameter进行权重归一化
  .  #w = g*v/||v||    g*v的单位向量
     #权重归一化是一种参数重新初始化来减弱权重在它方向上的模大小。
     #理解为使用小的权重来防止过拟合。它由参数名字为name的两个参数决定,
     #一个是决定大小的weight_g,一个是决定方向的weight_v权重归一化使用
     #过hook来在每个forward()前向传播之前从大小和方向重新计算权重tensor。
     #默认情况下,dim=0,对每个输出channel范数计算是独立的。
     #如果计算这个权重,dim=None
     #参数:
     #module (nn.Module): containing module
     #name (str, optional): 权重参数的名称
     #dim (int, optional):  需要计算范数的维度
     #返回值:
     # The original module with the weight norm hook
     #Example::
     # >>> m = weight_norm(nn.Linear(20, 40), name='weight')
     # Linear (20 -> 40)
     #>>> m.weight_g.size()
     # torch.Size([40, 1])
     # >>> m.weight_v.size()
     # torch.Size([40, 20])
     WeightNorm.apply(module, name, dim)
     return module

def remove_weight_norm(module, name='weight'):
     #从一个module中一处权重归一化
     #参数:
     #module (nn.Module): containing module
     #name (str, optional): name of weight parameter
     #Example:
     # >>> m = weight_norm(nn.Linear(20, 40))
     # >>> remove_weight_norm(m)
     for k, hook in module._forward_pre_hooks.items():
          if isinstance(hook, WeightNorm) and hook.name == name:             
          #移除weight权重归一化对象hook
               hook.remove(module)
               del module._forward_pre_hooks[k]
               return module
     raise ValueError("weight_norm of '{}' not found in {}"
                      .format(name, module))

整个文件定义了一个WeightNorm类来实现每次前向传播之前对weight进行归一化处理,将类加入到forward_hook中实现。  归一化主要是在相应的维度上进行了模大小的规整,方向保持不变。防止过拟合。  与clip_grad不同的是clip_grad是规整了反方向传播梯度的大小,限制在一定范围,防止梯度爆炸或者梯度消失问题  rnn.py主要是为RNN的输入输出序列做处理。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/04/03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • torch.nn.utils(nn/utils/)
    • 1、先看一下utils目录下的文件
      • 2、init.py
        • 3、clip_grad.py
          • 4、rnn.py
            • 5、weight_norm.py
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档