THU-MIG / torch-model-compression

针对pytorch模型的自动化模型结构分析和修改工具集,包含自动分析模型结构的模型压缩算法库

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

demo中剪枝后预测结果差距很大?

xn1997 opened this issue · comments

我在prune_by_class.py程序中测试了一下剪枝前后模型预测结果的差别,发现结果差距很大,这种现象正常吗,还是说我的理解有问题。代码如下

import sys

sys.path.append("..")
import torch
import torchpruner
import torchvision
import numpy as np

# 以下代码示例了对每一个BN层去除其weight系数绝对值前20%小的层
inputs_sample = torch.ones(1, 3, 224, 224).to('cpu')
# 加载模型
model = torchvision.models.vgg11_bn()
result_source = model(inputs_sample)
# 创建ONNXGraph对象,绑定需要被剪枝的模型
graph = torchpruner.ONNXGraph(model)
##build ONNX静态图结构,需要指定输入的张量
graph.build_graph(inputs=(torch.zeros(1, 3, 224, 224),))

# 遍历所有的Module
for key in list(graph.modules):
    module = graph.modules[key]
    # 如果该module对应了BN层
    if isinstance(module.nn_object, torch.nn.BatchNorm2d):
        # 获取该对象
        nn_object = module.nn_object
        # 排序,取前20%小的权重值对应的index
        weight = nn_object.weight.detach().cpu().numpy()
        index = np.argsort(np.abs(weight))[: int(weight.shape[0] * 0.02)]
        result = module.cut_analysis("weight", index=index, dim=0)
        model, context = torchpruner.set_cut(model, result)
        if context:
            # graph 存放了各层参数和输出张量的 numpy.ndarray 版本,需要更新
            graph = torchpruner.ONNXGraph(model)  # 也可以不重新创建 graph
            graph.build_graph(inputs=(torch.zeros(1, 3, 224, 224),))

# 新的model即为剪枝后的模型
print(model)

result_prune = model(inputs_sample)
print(f"剪枝前结果:{result_source.sum()}")
print(f"剪枝后结果:{result_prune.sum()}")
print(f"数据差距{(abs(result_source-result_prune)).sum()}")

呃你没加载预训练的模型吧?我试了一下我的torchvision.models.vgg11_bn 函数的pretrained参数默认是False。那么一个随机初始化的模型的结果是没意义的,偏差大的可能性也有。

建议在一个有能看的精度的模型上做测试,然后跑一下验证集来测精度损失多少。

我机子是 torch==1.9.0+cu111 torchvision==0.10.0+cu111

呃你没加载预训练的模型吧?我试了一下我的torchvision.models.vgg11_bn 函数的pretrained参数默认是False。那么一个随机初始化的模型的结果是没意义的,偏差大的可能性也有。

建议在一个有能看的精度的模型上做测试,然后跑一下验证集来测精度损失多少。

我机子是 torch==1.9.0+cu111 torchvision==0.10.0+cu111

好的,我尝试用这个程序对YOLOv4进行剪枝,但是在这里报错,有啥快速解决的方法吗

    graph.build_graph(inputs=(torch.zeros(1, 3, self.input_shape[1],self.input_shape[0]),))
  File "/home/scut/anaconda3/envs/torch18/lib/python3.7/site-packages/torchpruner-0.0.1-py3.7.egg/torchpruner/graph.py", line 492, in build_graph
  File "/home/scut/anaconda3/envs/torch18/lib/python3.7/site-packages/torchpruner-0.0.1-py3.7.egg/torchpruner/graph.py", line 27, in create_operator
RuntimeError: Can not find operator Softplus

考虑到Softplus是逐元素操作,可以直接用 onnx_operator.onnx_pw,也就是写 operator_reg.regist("Softplus", onnx_operator.onnx_pw)

考虑到Softplus是逐元素操作,可以直接用 onnx_operator.onnx_pw,也就是写 operator_reg.regist("Softplus", onnx_operator.onnx_pw)

谢谢,已经可以使用了。我仅仅按照1%的比例剪枝,剪完后的mAP是43,而不剪的时候是93,性能掉的有点多,这情况正常吗

@gdh1995 我目前尝试使用QAT对目标检测模型进行量化,参考的例子https://github.com/THU-MIG/torch-model-compression/blob/main/examples/torchslim/pytorch-cifar/qat.py 目前遇到了以下两个问题:
1.由于目标检测与分类输出有所区别,其标签格式是list[ndarry, ndarry,...]因此将

return_list.append(item.to(device))
改了以下内容,确保可以运行成功。

                if isinstance(item, (tuple, list)):
                    item = [ann.to(device) for ann in item]
                    return_list.append(item)
                else:
                    return_list.append(item.to(device))

程序可以正常运行,但是gpu显存不停的增加,当训练多个iteration时,就会发生内存不足,不知道是哪里出现了问题?
2.在把训练集调的很小的情况下,使其在没有发生内存不足时就开始保存模型,但是遇到了如下错误:

