美文网首页
Pytorch distiller库问题整理

Pytorch distiller库问题整理

作者: conson_wm | 来源:发表于2018-09-07 16:33 被阅读0次

项目主页
文档地址

1. Invoked with: OperatorExportTypes.RAW, False

  在使用ditiller库中的apputils.SummaryGraph出现的问题
https://github.com/lanpa/tensorboardX/issues/190
  这个问题只出现在0.4.1, 将torch版本退回到0.4.0就ok了

2. distiller源码中msglogger/tflogger/pylogger怎么理解和使用

  这三个logger中msglogger是RootLogger类, tflogger是Tens项目主页orBoardLogger类, pylogger是PythonLogger类
  msglogger是logging.getLogger()产生的Logger对象, 它是日志输出的主类, config_pylogger是将以文件形式(logging.conf)存储配置的logger信息载入这个主类, 这点可以具体看一下Python中logging模块的具体用法以及Effortless Logging: A deep dive into the logging module, 通俗来讲就是怎么显示log信息在logging.conf已经定义好了, 然后需要一个具体的对象Loggers来完成logger的动作, 这个对象就是msglogger
  前面说了Logger类是整个logger功能的载体, 一般用getLogger()方法创建, 而TensorBoardLogger和PythonLogger是distiller中定义的两个Logger的子类, 用来将msglogger的log信息具体的装在控制台/文档(pylogger)和TensorBoard(tflogger)中, 这两个子类在./distiller/data_loggers/logger.py中定义
  在使用的时候, 可以先把log信息通过msglogger.info装在msglogger中, 然后通过distiller.log_training_progress()或者distiller.log_weights_sparsity把不同形式的log信息再装到tflogger和pylogger中, 也就是说msglogger可以理解为在程序执行的内部装log信息的载体, 而送出去要靠pylogger和tflogger这两个载体以不同的形式把log信息存储下来, 具体的过程可以看一下./example/sr_model/solver.py也就是我们自己改的那个训练过程文件

3. distiller log中产生的Sensitivity的表格的各列意义是什么?

Parameters in Distiller log

  在distiller的项目主页中, 是这样解释这几列的Learning both Weights and Connections for Efficient Neural Networks

  The first column is the parameter name, followed by its shape, the number of non-zero elements (NNZ) in the dense model, and in the sparse model. The next set of columns show the column-wise, row-wise, channel-wise, kernel-wise, filter-wise and element-wise sparsities. Wrapping it up are the standard-deviation, mean, and mean of absolute values of the elements

  前4列分别是parameter name, 以一个layer为单位, shape是layer的shape, convolution的话是4维,

  • column-wise, 从列上看的sparsity, 主要是针对fc层而言的, 从distiller的项目主页上看, column-wise和row-wise就是针对fc层的structured pruning
    Distiller Feature set
  • row-wise, 和column-wise类似, 就是从行上看的sparsity, 也是针对fc层的structured pruning
  • channel-wise(Ch), 这是从input channel角度去看的sparsity, 就是input的某一个或某几个feature map不用了, 比如32x16x3x3, 如果有16个input feature map用不到了, 就变成了16x16x3x3, Ch(%)=16/32=50%
  • kernel-wise(2D), 这是从2D kernel的角度去看有多少个kernel不用了, 比如刚才这个例子32x16x3x3稀疏到16x16x3x3, 从channel-wise看, 是50% sparsity, 从kernel-wise看, 还是50% sparsity, 因为也少了50%个3x3 kernel, 下面这个图是项目主页上给出的channel-wise Group Lasso regularization的结果
    channel-wise Group Lasso regularization sparsity
    从图上我们可以看到, 如果只做channel-wise regulation, 那Ch/2D的sparsity是一样的, 并且和最后的Fine也一样
  • filter-wise(3D), 从output filter的角度看的sparsity, 就是看少了多少output filters, 比如32x16x3x3, 如果要产生50% 3D, 就是32x8x3x3, 将输出filter减少一半
  • element-wise(Fine), 这是non-structured sparsity的体现, 所谓element就是一个个的Weight, 这个就是看稀疏掉了多少Weight, 从Parameter in Distiller log(这张图是Distiller文档给的一个例子)我们可以看出来, Ch/2D/3D稀疏都很低, 这些是structured sparsity, 但是Fine很高, 这就说明稀疏的来源主要是non-structured sparsity, 因为这张图是sensitivity pruning产生的稀疏效果

