onnx模型转 ncnn 模型全连接层输出shape不对问题解决

1.简述

最近在把paddleocr 中cls分类模型通过ncnn部署框架部署时,发现onnx -> ncnn 模型的转换过程中出现问题。因为之前的项目都是使用ncnn框架部署的,只能去解决模型转换问题了。

2. 问题描述与分析

模型在onnx推理代码上正常,当把模型转换为ncnn模型之后,发现模型最后的softmax输出的维度不对。模型的为5个分类,onnx的输出为[1, 5] 是正确的,但是跑ncnn模型发现输出为 [1, 8, 5] ,经过打印log分析,是 softmax层前面的 fc层导致的问题。

分析:

通过Netron工具可视化模型,fc层前面的输入维度为 A: [1, 200],权重B的维度为 B: [200, 5],偏置 C为 C: [5]。我们再看到Gemm层的属性,transA = 0, transB = 0,表示矩阵A和B都不需要转置操作。此时全连接操作是可以直接计算的:

A * B + C,对应的维度为 [1, 200] * [200, 5] + [5]  =>  [1, 5] 输出维度正确

当把模型转换为ncnn模型时:

看到onnx2ncnn.cpp文件中关于Gemm 这个op的判断,代码中

(alpha == 1.f && beta == 1.f && transA == 0 && transB == 1) 的值为true时,才会跑 InnerProduct-like A * B + C 的流程,否则会进入 gemm矩阵乘法的流程。这样看来我的ncnn模型推理结果不正确 是因为fc层中的transB = 0,导致跑到了gemm的执行流程,没有跑到InnerProduct的执行流程。

跑到gemm矩阵乘法流程为什么输出的维度就不正确呢,我想原因还是矩阵乘法的计算问题,计算的维度:

A * B = [1, 200] * [5, 200] ,这个矩阵是不能正常计算的,由于维度不同不能直接计算,但是在执行代码的时候没出错的原因,可能是由于gemm分块计算的原因,然后最后输出为 [1, 8, 5]

3. 解决方法

在onnx2ncnn.cpp代码中加入 transB = 0 时,也跑InnerProduct的流程,然后在代码中把矩阵 B 的数据进行转置一下就可以了。

else if (alpha == 1.f && beta == 1.f && transA == 0 && transB == 0)
{
    // InnerProduct-like A * B + C
    const onnx::TensorProto& B = weights[node.input(1)];
    const onnx::TensorProto& C = weights[node.input(2)];

    fprintf(pp, " 0=%d", get_tensor_proto_data_size(C));
    fprintf(pp, " 1=1");
    fprintf(pp, " 2=%d", get_tensor_proto_data_size(B));

    int weight_data_size = get_tensor_proto_data_size(B);
    int num_output = B.dims(B.dims_size() - 1);
    int num_input = weight_data_size / num_output;

    int quantize_tag = 0;
    fwrite(&quantize_tag, sizeof(int), 1, bp);

    // reorder num_input-num_output to num_output-num_input
    {
        const float* bptr = B.has_raw_data() ? (const float*)B.raw_data().data() : B.float_data().data();

        for (int j = 0; j < num_output; j++)
        {
            for (int k = 0; k < num_input; k++)
            {
                float vb = bptr[k * num_output + j];
                fwrite(&vb, sizeof(float), 1, bp);
            }
        }
    }
    fwrite_tensor_proto_data(C, bp);

}

然后再重新编译一下代码,重新转换一下模型就正常了。

至此,问题终于得以解决,弄了好多天才解决问题,实属不易啊。

猜你喜欢

转载自blog.csdn.net/u012505617/article/details/131002019