torch.nn.utils

torch.nn.utils(nn/utils/)

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

torch.nn.utils(nn/utils/)

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

2、init.py

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

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

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

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的输入输出序列做处理。

发布了943 篇原创文章 · 获赞 136 · 访问量 33万+

猜你喜欢

转载自blog.csdn.net/weixin_36670529/article/details/105299285