4. Sparsity Analysis

5. Sparsity Pruning

  Sparsity pruner还是根据weighting的大小来进行pruning, 根据阈值来pruning掉less sensitive的connection.

pruning function

  关键在于设定的sparsity的百分比和这个λ阈值是什么关系
  对C4SRCNN, 我们给的sparsity的百分比是这样:

pruners:
  pruner1:
    class: 'SensitivityPruner'
    sensitivities:
      'first_part.0.weight': 0.25
      'shrink.0.g1conv1.weight': 0.25
      'shrink.0.dwconv.weight': 0.25
      'shrink.0.g1conv2.weight': 0.25
      'mid_part.0.g1conv1.weight': 0.25
      'mid_part.0.dwconv.weight': 0.25
      'mid_part.0.g1conv2.weight': 0.25
      'mid_part.1.g1conv1.weight': 0.25
      'mid_part.1.dwconv.weight': 0.25
      'mid_part.1.g1conv2.weight': 0.25
      'mid_part.2.g1conv1.weight': 0.25
      'mid_part.2.dwconv.weight': 0.25
      'mid_part.2.g1conv2.weight': 0.25
      'mid_part.3.g1conv1.weight': 0.25
      'mid_part.3.dwconv.weight': 0.25
      'mid_part.3.g1conv2.weight': 0.25
      'last_part.0.weight': 0.25

  文档中这样描述sparsity pruning的做法

We use the standard deviation of the weights tensor as a sort of normalizing factor between the different weights tensors. For example, if a tensor is Normally distributed, then about 68% of the elements have an absolute value less than the standard deviation (σ) of the tensor. Thus, if we set the threshold to s∗σ, then basically we are thresholding s∗68% of the tensor elements.

λ=s*σl where σl is the std of layer l as measured on the dense model

  这个68%是一个标准差的位置, 也就是说, 根据我们设定的s, 确定阈值λ的方法是找这一层weighting分布68%(一个标准差)位置σ, 然后s*σ就是我们要pruning的阈值λ, λ以下的weighting要被pruning掉, 因为每个layer weighting分布不同, weighting的个数不同, 所以实际pruning掉的比例会有差别

normal distribution

深蓝色区域是距平均值小于一个标准差之内的数值范围。在正态分布中,此范围所占比率为全部数值之68%,根据正态分布,两个标准差之内的比率合起来为95%;三个标准差之内的比率合起来为99%

  Sparsity pruning的理论基础来源于Han Song的Learning both Weights and Connections for Efficient Neural Networks, 具体是怎么做的我们可以参照这篇blog

  要点是分成三个步骤

  • Train Connectivity: 按照正常方法训练初始模型。作者认为该模型中权重的大小表征了其重要程度
  • Prune Connection: 将初始模型中那些低于某个阈值的的权重参数置成0(即所谓剪枝)
  • Re-Train: 重新训练,以期其他未被剪枝的权重能够补偿pruning带来的精度下降

  为达到满意的压缩比例和精度要求, 2和3要重复多次, 具体的做法就是Han Song的博士论文Efficient Methods and Hardware for Deep Learning中给出的这幅图, 注意剪枝后并不是这些weighting就freeze了, 而是全部重新训练, 把刚才位置的再重新置0, 所以置0位置是Train Connectivity之后就知道的, 因为Mask只算一次, 而其他位置的weighting要经过iteration再重新训练

pruning process