Saving model...
Traceback (most recent call last):
  File "/media/scut/dataD/XZY/Gitee/yolov4-pytorch/QAT_train.py", line 268, in <module>
    solver.run()
  File "/home/scut/anaconda3/envs/torch17/lib/python3.9/site-packages/torchpruner-0.0.1-py3.9.egg/torchslim/slim_solver.py", line 431, in run
  File "/home/scut/anaconda3/envs/torch17/lib/python3.9/site-packages/torchpruner-0.0.1-py3.9.egg/torchslim/quantizing/qat.py", line 148, in save_model
  File "/home/scut/anaconda3/envs/torch17/lib/python3.9/site-packages/torchpruner-0.0.1-py3.9.egg/torchslim/quantizing/qat_tools.py", line 316, in export_onnx
  File "/home/scut/anaconda3/envs/torch17/lib/python3.9/site-packages/torchpruner-0.0.1-py3.9.egg/torchslim/quantizing/qat_tools.py", line 223, in onnx_post_process
RuntimeError: the number of children node must be 1

这个又是什么原因呢?感觉我只是更改了模型,例子中的分类模型就正常保存,而目标检测模型就报这个错了

另外,https://github.com/THU-MIG/torch-model-compression/blob/main/torchslim/quantizing/qat.py ,代码中忘记导入os库了

_sample_to_device改的没问题;显存增加有很多可能的原因,首先要测测不剪枝或者量化的时候会不会增加。

比如,也许是calculate_loss_function 里缓存了什么?PyTorch会追踪tensor是经过了哪些算子得出的,所以如果forward的输出被缓存下来了,那每次迭代都记录的很多东西都跟着被缓存了,最后就肯定爆显存。

下边那个RuntimeError 可以被跳过去,我提交了新代码,你更新下。

qat.py的地方谢谢提醒。

@gdh1995 谢谢~

我其实是将https://github.com/bubbliiiing/yolov4-pytorch/blob/a046ae47a01ccaae9ee8c7ea936ef311c74ce766/utils/utils_fit.py#L6 函数拆成了qat中所需要的钩子函数,具体内容如下。在原程序中,不进行剪枝和量化进行训练时,显存很稳定,但是写到qat中就会显存爆掉。

        def predict_function(model, data):
            images, targets = data
            outputs = model(images)
            return outputs


        def calculate_loss(predict, data):
            _, targets = data
            loss_value_all = 0
            num_pos_all = 0
            # ----------------------#
            #   计算损失
            # ----------------------#
            for l in range(len(predict)):
                loss_item, num_pos = yolo_loss(l, predict[l], targets)
                loss_value_all += loss_item
                num_pos_all += num_pos
            loss_value = loss_value_all / num_pos_all
            return loss_value

        def evaluate(predict, data):
            _, targets = data
            loss_value_all = 0
            num_pos_all = 0
            # ----------------------#
            #   计算损失
            # ----------------------#
            for l in range(len(predict)):
                loss_item, num_pos = yolo_loss(l, predict[l], targets)
                loss_value_all += loss_item
                num_pos_all += num_pos
            loss_value = loss_value_all / num_pos_all
            return {"negative_loss": -loss_value}


        def dataset_generator():
            return train_dataset, val_dataset

还有一个问题,模型保存成功了,但是我看trt文件和原始模型大小差不多,所以还是以fp32精度保存的trt模型嘛?那要是想进行int8推理,是使用TensorRT以fp32精度读取trt模型,然后再使用TensorRT内置的int8量化函数,重新校准量化吗?

你能跑完2个batch吗?还是只跑了1个batch就崩出来了?

  • 如果是只能跑1个,那就是batch太大了,显存不够反向传播用
  • 如果跑了好几个batch才错,那才可能是泄露
  • 如果是跑完1个epoch就报错,可能是eval的batch太大了,显存不够跑验证集的

torchslim/quantizing/qat_tools.py#export_trt 这里已经设置了 trt.BuilderFlag.INT8,应该是int8保存的。大小差不多的原因不太清楚,更可能的是tensorrt自己存了很多算子优化融合的编译结果之类的东西。测一下速度就知道了,int8和fp32速度上大概会有区别的。

@gdh1995 是跑了好几个batch才出错的,跑的过程中,可以看出来每个运行一个batch,显存就增加几兆,最后就炸了

evaluate 有点问题,最后返回的时候,需要把tensor转成python float,你改成 {"...": -loss_value.item()}试试

evaluate 有点问题,最后返回的时候,需要把tensor转成python float,你改成 {"...": -loss_value.item()}试试

显存看起来不会一直增加了。但是出了另一个问题,就是在训练完一个epoch,进入evaluate时,显存炸了,如下,似乎是突然大量申请内存导致内存爆炸了,这是正常现象吗?

| epoch:0 | iteration:485 | negative_loss:-1.1794 | loss:1.1794 | 
| epoch:0 | iteration:486 | negative_loss:-1.1790 | loss:1.1790 | 
evaluating...
| epoch:0 | iteration:1 | negative_loss:-0.6776 | loss:0.6776 | 
Traceback (most recent call last):
  File "/media/scut/dataD/XZY/Gitee/yolov4-pytorch/QAT_train.py", line 268, in <module>
.........
RuntimeError: CUDA out of memory.

正常吧,eval的时候也需要大量显存,建议改小一点batch size重新试,有时候连train的batch size也要改小以留出空间。

可以用很小的数据集来测,比如train 5个batch就算一个epoch,然后就做eval,这样测起来快。

Softplus 的问题也是项目里有个地方弄错了,写成SoftPlus了。刚提交了代码修正它。现在可以不额外注册onnx op了。