THU-MIG / torch-model-compression

针对pytorch模型的自动化模型结构分析和修改工具集,包含自动分析模型结构的模型压缩算法库
MIT License
240 stars 40 forks source link

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

Open xn1997 opened 2 years ago

xn1997 commented 2 years ago

我在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()}")
gdh1995 commented 2 years ago

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

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

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

xn1997 commented 2 years ago

呃你没加载预训练的模型吧?我试了一下我的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
gdh1995 commented 2 years ago

可以参考 https://github.com/THU-MIG/torch-model-compression/blob/main/torchpruner/DOCUMENT.md#operator-注册 来写一个新算子支持解析Softplus。

gdh1995 commented 2 years ago

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

xn1997 commented 2 years ago

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

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

gdh1995 commented 2 years ago

重训练看看吧。普通分类模型确实不会掉这么多,往往是一次就一两点,分割的话我不太熟

---原始邮件--- 发件人: @.> 发送时间: 2022年1月11日(周二) 晚上8:22 收件人: @.>; 抄送: "Dahan @.**@.>; 主题: Re: [THU-MIG/torch-model-compression] demo中剪枝后预测结果差距很大? (Issue #8)

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

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

— Reply to this email directly, view it on GitHub, or unsubscribe. Triage notifications on the go with GitHub Mobile for iOS or Android. You are receiving this because you commented.Message ID: @.***>

xn1997 commented 2 years ago

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

                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库了

gdh1995 commented 2 years ago

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

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

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

qat.py的地方谢谢提醒。

xn1997 commented 2 years ago

@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量化函数,重新校准量化吗?

gdh1995 commented 2 years ago

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

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

xn1997 commented 2 years ago

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

gdh1995 commented 2 years ago

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

xn1997 commented 2 years ago

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.
gdh1995 commented 2 years ago

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

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

gdh1995 commented 2 years ago

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