6. Thinning

  在做完结构稀疏之后, 虽然权重稀疏了, 但是模型结构并没有改变, 比如32x32x3x3的layer, 通过filter_regular稀疏了一半的filter, 就变成32x16x3x3, 但是模型还是32x32x3x3, 只不过里面有一半都是0, Thinning要做的事情就是从"物理"上剪掉那些已经被pruning的结构, 让模型定义变成32x16x3x3

  文档中这样描述 Network Thinning

Following the pruning, we want to "physically" remove the pruned filters from the network, which involves reconfiguring the Convolutional layers and the parameter tensors. When we remove filters from Convolution layer n we need to perform several changes to the network: 1. Shrink layer n's weights tensor, leaving only the "important" filters. 2. Configure layer n's .out_channels member to its new, smaller, value. 3. If a BN layer follows layer n, then it also needs to be reconfigured and its scale and shift parameter vectors need to be shrunk. 4. If a Convolution layer follows the BN layer, then it will have less input channels which requires reconfiguration and shrinking of its weights.

  模型中Thinning写的比较死, 只能针对VGG/ResNet等少数几个网络, 并且特定输入(cifar:3x32x32, ImageNet:3x224x224), 而且不支持upsampling, deconvolution以及RNN架构, 所以基本上用不了

  我们可以暂时不考虑做thinning, 看平台的需要做决定, 如果只是稀疏就可以, 硬件可以根据权重自己省掉这些0权重的操作, 那thinning做与不做意义不大. 但是要明白thinning是怎么做的, 方便将来自己改

network thinning流程

  因为pytorch没法像tensorflow一样把architecture和weighting存在一个.pd文件中, 在inference的时候可以完全不需要模型定义文件, pytorch在inference的时候必须要从模型定义文件model.py中获取architecture, 从.pth文件中获取weighting, 那要在thinning的时候改模型定义文件不太现实, Distiller的做法是在存权重的时候多存一个thinning_recipe成员, 这个recipe中文是处方的意思, 就是我怎么来剪掉不需要的结构, 那在有这个recipe的情况下, 我在创建架构的时候还是dense network的architecture, 但是根据这个recipe我就可以把载入的model的架构直接改成sparse network

  具体的work flow就是上面那张图, 蓝色的model表示dense network, 红色的表示sparse network, 在载入权重和执行thinning的时候, 会经过execute_thinning_recipe()操作, model在经过这个操作前后结构就会改变

  我们可以compress_classifier.py来看一下这个过程, 参数是

    args.data = '../../../data.cifar10'
    args.arch = 'resnet20_cifar'
    args.lr = 0.1
    args.epochs = 250
    args.print_freq = 50
    args.workers = 1
    args.resume = '../ssl/checkpoints/checkpoint_trained_channel_regularized_resnet20.pth.tar'
    args.compress = '../ssl/ssl_channels-removal_finetuning.yaml'
    args.deterministic = True

  观察在载入权重load_checkpoint()调用的distiller.execute_thinning_recipe_list(){最终调用的还是execute_thinning_recipe()}前后, model的变化
  在载入权重的时候, 做了两件事情, ①在model中加入thinning_recipe成员;②改变model的结构, 下面这张图就是源码里面表示这两个过程


  thinning_recipe就是定义我要怎么改变model的方法, 拿model.module.layer2.1.conv举个例子, 这个layer我pruning之后, 只有20 input ch, 20 output ch

  看一下execute_thinning_recipe()前, model在这层的样子

  然后是执行后model的样子

  所以, 执行过这个操作以后, 模型就被改掉了, 那这个时候用export_onnx也就是转sparse network, 执行thinning的操作我们可以看下test_prunning.py中的distiller.remove_channels(), 理论上经过remove_channels()之后, model就应该是sparse network, inference的时候model(input)可以正常处理, 但是这里跑的好像有bug, 回头再看一下

相关文章

网友评论

      本文标题:Pytorch distiller库问题整理

      本文链接:https://www.haomeiwen.com/subject/qxicgftx.html