【问题标题】:Cannot Obtain Similar DL Prediction Result in Pytorch C++ API Compared to Python与 Python 相比,Pytorch C++ API 无法获得相似的 DL 预测结果
【发布时间】:2019-08-27 02:58:31
【问题描述】:

我已经使用 unet 架构训练了一个深度学习模型,以便在 python 和 pytorch 中分割核。我想加载这个预训练模型并用 C++ 进行预测。为此,我获得了跟踪文件(带有 pt 扩展名)。然后,我运行了这段代码:

#include <iostream>

#include <torch/script.h> // One-stop header.

#include <iostream>
#include <memory>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>


using namespace cv;


int main(int argc, const char* argv[]) {


    Mat image;
    image = imread("C:/Users/Sercan/PycharmProjects/samplepyTorch/test_2.png", CV_LOAD_IMAGE_COLOR);

    std::shared_ptr<torch::jit::script::Module> module = torch::jit::load("C:/Users/Sercan/PycharmProjects/samplepyTorch/epistroma_unet_best_model_trace.pt");
    module->to(torch::kCUDA);


    std::vector<int64_t> sizes = { 1, 3, image.rows, image.cols };
    torch::TensorOptions options(torch::ScalarType::Byte);
    torch::Tensor tensor_image = torch::from_blob(image.data, torch::IntList(sizes), options);
    tensor_image = tensor_image.toType(torch::kFloat);
    auto result = module->forward({ tensor_image.to(at::kCUDA) }).toTensor();


    result = result.squeeze().cpu();
    result = at::sigmoid(result);

    cv::Mat img_out(image.rows, image.cols, CV_32F, result.data<float>());

    cv::imwrite("img_out.png", img_out);

}

图像输出(第一张图:测试图,第二张图:Python预测结果,第三张图:C++预测结果):

如您所见,C++ 预测输出与 python 预测输出不同。您能提供解决此问题的解决方案吗?

【问题讨论】:

    标签: c++ machine-learning deep-learning computer-vision pytorch


    【解决方案1】:

    即使问题很老,它也可能对某些人有用。这个答案基于pytorch 1.5.0 版本(和第一个稳定的C++ 前端版本),这种情况在以前的版本中可能会有所不同(尽管1.4.0+ 可以使用相同的IIRC)。

    PyTorch C++ 前端代码

    • 如果您只想指定torch::from_blob 中的类型,则无需显式创建torch::TensorOptions 对象。检查 PyTorch 笔记中的Configuring Properties of Tensor,这将进一步清除它。基本上,你可以使用torch::ScalarType::Byte
    • 这种类型相当于torch::kUInt8,更容易在文档 IMO 中找到
    • 无需创建std::vector 对象来保持形状,因为torch::from_blob 具有IntArrayRef 类型的第二个参数,这是ArrayRef&lt;int64_t&gt;typedef(请参阅ArrayRef documentation)。反过来,这个类有多个重载的构造函数,其中一个采用std::initializer_list(这正是你的{ 1, 3, image.rows, image.cols }

    考虑到所有这些,您可以像这样在一行中创建tensor_image(添加auto,因为返回的类型很明显,const,因为在同一行):

    const auto tensor_image =
        torch::from_blob(image.data, {1, 3, image.rows, image.cols},
                         torch::kUInt8)
            .toType(torch::kFloat);
    

    实际错误

    OpenCV 以BGR(蓝-绿-红)格式加载图像,而 PyTorch 通常使用RGB(比如torchvision 中的Python)。解决方案是排列您的image,以便颜色匹配。

    包括上面的改动,整个代码变成:

    const auto tensor_image =
        torch::from_blob(image.data, {1, 3, image.rows, image.cols},
                         torch::kUInt8)
            .toType(torch::kFloat)
            .permute(0, 3, 2, 1);
    

    你现在应该对你的预测没问题。也许获得tensor &gt; 0 而不是sigmoid 会是有益的,因为它可能是二进制分类,并且本身不需要此操作。

    其他 PyTorch 相关的东西

    不再需要使用 atATen - 如文档、基础张量和数学运算库中所述)命名空间,因为 torch:: 命名空间重定向到它。

    更清晰、更容易混淆的选项是:

    • torch::kCUDA 而不是 at::kCUDA
    • torch::sigmoid 而不是 at::sigmoid

    此外,.data&lt;T&gt; 已被弃用,而支持 .data_ptr&lt;T&gt;

    总而言之,您很少需要使用与 torch:: 不同的命名空间,它是子命名空间。

    【讨论】:

    • 是的,实际报错与频道顺序有关。使用 permute 函数后,我得到了类似的结果。感谢您的详细解释。
    【解决方案2】:

    一般情况下,unet 的输出是(batch, classes, height, width),其中classes 指的是最终掩码中的段类。这意味着每个像素都有一个关联的概率向量,以昏暗1 表示,应该在这个维度上使用softmax 激活它,以便它们总和为1。之后,您可以在同一维度上使用argmax 来获取每个像素的最可能类别。在您的情况下,这只是两个类之一——对象或背景。

    如果你有任何机会使用 FastAI 来训练你的模型,你可以看看here。这是一个查找,它根据训练期间使用的损失函数映射应该在最后一层使用的激活函数。 Unet 使用cross_entropy_loss 损失函数。

    【讨论】:

    • 您好,欢迎来到 StackOverflow,感谢您的回答!为什么你认为softmax 是必要的?它是二进制图像分割(正如第二张图像中的matplotlib 输出所看到的那样),它是二进制的。大于 0 的张量值(简单的 tensor &gt; 0)可能更好,但 AFAIK cv::Mat 构造函数将 [0-1] 范围转换为像素值 [0-255]
    • 因为 OP 使用 unet 将分割减少为分类问题。在二进制分割中有两个类。应该使用softmax,以便所有输出层中每个像素的值总和为 1,因为它们是概率。之后,argmax 将为每个像素(在本例中为对象/背景)提供最可能的类。
    • 在二进制分割中,您只有一个 [0, 1] 掩码。如果有两个值,sigmoidsoftmax 的简化版本,在PyTorch 中,它用于单个值。 sigmoid 也隐含地与 1 相加(只有正类有值(例如 0.3),在这种情况下,负类等于 1-0.3)。这是为每个像素完成的。如果您在0.5 处设置sigmoid 的阈值,您将得到binary mask OP 之后(或者您可以在0 处设置阈值logits 而无需任何激活)。
    • 而且我不确定将分割减少到分类问题意味着什么。分割是分类(可以是二进制或多类),但如您所说,在像素域中。如果它是一个多类情况,则需要形状为(batch, classes, width, height) 的张量,在这种情况下,softmax(或简单的argmax)沿着1 暗淡就可以了。但他的 Python 图像显示binary 分类,而不是多类。
    • 好的,不知道,我正在使用 FastAI 的一般实现。就像你说的 if 它是以这种方式实现的。我认为您对交换的图像通道是正确的,这里就是这种情况,但在一般情况下,使用sigmoid 进行激活是不正确且违反直觉的。当我现在查看 OP 的代码时,我认为他们使用 sigmoid 只是为了在输出图像中获得正确的范围,它与结果解释没有任何关系,但我们必须询问 OP: )
    猜你喜欢
    • 1970-01-01
    • 2011-02-21
    • 1970-01-01
    • 1970-01-01
    • 2021-07-19
    • 1970-01-01
    • 1970-01-01
    • 2021-07-16
    • 2017-06-15
    相关资源
    最近更新 更多